diff --git a/.codecov.yml b/.codecov.yml index 5a94096..4af5eb2 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/.codespell/ignore_words.txt b/.codespell/ignore_words.txt index 9757d7c..e93ee30 100644 --- a/.codespell/ignore_words.txt +++ b/.codespell/ignore_words.txt @@ -9,3 +9,11 @@ socio-economic ;; Frobenius norm used in np.linalg.norm fro + +DISCUS +discus +CONECT +Discus +Te +Nd +BU diff --git a/.flake8 b/.flake8 index 2d2cb16..7b2865c 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, @@ -5,7 +7,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/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index 0f56027..6107962 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -6,30 +6,41 @@ 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. + 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 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: -### 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 on PyPI is correct. +- [ ] Docs are deployed successfully to `https:///`. +- [ ] Successfully run all tests, tutorial examples or do functional testing. +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. -- [ ] All relevant issues in the feedstock are addressed in the release PR. +- [ ] 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 -- [ ] 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/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..1099d86 --- /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 c89ca95..caeb09c 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -4,13 +4,15 @@ 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: - 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.structure + 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 1301ca8..e585398 100644 --- a/.github/workflows/check-news-item.yml +++ b/.github/workflows/check-news-item.yml @@ -3,10 +3,10 @@ name: Check for News on: pull_request_target: branches: - - main + - main jobs: - build: - uses: Billingegroup/release-scripts/.github/workflows/_check-news-item.yml@v0 + check-news-item: + uses: scikit-package/release-scripts/.github/workflows/_check-news-item.yml@v0 with: project: diffpy.structure 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 743193f..235c9f7 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -11,8 +11,8 @@ on: workflow_dispatch: jobs: - coverage: - uses: Billingegroup/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 + matrix-coverage: + uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.structure c_extension: false diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml new file mode 100644 index 0000000..461e8ac --- /dev/null +++ b/.github/workflows/publish-docs-on-release.yml @@ -0,0 +1,12 @@ +name: Deploy Documentation on Release + +on: + workflow_dispatch: + +jobs: + docs: + uses: scikit-package/release-scripts/.github/workflows/_publish-docs-on-release.yml@v0 + with: + project: diffpy.structure + c_extension: false + headless: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index 1dd2ee1..1ac611f 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: - validate: - uses: Billingegroup/release-scripts/.github/workflows/_tests-on-pr.yml@v0 + tests-on-pr: + uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.structure c_extension: false diff --git a/.gitignore b/.gitignore index a25212e..90d4ef7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ .Python env/ build/ +_build/ develop-eggs/ dist/ downloads/ diff --git a/.isort.cfg b/.isort.cfg index e0926f4..86f162b 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,4 +1,5 @@ [settings] -line_length = 115 +# Keep import statement below line_length character limit +line_length = 79 multi_line_output = 3 include_trailing_comma = True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3070e19..70c8684 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 @@ -44,3 +44,24 @@ 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 + args: [--ignore-words=.codespell/ignore_words.txt] + # 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 - PEP 257 compliant docstring formatter + - repo: https://github.com/s-weigand/docformatter + rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + hooks: + - id: docformatter + additional_dependencies: [tomli] + args: [--in-place, --config, ./pyproject.toml] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..47f7a01 --- /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/CHANGELOG.rst b/CHANGELOG.rst index 2bf42c4..2e55583 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,7 +28,7 @@ Release Notes **Fixed:** * Add getting started section and re-arrange install success check instructions -* Added termial script for transtru app in pyproject.toml +* Added terminal script for transtru app in pyproject.toml * Changed requires-python to align with classifiers diff --git a/CODE-OF-CONDUCT.rst b/CODE_OF_CONDUCT.rst similarity index 98% rename from CODE-OF-CONDUCT.rst rename to CODE_OF_CONDUCT.rst index ff9c356..e8199ca 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/LICENSE.rst b/LICENSE.rst index 2e8d4ba..9cc6a9d 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -1,141 +1,29 @@ -OPEN SOURCE LICENSE AGREEMENT -============================= +BSD 3-Clause License -Copyright (c) 1989, 1991 Free Software Foundation, Inc. - -Copyright (c) 2006, The Regents of the University of California through Lawrence Berkeley National Laboratory - -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) 2009-2011, University of Tennessee - -Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") - -Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National Laboratory - -Copyright (c) 2024, The Trustees of Columbia University in the City of New York. +Copyright (c) 2025, The Trustees of Columbia University in the City of New York. All rights reserved. -The "DiffPy-CMI" is distributed subject to the following license conditions: - -.. code-block:: text - - SOFTWARE LICENSE AGREEMENT - - Software: DiffPy-CMI - - - (1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either - source code, or binary form and accompanying documentation). - - Part of the software was derived from the DANSE, ObjCryst++ (with permission), - PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of - which the original Copyrights are contained in each individual file. - - Each licensee is addressed as "you" or "Licensee." - - - (2) The copyright holders shown above and their third-party Licensors hereby - grant licensee a royalty-free nonexclusive license, subject to the limitations - stated herein and U.S. Government license rights. - - - (3) You may modify and make a copy or copies of the software for use within - your organization, if you meet the following conditions: - - (a) Copies in source code must include the copyright notice and this - software license agreement. - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy. - - - (4) You may modify a copy or copies of the Software or any portion of it, thus - forming a work based on the Software, and distribute copies of such work - outside your organization, if you meet all of the following conditions: - - (a) Copies in source code must include the copyright notice and this - Software License Agreement; - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy; - - (c) Modified copies and works based on the Software must carry prominent - notices stating that you changed specified portions of the Software. - - (d) Neither the name of Brookhaven Science Associates or Brookhaven - National Laboratory nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - written permission. - - - (5) Portions of the Software resulted from work developed under a U.S. - Government contract and are subject to the following license: - The Government is granted for itself and others acting on its behalf a - paid-up, nonexclusive, irrevocable worldwide license in this computer software - to reproduce, prepare derivative works, and perform publicly and display - publicly. - - - (6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT - WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY - LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND - THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING - BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL - LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF - THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE - PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION - UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. - - - (7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR - THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF - ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, - CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING - BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, - WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING - NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS - BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. - - -Brookhaven National Laboratory Notice -===================================== - -Acknowledgment of sponsorship ------------------------------ - -This software was produced by the Brookhaven National Laboratory, under -Contract DE-AC02-98CH10886 with the Department of Energy. - - -Government disclaimer of liability ----------------------------------- - -Neither the United States nor the United States Department of Energy, nor -any of their employees, makes any warranty, express or implied, or assumes -any legal liability or responsibility for the accuracy, completeness, or -usefulness of any data, apparatus, product, or process disclosed, or -represents that its use would not infringe privately owned rights. - - -Brookhaven disclaimer of liability ----------------------------------- - -Brookhaven National Laboratory makes no representations or warranties, -express or implied, nor assumes any liability for the use of this software. - - -Maintenance of notice ---------------------- - -In the interest of clarity regarding the origin and status of this -software, Brookhaven National Laboratory requests that any recipient of it -maintain this notice affixed to any distribution by the recipient that -contains a copy or derivative of this software. - -END OF LICENSE +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.rst b/README.rst index cf7020c..03d783b 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ :target: https://diffpy.github.io/diffpy.structure :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.structure +.. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.structure :target: https://pypi.org/project/diffpy.structure/ .. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.structure @@ -35,34 +35,18 @@ .. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue :target: https://github.com/diffpy/diffpy.structure/issues -Crystal structure container and parsers for structure formats. - -The diffpy.structure package provides objects for storing atomic -coordinates, displacement parameters and other crystal structure data. -diffpy.structure supports import and export of structure data in several -structure formats such as CIF, PDB, and xyz. It provides conversion -between fractional and absolute Cartesian coordinates, functions for -symmetry expansion of atom sites in the asymmetric unit and generation -of symmetry constraints for atom positions and displacement parameters. -diffpy.structure includes definitions of all space groups in over 500 -symmetry settings. +Crystal structure container and parsers for structure formats +* LONGER DESCRIPTION HERE For more information about the diffpy.structure library, please consult our `online documentation `_. 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: +If you use diffpy.structure in a scientific publication, we would like you to cite this package as - 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). + diffpy.structure Package, https://github.com/diffpy/diffpy.structure Installation ------------ @@ -107,9 +91,7 @@ You may consult our `online documentation `_ is the discussion forum for general questions and discussions about the use of diffpy.structure. Please join the diffpy.structure users community by joining the Google group. The diffpy.structure 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 `_. Feel free to fork the project and contribute. To install diffpy.structure in a development mode, with its sources being directly used by Python @@ -132,18 +114,14 @@ trying to commit again. Improvements and fixes are always appreciated. -Before contribuing, please read our `Code of Conduct `_. - -Acknowledgement ---------------- - -Space group codes in *spacegroupmod.py* and *mmlibspacegroups.py* -originate from the `pymmlib project `_. - -Less common settings of space groups were generating using the -`Computational Crystallography Toolbox `_. +Before contributing, please read our `Code of Conduct `_. Contact ------- -For more information on diffpy.structure please visit the project `web-page `_ or email Prof. Simon Billinge at sb2896@columbia.edu. +For more information on diffpy.structure please visit the project `web-page `_ or email Simon Billinge at sb2896@columbia.edu. + +Acknowledgements +---------------- + +``diffpy.structure`` is built and maintained with `scikit-package `_. diff --git a/devutils/sgtbx_extra_groups.py b/devutils/sgtbx_extra_groups.py index 4cab28c..6551bb8 100644 --- a/devutils/sgtbx_extra_groups.py +++ b/devutils/sgtbx_extra_groups.py @@ -1,10 +1,11 @@ #!/usr/bin/env python -"""Quick and extremely dirty script for generating code for SpaceGroup that -are defined in cctbx, but not in mmLib. It was used to generate module +"""Quick and extremely dirty script for generating code for SpaceGroup that are +defined in cctbx, but not in mmLib. It was used to generate module sgtbxspacegroups. -This is a utility script that should not be included with code distribution. +This is a utility script that should not be included with code +distribution. Not to be included with code distributions. """ @@ -16,7 +17,12 @@ import numpy from cctbx import sgtbx -from diffpy.structure.spacegroups import IsSpaceGroupIdentifier, SpaceGroup, SymOp, mmLibSpaceGroupList +from diffpy.structure.spacegroups import ( + IsSpaceGroupIdentifier, + SpaceGroup, + SymOp, + mmLibSpaceGroupList, +) def tupleToSGArray(tpl): diff --git a/doc/source/api/diffpy.structure.example_package.rst b/doc/source/api/diffpy.structure.example_package.rst new file mode 100644 index 0000000..3595000 --- /dev/null +++ b/doc/source/api/diffpy.structure.example_package.rst @@ -0,0 +1,31 @@ +.. _example_package documentation: + +|title| +======= + +.. |title| replace:: diffpy.structure.example_package package + +.. automodule:: diffpy.structure.example_package + :members: + :undoc-members: + :show-inheritance: + +|foo| +----- + +.. |foo| replace:: diffpy.structure.example_package.foo module + +.. automodule:: diffpy.structure.example_package.foo + :members: + :undoc-members: + :show-inheritance: + +|bar| +----- + +.. |bar| replace:: diffpy.structure.example_package.bar module + +.. automodule:: diffpy.structure.example_package.foo + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.structure.rst b/doc/source/api/diffpy.structure.rst index 95ff128..cdcc80e 100644 --- a/doc/source/api/diffpy.structure.rst +++ b/doc/source/api/diffpy.structure.rst @@ -1,7 +1,9 @@ :tocdepth: -1 -diffpy.structure package -======================== +|title| +======= + +.. |title| replace:: diffpy.structure package .. automodule:: diffpy.structure :members: @@ -12,107 +14,17 @@ Subpackages ----------- .. toctree:: - :titlesonly: - - diffpy.structure.parsers - diffpy.structure.expansion - diffpy.structure.apps + diffpy.structure.example_package Submodules ---------- -diffpy.structure.spacegroups module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.spacegroups - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure._legacy_importer module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure._legacy_importer - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.structureerrors module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.structureerrors - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.spacegroupmod module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.spacegroupmod - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.utils module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.utils - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.lattice module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.lattice - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.structure module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.structure - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.mmlibspacegroups module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.mmlibspacegroups - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.symmetryutilities module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.symmetryutilities - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.atom module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.atom - :members: - :undoc-members: - :show-inheritance: - -diffpy.structure.pdffitstructure module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: diffpy.structure.pdffitstructure - :members: - :undoc-members: - :show-inheritance: +|module| +-------- -diffpy.structure.sgtbxspacegroups module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. |module| replace:: diffpy.structure.example_submodule module -.. automodule:: diffpy.structure.sgtbxspacegroups +.. automodule:: diffpy.structure.example_submodule :members: :undoc-members: :show-inheritance: diff --git a/doc/source/conf.py b/doc/source/conf.py index f7b1c77..ab91ec0 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# diffpy.structure documentation build configuration file, created by +# diffpy.structure documentation build configuration file, created by # noqa: E501 # sphinx-quickstart on Thu Jan 30 15:49:41 2014. # # This file is execfile()d with the current directory set to its @@ -18,15 +18,21 @@ from importlib.metadata import version from pathlib import Path +# Attempt to import the version dynamically from GitHub tag. +try: + fullversion = version("diffpy.structure") +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. +# documentation root, use Path().resolve() to make it absolute, like shown here. # noqa: E501 # sys.path.insert(0, str(Path(".").resolve())) sys.path.insert(0, str(Path("../..").resolve())) sys.path.insert(0, str(Path("../../src").resolve())) # abbreviations -ab_authors = "Billinge Group members and community contributors" +ab_authors = "Billinge Group members" # -- General configuration ------------------------------------------------ @@ -43,6 +49,7 @@ "sphinx.ext.viewcode", "sphinx.ext.intersphinx", "sphinx_rtd_theme", + "sphinx_copybutton", "m2r", ] @@ -68,7 +75,6 @@ # |version| and |release|, also used in various other places throughout the # built documents. -fullversion = version(project) # The short X.Y version. version = "".join(fullversion.split(".post")[:1]) # The full version, including alpha/beta/rc tags. @@ -88,6 +94,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 +134,14 @@ # html_theme = "sphinx_rtd_theme" +html_context = { + "display_github": True, + "github_user": "TingwenZhang", + "github_repo": "diffpy.structure", + "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. @@ -221,7 +240,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ("index", "diffpy.structure.tex", "diffpy.structure Documentation", ab_authors, "manual"), + ( + "index", + "diffpy.structure.tex", + "diffpy.structure Documentation", + ab_authors, + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -249,7 +274,15 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "diffpy.structure", "diffpy.structure Documentation", ab_authors, 1)] +man_pages = [ + ( + "index", + "diffpy.structure", + "diffpy.structure Documentation", + ab_authors, + 1, + ) +] # If true, show URL addresses after external links. # man_show_urls = False diff --git a/doc/source/getting-started.rst b/doc/source/getting-started.rst new file mode 100644 index 0000000..ac7d510 --- /dev/null +++ b/doc/source/getting-started.rst @@ -0,0 +1,79 @@ +:tocdepth: -1 + +.. index:: getting-started + +.. _getting-started: + +================ +Getting started +================ + +Here are some example templates provided to help you get started with writing your documentation. You can use these templates to create your own documentation. + +Reuse ``.rst`` files across multiple pages +------------------------------------------ + +Here is how you can reuse a reusable block of ``.rst`` files across multiple pages: + +.. include:: snippets/example-table.rst + +.. warning:: + + Ensure that the ``.rst`` file you are including is not too long. If it is too long, it may be better to split it into multiple files and include them separately. + +Refer to a specific section in the documentation +------------------------------------------------ + +You can use the ``ref`` tag to refer to a specific section in the documentation. For example, you can refer to the section below using the ``:ref:`` tag as shown :ref:`here `. + +.. note:: + + Please check the raw ``.rst`` file of this page to see the exact use of the ``:ref:`` tag. + +Embed your code snippets in the documentation +--------------------------------------------- + +Here is how you can write a block of code in the documentation. You can use the ``code-block`` directive to write a block of code in the documentation. For example, you can write a block of code as shown below: + +.. code-block:: bash + + # Create a new environment, without build dependencies (pure Python package) + conda create -n -env python=3.13 \ + --file requirements/test.txt \ + --file requirements/conda.txt + + # Create a new environment, with build dependencies (non-pure Python package) + conda create -n -env python=3.13 \ + --file requirements/test.txt \ + --file requirements/conda.txt \ + --file requirements/build.txt + + # Activate the environment + conda activate _env + + # Install your package locally + # `--no-deps` to NOT install packages again from `requirements.pip.txt` + pip install -e . --no-deps + + # Run pytest locally + pytest + + # ... run example tutorials + +.. _attach-image: + +Attach an image to the documentation +------------------------------------ + +Here is how you attach an image to the documentation. The ``/doc/source/img/scikit-package-logo-text.png`` example image is provided in the template. + +.. image:: ./img/scikit-package-logo-text.png + :alt: codecov-in-pr-comment + :width: 400px + :align: center + + +Other useful directives +----------------------- + +Here is how you can do menu selection :menuselection:`Admin --> Settings` and display labels for buttons like :guilabel:`Privacy level`. diff --git a/doc/source/img/scikit-package-logo-text.png b/doc/source/img/scikit-package-logo-text.png new file mode 100644 index 0000000..823178d Binary files /dev/null and b/doc/source/img/scikit-package-logo-text.png differ diff --git a/doc/source/index.rst b/doc/source/index.rst index 4fabbaa..0ff4673 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -4,74 +4,49 @@ .. |title| replace:: diffpy.structure documentation -diffpy.structure - Crystal structure container and parsers for structure formats. +``diffpy.structure`` - Crystal structure container and parsers for structure formats -| Software version |release|. +| Software version |release| | Last updated |today|. -The diffpy.structure package provides objects for storing atomic -coordinates, displacement parameters and other crystal structure data. -diffpy.structure supports import and export of structure data in several -structure formats such as CIF, PDB, xyz. It provides conversion -between fractional and absolute Cartesian coordinates, functions for -symmetry expansion from asymmetric unit and generation of symmetry -constraints for atom positions and displacement parameters. diffpy.structure -includes definitions of all space groups in over 500 symmetry settings. - -======= -Authors -======= - -diffpy.structure is developed by members of the Billinge Group at -Columbia University and at Brookhaven National Laboratory including -Pavol Juhás, Christopher L. Farrow, Xiaohao Yang, Simon J.L. Billinge. - -For a detailed list of contributors see -https://github.com/diffpy/diffpy.structure/graphs/contributors. - -Acknowledgments +=============== +Getting started =============== -Space group codes in *spacegroupmod.py* and *mmlibspacegroups.py* -originate from the pymmlib project, http://pymmlib.sourceforge.net. -Less common settings of space groups were generating using the -Computational Crystallography Toolbox, -http://cctbx.sourceforge.net. - - -.. index:: citation, reference +Welcome to the ``diffpy.structure`` documentation! -Reference -========= +To get started, please visit the :ref:`Getting started ` page. -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: +======= +Authors +======= - 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). +``diffpy.structure`` is developed by Billinge Group members. The maintainer for this project is Simon Billinge. For a detailed list of contributors see +https://github.com/TingwenZhang/diffpy.structure/graphs/contributors. ============ Installation ============ -See the `README `_ +See the `README `_ file included with the distribution. +================ +Acknowledgements +================ + +``diffpy.structure`` is built and maintained with `scikit-package `_. + ================= Table of contents ================= - .. toctree:: - :titlesonly: + :maxdepth: 2 - license - release + getting-started Package API + release + license ======= Indices diff --git a/doc/source/license.rst b/doc/source/license.rst index 33a363a..5e751f7 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -7,142 +7,32 @@ License OPEN SOURCE LICENSE AGREEMENT ============================= - -Copyright (c) 1989, 1991 Free Software Foundation, Inc. - -Copyright (c) 2006, The Regents of the University of California through Lawrence Berkeley National Laboratory - -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) 2009-2011, University of Tennessee - -Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") - -Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National Laboratory - -Copyright (c) 2024, The Trustees of Columbia University in the City of New York. -All rights reserved. - -The "DiffPy-CMI" is distributed subject to the following license conditions: - -.. code-block:: text - - SOFTWARE LICENSE AGREEMENT - - Software: DiffPy-CMI - - - (1) The "Software", below, refers to the aforementioned DiffPy-CMI (in either - source code, or binary form and accompanying documentation). - - Part of the software was derived from the DANSE, ObjCryst++ (with permission), - PyCifRW, Python periodictable, CCTBX, and SasView open source projects, of - which the original Copyrights are contained in each individual file. - - Each licensee is addressed as "you" or "Licensee." - - - (2) The copyright holders shown above and their third-party Licensors hereby - grant licensee a royalty-free nonexclusive license, subject to the limitations - stated herein and U.S. Government license rights. - - - (3) You may modify and make a copy or copies of the software for use within - your organization, if you meet the following conditions: - - (a) Copies in source code must include the copyright notice and this - software license agreement. - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy. - - - (4) You may modify a copy or copies of the Software or any portion of it, thus - forming a work based on the Software, and distribute copies of such work - outside your organization, if you meet all of the following conditions: - - (a) Copies in source code must include the copyright notice and this - Software License Agreement; - - (b) Copies in binary form must include the copyright notice and this - Software License Agreement in the documentation and/or other materials - provided with the copy; - - (c) Modified copies and works based on the Software must carry prominent - notices stating that you changed specified portions of the Software. - - (d) Neither the name of Brookhaven Science Associates or Brookhaven - National Laboratory nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - written permission. - - - (5) Portions of the Software resulted from work developed under a U.S. - Government contract and are subject to the following license: - The Government is granted for itself and others acting on its behalf a - paid-up, nonexclusive, irrevocable worldwide license in this computer software - to reproduce, prepare derivative works, and perform publicly and display - publicly. - - - (6) WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT - WARRANTY OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY - LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND - THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING - BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL - LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF - THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE SOFTWARE WOULD NOT INFRINGE - PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION - UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL BE CORRECTED. - - - (7) LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR - THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF - ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL, - CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE, INCLUDING - BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, - WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING - NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS - BEEN WARNED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES. - - -Brookhaven National Laboratory Notice -===================================== - -Acknowledgment of sponsorship ------------------------------ - -This software was produced by the Brookhaven National Laboratory, under -Contract DE-AC02-98CH10886 with the Department of Energy. - - -Government disclaimer of liability ----------------------------------- - -Neither the United States nor the United States Department of Energy, nor -any of their employees, makes any warranty, express or implied, or assumes -any legal liability or responsibility for the accuracy, completeness, or -usefulness of any data, apparatus, product, or process disclosed, or -represents that its use would not infringe privately owned rights. - - -Brookhaven disclaimer of liability ----------------------------------- - -Brookhaven National Laboratory makes no representations or warranties, -express or implied, nor assumes any liability for the use of this software. - - -Maintenance of notice ---------------------- - -In the interest of clarity regarding the origin and status of this -software, Brookhaven National Laboratory requests that any recipient of it -maintain this notice affixed to any distribution by the recipient that -contains a copy or derivative of this software. - -END OF LICENSE +BSD 3-Clause License + +Copyright (c) 2025, The Trustees of Columbia University in the City of New York. +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/source/snippets/example-table.rst b/doc/source/snippets/example-table.rst new file mode 100644 index 0000000..7c4c11d --- /dev/null +++ b/doc/source/snippets/example-table.rst @@ -0,0 +1,28 @@ +.. list-table:: 5 levels of reusing/sharing code + :widths: 5 15 40 40 + :header-rows: 1 + + * - Level + - Name + - Scope + - How to setup + * - 1 + - ``function`` + - Reuse code in the single file. + - See Level 1 tutorial + * - 2 + - ``module`` + - Reuse code across files. + - See Level 2 tutorial + * - 3 + - ``workspace`` + - Reuse code across project folders. + - ``package create workspace`` + * - 4 + - ``system`` + - Reuse code across any files in the computer. + - ``package create system`` + * - 5 + - ``public`` + - Share code as publicly installable package. + - ``package create public`` diff --git a/pyproject.toml b/pyproject.toml index dd1150c..d977015 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ namespaces = false # to disable scanning PEP 420 namespaces (true by default) dependencies = {file = ["requirements/pip.txt"]} [tool.black] -line-length = 115 +line-length = 79 include = '\.pyi?$' exclude = ''' /( diff --git a/requirements/docs.txt b/requirements/docs.txt index ab17b1c..5f34c6e 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,4 +1,5 @@ sphinx sphinx_rtd_theme +sphinx-copybutton doctr m2r diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index ee55ab5..ea1d936 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -1,7 +1,7 @@ #!/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. @@ -12,12 +12,3 @@ # See LICENSE.rst for license information. # ############################################################################## - -"""Blank namespace package for module diffpy.""" - - -from pkgutil import extend_path - -__path__ = extend_path(__path__, __name__) - -# End of file diff --git a/src/diffpy/structure/__init__.py b/src/diffpy/structure/__init__.py index f810649..51050d0 100644 --- a/src/diffpy/structure/__init__.py +++ b/src/diffpy/structure/__init__.py @@ -12,9 +12,7 @@ # See LICENSE.rst for license information. # ############################################################################## - -""" -Crystal structure container and parsers for structure formats. +"""Crystal structure container and parsers for structure formats. Classes related to the structure of materials: * Atom @@ -42,7 +40,11 @@ from diffpy.structure.parsers import getParser from diffpy.structure.pdffitstructure import PDFFitStructure from diffpy.structure.structure import Structure -from diffpy.structure.structureerrors import LatticeError, StructureFormatError, SymmetryError +from diffpy.structure.structureerrors import ( + LatticeError, + StructureFormatError, + SymmetryError, +) # package version from diffpy.structure.version import __version__ diff --git a/src/diffpy/structure/_legacy_importer.py b/src/diffpy/structure/_legacy_importer.py index e37a907..53cdb80 100644 --- a/src/diffpy/structure/_legacy_importer.py +++ b/src/diffpy/structure/_legacy_importer.py @@ -12,9 +12,7 @@ # See LICENSE.txt for license information. # ############################################################################## - -""" -Support import of old camel-case module names with DeprecationWarning. +"""Support import of old camel-case module names with DeprecationWarning. The imported camel-case modules are aliases for the current module instances. Their `__name__` attributes are thus all in lower-case. @@ -61,6 +59,7 @@ def find_spec(self, fullname, path=None, target=None): class MapRenamedStructureModule(importlib.abc.Loader): """Loader for old camel-case module names. + Import the current module and alias it under the old name. """ @@ -80,7 +79,11 @@ def exec_module(self, module): # ---------------------------------------------------------------------------- # show deprecation warning for diffpy.Structure -warn(WMSG.format("diffpy.Structure", "diffpy.structure"), DeprecationWarning, stacklevel=2) +warn( + WMSG.format("diffpy.Structure", "diffpy.structure"), + DeprecationWarning, + stacklevel=2, +) # install meta path finder for diffpy.Structure submodules sys.meta_path.append(FindRenamedStructureModule()) diff --git a/src/diffpy/structure/apps/__init__.py b/src/diffpy/structure/apps/__init__.py index 15ad470..98aa2fe 100644 --- a/src/diffpy/structure/apps/__init__.py +++ b/src/diffpy/structure/apps/__init__.py @@ -12,5 +12,4 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Script applications that use the `diffpy.structure` package.""" diff --git a/src/diffpy/structure/apps/anyeye.py b/src/diffpy/structure/apps/anyeye.py index b7904d8..ea7af6d 100755 --- a/src/diffpy/structure/apps/anyeye.py +++ b/src/diffpy/structure/apps/anyeye.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -""" -Anyeye view structure file in atomeye. +"""Anyeye view structure file in atomeye. Usage: ``anyeye [options] strufile`` -Anyeye understands more `Structure` formats than atomeye. It converts `strufile` -to a temporary XCFG file which is opened in atomeye. See supported file formats: +Anyeye understands more `Structure` formats than atomeye. It converts +`strufile` to a temporary XCFG file which is opened in atomeye. See supported +file formats: ``inputFormats`` Options: @@ -71,7 +70,11 @@ def usage(style=None): myname = os.path.basename(sys.argv[0]) msg = __doc__.replace("anyeye", myname) if style == "brief": - msg = msg.split("\n")[1] + "\n" + "Try `%s --help' for more information." % myname + msg = ( + msg.split("\n")[1] + + "\n" + + "Try `%s --help' for more information." % myname + ) else: from diffpy.structure.parsers import inputFormats @@ -139,7 +142,10 @@ def convertStructureFile(pd): if pd["formula"]: formula = pd["formula"] if len(formula) != len(stru): - emsg = "Formula has %i atoms while structure %i" % (len(formula), len(stru)) + emsg = "Formula has %i atoms while structure %i" % ( + len(formula), + len(stru), + ) raise RuntimeError(emsg) for a, el in zip(stru, formula): a.element = el @@ -175,7 +181,7 @@ def cleanUp(pd): def parseFormula(formula): - """Parse chemical formula and return a list of elements""" + """Parse chemical formula and return a list of elements.""" # remove all blanks formula = re.sub(r"\s", "", formula) if not re.match("^[A-Z]", formula): @@ -218,7 +224,9 @@ def main(): pd["watch"] = False try: opts, args = getopt.getopt( - sys.argv[1:], "f:whV", ["formula=", "watch", "viewer=", "formats=", "help", "version"] + sys.argv[1:], + "f:whV", + ["formula=", "watch", "viewer=", "formats=", "help", "version"], ) except getopt.GetoptError as errmsg: print(errmsg, file=sys.stderr) diff --git a/src/diffpy/structure/apps/transtru.py b/src/diffpy/structure/apps/transtru.py index 86c682d..26888b4 100755 --- a/src/diffpy/structure/apps/transtru.py +++ b/src/diffpy/structure/apps/transtru.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Translate structure file to different format. Usage: ``transtru INFMT..OUTFMT strufile`` -Translates structure file strufile from `INFMT` to `OUTFMT` format and prints it -to the screen. Use "-" as `strufile` to read from standard input. To save the -translated file, use +Translates structure file strufile from `INFMT` to `OUTFMT` format and prints +it to the screen. Use "-" as `strufile` to read from standard input. To save +the translated file, use ``transtru INFMT..OUTFMT strufile > strufile.out`` @@ -50,7 +49,11 @@ def usage(style=None): myname = os.path.basename(sys.argv[0]) msg = __doc__.replace("transtru", myname) if style == "brief": - msg = msg.split("\n")[1] + "\n" + "Try `%s --help' for more information." % myname + msg = ( + msg.split("\n")[1] + + "\n" + + "Try `%s --help' for more information." % myname + ) else: from diffpy.structure.parsers import inputFormats, outputFormats @@ -99,7 +102,10 @@ def main(): print("'%s' is not valid output format" % outfmt, file=sys.stderr) sys.exit(2) except ValueError: - print("invalid format specification '%s' does not contain .." % args[0], file=sys.stderr) + print( + "invalid format specification '%s' does not contain .." % args[0], + file=sys.stderr, + ) sys.exit(2) # ready to do some real work try: diff --git a/src/diffpy/structure/atom.py b/src/diffpy/structure/atom.py index e531780..5db50f3 100644 --- a/src/diffpy/structure/atom.py +++ b/src/diffpy/structure/atom.py @@ -12,10 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -""" -Provide class Atom for managing properties of an atom in structure model. -""" +"""Provide class Atom for managing properties of an atom in structure model.""" import numpy @@ -170,7 +167,9 @@ def msdLat(self, vl): lat = self.lattice or cartesian_lattice vln = numpy.array(vl, dtype=float) / lat.norm(vl) G = lat.metrics - rhs = numpy.array([G[0] * lat.ar, G[1] * lat.br, G[2] * lat.cr], dtype=float) + rhs = numpy.array( + [G[0] * lat.ar, G[1] * lat.br, G[2] * lat.cr], dtype=float + ) rhs = numpy.dot(rhs, vln) msd = numpy.dot(rhs, numpy.dot(self.U, rhs)) return msd @@ -202,7 +201,13 @@ def msdCart(self, vc): def __repr__(self): """String representation of this Atom.""" xyz = self.xyz - s = "%-4s %8.6f %8.6f %8.6f %6.4f" % (self.element, xyz[0], xyz[1], xyz[2], self.occupancy) + s = "%-4s %8.6f %8.6f %8.6f %6.4f" % ( + self.element, + xyz[0], + xyz[1], + xyz[2], + self.occupancy, + ) return s def __copy__(self, target=None): @@ -354,13 +359,19 @@ def _set_Uij(self, i, j, value): """ U11 = property( - lambda self: self._get_Uij(0, 0), lambda self, value: self._set_Uij(0, 0, value), doc=_doc_uii.format(0) + lambda self: self._get_Uij(0, 0), + lambda self, value: self._set_Uij(0, 0, value), + doc=_doc_uii.format(0), ) U22 = property( - lambda self: self._get_Uij(1, 1), lambda self, value: self._set_Uij(1, 1, value), doc=_doc_uii.format(1) + lambda self: self._get_Uij(1, 1), + lambda self, value: self._set_Uij(1, 1, value), + doc=_doc_uii.format(1), ) U33 = property( - lambda self: self._get_Uij(2, 2), lambda self, value: self._set_Uij(2, 2, value), doc=_doc_uii.format(2) + lambda self: self._get_Uij(2, 2), + lambda self, value: self._set_Uij(2, 2, value), + doc=_doc_uii.format(2), ) _doc_uij = """ @@ -371,13 +382,19 @@ def _set_Uij(self, i, j, value): """ U12 = property( - lambda self: self._get_Uij(0, 1), lambda self, value: self._set_Uij(0, 1, value), doc=_doc_uij.format(0, 1) + lambda self: self._get_Uij(0, 1), + lambda self, value: self._set_Uij(0, 1, value), + doc=_doc_uij.format(0, 1), ) U13 = property( - lambda self: self._get_Uij(0, 2), lambda self, value: self._set_Uij(0, 2, value), doc=_doc_uij.format(0, 2) + lambda self: self._get_Uij(0, 2), + lambda self, value: self._set_Uij(0, 2, value), + doc=_doc_uij.format(0, 2), ) U23 = property( - lambda self: self._get_Uij(1, 2), lambda self, value: self._set_Uij(1, 2, value), doc=_doc_uij.format(1, 2) + lambda self: self._get_Uij(1, 2), + lambda self, value: self._set_Uij(1, 2, value), + doc=_doc_uij.format(1, 2), ) # clean local variables @@ -481,7 +498,8 @@ def Uisoequiv(self, value): @property def Bisoequiv(self): - """float : The Debye-Waller isotropic displacement or an equivalent value. + """float : The Debye-Waller isotropic displacement or an equivalent + value. This equals ``8 * pi**2 * Uisoequiv``. Setting a new value rescales `U` tensor to yield equivalent direction-average of diff --git a/src/diffpy/structure/expansion/__init__.py b/src/diffpy/structure/expansion/__init__.py index faa60ca..8e6d02c 100644 --- a/src/diffpy/structure/expansion/__init__.py +++ b/src/diffpy/structure/expansion/__init__.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Methods and classes for manipulating `Structure` instances. Package content: diff --git a/src/diffpy/structure/expansion/makeellipsoid.py b/src/diffpy/structure/expansion/makeellipsoid.py index 009be57..547096c 100644 --- a/src/diffpy/structure/expansion/makeellipsoid.py +++ b/src/diffpy/structure/expansion/makeellipsoid.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Make a spheroid nanoparticle from a template structure.""" from math import ceil @@ -42,8 +41,7 @@ def makeSphere(S, radius): def makeEllipsoid(S, a, b=None, c=None): - """ - Cut a `Structure` out of another one. + """Cut a `Structure` out of another one. Parameters ---------- diff --git a/src/diffpy/structure/expansion/shapeutils.py b/src/diffpy/structure/expansion/shapeutils.py index 0e99df0..16f6c4c 100644 --- a/src/diffpy/structure/expansion/shapeutils.py +++ b/src/diffpy/structure/expansion/shapeutils.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Utilities for making shapes.""" @@ -33,7 +32,7 @@ def findCenter(S): """ best = -1 bestd = len(S) - center = [0.5, 0.5, 0.5] # the cannonical center + center = [0.5, 0.5, 0.5] # the canonical center for i in range(len(S)): d = S.lattice.dist(S[i].xyz, center) diff --git a/src/diffpy/structure/expansion/supercell_mod.py b/src/diffpy/structure/expansion/supercell_mod.py index dee99d9..9f03431 100644 --- a/src/diffpy/structure/expansion/supercell_mod.py +++ b/src/diffpy/structure/expansion/supercell_mod.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """This module contains functions for simple `Structure` manipulation.""" import numpy @@ -21,8 +20,7 @@ def supercell(S, mno): - """ - Perform supercell expansion for a `Structure`. + """Perform supercell expansion for a `Structure`. New `lattice` parameters are multiplied and fractional coordinates divided by corresponding multiplier. New `Atoms` are grouped with @@ -68,7 +66,12 @@ def supercell(S, mno): return newS # back to business - ijklist = [(i, j, k) for i in range(mno[0]) for j in range(mno[1]) for k in range(mno[2])] + ijklist = [ + (i, j, k) + for i in range(mno[0]) + for j in range(mno[1]) + for k in range(mno[2]) + ] # numpy.floor returns float array mnofloats = numpy.array(mno, dtype=float) @@ -83,7 +86,9 @@ def supercell(S, mno): newS.__setitem__(slice(None), newAtoms, copy=False) # take care of lattice parameters - newS.lattice.setLatPar(a=mno[0] * S.lattice.a, b=mno[1] * S.lattice.b, c=mno[2] * S.lattice.c) + newS.lattice.setLatPar( + a=mno[0] * S.lattice.a, b=mno[1] * S.lattice.b, c=mno[2] * S.lattice.c + ) return newS diff --git a/src/diffpy/structure/functions.py b/src/diffpy/structure/functions.py new file mode 100644 index 0000000..e7e2c8e --- /dev/null +++ b/src/diffpy/structure/functions.py @@ -0,0 +1,31 @@ +import numpy as np + + +def dot_product(a, b): + """Compute the dot product of two vectors of any size. + + Ensure that the inputs, a and b, are of the same size. + The supported types are "array_like" objects, which can + be converted to a NumPy array. Examples include lists and tuples. + + Parameters + ---------- + a : array_like + The first input vector. + b : array_like + The second input vector. + + Returns + ------- + float + The dot product of the two vectors. + + Examples + -------- + Compute the dot product of two lists: + >>> a = [1, 2, 3] + >>> b = [4, 5, 6] + >>> dot_product(a, b) + 32.0 + """ + return float(np.dot(a, b)) diff --git a/src/diffpy/structure/lattice.py b/src/diffpy/structure/lattice.py index fb51de0..6d0b628 100644 --- a/src/diffpy/structure/lattice.py +++ b/src/diffpy/structure/lattice.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Class Lattice stores properties and provides simple operations in lattice coordinate system. @@ -32,7 +31,16 @@ # Helper Functions ----------------------------------------------------------- # exact values of cosd -_EXACT_COSD = {0.0: +1.0, 60.0: +0.5, 90.0: 0.0, 120.0: -0.5, 180.0: -1.0, 240.0: -0.5, 270.0: 0.0, 300.0: +0.5} +_EXACT_COSD = { + 0.0: +1.0, + 60.0: +0.5, + 90.0: 0.0, + 120.0: -0.5, + 180.0: -1.0, + 240.0: -0.5, + 270.0: 0.0, + 300.0: +0.5, +} def cosd(x): @@ -159,15 +167,21 @@ class Lattice(object): # properties ------------------------------------------------------------- a = property( - lambda self: self._a, lambda self, value: self.setLatPar(a=value), doc="The unit cell length *a*." + lambda self: self._a, + lambda self, value: self.setLatPar(a=value), + doc="The unit cell length *a*.", ) b = property( - lambda self: self._b, lambda self, value: self.setLatPar(b=value), doc="The unit cell length *b*." + lambda self: self._b, + lambda self, value: self.setLatPar(b=value), + doc="The unit cell length *b*.", ) c = property( - lambda self: self._c, lambda self, value: self.setLatPar(c=value), doc="The unit cell length *c*." + lambda self: self._c, + lambda self, value: self.setLatPar(c=value), + doc="The unit cell length *c*.", ) alpha = property( @@ -201,47 +215,105 @@ def unitvolume(self): rv = math.sqrt(1.0 + 2.0 * ca * cb * cg - ca * ca - cb * cb - cg * cg) return rv - volume = property(lambda self: self.a * self.b * self.c * self.unitvolume, doc="The unit cell volume.") + volume = property( + lambda self: self.a * self.b * self.c * self.unitvolume, + doc="The unit cell volume.", + ) - ar = property(lambda self: self._ar, doc="The cell length *a* of the reciprocal lattice.") + ar = property( + lambda self: self._ar, + doc="The cell length *a* of the reciprocal lattice.", + ) - br = property(lambda self: self._br, doc="The cell length *b* of the reciprocal lattice.") + br = property( + lambda self: self._br, + doc="The cell length *b* of the reciprocal lattice.", + ) - cr = property(lambda self: self._cr, doc="The cell length *c* of the reciprocal lattice.") + cr = property( + lambda self: self._cr, + doc="The cell length *c* of the reciprocal lattice.", + ) - alphar = property(lambda self: self._alphar, doc="The reciprocal cell angle *alpha* in degrees.") + alphar = property( + lambda self: self._alphar, + doc="The reciprocal cell angle *alpha* in degrees.", + ) - betar = property(lambda self: self._betar, doc="The reciprocal cell angle *beta* in degrees") + betar = property( + lambda self: self._betar, + doc="The reciprocal cell angle *beta* in degrees", + ) - gammar = property(lambda self: self._gammar, doc="The reciprocal cell angle *gamma* in degrees") + gammar = property( + lambda self: self._gammar, + doc="The reciprocal cell angle *gamma* in degrees", + ) - ca = property(lambda self: self._ca, doc="The cosine of the cell angle *alpha*.") + ca = property( + lambda self: self._ca, doc="The cosine of the cell angle *alpha*." + ) - cb = property(lambda self: self._cb, doc="The cosine of the cell angle *beta*.") + cb = property( + lambda self: self._cb, doc="The cosine of the cell angle *beta*." + ) - cg = property(lambda self: self._cg, doc="The cosine of the cell angle *gamma*.") + cg = property( + lambda self: self._cg, doc="The cosine of the cell angle *gamma*." + ) - sa = property(lambda self: self._sa, doc="The sine of the cell angle *alpha*.") + sa = property( + lambda self: self._sa, doc="The sine of the cell angle *alpha*." + ) - sb = property(lambda self: self._sb, doc="The sine of the cell angle *beta*.") + sb = property( + lambda self: self._sb, doc="The sine of the cell angle *beta*." + ) - sg = property(lambda self: self._sg, doc="The sine of the cell angle *gamma*.") + sg = property( + lambda self: self._sg, doc="The sine of the cell angle *gamma*." + ) - car = property(lambda self: self._car, doc="The cosine of the reciprocal angle *alpha*.") + car = property( + lambda self: self._car, + doc="The cosine of the reciprocal angle *alpha*.", + ) - cbr = property(lambda self: self._cbr, doc="The cosine of the reciprocal angle *beta*.") + cbr = property( + lambda self: self._cbr, + doc="The cosine of the reciprocal angle *beta*.", + ) - cgr = property(lambda self: self._cgr, doc="The cosine of the reciprocal angle *gamma*.") + cgr = property( + lambda self: self._cgr, + doc="The cosine of the reciprocal angle *gamma*.", + ) - sar = property(lambda self: self._sar, doc="The sine of the reciprocal angle *alpha*.") + sar = property( + lambda self: self._sar, doc="The sine of the reciprocal angle *alpha*." + ) - sbr = property(lambda self: self._sbr, doc="The sine of the reciprocal angle *beta*.") + sbr = property( + lambda self: self._sbr, doc="The sine of the reciprocal angle *beta*." + ) - sgr = property(lambda self: self._sgr, doc="The sine of the reciprocal angle *gamma*.") + sgr = property( + lambda self: self._sgr, doc="The sine of the reciprocal angle *gamma*." + ) # done with properties --------------------------------------------------- - def __init__(self, a=None, b=None, c=None, alpha=None, beta=None, gamma=None, baserot=None, base=None): + def __init__( + self, + a=None, + b=None, + c=None, + alpha=None, + beta=None, + gamma=None, + baserot=None, + base=None, + ): # build a set of provided argument names for later use. apairs = ( ("a", a), @@ -288,7 +360,16 @@ def __init__(self, a=None, b=None, c=None, alpha=None, beta=None, gamma=None, ba self.setLatPar(a, b, c, alpha, beta, gamma, baserot=baserot) return - def setLatPar(self, a=None, b=None, c=None, alpha=None, beta=None, gamma=None, baserot=None): + def setLatPar( + self, + a=None, + b=None, + c=None, + alpha=None, + beta=None, + gamma=None, + baserot=None, + ): """Set one or more lattice parameters. This updates all attributes that depend on the lattice parameters. @@ -362,7 +443,11 @@ def setLatPar(self, a=None, b=None, c=None, alpha=None, beta=None, gamma=None, b ) # standard Cartesian coordinates of lattice vectors self.stdbase = numpy.array( - [[1.0 / ar, -cgr / sgr / ar, cb * self.a], [0.0, self.b * sa, self.b * ca], [0.0, 0.0, self.c]], + [ + [1.0 / ar, -cgr / sgr / ar, cb * self.a], + [0.0, self.b * sa, self.b * ca], + [0.0, 0.0, self.c], + ], dtype=float, ) # Cartesian coordinates of lattice vectors @@ -424,7 +509,12 @@ def setLatBase(self, base): self._gammar = math.degrees(math.acos(cgr)) # standard orientation of lattice vectors self.stdbase = numpy.array( - [[1.0 / ar, -cgr / sgr / ar, cb * a], [0.0, b * sa, b * ca], [0.0, 0.0, c]], dtype=float + [ + [1.0 / ar, -cgr / sgr / ar, cb * a], + [0.0, b * sa, b * ca], + [0.0, 0.0, c], + ], + dtype=float, ) # calculate unit cell rotation matrix, base = stdbase @ baserot self.baserot = numpy.dot(numalg.inv(self.stdbase), self.base) @@ -435,13 +525,18 @@ def setLatBase(self, base): self.isotropicunit = _isotropicunit(self.recnormbase) # update metrics tensor self.metrics = numpy.array( - [[a * a, a * b * cg, a * c * cb], [b * a * cg, b * b, b * c * ca], [c * a * cb, c * b * ca, c * c]], + [ + [a * a, a * b * cg, a * c * cb], + [b * a * cg, b * b, b * c * ca], + [c * a * cb, c * b * ca, c * c], + ], dtype=float, ) return def abcABG(self): """Return the cell parameters in the standard setting. + Returns ------- tuple : @@ -452,6 +547,7 @@ def abcABG(self): def reciprocal(self): """Return the reciprocal lattice of the current lattice. + Returns ------- Lattice @@ -627,7 +723,10 @@ def __repr__(self): elif numpy.fabs(latpardiff).max() < self._epsilon: s = "Lattice()" else: - s = "Lattice(a=%g, b=%g, c=%g, alpha=%g, beta=%g, gamma=%g)" % self.abcABG() + s = ( + "Lattice(a=%g, b=%g, c=%g, alpha=%g, beta=%g, gamma=%g)" + % self.abcABG() + ) return s diff --git a/src/diffpy/structure/mmlibspacegroups.py b/src/diffpy/structure/mmlibspacegroups.py index 1acd266..c3507f9 100644 --- a/src/diffpy/structure/mmlibspacegroups.py +++ b/src/diffpy/structure/mmlibspacegroups.py @@ -3269,7 +3269,11 @@ point_group_name="PG3", crystal_system="TRIGONAL", pdb_name="P 3", - symop_list=[SymOp(Rot_X_Y_Z, Tr_0_0_0), SymOp(Rot_mY_XmY_Z, Tr_0_0_0), SymOp(Rot_mXY_mX_Z, Tr_0_0_0)], + symop_list=[ + SymOp(Rot_X_Y_Z, Tr_0_0_0), + SymOp(Rot_mY_XmY_Z, Tr_0_0_0), + SymOp(Rot_mXY_mX_Z, Tr_0_0_0), + ], ) sg144 = SpaceGroup( @@ -3280,7 +3284,11 @@ point_group_name="PG3", crystal_system="TRIGONAL", pdb_name="P 31", - symop_list=[SymOp(Rot_X_Y_Z, Tr_0_0_0), SymOp(Rot_mY_XmY_Z, Tr_0_0_13), SymOp(Rot_mXY_mX_Z, Tr_0_0_23)], + symop_list=[ + SymOp(Rot_X_Y_Z, Tr_0_0_0), + SymOp(Rot_mY_XmY_Z, Tr_0_0_13), + SymOp(Rot_mXY_mX_Z, Tr_0_0_23), + ], ) sg145 = SpaceGroup( @@ -3291,7 +3299,11 @@ point_group_name="PG3", crystal_system="TRIGONAL", pdb_name="P 32", - symop_list=[SymOp(Rot_X_Y_Z, Tr_0_0_0), SymOp(Rot_mY_XmY_Z, Tr_0_0_23), SymOp(Rot_mXY_mX_Z, Tr_0_0_13)], + symop_list=[ + SymOp(Rot_X_Y_Z, Tr_0_0_0), + SymOp(Rot_mY_XmY_Z, Tr_0_0_23), + SymOp(Rot_mXY_mX_Z, Tr_0_0_13), + ], ) sg146 = SpaceGroup( @@ -3323,7 +3335,11 @@ point_group_name="PG3", crystal_system="TRIGONAL", pdb_name="R 3", - symop_list=[SymOp(Rot_X_Y_Z, Tr_0_0_0), SymOp(Rot_Z_X_Y, Tr_0_0_0), SymOp(Rot_Y_Z_X, Tr_0_0_0)], + symop_list=[ + SymOp(Rot_X_Y_Z, Tr_0_0_0), + SymOp(Rot_Z_X_Y, Tr_0_0_0), + SymOp(Rot_Y_Z_X, Tr_0_0_0), + ], ) sg147 = SpaceGroup( diff --git a/src/diffpy/structure/parsers/__init__.py b/src/diffpy/structure/parsers/__init__.py index 0d549b8..0f1297f 100644 --- a/src/diffpy/structure/parsers/__init__.py +++ b/src/diffpy/structure/parsers/__init__.py @@ -12,13 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Conversion plugins for various structure formats. The recognized structure formats are defined by subclassing `StructureParser`, -by convention these classes are named `P_.py`. The parser classes should -to override the `parseLines()` and `toLines()` methods of `StructureParser`. -Any structure parser needs to be registered in `parser_index` module. +by convention these classes are named `P_.py`. The parser classes +should to override the `parseLines()` and `toLines()` methods of +`StructureParser`. Any structure parser needs to be registered in +`parser_index` module. For normal usage it should be sufficient to use the routines provided in this module. @@ -71,13 +71,17 @@ def getParser(format, **kw): def inputFormats(): """Return list of implemented input structure formats.""" - input_formats = [fmt for fmt, prop in parser_index.items() if prop["has_input"]] + input_formats = [ + fmt for fmt, prop in parser_index.items() if prop["has_input"] + ] input_formats.sort() return input_formats def outputFormats(): """Return list of implemented output structure formats.""" - output_formats = [fmt for fmt, prop in parser_index.items() if prop["has_output"]] + output_formats = [ + fmt for fmt, prop in parser_index.items() if prop["has_output"] + ] output_formats.sort() return output_formats diff --git a/src/diffpy/structure/parsers/p_auto.py b/src/diffpy/structure/parsers/p_auto.py index 745eb5a..a39ac13 100644 --- a/src/diffpy/structure/parsers/p_auto.py +++ b/src/diffpy/structure/parsers/p_auto.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Parser for automatic file format detection. This Parser does not provide the the `toLines()` method. @@ -54,6 +53,7 @@ def __init__(self, **kw): # parseLines helpers def _getOrderedFormats(self): """Build a list of relevance ordered structure formats. + This only works when `self.filename` has a known extension. """ from diffpy.structure.parsers import inputFormats @@ -146,7 +146,7 @@ def parseFile(self, filename): def _wrapParseMethod(self, method, *args, **kwargs): """A helper evaluator method that try the specified parse method with each registered structure parser and return the first successful - resul. + result. Structure parsers that match structure file extension are tried first. @@ -188,7 +188,10 @@ def _wrapParseMethod(self, method, *args, **kwargs): pass if stru is None: emsg = "\n".join( - ["Unknown or invalid structure format.", "Errors per each tested structure format:"] + [ + "Unknown or invalid structure format.", + "Errors per each tested structure format:", + ] + parsers_emsgs ) raise StructureFormatError(emsg) diff --git a/src/diffpy/structure/parsers/p_cif.py b/src/diffpy/structure/parsers/p_cif.py index 6fba3b4..e0ccd54 100644 --- a/src/diffpy/structure/parsers/p_cif.py +++ b/src/diffpy/structure/parsers/p_cif.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Parser for basic CIF file format. Attributes @@ -69,11 +68,14 @@ class P_cif(StructureParser): eau : ExpandAsymmetricUnit Instance of `ExpandAsymmetricUnit` from `SymmetryUtilities`. asymmetric_unit : list - List of `Atom` instances for the original asymmetric unit in the CIF file. + List of `Atom` instances for the original asymmetric unit in the CIF + file. labelindex : dict - Dictionary mapping unique atom label to index of `Atom` in `self.asymmetric_unit`. + Dictionary mapping unique atom label to index of `Atom` in + `self.asymmetric_unit`. anisotropy : dict - Dictionary mapping unique atom label to displacement anisotropy resolved at that site. + Dictionary mapping unique atom label to displacement anisotropy + resolved at that site. cif_sgname : str or None Space group name obtained by looking up the value of `_space_group_name_Hall`, @@ -267,7 +269,8 @@ def _tr_atom_site_aniso_B_23(a, value): _tr_atom_site_aniso_B_23 = staticmethod(_tr_atom_site_aniso_B_23) def _get_atom_setters(cifloop): - """Static method for finding translators of CifLoop items to data in `Atom` instance. + """Static method for finding translators of CifLoop items to data in + `Atom` instance. Parameters ---------- @@ -482,7 +485,8 @@ def _parse_atom_site_label(self, block): # process _atom_site_label atom_site_loop = block.GetLoop("_atom_site_label") does_adp_type = ( - "_atom_site_adp_type" in atom_site_loop or "_atom_site_thermal_displace_type" in atom_site_loop + "_atom_site_adp_type" in atom_site_loop + or "_atom_site_thermal_displace_type" in atom_site_loop ) # get a list of setters for atom_site values prop_setters = P_cif._get_atom_setters(atom_site_loop) @@ -549,10 +553,18 @@ def _parse_space_group_symop_operation_xyz(self, block): block : CifBlock Instance of `CifBlock`. """ - from diffpy.structure.spacegroups import FindSpaceGroup, GetSpaceGroup, IsSpaceGroupIdentifier, SpaceGroup + from diffpy.structure.spacegroups import ( + FindSpaceGroup, + GetSpaceGroup, + IsSpaceGroupIdentifier, + SpaceGroup, + ) self.asymmetric_unit = list(self.stru) - sym_synonyms = ("_space_group_symop_operation_xyz", "_symmetry_equiv_pos_as_xyz") + sym_synonyms = ( + "_space_group_symop_operation_xyz", + "_symmetry_equiv_pos_as_xyz", + ) sym_loop_name = [n for n in sym_synonyms if n in block] # recover explicit list of symmetry operations symop_list = [] @@ -564,14 +576,20 @@ def _parse_space_group_symop_operation_xyz(self, block): opcif = getSymOp(eqxyz) symop_list.append(opcif) # determine space group number - sg_nameHall = block.get("_space_group_name_Hall", "") or block.get("_symmetry_space_group_name_Hall", "") + sg_nameHall = block.get("_space_group_name_Hall", "") or block.get( + "_symmetry_space_group_name_Hall", "" + ) sg_nameHM = ( block.get("_space_group_name_H-M_alt", "") or block.get("_space_group_name_H-M_ref", "") or block.get("_symmetry_space_group_name_H-M", "") ) self.cif_sgname = sg_nameHall or sg_nameHM or None - sgid = block.get("_space_group_IT_number", "") or block.get("_symmetry_Int_Tables_number", "") or sg_nameHM + sgid = ( + block.get("_space_group_IT_number", "") + or block.get("_symmetry_Int_Tables_number", "") + or sg_nameHM + ) self.spacegroup = None # try to reuse existing space group from symmetry operations if symop_list: @@ -587,10 +605,14 @@ def _parse_space_group_symop_operation_xyz(self, block): if symop_list and self.spacegroup is None: new_short_name = "CIF " + (sg_nameHall or "data") new_crystal_system = ( - block.get("_space_group_crystal_system") or block.get("_symmetry_cell_setting") or "TRICLINIC" + block.get("_space_group_crystal_system") + or block.get("_symmetry_cell_setting") + or "TRICLINIC" ).upper() self.spacegroup = SpaceGroup( - short_name=new_short_name, crystal_system=new_crystal_system, symop_list=symop_list + short_name=new_short_name, + crystal_system=new_crystal_system, + symop_list=symop_list, ) if self.spacegroup is None: emsg = "CIF file has unknown space group identifier {!r}." @@ -612,7 +634,9 @@ def _expandAsymmetricUnit(self, block): corepos = [a.xyz for a in self.stru] coreUijs = [a.U for a in self.stru] - self.eau = ExpandAsymmetricUnit(self.spacegroup, corepos, coreUijs, eps=self.eps) + self.eau = ExpandAsymmetricUnit( + self.spacegroup, corepos, coreUijs, eps=self.eps + ) # setup anisotropy according to symmetry requirements # unless it was already explicitly set for ca, uisotropy in zip(self.stru, self.eau.Uisotropy): @@ -691,7 +715,9 @@ def toLines(self, stru): a_site_label = [] a_adp_type = [] for a in stru: - cnt = element_count[a.element] = element_count.get(a.element, 0) + 1 + cnt = element_count[a.element] = ( + element_count.get(a.element, 0) + 1 + ) a_site_label.append("%s%i" % (a.element, cnt)) if numpy.all(a.U == a.U[0, 0] * numpy.identity(3)): a_adp_type.append("Uiso") diff --git a/src/diffpy/structure/parsers/p_discus.py b/src/diffpy/structure/parsers/p_discus.py index c7185cd..59a6823 100644 --- a/src/diffpy/structure/parsers/p_discus.py +++ b/src/diffpy/structure/parsers/p_discus.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Parser for DISCUS structure format""" +"""Parser for DISCUS structure format.""" import sys from functools import reduce @@ -24,8 +23,8 @@ class P_discus(StructureParser): - """Parser for DISCUS structure format. The parser chokes - on molecule and generator records. + """Parser for DISCUS structure format. The parser chokes on molecule and + generator records. Attributes ---------- @@ -116,12 +115,17 @@ def parseLines(self, lines): exp_natoms = reduce(lambda x, y: x * y, self.stru.pdffit["ncell"]) # only check if ncell record exists if self.ncell_read and exp_natoms != len(self.stru): - emsg = "Expected %d atoms, read %d." % (exp_natoms, len(self.stru)) + emsg = "Expected %d atoms, read %d." % ( + exp_natoms, + len(self.stru), + ) raise StructureFormatError(emsg) # take care of superlattice if self.stru.pdffit["ncell"][:3] != [1, 1, 1]: latpars = list(self.stru.lattice.abcABG()) - superlatpars = [latpars[i] * self.stru.pdffit["ncell"][i] for i in range(3)] + latpars[3:] + superlatpars = [ + latpars[i] * self.stru.pdffit["ncell"][i] for i in range(3) + ] + latpars[3:] superlattice = Lattice(*superlatpars) self.stru.placeInLattice(superlattice) self.stru.pdffit["ncell"] = [1, 1, 1, exp_natoms] @@ -164,12 +168,22 @@ def toLines(self, stru): if stru_pdffit.get("stepcut", 0.0) > 0.0: line = "shape stepcut, %g" % stru_pdffit["stepcut"] lines.append(line) - lines.append("cell %9.6f, %9.6f, %9.6f, %9.6f, %9.6f, %9.6f" % self.stru.lattice.abcABG()) + lines.append( + "cell %9.6f, %9.6f, %9.6f, %9.6f, %9.6f, %9.6f" + % self.stru.lattice.abcABG() + ) lines.append("ncell %9i, %9i, %9i, %9i" % (1, 1, 1, len(self.stru))) lines.append("atoms") for a in self.stru: lines.append( - "%-4s %17.8f %17.8f %17.8f %12.4f" % (a.element.upper(), a.xyz[0], a.xyz[1], a.xyz[2], a.Bisoequiv) + "%-4s %17.8f %17.8f %17.8f %12.4f" + % ( + a.element.upper(), + a.xyz[0], + a.xyz[1], + a.xyz[2], + a.Bisoequiv, + ) ) return lines @@ -194,7 +208,9 @@ def _parse_cell(self, words): try: self.stru.lattice.setLatPar(*latpars) except ZeroDivisionError: - emsg = "%d: Invalid lattice parameters - zero cell volume" % self.nl + emsg = ( + "%d: Invalid lattice parameters - zero cell volume" % self.nl + ) raise StructureFormatError(emsg) self.cell_read = True return @@ -273,7 +289,7 @@ def _parse_unknown_record(self, words): Raises ------ StructureFormatError - Unkown record. + Unknown record. """ self.ignored_lines.append(self.line) return @@ -291,7 +307,10 @@ def _parse_not_implemented(self, words): NotImplementedError If the record is not implemented. """ - emsg = "%d: reading of DISCUS record %r is not implemented." % (self.nl, words[0]) + emsg = "%d: reading of DISCUS record %r is not implemented." % ( + self.nl, + words[0], + ) raise NotImplementedError(emsg) diff --git a/src/diffpy/structure/parsers/p_pdb.py b/src/diffpy/structure/parsers/p_pdb.py index 6d0f95a..54aeb35 100644 --- a/src/diffpy/structure/parsers/p_pdb.py +++ b/src/diffpy/structure/parsers/p_pdb.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Basic parser for PDB structure format. Note @@ -176,7 +175,10 @@ def parseLines(self, lines): abcABGscale = numpy.array(stru.lattice.abcABG()) reldiff = numpy.fabs(1.0 - abcABGscale / abcABGcryst) if not numpy.all(reldiff < 1.0e-4): - emsg = "%d: " % p_nl + "SCALE and CRYST1 are not consistent." + emsg = ( + "%d: " % p_nl + + "SCALE and CRYST1 are not consistent." + ) raise StructureFormatError(emsg) if numpy.any(scaleU != 0.0): emsg = "Origin offset not yet implemented." @@ -281,9 +283,8 @@ def cryst1Lines(self, stru): return lines def atomLines(self, stru, idx): - """Build `ATOM` records and possibly `SIGATM`, `ANISOU` or `SIGUIJ` records - for `structure` stru `atom` number aidx. - """ + """Build `ATOM` records and possibly `SIGATM`, `ANISOU` or `SIGUIJ` + records for `structure` stru `atom` number aidx.""" lines = [] a = stru[idx] ad = a.__dict__ @@ -325,7 +326,19 @@ def atomLines(self, stru, idx): isotropic = numpy.all(a.U == a.U[0, 0] * numpy.identity(3)) if not isotropic: mid = " %7i%7i%7i%7i%7i%7i " % tuple( - numpy.around(1e4 * numpy.array([a.U[0, 0], a.U[1, 1], a.U[2, 2], a.U[0, 1], a.U[0, 2], a.U[1, 2]])) + numpy.around( + 1e4 + * numpy.array( + [ + a.U[0, 0], + a.U[1, 1], + a.U[2, 2], + a.U[0, 1], + a.U[0, 2], + a.U[1, 2], + ] + ) + ) ) line = "ANISOU" + atomline[6:27] + mid + atomline[72:80] lines.append(line) @@ -339,9 +352,9 @@ def atomLines(self, stru, idx): sigB = [8 * pi**2 * numpy.average([sigU[i, i] for i in range(3)])] sigmas = numpy.concatenate((sigxyz, sigo, sigB)) # no need to print sigmas if they all round to zero - hassigmas = numpy.any(numpy.fabs(sigmas) >= numpy.array(3 * [5e-4] + 2 * [5e-3])) or numpy.any( - numpy.fabs(sigU) > 5.0e-5 - ) + hassigmas = numpy.any( + numpy.fabs(sigmas) >= numpy.array(3 * [5e-4] + 2 * [5e-3]) + ) or numpy.any(numpy.fabs(sigU) > 5.0e-5) if hassigmas: mid = " %8.3f%8.3f%8.3f%6.2f%6.2f " % tuple(sigmas) line = "SIGATM" + atomline[6:27] + mid + atomline[72:80] @@ -350,7 +363,17 @@ def atomLines(self, stru, idx): if not numpy.all(sigU == sigU[0, 0] * numpy.identity(3)): mid = " %7i%7i%7i%7i%7i%7i " % tuple( numpy.around( - 1e4 * numpy.array([sigU[0, 0], sigU[1, 1], sigU[2, 2], sigU[0, 1], sigU[0, 2], sigU[1, 2]]) + 1e4 + * numpy.array( + [ + sigU[0, 0], + sigU[1, 1], + sigU[2, 2], + sigU[0, 1], + sigU[0, 2], + sigU[1, 2], + ] + ) ) ) line = "SIGUIJ" + atomline[6:27] + mid + atomline[72:80] @@ -383,7 +406,14 @@ def toLines(self, stru): + "%(resSeq)4i" # 23-26 + "%(iCode)c" # 27 + "%(blank)53s" # 28-80 - ) % {"serial": len(stru) + 1, "resName": "", "chainID": " ", "resSeq": 1, "iCode": " ", "blank": " "} + ) % { + "serial": len(stru) + 1, + "resName": "", + "chainID": " ", + "resSeq": 1, + "iCode": " ", + "blank": " ", + } lines.append(line) lines.append("%-80s" % "END") return lines diff --git a/src/diffpy/structure/parsers/p_pdffit.py b/src/diffpy/structure/parsers/p_pdffit.py index 7fb7a56..d17eabc 100644 --- a/src/diffpy/structure/parsers/p_pdffit.py +++ b/src/diffpy/structure/parsers/p_pdffit.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Parser for PDFfit structure format""" +"""Parser for PDFfit structure format.""" import sys from functools import reduce @@ -168,7 +167,9 @@ def parseLines(self, lines): emsg = "expected %d atoms, read %d" % (p_natoms, len(stru)) raise StructureFormatError(emsg) if stru.pdffit["ncell"][:3] != [1, 1, 1]: - superlatpars = [latpars[i] * stru.pdffit["ncell"][i] for i in range(3)] + latpars[3:] + superlatpars = [ + latpars[i] * stru.pdffit["ncell"][i] for i in range(3) + ] + latpars[3:] superlattice = Lattice(*superlatpars) stru.placeInLattice(superlattice) stru.pdffit["ncell"] = [1, 1, 1, p_natoms] @@ -209,7 +210,12 @@ def toLines(self, stru): lines.append("scale %9.6f" % stru_pdffit["scale"]) lines.append( "sharp %9.6f, %9.6f, %9.6f, %9.6f" - % (stru_pdffit["delta2"], stru_pdffit["delta1"], stru_pdffit["sratio"], stru_pdffit["rcut"]) + % ( + stru_pdffit["delta2"], + stru_pdffit["delta1"], + stru_pdffit["sratio"], + stru_pdffit["rcut"], + ) ) lines.append("spcgr " + stru_pdffit["spcgr"]) if stru_pdffit.get("spdiameter", 0.0) > 0.0: @@ -223,15 +229,27 @@ def toLines(self, stru): "cell %9.6f, %9.6f, %9.6f, %9.6f, %9.6f, %9.6f" % (lat.a, lat.b, lat.c, lat.alpha, lat.beta, lat.gamma) ) - lines.append("dcell %9.6f, %9.6f, %9.6f, %9.6f, %9.6f, %9.6f" % tuple(stru_pdffit["dcell"])) + lines.append( + "dcell %9.6f, %9.6f, %9.6f, %9.6f, %9.6f, %9.6f" + % tuple(stru_pdffit["dcell"]) + ) lines.append("ncell %9i, %9i, %9i, %9i" % (1, 1, 1, len(stru))) lines.append("atoms") for a in stru: ad = a.__dict__ lines.append( - "%-4s %17.8f %17.8f %17.8f %12.4f" % (a.element.upper(), a.xyz[0], a.xyz[1], a.xyz[2], a.occupancy) + "%-4s %17.8f %17.8f %17.8f %12.4f" + % ( + a.element.upper(), + a.xyz[0], + a.xyz[1], + a.xyz[2], + a.occupancy, + ) + ) + sigmas = numpy.concatenate( + (ad.get("sigxyz", d_sigxyz), [ad.get("sigo", d_sigo)]) ) - sigmas = numpy.concatenate((ad.get("sigxyz", d_sigxyz), [ad.get("sigo", d_sigo)])) lines.append(" %18.8f %17.8f %17.8f %12.4f" % tuple(sigmas)) sigU = ad.get("sigU", d_sigU) Uii = (a.U[0][0], a.U[1][1], a.U[2][2]) diff --git a/src/diffpy/structure/parsers/p_rawxyz.py b/src/diffpy/structure/parsers/p_rawxyz.py index 65e9588..359bc23 100644 --- a/src/diffpy/structure/parsers/p_rawxyz.py +++ b/src/diffpy/structure/parsers/p_rawxyz.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Parser for raw XYZ file format. -Raw XYZ is a 3 or 4 column text file with cartesian coordinates -of atoms and an optional first column for atom types. +Raw XYZ is a 3 or 4 column text file with cartesian coordinates of atoms +and an optional first column for atom types. """ import sys @@ -81,7 +80,9 @@ def parseLines(self, lines): floatfields = [isfloat(f) for f in linefields[start]] nfields = len(linefields[start]) if nfields not in (3, 4): - emsg = "%d: invalid RAWXYZ format, expected 3 or 4 columns" % (start + 1) + emsg = "%d: invalid RAWXYZ format, expected 3 or 4 columns" % ( + start + 1 + ) raise StructureFormatError(emsg) if floatfields[:3] == [True, True, True]: el_idx, x_idx = (None, 0) @@ -98,7 +99,10 @@ def parseLines(self, lines): if fields == []: continue elif len(fields) != nfields: - emsg = ("%d: all lines must have " + "the same number of columns") % p_nl + emsg = ( + "%d: all lines must have " + + "the same number of columns" + ) % p_nl raise StructureFormatError(emsg) element = el_idx is not None and fields[el_idx] or "" xyz = [float(f) for f in fields[x_idx : x_idx + 3]] diff --git a/src/diffpy/structure/parsers/p_xcfg.py b/src/diffpy/structure/parsers/p_xcfg.py index 372e7ba..ca95b5e 100644 --- a/src/diffpy/structure/parsers/p_xcfg.py +++ b/src/diffpy/structure/parsers/p_xcfg.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Parser for extended CFG format used by atomeye. Attributes @@ -218,7 +217,10 @@ def parseLines(self, lines): continue elif xcfg_Number_of_particles is None: if line.find("Number of particles =") != 0: - emsg = ("%d: first line must " + "contain 'Number of particles ='") % p_nl + emsg = ( + "%d: first line must " + + "contain 'Number of particles ='" + ) % p_nl raise StructureFormatError(emsg) xcfg_Number_of_particles = int(line[21:].split(None, 1)[0]) p_natoms = xcfg_Number_of_particles @@ -248,10 +250,15 @@ def parseLines(self, lines): p_auxiliary[i] = "aux%d" % i sorted_aux_keys = sorted(p_auxiliary.keys()) if p_auxnum != 0: - stru.xcfg = {"auxiliaries": [p_auxiliary[k] for k in sorted_aux_keys]} + stru.xcfg = { + "auxiliaries": [p_auxiliary[k] for k in sorted_aux_keys] + } ecnt = len(p_auxiliary) + (3 if xcfg_NO_VELOCITY else 6) if ecnt != xcfg_entry_count: - emsg = ("%d: auxiliary fields are " "not consistent with entry_count") % p_nl + emsg = ( + "%d: auxiliary fields are " + "not consistent with entry_count" + ) % p_nl raise StructureFormatError(emsg) # define proper lattice stru.lattice.setLatBase(xcfg_H0) @@ -272,7 +279,12 @@ def parseLines(self, lines): xyz = [xcfg_A * xi for xi in fields[:3]] stru.addNewAtom(p_element, xyz=xyz) a = stru[-1] - _assign_auxiliaries(a, fields, auxiliaries=p_auxiliary, no_velocity=xcfg_NO_VELOCITY) + _assign_auxiliaries( + a, + fields, + auxiliaries=p_auxiliary, + no_velocity=xcfg_NO_VELOCITY, + ) else: emsg = "%d: invalid record" % p_nl raise StructureFormatError(emsg) @@ -319,19 +331,28 @@ def toLines(self, stru): # range of CFG coordinates must be less than 1 p_A = numpy.ceil(max_range_xyz + 1.0e-13) # atomeye draws rubbish when boxsize is less than 3.5 - hi_ucvect = max([numpy.sqrt(numpy.dot(v, v)) for v in stru.lattice.base]) + hi_ucvect = max( + [numpy.sqrt(numpy.dot(v, v)) for v in stru.lattice.base] + ) if hi_ucvect * p_A < 3.5: p_A = numpy.ceil(3.5 / hi_ucvect) lines.append("A = %.8g Angstrom" % p_A) # how much do we need to shift the coordinates? p_dxyz = numpy.zeros(3, dtype=float) for i in range(3): - if lo_xyz[i] / p_A < 0.0 or hi_xyz[i] / p_A >= 1.0 or (lo_xyz[i] == hi_xyz[i] and lo_xyz[i] == 0.0): + if ( + lo_xyz[i] / p_A < 0.0 + or hi_xyz[i] / p_A >= 1.0 + or (lo_xyz[i] == hi_xyz[i] and lo_xyz[i] == 0.0) + ): p_dxyz[i] = 0.5 - (hi_xyz[i] + lo_xyz[i]) / 2.0 / p_A # H0 tensor for i in range(3): for j in range(3): - lines.append("H0(%i,%i) = %.8g A" % (i + 1, j + 1, stru.lattice.base[i, j])) + lines.append( + "H0(%i,%i) = %.8g A" + % (i + 1, j + 1, stru.lattice.base[i, j]) + ) # get out for empty structure if len(stru) == 0: return lines @@ -343,7 +364,9 @@ def toLines(self, stru): # if stru came from xcfg file, it would store original auxiliaries in # xcfg dictionary try: - p_auxiliaries = [(aux, "a." + aux) for aux in stru.xcfg["auxiliaries"]] + p_auxiliaries = [ + (aux, "a." + aux) for aux in stru.xcfg["auxiliaries"] + ] except AttributeError: p_auxiliaries = [] # add occupancy if any atom has nonunit occupancy @@ -367,7 +390,9 @@ def toLines(self, stru): elif p_allUiso: p_auxiliaries.append(("Uiso", "uflat[0]")) else: - p_auxiliaries.extend([("U11", "uflat[0]"), ("U22", "uflat[4]"), ("U33", "uflat[8]")]) + p_auxiliaries.extend( + [("U11", "uflat[0]"), ("U22", "uflat[4]"), ("U33", "uflat[8]")] + ) # check if there are off-diagonal elements allU = numpy.array([a.U for a in stru]) if numpy.any(allU[:, 0, 1] != 0.0): @@ -424,7 +449,7 @@ def getParser(): def _assign_auxiliaries(a, fields, auxiliaries, no_velocity): - """Assing auxiliary properties for `Atom` object when reading CFG format. + """Assign auxiliary properties for `Atom` object when reading CFG format. Parameters ---------- diff --git a/src/diffpy/structure/parsers/p_xyz.py b/src/diffpy/structure/parsers/p_xyz.py index 9d19e0c..706d117 100644 --- a/src/diffpy/structure/parsers/p_xyz.py +++ b/src/diffpy/structure/parsers/p_xyz.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Parser for XYZ file format, where +"""Parser for XYZ file format, where. * First line gives number of atoms. * Second line has optional title. @@ -78,11 +77,15 @@ def parseLines(self, lines): stru.title = lines[start + 1].strip() start += 2 else: - emsg = "%d: invalid XYZ format, missing number of atoms" % (start + 1) + emsg = "%d: invalid XYZ format, missing number of atoms" % ( + start + 1 + ) raise StructureFormatError(emsg) except (IndexError, ValueError): exc_type, exc_value, exc_traceback = sys.exc_info() - emsg = "%d: invalid XYZ format, missing number of atoms" % (start + 1) + emsg = "%d: invalid XYZ format, missing number of atoms" % ( + start + 1 + ) e = StructureFormatError(emsg) raise e.with_traceback(exc_traceback) # find the last valid record @@ -105,7 +108,10 @@ def parseLines(self, lines): if fields == []: continue elif len(fields) != nfields: - emsg = ("%d: all lines must have " + "the same number of columns") % p_nl + emsg = ( + "%d: all lines must have " + + "the same number of columns" + ) % p_nl raise StructureFormatError(emsg) element = fields[0] element = element[0].upper() + element[1:].lower() diff --git a/src/diffpy/structure/parsers/parser_index_mod.py b/src/diffpy/structure/parsers/parser_index_mod.py index 5bb6ba9..08271f3 100644 --- a/src/diffpy/structure/parsers/parser_index_mod.py +++ b/src/diffpy/structure/parsers/parser_index_mod.py @@ -12,9 +12,8 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Index of recognized structure formats, their IO capabilities and -associated modules where they are defined. +"""Index of recognized structure formats, their IO capabilities and associated +modules where they are defined. Attributes ---------- diff --git a/src/diffpy/structure/parsers/structureparser.py b/src/diffpy/structure/parsers/structureparser.py index 7a3ee3a..8ef14e5 100644 --- a/src/diffpy/structure/parsers/structureparser.py +++ b/src/diffpy/structure/parsers/structureparser.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Definition of StructureParser, a base class for specific parsers.""" @@ -41,7 +40,9 @@ def parseLines(self, lines): ---- This method has to be overloaded in derived class. """ - raise NotImplementedError("parseLines not defined for '%s' format" % self.format) + raise NotImplementedError( + "parseLines not defined for '%s' format" % self.format + ) return def toLines(self, stru): @@ -53,7 +54,9 @@ def toLines(self, stru): ---- This method has to be overloaded in derived class. """ - raise NotImplementedError("toLines not defined for '%s' format" % self.format) + raise NotImplementedError( + "toLines not defined for '%s' format" % self.format + ) def parse(self, s): """Create `Structure` instance from a string.""" diff --git a/src/diffpy/structure/pdffitstructure.py b/src/diffpy/structure/pdffitstructure.py index 414b10b..ee9cef9 100644 --- a/src/diffpy/structure/pdffitstructure.py +++ b/src/diffpy/structure/pdffitstructure.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Definition of PDFFitStructure class derived from Structure""" +"""Definition of PDFFitStructure class derived from Structure.""" from diffpy.structure.structure import Structure @@ -55,8 +54,8 @@ def __init__(self, *args, **kwargs): return def read(self, filename, format="auto"): - """Same as `Structure.read`, but update `spcgr` value in - `self.pdffit` when parser can get spacegroup. + """Same as `Structure.read`, but update `spcgr` value in `self.pdffit` + when parser can get spacegroup. See `Structure.read()` for more info. @@ -90,11 +89,11 @@ def readStr(self, s, format="auto"): s : str String with structure definition. format : str, Optional - All structure formats are defined in parsers submodule. When ``format == 'auto'``, - all parsers are tried one by one. + All structure formats are defined in parsers submodule. When + ``format == 'auto'``, all parsers are tried one by one. - Return - ------ + Returns + ------- StructureParser Instance of `StructureParser` used to load the data. """ diff --git a/src/diffpy/structure/sgtbxspacegroups.py b/src/diffpy/structure/sgtbxspacegroups.py index 0ba28fc..30aacfc 100644 --- a/src/diffpy/structure/sgtbxspacegroups.py +++ b/src/diffpy/structure/sgtbxspacegroups.py @@ -12,9 +12,9 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Extra space group representations generated using `sgtbx` module from `cctbx`. -Import of this module extends the `SpaceGroupList` in the `SpaceGroups` module. +"""Extra space group representations generated using `sgtbx` module from +`cctbx`. Import of this module extends the `SpaceGroupList` in the +`SpaceGroups` module. Attributes ---------- diff --git a/src/diffpy/structure/spacegroupmod.py b/src/diffpy/structure/spacegroupmod.py index 42fab5c..e860a80 100644 --- a/src/diffpy/structure/spacegroupmod.py +++ b/src/diffpy/structure/spacegroupmod.py @@ -7,71 +7,199 @@ import numpy -# 64 unique rotation matricies -Rot_Z_mY_X = numpy.array([[0.0, 0.0, 1.0], [0.0, -1.0, 0.0], [1.0, 0.0, 0.0]], float) -Rot_Y_mX_mZ = numpy.array([[0.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_XmY_X_mZ = numpy.array([[1.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_mX_Y_mZ = numpy.array([[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_X_mZ_Y = numpy.array([[1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]], float) -Rot_Y_mXY_Z = numpy.array([[0.0, 1.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_Y_mX_Z = numpy.array([[0.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_XmY_X_Z = numpy.array([[1.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_mX_mXY_mZ = numpy.array([[-1.0, 0.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_Y_Z_X = numpy.array([[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0]], float) -Rot_mY_mZ_X = numpy.array([[0.0, -1.0, 0.0], [0.0, 0.0, -1.0], [1.0, 0.0, 0.0]], float) -Rot_X_Z_mY = numpy.array([[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, -1.0, 0.0]], float) -Rot_XmY_mY_Z = numpy.array([[1.0, -1.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_Y_X_mZ = numpy.array([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_Y_mZ_X = numpy.array([[0.0, 1.0, 0.0], [0.0, 0.0, -1.0], [1.0, 0.0, 0.0]], float) -Rot_mXY_Y_Z = numpy.array([[-1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_mX_mY_mZ = numpy.array([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_X_Y_mZ = numpy.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_mXY_mX_Z = numpy.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_mZ_mY_mX = numpy.array([[0.0, 0.0, -1.0], [0.0, -1.0, 0.0], [-1.0, 0.0, 0.0]], float) -Rot_X_mZ_mY = numpy.array([[1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, -1.0, 0.0]], float) -Rot_X_Y_Z = numpy.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_mY_mX_mZ = numpy.array([[0.0, -1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_mY_X_Z = numpy.array([[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_Z_X_Y = numpy.array([[0.0, 0.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], float) -Rot_X_XmY_Z = numpy.array([[1.0, 0.0, 0.0], [1.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_mY_X_mZ = numpy.array([[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_mY_Z_mX = numpy.array([[0.0, -1.0, 0.0], [0.0, 0.0, 1.0], [-1.0, 0.0, 0.0]], float) -Rot_mY_Z_X = numpy.array([[0.0, -1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0]], float) -Rot_mX_mZ_mY = numpy.array([[-1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, -1.0, 0.0]], float) -Rot_mX_Z_Y = numpy.array([[-1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]], float) -Rot_mZ_mX_mY = numpy.array([[0.0, 0.0, -1.0], [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0]], float) -Rot_X_XmY_mZ = numpy.array([[1.0, 0.0, 0.0], [1.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_mY_XmY_mZ = numpy.array([[0.0, -1.0, 0.0], [1.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_Z_X_mY = numpy.array([[0.0, 0.0, 1.0], [1.0, 0.0, 0.0], [0.0, -1.0, 0.0]], float) -Rot_mZ_mY_X = numpy.array([[0.0, 0.0, -1.0], [0.0, -1.0, 0.0], [1.0, 0.0, 0.0]], float) -Rot_X_Z_Y = numpy.array([[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]], float) -Rot_Z_mX_mY = numpy.array([[0.0, 0.0, 1.0], [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0]], float) -Rot_mX_Z_mY = numpy.array([[-1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, -1.0, 0.0]], float) -Rot_X_mY_Z = numpy.array([[1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_mY_mX_Z = numpy.array([[0.0, -1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_Z_mY_mX = numpy.array([[0.0, 0.0, 1.0], [0.0, -1.0, 0.0], [-1.0, 0.0, 0.0]], float) -Rot_mX_mY_Z = numpy.array([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_Z_Y_X = numpy.array([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], float) -Rot_mZ_Y_mX = numpy.array([[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]], float) -Rot_Y_Z_mX = numpy.array([[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [-1.0, 0.0, 0.0]], float) -Rot_mY_XmY_Z = numpy.array([[0.0, -1.0, 0.0], [1.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_mXY_Y_mZ = numpy.array([[-1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_mZ_mX_Y = numpy.array([[0.0, 0.0, -1.0], [-1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], float) -Rot_mX_mZ_Y = numpy.array([[-1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]], float) -Rot_mX_Y_Z = numpy.array([[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_X_mY_mZ = numpy.array([[1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_mZ_X_Y = numpy.array([[0.0, 0.0, -1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], float) -Rot_Y_mZ_mX = numpy.array([[0.0, 1.0, 0.0], [0.0, 0.0, -1.0], [-1.0, 0.0, 0.0]], float) -Rot_mY_mZ_mX = numpy.array([[0.0, -1.0, 0.0], [0.0, 0.0, -1.0], [-1.0, 0.0, 0.0]], float) -Rot_mZ_Y_X = numpy.array([[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], float) -Rot_Z_Y_mX = numpy.array([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]], float) -Rot_mXY_mX_mZ = numpy.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_XmY_mY_mZ = numpy.array([[1.0, -1.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_Z_mX_Y = numpy.array([[0.0, 0.0, 1.0], [-1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], float) -Rot_mX_mXY_Z = numpy.array([[-1.0, 0.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float) -Rot_Y_mXY_mZ = numpy.array([[0.0, 1.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float) -Rot_mZ_X_mY = numpy.array([[0.0, 0.0, -1.0], [1.0, 0.0, 0.0], [0.0, -1.0, 0.0]], float) -Rot_Y_X_Z = numpy.array([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float) +# 64 unique rotation matrices +Rot_Z_mY_X = numpy.array( + [[0.0, 0.0, 1.0], [0.0, -1.0, 0.0], [1.0, 0.0, 0.0]], float +) +Rot_Y_mX_mZ = numpy.array( + [[0.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_XmY_X_mZ = numpy.array( + [[1.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_mX_Y_mZ = numpy.array( + [[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_X_mZ_Y = numpy.array( + [[1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]], float +) +Rot_Y_mXY_Z = numpy.array( + [[0.0, 1.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_Y_mX_Z = numpy.array( + [[0.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_XmY_X_Z = numpy.array( + [[1.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_mX_mXY_mZ = numpy.array( + [[-1.0, 0.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_Y_Z_X = numpy.array( + [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0]], float +) +Rot_mY_mZ_X = numpy.array( + [[0.0, -1.0, 0.0], [0.0, 0.0, -1.0], [1.0, 0.0, 0.0]], float +) +Rot_X_Z_mY = numpy.array( + [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, -1.0, 0.0]], float +) +Rot_XmY_mY_Z = numpy.array( + [[1.0, -1.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_Y_X_mZ = numpy.array( + [[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_Y_mZ_X = numpy.array( + [[0.0, 1.0, 0.0], [0.0, 0.0, -1.0], [1.0, 0.0, 0.0]], float +) +Rot_mXY_Y_Z = numpy.array( + [[-1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_mX_mY_mZ = numpy.array( + [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_X_Y_mZ = numpy.array( + [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_mXY_mX_Z = numpy.array( + [[-1.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_mZ_mY_mX = numpy.array( + [[0.0, 0.0, -1.0], [0.0, -1.0, 0.0], [-1.0, 0.0, 0.0]], float +) +Rot_X_mZ_mY = numpy.array( + [[1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, -1.0, 0.0]], float +) +Rot_X_Y_Z = numpy.array( + [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_mY_mX_mZ = numpy.array( + [[0.0, -1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_mY_X_Z = numpy.array( + [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_Z_X_Y = numpy.array( + [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], float +) +Rot_X_XmY_Z = numpy.array( + [[1.0, 0.0, 0.0], [1.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_mY_X_mZ = numpy.array( + [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_mY_Z_mX = numpy.array( + [[0.0, -1.0, 0.0], [0.0, 0.0, 1.0], [-1.0, 0.0, 0.0]], float +) +Rot_mY_Z_X = numpy.array( + [[0.0, -1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0]], float +) +Rot_mX_mZ_mY = numpy.array( + [[-1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, -1.0, 0.0]], float +) +Rot_mX_Z_Y = numpy.array( + [[-1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]], float +) +Rot_mZ_mX_mY = numpy.array( + [[0.0, 0.0, -1.0], [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0]], float +) +Rot_X_XmY_mZ = numpy.array( + [[1.0, 0.0, 0.0], [1.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_mY_XmY_mZ = numpy.array( + [[0.0, -1.0, 0.0], [1.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_Z_X_mY = numpy.array( + [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0], [0.0, -1.0, 0.0]], float +) +Rot_mZ_mY_X = numpy.array( + [[0.0, 0.0, -1.0], [0.0, -1.0, 0.0], [1.0, 0.0, 0.0]], float +) +Rot_X_Z_Y = numpy.array( + [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]], float +) +Rot_Z_mX_mY = numpy.array( + [[0.0, 0.0, 1.0], [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0]], float +) +Rot_mX_Z_mY = numpy.array( + [[-1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, -1.0, 0.0]], float +) +Rot_X_mY_Z = numpy.array( + [[1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_mY_mX_Z = numpy.array( + [[0.0, -1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_Z_mY_mX = numpy.array( + [[0.0, 0.0, 1.0], [0.0, -1.0, 0.0], [-1.0, 0.0, 0.0]], float +) +Rot_mX_mY_Z = numpy.array( + [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_Z_Y_X = numpy.array( + [[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], float +) +Rot_mZ_Y_mX = numpy.array( + [[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]], float +) +Rot_Y_Z_mX = numpy.array( + [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [-1.0, 0.0, 0.0]], float +) +Rot_mY_XmY_Z = numpy.array( + [[0.0, -1.0, 0.0], [1.0, -1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_mXY_Y_mZ = numpy.array( + [[-1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_mZ_mX_Y = numpy.array( + [[0.0, 0.0, -1.0], [-1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], float +) +Rot_mX_mZ_Y = numpy.array( + [[-1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]], float +) +Rot_mX_Y_Z = numpy.array( + [[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_X_mY_mZ = numpy.array( + [[1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_mZ_X_Y = numpy.array( + [[0.0, 0.0, -1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], float +) +Rot_Y_mZ_mX = numpy.array( + [[0.0, 1.0, 0.0], [0.0, 0.0, -1.0], [-1.0, 0.0, 0.0]], float +) +Rot_mY_mZ_mX = numpy.array( + [[0.0, -1.0, 0.0], [0.0, 0.0, -1.0], [-1.0, 0.0, 0.0]], float +) +Rot_mZ_Y_X = numpy.array( + [[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], float +) +Rot_Z_Y_mX = numpy.array( + [[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]], float +) +Rot_mXY_mX_mZ = numpy.array( + [[-1.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_XmY_mY_mZ = numpy.array( + [[1.0, -1.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_Z_mX_Y = numpy.array( + [[0.0, 0.0, 1.0], [-1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], float +) +Rot_mX_mXY_Z = numpy.array( + [[-1.0, 0.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, 1.0]], float +) +Rot_Y_mXY_mZ = numpy.array( + [[0.0, 1.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, -1.0]], float +) +Rot_mZ_X_mY = numpy.array( + [[0.0, 0.0, -1.0], [1.0, 0.0, 0.0], [0.0, -1.0, 0.0]], float +) +Rot_Y_X_Z = numpy.array( + [[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], float +) # 32 unique translation vectors Tr_0_0_34 = numpy.array([0.0, 0.0, 3.0 / 4.0], float) @@ -137,9 +265,24 @@ def __init__(self, R, t): def __str__(self): """Printable representation of this SymOp object.""" - x = "[%6.3f %6.3f %6.3f %6.3f]\n" % (self.R[0, 0], self.R[0, 1], self.R[0, 2], self.t[0]) - x += "[%6.3f %6.3f %6.3f %6.3f]\n" % (self.R[1, 0], self.R[1, 1], self.R[1, 2], self.t[1]) - x += "[%6.3f %6.3f %6.3f %6.3f]\n" % (self.R[2, 0], self.R[2, 1], self.R[2, 2], self.t[2]) + x = "[%6.3f %6.3f %6.3f %6.3f]\n" % ( + self.R[0, 0], + self.R[0, 1], + self.R[0, 2], + self.t[0], + ) + x += "[%6.3f %6.3f %6.3f %6.3f]\n" % ( + self.R[1, 0], + self.R[1, 1], + self.R[1, 2], + self.t[1], + ) + x += "[%6.3f %6.3f %6.3f %6.3f]\n" % ( + self.R[2, 0], + self.R[2, 1], + self.R[2, 2], + self.t[2], + ) return x def __call__(self, vec): @@ -163,7 +306,9 @@ def __eq__(self, symop): Return ``True`` when *self* and *symop* difference is within tiny round-off errors. """ - return numpy.allclose(self.R, symop.R) and numpy.allclose(self.t, symop.t) + return numpy.allclose(self.R, symop.R) and numpy.allclose( + self.t, symop.t + ) def is_identity(self): """Check if this SymOp is an identity operation. @@ -174,7 +319,9 @@ def is_identity(self): ``True`` if this is an identity operation within a small round-off. Return ``False`` otherwise. """ - rv = numpy.allclose(self.R, numpy.identity(3, float)) and numpy.allclose(self.t, numpy.zeros(3, float)) + rv = numpy.allclose( + self.R, numpy.identity(3, float) + ) and numpy.allclose(self.t, numpy.zeros(3, float)) return rv @@ -256,7 +403,10 @@ def __init__( def __repr__(self): """Return a string representation of the space group.""" - return ("SpaceGroup #%i (%s, %s). Symmetry matrices: %i, " "point sym. matr.: %i") % ( + return ( + "SpaceGroup #%i (%s, %s). Symmetry matrices: %i, " + "point sym. matr.: %i" + ) % ( self.number, self.short_name, self.crystal_system[0] + self.crystal_system[1:].lower(), diff --git a/src/diffpy/structure/spacegroups.py b/src/diffpy/structure/spacegroups.py index 414d56e..c391373 100644 --- a/src/diffpy/structure/spacegroups.py +++ b/src/diffpy/structure/spacegroups.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Space group classes and definitions from mmLib and sgtbx. Attributes @@ -768,6 +767,7 @@ def _hashSymOpList(symops): def _buildSGLookupTable(): """Rebuild space group lookup table from the `SpaceGroupList` data. + This routine updates the global `_sg_lookup_table` dictionary. """ _sg_lookup_table.clear() diff --git a/src/diffpy/structure/structure.py b/src/diffpy/structure/structure.py index aee9ed7..d3c429c 100644 --- a/src/diffpy/structure/structure.py +++ b/src/diffpy/structure/structure.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """This module defines class `Structure`.""" import codecs @@ -22,13 +21,18 @@ from diffpy.structure.atom import Atom from diffpy.structure.lattice import Lattice -from diffpy.structure.utils import _linkAtomAttribute, atomBareSymbol, isiterable +from diffpy.structure.utils import ( + _linkAtomAttribute, + atomBareSymbol, + isiterable, +) # ---------------------------------------------------------------------------- class Structure(list): - """Define group of atoms in a specified lattice. Structure --> group of atoms. + """Define group of atoms in a specified lattice. Structure --> group of + atoms. `Structure` class is inherited from Python `list`. It contains a list of `Atom` instances. `Structure` overloads `setitem` and `setslice` @@ -53,8 +57,8 @@ class Structure(list): Note ---- - Cannot use `filename` and `atoms` arguments together. Overrides `atoms` argument - when `filename` is specified. + Cannot use `filename` and `atoms` arguments together. Overrides `atoms` + argument when `filename` is specified. Attributes ---------- @@ -86,7 +90,9 @@ class Structure(list): pdffit = None """None: default values for `pdffit`.""" - def __init__(self, atoms=None, lattice=None, title=None, filename=None, format=None): + def __init__( + self, atoms=None, lattice=None, title=None, filename=None, format=None + ): # if filename is specified load it and return if filename is not None: if any((atoms, lattice, title)): @@ -183,7 +189,8 @@ def assignUniqueLabels(self): return def distance(self, aid0, aid1): - """Calculate distance between 2 `Atoms`, no periodic boundary conditions. + """Calculate distance between 2 `Atoms`, no periodic boundary + conditions. Parameters ---------- @@ -207,15 +214,15 @@ def distance(self, aid0, aid1): return self.lattice.dist(a0.xyz, a1.xyz) def angle(self, aid0, aid1, aid2): - """ - The bond angle at the second of three `Atoms` in degrees. + """The bond angle at the second of three `Atoms` in degrees. Parameters ---------- aid0 : int or str Zero based index of the first `Atom` or a string label. aid1 : int or str - Index or string label for the second atom, where the angle is formed. + Index or string label for the second atom, where the angle is + formed. aid2 : int or str Index or string label for the third atom. @@ -235,7 +242,7 @@ def angle(self, aid0, aid1, aid2): return self.lattice.angle(u10, u12) def placeInLattice(self, new_lattice): - """place structure into `new_lattice` coordinate system. + """Place structure into `new_lattice` coordinate system. Sets `lattice` to `new_lattice` and recalculate fractional coordinates of all `Atoms` so their absolute positions remain the same. @@ -305,8 +312,8 @@ def readStr(self, s, format="auto"): s : str String with structure definition. format : str, Optional - All structure formats are defined in parsers submodule. When ``format == 'auto'``, - all parsers are tried one by one. + All structure formats are defined in parsers submodule. When + ``format == 'auto'``, all parsers are tried one by one. Returns ------- @@ -352,7 +359,7 @@ def write(self, filename, format): return def writeStr(self, format): - """return string representation of the structure in specified format. + """Return string representation of the structure in specified format. Note ---- @@ -381,7 +388,8 @@ def append(self, a, copy=True): a : Atom Instance of `Atom` to be appended. copy : bool, Optional - Flag for appending a copy of `a`. When ``False``, append `a` and update `a.lattice`. + Flag for appending a copy of `a`. When ``False``, append `a` and + update `a.lattice`. """ adup = copy and Atom(a) or a adup.lattice = self.lattice @@ -398,7 +406,8 @@ def insert(self, idx, a, copy=True): a : Atom Instance of `Atom` to be inserted. copy : bool, Optional - Flag for inserting a copy of `a`. When ``False``, append `a` and update `a.lattice`. + Flag for inserting a copy of `a`. When ``False``, append `a` + and update `a.lattice`. """ adup = copy and copymod.copy(a) or a adup.lattice = self.lattice @@ -450,7 +459,7 @@ def __getitem__(self, idx): Parameters ---------- - idx : int ot str ot Iterable + idx : int or str or Iterable `Atom` identifier. When integer use standard list lookup. For iterables use numpy lookup, this supports integer or boolean flag arrays. For string or string-containing iterables @@ -497,7 +506,9 @@ def __getitem__(self, idx): pass # check if there is any string label that should be resolved scalarstringlabel = isinstance(idx, str) - hasstringlabel = scalarstringlabel or (isiterable(idx) and any(isinstance(ii, str) for ii in idx)) + hasstringlabel = scalarstringlabel or ( + isiterable(idx) and any(isinstance(ii, str) for ii in idx) + ) # if not, use numpy indexing to resolve idx if not hasstringlabel: idx1 = idx @@ -563,7 +574,7 @@ def _fixlat(a): keep = set(super(Structure, self).__getitem__(idx)) v1 = (a if a in keep else Atom(a) for a in value) vfinal = filter(_fixlat, v1) - # handle scalar assingment + # handle scalar assignment else: vfinal = Atom(value) if copy else value vfinal.lattice = self.lattice @@ -660,9 +671,9 @@ def __mul__(self, n): __rmul__ = __mul__ def __imul__(self, n): - """Concatenate this `Structure` to n-times more `Atoms`. - For positive multiple the current `Atom` objects remain at the - beginning of this `Structure`. + """Concatenate this `Structure` to n-times more `Atoms`. For positive + multiple the current `Atom` objects remain at the beginning of this + `Structure`. Parameters ---------- @@ -693,7 +704,11 @@ def _set_lattice(self, value): self._lattice = value return - lattice = property(_get_lattice, _set_lattice, doc="Coordinate system for this `Structure`.") + lattice = property( + _get_lattice, + _set_lattice, + doc="Coordinate system for this `Structure`.", + ) # composition @@ -703,7 +718,10 @@ def _get_composition(self): rv[a.element] = rv.get(a.element, 0.0) + a.occupancy return rv - composition = property(_get_composition, doc="Dictionary of chemical symbols and their total occupancies.") + composition = property( + _get_composition, + doc="Dictionary of chemical symbols and their total occupancies.", + ) # linked atom attributes @@ -858,7 +876,8 @@ def _get_composition(self): # Private Methods -------------------------------------------------------- def __emptySharedStructure(self): - """Return empty `Structure` with standard attributes same as in self.""" + """Return empty `Structure` with standard attributes same as in + self.""" rv = Structure() rv.__dict__.update([(k, getattr(self, k)) for k in rv.__dict__]) return rv diff --git a/src/diffpy/structure/structureerrors.py b/src/diffpy/structure/structureerrors.py index 8967936..f9a2aae 100644 --- a/src/diffpy/structure/structureerrors.py +++ b/src/diffpy/structure/structureerrors.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Exceptions used in Structure package.""" diff --git a/src/diffpy/structure/symmetryutilities.py b/src/diffpy/structure/symmetryutilities.py index b58e7c8..a69f253 100644 --- a/src/diffpy/structure/symmetryutilities.py +++ b/src/diffpy/structure/symmetryutilities.py @@ -12,9 +12,8 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Symmetry utility functions such as expansion of asymmetric unit, -and generation of positional constraints. +"""Symmetry utility functions such as expansion of asymmetric unit, and +generation of positional constraints. Attributes ---------- @@ -82,7 +81,9 @@ def check_tetragonal(): return a == b and alpha == beta == gamma == 90 def check_trigonal(): - rv = (a == b == c and alpha == beta == gamma) or (a == b and alpha == beta == 90 and gamma == 120) + rv = (a == b == c and alpha == beta == gamma) or ( + a == b and alpha == beta == 90 and gamma == 120 + ) return rv def check_hexagonal(): @@ -108,7 +109,9 @@ def check_cubic(): # isconstantFormula runs faster when regular expression is not # compiled per every single call. -_rx_constant_formula = re.compile(r"[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)??(/[-+]?\d+)?$") +_rx_constant_formula = re.compile( + r"[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)??(/[-+]?\d+)?$" +) def isconstantFormula(s): @@ -122,7 +125,8 @@ def isconstantFormula(s): Return ------ bool - ``True`` when argument is a floating point number or a fraction of float with integer. + ``True`` when argument is a floating point number or a fraction of + float with integer. """ res = _rx_constant_formula.match(s.replace(" ", "")) return bool(res) @@ -132,9 +136,9 @@ def isconstantFormula(s): class _Position2Tuple(object): - """Create callable object that converts fractional coordinates to - a tuple of integers with given precision. For presision close to zero - it will return a tuples of double. + """Create callable object that converts fractional coordinates to a tuple + of integers with given precision. For presision close to zero it will + return a tuples of double. Note ---- @@ -148,7 +152,7 @@ class _Position2Tuple(object): Attributes ---------- eps : float - Cutoff for equivalent coordinates. When two coordiantes map to the + Cutoff for equivalent coordinates. When two coordinates map to the same tuple, they are closer than `eps`. """ @@ -419,10 +423,27 @@ class GeneratorSite(object): ) """numpy.ndarray: 6x3x3 array of independent components of U matrices.""" - idx2Usymbol = {0: "U11", 1: "U12", 2: "U13", 3: "U12", 4: "U22", 5: "U23", 6: "U13", 7: "U23", 8: "U33"} + idx2Usymbol = { + 0: "U11", + 1: "U12", + 2: "U13", + 3: "U12", + 4: "U22", + 5: "U23", + 6: "U13", + 7: "U23", + 8: "U33", + } """dict: Mapping of index to standard U symbol.""" - def __init__(self, spacegroup, xyz, Uij=numpy.zeros((3, 3)), sgoffset=[0, 0, 0], eps=None): + def __init__( + self, + spacegroup, + xyz, + Uij=numpy.zeros((3, 3)), + sgoffset=[0, 0, 0], + eps=None, + ): if eps is None: eps = epsilon # just declare the members @@ -445,14 +466,18 @@ def __init__(self, spacegroup, xyz, Uij=numpy.zeros((3, 3)), sgoffset=[0, 0, 0], invariants = _findInvariants(ops) # shift self.xyz exactly to the special position if mult > 1: - xyzdups = numpy.array([op(xyz + self.sgoffset) - self.sgoffset for op in invariants]) + xyzdups = numpy.array( + [op(xyz + self.sgoffset) - self.sgoffset for op in invariants] + ) dxyz = xyzdups - xyz dxyz = numpy.mean(dxyz - dxyz.round(), axis=0) # recalculate if needed if numpy.any(dxyz != 0.0): self.xyz = xyz + dxyz self.xyz[numpy.fabs(self.xyz) < self.eps] = 0.0 - sites, ops, mult = expandPosition(spacegroup, self.xyz, self.sgoffset, eps) + sites, ops, mult = expandPosition( + spacegroup, self.xyz, self.sgoffset, eps + ) invariants = _findInvariants(ops) # self.xyz, sites, ops are all adjusted here self.eqxyz = sites @@ -495,6 +520,7 @@ def signedRatStr(self, x): def _findNullSpace(self): """Calculate `self.null_space` from `self.invariants`. + Try to represent `self.null_space` using small integers. """ R0 = self.invariants[0].R @@ -542,12 +568,18 @@ def _findPosParameters(self): def _findUSpace(self): """Find independent U components with respect to invariant - rotations. - """ + rotations.""" n = len(self.invariants) R6zall = numpy.tile(-numpy.identity(6, dtype=float), (n, 1)) R6zall_iter = numpy.split(R6zall, n, axis=0) - i6kl = ((0, (0, 0)), (1, (1, 1)), (2, (2, 2)), (3, (0, 1)), (4, (0, 2)), (5, (1, 2))) + i6kl = ( + (0, (0, 0)), + (1, (1, 1)), + (2, (2, 2)), + (3, (0, 1)), + (4, (0, 2)), + (5, (1, 2)), + ) for op, R6z in zip(self.invariants, R6zall_iter): R = op.R for j, Ucj in enumerate(self.Ucomponents): @@ -575,7 +607,9 @@ def _findUParameters(self): for Usp in self.Uspace: Uspflat = Usp.flatten() Uspnorm2 = numpy.dot(Uspflat, Uspflat) - permidx = next(i for i, x in enumerate(Uspflat[diagorder]) if x == 1) + permidx = next( + i for i, x in enumerate(Uspflat[diagorder]) if x == 1 + ) idx = diagorder[permidx] vname = self.idx2Usymbol[idx] varvalue = numpy.dot(Uijflat, Uspflat) / Uspnorm2 @@ -583,7 +617,8 @@ def _findUParameters(self): return def _findeqUij(self): - """Adjust `self.Uij` and `self.eqUij` to be consistent with spacegroup.""" + """Adjust `self.Uij` and `self.eqUij` to be consistent with + spacegroup.""" self.Uij = numpy.zeros((3, 3), dtype=float) for i in range(len(self.Uparameters)): Usp = self.Uspace[i] @@ -610,9 +645,10 @@ def positionFormula(self, pos, xyzsymbols=("x", "y", "z")): Return ------ dict - Position formulas in a dictionary with keys equal ``("x", "y", "z")`` - or an empty dictionary when pos is not equivalent to generator. - Formulas are formatted as ``[[-][%g*]{x|y|z}] [{+|-}%g]``, for example + Position formulas in a dictionary with keys equal + ``("x", "y", "z")`` or an empty dictionary when pos is not + equivalent to generator. Formulas are formatted as + ``[[-][%g*]{x|y|z}] [{+|-}%g]``, for example ``-x``, ``z +0.5``, ``0.25``. """ # find pos in eqxyz @@ -635,14 +671,19 @@ def positionFormula(self, pos, xyzsymbols=("x", "y", "z")): for i in range(3): if abs(nvec[i]) < epsilon: continue - xyzformula[i] += "%s*%s " % (self.signedRatStr(nvec[i]), name2sym[vname]) + xyzformula[i] += "%s*%s " % ( + self.signedRatStr(nvec[i]), + name2sym[vname], + ) # add constant offset teqpos to all formulas for i in range(3): if xyzformula[i] and abs(teqpos[i]) < epsilon: continue xyzformula[i] += self.signedRatStr(teqpos[i]) # reduce unnecessary +1* and -1* - xyzformula = [re.sub("^[+]1[*]|(?<=[+-])1[*]", "", f).strip() for f in xyzformula] + xyzformula = [ + re.sub("^[+]1[*]|(?<=[+-])1[*]", "", f).strip() for f in xyzformula + ] return dict(zip(("x", "y", "z"), xyzformula)) def UFormula(self, pos, Usymbols=stdUsymbols): @@ -753,7 +794,9 @@ class ExpandAsymmetricUnit(object): # By design Atom instances are not accepted as arguments to keep # number of required imports low. - def __init__(self, spacegroup, corepos, coreUijs=None, sgoffset=[0, 0, 0], eps=None): + def __init__( + self, spacegroup, corepos, coreUijs=None, sgoffset=[0, 0, 0], eps=None + ): if eps is None: eps = epsilon # declare data members @@ -773,7 +816,9 @@ def __init__(self, spacegroup, corepos, coreUijs=None, sgoffset=[0, 0, 0], eps=N else: self.coreUijs = numpy.zeros((corelen, 3, 3), dtype=float) for cpos, cUij in zip(self.corepos, self.coreUijs): - gen = GeneratorSite(self.spacegroup, cpos, cUij, self.sgoffset, self.eps) + gen = GeneratorSite( + self.spacegroup, cpos, cUij, self.sgoffset, self.eps + ) self.multiplicity.append(gen.multiplicity) self.Uisotropy.append(gen.Uisotropy) self.expandedpos.append(gen.eqxyz) @@ -861,7 +906,9 @@ class SymmetryConstraints(object): List of bool flags for isotropic thermal displacements. """ - def __init__(self, spacegroup, positions, Uijs=None, sgoffset=[0, 0, 0], eps=None): + def __init__( + self, spacegroup, positions, Uijs=None, sgoffset=[0, 0, 0], eps=None + ): if eps is None: eps = epsilon # fill in data members @@ -902,11 +949,14 @@ def __init__(self, spacegroup, positions, Uijs=None, sgoffset=[0, 0, 0], eps=Non return def _findConstraints(self): - """Find constraints for positions and anisotropic displacements `Uij`.""" + """Find constraints for positions and anisotropic displacements + `Uij`.""" numpos = len(self.positions) # canonical xyzsymbols and Usymbols xyzsymbols = [smbl + str(i) for i in range(numpos) for smbl in "xyz"] - Usymbols = [smbl + str(i) for i in range(numpos) for smbl in stdUsymbols] + Usymbols = [ + smbl + str(i) for i in range(numpos) for smbl in stdUsymbols + ] independent = set(range(numpos)) for genidx in range(numpos): if genidx not in independent: @@ -915,7 +965,9 @@ def _findConstraints(self): self.coremap[genidx] = [] genpos = self.positions[genidx] genUij = self.Uijs[genidx] - gen = GeneratorSite(self.spacegroup, genpos, genUij, self.sgoffset, self.eps) + gen = GeneratorSite( + self.spacegroup, genpos, genUij, self.sgoffset, self.eps + ) # append new pparameters if there are any gxyzsymbols = xyzsymbols[3 * genidx : 3 * (genidx + 1)] for k, v in gen.pparameters: @@ -970,13 +1022,16 @@ def positionFormulas(self, xyzsymbols=None): list List of coordinate formulas dictionaries. Formulas dictionary keys are from ``("x", "y", "z")`` and the values are formatted as - ``[[-]{symbol}] [{+|-}%g]``, for example: ``x0``, ``-sym``, ``@7 +0.5``, ``0.25``. + ``[[-]{symbol}] [{+|-}%g]``, for example: ``x0``, ``-sym``, + ``@7 +0.5``, ``0.25``. """ if not xyzsymbols: return list(self.poseqns) # check xyzsymbols if len(xyzsymbols) < len(self.pospars): - emsg = "Not enough symbols for %i position parameters" % len(self.pospars) + emsg = "Not enough symbols for %i position parameters" % len( + self.pospars + ) raise SymmetryError(emsg) # build translation dictionary trsmbl = dict(zip(self.posparSymbols(), xyzsymbols)) @@ -1010,7 +1065,10 @@ def positionFormulasPruned(self, xyzsymbols=None): list List of coordinate formula dictionaries. """ - rv = [pruneFormulaDictionary(eqns) for eqns in self.positionFormulas(xyzsymbols)] + rv = [ + pruneFormulaDictionary(eqns) + for eqns in self.positionFormulas(xyzsymbols) + ] return rv def UparSymbols(self): @@ -1033,8 +1091,9 @@ def UFormulas(self, Usymbols=None): ------ list List of atom displacement formula dictionaries per each site. - Formula dictionary keys are from ``('U11','U22','U33','U12','U13','U23')`` - and the values are formatted as ``{[%g*][Usymbol]|0}``, for example: + Formula dictionary keys are from + ``('U11','U22','U33','U12','U13','U23')`` and the values are + formatted as ``{[%g*][Usymbol]|0}``, for example: ``U11``, ``0.5*@37``, ``0``. """ if not Usymbols: @@ -1077,7 +1136,9 @@ def UFormulasPruned(self, Usymbols=None): List of atom displacement formulas in tuples of ``(U11, U22, U33, U12, U13, U23)``. """ - rv = [pruneFormulaDictionary(eqns) for eqns in self.UFormulas(Usymbols)] + rv = [ + pruneFormulaDictionary(eqns) for eqns in self.UFormulas(Usymbols) + ] return rv diff --git a/src/diffpy/structure/utils.py b/src/diffpy/structure/utils.py index 4fcee68..4424b15 100644 --- a/src/diffpy/structure/utils.py +++ b/src/diffpy/structure/utils.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Small shared functions.""" from collections.abc import Iterable as _Iterable @@ -39,13 +38,15 @@ def isfloat(s): def atomBareSymbol(smbl): """Remove atom type string stripped of isotope and ion charge symbols. - This function removes any blank, isotope numbers (0-9), leading hyphens (-), and ion charge - symbols (1-9)(+-) from the given atom type string, returning only the bare element symbol. + This function removes any blank, isotope numbers (0-9), + leading hyphens (-), and ion charge symbols (1-9)(+-) from the given atom + type string, returning only the bare element symbol. Parameters ---------- smbl : str - Atom type string that may include isotope numbers, ion charges, or hyphens. + Atom type string that may include isotope numbers, ion charges, or + hyphens. Returns ------- diff --git a/src/diffpy/structure/version.py b/src/diffpy/structure/version.py index be7f6a2..f3a5052 100644 --- a/src/diffpy/structure/version.py +++ b/src/diffpy/structure/version.py @@ -1,18 +1,17 @@ #!/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. # # See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.structure/graphs/contributors +# https://github.com/diffpy/diffpy.structure/graphs/contributors # noqa: E501 # # 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/tests/test_atom.py b/tests/test_atom.py index 67009e8..6c7dd32 100644 --- a/tests/test_atom.py +++ b/tests/test_atom.py @@ -12,10 +12,7 @@ # See LICENSE.txt for license information. # ############################################################################## - -""" -Unit tests for the Atom class. -""" +"""Unit tests for the Atom class.""" import unittest @@ -31,7 +28,7 @@ class TestAtom(unittest.TestCase): def test___init__(self): - """check Atom.__init__()""" + """Check Atom.__init__()""" a = Atom() self.assertEqual("", a.element) self.assertTrue((a.xyz == 0).all()) @@ -78,7 +75,7 @@ def test___init__(self): # return def test_xyz_cartn(self): - """check Atom.xyz_cartn property""" + """Check Atom.xyz_cartn property.""" hexagonal = Lattice(1, 1, 1, 90, 90, 120) a0 = Atom("C", [0, 0, 0], lattice=hexagonal) a1 = Atom("C", [1, 1, 1], lattice=hexagonal) diff --git a/tests/test_functions.py b/tests/test_functions.py new file mode 100644 index 0000000..64de21d --- /dev/null +++ b/tests/test_functions.py @@ -0,0 +1,40 @@ +import numpy as np +import pytest + +from diffpy.structure import functions # noqa + + +def test_dot_product_2D_list(): + a = [1, 2] + b = [3, 4] + expected = 11.0 + actual = functions.dot_product(a, b) + assert actual == expected + + +def test_dot_product_3D_list(): + a = [1, 2, 3] + b = [4, 5, 6] + expected = 32.0 + actual = functions.dot_product(a, b) + assert actual == expected + + +@pytest.mark.parametrize( + "a, b, expected", + [ + # Test whether the dot product function works with 2D and 3D vectors + # C1: lists, expect correct float output + ([1, 2], [3, 4], 11.0), + ([1, 2, 3], [4, 5, 6], 32.0), + # C2: tuples, expect correct float output + ((1, 2), (3, 4), 11.0), + ((1, 2, 3), (4, 5, 6), 32.0), + # C3: numpy arrays, expect correct float output + (np.array([1, 2]), np.array([3, 4]), 11.0), + (np.array([1, 2, 3]), np.array([4, 5, 6]), 32.0), + ], +) +def test_dot_product(a, b, expected): + actual = functions.dot_product(a, b) + assert actual == expected diff --git a/tests/test_lattice.py b/tests/test_lattice.py index c562d20..5965ea8 100644 --- a/tests/test_lattice.py +++ b/tests/test_lattice.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Unit tests for Lattice class.""" import unittest @@ -26,7 +25,7 @@ class TestLattice(unittest.TestCase): - """test methods of Lattice class""" + """Test methods of Lattice class.""" def setUp(self): self.lattice = Lattice() @@ -36,7 +35,12 @@ def setUp(self): def test___init__(self): """Check Lattice.__init__ processing of arguments.""" self.assertRaises(ValueError, Lattice, self.lattice, c=4) - self.assertRaises(ValueError, Lattice, base=self.lattice.base, baserot=self.lattice.baserot) + self.assertRaises( + ValueError, + Lattice, + base=self.lattice.base, + baserot=self.lattice.baserot, + ) self.assertRaises(ValueError, Lattice, 1, 2, 3) self.assertRaises(ValueError, Lattice, 1, 2, 3, 80, 90) L0 = self.lattice @@ -52,7 +56,7 @@ def test___init__(self): return def test_setLatPar(self): - """check calculation of standard unit cell vectors""" + """Check calculation of standard unit cell vectors.""" from math import cos, radians, sqrt from numpy import dot @@ -68,13 +72,19 @@ def cosd(x): self.assertAlmostEqual(1.0, norm(base[0]), self.places) self.assertAlmostEqual(2.0, norm(base[1]), self.places) self.assertAlmostEqual(3.0, norm(base[2]), self.places) - self.assertAlmostEqual(cosd(80.0), dot(base[1], base[2]) / (2 * 3), self.places) - self.assertAlmostEqual(cosd(100.0), dot(base[0], base[2]) / (1 * 3), self.places) - self.assertAlmostEqual(cosd(120.0), dot(base[0], base[1]) / (1 * 2), self.places) + self.assertAlmostEqual( + cosd(80.0), dot(base[1], base[2]) / (2 * 3), self.places + ) + self.assertAlmostEqual( + cosd(100.0), dot(base[0], base[2]) / (1 * 3), self.places + ) + self.assertAlmostEqual( + cosd(120.0), dot(base[0], base[1]) / (1 * 2), self.places + ) return def test_latpar_properties(self): - """check assignment to a, b, c, alpha, beta, gamma.""" + """Check assignment to a, b, c, alpha, beta, gamma.""" lat = self.lattice lat.a = 2 lat.b = 4 @@ -135,7 +145,7 @@ def test_readonly_properties(self): return def test_setLatBase(self): - """check calculation of unit cell rotation""" + """Check calculation of unit cell rotation.""" base = numpy.array([[1.0, 1.0, 0.0], [0.0, 1.0, 1.0], [1.0, 0.0, 1.0]]) self.lattice.setLatBase(base) self.assertAlmostEqual(self.lattice.a, numpy.sqrt(2.0), self.places) @@ -155,12 +165,20 @@ def test_setLatBase(self): self.assertTrue(numpy.allclose(base[1], self.lattice.base[1])) self.assertTrue(numpy.allclose(base[2], self.lattice.base[2])) # try base checking - self.assertRaises(LatticeError, self.lattice.setLatBase, [[1, 0, 0], [1, 0, 0], [0, 0, 1]]) - self.assertRaises(LatticeError, self.lattice.setLatBase, [[1, 0, 0], [0, 0, 1], [0, 1, 0]]) + self.assertRaises( + LatticeError, + self.lattice.setLatBase, + [[1, 0, 0], [1, 0, 0], [0, 0, 1]], + ) + self.assertRaises( + LatticeError, + self.lattice.setLatBase, + [[1, 0, 0], [0, 0, 1], [0, 1, 0]], + ) return def test_reciprocal(self): - """check calculation of reciprocal lattice.""" + """Check calculation of reciprocal lattice.""" r1 = self.lattice.reciprocal() self.assertEqual((1, 1, 1, 90, 90, 90), r1.abcABG()) L2 = Lattice(2, 4, 8, 90, 90, 90) @@ -171,7 +189,7 @@ def test_reciprocal(self): return def test_dot(self): - """check dot product of lattice vectors.""" + """Check dot product of lattice vectors.""" L = self.lattice L.setLatPar(gamma=120) self.assertAlmostEqual(-0.5, L.dot([1, 0, 0], [0, 1, 0]), self.places) @@ -183,7 +201,7 @@ def test_dot(self): return def test_norm(self): - """check norm of a lattice vector.""" + """Check norm of a lattice vector.""" self.assertEqual(1, self.lattice.norm([1, 0, 0])) u = numpy.array([[3, 4, 0], [1, 1, 1]]) self.assertTrue(numpy.allclose([5, 3**0.5], self.lattice.norm(u))) @@ -192,7 +210,7 @@ def test_norm(self): return def test_rnorm(self): - """check norm of a reciprocal vector.""" + """Check norm of a reciprocal vector.""" L = self.lattice L.setLatPar(1, 1.5, 2.3, 80, 95, 115) r = L.reciprocal() @@ -203,7 +221,7 @@ def test_rnorm(self): return def test_dist(self): - """check dist function for distance between lattice points.""" + """Check dist function for distance between lattice points.""" L = self.lattice L.setLatPar(1, 1.5, 2.3, 80, 95, 115) u = [0.1, 0.3, 0.7] @@ -219,7 +237,7 @@ def test_dist(self): return def test_angle(self): - """check angle calculation between lattice vectors.""" + """Check angle calculation between lattice vectors.""" from math import acos, degrees L = self.lattice @@ -228,7 +246,9 @@ def test_angle(self): v = [0.3, 0.7, 0.7] uc = L.cartesian(u) vc = L.cartesian(v) - a0 = degrees(acos(numpy.dot(uc, vc) / (numalg.norm(uc) * numalg.norm(vc)))) + a0 = degrees( + acos(numpy.dot(uc, vc) / (numalg.norm(uc) * numalg.norm(vc))) + ) self.assertAlmostEqual(a0, L.angle(u, v), self.places) self.assertAlmostEqual(a0, L.angle(v, u), self.places) u5 = numpy.tile(u, (5, 1)) @@ -239,7 +259,7 @@ def test_angle(self): return def test_repr(self): - """check string representation of this lattice""" + """Check string representation of this lattice.""" r = repr(self.lattice) self.assertEqual(r, "Lattice()") self.lattice.setLatPar(1, 2, 3, 10, 20, 30) diff --git a/tests/test_loadstructure.py b/tests/test_loadstructure.py index ad19742..ce14bcb 100644 --- a/tests/test_loadstructure.py +++ b/tests/test_loadstructure.py @@ -17,7 +17,7 @@ def prepare_fixture(self, datafile): self.datafile = datafile def test_xcfg(self): - """check loading of atomeye xcfg format""" + """Check loading of atomeye xcfg format.""" f = self.datafile("BubbleRaftShort.xcfg") stru = loadStructure(f) self.assertTrue(type(stru) is Structure) @@ -25,14 +25,14 @@ def test_xcfg(self): return def test_discus(self): - """check loading of discus file format""" + """Check loading of discus file format.""" f = self.datafile("Ni-discus.stru") stru = loadStructure(f) self.assertTrue(type(stru) is PDFFitStructure) return def test_cif(self): - """check loading of CIF file format""" + """Check loading of CIF file format.""" f = self.datafile("PbTe.cif") stru = loadStructure(f) self.assertTrue(isinstance(stru, Structure)) @@ -40,20 +40,20 @@ def test_cif(self): return def test_badfile(self): - """check loading of CIF file format""" + """Check loading of CIF file format.""" f = self.datafile("Ni-bad.stru") self.assertRaises(StructureFormatError, loadStructure, f) return def test_goodkwarg(self): - """check loading of CIF file and passing of parser keyword argument.""" + """Check loading of CIF file and passing of parser keyword argument.""" f = self.datafile("graphite.cif") stru = loadStructure(f, eps=1e-10) self.assertEqual(8, len(stru)) return def test_badkwarg(self): - """check loading of xyz file format with invalid keyword argument""" + """Check loading of xyz file format with invalid keyword argument.""" f = self.datafile("bucky.xyz") self.assertRaises(TypeError, loadStructure, f, eps=1e-10) return diff --git a/tests/test_p_cif.py b/tests/test_p_cif.py index d21b259..8cc7ab2 100644 --- a/tests/test_p_cif.py +++ b/tests/test_p_cif.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Unit tests for diffpy.structure.parsers.p_cif module""" +"""Unit tests for diffpy.structure.parsers.p_cif module.""" import unittest @@ -37,14 +36,14 @@ def tearDown(self): return def test_leading_float(self): - """check leading_float()""" + """Check leading_float()""" self.assertEqual(0.37, leading_float("0.37(3)")) self.assertEqual(0.37, leading_float("0.37ab\ncd")) self.assertRaises(ValueError, leading_float, "q1") return def test_getSymOp(self): - """check getSymOp()""" + """Check getSymOp()""" from diffpy.structure.spacegroups import Rot_X_mY_Z, SymOp, Tr_0_12_12 op = getSymOp("x,1/2-y,1/2+z") @@ -84,7 +83,7 @@ def tearDown(self): return def test_parse(self): - """check P_cif.parse()""" + """Check P_cif.parse()""" with open(self.pbteciffile) as fp1: sgood = fp1.read() with open(self.badciffile) as fp2: @@ -94,13 +93,15 @@ def test_parse(self): stru = ptest.parse(sgood) self.assertEqual(str(stru_check), str(stru)) self.assertEqual(str(stru_check.lattice), str(stru.lattice)) - self.assertEqual(pfile.spacegroup.short_name, ptest.spacegroup.short_name) + self.assertEqual( + pfile.spacegroup.short_name, ptest.spacegroup.short_name + ) ptestb = P_cif() self.assertRaises(StructureFormatError, ptestb.parse, sbad) return def test_parseLines(self): - """check P_cif.parseLines()""" + """Check P_cif.parseLines()""" with open(self.pbteciffile) as fp1: goodlines = fp1.readlines() with open(self.badciffile) as fp2: @@ -110,13 +111,15 @@ def test_parseLines(self): stru = ptest.parseLines(goodlines) self.assertEqual(str(stru_check), str(stru)) self.assertEqual(str(stru_check.lattice), str(stru.lattice)) - self.assertEqual(pfile.spacegroup.short_name, ptest.spacegroup.short_name) + self.assertEqual( + pfile.spacegroup.short_name, ptest.spacegroup.short_name + ) ptest2 = P_cif() self.assertRaises(StructureFormatError, ptest2.parseLines, badlines) return def test_parseFile(self): - """check P_cif.parseFile()""" + """Check P_cif.parseFile()""" # pbteciffile stru = self.pfile.parseFile(self.pbteciffile) self.assertEqual(8, len(stru)) @@ -136,7 +139,9 @@ def test_parseFile(self): self.assertEqual(0.0225566, a0.Uisoequiv) # badciffile pfile2 = P_cif() - self.assertRaises(StructureFormatError, pfile2.parseFile, self.badciffile) + self.assertRaises( + StructureFormatError, pfile2.parseFile, self.badciffile + ) # graphite pgraphite = P_cif() graphite = pgraphite.parseFile(self.graphiteciffile) @@ -199,7 +204,7 @@ def test_parseFile(self): # return def test_write_and_read(self): - """high-level check of P_cif.tostring()""" + """High-level check of P_cif.tostring()""" # high-level check stru_check = Structure() stru_check.read(self.cdsebulkpdffitfile) @@ -297,7 +302,11 @@ def test_spacegroup_isotropy(self): "verify site isotropy due to site symmetry." # remove the _atom_site_thermal_displace_type field with open(self.pbteciffile) as fp: - lines = [line.replace(" Uiso ", " ") for line in fp if "_atom_site_thermal_displace_type" not in line] + lines = [ + line.replace(" Uiso ", " ") + for line in fp + if "_atom_site_thermal_displace_type" not in line + ] ciftxt = "".join(lines) ptest = self.ptest stru = ptest.parse(ciftxt) @@ -344,7 +353,11 @@ def test_adp_type_iso(self): def test_adp_aniso_label(self): "verify ADP type setting from _atom_site_aniso_label loop" with open(self.teiciffile) as fp: - lines = [line.replace(" Uani ", " ") for line in fp if "_atom_site_adp_type" not in line] + lines = [ + line.replace(" Uani ", " ") + for line in fp + if "_atom_site_adp_type" not in line + ] ciftxt = "".join(lines) stru = self.ptest.parse(ciftxt) self.assertTrue(all(stru.anisotropy)) diff --git a/tests/test_p_discus.py b/tests/test_p_discus.py index 83f4494..175d539 100644 --- a/tests/test_p_discus.py +++ b/tests/test_p_discus.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Unit tests for diffpy.structure.parsers.p_discus module""" +"""Unit tests for diffpy.structure.parsers.p_discus module.""" import re import unittest @@ -27,7 +26,7 @@ class TestP_discus(unittest.TestCase): - """test Parser for PDFFit file format""" + """Test Parser for PDFFit file format.""" @pytest.fixture(autouse=True) def prepare_fixture(self, datafile): @@ -39,7 +38,7 @@ def setUp(self): self.places = 8 def test_read_discus_Ni(self): - """check reading of Ni structure in discus format""" + """Check reading of Ni structure in discus format.""" stru = self.stru stru.read(self.datafile("Ni-discus.stru"), self.format) f_title = "structure Ni FCC" @@ -60,7 +59,7 @@ def test_read_discus_Ni(self): return def test_except_other_formats(self): - """check exceptions when reading files in other formats""" + """Check exceptions when reading files in other formats.""" badfiles = [ "LiCl-bad.cif", "PbTe.cif", @@ -80,11 +79,13 @@ def test_except_other_formats(self): ] for ft in badfiles: ff = self.datafile(ft) - self.assertRaises(StructureFormatError, self.stru.read, ff, format=self.format) + self.assertRaises( + StructureFormatError, self.stru.read, ff, format=self.format + ) return def test_ignored_lines(self): - """check skipping of ignored lines in the header""" + """Check skipping of ignored lines in the header.""" r1 = "ignored record 1\n" r2 = "ignored record 2\n" with open(self.datafile("Ni-discus.stru")) as fp: @@ -96,11 +97,13 @@ def test_ignored_lines(self): self.assertEqual([r1.rstrip(), r2.rstrip()], p.ignored_lines) ni_lines.append(r1) s_s2 = "".join(ni_lines) - self.assertRaises(StructureFormatError, self.stru.readStr, s_s2, self.format) + self.assertRaises( + StructureFormatError, self.stru.readStr, s_s2, self.format + ) return def test_spdiameter_parsing(self): - """check parsing of spdiameter record from a file.""" + """Check parsing of spdiameter record from a file.""" stru = self.stru stru.read(self.datafile("Ni-discus.stru"), self.format) self.assertEqual(0, stru.pdffit["spdiameter"]) @@ -117,11 +120,13 @@ def test_spdiameter_parsing(self): ni_lines = fp.readlines() ni_lines.insert(3, "shape invalid, 7\n") sbad = "".join(ni_lines) - self.assertRaises(StructureFormatError, self.stru.readStr, sbad, format=self.format) + self.assertRaises( + StructureFormatError, self.stru.readStr, sbad, format=self.format + ) return def test_stepcut_parsing(self): - """check parsing of stepcut record from a file.""" + """Check parsing of stepcut record from a file.""" stru = self.stru stru.read(self.datafile("Ni-discus.stru"), self.format) self.assertEqual(0, stru.pdffit["stepcut"]) @@ -138,7 +143,9 @@ def test_stepcut_parsing(self): ni_lines = fp.readlines() ni_lines.insert(3, "shape invalid, 7\n") sbad = "".join(ni_lines) - self.assertRaises(StructureFormatError, self.stru.readStr, sbad, format=self.format) + self.assertRaises( + StructureFormatError, self.stru.readStr, sbad, format=self.format + ) return diff --git a/tests/test_p_pdffit.py b/tests/test_p_pdffit.py index b33b654..679ec9a 100644 --- a/tests/test_p_pdffit.py +++ b/tests/test_p_pdffit.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Unit tests for diffpy.structure.parsers.p_pdffit module""" +"""Unit tests for diffpy.structure.parsers.p_pdffit module.""" import re import unittest @@ -28,7 +27,7 @@ class TestP_pdffit(unittest.TestCase): - """test Parser for PDFFit file format""" + """Test Parser for PDFFit file format.""" @pytest.fixture(autouse=True) def prepare_fixture(self, datafile): @@ -40,7 +39,7 @@ def setUp(self): self.places = 8 def test_read_pdffit_ZnSb(self): - """check reading of ZnSb pdffit structure file""" + """Check reading of ZnSb pdffit structure file.""" stru = self.stru stru.read(self.datafile("ZnSb_RT_Q28X_VM_20_fxiso.rstr"), self.format) f_title = "Cell structure file of Zn4Sb3 #167 interstitial" @@ -85,7 +84,7 @@ def test_read_pdffit_ZnSb(self): self.assertAlmostEqual(s_sigo, f_sigo) def test_read_pdffit_Ni(self): - """check reading of Ni pdffit structure file""" + """Check reading of Ni pdffit structure file.""" stru = self.stru stru.read(self.datafile("Ni.stru"), self.format) f_title = "structure Ni FCC" @@ -118,7 +117,7 @@ def test_read_pdffit_Ni(self): self.assertAlmostEqual(s_o, f_o) def test_read_pdffit_Ni_prim123(self): - """check reading of Ni_prim supercell 1x2x3""" + """Check reading of Ni_prim supercell 1x2x3.""" stru = self.stru stru.read(self.datafile("Ni_prim123.stru"), self.format) s_lat = [ @@ -142,21 +141,34 @@ def test_read_pdffit_Ni_prim123(self): s_o = a5.occupancy f_o = 1.0 self.assertAlmostEqual(s_o, f_o) - s_U = [a5.U[ij[0], ij[1]] for ij in [(0, 0), (1, 1), (2, 2), (0, 1), (0, 2), (1, 2)]] + s_U = [ + a5.U[ij[0], ij[1]] + for ij in [(0, 0), (1, 1), (2, 2), (0, 1), (0, 2), (1, 2)] + ] f_U = 3 * [0.00126651] + 3 * [-0.00042217] for i in range(len(s_U)): self.assertAlmostEqual(s_U[i], f_U[i]) return def test_read_pdffit_bad(self): - """check exceptions when reading invalid pdffit file""" + """Check exceptions when reading invalid pdffit file.""" stru = self.stru - self.assertRaises(StructureFormatError, stru.read, self.datafile("Ni-bad.stru"), self.format) - self.assertRaises(StructureFormatError, stru.read, self.datafile("bucky.xyz"), self.format) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("Ni-bad.stru"), + self.format, + ) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("bucky.xyz"), + self.format, + ) return def test_writeStr_pdffit(self): - """check writing of normal xyz file""" + """Check writing of normal xyz file.""" stru = self.stru stru.read(self.datafile("Ni.stru"), self.format) with open(self.datafile("Ni.stru")) as fp: @@ -169,7 +181,7 @@ def test_writeStr_pdffit(self): return def test_huge_occupancy(self): - """check structure with huge occupancy can be read.""" + """Check structure with huge occupancy can be read.""" self.stru.read(self.datafile("Ni.stru"), self.format) self.stru[0].occupancy = 16e16 s_s = self.stru.writeStr(self.format) @@ -179,7 +191,7 @@ def test_huge_occupancy(self): return def test_ignored_lines(self): - """check skipping of ignored lines in the header""" + """Check skipping of ignored lines in the header.""" r1 = "ignored record 1" r2 = "ignored record 2" with open(self.datafile("Ni.stru")) as fp: @@ -191,11 +203,13 @@ def test_ignored_lines(self): self.assertEqual([r1, r2], p.ignored_lines) ni_lines.insert(-3, r1 + "\n") s_s2 = "".join(ni_lines) - self.assertRaises(StructureFormatError, self.stru.readStr, s_s2, self.format) + self.assertRaises( + StructureFormatError, self.stru.readStr, s_s2, self.format + ) return def test_spdiameter_parsing(self): - """check parsing of spdiameter record from a file.""" + """Check parsing of spdiameter record from a file.""" stru = self.stru stru.read(self.datafile("Ni.stru"), self.format) self.assertEqual(0, stru.pdffit["spdiameter"]) @@ -212,11 +226,13 @@ def test_spdiameter_parsing(self): ni_lines = fp.readlines() ni_lines.insert(3, "shape invalid, 7\n") sbad = "".join(ni_lines) - self.assertRaises(StructureFormatError, self.stru.readStr, sbad, format=self.format) + self.assertRaises( + StructureFormatError, self.stru.readStr, sbad, format=self.format + ) return def test_stepcut_parsing(self): - """check parsing of stepcut record from a file.""" + """Check parsing of stepcut record from a file.""" stru = self.stru stru.read(self.datafile("Ni.stru"), self.format) self.assertEqual(0, stru.pdffit["stepcut"]) @@ -233,7 +249,9 @@ def test_stepcut_parsing(self): ni_lines = fp.readlines() ni_lines.insert(3, "shape invalid, 7\n") sbad = "".join(ni_lines) - self.assertRaises(StructureFormatError, self.stru.readStr, sbad, format=self.format) + self.assertRaises( + StructureFormatError, self.stru.readStr, sbad, format=self.format + ) return diff --git a/tests/test_parsers.py b/tests/test_parsers.py index 55c5244..7723a2a 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Unit tests for structure.parsers module.""" import os @@ -30,7 +29,7 @@ class TestP_xyz(unittest.TestCase): - """test Parser for xyz file format""" + """Test Parser for xyz file format.""" @pytest.fixture(autouse=True) def prepare_fixture(self, datafile): @@ -53,7 +52,7 @@ def mktmpfile(self): return self.tmpnames[-1] def test_read_xyz(self): - """check reading of normal xyz file""" + """Check reading of normal xyz file.""" stru = self.stru stru.read(self.datafile("bucky.xyz"), self.format) s_els = [a.element for a in stru] @@ -62,16 +61,36 @@ def test_read_xyz(self): return def test_read_xyz_bad(self): - """check exceptions when reading invalid xyz file""" + """Check exceptions when reading invalid xyz file.""" stru = self.stru - self.assertRaises(StructureFormatError, stru.read, self.datafile("bucky-bad1.xyz"), self.format) - self.assertRaises(StructureFormatError, stru.read, self.datafile("bucky-bad2.xyz"), self.format) - self.assertRaises(StructureFormatError, stru.read, self.datafile("bucky-plain.xyz"), self.format) - self.assertRaises(StructureFormatError, stru.read, self.datafile("hexagon-raw.xy"), self.format) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("bucky-bad1.xyz"), + self.format, + ) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("bucky-bad2.xyz"), + self.format, + ) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("bucky-plain.xyz"), + self.format, + ) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("hexagon-raw.xy"), + self.format, + ) return def test_writeStr_xyz(self): - """check string representation of normal xyz file""" + """Check string representation of normal xyz file.""" stru = self.stru stru.title = "test of writeStr" stru.lattice = Lattice(1.0, 2.0, 3.0, 90.0, 90.0, 90.0) @@ -83,7 +102,7 @@ def test_writeStr_xyz(self): return def test_write_xyz(self): - """check writing of normal xyz file""" + """Check writing of normal xyz file.""" stru = self.stru stru.title = "test of writeStr" stru.lattice = Lattice(1.0, 2.0, 3.0, 90.0, 90.0, 90.0) @@ -104,7 +123,7 @@ def test_write_xyz(self): class TestP_rawxyz(unittest.TestCase): - """test Parser for rawxyz file format""" + """Test Parser for rawxyz file format.""" @pytest.fixture(autouse=True) def prepare_fixture(self, datafile): @@ -116,7 +135,7 @@ def setUp(self): return def test_read_plainxyz(self): - """check reading of a plain xyz file""" + """Check reading of a plain xyz file.""" stru = self.stru stru.read(self.datafile("bucky-plain.xyz"), self.format) s_els = [a.element for a in stru] @@ -125,13 +144,18 @@ def test_read_plainxyz(self): return def test_read_plainxyz_bad(self): - """check exceptions when reading invalid plain xyz file""" + """Check exceptions when reading invalid plain xyz file.""" stru = self.stru - self.assertRaises(StructureFormatError, stru.read, self.datafile("bucky-plain-bad.xyz"), self.format) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("bucky-plain-bad.xyz"), + self.format, + ) return def test_read_rawxyz(self): - """check reading of raw xyz file""" + """Check reading of raw xyz file.""" stru = self.stru stru.read(self.datafile("bucky-raw.xyz"), self.format) s_els = [a.element for a in stru] @@ -143,14 +167,24 @@ def test_read_rawxyz(self): return def test_read_rawxyz_bad(self): - """check exceptions when reading unsupported xy file""" + """Check exceptions when reading unsupported xy file.""" stru = self.stru - self.assertRaises(StructureFormatError, stru.read, self.datafile("hexagon-raw-bad.xyz"), self.format) - self.assertRaises(StructureFormatError, stru.read, self.datafile("hexagon-raw.xy"), self.format) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("hexagon-raw-bad.xyz"), + self.format, + ) + self.assertRaises( + StructureFormatError, + stru.read, + self.datafile("hexagon-raw.xy"), + self.format, + ) return def test_writeStr_rawxyz(self): - """check writing of normal xyz file""" + """Check writing of normal xyz file.""" stru = self.stru stru.title = "test of writeStr" stru.lattice = Lattice(1.0, 2.0, 3.0, 90.0, 90.0, 90.0) @@ -173,7 +207,7 @@ def test_writeStr_rawxyz(self): class TestP_pdb(unittest.TestCase): - """test Parser for PDB file format""" + """Test Parser for PDB file format.""" @pytest.fixture(autouse=True) def prepare_fixture(self, datafile): @@ -185,7 +219,7 @@ def setUp(self): self.places = 3 def test_read_pdb_arginine(self): - """check reading of arginine PDB file""" + """Check reading of arginine PDB file.""" stru = self.stru stru.read(self.datafile("arginine.pdb"), self.format) f_els = [ @@ -233,7 +267,7 @@ def test_read_pdb_arginine(self): self.assertTrue(numpy.allclose(a0.xyz, [0.735, 2.219, 1.389])) def test_rwStr_pdb_CdSe(self): - """check conversion to PDB file format""" + """Check conversion to PDB file format.""" stru = self.stru stru.read(self.datafile("CdSe_bulk.stru"), "pdffit") s = stru.writeStr(self.format) @@ -274,7 +308,7 @@ def test_rwStr_pdb_CdSe(self): class TestP_xcfg(unittest.TestCase): - """test Parser for XCFG file format""" + """Test Parser for XCFG file format.""" @pytest.fixture(autouse=True) def prepare_fixture(self, datafile): @@ -286,7 +320,7 @@ def setUp(self): self.places = 6 def test_read_xcfg(self): - """check reading of BubbleRaft XCFG file""" + """Check reading of BubbleRaft XCFG file.""" stru = self.stru stru.read(self.datafile("BubbleRaftShort.xcfg"), self.format) f_els = 500 * ["Ar"] @@ -306,7 +340,7 @@ def test_read_xcfg(self): return def test_rwStr_xcfg_CdSe(self): - """check conversion to XCFG file format""" + """Check conversion to XCFG file format.""" stru = self.stru stru.read(self.datafile("CdSe_bulk.stru"), "pdffit") s = stru.writeStr(self.format) diff --git a/tests/test_spacegroups.py b/tests/test_spacegroups.py index 996a73d..d8200c5 100644 --- a/tests/test_spacegroups.py +++ b/tests/test_spacegroups.py @@ -12,13 +12,17 @@ # See LICENSE.txt for license information. # ############################################################################## - -"""Unit tests for diffpy.structure.spacegroups""" +"""Unit tests for diffpy.structure.spacegroups.""" import unittest -from diffpy.structure.spacegroups import FindSpaceGroup, GetSpaceGroup, SpaceGroupList, _hashSymOpList +from diffpy.structure.spacegroups import ( + FindSpaceGroup, + GetSpaceGroup, + SpaceGroupList, + _hashSymOpList, +) # ---------------------------------------------------------------------------- @@ -82,7 +86,9 @@ def test_FindSpaceGroup(self): self.assertIsNot(sg123, sg123r) self.assertIsNot(sg123.symop_list, sg123r.symop_list) self.assertEqual(ops123[::-1], sg123r.symop_list) - self.assertEqual(_hashSymOpList(sg123.symop_list), _hashSymOpList(sg123r.symop_list)) + self.assertEqual( + _hashSymOpList(sg123.symop_list), _hashSymOpList(sg123r.symop_list) + ) self.assertIs(sg123, FindSpaceGroup(ops123[::-1], shuffle=True)) return @@ -95,27 +101,46 @@ def test__hashSymOpList(self): def test_spacegroup_representation(self): """Verify SpaceGroup.__repr__().""" self.assertEqual( - repr(GetSpaceGroup(1)), "SpaceGroup #1 (P1, Triclinic). Symmetry matrices: 1, point sym. matr.: 1" + repr(GetSpaceGroup(1)), + "SpaceGroup #1 (P1, Triclinic). Symmetry matrices: 1, " + "point sym. matr.: 1", ) self.assertEqual( - repr(GetSpaceGroup(3)), "SpaceGroup #3 (P2, Monoclinic). Symmetry matrices: 2, point sym. matr.: 2" + repr(GetSpaceGroup(3)), + "SpaceGroup #3 (P2, Monoclinic). Symmetry matrices: 2, " + "point sym. matr.: 2", ) self.assertEqual( repr(GetSpaceGroup(16)), - ("SpaceGroup #16 (P222, Orthorhombic). Symmetry matrices: 4, point sym. " "matr.: 4"), + ( + "SpaceGroup #16 (P222, Orthorhombic). Symmetry matrices: " + "4, point sym. " + "" + "matr.: 4" + ), ) self.assertEqual( - repr(GetSpaceGroup(75)), "SpaceGroup #75 (P4, Tetragonal). Symmetry matrices: 4, point sym. matr.: 4" + repr(GetSpaceGroup(75)), + "SpaceGroup #75 (P4, Tetragonal). " + "Symmetry matrices: 4, point sym. matr.: 4", ) self.assertEqual( - repr(GetSpaceGroup(143)), "SpaceGroup #143 (P3, Trigonal). Symmetry matrices: 3, point sym. matr.: 3" + repr(GetSpaceGroup(143)), + "SpaceGroup #143 (P3, Trigonal). " + "Symmetry matrices: 3, point sym. matr.: 3", ) self.assertEqual( - repr(GetSpaceGroup(168)), "SpaceGroup #168 (P6, Hexagonal). Symmetry matrices: 6, point sym. matr.: 6" + repr(GetSpaceGroup(168)), + "SpaceGroup #168 (P6, Hexagonal). " + "Symmetry matrices: 6, point sym. matr.: 6", ) self.assertEqual( repr(GetSpaceGroup(229)), - ("SpaceGroup #229 (Im-3m, Cubic). Symmetry matrices: 96, point sym. " "matr.: 48"), + ( + "SpaceGroup #229 (Im-3m, Cubic). Symmetry matrices: 96, " + "point sym. " + "matr.: 48" + ), ) return diff --git a/tests/test_structure.py b/tests/test_structure.py index d79f5ed..f8b4db2 100644 --- a/tests/test_structure.py +++ b/tests/test_structure.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Unit tests for Structure class.""" @@ -29,7 +28,7 @@ class TestStructure(unittest.TestCase): - """test methods of Structure class""" + """Test methods of Structure class.""" @pytest.fixture(autouse=True) def prepare_fixture(self, datafile): @@ -40,7 +39,10 @@ def prepare_fixture(self, datafile): _loaded_structures = {} def setUp(self): - self.stru = Structure([Atom("C", [0, 0, 0]), Atom("C", [1, 1, 1])], lattice=Lattice(1, 1, 1, 90, 90, 120)) + self.stru = Structure( + [Atom("C", [0, 0, 0]), Atom("C", [1, 1, 1])], + lattice=Lattice(1, 1, 1, 90, 90, 120), + ) # useful variables if not self._loaded_structures: @@ -56,11 +58,15 @@ def setUp(self): return def test___init__(self): - """check Structure.__init__()""" + """Check Structure.__init__()""" atoms = [Atom("C", [0, 0, 0]), Atom("C", [0.5, 0.5, 0.5])] self.assertRaises(ValueError, Structure, atoms, filename=self.teifile) - self.assertRaises(ValueError, Structure, lattice=Lattice(), filename=self.teifile) - self.assertRaises(ValueError, Structure, title="test", filename=self.teifile) + self.assertRaises( + ValueError, Structure, lattice=Lattice(), filename=self.teifile + ) + self.assertRaises( + ValueError, Structure, title="test", filename=self.teifile + ) stru1 = Structure(title="my title") self.assertEqual("my title", stru1.title) stru2a = Structure(atoms) @@ -72,7 +78,7 @@ def test___init__(self): return def test_copy(self): - """check Structure.copy()""" + """Check Structure.copy()""" class MyDerivedStructure(Structure): def __copy__(self): @@ -96,7 +102,7 @@ def __copy__(self): return def test___copy__(self): - """check Structure.__copy__()""" + """Check Structure.__copy__()""" cdse = Structure(filename=self.cdsefile) cdse_str = cdse.writeStr("pdffit") cdse2 = copy.copy(cdse) @@ -119,7 +125,7 @@ def test___copy__(self): # return def test_assignUniqueLabels(self): - """check Structure.assignUniqueLabels()""" + """Check Structure.assignUniqueLabels()""" self.assertEqual("", "".join([a.label for a in self.stru])) self.stru.assignUniqueLabels() self.assertEqual("C1", self.stru[0].label) @@ -127,19 +133,23 @@ def test_assignUniqueLabels(self): return def test_distance(self): - """check Structure.distance()""" + """Check Structure.distance()""" from math import sqrt self.stru.assignUniqueLabels() self.assertRaises(IndexError, self.stru.distance, 333, "C1") self.assertRaises(IndexError, self.stru.distance, "C", "C1") - self.assertAlmostEqual(sqrt(2.0), self.stru.distance(0, 1), self.places) - self.assertAlmostEqual(sqrt(2.0), self.stru.distance("C1", "C2"), self.places) + self.assertAlmostEqual( + sqrt(2.0), self.stru.distance(0, 1), self.places + ) + self.assertAlmostEqual( + sqrt(2.0), self.stru.distance("C1", "C2"), self.places + ) self.assertEqual(0, self.stru.distance(0, "C1")) return def test_angle(self): - """check Structure.angle()""" + """Check Structure.angle()""" cdse = Structure(filename=self.cdsefile) cdse.assignUniqueLabels() self.assertEqual(109, round(cdse.angle(0, 2, 1))) @@ -147,7 +157,7 @@ def test_angle(self): return def test_placeInLattice(self): - """check Structure.placeInLattice() -- conversion of coordinates""" + """Check Structure.placeInLattice() -- conversion of coordinates.""" stru = self.stru new_lattice = Lattice(0.5, 0.5, 0.5, 90, 90, 60) stru.placeInLattice(new_lattice) @@ -173,14 +183,14 @@ def test_placeInLattice(self): # return def test_aslist(self): - """check Structure.tolist()""" + """Check Structure.tolist()""" lst = self.stru.tolist() self.assertEqual(tuple(lst), tuple(self.stru)) self.assertEqual(list, type(lst)) return def test_append(self): - """check Structure.append()""" + """Check Structure.append()""" a = Atom("Si", (0.1, 0.2, 0.3)) lat = self.stru.lattice self.stru.append(a) @@ -194,7 +204,7 @@ def test_append(self): return def test_insert(self): - """check Structure.insert()""" + """Check Structure.insert()""" a = Atom("Si", (0.1, 0.2, 0.3)) lat = self.stru.lattice self.stru.insert(1, a) @@ -208,7 +218,7 @@ def test_insert(self): return def test_extend(self): - """check Structure.extend()""" + """Check Structure.extend()""" stru = self.stru cdse = Structure(filename=self.cdsefile) lst = stru.tolist() @@ -220,7 +230,7 @@ def test_extend(self): return def test___getitem__(self): - """check Structure.__getitem__()""" + """Check Structure.__getitem__()""" stru = self.stru self.assertTrue(stru[0] is stru.tolist()[0]) intidx = list(range(len(stru)))[::-1] @@ -238,7 +248,9 @@ def test___getitem__(self): cdse[0].label = "Hohenzollern" self.assertRaises(IndexError, cdse.__getitem__, "Cd1") self.assertTrue(cdse[0] is cdse["Hohenzollern"]) - self.assertEqual([cdse[0], cdse[3], cdse[1]], cdse["Hohenzollern", 3:0:-2].tolist()) + self.assertEqual( + [cdse[0], cdse[3], cdse[1]], cdse["Hohenzollern", 3:0:-2].tolist() + ) stru.label = ["A", "B"] self.assertTrue(stru[0] is stru["A"]) self.assertTrue(stru[1] is stru["B"]) @@ -247,14 +259,14 @@ def test___getitem__(self): return def test___getitem__slice(self): - """check Structure.__getitem__() with a slice argument""" + """Check Structure.__getitem__() with a slice argument.""" stru = self.stru self.assertEqual([stru[0]], stru[:1].tolist()) self.assertEqual([stru[1], stru[0]], stru[::-1].tolist()) return def test___setitem__(self): - """check Structure.__setitem__()""" + """Check Structure.__setitem__()""" a = Atom("Si", (0.1, 0.2, 0.3)) lat = self.stru.lattice self.stru[1] = a @@ -268,7 +280,7 @@ def test___setitem__(self): return def test___setitem__slice(self): - """check Structure.__setitem__() with a slice argument""" + """Check Structure.__setitem__() with a slice argument.""" a = Atom("Si", (0.1, 0.2, 0.3)) lat = self.stru.lattice self.stru[:] = [a] @@ -282,7 +294,7 @@ def test___setitem__slice(self): return def test___add__(self): - """check Structure.__add__()""" + """Check Structure.__add__()""" stru = self.stru cdse = Structure(filename=self.cdsefile) total = stru + cdse @@ -298,7 +310,7 @@ def test___add__(self): return def test___iadd__(self): - """check Structure.__iadd__()""" + """Check Structure.__iadd__()""" stru = self.stru lat0 = stru.lattice lst = stru.tolist() @@ -315,7 +327,7 @@ def test___iadd__(self): return def test___sub__(self): - """check Structure.__sub__()""" + """Check Structure.__sub__()""" cdse = Structure(filename=self.cdsefile) cadmiums = cdse - cdse[2:] self.assertEqual(2, len(cadmiums)) @@ -328,7 +340,7 @@ def test___sub__(self): return def test___isub__(self): - """check Structure.__isub__()""" + """Check Structure.__isub__()""" cdse = Structure(filename=self.cdsefile) lat = cdse.lattice lst = cdse.tolist() @@ -342,20 +354,26 @@ def test___isub__(self): return def test___mul__(self): - """check Structure.__mul__()""" + """Check Structure.__mul__()""" cdse = Structure(filename=self.cdsefile) self.assertEqual(12, len(set(3 * cdse))) self.assertEqual(12, len(set(cdse * 3))) cdsex3 = 3 * cdse self.assertEqual(12, len(cdsex3)) - self.assertEqual(3 * "Cd Cd Se Se".split(), [a.element for a in cdsex3]) - self.assertTrue(numpy.array_equal(3 * [a.xyz for a in cdse], [a.xyz for a in cdsex3])) + self.assertEqual( + 3 * "Cd Cd Se Se".split(), [a.element for a in cdsex3] + ) + self.assertTrue( + numpy.array_equal( + 3 * [a.xyz for a in cdse], [a.xyz for a in cdsex3] + ) + ) self.assertFalse(set(cdse).intersection(cdsex3)) self.assertFalse(cdse.lattice is cdsex3.lattice) return def test___imul__(self): - """check Structure.__imul__()""" + """Check Structure.__imul__()""" cdse = Structure(filename=self.cdsefile) lat = cdse.lattice els = cdse.element @@ -373,7 +391,7 @@ def test___imul__(self): return def test__get_lattice(self): - """check Structure._get_lattice()""" + """Check Structure._get_lattice()""" lat = Lattice() stru = Structure() self.assertEqual((1, 1, 1, 90, 90, 90), stru.lattice.abcABG()) @@ -382,14 +400,14 @@ def test__get_lattice(self): return def test__set_lattice(self): - """check Structure._set_lattice()""" + """Check Structure._set_lattice()""" lat = Lattice() self.stru.lattice = lat self.assertEqual(2 * [lat], [a.lattice for a in self.stru]) return def test_composition(self): - """check Structure.composition property""" + """Check Structure.composition property.""" stru = self.stru self.assertEqual({"C": 2}, stru.composition) stru *= 2 @@ -399,7 +417,7 @@ def test_composition(self): return def test_element(self): - """check Structure.element""" + """Check Structure.element.""" stru = self.stru cdse = self.cdse self.assertEqual("Cd Cd Se Se".split(), cdse.element.tolist()) @@ -409,7 +427,7 @@ def test_element(self): return def test_xyz(self): - """check Structure.xyz""" + """Check Structure.xyz.""" stru = self.stru self.assertEqual((2, 3), stru.xyz.shape) self.assertTrue(numpy.array_equal([1, 1, 1], stru.xyz[1])) @@ -427,7 +445,7 @@ def test_xyz(self): return def test_x(self): - """check Structure.x""" + """Check Structure.x.""" cdse = self.cdse self.assertEqual((4,), cdse.x.shape) self.assertAlmostEqual(0.6666, cdse.x[3], 5) @@ -438,7 +456,7 @@ def test_x(self): return def test_y(self): - """check Structure.y""" + """Check Structure.y.""" cdse = self.cdse self.assertEqual((4,), cdse.y.shape) self.assertAlmostEqual(0.3333, cdse.y[3], 5) @@ -449,7 +467,7 @@ def test_y(self): return def test_z(self): - """check Structure.z""" + """Check Structure.z.""" cdse = self.cdse self.assertEqual((4,), cdse.z.shape) self.assertAlmostEqual(0.87667, cdse.z[3], 5) @@ -460,7 +478,7 @@ def test_z(self): return def test_label(self): - """check Structure.label""" + """Check Structure.label.""" cdse = Structure(filename=self.cdsefile) self.assertEqual(4 * [""], cdse.label.tolist()) cdse.assignUniqueLabels() @@ -470,7 +488,7 @@ def test_label(self): return def test_occupancy(self): - """check Structure.occupancy""" + """Check Structure.occupancy.""" cdse = self.cdse self.assertTrue(numpy.array_equal(numpy.ones(4), cdse.occupancy)) self.stru.occupancy *= 0.5 @@ -480,17 +498,19 @@ def test_occupancy(self): return def test_xyz_cartn(self): - """check Structure.xyz_cartn""" + """Check Structure.xyz_cartn.""" pbte = copy.copy(self.pbte) self.assertEqual((8, 3), pbte.xyz_cartn.shape) - self.assertTrue(numpy.allclose(6.461 / 2.0 * numpy.ones(3), pbte.xyz_cartn[0])) + self.assertTrue( + numpy.allclose(6.461 / 2.0 * numpy.ones(3), pbte.xyz_cartn[0]) + ) pbte.xyz_cartn += numpy.array([0.1, 0.2, 0.3]) * 6.461 self.assertTrue(numpy.allclose([0.6, 0.7, 0.8], pbte[0].xyz)) self.assertTrue(numpy.allclose([0.6, 0.7, 0.3], pbte[7].xyz)) return def test_anisotropy(self): - """check Structure.anisotropy""" + """Check Structure.anisotropy.""" self.assertEqual((2,), self.stru.anisotropy.shape) self.assertFalse(numpy.any(self.stru.anisotropy)) tei = copy.copy(self.tei) @@ -501,13 +521,15 @@ def test_anisotropy(self): self.assertAlmostEqual(0.019227, tei[0].U22, 6) self.assertAlmostEqual(0.019227, tei[0].U33, 6) self.assertAlmostEqual(0.0, tei[0].U12, 6) - self.assertAlmostEqual(0.019227 * -numpy.cos(numpy.radians(128.09)), tei[0].U13, 6) + self.assertAlmostEqual( + 0.019227 * -numpy.cos(numpy.radians(128.09)), tei[0].U13, 6 + ) self.assertAlmostEqual(0.0, tei[0].U23, 6) self.assertAlmostEqual(0.019227, tei[0].Uisoequiv, 6) return def test_U(self): - """check Structure.U""" + """Check Structure.U.""" stru = self.stru self.assertEqual((2, 3, 3), stru.U.shape) self.assertFalse(numpy.any(stru.anisotropy)) @@ -529,7 +551,7 @@ def test_U(self): return def test_Uisoequiv(self): - """check Structure.Uisoequiv""" + """Check Structure.Uisoequiv.""" tei = copy.copy(self.tei) self.assertEqual((16,), tei.Uisoequiv.shape) self.assertAlmostEqual(0.019227, tei.Uisoequiv[0], 6) @@ -542,7 +564,7 @@ def test_Uisoequiv(self): return def test_Uij(self): - """check Structure.Uij""" + """Check Structure.Uij.""" stru = self.stru stru[1].anisotropy = True stru[1].U = [[1.1, 0.12, 0.13], [0.12, 2.2, 0.23], [0.13, 0.23, 3.3]] @@ -557,7 +579,7 @@ def test_Uij(self): return def test_Bisoequiv(self): - """check Structure.Bisoequiv""" + """Check Structure.Bisoequiv.""" utob = 8 * numpy.pi**2 tei = copy.copy(self.tei) self.assertEqual((16,), tei.Bisoequiv.shape) @@ -571,7 +593,7 @@ def test_Bisoequiv(self): return def test_Bij(self): - """check Structure.Bij""" + """Check Structure.Bij.""" stru = self.stru stru[1].anisotropy = True stru[1].U = [[1.1, 0.12, 0.13], [0.12, 2.2, 0.23], [0.13, 0.23, 3.3]] diff --git a/tests/test_supercell.py b/tests/test_supercell.py index 68aa809..d20434c 100644 --- a/tests/test_supercell.py +++ b/tests/test_supercell.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Unit tests for supercell.py""" +"""Unit tests for supercell.py.""" import unittest @@ -50,14 +49,14 @@ def tearDown(self): return def test_exceptions(self): - """check argument checking of supercell.""" + """Check argument checking of supercell.""" self.assertRaises(ValueError, supercell, self.stru_ni, (0, 1, 1)) self.assertRaises(ValueError, supercell, self.stru_ni, (0, 1)) self.assertRaises(TypeError, supercell, list(self.stru_ni), (1, 1, 1)) return def test_ni_supercell(self): - """check supercell expansion for Ni.""" + """Check supercell expansion for Ni.""" ni_123 = supercell(self.stru_ni, (1, 2, 3)) self.assertEqual(6 * len(self.stru_ni), len(ni_123)) a, b, c = self.stru_ni.lattice.abcABG()[:3] @@ -73,7 +72,7 @@ def test_ni_supercell(self): return def test_cdse_supercell(self): - """check supercell expansion for CdSe.""" + """Check supercell expansion for CdSe.""" cdse_222 = supercell(self.stru_cdse, (2, 2, 2)) # new atoms should be grouped together elems = sum([8 * [a.element] for a in self.stru_cdse], []) diff --git a/tests/test_symmetryutilities.py b/tests/test_symmetryutilities.py index 377f755..667bbe5 100644 --- a/tests/test_symmetryutilities.py +++ b/tests/test_symmetryutilities.py @@ -12,8 +12,7 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - -"""Unit tests for SymmetryUtilities.py""" +"""Unit tests for SymmetryUtilities.py.""" import re import sys @@ -45,7 +44,7 @@ def tearDown(self): return def test_isSpaceGroupLatPar(self): - """check isSpaceGroupLatPar()""" + """Check isSpaceGroupLatPar()""" triclinic = GetSpaceGroup("P1") monoclinic = GetSpaceGroup("P2") orthorhombic = GetSpaceGroup("P222") @@ -69,13 +68,13 @@ def test_isSpaceGroupLatPar(self): return def test_sgtbx_spacegroup_aliases(self): - """check GetSpaceGroup for non-standard aliases from sgtbx.""" + """Check GetSpaceGroup for non-standard aliases from sgtbx.""" self.assertIs(GetSpaceGroup("Fm3m"), GetSpaceGroup(225)) self.assertIs(GetSpaceGroup("Ia3d"), GetSpaceGroup("I a -3 d")) return def test_expandPosition(self): - """check expandPosition()""" + """Check expandPosition()""" # ok again Ni example fcc = GetSpaceGroup(225) pos, pops, pmult = expandPosition(fcc, [0, 0, 0]) @@ -86,14 +85,14 @@ def test_expandPosition(self): return def test_pruneFormulaDictionary(self): - """check pruneFormulaDictionary()""" + """Check pruneFormulaDictionary()""" fmdict = {"x": "3*y-0.17", "y": "0", "z": "0.13"} pruned = pruneFormulaDictionary(fmdict) self.assertEqual({"x": "3*y-0.17"}, pruned) return def test_isconstantFormula(self): - """check isconstantFormula()""" + """Check isconstantFormula()""" self.assertFalse(isconstantFormula("x-y+z")) self.assertTrue(isconstantFormula("6.023e23")) self.assertTrue(isconstantFormula("22/7")) @@ -119,14 +118,14 @@ def tearDown(self): return def test___init__(self): - """check _Position2Tuple.__init__()""" + """Check _Position2Tuple.__init__()""" self.assertNotEqual(0.0, self.pos2tuple.eps) self.pos2tuple = _Position2Tuple(1.0 / sys.maxsize / 2) self.assertEqual(0.0, self.pos2tuple.eps) return def test___call__(self): - """check _Position2Tuple.__call__()""" + """Check _Position2Tuple.__call__()""" pos2tuple = self.pos2tuple positions = numpy.zeros((100, 3), dtype=float) positions[:, 0] = numpy.arange(100) / 100.0 * pos2tuple.eps + 0.1 @@ -202,7 +201,7 @@ def tearDown(self): return def test___init__(self): - """check GeneratorSite.__init__()""" + """Check GeneratorSite.__init__()""" # check multiplicities self.assertEqual(2, self.g117c.multiplicity) self.assertEqual(4, self.g117h.multiplicity) @@ -230,7 +229,7 @@ def test_signedRatStr(self): return def test_positionFormula(self): - """check GeneratorSite.positionFormula()""" + """Check GeneratorSite.positionFormula()""" # 117c self.assertEqual([], self.g117c.pparameters) self.assertEqual([("x", self.x)], self.g117h.pparameters) @@ -265,18 +264,74 @@ def test_positionFormula_sg209(self): return def test_UFormula(self): - """check GeneratorSite.UFormula()""" + """Check GeneratorSite.UFormula()""" # Ref: Willis and Pryor, Thermal Vibrations in Crystallography, # Cambridge University Press 1975, p. 104-110 smbl = ("A", "B", "C", "D", "E", "F") - norule = {"U11": "A", "U22": "B", "U33": "C", "U12": "D", "U13": "E", "U23": "F"} - rule05 = {"U11": "A", "U22": "A", "U33": "C", "U12": "D", "U13": "0", "U23": "0"} - rule06 = {"U11": "A", "U22": "A", "U33": "C", "U12": "D", "U13": "E", "U23": "E"} - rule07 = {"U11": "A", "U22": "A", "U33": "C", "U12": "D", "U13": "E", "U23": "-E"} - rule15 = {"U11": "A", "U22": "B", "U33": "C", "U12": "0.5*B", "U13": "0.5*F", "U23": "F"} - rule16 = {"U11": "A", "U22": "A", "U33": "C", "U12": "0.5*A", "U13": "0", "U23": "0"} - rule17 = {"U11": "A", "U22": "A", "U33": "A", "U12": "0", "U13": "0", "U23": "0"} - rule18 = {"U11": "A", "U22": "A", "U33": "A", "U12": "D", "U13": "D", "U23": "D"} + norule = { + "U11": "A", + "U22": "B", + "U33": "C", + "U12": "D", + "U13": "E", + "U23": "F", + } + rule05 = { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "D", + "U13": "0", + "U23": "0", + } + rule06 = { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "D", + "U13": "E", + "U23": "E", + } + rule07 = { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "D", + "U13": "E", + "U23": "-E", + } + rule15 = { + "U11": "A", + "U22": "B", + "U33": "C", + "U12": "0.5*B", + "U13": "0.5*F", + "U23": "F", + } + rule16 = { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "0.5*A", + "U13": "0", + "U23": "0", + } + rule17 = { + "U11": "A", + "U22": "A", + "U33": "A", + "U12": "0", + "U13": "0", + "U23": "0", + } + rule18 = { + "U11": "A", + "U22": "A", + "U33": "A", + "U12": "D", + "U13": "D", + "U23": "D", + } ufm = self.g117c.UFormula(self.g117c.xyz, smbl) self.assertEqual(rule05, ufm) ufm = self.g117h.UFormula(self.g117h.xyz, smbl) @@ -315,15 +370,58 @@ def test_UFormula(self): return def test_UFormula_g186c_eqxyz(self): - """Check rotated U formulas at the symmetry positions of c-site in 186.""" + """Check rotated U formulas at the symmetry positions of c-site in + 186.""" sg186 = GetSpaceGroup(186) crules = [ - {"U11": "A", "U22": "A", "U33": "C", "U12": "D", "U13": "E", "U23": "-E"}, - {"U11": "A", "U22": "2*A-2*D", "U33": "C", "U12": "A-D", "U13": "E", "U23": "2*E"}, - {"U11": "2*A-2*D", "U22": "A", "U33": "C", "U12": "A-D", "U13": "-2*E", "U23": "-E"}, - {"U11": "A", "U22": "A", "U33": "C", "U12": "D", "U13": "-E", "U23": "E"}, - {"U11": "A", "U22": "2*A-2*D", "U33": "C", "U12": "A-D", "U13": "-E", "U23": "-2*E"}, - {"U11": "2*A-2*D", "U22": "A", "U33": "C", "U12": "A-D", "U13": "2*E", "U23": "E"}, + { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "D", + "U13": "E", + "U23": "-E", + }, + { + "U11": "A", + "U22": "2*A-2*D", + "U33": "C", + "U12": "A-D", + "U13": "E", + "U23": "2*E", + }, + { + "U11": "2*A-2*D", + "U22": "A", + "U33": "C", + "U12": "A-D", + "U13": "-2*E", + "U23": "-E", + }, + { + "U11": "A", + "U22": "A", + "U33": "C", + "U12": "D", + "U13": "-E", + "U23": "E", + }, + { + "U11": "A", + "U22": "2*A-2*D", + "U33": "C", + "U12": "A-D", + "U13": "-E", + "U23": "-2*E", + }, + { + "U11": "2*A-2*D", + "U22": "A", + "U33": "C", + "U12": "A-D", + "U13": "2*E", + "U23": "E", + }, ] self.assertEqual(6, len(self.g186c.eqxyz)) gc = self.g186c @@ -334,13 +432,22 @@ def test_UFormula_g186c_eqxyz(self): for u in eau.expandedUijs: du = numpy.linalg.norm((uiso - u).flatten()) self.assertAlmostEqual(0.0, du, 8) - symcon = SymmetryConstraints(sg186, sum(eau.expandedpos, []), sum(eau.expandedUijs, [])) + symcon = SymmetryConstraints( + sg186, sum(eau.expandedpos, []), sum(eau.expandedUijs, []) + ) upd = dict(symcon.Upars) self.assertEqual(2.0, upd["U110"]) self.assertEqual(2.0, upd["U330"]) self.assertEqual(1.0, upd["U120"]) self.assertEqual(0.0, upd["U130"]) - uisod = {"U11": 2.0, "U22": 2.0, "U33": 2.0, "U12": 1.0, "U13": 0.0, "U23": 0.0} + uisod = { + "U11": 2.0, + "U22": 2.0, + "U33": 2.0, + "U12": 1.0, + "U13": 0.0, + "U23": 0.0, + } for ufms in symcon.UFormulas(): for n, fm in ufms.items(): self.assertEqual(uisod[n], eval(fm, upd)) @@ -349,12 +456,16 @@ def test_UFormula_g186c_eqxyz(self): def test_UFormula_self_reference(self): "Ensure U formulas have no self reference such as U13=0.5*U13." for g in self.generators.values(): - badformulas = [(n, fm) for n, fm in g.UFormula(g.xyz).items() if n in fm and n != fm] + badformulas = [ + (n, fm) + for n, fm in g.UFormula(g.xyz).items() + if n in fm and n != fm + ] self.assertEqual([], badformulas) return def test__findUParameters(self): - """check GeneratorSite._findUParameters()""" + """Check GeneratorSite._findUParameters()""" # by default all Uparameters equal zero, this would fail for NaNs for gen in TestGeneratorSite.generators.values(): for usym, uval in gen.Uparameters: @@ -371,7 +482,7 @@ def test__findUParameters(self): return def test_eqIndex(self): - """check GeneratorSite.eqIndex()""" + """Check GeneratorSite.eqIndex()""" self.assertEqual(13, self.g227oc.eqIndex(self.g227oc.eqxyz[13])) return @@ -390,7 +501,7 @@ def tearDown(self): return def test___init__(self): - """check SymmetryConstraints.__init__()""" + """Check SymmetryConstraints.__init__()""" sg225 = GetSpaceGroup(225) # initialize from nested lists and arrays from ExpandAsymmetricUnit eau = ExpandAsymmetricUnit(sg225, [[0, 0, 0]]) @@ -430,7 +541,7 @@ def test_corepos(self): return def test_Uisotropy(self): - """check isotropy value for ADP-s at specified sites.""" + """Check isotropy value for ADP-s at specified sites.""" sg225 = GetSpaceGroup(225) corepos = [[0, 0, 0], [0.1, 0.13, 0.17]] eau = ExpandAsymmetricUnit(sg225, corepos) @@ -465,7 +576,7 @@ def test_Uisotropy(self): # return # def test_UparSymbols(self): - """check SymmetryConstraints.UparSymbols()""" + """Check SymmetryConstraints.UparSymbols()""" sg1 = GetSpaceGroup(1) sg225 = GetSpaceGroup(225) pos = [[0, 0, 0]] @@ -477,7 +588,7 @@ def test_UparSymbols(self): return def test_UparValues(self): - """check SymmetryConstraints.UparValues()""" + """Check SymmetryConstraints.UparValues()""" places = 12 sg1 = GetSpaceGroup(1) sg225 = GetSpaceGroup(225) diff --git a/tests/test_version.py b/tests/test_version.py index de45655..a4544df 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,9 +1,10 @@ -"""Unit tests for __version__.py""" +"""Unit tests for __version__.py.""" import diffpy.structure 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.structure, "__version__") assert diffpy.structure.__version__ != "0.0.0"