diff --git a/.codecov.yml b/.codecov.yml index 70686df3..4af5eb24 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,33 +1,14 @@ -# codecov can find this file anywhere in the repo, so we don't need to clutter -# the root folder. -#comment: false - -codecov: - notify: - require_ci_to_pass: no - coverage: status: - patch: + project: # more options at https://docs.codecov.com/docs/commit-status default: - target: '80' - if_no_uploads: error - if_not_found: success - if_ci_failed: failure - project: - default: false - library: - target: auto - if_no_uploads: error - if_not_found: success - if_ci_failed: failure - paths: '!*/tests/.*' - - tests: - target: 97.9% - paths: '*/tests/.*' - -flags: - tests: - paths: - - tests/ + target: auto # use the coverage from the base commit, fail if coverage is lower + threshold: 0% # allow the coverage to drop by + +comment: + layout: " diff, flags, files" + behavior: default + require_changes: false + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] + hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] diff --git a/.codespell/ignore_lines.txt b/.codespell/ignore_lines.txt new file mode 100644 index 00000000..07fa7c8c --- /dev/null +++ b/.codespell/ignore_lines.txt @@ -0,0 +1,2 @@ +;; Please include filenames and explanations for each ignored line. +;; See https://docs.openverse.org/meta/codespell.html for docs. diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt new file mode 100644 index 00000000..3e653cfb --- /dev/null +++ b/.codespell/ignore_words.txt @@ -0,0 +1,14 @@ +;; Please include explanations for each ignored word (lowercase). +;; See https://docs.openverse.org/meta/codespell.html for docs. + +;; abbreviation for "materials" often used in a journal title +mater + +;; Frobenius norm used in np.linalg.norm +fro + +;; nin is a legit variable in builder +nin + +;; highT is used for high Temperature in examples/debymodelII +highT diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index d0a0d16d..00000000 --- a/.coveragerc +++ /dev/null @@ -1,22 +0,0 @@ -# Configuration of the coverage.py tool for reporting test coverage. - -[report] -# RE patterns for lines to be excluded from consideration. -exclude_lines = - ## Have to re-enable the standard pragma - pragma: no cover - ## Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - ^[ ]*assert False - - ## Don't complain if non-runnable code isn't run: - ^[ ]*@unittest.skip\b - ^[ ]{4}unittest.main() - if __name__ == .__main__.: - - -[run] -omit = - ## exclude debug.py from codecov report - */tests/debug.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..7b2865c1 --- /dev/null +++ b/.flake8 @@ -0,0 +1,13 @@ +# As of now, flake8 does not natively support configuration via pyproject.toml +# https://github.com/microsoft/vscode-flake8/issues/135 +[flake8] +exclude = + .git, + __pycache__, + build, + dist, + doc/source/conf.py +max-line-length = 79 +# Ignore some style 'errors' produced while formatting by 'black' +# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings +extend-ignore = E203 diff --git a/.gitarchive.cfg b/.gitarchive.cfg deleted file mode 100644 index 95e1448c..00000000 --- a/.gitarchive.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[DEFAULT] -commit = $Format:%H$ -date = $Format:%ci$ -timestamp = $Format:%ct$ -refnames = $Format:%D$ diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 9d58a8cd..00000000 --- a/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -/.gitattributes export-ignore -/.gitignore export-ignore -/.travis.yml export-ignore -/conda-recipe/ export-ignore -/devutils export-ignore -.gitarchive.cfg export-subst -*.bat text eol=crlf diff --git a/.github/ISSUE_TEMPLATE/bug_feature.md b/.github/ISSUE_TEMPLATE/bug_feature.md new file mode 100644 index 00000000..b3454deb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_feature.md @@ -0,0 +1,16 @@ +--- +name: Bug Report or Feature Request +about: Report a bug or suggest a new feature! +title: "" +labels: "" +assignees: "" +--- + +### Problem + + + +### Proposed solution diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md new file mode 100644 index 00000000..6107962c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -0,0 +1,46 @@ +--- +name: Release +about: Checklist and communication channel for PyPI and GitHub release +title: "Ready for PyPI/GitHub release" +labels: "release" +assignees: "" +--- + +### 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 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 tag the maintainer (e.g., @username) in the comment here when you are ready for the PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: + +### PyPI/GitHub full-release preparation checklist: + +- [ ] Create a new conda environment and install the rc from PyPI (`pip install ==??`) +- [ ] License information on PyPI is correct. +- [ ] Docs are deployed successfully to `https:///`. +- [ ] 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. +- [ ] 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. diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..1099d862 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,15 @@ +### What problem does this PR address? + + + +### What should the reviewer(s) do? + + + + diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml new file mode 100644 index 00000000..4d66f1b5 --- /dev/null +++ b/.github/workflows/build-wheel-release-upload.yml @@ -0,0 +1,18 @@ +name: Release (GitHub/PyPI) and Deploy Docs + +on: + workflow_dispatch: + push: + tags: + - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + +jobs: + release: + uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 + with: + project: diffpy.srfit + 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 new file mode 100644 index 00000000..bd352c24 --- /dev/null +++ b/.github/workflows/check-news-item.yml @@ -0,0 +1,12 @@ +name: Check for News + +on: + pull_request_target: + branches: + - main + +jobs: + check-news-item: + uses: scikit-package/release-scripts/.github/workflows/_check-news-item.yml@v0 + with: + project: diffpy.srfit diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml new file mode 100644 index 00000000..c4a683c5 --- /dev/null +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + branches: + - main + release: + types: + - prereleased + - published + workflow_dispatch: + +jobs: + matrix-coverage: + uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 + with: + project: diffpy.srfit + c_extension: false + headless: false + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml new file mode 100644 index 00000000..54922a28 --- /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.srfit + c_extension: false + headless: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml new file mode 100644 index 00000000..190b19cc --- /dev/null +++ b/.github/workflows/tests-on-pr.yml @@ -0,0 +1,15 @@ +name: Tests on PR + +on: + pull_request: + workflow_dispatch: + +jobs: + tests-on-pr: + uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr.yml@v0 + with: + project: diffpy.srfit + c_extension: false + headless: false + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 9f5ecbf0..099e2948 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,93 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ *.py[cod] +*$py.class # C extensions *.so -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -temp -develop-eggs +# Distribution / packaging +.Python +env/ +build/ +_build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +venv/ +*.egg-info/ .installed.cfg -lib -lib64 -tags +*.egg +bin/ +temp/ +tags/ errors.err +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + # Installer logs pip-log.txt +pip-delete-this-directory.txt MANIFEST # Unit test / coverage reports +htmlcov/ +.tox/ .coverage -.tox +.coverage.* +.cache nosetests.xml +coverage.xml +*,cover +.hypothesis/ # Translations *.mo +*.pot # Mr Developer .mr.developer.cfg .project .pydevproject -.settings -# version information -setup.cfg -/src/diffpy/*/version.cfg +# Django stuff: +*.log + +# Sphinx documentation +docs/build/ +docs/source/generated/ + +# pytest +.pytest_cache/ + +# PyBuilder +target/ + +# Editor files +# mac +.DS_Store +*~ + +# vim +*.swp +*.swo + +# pycharm +.idea/ + +# VSCode +.vscode/ + +# Ipython Notebook +.ipynb_checkpoints diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 00000000..86f162b8 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,5 @@ +[settings] +# Keep import statement below line_length character limit +line_length = 79 +multi_line_output = 3 +include_trailing_comma = True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..0e4a84d1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,66 @@ +default_language_version: + 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 +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-added-large-files + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: no-commit-to-branch + 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 + # 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 00000000..47f7a017 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "latest" + +python: + install: + - requirements: requirements/docs.txt + +sphinx: + configuration: doc/source/conf.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a3469af5..00000000 --- a/.travis.yml +++ /dev/null @@ -1,123 +0,0 @@ -dist: xenial -language: generic - -os: - - linux - - osx - -env: - - MYUSEMC=true MYPYTHON_VERSION=2.7 - - MYUSEMC=true MYPYTHON_VERSION=3.5 - - MYUSEMC=true MYPYTHON_VERSION=3.6 - - MYUSEMC=true MYPYTHON_VERSION=3.7 - - MYUSEMC=false - -git: - depth: 999999 - -branches: - except: - - /^v[0-9]/ - - -before_install: - - MYNAME=diffpy.srfit - - MYCOMMIT="$(git rev-parse HEAD)" - - umask 022 - - git fetch origin --tags - - MYPYTHON=python; MYPIP=pip - - NOSYS=true; NOAPT=true; NOBREW=true; NOMC=true - - if ${MYUSEMC}; then - NOMC=false; - elif [[ ${TRAVIS_OS_NAME} == linux ]]; then - NOAPT=false; NOSYS=false; - MYPIPFLAGS="--user"; - elif [[ ${TRAVIS_OS_NAME} == osx ]]; then - NOBREW=false; NOSYS=false; - MYPYTHON=python3; - MYPIP=pip3; - MYPIPFLAGS="--user"; - fi - - MYMCREPO=https://repo.anaconda.com/miniconda - - case ${TRAVIS_OS_NAME} in - linux) - MYMCBUNDLE=Miniconda3-latest-Linux-x86_64.sh ;; - osx) - MYMCBUNDLE=Miniconda3-latest-MacOSX-x86_64.sh ;; - *) - echo "Unsupported operating system." >&2; - exit 2 ;; - esac - - MYRUNDIR=${PWD}/build/rundir - - - mkdir -p ~/pkgs - - mkdir -p ${MYRUNDIR} - - cp .coveragerc ${MYRUNDIR}/ - - - $NOMC || pushd ~/pkgs - - $NOMC || wget --timestamping ${MYMCREPO}/${MYMCBUNDLE} - - $NOMC || test -x ~/mc/bin/conda || bash ${MYMCBUNDLE} -b -f -p ~/mc - - $NOMC || popd - - $NOMC || source ~/mc/bin/activate base - - $NOMC || conda update --yes conda - - $NOMC || conda install --yes conda-build conda-verify jinja2 - - $NOMC || conda create --name=testenv --yes python=${MYPYTHON_VERSION} coverage - - $NOMC || conda config --add channels diffpy - - - $NOAPT || test "${TRAVIS_OS_NAME}" = "linux" || exit $? - - $NOAPT || PATH="$(echo "$PATH" | sed 's,:/opt/pyenv/[^:]*,,g')" - - $NOAPT || test "$(which python)" = "/usr/bin/python" || ( - which python; exit 1) - - $NOAPT || sudo apt-get update -qq - - $NOAPT || sudo apt-get install -y - python-dev python-setuptools python-numpy - build-essential - - - $NOBREW || test "${TRAVIS_OS_NAME}" = "osx" || exit $? - - $NOBREW || brew update - - $NOBREW || brew upgrade python - - $NOBREW || brew install gcc || brew link --overwrite gcc - - - $NOSYS || devutils/makesdist - - $NOSYS || MYTARBUNDLE="$(ls -t "${PWD}"/dist/*.tar.gz | head -1)" - - -install: - - $NOMC || conda build --python=${MYPYTHON_VERSION} conda-recipe - - $NOMC || conda render --python=${MYPYTHON_VERSION} --output conda-recipe | - sed 's,.*/,,; s/[.]tar[.]bz2$//; s/-/=/g' > /tmp/mypackage.txt - - $NOMC || source activate testenv - - $NOMC || conda install --yes --use-local --file=/tmp/mypackage.txt - - $NOMC || conda install --yes - diffpy.structure pyobjcryst "diffpy.srreal>=1.3.0" - # TODO - always install srfit-sasview when ready for Python 3. - - if $MYUSEMC && [[ "$MYPYTHON_VERSION" == 2.7 ]]; then - conda install --yes srfit-sasview; - fi - - - $NOSYS || $MYPIP install $MYPIPFLAGS coverage - - $NOSYS || $MYPIP install $MYPIPFLAGS pycifrw - - $NOSYS || $MYPIP install $MYPIPFLAGS diffpy.structure - - $NOSYS || $MYPIP install $MYPIPFLAGS "${MYTARBUNDLE}" - - - cd ${MYRUNDIR} - - MYGIT_REV=$($MYPYTHON -c "import ${MYNAME}.version as v; print(v.__git_commit__)") - - if [[ "${MYCOMMIT}" != "${MYGIT_REV}" ]]; then - echo "Version mismatch ${MYCOMMIT} vs ${MYGIT_REV}."; - exit 1; - fi - - -before_script: - - $NOBREW || USER_BASE="$(python3 -c 'import site; print(site.USER_BASE)')" - - $NOBREW || PATH="${USER_BASE}/bin:${PATH}" - - -script: - - coverage run --source ${MYNAME} -m ${MYNAME}.tests.run - - -after_success: - # do not post coverage reports when testing with system Python. - - $NOMC || $MYPIP install $MYPIPFLAGS codecov - - $NOMC || codecov diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 00000000..b2335043 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,10 @@ +Authors +======= + +Christopher Farrow, Pavol Juhas, Simon J. L. Billinge, and members of the Billinge Group + +Contributors +------------ + +For a list of contributors, visit +https://github.com/diffpy/diffpy.srfit/graphs/contributors diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 2e7b1714..00000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,9 +0,0 @@ -Authors: - -Chris Farrow -Pavol Juhas -Simon J.L. Billinge - -Contributors: - -https://github.com/diffpy/diffpy.srfit/graphs/contributors diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 7b90ae3f..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,33 +0,0 @@ -# Release notes - -## Version 3.0.0 – 2019-03-14 - -Differences from version 1.3. - -### Added - -- Support for Python 3.7, 3.6, 3.5 in addition to 2.7. - -### Changed - -- Always use lower-case imports from `diffpy.structure`. -- Use numeric-value sort to order variables in `PrintFitHook`. - -### Deprecated - -- Variable `__gitsha__` in the `version` module renamed to `__git_commit__`. - -### Removed - -- Optional upper and lower-bound arguments in `Parameter.setValue`. - The bounds can be set with `Parameter.boundRange` instead. -- Unused classes `ListOperator`, `SetOperator`. - -### Fixed - -- Metadata retrieval from `PDFContribution` hierarchy. -- Refresh `PDFGenerator` when its `rgrid` is changed in-place. -- Zero division in the `nppdfsas.py` example. -- Crash in `ellipsoidsas.py` example because of bug in `Parameter.setValue`. -- Pickling of `ProfileGenerator` objects and of bound class methods. -- Invalid escape sequences in string values. diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..7a65655d --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,36 @@ +============= +Release notes +============= + +.. current developments + +Version 3.0.0 – 2019-03-14 +========================== + +**Added:** + +* Support for Python 3.7, 3.6, 3.5 in addition to 2.7. + +**Changed:** + +* Always use lower-case imports from `diffpy.structure`. +* Use numeric-value sort to order variables in `PrintFitHook`. + +**Deprecated:** + +* Variable `__gitsha__` in the `version` module renamed to `__git_commit__`. + +**Removed:** + +* Optional upper and lower-bound arguments in `Parameter.setValue`. + The bounds can be set with `Parameter.boundRange` instead. +* Unused classes `ListOperator`, `SetOperator`. + +**Fixed:** + +* Metadata retrieval from `PDFContribution` hierarchy. +* Refresh `PDFGenerator` when its `rgrid` is changed in-place. +* Zero division in the `nppdfsas.py` example. +* Crash in `ellipsoidsas.py` example because of bug in `Parameter.setValue`. +* Pickling of `ProfileGenerator` objects and of bound class methods. +* Invalid escape sequences in string values. diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 00000000..e8199ca5 --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,133 @@ +===================================== + Contributor Covenant Code of Conduct +===================================== + +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, socioeconomic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sb2896@columbia.edu. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +**************** + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +************* + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +****************** + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +****************** + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant `_. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder `_. + +For answers to common questions about this code of conduct, see the `FAQ `_. `Translations are available `_ diff --git a/LICENSE.txt b/LICENSE.rst similarity index 98% rename from LICENSE.txt rename to LICENSE.rst index f6d92af7..7342dda5 100644 --- a/LICENSE.txt +++ b/LICENSE.rst @@ -9,9 +9,10 @@ Copyright (c) 2014, Australian Synchrotron Research Program Inc., ("ASRP") Copyright (c) 2006-2007, Board of Trustees of Michigan State University Copyright (c) 2008-2012, The Trustees of Columbia University in the City of New York - Copyright (c) 2014-2019, Brookhaven Science Associates, Brookhaven National Laboratory +Copyright (c) 2020-2025, The Trustees of Columbia University in the City + of New York The "DiffPy-CMI" is distributed subject to the following license conditions: diff --git a/LICENSE_DANSE.txt b/LICENSE_DANSE.rst similarity index 100% rename from LICENSE_DANSE.txt rename to LICENSE_DANSE.rst diff --git a/MANIFEST.in b/MANIFEST.in index 3894e104..b31fb162 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,16 +1,14 @@ -recursive-include src * -include AUTHORS.txt LICENSE*.txt README.rst -recursive-exclude src *.pyc -global-exclude .gitattributes .gitignore .gitarchive.cfg -global-exclude .DS_Store +graft src +graft tests +graft requirements -# Avoid user content in setup.cfg to make distribution reproducible. -exclude setup.cfg +include AUTHORS.rst LICENSE*.rst README.rst -# Exclude git-tracked files spuriously added by setuptools_scm +# Exclude all bytecode files and __pycache__ directories +global-exclude *.py[cod] # Exclude all .pyc, .pyo, and .pyd files. +global-exclude .DS_Store # Exclude Mac filesystem artifacts. +global-exclude __pycache__ # Exclude Python cache directories. +global-exclude .git* # Exclude git files and directories. +global-exclude .idea # Exclude PyCharm project settings. exclude .codecov.yml exclude .coveragerc -exclude .travis* -prune conda-recipe -prune devutils -prune doc diff --git a/README.rst b/README.rst index 59fefa83..2d4040ec 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,42 @@ -.. image:: https://travis-ci.org/diffpy/diffpy.srfit.svg?branch=master - :target: https://travis-ci.org/diffpy/diffpy.srfit +|Icon| |title|_ +=============== -.. image:: https://codecov.io/gh/diffpy/diffpy.srfit/branch/master/graph/badge.svg - :target: https://codecov.io/gh/diffpy/diffpy.srfit +.. |title| replace:: diffpy.srfit +.. _title: https://diffpy.github.io/diffpy.srfit +.. |Icon| image:: https://avatars.githubusercontent.com/diffpy + :target: https://diffpy.github.io/diffpy.srfit + :height: 100px + +|PyPI| |Forge| |PythonVersion| |PR| + +|CI| |Codecov| |Black| |Tracking| + +.. |Black| image:: https://img.shields.io/badge/code_style-black-black + :target: https://github.com/psf/black + +.. |CI| image:: https://github.com/diffpy/diffpy.srfit/actions/workflows/matrix-and-codecov-on-merge-to-main.yml/badge.svg + :target: https://github.com/diffpy/diffpy.srfit/actions/workflows/matrix-and-codecov-on-merge-to-main.yml + +.. |Codecov| image:: https://codecov.io/gh/diffpy/diffpy.srfit/branch/main/graph/badge.svg + :target: https://codecov.io/gh/diffpy/diffpy.srfit + +.. |Forge| image:: https://img.shields.io/conda/vn/conda-forge/diffpy.srfit + :target: https://anaconda.org/conda-forge/diffpy.srfit + +.. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + +.. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.srfit + :target: https://pypi.org/project/diffpy.srfit/ + +.. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.srfit + :target: https://pypi.org/project/diffpy.srfit/ + +.. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue + :target: https://github.com/diffpy/diffpy.srfit/issues diffpy.srfit -======================================================================== +============ Configurable code for solving atomic structures. @@ -32,23 +62,47 @@ obtain the total cost function. Additionally, diffpy.srfit is designed to be extensible, allowing the user to integrate external calculators to perform co-refinements with other techniques. -For more information about the diffpy.srfit library, see the users manual at -http://diffpy.github.io/diffpy.srfit. +For more information about the diffpy.srfit library, please consult our `online documentation `_. + +Citation +-------- + +If you use diffpy.srfit 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). -REQUIREMENTS ------------------------------------------------------------------------- +Installation +------------ -The diffpy.srfit package requires Python 3.5 or later or 2.7 and -the following software: +The preferred method is to use `Miniconda Python +`_ +and install from the "conda-forge" channel of Conda packages. -* ``setuptools`` - software distribution tools for Python -* ``NumPy`` - numerical mathematics and fast array operations for Python -* ``SciPy`` - scientific libraries for Python -* ``matplotlib`` - python plotting library +To add "conda-forge" to the conda channels, run the following in a terminal. :: -Recommended software: + conda config --add channels conda-forge -Optimizations involving crystal structures or molecules require +We want to install our packages in a suitable conda environment. +The following creates and activates a new environment named ``diffpy.srfit_env`` :: + + conda create -n diffpy.srfit_env diffpy.srfit + conda activate diffpy.srfit_env + +To confirm that the installation was successful, type :: + + python -c "import diffpy.srfit; print(diffpy.srfit.__version__)" + +The output should print the latest version displayed on the badges above. + +This will install the minimal `diffpy.srfit` installation. It will often be used +as along with other packages for manipulating and computing crystal structures +and so on. We also therefore recommend installing the following: * ``diffpy.structure`` - crystal structure container and parsers, https://github.com/diffpy/diffpy.structure @@ -67,80 +121,63 @@ functions from the diffpy.srfit.sas module require * ``sas`` - module for calculation of P(R) in small-angle scattering from the SasView project, http://www.sasview.org -We recommend to use `Anaconda Python `_ -as it allows to install all software dependencies together with -diffpy.srfit. For other Python distributions it is necessary to -install the required software separately. As an example, on Ubuntu -Linux some of the required software can be installed using :: +Fallback Installation +--------------------- - sudo apt-get install \ - python3-setuptools python3-numpy python3-scipy python3-matplotlib +If the above does not work, you can use ``pip`` to download and install the latest release from +`Python Package Index `_. +To install using ``pip`` into your ``diffpy.srfit_env`` environment, type :: -For other required packages see their respective web pages for installation -instructions. + pip install diffpy.srfit +If you prefer to install from sources, after installing the dependencies, obtain the source archive from +`GitHub `_. Once installed, ``cd`` into your ``diffpy.srfit`` directory +and run the following :: -INSTALLATION ------------------------------------------------------------------------- + pip install . -The preferred method is to use Anaconda Python and install from the -"diffpy" channel of Anaconda packages :: +Getting Started +--------------- - conda config --add channels diffpy - conda install diffpy.srfit +You may consult our `online documentation `_ for tutorials and API references. -diffpy.srfit is also included in the "diffpy-cmi" collection -of packages for structure analysis :: +Support and Contribute +---------------------- - conda install diffpy-cmi +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 `_. -Another option is to use ``easy_install`` to download and install the -latest release from `Python Package Index `_ :: +Feel free to fork the project and contribute. To install diffpy.srfit +in a development mode, with its sources being directly used by Python +rather than copied to a package directory, use the following in the root +directory :: - easy_install diffpy.srfit + pip install -e . -If you prefer to install from sources, make sure all required software -packages are in place and then run :: +To ensure code quality and to prevent accidental commits into the default branch, please set up the use of our pre-commit +hooks. - python setup.py install +1. Install pre-commit in your working environment by running ``conda install pre-commit``. -You may need to use ``sudo`` with system Python so the process is -allowed to put files to the system directories. If administrator (root) -access is not available, consult the output from -``python setup.py install --help`` for options to install to a -user-writable locations. The installation integrity can be verified by -changing to the HOME directory and running :: +2. Initialize pre-commit (one time only) ``pre-commit install``. - python -m diffpy.srfit.tests.run +Thereafter your code will be linted by black and isort and checked against flake8 before you can commit. +If it fails by black or isort, just rerun and it should pass (black and isort will modify the files so should +pass after they are modified). If the flake8 test fails please see the error messages and fix them manually before +trying to commit again. +Improvements and fixes are always appreciated. -DEVELOPMENT ------------------------------------------------------------------------- +Before contributing, please read our `Code of Conduct `_. -diffpy.srfit is an open-source software developed as a part of the DiffPy-CMI -complex modeling initiative at the Brookhaven National Laboratory. The -diffpy.srfit sources are hosted at -https://github.com/diffpy/diffpy.srfit. +Contact +------- -Feel free to fork the project and contribute. To install diffpy.srfit -in a development mode, with its sources being directly used by Python -rather than copied to a package directory, use :: +For more information on diffpy.srfit please visit the project `web-page `_ or email Simon Billinge at sb2896@columbia.edu. - python setup.py develop --user +Acknowledgements +---------------- - -ACKNOWLEDGEMENT ------------------------------------------------------------------------- +``diffpy.srfit`` is built and maintained with `scikit-package `_. The source code in *observable.py* was derived from the 1.0 version of the Caltech "Pyre" project. - - -CONTACTS ------------------------------------------------------------------------- - -For more information on diffpy.srfit please visit the project web-page - -http://www.diffpy.org - -or email Prof. Simon Billinge at sb2896@columbia.edu. diff --git a/conda-recipe/bld.bat b/conda-recipe/bld.bat deleted file mode 100644 index 0a79fa23..00000000 --- a/conda-recipe/bld.bat +++ /dev/null @@ -1,7 +0,0 @@ -"%PYTHON%" setup.py install -if errorlevel 1 exit 1 - -:: Add more build steps here, if they are necessary. - -:: See http://docs.continuum.io/conda/build.html -:: for a list of environment variables that are set during the build process. diff --git a/conda-recipe/build.sh b/conda-recipe/build.sh deleted file mode 100644 index b7920393..00000000 --- a/conda-recipe/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -$PYTHON setup.py install - -# Add more build steps here, if they are necessary. - -# See http://docs.continuum.io/conda/build.html -# for a list of environment variables that are set during the build process. diff --git a/conda-recipe/conda_build_config.yaml b/conda-recipe/conda_build_config.yaml deleted file mode 100644 index 8d446529..00000000 --- a/conda-recipe/conda_build_config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -python: - - 3.7 - - 3.6 - - 3.5 - - 2.7 diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml deleted file mode 100644 index d3c86a23..00000000 --- a/conda-recipe/meta.yaml +++ /dev/null @@ -1,66 +0,0 @@ -{% set setupdata = load_setup_py_data() %} - -package: - name: diffpy.srfit - version: {{ setupdata['version'] }} - -source: - git_url: .. - -build: - preserve_egg_dir: True - - # If this is a new build for the same version, increment the build - # number. If you do not include this key, it defaults to 0. - # number: 0 - -requirements: - build: - - python {{ python }} - - setuptools - - six - - run: - - python - - setuptools - - numpy >=1.11 - - six - -test: - # Python imports - imports: - - diffpy.srfit - - diffpy.srfit.equation - - diffpy.srfit.equation.literals - - diffpy.srfit.equation.visitors - - diffpy.srfit.fitbase - - diffpy.srfit.interface - - diffpy.srfit.pdf - - diffpy.srfit.sas - - diffpy.srfit.structure - - diffpy.srfit.tests - - diffpy.srfit.util - - commands: - # You can put test commands to be run here. Use this to test that the - # entry points work. - - # You can also put a file called run_test.py in the recipe that will be run - # at test time. - - requires: - # Put any additional test requirements here. For example - - diffpy.structure - - pyobjcryst - - diffpy.srreal - # FIXME - correct when packages become available for Python 3. - - srfit-sasview # [py2k] - -about: - home: https://github.com/diffpy/diffpy.srfit/ - summary: Framework for complex modeling and atomic structure optimization. - license: Modified BSD License - license_file: LICENSE.txt - -# See http://docs.continuum.io/conda/build.html -# for more information about meta.yaml. diff --git a/conda-recipe/run_test.py b/conda-recipe/run_test.py deleted file mode 100644 index 545d32fa..00000000 --- a/conda-recipe/run_test.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python - -import diffpy.srfit.tests -assert diffpy.srfit.tests.test().wasSuccessful() diff --git a/devutils/makesdist b/devutils/makesdist deleted file mode 100755 index 6aaae616..00000000 --- a/devutils/makesdist +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python - -'''Create source distribution tar.gz archive, where each file belongs -to a root user and modification time is set to the git commit time. -''' - -import sys -import os -import subprocess -import glob -import tarfile -import gzip - -BASEDIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -sys.path.insert(0, BASEDIR) - -from setup import versiondata, FALLBACK_VERSION -timestamp = versiondata.getint('DEFAULT', 'timestamp') - -vfb = versiondata.get('DEFAULT', 'version').split('.post')[0] + '.post0' -emsg = "Invalid FALLBACK_VERSION. Expected %r got %r." -assert vfb == FALLBACK_VERSION, emsg % (vfb, FALLBACK_VERSION) - -def inform(s): - sys.stdout.write(s) - sys.stdout.flush() - return - -inform('Run "setup.py sdist --formats=tar" ') -cmd_sdist = [sys.executable] + 'setup.py sdist --formats=tar'.split() -ec = subprocess.call(cmd_sdist, cwd=BASEDIR, stdout=open(os.devnull, 'w')) -if ec: sys.exit(ec) -inform("[done]\n") - -tarname = max(glob.glob(BASEDIR + '/dist/*.tar'), key=os.path.getmtime) - -tfin = tarfile.open(tarname) -fpout = gzip.GzipFile(tarname + '.gz', 'w', mtime=0) -tfout = tarfile.open(fileobj=fpout, mode='w') - -def fixtarinfo(tinfo): - tinfo.uid = tinfo.gid = 0 - tinfo.uname = tinfo.gname = 'root' - tinfo.mtime = timestamp - tinfo.mode &= ~0o022 - return tinfo - -inform('Filter %s --> %s.gz ' % (2 * (os.path.basename(tarname),))) -for ti in tfin: - tfout.addfile(fixtarinfo(ti), tfin.extractfile(ti)) - -tfin.close() -os.remove(tarname) -inform("[done]\n") diff --git a/doc/Makefile b/doc/Makefile index b9c30f1e..3720ec8e 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,34 +1,194 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build +BASENAME = $(subst .,,$(subst $() $(),,diffpy.srfit)) + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" -all: doc examples upload - -RELEASE = alpha9 -TARGET = farrowch@login.cacr.caltech.edu -DOCROOT = ~/docroot/diffraction/ -PKGROOT = ~/dev_danse_us/ - -.PHONY : doc -doc: - epydoc diffpy.srfit --html -vvv -o diffpy.srfitapi -n diffpy.srfit \ ---include-log --exclude diffpy.srfit.structure.cctbxstructure $@ - $(MAKE) -C devmanual $@ - -.PHONY : upload -upload: - rsync -ruvz --delete diffpy.srfitapi $(TARGET):$(DOCROOT) - ssh $(TARGET) "rm -rf $(DOCROOT)/diffpy.srfitapi-$(RELEASE)" - ssh $(TARGET) "cp -r $(DOCROOT)/diffpy.srfitapi $(DOCROOT)/diffpy.srfitapi-$(RELEASE)" - rsync -ruv srfit_examples.zip $(TARGET):$(PKGROOT) - ssh $(TARGET) "rm -rf $(PKGROOT)/srfit_examples-$(RELEASE).zip" - ssh $(TARGET) "cp -r $(PKGROOT)/srfit_examples.zip $(PKGROOT)/srfit_examples-$(RELEASE).zip" - $(MAKE) -C devmanual $@ - -.PHONY : examples -examples: - zip -r srfit_examples.zip ./examples/*.py ./examples/data -x \*svn\* -x \*threedoublepeaks\* -x \*temp\* -x \*test\* - -.PHONY : clean clean: - rm -rf diffpy.srfitapi - rm -f srfit_examples.zip - $(MAKE) -C devmanual $@ + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/$(BASENAME).qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(BASENAME).qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +# Manual publishing to the gh-pages branch + +GITREPOPATH = $(shell cd $(CURDIR) && git rev-parse --git-dir) +GITREMOTE = origin +GITREMOTEURL = $(shell git config --get remote.$(GITREMOTE).url) +GITLASTCOMMIT = $(shell git rev-parse --short HEAD) +publish: + @test -d build/html || \ + ( echo >&2 "Run 'make html' first!"; false ) + git show-ref --verify --quiet refs/heads/gh-pages || \ + git branch --track gh-pages $(GITREMOTE)/gh-pages + test -d build/gh-pages || \ + git clone -s -b gh-pages $(GITREPOPATH) build/gh-pages + cd build/gh-pages && \ + git pull $(GITREMOTEURL) gh-pages + rsync -acv --delete --exclude=.git --exclude=.rsync-exclude \ + --exclude-from=build/gh-pages/.rsync-exclude \ + --link-dest=$(CURDIR)/build/html build/html/ build/gh-pages/ + cd build/gh-pages && \ + git add --all . && \ + git diff --cached --quiet || \ + git commit -m "Sync with the source at $(GITLASTCOMMIT)." + cd build/gh-pages && \ + git push origin gh-pages diff --git a/doc/examples/README b/doc/examples/README index 0d151e2d..92b43cb0 100644 --- a/doc/examples/README +++ b/doc/examples/README @@ -1,26 +1,23 @@ -Purpose ----------- +## Purpose These example scripts are intended to help a developer get acquainted with the SrFit programming interface. Although one can write scripts like these to drive optimization, these scripts do not represent the SrFit user interface. That interface will be made available in a future release. -A secondary purpose of these tutorials is to generate interest in SrFit. By +A secondary purpose of these tutorials is to generate interest in SrFit. By reading through the examples we hope that you are inspired to think of exciting new ways to get the most out of your scientific data. If you think SrFit can help you with that, please feel free to contact us through the DiffPy website. http://www.diffpy.org - -Overview ----------- +## Overview Three things are required for optimization: a function that generates a quantity to be minimized, variables that can be used to manipulate that function and an algorithm that can drive the function output to a smaller value -by steering the variables. For scientific purposes, the quantity to be +by steering the variables. For scientific purposes, the quantity to be minimized is the disagreement between a measured profile and a theoretical profile. The scientific understanding of the system under consideration partly determines the suitability of the theoretical profile generator and the @@ -30,38 +27,36 @@ The purpose of SrFit is to give users the means to combine known information about a system of interest in order to extract scientifically relevant quantities, and thus understanding from it. Various experimental procedures and theoretical calculations may be needed to gain the desired understanding of the -system. SrFit helps users combine these views of the system in a coherent and +system. SrFit helps users combine these views of the system in a coherent and consistent manner. To achieve this purpose, SrFit provides: -1) a function to be optimized (the residual) given one or more measured +1. a function to be optimized (the residual) given one or more measured profiles, one or more profile generators and variables to be steered by an optimizer. -2) constraints and restraints that encapsulate known information about the +2. constraints and restraints that encapsulate known information about the system. -3) a clearly defined programming interface that developers can use to add their +3. a clearly defined programming interface that developers can use to add their own profile generators, thereby enabling the combination of more views of a system. -4) an equation building interface that allows users to tweak profile generators +4. an equation building interface that allows users to tweak profile generators when scientific understanding of a system is more advanced than the existing software. The examples described below will go into detail about each of these points. +## Examples -Examples ----------- - -The following examples are contained in the *doc/examples/* directory of the -SrFit source distribution. They can be downloaded from +The following examples are contained in the _doc/examples/_ directory of the +SrFit source distribution. They can be downloaded from http://dev.danse.us/packages/srfit_examples-alpha9.zip For each example, start by running the example by typing in the command line :: python example.py -where *example.py* represents the example file. The output will show on screen +where _example.py_ represents the example file. The output will show on screen and a plot window will display. Once you've studied the output and plot, close the plot window and open the example file. In the file there will be a description of what the script is doing and the purpose of the example. By @@ -73,97 +68,94 @@ them in the order listed below. Basic: -* gaussianrecipe.py_ - Introductory recipe building and configuration. This introduces the - fundamental classes in SrFit. +- gaussianrecipe.py\_ + Introductory recipe building and configuration. This introduces the + fundamental classes in SrFit. -* debyemodel.py_ - Introductory recipe building and configuration. This shows how to use a - function created by someone else in a refinement. This example also - introduces restraints. +- debyemodel.py\_ + Introductory recipe building and configuration. This shows how to use a + function created by someone else in a refinement. This example also + introduces restraints. -* debyemodelII.py_ - Refine two different values of a variable from two different regions of a - profile. This example introduces constraints and working with multiple - contributions to a fit. +- debyemodelII.py\_ + Refine two different values of a variable from two different regions of a + profile. This example introduces constraints and working with multiple + contributions to a fit. Advanced: -* gaussiangenerator.py_ - Create a custom ProfileGenerator and use it in a refinement. This is an - instructive extension to gaussianrecipe.py_. - -* npintensity.py_ - Use diffpy.structure to build a nanoparticle intensity generator, and use - it to refine a structure to simulated data. +- gaussiangenerator.py* + Create a custom ProfileGenerator and use it in a refinement. This is an + instructive extension to gaussianrecipe.py*. -* npintensityII.py_ - Use the calculator built in npintensity.py to simultaneously refine a - structure to two data sets. +- npintensity.py\_ + Use diffpy.structure to build a nanoparticle intensity generator, and use + it to refine a structure to simulated data. +- npintensityII.py\_ + Use the calculator built in npintensity.py to simultaneously refine a + structure to two data sets. -Use Cases ------------ +## Use Cases There are several examples that demonstrate various SrFit use cases. These do not adopt the tutorial format of the previous examples. Regardless, developers should read through these use cases to gain an understanding of PDF and SAS refinement with SrFit. -* crystalpdf.py_ - Refine a diffpy.structure crystal to PDF data using automatic explicit - space group constraints. +- crystalpdf.py\_ + Refine a diffpy.structure crystal to PDF data using automatic explicit + space group constraints. -* simplepdf.py_ - As crystalpdf.py_, but with a simplified interface. +- simplepdf.py* + As crystalpdf.py*, but with a simplified interface. -* crystalpdfobjcryst.py_ - Refine a pyobjcryst crystal to PDF data using automatic implicit space - group constraints. +- crystalpdfobjcryst.py\_ + Refine a pyobjcryst crystal to PDF data using automatic implicit space + group constraints. -* crystalpdftwophase.py_ - Refine a two-phase structure to PDF data using two profile generators. +- crystalpdftwophase.py\_ + Refine a two-phase structure to PDF data using two profile generators. -* simplepdftwophase.py_ - crystalpdftwophase.py_ using the simplified PDFContribution interface. +- simplepdftwophase.py* + crystalpdftwophase.py* using the simplified PDFContribution interface. -* crystalpdftwodata.py_ - Refine a single structure to x-ray and neutron data simultaneously. +- crystalpdftwodata.py\_ + Refine a single structure to x-ray and neutron data simultaneously. -* crystalpdfall.py_ - Refine a two-phase structure using four data sets. +- crystalpdfall.py\_ + Refine a two-phase structure using four data sets. -* nppdfobjcryst.py_ - Refine the C60 structure to real data. +- nppdfobjcryst.py\_ + Refine the C60 structure to real data. -* nppdfcrystal.py_ - Fit a nanoparticle PDF as a crystal PDF attenuated by a nanoparticle form - factor. +- nppdfcrystal.py\_ + Fit a nanoparticle PDF as a crystal PDF attenuated by a nanoparticle form + factor. -* coreshellnp.py_ - As above, but fit the PDF from core-shell nanoparticles. +- coreshellnp.py\_ + As above, but fit the PDF from core-shell nanoparticles. -* ellipsoidsas.py_ - Refine an ellipsoid SAS model to ideal data. +- ellipsoidsas.py\_ + Refine an ellipsoid SAS model to ideal data. -* nppdfsas.py_ - Refine PDF from nanoparticle of assumed shape using a crystal model and SAS - data from the same system. +- nppdfsas.py\_ + Refine PDF from nanoparticle of assumed shape using a crystal model and SAS + data from the same system. -Miscellaneous --------------- +## Miscellaneous These demonstrate other SrFit features. These are in flux and may not be in future versions. -* simplerecipe.py_ - This introduces the SimpleRecipe class that is a FitRecipe with an embedded - Profile and FitContribution. SimpleRecipe exposes methods from the Profile - and FitContribution, and adds other methods so it is easy to set up a - simple fit. - -* interface.py_ - This example introduces some interface enhancements that allow the SrFit - recipes to be written with less code. This is not the same as a - full-featured scripting interface, but rather somewhere in between a - scripting interface and the API. +- simplerecipe.py\_ + This introduces the SimpleRecipe class that is a FitRecipe with an embedded + Profile and FitContribution. SimpleRecipe exposes methods from the Profile + and FitContribution, and adds other methods so it is easy to set up a + simple fit. + +- interface.py\_ + This example introduces some interface enhancements that allow the SrFit + recipes to be written with less code. This is not the same as a + full-featured scripting interface, but rather somewhere in between a + scripting interface and the API. diff --git a/doc/examples/coreshellnp.py b/doc/examples/coreshellnp.py index 2f1da057..8030a346 100644 --- a/doc/examples/coreshellnp.py +++ b/doc/examples/coreshellnp.py @@ -12,24 +12,25 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Refine the structure of a core-shell nanoparticle. -This applies the characteristic function formalism described in nppdfcrystal.py -to the case of a spherical core-shell nanoparticle. The modeling approach we -use is to refine the core and shell as two different phases, each with an -appropriate characteristic function. +This applies the characteristic function formalism described in +nppdfcrystal.py to the case of a spherical core-shell nanoparticle. The +modeling approach we use is to refine the core and shell as two +different phases, each with an appropriate characteristic function. """ import numpy -from scipy.optimize import leastsq - from pyobjcryst import loadCrystal +from scipy.optimize import leastsq +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults # Example Code @@ -73,7 +74,8 @@ def makeRecipe(stru1, stru2, datname): # and a spherical shell CF for the shell. Since this is set up as two # phases, we implicitly assume that the core-shell correlations contribute # very little to the PDF. - from diffpy.srfit.pdf.characteristicfunctions import sphericalCF, shellCF + from diffpy.srfit.pdf.characteristicfunctions import shellCF, sphericalCF + contribution.registerFunction(sphericalCF, name="f_CdS") contribution.registerFunction(shellCF, name="f_ZnS") @@ -99,7 +101,7 @@ def makeRecipe(stru1, stru2, datname): recipe.constrain(generator_zns.scale, "1 - scale_CdS") # We also want the resolution factor to be the same on each. - # Vary the gloabal scale as well. + # Vary the global scale as well. recipe.addVar(contribution.scale, 0.3) # Now we can configure the structural parameters. We tag the different @@ -140,10 +142,11 @@ def plotResults(recipe): diff = g - gcalc + diffzero import pylab - pylab.plot(r, g, 'bo', label="G(r) Data") - pylab.plot(r, gcalc, 'r-', label="G(r) Fit") - pylab.plot(r, diff, 'g-', label="G(r) diff") - pylab.plot(r, diffzero, 'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -165,6 +168,7 @@ def main(): stru2 = loadCrystal(znsciffile) recipe = makeRecipe(stru1, stru2, data) from diffpy.srfit.fitbase.fithook import PlotFitHook + recipe.pushFitHook(PlotFitHook()) recipe.fithooks[0].verbose = 3 diff --git a/doc/examples/crystalpdf.py b/doc/examples/crystalpdf.py index ced49e9a..0d668da5 100644 --- a/doc/examples/crystalpdf.py +++ b/doc/examples/crystalpdf.py @@ -12,31 +12,33 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement using diffpy.structure and PDFGenerator. -This is example of fitting the fcc nickel structure to measured PDF data. The -purpose of this example is to demonstrate and describe the classes in -configuraiton options involved with setting up a fit in this way. The main -benefit of using SrFit for PDF refinement is the flexibility of modifying the -PDF profile function for specific needs, adding restraints to a fit and the -ability to simultaneously refine a structure to PDF data and data from other -sources. This example demonstrates only the basic configuration. - +This is example of fitting the fcc nickel structure to measured PDF +data. The purpose of this example is to demonstrate and describe the +classes in configuration options involved with setting up a fit in this +way. The main benefit of using SrFit for PDF refinement is the +flexibility of modifying the PDF profile function for specific needs, +adding restraints to a fit and the ability to simultaneously refine a +structure to PDF data and data from other sources. This example +demonstrates only the basic configuration. """ import numpy +from gaussianrecipe import scipyOptimize -from diffpy.structure import Structure +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize +from diffpy.structure import Structure ####### Example Code + def makeRecipe(ciffile, datname): """Create a fitting recipe for crystalline PDF data.""" @@ -53,7 +55,7 @@ def makeRecipe(ciffile, datname): parser = PDFParser() parser.parseFile(datname) profile.loadParsedData(parser) - profile.setCalculationRange(xmax = 20) + profile.setCalculationRange(xmax=20) ## The ProfileGenerator # The PDFGenerator is for configuring and calculating a PDF profile. Here, @@ -72,7 +74,7 @@ def makeRecipe(ciffile, datname): # before. contribution = FitContribution("nickel") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") ## Make the FitRecipe and add the FitContribution. recipe = FitRecipe() @@ -95,6 +97,7 @@ def makeRecipe(ciffile, datname): # documentation for more details. The 'constrainAsSpaceGroup' method may # create new Parameters, which it returns in a SpaceGroupParameters object. from diffpy.srfit.structure import constrainAsSpaceGroup + sgpars = constrainAsSpaceGroup(phase, "Fm-3m") # The SpaceGroupParameters object returned by 'constrainAsSpaceGroup' holds @@ -124,6 +127,7 @@ def makeRecipe(ciffile, datname): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -135,10 +139,11 @@ def plotResults(recipe): diff = g - gcalc + diffzero import pylab - pylab.plot(r,g,'bo',label="G(r) Data") - pylab.plot(r, gcalc,'r-',label="G(r) Fit") - pylab.plot(r,diff,'g-',label="G(r) diff") - pylab.plot(r,diffzero,'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -146,6 +151,7 @@ def plotResults(recipe): pylab.show() return + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/crystalpdfall.py b/doc/examples/crystalpdfall.py index c79cf6c5..918b7a9d 100644 --- a/doc/examples/crystalpdfall.py +++ b/doc/examples/crystalpdfall.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement of two-phase structure. This example uses PDFGenerator to refine a the two phase nickel-silicon @@ -20,36 +19,41 @@ """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize ####### Example Code + def makeProfile(datafile): """Make an place data within a Profile.""" profile = Profile() parser = PDFParser() parser.parseFile(datafile) profile.loadParsedData(parser) - profile.setCalculationRange(xmax = 20) + profile.setCalculationRange(xmax=20) return profile + def makeContribution(name, generator, profile): """Make a FitContribution and add a generator and profile.""" contribution = FitContribution(name) contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") return contribution -def makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, - xdata_sini): + +def makeRecipe( + ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, xdata_sini +): """Create a fitting recipe for crystalline PDF data.""" ## The Profiles @@ -85,8 +89,9 @@ def makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, xcontribution_ni = makeContribution("xnickel", xgenerator_ni, xprofile_ni) xcontribution_si = makeContribution("xsilicon", xgenerator_si, xprofile_si) ncontribution_ni = makeContribution("nnickel", ngenerator_ni, nprofile_ni) - xcontribution_sini = makeContribution("xsini", xgenerator_sini_ni, - xprofile_sini) + xcontribution_sini = makeContribution( + "xsini", xgenerator_sini_ni, xprofile_sini + ) xcontribution_sini.addProfileGenerator(xgenerator_sini_si) xcontribution_sini.setEquation("scale * (xG_sini_ni + xG_sini_si)") @@ -105,29 +110,29 @@ def makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, # Now we vary and constrain Parameters as before. for par in phase_ni.sgpars: - recipe.addVar(par, name = par.name + "_ni") + recipe.addVar(par, name=par.name + "_ni") delta2_ni = recipe.newVar("delta2_ni", 2.5) recipe.constrain(xgenerator_ni.delta2, delta2_ni) recipe.constrain(ngenerator_ni.delta2, delta2_ni) recipe.constrain(xgenerator_sini_ni.delta2, delta2_ni) for par in phase_si.sgpars: - recipe.addVar(par, name = par.name + "_si") + recipe.addVar(par, name=par.name + "_si") delta2_si = recipe.newVar("delta2_si", 2.5) recipe.constrain(xgenerator_si.delta2, delta2_si) recipe.constrain(xgenerator_sini_si.delta2, delta2_si) # Now the experimental parameters - recipe.addVar(xgenerator_ni.scale, name = "xscale_ni") - recipe.addVar(xgenerator_si.scale, name = "xscale_si") - recipe.addVar(ngenerator_ni.scale, name = "nscale_ni") + recipe.addVar(xgenerator_ni.scale, name="xscale_ni") + recipe.addVar(xgenerator_si.scale, name="xscale_si") + recipe.addVar(ngenerator_ni.scale, name="nscale_ni") recipe.addVar(xcontribution_sini.scale, 1.0, "xscale_sini") recipe.newVar("pscale_sini_ni", 0.8) recipe.constrain(xgenerator_sini_ni.scale, "pscale_sini_ni") recipe.constrain(xgenerator_sini_si.scale, "1 - pscale_sini_ni") # The qdamp parameters are too correlated to vary so we fix them based on - # previous measurments. + # previous measurements. xgenerator_ni.qdamp.value = 0.055 xgenerator_si.qdamp.value = 0.051 ngenerator_ni.qdamp.value = 0.030 @@ -137,6 +142,7 @@ def makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -145,65 +151,66 @@ def plotResults(recipe): xr_ni = xnickel.profile.x xg_ni = xnickel.profile.y xgcalc_ni = xnickel.profile.ycalc - xdiffzero_ni = -0.8 * max(xg_ni) * numpy.ones_like(xg_ni) + xdiffzero_ni = -0.8 * max(xg_ni) * numpy.ones_like(xg_ni) xdiff_ni = xg_ni - xgcalc_ni + xdiffzero_ni xsilicon = recipe.xsilicon xr_si = xsilicon.profile.x xg_si = xsilicon.profile.y xgcalc_si = xsilicon.profile.ycalc - xdiffzero_si = -0.8 * max(xg_si) * numpy.ones_like(xg_si) + xdiffzero_si = -0.8 * max(xg_si) * numpy.ones_like(xg_si) xdiff_si = xg_si - xgcalc_si + xdiffzero_si nnickel = recipe.nnickel nr_ni = nnickel.profile.x ng_ni = nnickel.profile.y ngcalc_ni = nnickel.profile.ycalc - ndiffzero_ni = -0.8 * max(ng_ni) * numpy.ones_like(ng_ni) + ndiffzero_ni = -0.8 * max(ng_ni) * numpy.ones_like(ng_ni) ndiff_ni = ng_ni - ngcalc_ni + ndiffzero_ni xsini = recipe.xsini xr_sini = xsini.profile.x xg_sini = xsini.profile.y xgcalc_sini = xsini.profile.ycalc - xdiffzero_sini = -0.8 * max(xg_sini) * numpy.ones_like(xg_sini) + xdiffzero_sini = -0.8 * max(xg_sini) * numpy.ones_like(xg_sini) xdiff_sini = xg_sini - xgcalc_sini + xdiffzero_sini - import pylab + pylab.subplot(2, 2, 1) - pylab.plot(xr_ni,xg_ni,'bo',label="G(r) x-ray nickel Data") - pylab.plot(xr_ni,xgcalc_ni,'r-',label="G(r) x-ray nickel Fit") - pylab.plot(xr_ni,xdiff_ni,'g-',label="G(r) x-ray nickel diff") - pylab.plot(xr_ni,xdiffzero_ni,'k-') + pylab.plot(xr_ni, xg_ni, "bo", label="G(r) x-ray nickel Data") + pylab.plot(xr_ni, xgcalc_ni, "r-", label="G(r) x-ray nickel Fit") + pylab.plot(xr_ni, xdiff_ni, "g-", label="G(r) x-ray nickel diff") + pylab.plot(xr_ni, xdiffzero_ni, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) pylab.subplot(2, 2, 2) - pylab.plot(xr_si,xg_si,'bo',label="G(r) x-ray silicon Data") - pylab.plot(xr_si,xgcalc_si,'r-',label="G(r) x-ray silicon Fit") - pylab.plot(xr_si,xdiff_si,'g-',label="G(r) x-ray silicon diff") - pylab.plot(xr_si,xdiffzero_si,'k-') + pylab.plot(xr_si, xg_si, "bo", label="G(r) x-ray silicon Data") + pylab.plot(xr_si, xgcalc_si, "r-", label="G(r) x-ray silicon Fit") + pylab.plot(xr_si, xdiff_si, "g-", label="G(r) x-ray silicon diff") + pylab.plot(xr_si, xdiffzero_si, "k-") pylab.legend(loc=1) pylab.subplot(2, 2, 3) - pylab.plot(nr_ni,ng_ni,'bo',label="G(r) neutron nickel Data") - pylab.plot(nr_ni,ngcalc_ni,'r-',label="G(r) neutron nickel Fit") - pylab.plot(nr_ni,ndiff_ni,'g-',label="G(r) neutron nickel diff") - pylab.plot(nr_ni,ndiffzero_ni,'k-') + pylab.plot(nr_ni, ng_ni, "bo", label="G(r) neutron nickel Data") + pylab.plot(nr_ni, ngcalc_ni, "r-", label="G(r) neutron nickel Fit") + pylab.plot(nr_ni, ndiff_ni, "g-", label="G(r) neutron nickel diff") + pylab.plot(nr_ni, ndiffzero_ni, "k-") pylab.legend(loc=1) pylab.subplot(2, 2, 4) - pylab.plot(xr_sini,xg_sini,'bo',label="G(r) x-ray sini Data") - pylab.plot(xr_sini,xgcalc_sini,'r-',label="G(r) x-ray sini Fit") - pylab.plot(xr_sini,xdiff_sini,'g-',label="G(r) x-ray sini diff") - pylab.plot(xr_sini,xdiffzero_sini,'k-') + pylab.plot(xr_sini, xg_sini, "bo", label="G(r) x-ray sini Data") + pylab.plot(xr_sini, xgcalc_sini, "r-", label="G(r) x-ray sini Fit") + pylab.plot(xr_sini, xdiff_sini, "g-", label="G(r) x-ray sini diff") + pylab.plot(xr_sini, xdiffzero_sini, "k-") pylab.legend(loc=1) pylab.show() return + if __name__ == "__main__": # Make the data and the recipe @@ -215,8 +222,9 @@ def plotResults(recipe): xdata_sini = "data/si90ni10-q27r60-xray.gr" # Make the recipe - recipe = makeRecipe(ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, - xdata_sini) + recipe = makeRecipe( + ciffile_ni, ciffile_si, xdata_ni, ndata_ni, xdata_si, xdata_sini + ) # Optimize scipyOptimize(recipe) diff --git a/doc/examples/crystalpdfobjcryst.py b/doc/examples/crystalpdfobjcryst.py index c7201493..48f94e0e 100644 --- a/doc/examples/crystalpdfobjcryst.py +++ b/doc/examples/crystalpdfobjcryst.py @@ -12,26 +12,28 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement using pyobjcryst and PDFGenerator. This example is similar to crystalpdf.py, except that here we refine a -pyobjcryst crystal object. In this example we use internal constraints provided -by the ObjCrystCrystalParSet structure adapter. +pyobjcryst crystal object. In this example we use internal constraints +provided by the ObjCrystCrystalParSet structure adapter. """ +from crystalpdf import plotResults +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize -from crystalpdf import plotResults ####### Example Code + def makeRecipe(ciffile, datname): """Create a fitting recipe for crystalline PDF data.""" @@ -46,7 +48,7 @@ def makeRecipe(ciffile, datname): parser = PDFParser() parser.parseFile(datname) profile.loadParsedData(parser) - profile.setCalculationRange(xmax = 20) + profile.setCalculationRange(xmax=20) ## The ProfileGenerator # This time we use the CreateCrystalFromCIF method of pyobjcryst.crystal to @@ -60,7 +62,7 @@ def makeRecipe(ciffile, datname): ## The FitContribution contribution = FitContribution("nickel") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") # Make the FitRecipe and add the FitContribution. recipe = FitRecipe() @@ -89,7 +91,7 @@ def makeRecipe(ciffile, datname): for par in phase.sgpars: recipe.addVar(par) # set the initial thermal factor to a non-zero value - assert hasattr(recipe, 'B11_0') + assert hasattr(recipe, "B11_0") recipe.B11_0 = 0.1 # We now select non-structural parameters to refine. @@ -103,6 +105,7 @@ def makeRecipe(ciffile, datname): # Give the recipe away so it can be used! return recipe + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/crystalpdftwodata.py b/doc/examples/crystalpdftwodata.py index a43da784..cd2aa0c6 100644 --- a/doc/examples/crystalpdftwodata.py +++ b/doc/examples/crystalpdftwodata.py @@ -12,28 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement of two-phase structure. -This example uses PDFGenerator to refine a single structure two profiles. -This will require setting up two FitContribution, each with its own -PDFGenerator. However, the PDFGenerators will refer to the same underlying -ObjCrystCrystalParSet. +This example uses PDFGenerator to refine a single structure two +profiles. This will require setting up two FitContribution, each with +its own PDFGenerator. However, the PDFGenerators will refer to the same +underlying ObjCrystCrystalParSet. """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize ####### Example Code + def makeRecipe(ciffile, xdatname, ndatname): """Create a fitting recipe for crystalline PDF data.""" @@ -47,12 +48,12 @@ def makeRecipe(ciffile, xdatname, ndatname): parser = PDFParser() parser.parseFile(xdatname) xprofile.loadParsedData(parser) - xprofile.setCalculationRange(xmax = 20) + xprofile.setCalculationRange(xmax=20) parser = PDFParser() parser.parseFile(ndatname) nprofile.loadParsedData(parser) - nprofile.setCalculationRange(xmax = 20) + nprofile.setCalculationRange(xmax=20) ## The ProfileGenerators # We need one of these for the x-ray data. @@ -83,11 +84,11 @@ def makeRecipe(ciffile, xdatname, ndatname): # We associate the x-ray PDFGenerator and Profile in one FitContribution... xcontribution = FitContribution("xnickel") xcontribution.addProfileGenerator(xgenerator) - xcontribution.setProfile(xprofile, xname = "r") + xcontribution.setProfile(xprofile, xname="r") # and the neutron objects in another. ncontribution = FitContribution("nnickel") ncontribution.addProfileGenerator(ngenerator) - ncontribution.setProfile(nprofile, xname = "r") + ncontribution.setProfile(nprofile, xname="r") # This example is different than the previous ones in that we are composing # a residual function from other residuals (one for the x-ray contribution @@ -116,7 +117,7 @@ def makeRecipe(ciffile, xdatname, ndatname): recipe.addVar(ngenerator.scale, 1, "nscale") recipe.addVar(xgenerator.qdamp, 0.01, "xqdamp") recipe.addVar(ngenerator.qdamp, 0.01, "nqdamp") - # delta2 is a non-structual material propery. Thus, we constrain together + # delta2 is a non-structual material property. Thus, we constrain together # delta2 Parameter from each PDFGenerator. delta2 = recipe.newVar("delta2", 2) recipe.constrain(xgenerator.delta2, delta2) @@ -132,6 +133,7 @@ def makeRecipe(ciffile, xdatname, ndatname): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -149,18 +151,19 @@ def plotResults(recipe): ndiff = ng - ngcalc + ndiffzero import pylab + pylab.subplot(2, 1, 1) - pylab.plot(xr,xg,'bo',label="G(r) x-ray Data") - pylab.plot(xr,xgcalc,'r-',label="G(r) x-ray Fit") - pylab.plot(xr,xdiff,'g-',label="G(r) x-ray diff") - pylab.plot(xr,xdiffzero,'k-') + pylab.plot(xr, xg, "bo", label="G(r) x-ray Data") + pylab.plot(xr, xgcalc, "r-", label="G(r) x-ray Fit") + pylab.plot(xr, xdiff, "g-", label="G(r) x-ray diff") + pylab.plot(xr, xdiffzero, "k-") pylab.legend(loc=1) pylab.subplot(2, 1, 2) - pylab.plot(nr,ng,'bo',label="G(r) neutron Data") - pylab.plot(nr,ngcalc,'r-',label="G(r) neutron Fit") - pylab.plot(nr,ndiff,'g-',label="G(r) neutron diff") - pylab.plot(nr,ndiffzero,'k-') + pylab.plot(nr, ng, "bo", label="G(r) neutron Data") + pylab.plot(nr, ngcalc, "r-", label="G(r) neutron Fit") + pylab.plot(nr, ndiff, "g-", label="G(r) neutron diff") + pylab.plot(nr, ndiffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -168,6 +171,7 @@ def plotResults(recipe): pylab.show() return + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/crystalpdftwophase.py b/doc/examples/crystalpdftwophase.py index df7cce77..b0fcf30d 100644 --- a/doc/examples/crystalpdftwophase.py +++ b/doc/examples/crystalpdftwophase.py @@ -12,28 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF refinement of two-phase structure. -Like the ones before, this example uses PDFGenerator to refine a structure to -PDF data. However, for a multi-phase structure one must use multiple -PDFGenerators. This example refines a physical mixture of nickel and silicon to -find the structures and phase fractions. +Like the ones before, this example uses PDFGenerator to refine a +structure to PDF data. However, for a multi-phase structure one must use +multiple PDFGenerators. This example refines a physical mixture of +nickel and silicon to find the structures and phase fractions. """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults - -from gaussianrecipe import scipyOptimize ####### Example Code + def makeRecipe(niciffile, siciffile, datname): """Create a fitting recipe for crystalline PDF data.""" @@ -44,7 +45,7 @@ def makeRecipe(niciffile, siciffile, datname): parser = PDFParser() parser.parseFile(datname) profile.loadParsedData(parser) - profile.setCalculationRange(xmax = 20) + profile.setCalculationRange(xmax=20) ## The ProfileGenerator # In order to fit two phases simultaneously, we must use two PDFGenerators. @@ -72,7 +73,7 @@ def makeRecipe(niciffile, siciffile, datname): contribution = FitContribution("nisi") contribution.addProfileGenerator(generator_ni) contribution.addProfileGenerator(generator_si) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") # Write the fitting equation. We want to sum the PDFs from each phase and # multiply it by a scaling factor. We also want a certain phase scaling @@ -95,7 +96,7 @@ def makeRecipe(niciffile, siciffile, datname): recipe.constrain(generator_ni.qdamp, "qdamp") recipe.constrain(generator_si.qdamp, "qdamp") - # Vary the gloabal scale as well. + # Vary the global scale as well. recipe.addVar(contribution.scale, 1) # Now we can configure the structural parameters. Since we're using @@ -105,13 +106,13 @@ def makeRecipe(niciffile, siciffile, datname): # First the nickel parameters phase_ni = generator_ni.phase for par in phase_ni.sgpars: - recipe.addVar(par, name = par.name + "_ni") - recipe.addVar(generator_ni.delta2, name = "delta2_ni") + recipe.addVar(par, name=par.name + "_ni") + recipe.addVar(generator_ni.delta2, name="delta2_ni") # Next the silicon parameters phase_si = generator_si.phase for par in phase_si.sgpars: - recipe.addVar(par, name = par.name + "_si") - recipe.addVar(generator_si.delta2, name = "delta2_si") + recipe.addVar(par, name=par.name + "_si") + recipe.addVar(generator_si.delta2, name="delta2_si") # We have prior information from the earlier examples so we'll use it here # in the form of restraints. @@ -121,22 +122,23 @@ def makeRecipe(niciffile, siciffile, datname): # derived has no uncertainty. Thus, we will tell the recipe to scale the # residual, which means that it will be weighted as much as the average # data point during the fit. - recipe.restrain("a_ni", lb = 3.527, ub = 3.527, scaled = True) + recipe.restrain("a_ni", lb=3.527, ub=3.527, scaled=True) # Now we do the same with the delta2 and Biso parameters (remember that # Biso = 8*pi**2*Uiso) - recipe.restrain("delta2_ni", lb = 2.22, ub = 2.22, scaled = True) - recipe.restrain("Biso_0_ni", lb = 0.454, ub = 0.454, scaled = True) + recipe.restrain("delta2_ni", lb=2.22, ub=2.22, scaled=True) + recipe.restrain("Biso_0_ni", lb=0.454, ub=0.454, scaled=True) # # We can do the same with the silicon values. We haven't done a thorough # job of measuring the uncertainties in the results, so we'll scale these # as well. - recipe.restrain("a_si", lb = 5.430, ub = 5.430, scaled = True) - recipe.restrain("delta2_si", lb = 3.54, ub = 3.54, scaled = True) - recipe.restrain("Biso_0_si", lb = 0.645, ub = 0.645, scaled = True) + recipe.restrain("a_si", lb=5.430, ub=5.430, scaled=True) + recipe.restrain("delta2_si", lb=3.54, ub=3.54, scaled=True) + recipe.restrain("Biso_0_si", lb=0.645, ub=0.645, scaled=True) # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -148,10 +150,11 @@ def plotResults(recipe): diff = g - gcalc + diffzero import pylab - pylab.plot(r,g,'bo',label="G(r) Data") - pylab.plot(r, gcalc,'r-',label="G(r) Fit") - pylab.plot(r,diff,'g-',label="G(r) diff") - pylab.plot(r,diffzero,'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) diff --git a/doc/examples/data/ni.iq b/doc/examples/data/ni.iq index 917301a1..56594ba3 100644 --- a/doc/examples/data/ni.iq +++ b/doc/examples/data/ni.iq @@ -2462,4 +2462,3 @@ 26.123511 0 26.133371 0 26.143228 0 - diff --git a/doc/examples/debyemodel.py b/doc/examples/debyemodel.py index 79bf7add..0ec673a7 100644 --- a/doc/examples/debyemodel.py +++ b/doc/examples/debyemodel.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting the Debye model to experimental Debye-Waller factors. In this example, we build a fit recipe that uses an external function that can @@ -34,11 +33,15 @@ """ import numpy - -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults - from gaussianrecipe import scipyOptimize +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) + # The data data = """\ 015.0 0.00334 0.00013 @@ -56,28 +59,29 @@ ####### Example Code + def makeRecipe(): """Make the recipe for the fit. - The instructions for what we want to refine, and how to refine it will be - defined within a FitRecipe instance. The job of a FitRecipe is to collect - and associate all the data, the fitting equations, fitting variables, - constraints and restrations. We will demonstrate each of these within the - code. - - Data is held within a Profile object. The Profile is simply a container - that holds the data, and the theoretical profile once it has been - calculated. - - Data is associated with a fitting equation within a FitContribution. The - FitContribution defines the equation and parameters that will be adjusted - to fit the data. The fitting equation can be defined within a function or - optionally within the ProfileGenerator class. We won't need the - ProfileGenerator class in this example since the signature of the fitting - equation (the 'debye' function defined below) is so simple. The - FitContribution also defines the residual function to optimize for the - data/equation pair. This can be modified, but we won't do that here. - + The instructions for what we want to refine, and how to refine it + will be defined within a FitRecipe instance. The job of a FitRecipe + is to collect and associate all the data, the fitting equations, + fitting variables, constraints and restrations. We will demonstrate + each of these within the code. + + Data is held within a Profile object. The Profile is simply a + container that holds the data, and the theoretical profile once it + has been calculated. + + Data is associated with a fitting equation within a FitContribution. + The FitContribution defines the equation and parameters that will be + adjusted to fit the data. The fitting equation can be defined within + a function or optionally within the ProfileGenerator class. We won't + need the ProfileGenerator class in this example since the signature + of the fitting equation (the 'debye' function defined below) is so + simple. The FitContribution also defines the residual function to + optimize for the data/equation pair. This can be modified, but we + won't do that here. """ ## The Profile @@ -86,7 +90,7 @@ def makeRecipe(): # Load data and add it to the profile. It is our responsibility to get our # data into the profile. - xydy = numpy.array(map(float, data.split()), dtype=float).reshape(-1,3) + xydy = numpy.array(map(float, data.split()), dtype=float).reshape(-1, 3) x, y, dy = numpy.hsplit(xydy, 3) profile.setObservedProfile(x, y, dy) @@ -141,16 +145,17 @@ def makeRecipe(): # We would like to 'suggest' that the offset should remain positive. This # is somethine that we know about the system that might help the refinement # converge to a physically reasonable result. We will do this with a soft - # contraint, or restraint. Here we restrain the offset variable to between + # constraint, or restraint. Here we restrain the offset variable to between # 0 and infinity. We tell the recipe that we want to scale the penalty for # breaking the restraint by the point-average chi^2 value so that the # restraint is roughly as significant as any other data point throughout # the fit. - recipe.restrain(recipe.offset, lb = 0, scaled = True) + recipe.restrain(recipe.offset, lb=0, scaled=True) # We're done setting up the recipe. We can now do other things with it. return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -163,15 +168,17 @@ def plotResults(recipe): Ucalc = recipe.pb.profile.ycalc import pylab - pylab.plot(T,U,'o',label="Pb $U_{iso}$ Data") - pylab.plot(T,Ucalc) + + pylab.plot(T, U, "o", label="Pb $U_{iso}$ Data") + pylab.plot(T, Ucalc) pylab.xlabel("T (K)") pylab.ylabel(r"$U_{iso} (\AA^2)$") - pylab.legend(loc = (0.0,0.8)) + pylab.legend(loc=(0.0, 0.8)) pylab.show() return + def main(): """The workflow of creating, running and inspecting a fit.""" @@ -197,13 +204,15 @@ def main(): # as we treat them as if existing in some external library that we cannot # modify. + def debye(T, m, thetaD): """A wrapped version of 'adps' that can handle an array of T-values.""" y = numpy.array([adps(m, thetaD, x) for x in T]) return y -def adps(m,thetaD,T): - """Calculates atomic displacement factors within the Debye model + +def adps(m, thetaD, T): + """Calculates atomic displacement factors within the Debye model. = (3h^2/4 pi^2 m kB thetaD)(phi(thetaD/T)/(ThetaD/T) + 1/4) @@ -214,14 +223,13 @@ def adps(m,thetaD,T): return: Uiso -- float -- the thermal factor from the Debye recipe at temp T - """ - h = 6.6260755e-34 # Planck's constant. J.s of m^2.kg/s + h = 6.6260755e-34 # Planck's constant. J.s of m^2.kg/s kB = 1.3806503e-23 # Boltzmann's constant. J/K - amu = 1.66053886e-27 # Atomic mass unit. kg + amu = 1.66053886e-27 # Atomic mass unit. kg def __phi(x): - """evaluates the phi integral needed in Debye calculation + """Evaluates the phi integral needed in Debye calculation. phi(x) = (1/x) int_0^x xi/(exp(xi)-1) dxi @@ -230,28 +238,27 @@ def __phi(x): returns: phi -- float -- value of the phi function - """ - def __debyeKernel(xi): - """function needed by debye calculators - """ - y = xi/(numpy.exp(xi)-1) + def __debyeKernel(xi): + """Function needed by debye calculators.""" + y = xi / (numpy.exp(xi) - 1) return y import scipy.integrate int = scipy.integrate.quad(__debyeKernel, 0, x) - phi = (1/x) * int[0] + phi = (1 / x) * int[0] return phi - m = m * amu - u2 = (3*h**2 / (4 * numpy.pi**2 *m *kB *thetaD))*\ - (__phi(thetaD/T)/(thetaD/T) + 1./4.) + u2 = (3 * h**2 / (4 * numpy.pi**2 * m * kB * thetaD)) * ( + __phi(thetaD / T) / (thetaD / T) + 1.0 / 4.0 + ) + + return u2 * 1e20 - return u2*1e20 if __name__ == "__main__": diff --git a/doc/examples/debyemodelII.py b/doc/examples/debyemodelII.py index 0e5e3542..6ff8d1e6 100644 --- a/doc/examples/debyemodelII.py +++ b/doc/examples/debyemodelII.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting the Debye recipe to experimental Debye-Waller factors. This is an extension of example in debyemodel.py. The recipe we create will @@ -34,23 +33,25 @@ done. """ -from diffpy.srfit.fitbase import FitRecipe, FitResults - from debyemodel import makeRecipe, scipyOptimize +from diffpy.srfit.fitbase import FitRecipe, FitResults + ####### Example Code + def makeRecipeII(): """Make a recipe for fitting low and high temperature regions. We will fit the low and high temperature parts of Debye curve - simultaneously with the same Debye temperature, but different offsets. + simultaneously with the same Debye temperature, but different + offsets. We will make two FitRecipes using the makeRecipe function from - debyemodel.py and extract the configured FitContribution from each. We will - use different fitting ranges for each FitContribution and constrain the - Debye temperature in each FitContribution to be the same. - + debyemodel.py and extract the configured FitContribution from each. + We will use different fitting ranges for each FitContribution and + constrain the Debye temperature in each FitContribution to be the + same. """ # We'll throw these away. We just want the FitContributions that are @@ -81,8 +82,8 @@ def makeRecipeII(): # Vary the offset from each FitContribution separately, while keeping the # Debye temperatures the same. We give each offset variable a different # name in the recipe so it retains its identity. - recipe.addVar(recipe.lowT.offset, name = "lowToffset") - recipe.addVar(recipe.highT.offset, name = "highToffset") + recipe.addVar(recipe.lowT.offset, name="lowToffset") + recipe.addVar(recipe.highT.offset, name="highToffset") # We create a new Variable and use the recipe's "constrain" method to # associate the Debye temperature parameters with that variable. recipe.newVar("thetaD", 100) @@ -90,6 +91,7 @@ def makeRecipeII(): recipe.constrain(recipe.highT.thetaD, "thetaD") return recipe + def plotResults(recipe): """Display the results contained within a refined FitRecipe.""" @@ -99,8 +101,8 @@ def plotResults(recipe): # We want to extend the fitting range to its full extent so we can get a # nice full plot. - recipe.lowT.profile.setCalculationRange(xmin='obs', xmax='obs') - recipe.highT.profile.setCalculationRange(xmin='obs', xmax='obs') + recipe.lowT.profile.setCalculationRange(xmin="obs", xmax="obs") + recipe.highT.profile.setCalculationRange(xmin="obs", xmax="obs") T = recipe.lowT.profile.x U = recipe.lowT.profile.y # We can use a FitContribution's 'evaluateEquation' method to evaluate @@ -114,18 +116,23 @@ def plotResults(recipe): # Now we can plot this. import pylab - pylab.plot(T,U,'o',label="Pb $U_{iso}$ Data") - lbl1 = r"$T_d$=%3.1f K, lowToff=%1.5f $\AA^2$"% (abs(thetaD),lowToffset) - lbl2 = r"$T_d$=%3.1f K, highToff=%1.5f $\AA^2$"% (abs(thetaD),highToffset) - pylab.plot(T,lowU,label=lbl1) - pylab.plot(T,highU,label=lbl2) + + pylab.plot(T, U, "o", label="Pb $U_{iso}$ Data") + lbl1 = r"$T_d$=%3.1f K, lowToff=%1.5f $\AA^2$" % (abs(thetaD), lowToffset) + lbl2 = r"$T_d$=%3.1f K, highToff=%1.5f $\AA^2$" % ( + abs(thetaD), + highToffset, + ) + pylab.plot(T, lowU, label=lbl1) + pylab.plot(T, highU, label=lbl2) pylab.xlabel("T (K)") pylab.ylabel(r"$U_{iso} (\AA^2)$") - pylab.legend(loc = (0.0,0.8)) + pylab.legend(loc=(0.0, 0.8)) pylab.show() return + def main(): # Create the recipe diff --git a/doc/examples/ellipsoidsas.py b/doc/examples/ellipsoidsas.py index 87c4d016..b3e8d18e 100644 --- a/doc/examples/ellipsoidsas.py +++ b/doc/examples/ellipsoidsas.py @@ -12,18 +12,21 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## +"""Example of a refinement of SAS I(Q) data to an ellipsoidal model.""" -"""Example of a refinement of SAS I(Q) data to an ellipsoidal model. -""" +from gaussianrecipe import scipyOptimize +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.sas import SASGenerator, SASParser -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults, Profile - -from gaussianrecipe import scipyOptimize ####### Example Code + def makeRecipe(datname): """Create a fitting recipe for ellipsoidal SAS data.""" @@ -45,6 +48,7 @@ def makeRecipe(datname): # data. The documentation for the various sas models can be found at # http://www.sasview.org. from sas.models.EllipsoidModel import EllipsoidModel + model = EllipsoidModel() generator = SASGenerator("generator", model) @@ -53,7 +57,7 @@ def makeRecipe(datname): # before. contribution = FitContribution("ellipsoid") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "q") + contribution.setProfile(profile, xname="q") # We want to fit the log of the signal to the log of the data so that the # higher-Q information remains significant. There are no I(Q) uncertainty @@ -67,7 +71,7 @@ def makeRecipe(datname): ## Configure the fit variables # The SASGenerator uses the parameters from the params and dispersion - # attribues of the model. These vary from model to model, but are adopted + # attributes of the model. These vary from model to model, but are adopted # as SrFit Parameters within the generator. Whereas the dispersion # parameters are accessible as, e.g. "radius.width", within the # SASGenerator these are named like "radius_width". @@ -81,6 +85,7 @@ def makeRecipe(datname): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -91,9 +96,10 @@ def plotResults(recipe): diff = y - ycalc + min(y) import pylab - pylab.loglog(r,y,'bo',label="I(Q) Data") - pylab.loglog(r, ycalc,'r-',label="I(Q) Fit") - pylab.loglog(r,diff,'g-',label="I(Q) diff") + + pylab.loglog(r, y, "bo", label="I(Q) Data") + pylab.loglog(r, ycalc, "r-", label="I(Q) Fit") + pylab.loglog(r, diff, "g-", label="I(Q) diff") pylab.xlabel(r"$Q (\AA^{-1})$") pylab.ylabel("$I (arb. units)$") pylab.legend(loc=1) @@ -101,6 +107,7 @@ def plotResults(recipe): pylab.show() return + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/gaussiangenerator.py b/doc/examples/gaussiangenerator.py index 7b0818b8..6df50694 100644 --- a/doc/examples/gaussiangenerator.py +++ b/doc/examples/gaussiangenerator.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of using ProfileGenerators in FitContributions. This is an example of building a ProfileGenerator and using it in a @@ -41,28 +40,32 @@ from numpy import exp -from diffpy.srfit.fitbase import ProfileGenerator, Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + Profile, + ProfileGenerator, +) ####### Example Code + class GaussianGenerator(ProfileGenerator): """A class for calculating a Gaussian profile. - Generating a Gaussian is not difficult, as was shown in gaussianrecipe.py. - Here we create a class that encapsulates this functionality. Placing this - class in a python module would make it possible to import it and reuse it, - thereby saving future code writing and debugging. - - The purpose of a ProfileGenerator is to - 1) provide a function that generates a profile signal - 2) organize the Parameters required for the calculation - - Thus, this class overloads the __init__ method to create the necessary - Parameters for the calculation, and the __call__ method to generate the - signal. + Generating a Gaussian is not difficult, as was shown in + gaussianrecipe.py. Here we create a class that encapsulates this + functionality. Placing this class in a python module would make it + possible to import it and reuse it, thereby saving future code + writing and debugging. + The purpose of a ProfileGenerator is to 1) provide a function that + generates a profile signal 2) organize the Parameters required for + the calculation + Thus, this class overloads the __init__ method to create the + necessary Parameters for the calculation, and the __call__ method to + generate the signal. """ def __init__(self, name): @@ -77,7 +80,6 @@ def __init__(self, name): A -- The amplitude x0 -- The center sigma -- The width - """ # This initializes various parts of the generator ProfileGenerator.__init__(self, name) @@ -86,17 +88,16 @@ def __init__(self, name): # ProfileGenerator. The signature is # _newParameter(name, value). # See the API for full details. - self._newParameter('A', 1.0) - self._newParameter('x0', 0.0) - self._newParameter('sigma', 1.0) + self._newParameter("A", 1.0) + self._newParameter("x0", 0.0) + self._newParameter("sigma", 1.0) return def __call__(self, x): """Calculate the profile. - Here we calculate the Gaussian profile given the independent variable, - x. We will define it as we did in gaussianrecipe.py. - + Here we calculate the Gaussian profile given the independent + variable, x. We will define it as we did in gaussianrecipe.py. """ # First we must get the values of the Parameters. Since we used # _newParameter to create them, the Parameters are accessible as @@ -107,19 +108,20 @@ def __call__(self, x): # Now we can use them. Note that we imported exp from numpy at the top # of the module. - y = A * exp(-0.5*(x-x0)**2/sigma**2) + y = A * exp(-0.5 * (x - x0) ** 2 / sigma**2) # Now return the value. return y + # End class GaussianGenerator + def makeRecipe(): """Create a recipe that uses the GaussianGenerator. This will create a FitContribution that uses the GaussianGenerator, associate this with a Profile, and use this to define a FitRecipe. - """ ## The Profile @@ -159,7 +161,7 @@ def makeRecipe(): # gaussianrecipe.py so we can expect the same output. recipe.addVar(generator.A, 1) recipe.addVar(generator.x0, 5) - recipe.addVar(generator.sigma, name = "sig") + recipe.addVar(generator.sigma, name="sig") recipe.sig.value = 1 # Give the recipe away so it can be used! @@ -171,6 +173,7 @@ def makeRecipe(): # We can use main from gaussianrecipe.py, since this doesn't care if we use # a ProfileGenerator or not. from gaussianrecipe import main + main() # End of file diff --git a/doc/examples/gaussianrecipe.py b/doc/examples/gaussianrecipe.py index b4bb404d..8f762ba7 100644 --- a/doc/examples/gaussianrecipe.py +++ b/doc/examples/gaussianrecipe.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting a Gaussian to simulated data. This is an example of building a fit recipe that can be driven by an optimizer @@ -32,7 +31,7 @@ Extensions -After reading through the code, try to perform the folowing tasks. The process +After reading through the code, try to perform the following tasks. The process will leave you with a much better understanding of how SrFit works. - Play around with setting the values of the Variables and Parameters. What @@ -46,10 +45,16 @@ from __future__ import print_function -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) ####### Example Code + def main(): """The workflow of creating, running and inspecting a fit.""" @@ -78,17 +83,16 @@ def main(): def makeRecipe(): """Make a FitRecipe for fitting a Gaussian curve to data. - The instructions for what we want to refine, and how to refine it will be - defined within a FitRecipe instance. The job of a FitRecipe is to collect - and associate all the data, the fitting equations, fitting variables, - constraints and restraints. The configured recipe provides a 'residual' - function and the initial variable values that an optimizer can use to - refine the variables to minimize the disagreement between the calculated - profile and the data. - - Once we define the FitRecipe, we can send it an optimizer to be optimized. - See the 'scipyOptimize' function. + The instructions for what we want to refine, and how to refine it + will be defined within a FitRecipe instance. The job of a FitRecipe + is to collect and associate all the data, the fitting equations, + fitting variables, constraints and restraints. The configured recipe + provides a 'residual' function and the initial variable values that + an optimizer can use to refine the variables to minimize the + disagreement between the calculated profile and the data. + Once we define the FitRecipe, we can send it an optimizer to be + optimized. See the 'scipyOptimize' function. """ ## The Profile @@ -150,18 +154,18 @@ def makeRecipe(): # Here we create a Variable named 'sig', which is tied to the 'sigma' # Parameter of our FitContribution. We give it an initial value through the # FitRecipe instance. - recipe.addVar(contribution.sigma, name = "sig") + recipe.addVar(contribution.sigma, name="sig") recipe.sig.value = 1 return recipe + def scipyOptimize(recipe): """Optimize the recipe created above using scipy. - The FitRecipe we created in makeRecipe has a 'residual' method that we can - be minimized using a scipy optimizer. The details are described in the - source. - + The FitRecipe we created in makeRecipe has a 'residual' method that + we can be minimized using a scipy optimizer. The details are + described in the source. """ # We're going to use the least-squares (Levenberg-Marquardt) optimizer from @@ -169,11 +173,13 @@ def scipyOptimize(recipe): # (recipe.residual) and the starting values of the Variables # (recipe.getValues()). from scipy.optimize.minpack import leastsq + print("Fit using scipy's LM optimizer") leastsq(recipe.residual, recipe.getValues()) return + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -189,15 +195,17 @@ def plotResults(recipe): # This stuff is specific to pylab from the matplotlib distribution. import pylab - pylab.plot(x, y, 'b.', label = "observed Gaussian") - pylab.plot(x, ycalc, 'g-', label = "calculated Gaussian") - pylab.legend(loc = (0.0,0.8)) + + pylab.plot(x, y, "b.", label="observed Gaussian") + pylab.plot(x, ycalc, "g-", label="calculated Gaussian") + pylab.legend(loc=(0.0, 0.8)) pylab.xlabel("x") pylab.ylabel("y") pylab.show() return + if __name__ == "__main__": main() diff --git a/doc/examples/interface.py b/doc/examples/interface.py index 106af7a2..5b1a250e 100644 --- a/doc/examples/interface.py +++ b/doc/examples/interface.py @@ -12,17 +12,22 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting a Gaussian to simulated data. -This is like gaussianrecipe.py, but it uses a shorthand interface defined in -the diffpy.srfit.interface.interface.py module. +This is like gaussianrecipe.py, but it uses a shorthand interface +defined in the diffpy.srfit.interface.interface.py module. """ -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) ####### Example Code + def main(): p = Profile() @@ -47,11 +52,12 @@ def main(): # loosely tying parameters to a value. r = FitRecipe() r |= c - r += (c.A, 0.5), (c.x0, 5), 'sig' - r *= c.sigma, 'sig' + r += (c.A, 0.5), (c.x0, 5), "sig" + r *= c.sigma, "sig" r %= c.A, 0.5, 0.5 from gaussianrecipe import scipyOptimize + scipyOptimize(r) res = FitResults(r) @@ -59,6 +65,7 @@ def main(): res.printResults() # Plot the results. from gaussianrecipe import plotResults + plotResults(r) return diff --git a/doc/examples/npintensity.py b/doc/examples/npintensity.py index 91a2b55d..64498fc6 100644 --- a/doc/examples/npintensity.py +++ b/doc/examples/npintensity.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of using ProfileGenerators in FitContributions. This is an example of building a ProfileGenerator and using it in a @@ -45,44 +44,48 @@ from __future__ import print_function import numpy +from gaussianrecipe import scipyOptimize -from diffpy.srfit.fitbase import ProfileGenerator, Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, + ProfileGenerator, +) from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet -from gaussianrecipe import scipyOptimize - ####### Example Code + class IntensityGenerator(ProfileGenerator): """A class for calculating intensity using the Debye equation. - Calculating intensity from a structure is difficult in general. This class - takes a diffpy.structure.Structure instance and from that generates a - theoretical intensity signal. Unlike the example in gaussianrecipe.py, the - intensity generator is not simple. It must take a structure object and some - Parameters, and from that generate a signal. At the same time, the - structure itself (the lattice, atom positions, thermal parameters, etc.) - needs to be refinable. Thus we define this ProfileGenerator to help us - interface which exposes the Parameters required by the calculation and - provides a way for a FitContribution to perform that calculation. - - The purpose of a ProfileGenerator is to - 1) provide a function that generates a profile signal - 2) organize the Parameters required for the calculation - - This generator wraps the 'iofq' function defined below. Knowledge of this - function is not required for this example. - + Calculating intensity from a structure is difficult in general. This + class takes a diffpy.structure.Structure instance and from that + generates a theoretical intensity signal. Unlike the example in + gaussianrecipe.py, the intensity generator is not simple. It must + take a structure object and some Parameters, and from that generate + a signal. At the same time, the structure itself (the lattice, atom + positions, thermal parameters, etc.) needs to be refinable. Thus we + define this ProfileGenerator to help us interface which exposes the + Parameters required by the calculation and provides a way for a + FitContribution to perform that calculation. + + The purpose of a ProfileGenerator is to 1) provide a function that + generates a profile signal 2) organize the Parameters required for + the calculation + + This generator wraps the 'iofq' function defined below. Knowledge of + this function is not required for this example. """ def __init__(self, name): """Define our generator. - In this example we will keep count of how many times the calculation - gets performed. The 'count' attribute will be used to store the count. - + In this example we will keep count of how many times the + calculation gets performed. The 'count' attribute will be used + to store the count. """ ProfileGenerator.__init__(self, name) # Count the calls @@ -132,10 +135,10 @@ def setStructure(self, strufile): The diffpy.structure.Structure instance is held within the DiffpyStructureParSet as the 'stru' attribute. - """ # Load the structure from file from diffpy.structure import Structure + stru = Structure() stru.read(strufile) @@ -156,17 +159,18 @@ def setStructure(self, strufile): def __call__(self, q): """Calculate the intensity. - This ProfileGenerator will be used in a FitContribution that will be - optimized to fit some data. By the time this function is evaluated, - the diffpy.structure.Structure instance has been updated by the - optimizer via the DiffpyStructureParSet defined in setStructure. Thus, - we need only call iofq with the internal structure object. - + This ProfileGenerator will be used in a FitContribution that + will be optimized to fit some data. By the time this function + is evaluated, the diffpy.structure.Structure instance has been + updated by the optimizer via the DiffpyStructureParSet defined + in setStructure. Thus, we need only call iofq with the internal + structure object. """ self.count += 1 print("iofq called", self.count) return iofq(self.phase.stru, q) + # End class IntensityGenerator @@ -175,7 +179,6 @@ def makeRecipe(strufile, datname): This will create a FitContribution that uses the IntensityGenerator, associate this with a Profile, and use this to define a FitRecipe. - """ ## The Profile @@ -200,7 +203,7 @@ def makeRecipe(strufile, datname): # use it in equations with this name. contribution = FitContribution("bucky") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "q") + contribution.setProfile(profile, xname="q") # Now we're ready to define the fitting equation for the FitContribution. # We need to modify the intensity calculation, and we'll do that from @@ -233,8 +236,13 @@ def makeRecipe(strufile, datname): # function and registering it with the FitContribution. pi = numpy.pi exp = numpy.exp + def gaussian(q, q0, width): - return 1/(2*pi*width**2)**0.5 * exp(-0.5 * ((q-q0)/width)**2) + return ( + 1 + / (2 * pi * width**2) ** 0.5 + * exp(-0.5 * ((q - q0) / width) ** 2) + ) # This registers the python function and extracts the name and creates # Parameters from the arguments. @@ -294,6 +302,7 @@ def gaussian(q, q0, width): # Give the recipe away so it can be used! return recipe + def main(): # Make the data and the recipe @@ -315,14 +324,15 @@ def main(): # 'iofq' from the IntensityGenerator. rescount = recipe.fithooks[0].count calcount = recipe.bucky.I.count - footer = "iofq called %i%% of the time"%int(100.0*calcount/rescount) - res.printResults(footer = footer) + footer = "iofq called %i%% of the time" % int(100.0 * calcount / rescount) + res.printResults(footer=footer) # Plot! plotResults(recipe) return + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -335,10 +345,11 @@ def plotResults(recipe): diff = I - Icalc import pylab - pylab.plot(q,I,'ob',label="I(Q) Data") - pylab.plot(q,Icalc,'r-',label="I(Q) Fit") - pylab.plot(q,diff,'g-',label="I(Q) diff") - pylab.plot(q,bkgd,'c-',label="Bkgd. Fit") + + pylab.plot(q, I, "ob", label="I(Q) Data") + pylab.plot(q, Icalc, "r-", label="I(Q) Fit") + pylab.plot(q, diff, "g-", label="I(Q) diff") + pylab.plot(q, bkgd, "c-", label="Bkgd. Fit") pylab.xlabel(r"$Q (\AA^{-1})$") pylab.ylabel("Intensity (arb. units)") pylab.legend(loc=1) @@ -360,7 +371,6 @@ def iofq(S, q): This uses cctbx for the calculation of the f_i if it is available, otherwise f_i = 1. - """ # The functions we need sinc = numpy.sinc @@ -371,9 +381,9 @@ def iofq(S, q): # The precision of distance measurements deltad = 1e-6 - dmult = int(1/deltad) + dmult = int(1 / deltad) deltau = deltad**2 - umult = int(1/deltau) + umult = int(1 / deltau) pairdict = {} elcount = {} @@ -395,11 +405,11 @@ def iofq(S, q): # Get the distance to the desired precision d = S.distance(i, j) - D = int(d*dmult) + D = int(d * dmult) # Get the DW factor to the same precision ss = S[i].Uisoequiv + S[j].Uisoequiv - SS = int(ss*umult) + SS = int(ss * umult) # Record the multiplicity of this pair key = (els[0], els[1], D, SS) @@ -439,23 +449,25 @@ def iofq(S, q): return y + def getXScatteringFactor(el, q): """Get the x-ray scattering factor for an element over the q range. If cctbx is not available, f(q) = 1 is used. - """ try: import cctbx.eltbx.xray_scattering as xray + wk1995 = xray.wk1995(el) g = wk1995.fetch() # at_stol - at sin(theta)/lambda = Q/(4*pi) - f = numpy.asarray( map( g.at_stol, q/(4*numpy.pi) ) ) + f = numpy.asarray(map(g.at_stol, q / (4 * numpy.pi))) return f except ImportError: return 1 -def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl = 1): + +def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl=1): """Make some fake data and save it to file. Make some data to fit. This uses iofq to calculate an intensity curve, and @@ -470,10 +482,10 @@ def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl = 1): sig -- The broadening factor bkgc -- A parameter that gives minor control of the background. nl -- Noise level (0, inf), default 1, larger -> less noise. - """ from diffpy.structure import Structure + S = Structure() S.read(strufile) @@ -487,24 +499,24 @@ def makeData(strufile, q, datname, scale, a, Uiso, sig, bkgc, nl = 1): # We want to broaden the peaks as well. This simulates instrument effects. q0 = q[len(q) // 2] - g = numpy.exp(-0.5*((q-q0)/sig)**2) - y = numpy.convolve(y, g, mode='same')/sum(g) + g = numpy.exp(-0.5 * ((q - q0) / sig) ** 2) + y = numpy.convolve(y, g, mode="same") / sum(g) # Add a polynomial background. - bkgd = (q + bkgc)**2 * (1.5*max(q) - q)**5 + bkgd = (q + bkgc) ** 2 * (1.5 * max(q) - q) ** 5 bkgd *= 0.2 * max(y) / max(bkgd) y += bkgd - # Multipy by a scale factor + # Multiply by a scale factor y *= scale # Calculate the uncertainty - u = (y/nl)**0.5 + u = (y / nl) ** 0.5 # And apply the noise if nl > 0: - y = numpy.random.poisson(y*nl) / nl + y = numpy.random.poisson(y * nl) / nl # Now save it numpy.savetxt(datname, numpy.transpose([q, y, u])) diff --git a/doc/examples/npintensityII.py b/doc/examples/npintensityII.py index 7ab98dce..e4607f90 100644 --- a/doc/examples/npintensityII.py +++ b/doc/examples/npintensityII.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of extracting information from multiple data sets simultaneously. This example builds on npintensitygenerator.py, and uses IntensityGenerator @@ -35,28 +34,32 @@ """ import numpy - -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults -from npintensity import IntensityGenerator -from npintensity import makeData - from gaussianrecipe import scipyOptimize +from npintensity import IntensityGenerator, makeData + +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) ####### Example Code + def makeRecipe(strufile, datname1, datname2): """Create a recipe that uses the IntensityGenerator. - We will create two FitContributions that use the IntensityGenerator from - npintensitygenerator.py and associate each of these with a Profile, and use - this to define a FitRecipe. - - Both simulated data sets come from the same structure. We're going to make - two FitContributions that are identical, except for the profile that is - held in each. We're going to assure that the structures are identical by - using the same DiffpyStructureParSet (which is generated by the - IntensityGenerator when we load the structure) in both generators. + We will create two FitContributions that use the IntensityGenerator + from npintensitygenerator.py and associate each of these with a + Profile, and use this to define a FitRecipe. + Both simulated data sets come from the same structure. We're going + to make two FitContributions that are identical, except for the + profile that is held in each. We're going to assure that the + structures are identical by using the same DiffpyStructureParSet + (which is generated by the IntensityGenerator when we load the + structure) in both generators. """ ## The Profiles @@ -85,10 +88,10 @@ def makeRecipe(strufile, datname1, datname2): # Create the FitContributions. contribution1 = FitContribution("bucky1") contribution1.addProfileGenerator(generator1) - contribution1.setProfile(profile1, xname = "q") + contribution1.setProfile(profile1, xname="q") contribution2 = FitContribution("bucky2") contribution2.addProfileGenerator(generator2) - contribution2.setProfile(profile2, xname = "q") + contribution2.setProfile(profile2, xname="q") # Now we're ready to define the fitting equation for each FitContribution. # The functions registered below will be independent, even though they take @@ -105,8 +108,13 @@ def makeRecipe(strufile, datname1, datname2): # We will create the broadening function by registering a python function. pi = numpy.pi exp = numpy.exp + def gaussian(q, q0, width): - return 1/(2*pi*width**2)**0.5 * exp(-0.5 * ((q-q0)/width)**2) + return ( + 1 + / (2 * pi * width**2) ** 0.5 + * exp(-0.5 * ((q - q0) / width) ** 2) + ) contribution1.registerFunction(gaussian) contribution2.registerFunction(gaussian) @@ -128,32 +136,32 @@ def gaussian(q, q0, width): # background that we just defined in the FitContributions. We have to do # this separately for each FitContribution. We tag the variables so it is # easy to retrieve the background variables. - recipe.addVar(contribution1.b0, 0, name = "b1_0", tag = "bcoeffs1") - recipe.addVar(contribution1.b1, 0, name = "b1_1", tag = "bcoeffs1") - recipe.addVar(contribution1.b2, 0, name = "b1_2", tag = "bcoeffs1") - recipe.addVar(contribution1.b3, 0, name = "b1_3", tag = "bcoeffs1") - recipe.addVar(contribution1.b4, 0, name = "b1_4", tag = "bcoeffs1") - recipe.addVar(contribution1.b5, 0, name = "b1_5", tag = "bcoeffs1") - recipe.addVar(contribution1.b6, 0, name = "b1_6", tag = "bcoeffs1") - recipe.addVar(contribution1.b7, 0, name = "b1_7", tag = "bcoeffs1") - recipe.addVar(contribution1.b8, 0, name = "b1_8", tag = "bcoeffs1") - recipe.addVar(contribution1.b9, 0, name = "b1_9", tag = "bcoeffs1") - recipe.addVar(contribution2.b0, 0, name = "b2_0", tag = "bcoeffs2") - recipe.addVar(contribution2.b1, 0, name = "b2_1", tag = "bcoeffs2") - recipe.addVar(contribution2.b2, 0, name = "b2_2", tag = "bcoeffs2") - recipe.addVar(contribution2.b3, 0, name = "b2_3", tag = "bcoeffs2") - recipe.addVar(contribution2.b4, 0, name = "b2_4", tag = "bcoeffs2") - recipe.addVar(contribution2.b5, 0, name = "b2_5", tag = "bcoeffs2") - recipe.addVar(contribution2.b6, 0, name = "b2_6", tag = "bcoeffs2") - recipe.addVar(contribution2.b7, 0, name = "b2_7", tag = "bcoeffs2") - recipe.addVar(contribution2.b8, 0, name = "b2_8", tag = "bcoeffs2") - recipe.addVar(contribution2.b9, 0, name = "b2_9", tag = "bcoeffs2") + recipe.addVar(contribution1.b0, 0, name="b1_0", tag="bcoeffs1") + recipe.addVar(contribution1.b1, 0, name="b1_1", tag="bcoeffs1") + recipe.addVar(contribution1.b2, 0, name="b1_2", tag="bcoeffs1") + recipe.addVar(contribution1.b3, 0, name="b1_3", tag="bcoeffs1") + recipe.addVar(contribution1.b4, 0, name="b1_4", tag="bcoeffs1") + recipe.addVar(contribution1.b5, 0, name="b1_5", tag="bcoeffs1") + recipe.addVar(contribution1.b6, 0, name="b1_6", tag="bcoeffs1") + recipe.addVar(contribution1.b7, 0, name="b1_7", tag="bcoeffs1") + recipe.addVar(contribution1.b8, 0, name="b1_8", tag="bcoeffs1") + recipe.addVar(contribution1.b9, 0, name="b1_9", tag="bcoeffs1") + recipe.addVar(contribution2.b0, 0, name="b2_0", tag="bcoeffs2") + recipe.addVar(contribution2.b1, 0, name="b2_1", tag="bcoeffs2") + recipe.addVar(contribution2.b2, 0, name="b2_2", tag="bcoeffs2") + recipe.addVar(contribution2.b3, 0, name="b2_3", tag="bcoeffs2") + recipe.addVar(contribution2.b4, 0, name="b2_4", tag="bcoeffs2") + recipe.addVar(contribution2.b5, 0, name="b2_5", tag="bcoeffs2") + recipe.addVar(contribution2.b6, 0, name="b2_6", tag="bcoeffs2") + recipe.addVar(contribution2.b7, 0, name="b2_7", tag="bcoeffs2") + recipe.addVar(contribution2.b8, 0, name="b2_8", tag="bcoeffs2") + recipe.addVar(contribution2.b9, 0, name="b2_9", tag="bcoeffs2") # We also want to adjust the scale and the convolution width - recipe.addVar(contribution1.scale, 1, name = "scale1") - recipe.addVar(contribution1.width, 0.1, name = "width1") - recipe.addVar(contribution2.scale, 1, name = "scale2") - recipe.addVar(contribution2.width, 0.1, name = "width2") + recipe.addVar(contribution1.scale, 1, name="scale1") + recipe.addVar(contribution1.width, 0.1, name="width1") + recipe.addVar(contribution2.scale, 1, name="scale2") + recipe.addVar(contribution2.width, 0.1, name="width2") # We can also refine structural parameters. We only have to do this once, # since each generator holds the same DiffpyStructureParSet. @@ -175,6 +183,7 @@ def gaussian(q, q0, width): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -197,18 +206,19 @@ def plotResults(recipe): diff1 += offset import pylab + pylab.subplot(2, 1, 1) - pylab.plot(q,I1,'bo',label="I1(Q) Data") - pylab.plot(q,Icalc1,'r-',label="I1(Q) Fit") - pylab.plot(q,diff1,'g-',label="I1(Q) diff") - pylab.plot(q,bkgd1,'c-',label="Bkgd1 Fit") + pylab.plot(q, I1, "bo", label="I1(Q) Data") + pylab.plot(q, Icalc1, "r-", label="I1(Q) Fit") + pylab.plot(q, diff1, "g-", label="I1(Q) diff") + pylab.plot(q, bkgd1, "c-", label="Bkgd1 Fit") pylab.legend(loc=1) pylab.subplot(2, 1, 2) - pylab.plot(q,I2,'bo',label="I2(Q) Data") - pylab.plot(q,Icalc2,'r-',label="I2(Q) Fit") - pylab.plot(q,diff2,'g-',label="I2(Q) diff") - pylab.plot(q,bkgd2,'c-',label="Bkgd2 Fit") + pylab.plot(q, I2, "bo", label="I2(Q) Data") + pylab.plot(q, Icalc2, "r-", label="I2(Q) Fit") + pylab.plot(q, diff2, "g-", label="I2(Q) diff") + pylab.plot(q, bkgd2, "c-", label="Bkgd2 Fit") pylab.xlabel(r"$Q (\AA^{-1})$") pylab.ylabel("Intensity (arb. units)") pylab.legend(loc=1) @@ -216,6 +226,7 @@ def plotResults(recipe): pylab.show() return + def main(): # Make two different data sets, each from the same structure, but with @@ -258,6 +269,7 @@ def main(): return + if __name__ == "__main__": main() diff --git a/doc/examples/nppdfcrystal.py b/doc/examples/nppdfcrystal.py index 46ffb848..0c098efe 100644 --- a/doc/examples/nppdfcrystal.py +++ b/doc/examples/nppdfcrystal.py @@ -12,29 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of fitting a crystal-like nanoparticle (nanocrystal) PDF. -This is an example of modeling the PDF from a nanocrystal as an attenuated bulk -PDF. This involves a crystal PDF calculation and a spherical nanoparticle -characteristic function. -The equation we model is -Gnano(r) = f(r) * Gbulk(r), -where f(r) is the nanoparticle characteristic function for the nanoparticle -shape. Functions for calculating the characteristic function in the +This is an example of modeling the PDF from a nanocrystal as an +attenuated bulk PDF. This involves a crystal PDF calculation and a +spherical nanoparticle characteristic function. The equation we model is +Gnano(r) = f(r) * Gbulk(r), where f(r) is the nanoparticle +characteristic function for the nanoparticle shape. Functions for +calculating the characteristic function in the diffpy.srfit.pdf.characteristicfunctions module. """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults -from gaussianrecipe import scipyOptimize def makeRecipe(ciffile, grdata): """Make a recipe to model a crystal-like nanoparticle PDF.""" @@ -45,10 +45,10 @@ def makeRecipe(ciffile, grdata): pdfparser = PDFParser() pdfparser.parseFile(grdata) pdfprofile.loadParsedData(pdfparser) - pdfprofile.setCalculationRange(xmin = 0.1, xmax = 20) + pdfprofile.setCalculationRange(xmin=0.1, xmax=20) pdfcontribution = FitContribution("pdf") - pdfcontribution.setProfile(pdfprofile, xname = "r") + pdfcontribution.setProfile(pdfprofile, xname="r") pdfgenerator = PDFGenerator("G") pdfgenerator.setQmax(30.0) @@ -58,7 +58,8 @@ def makeRecipe(ciffile, grdata): # Register the nanoparticle shape factor. from diffpy.srfit.pdf.characteristicfunctions import sphericalCF - pdfcontribution.registerFunction(sphericalCF, name = "f") + + pdfcontribution.registerFunction(sphericalCF, name="f") # Now we set up the fitting equation. pdfcontribution.setEquation("f * G") @@ -79,6 +80,7 @@ def makeRecipe(ciffile, grdata): return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -96,12 +98,13 @@ def plotResults(recipe): fr *= max(g) / fr[0] import pylab - pylab.plot(r,g,'bo',label="G(r) Data") - pylab.plot(r, gcryst,'y--',label="G(r) Crystal") - pylab.plot(r, fr,'k--',label="f(r) calculated (scaled)") - pylab.plot(r, gcalc,'r-',label="G(r) Fit") - pylab.plot(r,diff,'g-',label="G(r) diff") - pylab.plot(r, diffzero,'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcryst, "y--", label="G(r) Crystal") + pylab.plot(r, fr, "k--", label="f(r) calculated (scaled)") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -109,6 +112,7 @@ def plotResults(recipe): pylab.show() return + if __name__ == "__main__": ciffile = "data/pb.cif" diff --git a/doc/examples/nppdfobjcryst.py b/doc/examples/nppdfobjcryst.py index bab460fc..ad420944 100644 --- a/doc/examples/nppdfobjcryst.py +++ b/doc/examples/nppdfobjcryst.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a nanoparticle PDF refinement using DebyePDFGenerator. This example is similar to crystalpdfobjcryst.py, except that it uses @@ -21,13 +20,17 @@ import numpy -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import DebyePDFGenerator ####### Example Code + def makeRecipe(molecule, datname): """Create a recipe that uses the DebyePDFGenerator.""" @@ -49,7 +52,7 @@ def makeRecipe(molecule, datname): ## The FitContribution contribution = FitContribution("bucky") contribution.addProfileGenerator(generator) - contribution.setProfile(profile, xname = "r") + contribution.setProfile(profile, xname="r") # Make a FitRecipe. recipe = FitRecipe() @@ -92,7 +95,7 @@ def makeRecipe(molecule, datname): # This creates a Parameter that moves the second atom according to the # bond length. Note that each Parameter needs a unique name. - par = c60.addBondLengthParameter("rad%i"%i, center, atom) + par = c60.addBondLengthParameter("rad%i" % i, center, atom) recipe.constrain(par, radius) # Add the correlation term, scale. The scale is too short to effectively @@ -103,6 +106,7 @@ def makeRecipe(molecule, datname): # Give the recipe away so it can be used! return recipe + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -110,14 +114,15 @@ def plotResults(recipe): r = recipe.bucky.profile.x g = recipe.bucky.profile.y gcalc = recipe.bucky.profile.ycalc - diffzero = -0.8 * max(g) * numpy.ones_like(g) + diffzero = -0.8 * max(g) * numpy.ones_like(g) diff = g - gcalc + diffzero import pylab - pylab.plot(r,g,'ob',label="G(r) Data") - pylab.plot(r,gcalc,'-r',label="G(r) Fit") - pylab.plot(r,diff,'-g',label="G(r) diff") - pylab.plot(r,diffzero,'-k') + + pylab.plot(r, g, "ob", label="G(r) Data") + pylab.plot(r, gcalc, "-r", label="G(r) Fit") + pylab.plot(r, diff, "-g", label="G(r) diff") + pylab.plot(r, diffzero, "-k") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) @@ -125,6 +130,7 @@ def plotResults(recipe): pylab.show() return + def main(): molecule = makeC60() @@ -135,6 +141,7 @@ def main(): # Optimize from scipy.optimize import leastsq + leastsq(recipe.residual, recipe.getValues()) # Print results @@ -146,8 +153,8 @@ def main(): return -c60xyz = \ -""" + +c60xyz = """ 3.451266498 0.685000000 0.000000000 3.451266498 -0.685000000 0.000000000 -3.451266498 0.685000000 0.000000000 @@ -210,6 +217,7 @@ def main(): -2.279809890 -2.580456608 -0.724000000 """ + def makeC60(): """Make the C60 molecule using pyobjcryst.""" @@ -236,7 +244,7 @@ def makeC60(): # Add the other atoms. They will be named C1, C2, ..., C60. for i, l in enumerate(c60xyz.strip().splitlines()): x, y, z = map(float, l.split()) - m.AddAtom(x, y, z, sp, "C%i"%(i+1)) + m.AddAtom(x, y, z, sp, "C%i" % (i + 1)) return m diff --git a/doc/examples/nppdfsas.py b/doc/examples/nppdfsas.py index b5975345..0a9b8202 100644 --- a/doc/examples/nppdfsas.py +++ b/doc/examples/nppdfsas.py @@ -12,37 +12,36 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of combining PDF and SAS nanoparticles data. -This is an example of using both PDF and SAS data in the same fit. This fits a -crystal model to the PDF while fitting a shape model to both the SAS profile -and the PDF data. Using the same shape for the PDF and SAS provides a feedback -mechanism into the fit that allows the PDF and SAS portions of the fit to guide -one another, and in the end gives the shape of the nanoparticle that agrees -best with both the PDF and SAS data. +This is an example of using both PDF and SAS data in the same fit. This +fits a crystal model to the PDF while fitting a shape model to both the +SAS profile and the PDF data. Using the same shape for the PDF and SAS +provides a feedback mechanism into the fit that allows the PDF and SAS +portions of the fit to guide one another, and in the end gives the shape +of the nanoparticle that agrees best with both the PDF and SAS data. """ import numpy - +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) from diffpy.srfit.pdf import PDFGenerator, PDFParser from diffpy.srfit.pdf.characteristicfunctions import SASCF -from diffpy.srfit.sas import SASParser, SASGenerator -from diffpy.srfit.fitbase import Profile -from diffpy.srfit.fitbase import FitContribution, FitRecipe -from diffpy.srfit.fitbase import FitResults +from diffpy.srfit.sas import SASGenerator, SASParser -from gaussianrecipe import scipyOptimize def makeRecipe(ciffile, grdata, iqdata): - """Make complex-modeling recipe where I(q) and G(r) are fit - simultaneously. - - The fit I(q) is fed into the calculation of G(r), which provides feedback - for the fit parameters of both. + """Make complex-modeling recipe where I(q) and G(r) are fit simultaneously. + The fit I(q) is fed into the calculation of G(r), which provides + feedback for the fit parameters of both. """ # Create a PDF contribution as before @@ -50,10 +49,10 @@ def makeRecipe(ciffile, grdata, iqdata): pdfparser = PDFParser() pdfparser.parseFile(grdata) pdfprofile.loadParsedData(pdfparser) - pdfprofile.setCalculationRange(xmin = 0.1, xmax = 20) + pdfprofile.setCalculationRange(xmin=0.1, xmax=20) pdfcontribution = FitContribution("pdf") - pdfcontribution.setProfile(pdfprofile, xname = "r") + pdfcontribution.setProfile(pdfprofile, xname="r") pdfgenerator = PDFGenerator("G") pdfgenerator.setQmax(30.0) @@ -75,6 +74,7 @@ def makeRecipe(ciffile, grdata, iqdata): sascontribution.setProfile(sasprofile) from sas.models.EllipsoidModel import EllipsoidModel + model = EllipsoidModel() sasgenerator = SASGenerator("generator", model) sascontribution.addProfileGenerator(sasgenerator) @@ -105,7 +105,7 @@ def makeRecipe(ciffile, grdata, iqdata): recipe.addVar(pdfgenerator.delta2, 0) # SAS - recipe.addVar(sasgenerator.scale, 1, name = "iqscale") + recipe.addVar(sasgenerator.scale, 1, name="iqscale") recipe.addVar(sasgenerator.radius_a, 10) recipe.addVar(sasgenerator.radius_b, 10) @@ -117,16 +117,17 @@ def makeRecipe(ciffile, grdata, iqdata): return recipe + def fitRecipe(recipe): """We refine in stages to help the refinement converge.""" # Tune SAS. recipe.setWeight(recipe.pdf, 0) recipe.fix("all") - recipe.free("radius_a", "radius_b", iqscale = 1e8) - recipe.constrain('radius_b', 'radius_a') + recipe.free("radius_a", "radius_b", iqscale=1e8) + recipe.constrain("radius_b", "radius_a") scipyOptimize(recipe) - recipe.unconstrain('radius_b') + recipe.unconstrain("radius_b") # Tune PDF recipe.setWeight(recipe.pdf, 1) @@ -143,6 +144,7 @@ def fitRecipe(recipe): return + def plotResults(recipe): """Plot the results contained within a refined FitRecipe.""" @@ -160,12 +162,13 @@ def plotResults(recipe): fr *= max(g) / fr[0] import pylab - pylab.plot(r,g,'bo',label="G(r) Data") - pylab.plot(r, gcryst,'y--',label="G(r) Crystal") - pylab.plot(r, fr,'k--',label="f(r) calculated (scaled)") - pylab.plot(r, gcalc,'r-',label="G(r) Fit") - pylab.plot(r, diff,'g-',label="G(r) diff") - pylab.plot(r, diffzero,'k-') + + pylab.plot(r, g, "bo", label="G(r) Data") + pylab.plot(r, gcryst, "y--", label="G(r) Crystal") + pylab.plot(r, fr, "k--", label="f(r) calculated (scaled)") + pylab.plot(r, gcalc, "r-", label="G(r) Fit") + pylab.plot(r, diff, "g-", label="G(r) diff") + pylab.plot(r, diffzero, "k-") pylab.xlabel(r"$r (\AA)$") pylab.ylabel(r"$G (\AA^{-2})$") pylab.legend(loc=1) diff --git a/doc/examples/simplepdf.py b/doc/examples/simplepdf.py index df95bbd2..dc36862a 100644 --- a/doc/examples/simplepdf.py +++ b/doc/examples/simplepdf.py @@ -12,29 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a PDF using the PDFContribution helper class. -This is example of fitting the fcc nickel structure to measured PDF data. -It uses the PDFContribution class to simplify fit setup. +This is example of fitting the fcc nickel structure to measured PDF +data. It uses the PDFContribution class to simplify fit setup. """ -from diffpy.structure import Structure -from diffpy.srfit.pdf import PDFContribution -from diffpy.srfit.fitbase import FitRecipe, FitResults - -from gaussianrecipe import scipyOptimize from crystalpdf import plotResults +from gaussianrecipe import scipyOptimize + +from diffpy.srfit.fitbase import FitRecipe, FitResults +from diffpy.srfit.pdf import PDFContribution +from diffpy.structure import Structure ####### Example Code + def makeRecipe(ciffile, datname): """Create a fitting recipe for crystalline PDF data.""" # Work directly with a custom PDFContribution to load the data contribution = PDFContribution("nickel") contribution.loadData(datname) - contribution.setCalculationRange(xmin = 1, xmax = 20, dx = 0.1) + contribution.setCalculationRange(xmin=1, xmax=20, dx=0.1) # and the phase stru = Structure() @@ -49,6 +49,7 @@ def makeRecipe(ciffile, datname): phase = contribution.nickel.phase from diffpy.srfit.structure import constrainAsSpaceGroup + sgpars = constrainAsSpaceGroup(phase, "Fm-3m") for par in sgpars.latpars: @@ -57,12 +58,13 @@ def makeRecipe(ciffile, datname): recipe.addVar(par, 0.005) recipe.addVar(contribution.scale, 1) - recipe.addVar(contribution.qdamp, 0.03, fixed = True) + recipe.addVar(contribution.qdamp, 0.03, fixed=True) recipe.addVar(contribution.nickel.delta2, 5) # Give the recipe away so it can be used! return recipe + if __name__ == "__main__": # Make the data and the recipe diff --git a/doc/examples/simplepdftwophase.py b/doc/examples/simplepdftwophase.py index f000926a..526c4780 100644 --- a/doc/examples/simplepdftwophase.py +++ b/doc/examples/simplepdftwophase.py @@ -12,26 +12,25 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of a simplified PDF refinement of two-phase structure.""" +from crystalpdftwophase import plotResults +from gaussianrecipe import scipyOptimize from pyobjcryst import loadCrystal -from diffpy.srfit.pdf import PDFContribution from diffpy.srfit.fitbase import FitRecipe, FitResults - -from gaussianrecipe import scipyOptimize -from crystalpdftwophase import plotResults +from diffpy.srfit.pdf import PDFContribution ####### Example Code + def makeRecipe(niciffile, siciffile, datname): """Create a fitting recipe for crystalline PDF data.""" # Load data and add it to the profile contribution = PDFContribution("nisi") contribution.loadData(datname) - contribution.setCalculationRange(xmax = 20) + contribution.setCalculationRange(xmax=20) stru = loadCrystal(niciffile) contribution.addStructure("ni", stru) @@ -53,7 +52,7 @@ def makeRecipe(niciffile, siciffile, datname): # for free by the PDFContribution. We simply need to add it to the recipe. recipe.addVar(contribution.qdamp, 0.03) - # Vary the gloabal scale as well. + # Vary the global scale as well. recipe.addVar(contribution.scale, 1) # Now we can configure the structural parameters. Since we're using @@ -66,13 +65,13 @@ def makeRecipe(niciffile, siciffile, datname): # above. phase_ni = contribution.ni.phase for par in phase_ni.sgpars: - recipe.addVar(par, name = par.name + "_ni") - recipe.addVar(contribution.ni.delta2, name = "delta2_ni") + recipe.addVar(par, name=par.name + "_ni") + recipe.addVar(contribution.ni.delta2, name="delta2_ni") # Next the silicon parameters phase_si = contribution.si.phase for par in phase_si.sgpars: - recipe.addVar(par, name = par.name + "_si") - recipe.addVar(contribution.si.delta2, name = "delta2_si") + recipe.addVar(par, name=par.name + "_si") + recipe.addVar(contribution.si.delta2, name="delta2_si") # We have prior information from the earlier examples so we'll use it here # in the form of restraints. @@ -82,18 +81,18 @@ def makeRecipe(niciffile, siciffile, datname): # derived has no uncertainty. Thus, we will tell the recipe to scale the # residual, which means that it will be weighted as much as the average # data point during the fit. - recipe.restrain("a_ni", lb = 3.527, ub = 3.527, scaled = True) + recipe.restrain("a_ni", lb=3.527, ub=3.527, scaled=True) # Now we do the same with the delta2 and Biso parameters (remember that # Biso = 8*pi**2*Uiso) - recipe.restrain("delta2_ni", lb = 2.22, ub = 2.22, scaled = True) - recipe.restrain("Biso_0_ni", lb = 0.454, ub = 0.454, scaled = True) + recipe.restrain("delta2_ni", lb=2.22, ub=2.22, scaled=True) + recipe.restrain("Biso_0_ni", lb=0.454, ub=0.454, scaled=True) # # We can do the same with the silicon values. We haven't done a thorough # job of measuring the uncertainties in the results, so we'll scale these # as well. - recipe.restrain("a_si", lb = 5.430, ub = 5.430, scaled = True) - recipe.restrain("delta2_si", lb = 3.54, ub = 3.54, scaled = True) - recipe.restrain("Biso_0_si", lb = 0.645, ub = 0.645, scaled = True) + recipe.restrain("a_si", lb=5.430, ub=5.430, scaled=True) + recipe.restrain("delta2_si", lb=3.54, ub=3.54, scaled=True) + recipe.restrain("Biso_0_si", lb=0.645, ub=0.645, scaled=True) # Give the recipe away so it can be used! return recipe diff --git a/doc/examples/simplerecipe.py b/doc/examples/simplerecipe.py index 45b8785f..92ed185e 100644 --- a/doc/examples/simplerecipe.py +++ b/doc/examples/simplerecipe.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - """Example of simplified fitting. This is like gaussianrecipe.py, but it uses the SimpleRecipe, which @@ -24,6 +23,7 @@ ####### Example Code + def main(): """Set up a simple recipe in a few lines.""" @@ -46,6 +46,7 @@ def main(): # We explicitly optimize the residual method of the SimpleRecipe from scipy.optimize import leastsq + leastsq(recipe.residual, recipe.values) # Print the results @@ -53,6 +54,7 @@ def main(): return + if __name__ == "__main__": main() diff --git a/doc/examples/threedoublepeaks.py b/doc/examples/threedoublepeaks.py index 35b7597b..75c3d9ef 100644 --- a/doc/examples/threedoublepeaks.py +++ b/doc/examples/threedoublepeaks.py @@ -12,33 +12,34 @@ # See LICENSE_DANSE.txt for license information. # ######################################################################## - -"""Example of fitting a three double peaks to simulated data. -""" +"""Example of fitting a three double peaks to simulated data.""" from __future__ import print_function import numpy -from diffpy.srfit.fitbase import FitContribution, FitRecipe, Profile, FitResults +from diffpy.srfit.fitbase import ( + FitContribution, + FitRecipe, + FitResults, + Profile, +) ####### Example Code + def makeRecipe(): """Make a FitRecipe for fitting three double-gaussian curves to data. - The separation and amplitude ratio of the double peaks follows a specific - relationship. The peaks are broadend according to their position and they - sit on top of a background. We are seeking the absolute locations of the - peaks as well as their amplitudes. + The separation and amplitude ratio of the double peaks follows a + specific relationship. The peaks are broadend according to their + position and they sit on top of a background. We are seeking the + absolute locations of the peaks as well as their amplitudes. The independent variable is t. The relationship between the double - peaks is - sin(t2) / l2 = sin(t1) / l1 - amplitude(peak2) = r * amplitude(peak1) - The values of l1, l2 and r come from experiment. For this example, we - use l1 = 1.012, l2 = 1.0 and r = 0.23. - + peaks is sin(t2) / l2 = sin(t1) / l1 amplitude(peak2) = r * + amplitude(peak1) The values of l1, l2 and r come from experiment. + For this example, we use l1 = 1.012, l2 = 1.0 and r = 0.23. """ ## The Profile @@ -48,22 +49,21 @@ def makeRecipe(): # Create the contribution contribution = FitContribution("peaks") - contribution.setProfile(profile, xname = "t") + contribution.setProfile(profile, xname="t") pi = numpy.pi exp = numpy.exp # This is a building-block of our profile function def gaussian(t, mu, sig): - return 1/(2*pi*sig**2)**0.5 * exp(-0.5 * ((t-mu)/sig)**2) + return 1 / (2 * pi * sig**2) ** 0.5 * exp(-0.5 * ((t - mu) / sig) ** 2) - contribution.registerFunction(gaussian, name = "peakshape") + contribution.registerFunction(gaussian, name="peakshape") def delta(t, mu): """Calculate a delta-function. - We don't have perfect precision, so we must make this a very thin - Gaussian. - + We don't have perfect precision, so we must make this a very + thin Gaussian. """ sig = t[1] - t[0] return gaussian(t, mu, sig) @@ -83,10 +83,11 @@ def delta(t, mu): + 0.23*convolve( delta(t, mu22), peakshape(t, c, sig22) ) ) + \ A3 * ( convolve( delta(t, mu31), peakshape(t, c, sig31) ) \ + 0.23*convolve( delta(t, mu32), peakshape(t, c, sig32) ) ) + \ - bkgd") + bkgd" + ) # c is the center of the gaussian. - contribution.c.value = x[len(x) // 2] + contribution.c.value = x[len(x) // 2] ## The FitRecipe # The FitRecipe lets us define what we want to fit. It is where we can @@ -109,12 +110,13 @@ def delta(t, mu): recipe.addVar(contribution.mu31, 33.0) # Constrain the position of the second double peak - from numpy import sin, arcsin + from numpy import arcsin, sin + def peakloc(mu): """Calculate the location of the second peak given the first.""" l1 = 1.012 l2 = 1.0 - return 180 / pi * arcsin( pi / 180 * l2 * sin(mu) / l1 ) + return 180 / pi * arcsin(pi / 180 * l2 * sin(mu) / l1) recipe.registerFunction(peakloc) recipe.constrain(contribution.mu12, "peakloc(mu11)") @@ -128,7 +130,7 @@ def peakloc(mu): def sig(sig0, dsig, mu): """Calculate the peak broadening with respect to position.""" - return sig0 * (1 - dsig * mu**2); + return sig0 * (1 - dsig * mu**2) recipe.registerFunction(sig) recipe.fix("mu") @@ -136,32 +138,41 @@ def sig(sig0, dsig, mu): recipe.sig0.value = 0.001 recipe.dsig.value = 4.0 recipe.constrain(contribution.sig11, "sig(sig0, dsig, mu11)") - recipe.constrain(contribution.sig12, "sig(sig0, dsig, mu12)", - ns = {"mu12" : contribution.mu12} ) + recipe.constrain( + contribution.sig12, + "sig(sig0, dsig, mu12)", + ns={"mu12": contribution.mu12}, + ) recipe.constrain(contribution.sig21, "sig(sig0, dsig, mu21)") - recipe.constrain(contribution.sig22, "sig(sig0, dsig, mu22)", - ns = {"mu22" : contribution.mu22} ) + recipe.constrain( + contribution.sig22, + "sig(sig0, dsig, mu22)", + ns={"mu22": contribution.mu22}, + ) recipe.constrain(contribution.sig31, "sig(sig0, dsig, mu31)") - recipe.constrain(contribution.sig32, "sig(sig0, dsig, mu32)", - ns = {"mu32" : contribution.mu32} ) + recipe.constrain( + contribution.sig32, + "sig(sig0, dsig, mu32)", + ns={"mu32": contribution.mu32}, + ) # Also the background - recipe.addVar(contribution.b0, 0, tag = "bkgd") - recipe.addVar(contribution.b1, 0, tag = "bkgd") - recipe.addVar(contribution.b2, 0, tag = "bkgd") - recipe.addVar(contribution.b3, 0, tag = "bkgd") - recipe.addVar(contribution.b4, 0, tag = "bkgd") - recipe.addVar(contribution.b5, 0, tag = "bkgd") - recipe.addVar(contribution.b6, 0, tag = "bkgd") + recipe.addVar(contribution.b0, 0, tag="bkgd") + recipe.addVar(contribution.b1, 0, tag="bkgd") + recipe.addVar(contribution.b2, 0, tag="bkgd") + recipe.addVar(contribution.b3, 0, tag="bkgd") + recipe.addVar(contribution.b4, 0, tag="bkgd") + recipe.addVar(contribution.b5, 0, tag="bkgd") + recipe.addVar(contribution.b6, 0, tag="bkgd") return recipe + def scipyOptimize(recipe): """Optimize the recipe created above using scipy. - The FitRecipe we created in makeRecipe has a 'residual' method that we can - be minimized using a scipy optimizer. The details are described in the - source. - + The FitRecipe we created in makeRecipe has a 'residual' method that + we can be minimized using a scipy optimizer. The details are + described in the source. """ # We're going to use the least-squares (Levenberg-Marquardt) optimizer from @@ -169,6 +180,7 @@ def scipyOptimize(recipe): # (recipe.residual) and the starting values of the Variables # (recipe.getValues()). from scipy.optimize.minpack import leastsq + print("Fit using scipy's LM optimizer") leastsq(recipe.residual, recipe.getValues()) @@ -190,16 +202,18 @@ def plotResults(recipe): # This stuff is specific to pylab from the matplotlib distribution. import pylab - pylab.plot(x, y, 'b.', label = "observed profile") - pylab.plot(x, ycalc, 'r-', label = "calculated profile") - pylab.plot(x, y - ycalc - 0.1 * max(y), 'g-', label = "difference") - pylab.legend(loc = (0.0,0.8)) + + pylab.plot(x, y, "b.", label="observed profile") + pylab.plot(x, ycalc, "r-", label="calculated profile") + pylab.plot(x, y - ycalc - 0.1 * max(y), "g-", label="difference") + pylab.legend(loc=(0.0, 0.8)) pylab.xlabel("x") pylab.ylabel("y") pylab.show() return + def steerFit(recipe): """Steer the fit for this problem. @@ -219,6 +233,7 @@ def steerFit(recipe): return + if __name__ == "__main__": # Create the recipe diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 00000000..2be83069 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=PackagingScientificPython + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/doc/manual/Makefile b/doc/manual/Makefile deleted file mode 100644 index beb126b4..00000000 --- a/doc/manual/Makefile +++ /dev/null @@ -1,179 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext publish - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SrFit.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SrFit.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/SrFit" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SrFit" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -# Manual publishing to the gh-pages branch - -GITREPOPATH = $(shell cd $(CURDIR) && git rev-parse --git-dir) -GITREMOTE = origin -GITREMOTEURL = $(shell git config --get remote.$(GITREMOTE).url) -GITLASTCOMMIT = $(shell git rev-parse --short HEAD) - -publish: - @test -d build/html || \ - ( echo >&2 "Run 'make html' first!"; false ) - git show-ref --verify --quiet refs/heads/gh-pages || \ - git branch --track gh-pages $(GITREMOTE)/gh-pages - test -d build/gh-pages || \ - git clone -s -b gh-pages $(GITREPOPATH) build/gh-pages - cd build/gh-pages && \ - git pull $(GITREMOTEURL) gh-pages - rsync -acv --delete --exclude=.git --exclude=.rsync-exclude \ - --exclude-from=build/gh-pages/.rsync-exclude \ - --link-dest=$(CURDIR)/build/html build/html/ build/gh-pages/ - cd build/gh-pages && \ - git add --all . && \ - git diff --cached --quiet || \ - git commit -m "Sync with the source at $(GITLASTCOMMIT)." - cd build/gh-pages && \ - git push origin gh-pages diff --git a/doc/manual/source/api/diffpy.srfit.rst b/doc/manual/source/api/diffpy.srfit.rst deleted file mode 100644 index bbe6224d..00000000 --- a/doc/manual/source/api/diffpy.srfit.rst +++ /dev/null @@ -1,38 +0,0 @@ -:tocdepth: 2 - -diffpy.srfit package -==================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 2 - - diffpy.srfit.equation - diffpy.srfit.fitbase - diffpy.srfit.interface - diffpy.srfit.pdf - diffpy.srfit.sas - diffpy.srfit.structure - diffpy.srfit.util - -Submodules ----------- - -diffpy.srfit.version module ---------------------------- - -.. automodule:: diffpy.srfit.version - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: diffpy.srfit - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/_static/.placeholder b/doc/source/_static/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/api/.placeholder b/doc/source/api/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/manual/source/api/diffpy.srfit.equation.literals.rst b/doc/source/api/diffpy.srfit.equation.literals.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.equation.literals.rst rename to doc/source/api/diffpy.srfit.equation.literals.rst diff --git a/doc/manual/source/api/diffpy.srfit.equation.rst b/doc/source/api/diffpy.srfit.equation.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.equation.rst rename to doc/source/api/diffpy.srfit.equation.rst diff --git a/doc/manual/source/api/diffpy.srfit.equation.visitors.rst b/doc/source/api/diffpy.srfit.equation.visitors.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.equation.visitors.rst rename to doc/source/api/diffpy.srfit.equation.visitors.rst diff --git a/doc/source/api/diffpy.srfit.example_package.rst b/doc/source/api/diffpy.srfit.example_package.rst new file mode 100644 index 00000000..60910070 --- /dev/null +++ b/doc/source/api/diffpy.srfit.example_package.rst @@ -0,0 +1,31 @@ +.. _example_package documentation: + +|title| +======= + +.. |title| replace:: diffpy.srfit.example_package package + +.. automodule:: diffpy.srfit.example_package + :members: + :undoc-members: + :show-inheritance: + +|foo| +----- + +.. |foo| replace:: diffpy.srfit.example_package.foo module + +.. automodule:: diffpy.srfit.example_package.foo + :members: + :undoc-members: + :show-inheritance: + +|bar| +----- + +.. |bar| replace:: diffpy.srfit.example_package.bar module + +.. automodule:: diffpy.srfit.example_package.foo + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/manual/source/api/diffpy.srfit.fitbase.rst b/doc/source/api/diffpy.srfit.fitbase.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.fitbase.rst rename to doc/source/api/diffpy.srfit.fitbase.rst diff --git a/doc/manual/source/api/diffpy.srfit.interface.rst b/doc/source/api/diffpy.srfit.interface.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.interface.rst rename to doc/source/api/diffpy.srfit.interface.rst diff --git a/doc/manual/source/api/diffpy.srfit.pdf.rst b/doc/source/api/diffpy.srfit.pdf.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.pdf.rst rename to doc/source/api/diffpy.srfit.pdf.rst diff --git a/doc/source/api/diffpy.srfit.rst b/doc/source/api/diffpy.srfit.rst new file mode 100644 index 00000000..5b2b697b --- /dev/null +++ b/doc/source/api/diffpy.srfit.rst @@ -0,0 +1,30 @@ +:tocdepth: -1 + +|title| +======= + +.. |title| replace:: diffpy.srfit package + +.. automodule:: diffpy.srfit + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + diffpy.srfit.example_package + +Submodules +---------- + +|module| +-------- + +.. |module| replace:: diffpy.srfit.example_submodule module + +.. automodule:: diffpy.srfit.example_submodule + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/manual/source/api/diffpy.srfit.sas.rst b/doc/source/api/diffpy.srfit.sas.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.sas.rst rename to doc/source/api/diffpy.srfit.sas.rst diff --git a/doc/manual/source/api/diffpy.srfit.structure.rst b/doc/source/api/diffpy.srfit.structure.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.structure.rst rename to doc/source/api/diffpy.srfit.structure.rst diff --git a/doc/manual/source/api/diffpy.srfit.util.rst b/doc/source/api/diffpy.srfit.util.rst similarity index 100% rename from doc/manual/source/api/diffpy.srfit.util.rst rename to doc/source/api/diffpy.srfit.util.rst diff --git a/doc/manual/source/conf.py b/doc/source/conf.py similarity index 68% rename from doc/manual/source/conf.py rename to doc/source/conf.py index 53398e50..8d285db5 100644 --- a/doc/manual/source/conf.py +++ b/doc/source/conf.py @@ -1,10 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# diffpy.srfit documentation build configuration file, created by -# sphinx-quickstart on Fri Dec 6 18:09:01 2013. +# diffpy.srfit 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 containing dir. +# This file is execfile()d with the current directory set to its +# containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. @@ -12,58 +13,72 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys import time +from importlib.metadata import version +from pathlib import Path + +# Attempt to import the version dynamically from GitHub tag. +try: + fullversion = version("diffpy.srfit") +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 os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) -# sys.path.insert(0, os.path.abspath('../../..')) +# 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 = u'Christopher L. Farrow, Pavol Juhás, Simon J.L. Billinge group' +ab_authors = ( + "Christopher Farrow, Pavol Juhas, and members of the Billinge Group" +) -# -- General configuration ----------------------------------------------------- +# -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', - 'sphinx.ext.intersphinx', - 'm2r', + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_rtd_theme", + "sphinx_copybutton", + "m2r", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'diffpy.srfit' -copyright = u'%Y, Brookhaven National Laboratory' +project = "diffpy.srfit" +copyright = "%Y, The Trustees of Columbia University in the City of New York" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -sys.path.insert(0, os.path.abspath('../../..')) -from setup import versiondata -fullversion = versiondata.get('DEFAULT', 'version') + # The short X.Y version. -version = ''.join(fullversion.split('.post')[:1]) +version = "".join(fullversion.split(".post")[:1]) # The full version, including alpha/beta/rc tags. release = fullversion @@ -74,19 +89,24 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' -today_seconds = versiondata.getint('DEFAULT', 'timestamp') -today = time.strftime('%B %d, %Y', time.localtime(today_seconds)) +today = time.strftime("%B %d, %Y", time.localtime()) year = today.split()[-1] # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # substitute YEAR in the copyright string -copyright = copyright.replace('%Y', year) +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 = [] +exclude_patterns = ["build"] -# The reST default role (used for this markup: `text`) to use for all documents. +# The reST default role (used for this markup: `text`) to use for all +# documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. @@ -101,26 +121,35 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['diffpy.srfit.'] +modindex_common_prefix = ["diffpy.srfit"] # Display all warnings for missing links. nitpicky = True -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_py3doc_enhanced_theme' +# +html_theme = "sphinx_rtd_theme" + +html_context = { + "display_github": True, + "github_user": "diffpy", + "github_repo": "diffpy.srfit", + "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. +# html_theme_options = { - 'collapsiblesidebar' : 'true', - 'navigation_with_keys' : 'true', + "navigation_with_keys": "true", } # Add any paths that contain custom themes here, relative to this directory. @@ -147,6 +176,11 @@ # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' @@ -169,7 +203,7 @@ # html_use_index = True # If true, the index is split into individual pages for each letter. -html_split_index = True +# html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True @@ -189,27 +223,32 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'srfitdoc' +basename = "diffpy.srfit".replace(" ", "").replace(".", "") +htmlhelp_basename = basename + "doc" -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -# 'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -# 'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -# 'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'srfit_manual.tex', u'diffpy.srfit documentation', - ab_authors, 'manual'), + ( + "index", + "diffpy.srfit.tex", + "diffpy.srfit Documentation", + ab_authors, + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -233,28 +272,39 @@ # latex_domain_indices = True -# -- Options for manual page output -------------------------------------------- +# -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'diffpy.srfit', u'diffpy.srfit documentation', - ab_authors, 1) + ( + "index", + "diffpy.srfit", + "diffpy.srfit Documentation", + ab_authors, + 1, + ) ] # If true, show URL addresses after external links. # man_show_urls = False -# -- Options for Texinfo output ------------------------------------------------ +# -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'diffpy.srfit', u'diffpy.srfit documentation', - ab_authors, 'diffpy.srfit', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "diffpy.srfit", + "diffpy.srfit Documentation", + ab_authors, + "diffpy.srfit", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. @@ -266,9 +316,9 @@ # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - 'numpy': ('https://docs.scipy.org/doc/numpy', None), - 'python' : ('https://docs.python.org/3.7', None), -} +# intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/manual/source/examples.rst b/doc/source/examples.rst similarity index 100% rename from doc/manual/source/examples.rst rename to doc/source/examples.rst diff --git a/doc/manual/source/examples/coreshellnp.rst b/doc/source/examples/coreshellnp.rst similarity index 100% rename from doc/manual/source/examples/coreshellnp.rst rename to doc/source/examples/coreshellnp.rst diff --git a/doc/manual/source/examples/crystalpdf.rst b/doc/source/examples/crystalpdf.rst similarity index 100% rename from doc/manual/source/examples/crystalpdf.rst rename to doc/source/examples/crystalpdf.rst diff --git a/doc/manual/source/examples/crystalpdfall.rst b/doc/source/examples/crystalpdfall.rst similarity index 100% rename from doc/manual/source/examples/crystalpdfall.rst rename to doc/source/examples/crystalpdfall.rst diff --git a/doc/manual/source/examples/crystalpdfobjcryst.rst b/doc/source/examples/crystalpdfobjcryst.rst similarity index 100% rename from doc/manual/source/examples/crystalpdfobjcryst.rst rename to doc/source/examples/crystalpdfobjcryst.rst diff --git a/doc/manual/source/examples/crystalpdftwodata.rst b/doc/source/examples/crystalpdftwodata.rst similarity index 100% rename from doc/manual/source/examples/crystalpdftwodata.rst rename to doc/source/examples/crystalpdftwodata.rst diff --git a/doc/manual/source/examples/crystalpdftwophase.rst b/doc/source/examples/crystalpdftwophase.rst similarity index 100% rename from doc/manual/source/examples/crystalpdftwophase.rst rename to doc/source/examples/crystalpdftwophase.rst diff --git a/doc/manual/source/examples/debyemodel.rst b/doc/source/examples/debyemodel.rst similarity index 100% rename from doc/manual/source/examples/debyemodel.rst rename to doc/source/examples/debyemodel.rst diff --git a/doc/manual/source/examples/debyemodelII.rst b/doc/source/examples/debyemodelII.rst similarity index 100% rename from doc/manual/source/examples/debyemodelII.rst rename to doc/source/examples/debyemodelII.rst diff --git a/doc/manual/source/examples/ellipsoidsas.rst b/doc/source/examples/ellipsoidsas.rst similarity index 100% rename from doc/manual/source/examples/ellipsoidsas.rst rename to doc/source/examples/ellipsoidsas.rst diff --git a/doc/manual/source/examples/gaussiangenerator.rst b/doc/source/examples/gaussiangenerator.rst similarity index 100% rename from doc/manual/source/examples/gaussiangenerator.rst rename to doc/source/examples/gaussiangenerator.rst diff --git a/doc/manual/source/examples/gaussianrecipe.rst b/doc/source/examples/gaussianrecipe.rst similarity index 100% rename from doc/manual/source/examples/gaussianrecipe.rst rename to doc/source/examples/gaussianrecipe.rst diff --git a/doc/manual/source/examples/interface.rst b/doc/source/examples/interface.rst similarity index 100% rename from doc/manual/source/examples/interface.rst rename to doc/source/examples/interface.rst diff --git a/doc/manual/source/examples/npintensity.rst b/doc/source/examples/npintensity.rst similarity index 100% rename from doc/manual/source/examples/npintensity.rst rename to doc/source/examples/npintensity.rst diff --git a/doc/manual/source/examples/npintensityII.rst b/doc/source/examples/npintensityII.rst similarity index 100% rename from doc/manual/source/examples/npintensityII.rst rename to doc/source/examples/npintensityII.rst diff --git a/doc/manual/source/examples/nppdfcrystal.rst b/doc/source/examples/nppdfcrystal.rst similarity index 100% rename from doc/manual/source/examples/nppdfcrystal.rst rename to doc/source/examples/nppdfcrystal.rst diff --git a/doc/manual/source/examples/nppdfobjcryst.rst b/doc/source/examples/nppdfobjcryst.rst similarity index 100% rename from doc/manual/source/examples/nppdfobjcryst.rst rename to doc/source/examples/nppdfobjcryst.rst diff --git a/doc/manual/source/examples/nppdfsas.rst b/doc/source/examples/nppdfsas.rst similarity index 100% rename from doc/manual/source/examples/nppdfsas.rst rename to doc/source/examples/nppdfsas.rst diff --git a/doc/manual/source/examples/simplepdf.rst b/doc/source/examples/simplepdf.rst similarity index 100% rename from doc/manual/source/examples/simplepdf.rst rename to doc/source/examples/simplepdf.rst diff --git a/doc/manual/source/examples/simplepdftwophase.rst b/doc/source/examples/simplepdftwophase.rst similarity index 100% rename from doc/manual/source/examples/simplepdftwophase.rst rename to doc/source/examples/simplepdftwophase.rst diff --git a/doc/manual/source/examples/simplerecipe.rst b/doc/source/examples/simplerecipe.rst similarity index 100% rename from doc/manual/source/examples/simplerecipe.rst rename to doc/source/examples/simplerecipe.rst diff --git a/doc/manual/source/extending.rst b/doc/source/extending.rst similarity index 100% rename from doc/manual/source/extending.rst rename to doc/source/extending.rst diff --git a/doc/source/img/.placeholder b/doc/source/img/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/images/fitting_architecture.dia b/doc/source/img/fitting_architecture.dia similarity index 100% rename from doc/images/fitting_architecture.dia rename to doc/source/img/fitting_architecture.dia diff --git a/doc/images/fitting_architecture.png b/doc/source/img/fitting_architecture.png similarity index 100% rename from doc/images/fitting_architecture.png rename to doc/source/img/fitting_architecture.png diff --git a/doc/images/overview.odg b/doc/source/img/overview.odg similarity index 100% rename from doc/images/overview.odg rename to doc/source/img/overview.odg diff --git a/doc/images/partition_equation.dia b/doc/source/img/partition_equation.dia similarity index 100% rename from doc/images/partition_equation.dia rename to doc/source/img/partition_equation.dia diff --git a/doc/images/partition_equation.png b/doc/source/img/partition_equation.png similarity index 100% rename from doc/images/partition_equation.png rename to doc/source/img/partition_equation.png diff --git a/doc/images/simple_equation.dia b/doc/source/img/simple_equation.dia similarity index 100% rename from doc/images/simple_equation.dia rename to doc/source/img/simple_equation.dia diff --git a/doc/images/simple_equation.png b/doc/source/img/simple_equation.png similarity index 100% rename from doc/images/simple_equation.png rename to doc/source/img/simple_equation.png diff --git a/doc/images/visitor_interaction.dia b/doc/source/img/visitor_interaction.dia similarity index 100% rename from doc/images/visitor_interaction.dia rename to doc/source/img/visitor_interaction.dia diff --git a/doc/images/visitor_interaction.png b/doc/source/img/visitor_interaction.png similarity index 100% rename from doc/images/visitor_interaction.png rename to doc/source/img/visitor_interaction.png diff --git a/doc/manual/source/index.rst b/doc/source/index.rst similarity index 100% rename from doc/manual/source/index.rst rename to doc/source/index.rst diff --git a/doc/manual/source/license.rst b/doc/source/license.rst similarity index 100% rename from doc/manual/source/license.rst rename to doc/source/license.rst diff --git a/doc/manual/source/release.rst b/doc/source/release.rst similarity index 100% rename from doc/manual/source/release.rst rename to doc/source/release.rst diff --git a/doc/source/snippets/.placeholder b/doc/source/snippets/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/news/TEMPLATE.rst b/news/TEMPLATE.rst new file mode 100644 index 00000000..790d30b1 --- /dev/null +++ b/news/TEMPLATE.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3bdcbb03 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,80 @@ +[build-system] +requires = ["setuptools>=62.0", "setuptools-git-versioning>=2.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "diffpy.srfit" +dynamic=['version', 'dependencies'] +authors = [ + { name="Simon Billinge", email="sb2896@columbia.edu" }, +] +maintainers = [ + { name="Simon Billinge", email="sb2896@columbia.edu" }, +] +description = "Configurable code for solving atomic structures." +keywords = ['regression', 'modelling', 'fitting', 'diffraction', 'PDF'] +readme = "README.rst" +requires-python = ">=3.11, <3.14" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Topic :: Scientific/Engineering :: Physics', + 'Topic :: Scientific/Engineering :: Chemistry', +] + +[project.urls] +Homepage = "https://github.com/diffpy/diffpy.srfit/" +Issues = "https://github.com/diffpy/diffpy.srfit/issues/" + +[tool.setuptools-git-versioning] +enabled = true +template = "{tag}" +dev_template = "{tag}" +dirty_template = "{tag}" + +[tool.setuptools.packages.find] +where = ["src"] # list of folders that contain the packages (["."] by default) +include = ["*"] # package names should match these glob patterns (["*"] by default) +exclude = [] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements/pip.txt"]} + +[tool.codespell] +exclude-file = ".codespell/ignore_lines.txt" +ignore-words = ".codespell/ignore_words.txt" +skip = "*.cif,*.dat" + +[tool.black] +line-length = 79 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | \.rst + | \.txt + | _build + | buck-out + | build + | dist + + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data +)/ +''' diff --git a/requirements/build.txt b/requirements/build.txt new file mode 100644 index 00000000..e69de29b diff --git a/requirements/conda.txt b/requirements/conda.txt new file mode 100644 index 00000000..5306b5d1 --- /dev/null +++ b/requirements/conda.txt @@ -0,0 +1,3 @@ +matplotlib-base +numpy +scipy diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 00000000..5f34c6ed --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,5 @@ +sphinx +sphinx_rtd_theme +sphinx-copybutton +doctr +m2r diff --git a/requirements/pip.txt b/requirements/pip.txt new file mode 100644 index 00000000..74fa65e6 --- /dev/null +++ b/requirements/pip.txt @@ -0,0 +1,3 @@ +matplotlib +numpy +scipy diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 00000000..a7277865 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,6 @@ +flake8 +pytest +codecov +coverage +pytest-cov +pytest-env diff --git a/setup.py b/setup.py deleted file mode 100755 index d3577dd2..00000000 --- a/setup.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python - -# Installation script for diffpy.srfit - -"""diffpy.srfit - framework for setting up complex modeling refinements. - -Packages: diffpy.srfit -""" - -import os -import re -import sys -from setuptools import setup, find_packages - -# Use this version when git data are not available, like in git zip archive. -# Update when tagging a new release. -FALLBACK_VERSION = '3.0.0.post0' - -# determine if we run with Python 3. -PY3 = (sys.version_info[0] == 3) - -# versioncfgfile holds version data for git commit hash and date. -# It must reside in the same directory as version.py. -MYDIR = os.path.dirname(os.path.abspath(__file__)) -versioncfgfile = os.path.join(MYDIR, 'src/diffpy/srfit/version.cfg') -gitarchivecfgfile = os.path.join(MYDIR, '.gitarchive.cfg') - -def gitinfo(): - from subprocess import Popen, PIPE - kw = dict(stdout=PIPE, cwd=MYDIR, universal_newlines=True) - proc = Popen(['git', 'describe', '--match=v[[:digit:]]*'], **kw) - desc = proc.stdout.read() - proc = Popen(['git', 'log', '-1', '--format=%H %ct %ci'], **kw) - glog = proc.stdout.read() - rv = {} - rv['version'] = '.post'.join(desc.strip().split('-')[:2]).lstrip('v') - rv['commit'], rv['timestamp'], rv['date'] = glog.strip().split(None, 2) - return rv - - -def getversioncfg(): - if PY3: - from configparser import RawConfigParser - else: - from ConfigParser import RawConfigParser - vd0 = dict(version=FALLBACK_VERSION, commit='', date='', timestamp=0) - # first fetch data from gitarchivecfgfile, ignore if it is unexpanded - g = vd0.copy() - cp0 = RawConfigParser(vd0) - cp0.read(gitarchivecfgfile) - if len(cp0.get('DEFAULT', 'commit')) > 20: - g = cp0.defaults() - mx = re.search(r'\btag: v(\d[^,]*)', g.pop('refnames')) - if mx: - g['version'] = mx.group(1) - # then try to obtain version data from git. - gitdir = os.path.join(MYDIR, '.git') - if os.path.exists(gitdir) or 'GIT_DIR' in os.environ: - try: - g = gitinfo() - except OSError: - pass - # finally, check and update the active version file - cp = RawConfigParser() - cp.read(versioncfgfile) - d = cp.defaults() - rewrite = not d or (g['commit'] and ( - g['version'] != d.get('version') or g['commit'] != d.get('commit'))) - if rewrite: - cp.set('DEFAULT', 'version', g['version']) - cp.set('DEFAULT', 'commit', g['commit']) - cp.set('DEFAULT', 'date', g['date']) - cp.set('DEFAULT', 'timestamp', g['timestamp']) - with open(versioncfgfile, 'w') as fp: - cp.write(fp) - return cp - -versiondata = getversioncfg() - -with open(os.path.join(MYDIR, 'README.rst')) as fp: - long_description = fp.read() - -# define distribution -setup_args = dict( - name = "diffpy.srfit", - version = versiondata.get('DEFAULT', 'version'), - packages = find_packages(os.path.join(MYDIR, 'src')), - package_dir = {'' : 'src'}, - test_suite = 'diffpy.srfit.tests', - include_package_data = True, - install_requires = ['six'], - zip_safe = False, - author = "Simon J.L. Billinge", - author_email = "sb2896@columbia.edu", - maintainer = "Pavol Juhas", - maintainer_email = "pavol.juhas@gmail.com", - description = "SrFit - Structure refinement from diffraction data", - long_description = long_description, - long_description_content_type = 'text/x-rst', - license = 'BSD-style license', - url = "https://github.com/diffpy/diffpy.srfit", - keywords = "optimization constraints restraints structure refinement complex modeling", - classifiers = [ - # List of possible values at - # http://pypi.python.org/pypi?:action=list_classifiers - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Operating System :: MacOS', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Scientific/Engineering :: Chemistry', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Software Development :: Libraries', - ], -) - -if __name__ == '__main__': - setup(**setup_args) - -# End of file diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 11098213..406751a6 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -1,26 +1,14 @@ #!/usr/bin/env python ############################################################################## # -# diffpy by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2008-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Chris Farrow and Billinge Group members and community contributors. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srfit/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## - -"""diffpy - tools for structure analysis by diffraction. - -Blank namespace package. -""" - - -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__) - - -# End of file diff --git a/src/diffpy/srfit/__init__.py b/src/diffpy/srfit/__init__.py index 76d70de4..79dc93c5 100644 --- a/src/diffpy/srfit/__init__.py +++ b/src/diffpy/srfit/__init__.py @@ -1,39 +1,40 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2008-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Christopher Farrow, Pavol Juhas, and members of the Billinge Group. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srfit/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## - """Complex modeling framework for structure refinement and solution. -SrFit is a tool for coherently combining known information about a material to -derive other properties, in particular material structure. SrFit allows the -customization and creation of structure representations, profile calculators, -constraints, restraints and file input parsers. The customized pieces can be -glued together within SrFit to optimize a structure, or other physically -relevant information from one or more experimental profiles. Other known -information about the system of interest can be included with arbitrarily -complex constraints and restraints. In this way, the end user creates a -customized fitting application that suits the problem to the available -information. +SrFit is a tool for coherently combining known information about a +material to derive other properties, in particular material structure. +SrFit allows the customization and creation of structure +representations, profile calculators, constraints, restraints and file +input parsers. The customized pieces can be glued together within SrFit +to optimize a structure, or other physically relevant information from +one or more experimental profiles. Other known information about the +system of interest can be included with arbitrarily complex constraints +and restraints. In this way, the end user creates a customized fitting +application that suits the problem to the available information. -The subpackages herein define various pieces of the SrFit framework. Developers -are encouraged to work through the examples described in the documentation to -learn how to use and customize the various parts of SrFit. +The subpackages herein define various pieces of the SrFit framework. +Developers are encouraged to work through the examples described in the +documentation to learn how to use and customize the various parts of +SrFit. """ -__all__ = ["__version__"] - # package version from diffpy.srfit.version import __version__ +# silence the pyflakes syntax checker +assert __version__ or True + # End of file diff --git a/src/diffpy/srfit/equation/__init__.py b/src/diffpy/srfit/equation/__init__.py index 135a6af2..d662474a 100644 --- a/src/diffpy/srfit/equation/__init__.py +++ b/src/diffpy/srfit/equation/__init__.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The core equation evaluator for diffpy.srfit. -This package contains modules and subpackages that are used to create Equation -objects. The Equation class is an encapsulation of a lazy evaluation network -that is used throughout SrFit. The EquationsFactory class is used to create -Equation objects from strings and can incorporate user-defined functions as -well as default operations. +This package contains modules and subpackages that are used to create +Equation objects. The Equation class is an encapsulation of a lazy +evaluation network that is used throughout SrFit. The EquationsFactory +class is used to create Equation objects from strings and can +incorporate user-defined functions as well as default operations. The subpackages define various pieces of the evaluation network. """ @@ -29,5 +28,4 @@ from diffpy.srfit.equation.equationmod import Equation - # End of file diff --git a/src/diffpy/srfit/equation/builder.py b/src/diffpy/srfit/equation/builder.py index af1d12ff..d19a17a4 100644 --- a/src/diffpy/srfit/equation/builder.py +++ b/src/diffpy/srfit/equation/builder.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Classes and utilities for creating equations. The EquationFactory class is used to create an equation (an Equation instance) @@ -77,9 +76,16 @@ > eq = beq.makeEquation() """ -__all__ = ["EquationFactory", "BaseBuilder", "ArgumentBuilder", - "OperatorBuilder", "wrapArgument", "wrapOperator", "wrapFunction", - "getBuilder"] +__all__ = [ + "EquationFactory", + "BaseBuilder", + "ArgumentBuilder", + "OperatorBuilder", + "wrapArgument", + "wrapOperator", + "wrapFunction", + "getBuilder", +] # NOTE - the builder cannot handle numpy arrays on the left of a binary # operation because the array will automatically loop the operator of the @@ -91,13 +97,13 @@ import inspect import numbers -import numpy +import numpy import six import diffpy.srfit.equation.literals as literals -from diffpy.srfit.equation.literals.literal import Literal from diffpy.srfit.equation.equationmod import Equation +from diffpy.srfit.equation.literals.literal import Literal class EquationFactory(object): @@ -125,8 +131,9 @@ def __init__(self): self.registerConstant("e", numpy.e) return - def makeEquation(self, eqstr, buildargs = True, argclass = - literals.Argument, argkw = {}): + def makeEquation( + self, eqstr, buildargs=True, argclass=literals.Argument, argkw={} + ): """Make an equation from an equation string. Arguments @@ -152,7 +159,7 @@ def makeEquation(self, eqstr, buildargs = True, argclass = # handle scalar numbers or numpy arrays if isinstance(beq, (numbers.Number, numpy.ndarray)): lit = literals.Argument(value=beq, const=True) - eq = Equation(name='', root=lit) + eq = Equation(name="", root=lit) else: eq = beq.getEquation() self.equations.add(eq) @@ -177,11 +184,12 @@ def registerArgument(self, name, arg): def registerOperator(self, name, op): """Register an Operator literal with the factory. - Operators can be used with or without arguments (or parentheses) in an - equation string. If used with arguments, then the Operator will use - the passed arguments as arguments for the operation. If used without - arguments, it is assumed that the operator is already populated with - arguments, and those will be used. + Operators can be used with or without arguments (or parentheses) + in an equation string. If used with arguments, then the + Operator will use the passed arguments as arguments for the + operation. If used without arguments, it is assumed that the + operator is already populated with arguments, and those will be + used. Returns the registered builder. """ @@ -215,13 +223,14 @@ def registerFunction(self, name, func, argnames): def registerBuilder(self, name, builder): """Register builder in this module so it can be used in makeEquation. - If an extant builder with the given name is already registered, this - will replace all instances of the old builder's literal in the - factory's equation set with the new builder's literal. Note that this - may lead to errors if one of the replacements causes a self-reference. + If an extant builder with the given name is already registered, + this will replace all instances of the old builder's literal in + the factory's equation set with the new builder's literal. Note + that this may lead to errors if one of the replacements causes a + self-reference. - Raises ValueError if the new builder's literal causes a self-reference - in an existing equation. + Raises ValueError if the new builder's literal causes a self- + reference in an existing equation. """ if not isinstance(name, six.string_types): raise TypeError("Name must be a string") @@ -248,21 +257,21 @@ def registerBuilder(self, name, builder): def deRegisterBuilder(self, name): """De-register a builder by name. - This does not change the equations that use the Literal wrapped by the - builder. + This does not change the equations that use the Literal wrapped + by the builder. """ if name in self.builders: del self.builders[name] return - def wipeout(self, eq): """Invalidate the specified equation and remove it from the factory. - This will remove the equation from the purview of the factory and also - change its formula to return NaN. This ensures that eq does not - observe any object in the factory and thus prevents its indirect - pickling with the factory because of observer callback function. + This will remove the equation from the purview of the factory + and also change its formula to return NaN. This ensures that eq + does not observe any object in the factory and thus prevents its + indirect pickling with the factory because of observer callback + function. No return value. """ @@ -272,11 +281,10 @@ def wipeout(self, eq): self.equations.discard(eq) # invalidate this equation to clean up any observer relations of # objects in the factory towards its literals tree. - nan = literals.Argument('nan', value=numpy.nan, const=True) + nan = literals.Argument("nan", value=numpy.nan, const=True) eq.setRoot(nan) return - def _prepareBuilders(self, eqstr, buildargs, argclass, argkw): """Prepare builders so that equation string can be evaluated. @@ -309,14 +317,14 @@ def _prepareBuilders(self, eqstr, buildargs, argclass, argkw): # this is disallowed. if not buildargs and eqargs: eqargsstr = ", ".join(eqargs) - msg = "The equation contains undefined arguments: %s"%eqargsstr + msg = "The equation contains undefined arguments: %s" % eqargsstr raise ValueError(msg) # Make the arguments newargs = set() for argname in eqargs: - arg = argclass(name = argname, **argkw) - argbuilder = ArgumentBuilder(name = argname, arg = arg) + arg = argclass(name=argname, **argkw) + argbuilder = ArgumentBuilder(name=argname, arg=arg) newargs.add(arg) self.registerBuilder(argname, argbuilder) @@ -327,14 +335,14 @@ def _prepareBuilders(self, eqstr, buildargs, argclass, argkw): def _getUndefinedArgs(self, eqstr): """Get the undefined arguments from eqstr. - This tokenizes eqstr and extracts undefined arguments. An undefined - argument is defined as any token that is not a special character that - does not correspond to a builder. + This tokenizes eqstr and extracts undefined arguments. An + undefined argument is defined as any token that is not a special + character that does not correspond to a builder. Raises SyntaxError if the equation string uses invalid syntax. """ - import tokenize import token + import tokenize interface = six.StringIO(eqstr).readline # output is an iterator. Each entry (token) is a 5-tuple @@ -353,28 +361,32 @@ def _getUndefinedArgs(self, eqstr): if tok[0] in (token.NAME, token.OP): args.add(tok[1]) except tokenize.TokenError: - m = "invalid syntax: '%s'"%eqstr + m = "invalid syntax: '%s'" % eqstr raise SyntaxError(m) # Scan the tokens for names that do not correspond to registered # builders. These will be treated as arguments that need to be # generated. for tok in set(args): - # Move genuine varibles to the eqargs dictionary + # Move genuine variables to the eqargs dictionary if ( # Check registered builders - tok in self.builders or + tok in self.builders + or # Check symbols - tok in EquationFactory.symbols or + tok in EquationFactory.symbols + or # Check ignored characters tok in EquationFactory.ignore - ): + ): args.remove(tok) return args + # End class EquationFactory + class BaseBuilder(object): """Class for building equations. @@ -392,35 +404,36 @@ def __init__(self): def __call__(self, *args): """Raises exception for easier debugging.""" - m = "%s (%s) cannot accept arguments"%\ - (self.literal.name, self.__class__.__name__) + m = "%s (%s) cannot accept arguments" % ( + self.literal.name, + self.__class__.__name__, + ) raise TypeError(m) - def getEquation(self): """Get the equation built by this object. - The equation will given the name "_eq_" where "" is the - name of the root node. + The equation will given the name "_eq_" where "" is + the name of the root node. """ # We need to make a name for this, so we name it after its root - name = "_eq_%s"%self.literal.name + name = "_eq_%s" % self.literal.name eq = Equation(name, self.literal) return eq - def __evalBinary(self, other, OperatorClass, onleft = True): + def __evalBinary(self, other, OperatorClass, onleft=True): """Evaluate a binary function. Other can be an BaseBuilder or a constant. onleft -- Indicates that the operator was passed on the left side - (defualt True). + (default True). """ # Create the Operator op = OperatorClass() # onleft takes care of non-commutative operators, and assures that the - # ordering is perserved. + # ordering is preserved. if onleft: # Add the literals to the operator op.addLiteral(self.literal) @@ -498,8 +511,10 @@ def __rmod__(self, other): def __neg__(self): return self.__evalUnary(literals.NegationOperator) + ## These are used by the class. + class ArgumentBuilder(BaseBuilder): """BaseBuilder wrapper around an Argument literal. @@ -510,7 +525,7 @@ class ArgumentBuilder(BaseBuilder): literal -- The Argument wrapped by this instance. """ - def __init__(self, value = None, name = None, const = False, arg = None): + def __init__(self, value=None, name=None, const=False, arg=None): """Create an ArgumentBuilder instance, containing a new Argument. Arguments @@ -524,14 +539,17 @@ def __init__(self, value = None, name = None, const = False, arg = None): """ BaseBuilder.__init__(self) if arg is None: - self.literal = literals.Argument(value=value, name=name, - const=const) + self.literal = literals.Argument( + value=value, name=name, const=const + ) else: self.literal = arg return + # end class ArgumentBuilder + class OperatorBuilder(BaseBuilder): """BaseBuilder wrapper around an Operator literal. @@ -540,7 +558,7 @@ class OperatorBuilder(BaseBuilder): name -- The name of the operator to be wrapped """ - def __init__(self, name, op = None): + def __init__(self, name, op=None): """Wrap an Operator or a function by name. Arguments @@ -573,43 +591,51 @@ def __call__(self, *args): self.literal = literals.UFuncOperator(ufunc) # Here the Operator is already specified. We can copy its attributes # to a new Operator inside of the new OperatorBuilder. - op = literals.makeOperator(name=self.literal.name, - symbol=self.literal.symbol, - nin=self.literal.nin, - nout=self.literal.nout, - operation=self.literal.operation) + op = literals.makeOperator( + name=self.literal.name, + symbol=self.literal.symbol, + nin=self.literal.nin, + nout=self.literal.nout, + operation=self.literal.operation, + ) newobj.literal = op # Now that we have a literal, let's check our inputs literal = newobj.literal if literal.nin >= 0 and len(args) != literal.nin: - raise ValueError("%s takes %i arguments (%i given)"%\ - (self.literal, self.literal.nin, len(args))) + raise ValueError( + "%s takes %i arguments (%i given)" + % (self.literal, self.literal.nin, len(args)) + ) # Wrap scalar arguments for i, arg in enumerate(args): # Wrap the argument if it is not already if not isinstance(arg, BaseBuilder): - name = self.name + "_%i"%i - arg = ArgumentBuilder(value = arg, name = name, const = True) + name = self.name + "_%i" % i + arg = ArgumentBuilder(value=arg, name=name, const=True) newobj.literal.addLiteral(arg.literal) return newobj + # end class OperatorBuilder # Utility functions + def wrapArgument(name, arg): """Wrap an Argument as a builder.""" - argbuilder = ArgumentBuilder(arg = arg) + argbuilder = ArgumentBuilder(arg=arg) return argbuilder + def wrapOperator(name, op): """Wrap an Operator as a builder.""" opbuilder = OperatorBuilder(name, op) return opbuilder + def wrapFunction(name, func, nin=2, nout=1): """Wrap a function in an OperatorBuilder instance. @@ -620,19 +646,21 @@ def wrapFunction(name, func, nin=2, nout=1): Returns the OperatorBuilder instance that wraps the function. """ - op = literals.makeOperator(name=name, symbol=name, - nin=nin, nout=nout, - operation=func) + op = literals.makeOperator( + name=name, symbol=name, nin=nin, nout=nout, operation=func + ) # Create the OperatorBuilder opbuilder = OperatorBuilder(name, op) return opbuilder + def getBuilder(name): """Get an operator from the global builders dictionary.""" return _builders[name] + def __wrapNumpyOperators(): """Export all numpy operators as OperatorBuilder instances in the module namespace.""" @@ -641,8 +669,11 @@ def __wrapNumpyOperators(): if isinstance(op, numpy.ufunc): _builders[name] = OperatorBuilder(name) return + + __wrapNumpyOperators() + # Register other functions as well def __wrapSrFitOperators(): """Export all non-base operators from the @@ -651,16 +682,20 @@ def __wrapSrFitOperators(): opmod = literals.operators excluded_types = set((opmod.CustomOperator, opmod.UFuncOperator)) # check if opmod member should be wrapped as OperatorBuilder - is_exported_type = lambda cls : ( - inspect.isclass(cls) and issubclass(cls, opmod.Operator) and - not inspect.isabstract(cls) and - not cls in excluded_types) + is_exported_type = lambda cls: ( + inspect.isclass(cls) + and issubclass(cls, opmod.Operator) + and not inspect.isabstract(cls) + and not cls in excluded_types + ) # create OperatorBuilder objects for nm, opclass in inspect.getmembers(opmod, is_exported_type): op = opclass() assert op.name, "Unnamed Operator should never appear here." _builders[op.name] = OperatorBuilder(op.name, op) return + + __wrapSrFitOperators() # End of file diff --git a/src/diffpy/srfit/equation/equationmod.py b/src/diffpy/srfit/equation/equationmod.py index 7df4967e..091f7a55 100644 --- a/src/diffpy/srfit/equation/equationmod.py +++ b/src/diffpy/srfit/equation/equationmod.py @@ -12,26 +12,21 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The Equation class for holding and evaluating an equation. -Equation is a functor that holds a Literal tree that defines an equation. It's -__call__ method evaluates the equation at the most recent value of its -Arguments. The non-constant arguments are accessible as attributes of the -Equation instance and can be passed as arguments to __call__. - -Example -> # make a Literal tree. Here's a simple one -> add = AdditionOperator() -> a = Argument(name="a") # Don't forget to name these! -> b = Argument(name="b") -> add.addLiteral(a) -> add.addLiteral(b) -> # make an Equation instance and pass the root > eq = Equation(root = add) -> eq(a=3, b=4) # returns 7 > eq(a=2) # remembers b=4, returns 6 -> eq.a.setValue(-3) -> eq.b.setValue(3) -> eq() # uses last assignment of a and b, returns 0 +Equation is a functor that holds a Literal tree that defines an +equation. It's __call__ method evaluates the equation at the most recent +value of its Arguments. The non-constant arguments are accessible as +attributes of the Equation instance and can be passed as arguments to +__call__. + +Example > # make a Literal tree. Here's a simple one > add = +AdditionOperator() > a = Argument(name="a") # Don't forget to name +these! > b = Argument(name="b") > add.addLiteral(a) > add.addLiteral(b) +> # make an Equation instance and pass the root > eq = Equation(root = +add) > eq(a=3, b=4) # returns 7 > eq(a=2) # remembers b=4, returns 6 > +eq.a.setValue(-3) > eq.b.setValue(3) > eq() # uses last assignment of a +and b, returns 0 See the class documentation for more information. """ @@ -40,9 +35,10 @@ from collections import OrderedDict -from diffpy.srfit.equation.visitors import validate, getArgs, swap -from diffpy.srfit.equation.literals.operators import Operator from diffpy.srfit.equation.literals.literal import Literal +from diffpy.srfit.equation.literals.operators import Operator +from diffpy.srfit.equation.visitors import getArgs, swap, validate + class Equation(Operator): """Class for holding and evaluating a Literal tree. @@ -79,7 +75,7 @@ class Equation(Operator): nin = None nout = 1 - def __init__(self, name = None, root = None): + def __init__(self, name=None, root=None): """Initialize. name -- A name for this Equation. @@ -90,7 +86,7 @@ def __init__(self, name = None, root = None): # Operator stuff. We circumvent Operator.__init__ since we're using # args as a property. We cannot set it, as the Operator tries to do. if name is None and root is not None: - name = "eq_%s"%root.name + name = "eq_%s" % root.name Literal.__init__(self, name) self.root = None self.argdict = OrderedDict() @@ -98,23 +94,20 @@ def __init__(self, name = None, root = None): self.setRoot(root) return - @property def symbol(self): return self.name - def operation(self, *args, **kw): """Evaluate this Equation object. - Same as the __call__ method. This method is used via the Operator - interface. + Same as the __call__ method. This method is used via the + Operator interface. Return the result of __call__(*args, **kw). """ return self.__call__(*args, **kw) - def _getArgs(self): return list(self.argdict.values()) @@ -123,16 +116,15 @@ def _getArgs(self): def __getattr__(self, name): """Gives access to the Arguments as attributes.""" # Avoid infinite loop on argdict lookup. - argdict = object.__getattribute__(self, 'argdict') + argdict = object.__getattribute__(self, "argdict") if not name in argdict: raise AttributeError("No argument named '%s' here" % name) return argdict[name] - # Ensure there is no __dir__ override in the base class. - assert (getattr(Operator, '__dir__', None) is - getattr(object, '__dir__', None)) - + assert getattr(Operator, "__dir__", None) is getattr( + object, "__dir__", None + ) def __dir__(self): "Return sorted list of attributes for this object." @@ -141,7 +133,6 @@ def __dir__(self): rv = sorted(rv) return rv - def setRoot(self, root): """Set the root of the Literal tree. @@ -163,20 +154,20 @@ def setRoot(self, root): # Get the args args = getArgs(root, getconsts=False) - self.argdict = OrderedDict( [(arg.name, arg) for arg in args] ) + self.argdict = OrderedDict([(arg.name, arg) for arg in args]) # Set Operator attributes self.nin = len(self.args) return - def __call__(self, *args, **kw): """Call the equation. - New Argument values are acceped as arguments or keyword assignments (or - both). The order of accepted arguments is given by the args attribute. - The Equation will remember values set in this way. + New Argument values are accepted as arguments or keyword + assignments (or both). The order of accepted arguments is given + by the args attribute. The Equation will remember values set in + this way. Raises ValueError when a passed argument cannot be found """ @@ -191,7 +182,7 @@ def __call__(self, *args, **kw): for name, val in kw.items(): arg = self.argdict.get(name) if arg is None: - raise ValueError("No argument named '%s' here"%name) + raise ValueError("No argument named '%s' here" % name) arg.setValue(val) self._value = self.root.getValue() @@ -218,4 +209,5 @@ def identify(self, visitor): """Identify self to a visitor.""" return visitor.onEquation(self) + # End of file diff --git a/src/diffpy/srfit/equation/literals/__init__.py b/src/diffpy/srfit/equation/literals/__init__.py index 2101faba..862cac3b 100644 --- a/src/diffpy/srfit/equation/literals/__init__.py +++ b/src/diffpy/srfit/equation/literals/__init__.py @@ -12,45 +12,60 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Building blocks for defining a lazy evaluation network. -Literals are the building blocks of the evaluation network. An Argument holds -the name and value of an equation variable. Operators are used to compose other -Literals to produce a new value. +Literals are the building blocks of the evaluation network. An Argument +holds the name and value of an equation variable. Operators are used to +compose other Literals to produce a new value. + +Literal networks can be evaluated or have other actions performed on +them by Visitors (in diffpy.srfit.equation.visitors). The Literal- +Visitor relationship is that described by the Visitor pattern ( -Literal networks can be evaluated or have other actions performed on them by -Visitors (in diffpy.srfit.equation.visitors). The Literal-Visitor relationship -is that described by the Visitor pattern -(http://en.wikipedia.org/wiki/Visitor_pattern). +http://en.wikipedia.org/wiki/Visitor_pattern). """ -__all__ = ["Argument", "Operator", "BinaryOperator", "CustomOperator", - "AdditionOperator", "SubtractionOperator", - "MultiplicationOperator", "DivisionOperator", "ExponentiationOperator", - "RemainderOperator", "NegationOperator", "ConvolutionOperator", - "SumOperator", "UFuncOperator", "ArrayOperator", "PolyvalOperator", - "makeOperator"] +__all__ = [ + "Argument", + "Operator", + "BinaryOperator", + "CustomOperator", + "AdditionOperator", + "SubtractionOperator", + "MultiplicationOperator", + "DivisionOperator", + "ExponentiationOperator", + "RemainderOperator", + "NegationOperator", + "ConvolutionOperator", + "SumOperator", + "UFuncOperator", + "ArrayOperator", + "PolyvalOperator", + "makeOperator", +] # Import the operators from diffpy.srfit.equation.literals.argument import Argument -from diffpy.srfit.equation.literals.operators import Operator -from diffpy.srfit.equation.literals.operators import BinaryOperator -from diffpy.srfit.equation.literals.operators import CustomOperator -from diffpy.srfit.equation.literals.operators import AdditionOperator -from diffpy.srfit.equation.literals.operators import SubtractionOperator -from diffpy.srfit.equation.literals.operators import MultiplicationOperator -from diffpy.srfit.equation.literals.operators import DivisionOperator -from diffpy.srfit.equation.literals.operators import ExponentiationOperator -from diffpy.srfit.equation.literals.operators import RemainderOperator -from diffpy.srfit.equation.literals.operators import NegationOperator -from diffpy.srfit.equation.literals.operators import ConvolutionOperator -from diffpy.srfit.equation.literals.operators import UFuncOperator -from diffpy.srfit.equation.literals.operators import SumOperator -from diffpy.srfit.equation.literals.operators import ArrayOperator -from diffpy.srfit.equation.literals.operators import PolyvalOperator -from diffpy.srfit.equation.literals.operators import makeOperator +from diffpy.srfit.equation.literals.operators import ( + AdditionOperator, + ArrayOperator, + BinaryOperator, + ConvolutionOperator, + CustomOperator, + DivisionOperator, + ExponentiationOperator, + MultiplicationOperator, + NegationOperator, + Operator, + PolyvalOperator, + RemainderOperator, + SubtractionOperator, + SumOperator, + UFuncOperator, + makeOperator, +) # End of file diff --git a/src/diffpy/srfit/equation/literals/abcs.py b/src/diffpy/srfit/equation/literals/abcs.py index d57bf047..605bc53e 100644 --- a/src/diffpy/srfit/equation/literals/abcs.py +++ b/src/diffpy/srfit/equation/literals/abcs.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Abstract Base Classes for Literals.""" __all__ = ["LiteralABC", "ArgumentABC", "OperatorABC"] @@ -31,13 +30,16 @@ class LiteralABC(object): """ @abstractmethod - def identify(self, visitor): pass + def identify(self, visitor): + pass @abstractmethod - def getValue(self): pass + def getValue(self): + pass name = abstractproperty(None, None) + # End class LiteralABC @@ -48,11 +50,13 @@ class ArgumentABC(LiteralABC): """ @abstractmethod - def setValue(self, value): pass + def setValue(self, value): + pass const = abstractproperty(None, None) value = abstractproperty(None, None) + # End class ArgumentABC @@ -63,7 +67,8 @@ class OperatorABC(LiteralABC): """ @abstractmethod - def addLiteral(self, literal): pass + def addLiteral(self, literal): + pass args = abstractproperty(None, None) nin = abstractproperty(None, None) @@ -72,4 +77,5 @@ def addLiteral(self, literal): pass symbol = abstractproperty(None, None) value = abstractproperty(None, None) + # End class OperatorABC diff --git a/src/diffpy/srfit/equation/literals/argument.py b/src/diffpy/srfit/equation/literals/argument.py index 83564207..9f9f1cd7 100644 --- a/src/diffpy/srfit/equation/literals/argument.py +++ b/src/diffpy/srfit/equation/literals/argument.py @@ -12,19 +12,20 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Argument class. -Arguments are the leaves of an equation tree, in essense a variable or a +Arguments are the leaves of an equation tree, in essence a variable or a constant. """ __all__ = ["Argument"] -from numpy import ndarray, array_equal +from numpy import array_equal, ndarray + from diffpy.srfit.equation.literals.abcs import ArgumentABC from diffpy.srfit.equation.literals.literal import Literal + class Argument(Literal, ArgumentABC): """Argument class. @@ -38,7 +39,7 @@ class Argument(Literal, ArgumentABC): const = None - def __init__(self, name = None, value = None, const = False): + def __init__(self, name=None, value=None, const=False): """Initialization.""" Literal.__init__(self, name) self.const = const @@ -67,7 +68,9 @@ def setValue(self, val): self.notify() return - value = property( lambda self: self.getValue(), - lambda self, val: self.setValue(val)) + value = property( + lambda self: self.getValue(), lambda self, val: self.setValue(val) + ) + # End of file diff --git a/src/diffpy/srfit/equation/literals/literal.py b/src/diffpy/srfit/equation/literals/literal.py index 4a6f41b5..1bf7fdac 100644 --- a/src/diffpy/srfit/equation/literals/literal.py +++ b/src/diffpy/srfit/equation/literals/literal.py @@ -12,12 +12,11 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Literal base class used to construct equation trees. -Literals are base pieces of the equation hierarchy. The 'identify' method -identifies the Literal to a visitor by calling the identifying method of the -vistior. +Literals are base pieces of the equation hierarchy. The 'identify' +method identifies the Literal to a visitor by calling the identifying +method of the vistior. """ __all__ = ["Literal"] @@ -25,7 +24,8 @@ from diffpy.srfit.equation.literals.abcs import LiteralABC from diffpy.srfit.util.observable import Observable -class Literal(Observable,LiteralABC): + +class Literal(Observable, LiteralABC): """Abstract class for equation pieces, such as operators and arguments. Literal derives from Observable. See diffpy.srfit.util.observable. @@ -63,6 +63,7 @@ def _flush(self, other): return def __str__(self): - return "%s(%s)"%(self.__class__.__name__, self.name) + return "%s(%s)" % (self.__class__.__name__, self.name) + # End of file diff --git a/src/diffpy/srfit/equation/literals/operators.py b/src/diffpy/srfit/equation/literals/operators.py index 3a4e9eaa..cfe612e9 100644 --- a/src/diffpy/srfit/equation/literals/operators.py +++ b/src/diffpy/srfit/equation/literals/operators.py @@ -12,24 +12,34 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Operator classes. -Operators are combined with other Literals to create an equation. Operators are -non-leaf nodes on a Literal tree. These trees can be evaluated by the Evaluator -visitor, or otherwise inspected. +Operators are combined with other Literals to create an equation. +Operators are non-leaf nodes on a Literal tree. These trees can be +evaluated by the Evaluator visitor, or otherwise inspected. -The Operator class contains all the information necessary to be identified and -evaluated by a Visitor. Thus, a single onOperator method exists in the Visitor -base class. Other Operators can be derived from Operator (see -AdditionOperator), but they all identify themselves with the Visitor.onOperator -method. +The Operator class contains all the information necessary to be +identified and evaluated by a Visitor. Thus, a single onOperator method +exists in the Visitor base class. Other Operators can be derived from +Operator (see AdditionOperator), but they all identify themselves with +the Visitor.onOperator method. """ -__all__ = ["Operator", "AdditionOperator", "SubtractionOperator", - "MultiplicationOperator", "DivisionOperator", "ExponentiationOperator", - "RemainderOperator", "NegationOperator", "ConvolutionOperator", - "SumOperator", "UFuncOperator", "ArrayOperator", "PolyvalOperator"] +__all__ = [ + "Operator", + "AdditionOperator", + "SubtractionOperator", + "MultiplicationOperator", + "DivisionOperator", + "ExponentiationOperator", + "RemainderOperator", + "NegationOperator", + "ConvolutionOperator", + "SumOperator", + "UFuncOperator", + "ArrayOperator", + "PolyvalOperator", +] import numpy @@ -41,7 +51,7 @@ class Operator(Literal, OperatorABC): """Abstract class for specifying a general operator. This class provides several methods that are common to a derived - classes for concrete concrete operations. + classes for concrete operations. Class Attributes ---------------- @@ -72,13 +82,11 @@ class Operator(Literal, OperatorABC): # _value : float, numpy.ndarray or None # The last value of the operator or None. - # We must declare the abstract `args` here. args = None # default for the value _value = None - def __init__(self, name=None): """Initialize the operator object with the specified name. @@ -92,7 +100,6 @@ def __init__(self, name=None): self.args = [] return - def identify(self, visitor): """Identify self to a visitor.""" return visitor.onOperator(self) @@ -100,8 +107,8 @@ def identify(self, visitor): def addLiteral(self, literal): """Add a literal to this operator. - Note that order of operation matters. The first literal added is the - leftmost argument. The last is the rightmost. + Note that order of operation matters. The first literal added is + the leftmost argument. The last is the rightmost. Raises ValueError if the literal causes a self-reference. """ @@ -124,7 +131,7 @@ def getValue(self): def _loopCheck(self, literal): """Check if a literal causes self-reference.""" if literal is self: - raise ValueError("'%s' causes self-reference"%literal) + raise ValueError("'%s' causes self-reference" % literal) # Check to see if I am a dependency of the literal. if hasattr(literal, "args"): @@ -203,6 +210,7 @@ def makeOperator(name, symbol, operation, nin, nout): op.nout = nout return op + # Some specified operators @@ -275,10 +283,10 @@ def _conv(v1, v2): # Find the centroid of the first signal s1 = sum(v1) x1 = numpy.arange(len(v1), dtype=float) - c1idx = numpy.sum(v1 * x1)/s1 + c1idx = numpy.sum(v1 * x1) / s1 # Find the centroid of the convolution xc = numpy.arange(len(c), dtype=float) - ccidx = numpy.sum(c * xc)/sum(c) + ccidx = numpy.sum(c * xc) / sum(c) # Interpolate the convolution such that the centroids line up. This # uses linear interpolation. shift = ccidx - c1idx @@ -288,7 +296,7 @@ def _conv(v1, v2): # Normalize sc = sum(c) if sc > 0: - c *= s1/sc + c *= s1 / sc return c @@ -296,13 +304,14 @@ def _conv(v1, v2): class ConvolutionOperator(BinaryOperator): """Convolve two signals. - This convolves two signals such that centroid of the first array is not - altered by the convolution. Furthermore, the integrated amplitude of the - convolution is scaled to be that of the first signal. This is mean to act - as a convolution of a signal by a probability distribution. + This convolves two signals such that centroid of the first array is + not altered by the convolution. Furthermore, the integrated + amplitude of the convolution is scaled to be that of the first + signal. This is mean to act as a convolution of a signal by a + probability distribution. - Note that this is only possible when the signals are computed over the same - range. + Note that this is only possible when the signals are computed over + the same range. """ name = "convolve" @@ -367,4 +376,5 @@ class PolyvalOperator(BinaryOperator): operation = staticmethod(numpy.polyval) pass + # End of file diff --git a/src/diffpy/srfit/equation/visitors/__init__.py b/src/diffpy/srfit/equation/visitors/__init__.py index b4014c3e..f6cce3bd 100644 --- a/src/diffpy/srfit/equation/visitors/__init__.py +++ b/src/diffpy/srfit/equation/visitors/__init__.py @@ -12,26 +12,27 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Visitors that perform on Literal networks. -Visitors are designed to traverse and extract information from Literal networks -(diffpy.srfit.equation.literals). Visitors are used to validate, print and -extracting Arguments from Literal networks. +Visitors are designed to traverse and extract information from Literal +networks (diffpy.srfit.equation.literals). Visitors are used to +validate, print and extracting Arguments from Literal networks. + +The Literal-Visitor relationship is that described by the Visitor +pattern ( -The Literal-Visitor relationship is that described by the Visitor pattern -(http://en.wikipedia.org/wiki/Visitor_pattern). +http://en.wikipedia.org/wiki/Visitor_pattern). """ from __future__ import print_function from diffpy.srfit.equation.visitors.argfinder import ArgFinder from diffpy.srfit.equation.visitors.printer import Printer -from diffpy.srfit.equation.visitors.validator import Validator from diffpy.srfit.equation.visitors.swapper import Swapper +from diffpy.srfit.equation.visitors.validator import Validator -def getArgs(literal, getconsts = True): +def getArgs(literal, getconsts=True): """Get the Arguments of a Literal tree. getconsts -- If True (default), then Arguments designated as constant @@ -72,7 +73,7 @@ def validate(literal): v = Validator() errors = literal.identify(v) if errors: - m = "Errors found in Literal tree '%s'\n"%literal + m = "Errors found in Literal tree '%s'\n" % literal m += "\n".join(errors) raise ValueError(m) return @@ -81,8 +82,8 @@ def validate(literal): def swap(literal, oldlit, newlit): """Swap one literal for another in a Literal tree. - Corrections are done in-place unless literal is oldlit, in which case the - return value is newlit. + Corrections are done in-place unless literal is oldlit, in which + case the return value is newlit. Returns the literal tree with oldlit swapped for newlit. """ diff --git a/src/diffpy/srfit/equation/visitors/argfinder.py b/src/diffpy/srfit/equation/visitors/argfinder.py index 566955e8..089c8bcc 100644 --- a/src/diffpy/srfit/equation/visitors/argfinder.py +++ b/src/diffpy/srfit/equation/visitors/argfinder.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Visitor for extracting the Argument entries in a Literal tree. ArgFinder extracts all Arguments from a literal true. @@ -31,11 +30,11 @@ class ArgFinder(Visitor): getconsts -- Flag indicating whether to grab constant arguments. """ - def __init__(self, getconsts = True): + def __init__(self, getconsts=True): """Initialize. Arguments - getconsts -- Flag indicating whether to grap constant arguments + getconsts -- Flag indicating whether to parse constant arguments (default True). """ self.args = [] @@ -59,4 +58,5 @@ def onOperator(self, op): arg.identify(self) return self.args + # End of file diff --git a/src/diffpy/srfit/equation/visitors/printer.py b/src/diffpy/srfit/equation/visitors/printer.py index 9b98414c..b3bc5d9d 100644 --- a/src/diffpy/srfit/equation/visitors/printer.py +++ b/src/diffpy/srfit/equation/visitors/printer.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Printer visitor for printing the equation represented by a Literal tree. -The Printer visitor creates a one-line representation of the Literal tree, -which is valid as a string equivalent of the equation. +The Printer visitor creates a one-line representation of the Literal +tree, which is valid as a string equivalent of the equation. """ __all__ = ["Printer"] @@ -25,6 +24,7 @@ from diffpy.srfit.equation.visitors.visitor import Visitor + class Printer(Visitor): """Printer for printing a Literal tree. @@ -44,13 +44,11 @@ def __init__(self): self.reset() return - def reset(self): """Reset the out put string.""" self.output = "" return - @property def eqskip(self): """Pattern for equation objects to be skipped. @@ -70,7 +68,6 @@ def eqskip(self, value): self._eqpat = re.compile(value) return - def onArgument(self, arg): """Process an Argument node. @@ -82,7 +79,6 @@ def onArgument(self, arg): self.output += str(arg.name) return self.output - def onOperator(self, op): """Process an Operator node.""" # We have to deal with infix operators @@ -93,31 +89,32 @@ def onOperator(self, op): self.output += str(op.name) + "(" for idx, literal in enumerate(op.args): - if idx != 0: self.output += ", " + if idx != 0: + self.output += ", " literal.identify(self) self.output += ")" return self.output - def onEquation(self, eq): """Process an Equation node.""" - skipthis = (self._eqpat is not None and eq.name and - self._eqpat.match(eq.name)) + skipthis = ( + self._eqpat is not None and eq.name and self._eqpat.match(eq.name) + ) if skipthis: self.onArgument(eq) else: eq.root.identify(self) return self.output - def _onInfix(self, op): """Process infix operators.""" self.output += "(" op.args[0].identify(self) - self.output += " %s "%op.symbol + self.output += " %s " % op.symbol op.args[1].identify(self) self.output += ")" return + # End of file diff --git a/src/diffpy/srfit/equation/visitors/swapper.py b/src/diffpy/srfit/equation/visitors/swapper.py index 85603911..756cdb68 100644 --- a/src/diffpy/srfit/equation/visitors/swapper.py +++ b/src/diffpy/srfit/equation/visitors/swapper.py @@ -12,13 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Swapper for replacing a Literal in an equation with another Literals.""" __all__ = ["Swapper"] from diffpy.srfit.equation.visitors.visitor import Visitor + class Swapper(Visitor): """Swapper for swapping out one literal for another in a literal tree. @@ -48,7 +48,8 @@ class for how the replacement takes place. def onArgument(self, arg): """Process an Argument node. - Tell the parent to swap the old Argument with the replacement Literal. + Tell the parent to swap the old Argument with the replacement + Literal. """ if arg is self.oldlit: @@ -59,7 +60,8 @@ def onArgument(self, arg): def onOperator(self, op): """Process an Operator node. - Tell the parent to swap the old Operator with the replacement Literal. + Tell the parent to swap the old Operator with the replacement + Literal. """ # Check to see if we need to swap out this Operator. If so, then we @@ -108,7 +110,6 @@ def onOperator(self, op): newlit.addObserver(op._flush) op._flush(other=()) - self._swap = False return @@ -137,4 +138,5 @@ def onEquation(self, eq): return + # End of file diff --git a/src/diffpy/srfit/equation/visitors/validator.py b/src/diffpy/srfit/equation/visitors/validator.py index 318204e0..cdf6d542 100644 --- a/src/diffpy/srfit/equation/visitors/validator.py +++ b/src/diffpy/srfit/equation/visitors/validator.py @@ -12,16 +12,15 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Validator visitor for validating a tree of Literals. The Validator walks an equation tree composed of Literals and checks the validity of each equation as much as possible without evaluating it. It collects errors in a list. -The Validator checks that the input count of each Operator is equal to the -output count of its arguments. It also checks that each object has the proper -interface. +The Validator checks that the input count of each Operator is equal to +the output count of its arguments. It also checks that each object has +the proper interface. """ __all__ = ["Validator"] @@ -59,7 +58,7 @@ def onArgument(self, arg): diffpy.srfit.equation.literals.abcs """ if not isinstance(arg, ArgumentABC): - m = msg%(arg, ArgumentABC.__name__) + m = msg % (arg, ArgumentABC.__name__) self.errors.append(m) self._nin = 1 return self.errors @@ -72,24 +71,24 @@ def onOperator(self, op): """ if not isinstance(op, OperatorABC): - m = msg%(op, OperatorABC.__name__) + m = msg % (op, OperatorABC.__name__) self.errors.append(m) # Can only process single-valued functions if op.nout != 1: - m = "'%s' is not single-valued (nout != 1)"%op + m = "'%s' is not single-valued (nout != 1)" % op self.errors.append(m) # Check name if op.name is None: - m = "'%s' does not have a name"%op + m = "'%s' does not have a name" % op self.errors.append(m) # Check symbol if op.symbol is None: - m = "'%s' does not have a symbol"%op + m = "'%s' does not have a symbol" % op self.errors.append(m) # Check operation without evaluating it if op.operation is None: - m = "'%s' does not define and operation"%op + m = "'%s' does not define and operation" % op self.errors.append(m) localnin = 0 @@ -100,10 +99,15 @@ def onOperator(self, op): # Check the input/output balance if op.nin >= 0 and localnin != op.nin: - m = "'%s' requires %i inputs but receives %i"%(op, op.nin, localnin) + m = "'%s' requires %i inputs but receives %i" % ( + op, + op.nin, + localnin, + ) self.errors.append(m) self._nin = op.nout return self.errors + # End of file diff --git a/src/diffpy/srfit/equation/visitors/visitor.py b/src/diffpy/srfit/equation/visitors/visitor.py index 194ea72c..965c5a92 100644 --- a/src/diffpy/srfit/equation/visitors/visitor.py +++ b/src/diffpy/srfit/equation/visitors/visitor.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Base visitor class. Visitors work with Literal trees to perform a specified action. They are @@ -29,6 +28,7 @@ __all__ = ["Visitor"] + class Visitor(object): """Abstract class for all visitors to a Literal tree. @@ -46,15 +46,17 @@ def onOperator(self, op): def onEquation(self, eq): """Process an Equation node. - Equations are specialized Operators. They don't need to be specifically - supported by a Visitor. + Equations are specialized Operators. They don't need to be + specifically supported by a Visitor. """ return self.onOperator(eq) # throw an exception def _abstract(self, method): raise NotImplementedError( - "class '%s' should override method '%s'" % (self.__class__.__name__, method)) + "class '%s' should override method '%s'" + % (self.__class__.__name__, method) + ) # End of file diff --git a/src/diffpy/srfit/exceptions.py b/src/diffpy/srfit/exceptions.py index eed5a572..f61ada87 100644 --- a/src/diffpy/srfit/exceptions.py +++ b/src/diffpy/srfit/exceptions.py @@ -12,7 +12,6 @@ # See LICENSE.txt for license information. # ############################################################################## - """ Exceptions used for SrFit - specific errors. """ @@ -20,9 +19,11 @@ class SrFitError(Exception): """Generic error in SrFit expressions or recipe.""" + pass class ParseError(Exception): """Exception used by ProfileParsers.""" + pass diff --git a/src/diffpy/srfit/fitbase/__init__.py b/src/diffpy/srfit/fitbase/__init__.py index 48b96c79..bdbcb2d1 100644 --- a/src/diffpy/srfit/fitbase/__init__.py +++ b/src/diffpy/srfit/fitbase/__init__.py @@ -12,33 +12,42 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The base fitting classes for diffpy.srfit. -This package contains modules and subpackages that are used to define a fit -problem in SrFit. Unaltered, these classes will help set up a fit problem that -can be optimized within a fitting framework or with a standalone optimizer. -They provide the basic framework for defining a forward calculator, and from -that defining a fit problem with data, constraints and restraints. The classes -involved in this collaboration can be tied to a fitting framework at various -levels through inheritance. One can create a fitting problem using the -FitContribution, Profile and FitRecipe classes. +This package contains modules and subpackages that are used to define a +fit problem in SrFit. Unaltered, these classes will help set up a fit +problem that can be optimized within a fitting framework or with a +standalone optimizer. They provide the basic framework for defining a +forward calculator, and from that defining a fit problem with data, +constraints and restraints. The classes involved in this collaboration +can be tied to a fitting framework at various levels through +inheritance. One can create a fitting problem using the FitContribution, +Profile and FitRecipe classes. Various code and design taken from Paul Kienzle's PARK package. http://www.reflectometry.org/danse/park.html """ -__all__ = ['Calculator', 'FitContribution', 'FitHook', 'FitRecipe', - 'FitResults', 'initializeRecipe', 'PlotFitHook', 'Profile', - 'ProfileGenerator', 'SimpleRecipe'] +__all__ = [ + "Calculator", + "FitContribution", + "FitHook", + "FitRecipe", + "FitResults", + "initializeRecipe", + "PlotFitHook", + "Profile", + "ProfileGenerator", + "SimpleRecipe", +] from diffpy.srfit.fitbase.calculator import Calculator from diffpy.srfit.fitbase.fitcontribution import FitContribution from diffpy.srfit.fitbase.fithook import FitHook, PlotFitHook from diffpy.srfit.fitbase.fitrecipe import FitRecipe -from diffpy.srfit.fitbase.simplerecipe import SimpleRecipe from diffpy.srfit.fitbase.fitresults import FitResults, initializeRecipe from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator +from diffpy.srfit.fitbase.simplerecipe import SimpleRecipe # End of file diff --git a/src/diffpy/srfit/fitbase/calculator.py b/src/diffpy/srfit/fitbase/calculator.py index de6e5719..32e4c094 100644 --- a/src/diffpy/srfit/fitbase/calculator.py +++ b/src/diffpy/srfit/fitbase/calculator.py @@ -12,22 +12,21 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The Calculator for Parameter-aware functions. -Calculator is a functor class for producing a signal from embedded Parameters. -Calculators can store Parameters and ParameterSets, Constraints and Restraints. -Also, the __call__ function can be overloaded to accept external arguments. -Calculators are used to wrap registered functions so that the function's -Parameters are contained in an object specific to the function. A custom -Calculator can be added to another RecipeOrganizer with the -'registerCalculator' method. +Calculator is a functor class for producing a signal from embedded +Parameters. Calculators can store Parameters and ParameterSets, +Constraints and Restraints. Also, the __call__ function can be +overloaded to accept external arguments. Calculators are used to wrap +registered functions so that the function's Parameters are contained in +an object specific to the function. A custom Calculator can be added to +another RecipeOrganizer with the 'registerCalculator' method. """ __all__ = ["Calculator"] -from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.equation.literals.operators import Operator +from diffpy.srfit.fitbase.parameterset import ParameterSet class Calculator(Operator, ParameterSet): @@ -84,9 +83,9 @@ def symbol(self): def __call__(self, *args): """Calculate something. - This method must be overloaded. When overloading, you should specify - the arguments explicitly, otherwise the parameters must be specified - when adding the Calculator to a RecipeOrganizer. + This method must be overloaded. When overloading, you should + specify the arguments explicitly, otherwise the parameters must + be specified when adding the Calculator to a RecipeOrganizer. """ return 0 @@ -97,9 +96,9 @@ def operation(self, *args): def _validate(self): """Validate my state. - This performs ParameterSet validations. This does not validate the - operation, since this could be costly. The operation should be - validated with a containing equation. + This performs ParameterSet validations. This does not validate + the operation, since this could be costly. The operation should + be validated with a containing equation. Raises AttributeError if validation fails. """ @@ -107,4 +106,5 @@ def _validate(self): return + # End class Calculator diff --git a/src/diffpy/srfit/fitbase/configurable.py b/src/diffpy/srfit/fitbase/configurable.py index d0c185cf..4aecfd5e 100644 --- a/src/diffpy/srfit/fitbase/configurable.py +++ b/src/diffpy/srfit/fitbase/configurable.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Configurable class. A Configurable has state of which a FitRecipe must be aware. @@ -27,8 +26,8 @@ class Configurable(object): A Configurable has state of which a FitRecipe must be aware. Attributes - _configobjs -- Set of Configureables in a hierarcy or instances. - Messasges get passed up the hierarcy to a FitReciple + _configobjs -- Set of Configureables in a hierarchy or instances. + Messages get passed up the hierarchy to a FitReciple via these objects. """ @@ -45,13 +44,14 @@ def _updateConfiguration(self): def _storeConfigurable(self, obj): """Store a Configurable. - The passed obj is only stored if it is a a Configurable, otherwise this - method quietly exits. + The passed obj is only stored if it is a a Configurable, + otherwise this method quietly exits. """ if isinstance(obj, Configurable): self._configobjs.add(obj) return + # End class Configurable # End of file diff --git a/src/diffpy/srfit/fitbase/constraint.py b/src/diffpy/srfit/fitbase/constraint.py index a0f06178..5cf1e2f3 100644 --- a/src/diffpy/srfit/fitbase/constraint.py +++ b/src/diffpy/srfit/fitbase/constraint.py @@ -12,13 +12,12 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Constraint class. -Constraints are used by a FitRecipe (and other RecipeOrganizers) to organize -constraint equations. They store a Parameter object and an Equation object that -is used to compute its value. The Constraint.constrain method is used to create -this association. +Constraints are used by a FitRecipe (and other RecipeOrganizers) to +organize constraint equations. They store a Parameter object and an +Equation object that is used to compute its value. The +Constraint.constrain method is used to create this association. """ __all__ = ["Constraint"] @@ -48,17 +47,17 @@ def __init__(self): def constrain(self, par, eq): """Constrain a Parameter according to an Equation. - The parameter will be set constant once it is constrained. This will - keep it from being constrained multiple times. + The parameter will be set constant once it is constrained. This + will keep it from being constrained multiple times. Raises a ValueError if par is const. """ if par.const: - raise ValueError("The parameter '%s' is constant"%par) + raise ValueError("The parameter '%s' is constant" % par) if par.constrained: - raise ValueError("The parameter '%s' is already constrained"%par) + raise ValueError("The parameter '%s' is already constrained" % par) par.constrained = True @@ -86,8 +85,7 @@ def update(self): def _validate(self): """Validate my state. - This validates that par is not None. - This validates eq. + This validates that par is not None. This validates eq. Raises SrFitError if validation fails. """ @@ -97,6 +95,7 @@ def _validate(self): raise SrFitError("eq is None") self.par._validate() from diffpy.srfit.equation.visitors import validate + try: validate(self.eq) except ValueError as e: @@ -114,4 +113,5 @@ def _validate(self): return + # End of file diff --git a/src/diffpy/srfit/fitbase/fitcontribution.py b/src/diffpy/srfit/fitbase/fitcontribution.py index 48640c19..7b5abbf1 100644 --- a/src/diffpy/srfit/fitbase/fitcontribution.py +++ b/src/diffpy/srfit/fitbase/fitcontribution.py @@ -12,31 +12,31 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """FitContribution class. FitContributions generate a residual function for a FitRecipe. A -FitContribution associates an Equation for generating a signal, optionally one -or more ProfileGenerators or Calculators that help in this, and a Profile that -holds the observed and calculated signals. +FitContribution associates an Equation for generating a signal, +optionally one or more ProfileGenerators or Calculators that help in +this, and a Profile that holds the observed and calculated signals. -See the examples in the documention for how to use a FitContribution. +See the examples in the documentation for how to use a FitContribution. """ __all__ = ["FitContribution"] -from diffpy.srfit.fitbase.parameterset import ParameterSet -from diffpy.srfit.fitbase.recipeorganizer import equationFromString +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.parameter import ParameterProxy +from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.fitbase.profile import Profile -from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.recipeorganizer import equationFromString + class FitContribution(ParameterSet): """FitContribution class. FitContributions organize an Equation that calculates the signal, and a Profile that holds the signal. ProfileGenerators and Calculators can be - used as well. Contraints and Restraints can be created as part of a + used as well. Constraints and Restraints can be created as part of a FitContribution. Attributes @@ -79,7 +79,7 @@ def __init__(self, name): self._manage(self._generators) return - def setProfile(self, profile, xname = None, yname = None, dyname = None): + def setProfile(self, profile, xname=None, yname=None, dyname=None): """Assign the Profile for this FitContribution. profile -- A Profile that specifies the calculation points and that @@ -119,9 +119,9 @@ def setProfile(self, profile, xname = None, yname = None, dyname = None): xpar = ParameterProxy(xname, self.profile.xpar) ypar = ParameterProxy(yname, self.profile.ypar) dypar = ParameterProxy(dyname, self.profile.dypar) - self.addParameter(xpar, check = False) - self.addParameter(ypar, check = False) - self.addParameter(dypar, check = False) + self.addParameter(xpar, check=False) + self.addParameter(ypar, check=False) + self.addParameter(dypar, check=False) # If we have ProfileGenerators, set their Profiles. for gen in self._generators.values(): @@ -129,12 +129,11 @@ def setProfile(self, profile, xname = None, yname = None, dyname = None): # If we have _eq, but not _reseq, set the residual if self._eq is not None and self._reseq is None: - self.setResidualEquation('chiv') + self.setResidualEquation("chiv") return - - def addProfileGenerator(self, gen, name = None): + def addProfileGenerator(self, gen, name=None): """Add a ProfileGenerator to be used by this FitContribution. The ProfileGenerator is given a name so that it can be used as part of @@ -173,7 +172,7 @@ def addProfileGenerator(self, gen, name = None): return - def setEquation(self, eqstr, ns = {}): + def setEquation(self, eqstr, ns={}): """Set the profile equation for the FitContribution. This sets the equation that will be used when generating the residual @@ -193,8 +192,7 @@ def setEquation(self, eqstr, ns = {}): variable. """ # Build the equation instance. - eq = equationFromString(eqstr, self._eqfactory, - buildargs=True, ns=ns) + eq = equationFromString(eqstr, self._eqfactory, buildargs=True, ns=ns) eq.name = "eq" # Register any new Parameters. @@ -208,11 +206,10 @@ def setEquation(self, eqstr, ns = {}): # Set the residual if we need to if self.profile is not None and self._reseq is None: - self.setResidualEquation('chiv') + self.setResidualEquation("chiv") return - def getEquation(self): """Get math expression string for the active profile equation. @@ -220,12 +217,12 @@ def getEquation(self): equation has not been set yet. """ from diffpy.srfit.equation.visitors import getExpression + rv = "" if self._eq is not None: rv = getExpression(self._eq) return rv - def setResidualEquation(self, eqstr): """Set the residual equation for the FitContribution. @@ -266,20 +263,19 @@ def setResidualEquation(self, eqstr): return - def getResidualEquation(self): """Get math expression string for the active residual equation. - Return normalized math formula or an empty string if residual equation - has not been configured yet. + Return normalized math formula or an empty string if residual + equation has not been configured yet. """ from diffpy.srfit.equation.visitors import getExpression + rv = "" if self._reseq is not None: - rv = getExpression(self._reseq, eqskip='eq$') + rv = getExpression(self._reseq, eqskip="eq$") return rv - def residual(self): """Calculate the residual for this fitcontribution. @@ -300,7 +296,6 @@ def residual(self): # the following will not recompute the equation. return self._reseq() - def evaluate(self): """Evaluate the contribution equation and update profile.ycalc.""" yc = self._eq() @@ -308,12 +303,12 @@ def evaluate(self): self.profile.ycalc = yc return yc - def _validate(self): """Validate my state. - This performs profile validations. This performs ProfileGenerator - validations. This validates _eq. This validates _reseq and residual. + This performs profile validations. This performs + ProfileGenerator validations. This validates _eq. This validates + _reseq and residual. Raises SrFitError if validation fails. """ @@ -322,6 +317,7 @@ def _validate(self): # Try to get the value of eq. from diffpy.srfit.equation.visitors import validate + try: validate(self._eq) except ValueError as e: @@ -333,7 +329,7 @@ def _validate(self): try: val = self._eq() except TypeError as e: - raise SrFitError("_eq cannot be evaluated: %s"%e) + raise SrFitError("_eq cannot be evaluated: %s" % e) if val is None: raise SrFitError("_eq evaluates to None") @@ -351,4 +347,5 @@ def _validate(self): raise SrFitError("residual evaluates to None") return + # End of file diff --git a/src/diffpy/srfit/fitbase/fithook.py b/src/diffpy/srfit/fitbase/fithook.py index dfa521b1..35e775f6 100644 --- a/src/diffpy/srfit/fitbase/fithook.py +++ b/src/diffpy/srfit/fitbase/fithook.py @@ -12,17 +12,16 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The FitHook class for inspecting the progress of a FitRecipe refinement. -FitHooks are called by a FitRecipe during various times of the residual is -evaluation. The default FitHook simply counts the number of times the residual -is called, and reports that number every time the residual is calculated. -Depending on the verbosity, it will also report the residual and the current -variable values. +FitHooks are called by a FitRecipe during various times of the residual +is evaluation. The default FitHook simply counts the number of times the +residual is called, and reports that number every time the residual is +calculated. Depending on the verbosity, it will also report the residual +and the current variable values. -Custom FitHooks can be added to a FitRecipe with the FitRecipe.setFitHook -method. +Custom FitHooks can be added to a FitRecipe with the +FitRecipe.setFitHook method. """ from __future__ import print_function @@ -37,18 +36,20 @@ class FitHook(object): """Base class for inspecting the progress of a FitRecipe refinement. - Can serve as a fithook for the FitRecipe class (see FitRecipe.pushFitHook - method.) The methods in this class are called during the preparation of the - FitRecipe for refinement, and during the residual call. See the class - methods for a description of their purpose. + Can serve as a fithook for the FitRecipe class (see + FitRecipe.pushFitHook method.) The methods in this class are called + during the preparation of the FitRecipe for refinement, and during + the residual call. See the class methods for a description of their + purpose. """ def reset(self, recipe): """Reset the hook data. - This is called whenever FitRecipe._prepare is called, which is whenever - a configurational change to the fit hierarchy takes place, such as - adding a new ParameterSet, constraint or restraint. + This is called whenever FitRecipe._prepare is called, which is + whenever a configurational change to the fit hierarchy takes + place, such as adding a new ParameterSet, constraint or + restraint. """ return @@ -67,8 +68,10 @@ def postcall(self, recipe, chiv): """ return + # End class FitHook + class PrintFitHook(FitHook): """Base class for inspecting the progress of a FitRecipe refinement. @@ -94,9 +97,10 @@ def __init__(self): def reset(self, recipe): """Reset the hook data. - This is called whenever FitRecipe._prepare is called, which is whenever - a configurational change to the fit hierarchy takes place, such as - adding a new ParameterSet, constraint or restraint. + This is called whenever FitRecipe._prepare is called, which is + whenever a configurational change to the fit hierarchy takes + place, such as adding a new ParameterSet, constraint or + restraint. """ self.count = 0 return @@ -140,14 +144,16 @@ def postcall(self, recipe, chiv): print("Variables") vnames = recipe.getNames() vals = recipe.getValues() - byname = lambda nv : sortKeyForNumericString(nv[0]) + byname = lambda nv: sortKeyForNumericString(nv[0]) items = sorted(zip(vnames, vals), key=byname) for name, val in items: print(" %s = %f" % (name, val)) return + # End class PrintFitHook + # TODO - Display the chi^2 on the plot during refinement. class PlotFitHook(FitHook): """This FitHook has live plotting of whatever is being refined.""" @@ -177,20 +183,20 @@ def reset(self, recipe): # Create a subplot if nc > 1: - pylab.subplot(nrows, ncols, idx+1) - pdata = pylab.plot(p.x, p.y, 'bo')[0] - pfit = pylab.plot(p.x, p.y, 'r-')[0] + pylab.subplot(nrows, ncols, idx + 1) + pdata = pylab.plot(p.x, p.y, "bo")[0] + pfit = pylab.plot(p.x, p.y, "r-")[0] self._plots.append((pdata, pfit)) pylab.xlabel(xname) pylab.ylabel(yname) pylab.title(name) # Set up some event handling, so things behave nicely. - #def redraw(event): + # def redraw(event): # canvas = event.canvas # canvas.draw() # return - #pylab.connect('resize_event', redraw) + # pylab.connect('resize_event', redraw) return diff --git a/src/diffpy/srfit/fitbase/fitrecipe.py b/src/diffpy/srfit/fitbase/fitrecipe.py index 9e09c271..f6f0cbd7 100644 --- a/src/diffpy/srfit/fitbase/fitrecipe.py +++ b/src/diffpy/srfit/fitbase/fitrecipe.py @@ -12,37 +12,38 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """FitRecipe class. -FitRecipes organize FitContributions, variables, Restraints and Constraints to -create a recipe of the system you wish to optimize. From the client's -perspective, the FitRecipe is a residual calculator. The residual method does -the work of updating variable values, which get propagated to the Parameters of -the underlying FitContributions via the variables and Constraints. This class -needs no special knowledge of the type of FitContribution or data being used. -Thus, it is suitable for combining residual equations from various types of -refinements into a single residual. - -Variables added to a FitRecipe can be tagged with string identifiers. Variables -can be later retrieved or manipulated by tag. The tag name "__fixed" is -reserved. - -See the examples in the documentation for how to create an optimization problem -using FitRecipe. +FitRecipes organize FitContributions, variables, Restraints and +Constraints to create a recipe of the system you wish to optimize. From +the client's perspective, the FitRecipe is a residual calculator. The +residual method does the work of updating variable values, which get +propagated to the Parameters of the underlying FitContributions via the +variables and Constraints. This class needs no special knowledge of the +type of FitContribution or data being used. Thus, it is suitable for +combining residual equations from various types of refinements into a +single residual. + +Variables added to a FitRecipe can be tagged with string identifiers. +Variables can be later retrieved or manipulated by tag. The tag name +"__fixed" is reserved. + +See the examples in the documentation for how to create an optimization +problem using FitRecipe. """ __all__ = ["FitRecipe"] from collections import OrderedDict -from numpy import array, concatenate, sqrt, dot + import six +from numpy import array, concatenate, dot, sqrt -from diffpy.srfit.interface import _fitrecipe_interface -from diffpy.srfit.util.tagmanager import TagManager +from diffpy.srfit.fitbase.fithook import PrintFitHook from diffpy.srfit.fitbase.parameter import ParameterProxy from diffpy.srfit.fitbase.recipeorganizer import RecipeOrganizer -from diffpy.srfit.fitbase.fithook import PrintFitHook +from diffpy.srfit.interface import _fitrecipe_interface +from diffpy.srfit.util.tagmanager import TagManager class FitRecipe(_fitrecipe_interface, RecipeOrganizer): @@ -87,18 +88,28 @@ class FitRecipe(_fitrecipe_interface, RecipeOrganizer): bounds2 -- Bounds on parameters (read only). See getBounds2. """ - fixednames = property(lambda self: - [v.name for v in self._parameters.values() - if not (self.isFree(v) or self.isConstrained(v))], - doc='names of the fixed refinable variables') - fixedvalues = property(lambda self: - array([v.value for v in self._parameters.values() - if not (self.isFree(v) or self.isConstrained(v))]), - doc='values of the fixed refinable variables') + fixednames = property( + lambda self: [ + v.name + for v in self._parameters.values() + if not (self.isFree(v) or self.isConstrained(v)) + ], + doc="names of the fixed refinable variables", + ) + fixedvalues = property( + lambda self: array( + [ + v.value + for v in self._parameters.values() + if not (self.isFree(v) or self.isConstrained(v)) + ] + ), + doc="values of the fixed refinable variables", + ) bounds = property(lambda self: self.getBounds()) bounds2 = property(lambda self: self.getBounds2()) - def __init__(self, name = "fit"): + def __init__(self, name="fit"): """Initialization.""" RecipeOrganizer.__init__(self, name) self.fithooks = [] @@ -119,7 +130,7 @@ def __init__(self, name = "fit"): return - def pushFitHook(self, fithook, index = None): + def pushFitHook(self, fithook, index=None): """Add a FitHook to be called within the residual method. The hook is an object for reporting updates, or more fundamentally, @@ -138,7 +149,7 @@ def pushFitHook(self, fithook, index = None): self._updateConfiguration() return - def popFitHook(self, fithook = None, index = -1): + def popFitHook(self, fithook=None, index=-1): """Remove a FitHook by index or reference. fithook -- FitHook instance to remove from the sequence. If this is @@ -164,7 +175,7 @@ def clearFitHooks(self): del self.fithooks[:] return - def addContribution(self, con, weight = 1.0): + def addContribution(self, con, weight=1.0): """Add a FitContribution to the FitRecipe. con -- The FitContribution to be stored. @@ -203,7 +214,7 @@ def removeParameterSet(self, parset): self._removeObject(parset, self._parsets) return - def residual(self, p = []): + def residual(self, p=[]): """Calculate the vector residual to be optimized. Arguments @@ -234,22 +245,25 @@ def residual(self, p = []): con.update() # Calculate the bare chiv - chiv = concatenate([ - wi * ci.residual().flatten() - for wi, ci in zip(self._weights, self._contributions.values())]) + chiv = concatenate( + [ + wi * ci.residual().flatten() + for wi, ci in zip(self._weights, self._contributions.values()) + ] + ) # Calculate the point-average chi^2 - w = dot(chiv, chiv)/len(chiv) + w = dot(chiv, chiv) / len(chiv) # Now we must append the restraints - penalties = [ sqrt(res.penalty(w)) for res in self._restraintlist ] - chiv = concatenate( [ chiv, penalties ] ) + penalties = [sqrt(res.penalty(w)) for res in self._restraintlist] + chiv = concatenate([chiv, penalties]) for fithook in self.fithooks: fithook.postcall(self, chiv) return chiv - def scalarResidual(self, p = []): + def scalarResidual(self, p=[]): """Calculate the scalar residual to be optimized. Arguments @@ -267,7 +281,7 @@ def scalarResidual(self, p = []): chiv = self.residual(p) return dot(chiv, chiv) - def __call__(self, p = []): + def __call__(self, p=[]): """Same as scalarResidual method.""" return self.scalarResidual(p) @@ -277,7 +291,8 @@ def _prepare(self): This will prepare the data attributes to be used in the residual calculation. - This updates the local restraints with those of the contributions. + This updates the local restraints with those of the + contributions. Raises AttributeError if there are variables without a value. """ @@ -318,14 +333,16 @@ def __verifyProfiles(self): # Check for profile values for con in self._contributions.values(): if con.profile is None: - m = "FitContribution '%s' does not have a Profile"%con.name + m = "FitContribution '%s' does not have a Profile" % con.name raise AttributeError(m) - if con.profile.x is None or\ - con.profile.y is None or\ - con.profile.dy is None: + if ( + con.profile.x is None + or con.profile.y is None + or con.profile.dy is None + ): - m = "Profile for '%s' is missing data"%con.name - raise AttributeError(m) + m = "Profile for '%s' is missing data" % con.name + raise AttributeError(m) return def __verifyParameters(self): @@ -344,7 +361,7 @@ def __verifyParameters(self): for par in badpars: objlist = self._locateManagedObject(par) names = [obj.name for obj in objlist] - badnames.append( ".".join(names) ) + badnames.append(".".join(names)) # Construct an error message, if necessary m = "" @@ -362,14 +379,15 @@ def __verifyParameters(self): def __collectConstraintsAndRestraints(self): """Collect the Constraints and Restraints from subobjects.""" - from itertools import chain from functools import cmp_to_key + from itertools import chain + rset = set(self._restraints) cdict = {} for org in chain(self._contributions.values(), self._parsets.values()): - rset.update( org._getRestraints() ) - cdict.update( org._getConstraints() ) + rset.update(org._getRestraints()) + cdict.update(org._getConstraints()) cdict.update(self._constraints) # The order of the restraint list does not matter @@ -386,7 +404,7 @@ def __collectConstraintsAndRestraints(self): # Now check the constraint's equation for constrained arguments for arg in con.eq.args: if arg in cdict: - depmap[con].add( cdict[arg] ) + depmap[con].add(cdict[arg]) # Turn the dependency map into multi-level map. def _extendDeps(con): @@ -422,8 +440,9 @@ def cmp(x, y): # Variable manipulation - def addVar(self, par, value = None, name = None, fixed = False, tag = None, - tags = []): + def addVar( + self, par, value=None, name=None, fixed=False, tag=None, tags=[] + ): """Add a variable to be refined. par -- A Parameter that will be varied during a fit. @@ -448,10 +467,10 @@ def addVar(self, par, value = None, name = None, fixed = False, tag = None, name = name or par.name if par.const: - raise ValueError("The parameter '%s' is constant"%par) + raise ValueError("The parameter '%s' is constant" % par) if par.constrained: - raise ValueError("The parameter '%s' is constrained"%par) + raise ValueError("The parameter '%s' is constrained" % par) var = ParameterProxy(name, par) if value is not None: @@ -487,19 +506,18 @@ def delVar(self, var): def __delattr__(self, name): if name in self._parameters: - self.delVar( self._parameters[name] ) + self.delVar(self._parameters[name]) return super(FitRecipe, self).__delattr__(name) return - - def newVar(self, name, value = None, fixed = False, tag = None, tags = []): + def newVar(self, name, value=None, fixed=False, tag=None, tags=[]): """Create a new variable of the fit. This method lets new variables be created that are not tied to a Parameter. Orphan variables may cause a fit to fail, depending on the optimization routine, and therefore should only be created to be used - in contraint or restraint equations. + in constraint or restraint equations. name -- The name of the variable. The variable will be able to be used by this name in restraint and constraint equations. @@ -543,7 +561,6 @@ def _newParameter(self, name, value, check=True): self.fix(par.name) return par - def __getVarAndCheck(self, var): """Get the actual variable from var. @@ -563,30 +580,32 @@ def __getVarAndCheck(self, var): def __getVarsFromArgs(self, *args, **kw): """Get a list of variables from passed arguments. - This method accepts string or variable arguments. An argument of "all" - selects all variables. Keyword arguments must be parameter names, - followed by a value to assign to the fixed variable. This method is - used by the fix and free methods. + This method accepts string or variable arguments. An argument of + "all" selects all variables. Keyword arguments must be parameter + names, followed by a value to assign to the fixed variable. This + method is used by the fix and free methods. - Raises ValueError if an unknown variable, name or tag is passed, or if - a tag is passed in a keyword. + Raises ValueError if an unknown variable, name or tag is passed, + or if a tag is passed in a keyword. """ # Process args. Each variable is tagged with its name, so this is easy. - strargs = set([arg for arg in args if isinstance(arg, six.string_types)]) + strargs = set( + [arg for arg in args if isinstance(arg, six.string_types)] + ) varargs = set(args) - strargs # Check that the tags are valid alltags = set(self._tagmanager.alltags()) badtags = strargs - alltags if badtags: names = ",".join(badtags) - raise ValueError("Variables or tags cannot be found (%s)"% names) + raise ValueError("Variables or tags cannot be found (%s)" % names) # Check that variables are valid allvars = set(self._parameters.values()) badvars = varargs - allvars if badvars: names = ",".join(v.name for v in badvars) - raise ValueError("Variables cannot be found (%s)"% names) + raise ValueError("Variables cannot be found (%s)" % names) # Make sure that we only have parameters in kw kwnames = set(kw.keys()) @@ -594,7 +613,7 @@ def __getVarsFromArgs(self, *args, **kw): badkw = kwnames - allnames if badkw: names = ",".join(badkw) - raise ValueError("Tags cannot be passed as keywords (%s)"% names) + raise ValueError("Tags cannot be passed as keywords (%s)" % names) # Now get all the objects referred to in the arguments. varargs |= self._tagmanager.union(*strargs) @@ -606,17 +625,16 @@ def fix(self, *args, **kw): A fixed variable is not refined. Variables are free by default. - This method accepts string or variable arguments. An argument of "all" - selects all variables. Keyword arguments must be parameter names, - followed by a value to assign to the fixed variable. + This method accepts string or variable arguments. An argument of + "all" selects all variables. Keyword arguments must be parameter + names, followed by a value to assign to the fixed variable. - Raises ValueError if an unknown Parameter, name or tag is passed, or if - a tag is passed in a keyword. + Raises ValueError if an unknown Parameter, name or tag is + passed, or if a tag is passed in a keyword. """ # Check the inputs and get the variables from them varargs = self.__getVarsFromArgs(*args, **kw) - # Fix all of these for var in varargs: self._tagmanager.tag(var, self._fixedtag) @@ -630,15 +648,15 @@ def fix(self, *args, **kw): def free(self, *args, **kw): """Free a parameter by reference, name or tag. - A free variable is refined. Variables are free by default. Constrained - variables are not free. + A free variable is refined. Variables are free by default. + Constrained variables are not free. - This method accepts string or variable arguments. An argument of "all" - selects all variables. Keyword arguments must be parameter names, - followed by a value to assign to the fixed variable. + This method accepts string or variable arguments. An argument of + "all" selects all variables. Keyword arguments must be parameter + names, followed by a value to assign to the fixed variable. - Raises ValueError if an unknown Parameter, name or tag is passed, or if - a tag is passed in a keyword. + Raises ValueError if an unknown Parameter, name or tag is + passed, or if a tag is passed in a keyword. """ # Check the inputs and get the variables from them varargs = self.__getVarsFromArgs(*args, **kw) @@ -656,7 +674,7 @@ def free(self, *args, **kw): def isFree(self, var): """Check if a variable is fixed.""" - return (not self._tagmanager.hasTags(var, self._fixedtag)) + return not self._tagmanager.hasTags(var, self._fixedtag) def unconstrain(self, *pars): """Unconstrain a Parameter. @@ -692,7 +710,7 @@ def unconstrain(self, *pars): return - def constrain(self, par, con, ns = {}): + def constrain(self, par, con, ns={}): """Constrain a parameter to an equation. Note that only one constraint can exist on a Parameter at a time. @@ -722,13 +740,13 @@ def constrain(self, par, con, ns = {}): if par is None: par = ns.get(name) if par is None: - raise ValueError("The parameter '%s' cannot be found"%name) + raise ValueError("The parameter '%s' cannot be found" % name) if con in self._parameters.keys(): con = self._parameters[con] if par.const: - raise ValueError("The parameter '%s' is constant"%par) + raise ValueError("The parameter '%s' is constant" % par) # This will pass the value of a constrained parameter to the initial # value of a parameter constraint. @@ -744,11 +762,11 @@ def constrain(self, par, con, ns = {}): RecipeOrganizer.constrain(self, par, con, ns) return - def getValues(self): """Get the current values of the variables in a list.""" - return array([v.value for v in self._parameters.values() if - self.isFree(v)]) + return array( + [v.value for v in self._parameters.values() if self.isFree(v)] + ) def getNames(self): """Get the names of the variables in a list.""" @@ -757,8 +775,8 @@ def getNames(self): def getBounds(self): """Get the bounds on variables in a list. - Returns a list of (lb, ub) pairs, where lb is the lower bound and ub is - the upper bound. + Returns a list of (lb, ub) pairs, where lb is the lower bound + and ub is the upper bound. """ return [v.bounds for v in self._parameters.values() if self.isFree(v)] @@ -772,7 +790,7 @@ def getBounds2(self): ub = array([b[1] for b in bounds]) return lb, ub - def boundsToRestraints(self, sig = 1, scaled = False): + def boundsToRestraints(self, sig=1, scaled=False): """Turn all bounded parameters into restraints. The bounds become limits on the restraint. @@ -785,13 +803,15 @@ def boundsToRestraints(self, sig = 1, scaled = False): if not hasattr(sig, "__iter__"): sig = [sig] * len(pars) for par, x in zip(pars, sig): - self.restrain(par, par.bounds[0], par.bounds[1], sig = x, - scaled = scaled) + self.restrain( + par, par.bounds[0], par.bounds[1], sig=x, scaled=scaled + ) return def _applyValues(self, p): """Apply variable values to the variables.""" - if len(p) == 0: return + if len(p) == 0: + return vargen = (v for v in self._parameters.values() if self.isFree(v)) for var, pval in zip(vargen, p): var.setValue(pval) @@ -802,4 +822,5 @@ def _updateConfiguration(self): self._ready = False return + # End of file diff --git a/src/diffpy/srfit/fitbase/fitresults.py b/src/diffpy/srfit/fitbase/fitresults.py index e4c4f1e9..26c32f5b 100644 --- a/src/diffpy/srfit/fitbase/fitresults.py +++ b/src/diffpy/srfit/fitbase/fitresults.py @@ -12,12 +12,11 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The FitResults and ContributionResults classes for storing results of a fit. -The FitResults class is used to display the current state of a FitRecipe. It -stores the state, and uses it to calculate useful statistics, which can be -displayed on screen or saved to file. +The FitResults class is used to display the current state of a +FitRecipe. It stores the state, and uses it to calculate useful +statistics, which can be displayed on screen or saved to file. """ from __future__ import print_function @@ -25,12 +24,13 @@ __all__ = ["FitResults", "ContributionResults", "initializeRecipe"] import re -import numpy from collections import OrderedDict -from diffpy.srfit.util.inpututils import inputToString +import numpy + from diffpy.srfit.util import _DASHEDLINE from diffpy.srfit.util import sortKeyForNumericString as numstr +from diffpy.srfit.util.inpututils import inputToString class FitResults(object): @@ -69,8 +69,7 @@ class FitResults(object): the update method is called. """ - def __init__(self, recipe, update = True, showfixed = True, showcon = - False): + def __init__(self, recipe, update=True, showfixed=True, showcon=False): """Initialize the attributes. recipe -- The recipe containing the results @@ -137,15 +136,18 @@ def update(self): self._calculateCovariance() # Get the variable uncertainties - self.varunc = [self.cov[i,i]**0.5 for i in \ - range(len(self.varnames))] + self.varunc = [ + self.cov[i, i] ** 0.5 for i in range(len(self.varnames)) + ] # Get the constraint uncertainties self._calculateConstraintUncertainties() # Store the fitting arrays and metrics for each FitContribution. self.conresults = OrderedDict() - for con, weight in zip(recipe._contributions.values(), recipe._weights): + for con, weight in zip( + recipe._contributions.values(), recipe._weights + ): self.conresults[con.name] = ContributionResults(con, weight, self) # Calculate the metrics @@ -153,7 +155,7 @@ def update(self): self.residual = numpy.dot(res, res) self._calculateMetrics() - # Calcualte the restraints penalty + # Calculate the restraints penalty w = self.chi2 / len(res) self.penalty = sum([r.penalty(w) for r in recipe._restraintlist]) @@ -167,8 +169,8 @@ def _calculateCovariance(self): """ try: J = self._calculateJacobian() - u,s,vh = numpy.linalg.svd(J,0) - self.cov = numpy.dot(vh.T.conj()/s**2,vh) + u, s, vh = numpy.linalg.svd(J, 0) + self.cov = numpy.dot(vh.T.conj() / s**2, vh) except numpy.linalg.LinAlgError: self.messages.append("Cannot compute covariance matrix.") l = len(self.varvals) @@ -178,14 +180,14 @@ def _calculateCovariance(self): def _calculateJacobian(self): """Calculate the Jacobian for the fitting. - Adapted from PARK. Returns the derivative wrt the fit variables at - point p. + Adapted from PARK. Returns the derivative wrt the fit variables + at point p. - This also calculates the derivatives of the constrained parameters - while we're at it. + This also calculates the derivatives of the constrained + parameters while we're at it. - Numeric derivatives are calculated based on step, where step is the - portion of variable value. E.g. step = dv/v. + Numeric derivatives are calculated based on step, where step is + the portion of variable value. E.g. step = dv/v. """ recipe = self.recipe step = self.derivstep @@ -203,7 +205,7 @@ def _calculateJacobian(self): # The list of constraint derivatives with respect to variables # The forward difference would be faster, but perhaps not as accurate. conr = [] - for k,v in enumerate(pvals): + for k, v in enumerate(pvals): h = delta[k] pvals[k] = v + h rk = self.recipe.residual(pvals) @@ -223,14 +225,14 @@ def _calculateJacobian(self): val = con.par.getValue() if numpy.isscalar(val): cond[i] -= con.par.getValue() - cond[i] /= 2*h + cond[i] /= 2 * h else: cond[i] = 0.0 conr.append(cond) pvals[k] = v - r.append(rk/(2*h)) + r.append(rk / (2 * h)) # Reset the constrained parameters to their original values for con in recipe._oconstraints: @@ -290,7 +292,7 @@ def _calculateConstraintUncertainties(self): self.conunc.append(sig2c**0.5) return - def formatResults(self, header = "", footer = "", update = False): + def formatResults(self, header="", footer="", update=False): """Format the results and return them in a string. This function is called by printResults and saveResults. Overloading @@ -308,7 +310,7 @@ def formatResults(self, header = "", footer = "", update = False): lines = [] corrmin = 0.25 p = self.precision - pe = "%-" + "%i.%ie" % (p+6, p) + pe = "%-" + "%i.%ie" % (p + 6, p) pet = "%" + ".%ie" % (p,) # Check to see if the uncertainty values are reliable. certain = True @@ -335,12 +337,14 @@ def formatResults(self, header = "", footer = "", update = False): lines.append(l) lines.append(_DASHEDLINE) formatstr = "%-14s %.8f" - lines.append(formatstr%("Residual",self.residual)) - lines.append(formatstr%("Contributions", self.residual - self.penalty)) - lines.append(formatstr%("Restraints", self.penalty)) - lines.append(formatstr%("Chi2",self.chi2)) - lines.append(formatstr%("Reduced Chi2",self.rchi2)) - lines.append(formatstr%("Rw",self.rw)) + lines.append(formatstr % ("Residual", self.residual)) + lines.append( + formatstr % ("Contributions", self.residual - self.penalty) + ) + lines.append(formatstr % ("Restraints", self.penalty)) + lines.append(formatstr % ("Chi2", self.chi2)) + lines.append(formatstr % ("Reduced Chi2", self.rchi2)) + lines.append(formatstr % ("Rw", self.rw)) ## Per-FitContribution results if len(self.conresults) > 1: @@ -357,12 +361,12 @@ def formatResults(self, header = "", footer = "", update = False): for name in keys: res = self.conresults[name] lines.append("") - namestr = name + " (%f)"%res.weight + namestr = name + " (%f)" % res.weight lines.append(namestr) - lines.append("-"*len(namestr)) - lines.append(formatstr%("Residual",res.residual)) - lines.append(formatstr%("Chi2",res.chi2)) - lines.append(formatstr%("Rw",res.rw)) + lines.append("-" * len(namestr)) + lines.append(formatstr % ("Residual", res.residual)) + lines.append(formatstr % ("Chi2", res.chi2)) + lines.append(formatstr % ("Rw", res.rw)) ## The variables if self.varnames: @@ -370,7 +374,7 @@ def formatResults(self, header = "", footer = "", update = False): l = "Variables" if not certain: m = "Uncertainties invalid" - l += " (%s)"%m + l += " (%s)" % m lines.append(l) lines.append(_DASHEDLINE) @@ -380,11 +384,11 @@ def formatResults(self, header = "", footer = "", update = False): varlines = [] w = max(map(len, varnames)) - w = str(w+1) + w = str(w + 1) # Format the lines - formatstr = "%-"+w+"s " + pe + " +/- " + pet + formatstr = "%-" + w + "s " + pe + " +/- " + pet for name, val, unc in zip(varnames, varvals, varunc): - varlines.append(formatstr%(name, val, unc)) + varlines.append(formatstr % (name, val, unc)) varlines.sort() lines.extend(varlines) @@ -396,14 +400,13 @@ def formatResults(self, header = "", footer = "", update = False): lines.append("Fixed Variables") lines.append(_DASHEDLINE) w = max(map(len, self.fixednames)) - w = str(w+1) - formatstr = "%-"+w+"s " + pet + w = str(w + 1) + formatstr = "%-" + w + "s " + pet for name, val in zip(self.fixednames, self.fixedvals): - varlines.append(formatstr%(name, val)) + varlines.append(formatstr % (name, val)) varlines.sort() lines.extend(varlines) - ## The constraints if self.connames and self.showcon: lines.append("") @@ -427,16 +430,16 @@ def formatResults(self, header = "", footer = "", update = False): vals[name] = (val, unc) keys.sort(key=numstr) - w = str(w+1) - formatstr = "%-"+w+"s %- 15f +/- %-15f" + w = str(w + 1) + formatstr = "%-" + w + "s %- 15f +/- %-15f" for name in keys: val, unc = vals[name] - lines.append(formatstr%(name, val, unc)) + lines.append(formatstr % (name, val, unc)) ## Variable correlations lines.append("") - corint = int(corrmin*100) - l = "Variable Correlations greater than %i%%"%corint + corint = int(corrmin * 100) + l = "Variable Correlations greater than %i%%" % corint if not certain: l += " (Correlations invalid)" lines.append(l) @@ -446,33 +449,32 @@ def formatResults(self, header = "", footer = "", update = False): n = len(self.varnames) for i in range(n): for j in range(i + 1, n): - name = "corr(%s, %s)"%(varnames[i], varnames[j]) - val = (self.cov[i,j]/(self.cov[i,i] * self.cov[j,j])**0.5) + name = "corr(%s, %s)" % (varnames[i], varnames[j]) + val = self.cov[i, j] / (self.cov[i, i] * self.cov[j, j]) ** 0.5 if abs(val) > corrmin: cornames.append(name) tup.append((val, name)) - tup.sort(key=lambda vn : abs(vn[0])) + tup.sort(key=lambda vn: abs(vn[0])) tup.reverse() if cornames: w = max(map(len, cornames)) w = str(w + 1) - formatstr = "%-"+w+"s %.4f" + formatstr = "%-" + w + "s %.4f" for val, name in tup: - lines.append(formatstr%(name, val)) + lines.append(formatstr % (name, val)) else: - lines.append("No correlations greater than %i%%"%corint) - + lines.append("No correlations greater than %i%%" % corint) # User-defined footer if footer: lines.append(footer) - out = "\n".join(lines) + '\n' + out = "\n".join(lines) + "\n" return out - def printResults(self, header = "", footer = "", update = False): + def printResults(self, header="", footer="", update=False): """Format and print the results. header -- A header to add to the output (default "") @@ -485,8 +487,7 @@ def printResults(self, header = "", footer = "", update = False): def __str__(self): return self.formatResults() - - def saveResults(self, filename, header = "", footer = "", update = False): + def saveResults(self, filename, header="", footer="", update=False): """Format and save the results. filename - Name of the save file. @@ -495,20 +496,23 @@ def saveResults(self, filename, header = "", footer = "", update = False): update -- Flag indicating whether to call update() (default False). """ # Save the time and user - from time import ctime from getpass import getuser + from time import ctime + myheader = "Results written: " + ctime() + "\n" myheader += "produced by " + getuser() + "\n" header = myheader + header res = self.formatResults(header, footer, update) - f = open(filename, 'w') + f = open(filename, "w") f.write(res) f.close() return + # End class FitResults + class ContributionResults(object): """Class for processing, storing FitContribution results. @@ -596,17 +600,18 @@ def _init(self, con, weight, fitres): # FIXME: factor rw, chi2, cumrw, cumchi2 to separate functions. def _calculateMetrics(self): - """Calculte chi2 and Rw of the recipe.""" + """Calculate chi2 and Rw of the recipe.""" # We take absolute values in case the signal is complex num = numpy.abs(self.y - self.ycalc) y = numpy.abs(self.y) - chiv = num/self.dy + chiv = num / self.dy self.cumchi2 = numpy.cumsum(chiv**2) # avoid index error for empty array self.chi2 = self.cumchi2[-1:].sum() yw = y / self.dy yw2tot = numpy.dot(yw, yw) - if yw2tot == 0.0: yw2tot = 1.0 + if yw2tot == 0.0: + yw2tot = 1.0 self.cumrw = numpy.sqrt(self.cumchi2 / yw2tot) # avoid index error for empty array self.rw = self.cumrw[-1:].sum() @@ -615,6 +620,7 @@ def _calculateMetrics(self): # End class ContributionResults + def resultsDictionary(results): """Get dictionary of results from file. @@ -626,8 +632,10 @@ def resultsDictionary(results): """ resstr = inputToString(results) - rx = {'f' : r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?", - 'n' : r'[a-zA-Z_]\w*'} + rx = { + "f": r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?", + "n": r"[a-zA-Z_]\w*", + } pat = r"(%(n)s)\s+(%(f)s)" % rx matches = re.findall(pat, resstr) @@ -636,6 +644,7 @@ def resultsDictionary(results): mpairs = dict(matches) return mpairs + def initializeRecipe(recipe, results): """Initialize the variables of a recipe from a results file. diff --git a/src/diffpy/srfit/fitbase/parameter.py b/src/diffpy/srfit/fitbase/parameter.py index 6b703336..21625b6d 100644 --- a/src/diffpy/srfit/fitbase/parameter.py +++ b/src/diffpy/srfit/fitbase/parameter.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Parameter classes. Parameters encapsulate an adjustable parameter within SrFit. @@ -29,12 +28,12 @@ import numpy -from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.equation.literals import Argument -from diffpy.srfit.util.nameutils import validateName -from diffpy.srfit.util.argbinders import bind2nd -from diffpy.srfit.interface import _parameter_interface +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.validatable import Validatable +from diffpy.srfit.interface import _parameter_interface +from diffpy.srfit.util.argbinders import bind2nd +from diffpy.srfit.util.nameutils import validateName class Parameter(_parameter_interface, Argument, Validatable): @@ -52,7 +51,7 @@ class Parameter(_parameter_interface, Argument, Validatable): FitRecipe.getBounds and FitRecipe.boundsToRestraints. """ - def __init__(self, name, value = None, const = False): + def __init__(self, name, value=None, const=False): """Initialization. name -- The name of this Parameter (must be a valid attribute @@ -83,7 +82,7 @@ def setValue(self, val): Argument.setValue(self, val) return self - def setConst(self, const = True, value = None): + def setConst(self, const=True, value=None): """Toggle the Parameter as constant. const -- Flag indicating if the parameter is constant (default @@ -99,7 +98,6 @@ def setConst(self, const = True, value = None): self.setValue(value) return self - def boundRange(self, lb=None, ub=None): """Set lower and upper bound of the Parameter. @@ -114,8 +112,7 @@ def boundRange(self, lb=None, ub=None): self.bounds[1] = ub return self - - def boundWindow(self, lr = 0, ur = None): + def boundWindow(self, lr=0, ur=None): """Create bounds centered on the current value of the Parameter. lr -- The radius of the lower bound (default 0). The lower bound is @@ -142,11 +139,13 @@ def _validate(self): Raises SrFitError if validation fails. """ if self.value is None: - raise SrFitError("value of '%s' is None"%self.name) + raise SrFitError("value of '%s' is None" % self.name) return + # End class Parameter + class ParameterProxy(Parameter): """A Parameter proxy for another parameter. @@ -158,7 +157,6 @@ class ParameterProxy(Parameter): par -- The Parameter this is a proxy for. """ - def __init__(self, name, par): """Initialization. @@ -185,13 +183,13 @@ def constrained(self, value): self.par.constrained = bool(value) return - @property def bounds(self): """List of lower and upper bounds of the proxied Parameter. - This can be used by some optimizers when the Parameter is varied. See - FitRecipe.getBounds and FitRecipe.boundsToRestraints. + This can be used by some optimizers when the Parameter is + varied. See FitRecipe.getBounds and + FitRecipe.boundsToRestraints. """ return self.par.bounds @@ -200,7 +198,6 @@ def bounds(self, value): self.par.bounds = value return - @property def _observers(self): return self.par._observers @@ -211,27 +208,22 @@ def _observers(self): def setValue(self, val): return self.par.setValue(val) - @wraps(Parameter.getValue) def getValue(self): return self.par.getValue() - @wraps(Parameter.setConst) def setConst(self, const=True, value=None): return self.par.setConst(const, value) - @wraps(Parameter.boundRange) def boundRange(self, lb=None, ub=None): return self.par.boundRange(lb, ub) - @wraps(Parameter.boundWindow) def boundWindow(self, lr=0, ur=None): return self.par.boundWindow(lr, ur) - def _validate(self): """Validate my state. @@ -244,6 +236,7 @@ def _validate(self): self.par._validate() return + # End class ParameterProxy @@ -254,7 +247,7 @@ class ParameterAdapter(Parameter): methods defer to the data of the wrapped object. """ - def __init__(self, name, obj, getter = None, setter = None, attr = None): + def __init__(self, name, obj, getter=None, setter=None, attr=None): """Wrap an object as a Parameter. name -- The name of this Parameter. @@ -314,6 +307,7 @@ def setValue(self, value): self.notify() return self + # End class ParameterAdapter # End of file diff --git a/src/diffpy/srfit/fitbase/parameterset.py b/src/diffpy/srfit/fitbase/parameterset.py index 0ae80a48..69efaa7c 100644 --- a/src/diffpy/srfit/fitbase/parameterset.py +++ b/src/diffpy/srfit/fitbase/parameterset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """ParameterSet class. ParameterSets organize Parameters, Constraints, Restraints and other @@ -94,7 +93,7 @@ def removeParameterSet(self, parset): self._removeObject(parset, self._parsets) return - def setConst(self, const = True): + def setConst(self, const=True): """Set every parameter within the set to a constant. const -- Flag indicating if the parameter is constant (default @@ -105,6 +104,7 @@ def setConst(self, const = True): return + # End class ParameterSet # End of file diff --git a/src/diffpy/srfit/fitbase/profile.py b/src/diffpy/srfit/fitbase/profile.py index 11c6ea57..0da465fb 100644 --- a/src/diffpy/srfit/fitbase/profile.py +++ b/src/diffpy/srfit/fitbase/profile.py @@ -12,24 +12,23 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The Profile class containing the physical and calculated data. -Profile holds the arrays representing an observed profile, a selected subset of -the observed profile and a calculated profile. Profiles are used by Calculators -to store a calculated signal, and by FitContributions to help calculate a -residual equation. +Profile holds the arrays representing an observed profile, a selected +subset of the observed profile and a calculated profile. Profiles are +used by Calculators to store a calculated signal, and by +FitContributions to help calculate a residual equation. """ __all__ = ["Parameter", "Profile"] -import six import numpy +import six -from diffpy.srfit.util.observable import Observable +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.parameter import Parameter from diffpy.srfit.fitbase.validatable import Validatable -from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.util.observable import Observable # This is the roundoff tolerance for selecting bounds on arrays. epsilon = 1e-8 @@ -87,19 +86,27 @@ def __init__(self): return # We want x, y, ycalc and dy to stay in-sync with xpar, ypar and dypar - x = property( lambda self : self.xpar.getValue(), - lambda self, val : self.xpar.setValue(val) ) - y = property( lambda self : self.ypar.getValue(), - lambda self, val : self.ypar.setValue(val) ) - dy = property( lambda self : self.dypar.getValue(), - lambda self, val : self.dypar.setValue(val) ) - ycalc = property( lambda self : self.ycpar.getValue(), - lambda self, val : self.ycpar.setValue(val) ) + x = property( + lambda self: self.xpar.getValue(), + lambda self, val: self.xpar.setValue(val), + ) + y = property( + lambda self: self.ypar.getValue(), + lambda self, val: self.ypar.setValue(val), + ) + dy = property( + lambda self: self.dypar.getValue(), + lambda self, val: self.dypar.setValue(val), + ) + ycalc = property( + lambda self: self.ycpar.getValue(), + lambda self, val: self.ycpar.setValue(val), + ) # We want xobs, yobs and dyobs to be read-only - xobs = property( lambda self: self._xobs ) - yobs = property( lambda self: self._yobs ) - dyobs = property( lambda self: self._dyobs ) + xobs = property(lambda self: self._xobs) + yobs = property(lambda self: self._yobs) + dyobs = property(lambda self: self._dyobs) def loadParsedData(self, parser): """Load parsed data from a ProfileParser. @@ -111,7 +118,7 @@ def loadParsedData(self, parser): self.setObservedProfile(x, y, dy) return - def setObservedProfile(self, xobs, yobs, dyobs = None): + def setObservedProfile(self, xobs, yobs, dyobs=None): """Set the observed profile. Arguments @@ -179,29 +186,37 @@ def setCalculationRange(self, xmin=None, xmax=None, dx=None): """ if self.xobs is None: raise AttributeError("No observed profile") + # local helper function def _isobs(a): if not isinstance(a, six.string_types): return False - if a != 'obs': + if a != "obs": raise ValueError('Must be either float or "obs".') return True + # resolve new low and high bounds for x - lo = (self.x[0] if xmin is None else - self.xobs[0] if _isobs(xmin) else float(xmin)) + lo = ( + self.x[0] + if xmin is None + else self.xobs[0] if _isobs(xmin) else float(xmin) + ) lo = max(lo, self.xobs[0]) - hi = (self.x[-1] if xmax is None else - self.xobs[-1] if _isobs(xmax) else float(xmax)) + hi = ( + self.x[-1] + if xmax is None + else self.xobs[-1] if _isobs(xmax) else float(xmax) + ) hi = min(hi, self.xobs[-1]) # determine if we need to clip the original grid clip = True step = None ncur = len(self.x) - stepcur = (1 if ncur < 2 - else (self.x[-1] - self.x[0]) / (ncur - 1.0)) + stepcur = 1 if ncur < 2 else (self.x[-1] - self.x[0]) / (ncur - 1.0) nobs = len(self.xobs) - stepobs = (1 if nobs < 2 - else (self.xobs[-1] - self.xobs[0]) / (nobs - 1.0)) + stepobs = ( + 1 if nobs < 2 else (self.xobs[-1] - self.xobs[0]) / (nobs - 1.0) + ) if dx is None: # check if xobs overlaps with x i0 = numpy.fabs(self.xobs - self.x[0]).argmin() @@ -241,7 +256,6 @@ def _isobs(a): self.setCalculationPoints(x1) return - def setCalculationPoints(self, x): """Set the calculation points. @@ -254,8 +268,8 @@ def setCalculationPoints(self, x): """ x = numpy.asarray(x) if self.xobs is not None: - x = x[ x >= self.xobs[0] - epsilon ] - x = x[ x <= self.xobs[-1] + epsilon ] + x = x[x >= self.xobs[0] - epsilon] + x = x[x <= self.xobs[-1] + epsilon] self.x = x if self.yobs is not None: self.y = rebinArray(self.yobs, self.xobs, self.x) @@ -264,8 +278,8 @@ def setCalculationPoints(self, x): if (self.dyobs == 1).all(): self.dy = numpy.ones_like(self.x) else: - # FIXME - This does not follow error propogation rules and it - # introduces (more) correlation between the data points. + # FIXME - This does not follow error propagation rules and it + # introduces (more) correlation between the data points. self.dy = rebinArray(self.dyobs, self.xobs, self.x) return @@ -273,13 +287,14 @@ def setCalculationPoints(self, x): def loadtxt(self, *args, **kw): """Use numpy.loadtxt to load data. - Arguments are passed to numpy.loadtxt. unpack = True is enforced. - The first two arrays returned by numpy.loadtxt are assumed to be x and y. - If there is a third array, it is assumed to by dy. Any other arrays are - ignored. These are passed to setObservedProfile. + Arguments are passed to numpy.loadtxt. unpack = True is + enforced. The first two arrays returned by numpy.loadtxt are + assumed to be x and y. If there is a third array, it is assumed + to by dy. Any other arrays are ignored. These are passed to + setObservedProfile. - Raises ValueError if the call to numpy.loadtxt returns fewer than 2 - arrays. + Raises ValueError if the call to numpy.loadtxt returns fewer + than 2 arrays. Returns the x, y and dy arrays loaded from the file """ @@ -303,7 +318,6 @@ def loadtxt(self, *args, **kw): self.setObservedProfile(x, y, dy) return x, y, dy - def savetxt(self, fname, **kwargs): """Call `numpy.savetxt` with x, ycalc, y, dy. @@ -331,12 +345,11 @@ def savetxt(self, fname, **kwargs): raise SrFitError("ycalc is None") y = self.y dy = self.dy - kwargs.setdefault('header', 'x ycalc y dy') + kwargs.setdefault("header", "x ycalc y dy") data = numpy.transpose([x, ycalc, y, dy]) numpy.savetxt(fname, data, **kwargs) return - def _flush(self, other): """Invalidate cached state. @@ -349,13 +362,22 @@ def _flush(self, other): def _validate(self): """Validate my state. - This validates that x, y, dy, xobx, yobs and dyobs are not None. This - validates that x, y, and dy are the same length. + This validates that x, y, dy, xobx, yobs and dyobs are not None. + This validates that x, y, and dy are the same length. Raises SrFitError if validation fails. """ - datanotset = any(v is None for v in - [self.x, self.y, self.dy, self.xobs, self.yobs, self.dyobs]) + datanotset = any( + v is None + for v in [ + self.x, + self.y, + self.dy, + self.xobs, + self.yobs, + self.dyobs, + ] + ) if datanotset: raise SrFitError("Missing data") if len(self.x) != len(self.y) or len(self.x) != len(self.dy): @@ -365,6 +387,7 @@ def _validate(self): # End class Profile + def rebinArray(A, xold, xnew): """Rebin the an array by interpolating over the new x range. diff --git a/src/diffpy/srfit/fitbase/profilegenerator.py b/src/diffpy/srfit/fitbase/profilegenerator.py index 46d7fe35..c1f0d2e7 100644 --- a/src/diffpy/srfit/fitbase/profilegenerator.py +++ b/src/diffpy/srfit/fitbase/profilegenerator.py @@ -12,41 +12,33 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """The ProfileGenerator class for generating a profile. ProfileGenerators encapsulate the evaluation and required Parameters and -ParameterSets of a profile calculator. The ProfileGenerator class can be -associated with a FitContribution to help calculate a profile. - -To define a ProfileGenerator, one must implement the required Parameters and -ParameterSets as well as overload the __call__ method with the calculation. A -very simple example is -> class Gaussian(ProfileGenerator): -> -> def __init__(self): -> # Initialize and give this a name -> ProfileGenerator.__init__(self, "g") -> # Add amplitude, center and width parameters -> self.newParameter("amp", 0) -> self.newParameter("center", 0) -> self.newParameter("width", 0) -> -> def __call__(self, x): -> a = self.amp.getValue() -> x0 = self.center.getValue() -> w = self.width.getValue() -> return a * exp(-0.5*((x-x0)/w)**2) - -More examples can be found in the example directory of the documentation. +ParameterSets of a profile calculator. The ProfileGenerator class can +be associated with a FitContribution to help calculate a profile. + +To define a ProfileGenerator, one must implement the required Parameters +and ParameterSets as well as overload the __call__ method with the +calculation. A very simple example is > class +Gaussian(ProfileGenerator): > > def __init__(self): > # +Initialize and give this a name > ProfileGenerator.__init__(self, +"g") > # Add amplitude, center and width parameters > +self.newParameter("amp", 0) > self.newParameter("center", 0) > +self.newParameter("width", 0) > > def __call__(self, x): > a = +self.amp.getValue() > x0 = self.center.getValue() > w = +self.width.getValue() > return a * exp(-0.5*((x-x0)/w)**2) + +More examples can be found in the example directory of the +documentation. """ __all__ = ["ProfileGenerator"] from diffpy.srfit.equation.literals.operators import Operator -from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.parameterset import ParameterSet class ProfileGenerator(Operator, ParameterSet): @@ -95,7 +87,6 @@ class ProfileGenerator(Operator, ParameterSet): nin = 0 nout = 1 - def __init__(self, name): """Initialize the attributes.""" Operator.__init__(self) @@ -104,7 +95,6 @@ def __init__(self, name): self.meta = {} return - @property def symbol(self): return self.name @@ -116,7 +106,8 @@ def __call__(self, x): This method must be overloaded. - This method only takes the independent variables to calculate over. + This method only takes the independent variables to calculate + over. """ return x @@ -130,7 +121,6 @@ def operation(self): y = self.__call__(self.profile.x) return y - def setProfile(self, profile): """Assign the profile. @@ -145,15 +135,16 @@ def setProfile(self, profile): self._flush(other=(self,)) # Merge the profiles metadata with our own - self.meta.update( self.profile.meta ) + self.meta.update(self.profile.meta) self.processMetaData() return def processMetaData(self): """Process the metadata. - This can be used to configure a ProfileGenerator upon a change in the - metadata. This method gets called whenever the Profile is set. + This can be used to configure a ProfileGenerator upon a change + in the metadata. This method gets called whenever the Profile is + set. """ return @@ -161,8 +152,9 @@ def _validate(self): """Validate my state. This performs profile validations. This performs ParameterSet - validations. This does not validate the operation, since this could be - costly. The operation should be validated with a containing equation. + validations. This does not validate the operation, since this + could be costly. The operation should be validated with a + containing equation. Raises SrFitError if validation fails. """ diff --git a/src/diffpy/srfit/fitbase/profileparser.py b/src/diffpy/srfit/fitbase/profileparser.py index bd17d4de..c38af1a0 100644 --- a/src/diffpy/srfit/fitbase/profileparser.py +++ b/src/diffpy/srfit/fitbase/profileparser.py @@ -12,12 +12,12 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """This module contains classes for parsing profiles from files. -ProfileParser is a base class for parsing data. It can interact with a Profile -object to automatically set the Profile's data and metadata. Each specific file -format must be encapsulated in a ProfileParser subclass. +ProfileParser is a base class for parsing data. It can interact with a +Profile object to automatically set the Profile's data and metadata. +Each specific file format must be encapsulated in a ProfileParser +subclass. See the class documentation for more information. """ @@ -46,7 +46,7 @@ class ProfileParser(object): dy -- A numpy array containing the uncertainty read from the file. This is None if the uncertainty cannot be read. - _x -- Indpendent variable from the chosen bank + _x -- Independent variable from the chosen bank _y -- Profile from the chosen bank _dx -- Uncertainty in independent variable from the chosen bank _dy -- Uncertainty in profile from the chosen bank @@ -102,7 +102,7 @@ def parseFile(self, filename): Raises IOError if the file cannot be read Raises ParseError if the file cannot be parsed """ - infile = open(filename, 'r') + infile = open(filename, "r") self._banks = [] self._meta = {} filestring = infile.read() @@ -151,7 +151,7 @@ def selectBank(self, index): self._x, self._y, self._dx, self._dy = self._banks[index] return - def getData(self, index = None): + def getData(self, index=None): """Get the data. This method should only be called after the data has been parsed. The @@ -174,4 +174,5 @@ def getMetaData(self): """Get the parsed metadata.""" return self._meta + # End of ProfileParser diff --git a/src/diffpy/srfit/fitbase/recipeorganizer.py b/src/diffpy/srfit/fitbase/recipeorganizer.py index b31df9b4..e4902734 100644 --- a/src/diffpy/srfit/fitbase/recipeorganizer.py +++ b/src/diffpy/srfit/fitbase/recipeorganizer.py @@ -12,37 +12,35 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Base classes and tools for constructing a FitRecipe. RecipeContainer is the base class for organizing Parameters, and other RecipeContainers. RecipeOrganizer is an extended RecipeContainer that -incorporates equation building, constraints and Restraints. equationFromString -creates an Equation instance from a string. +incorporates equation building, constraints and Restraints. +equationFromString creates an Equation instance from a string. """ __all__ = ["RecipeContainer", "RecipeOrganizer", "equationFromString"] -from numpy import inf +import re from collections import OrderedDict from itertools import chain, groupby -import re import six +from numpy import inf +from diffpy.srfit.equation import Equation +from diffpy.srfit.equation.builder import EquationFactory +from diffpy.srfit.fitbase.configurable import Configurable from diffpy.srfit.fitbase.constraint import Constraint -from diffpy.srfit.fitbase.restraint import Restraint from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.fitbase.configurable import Configurable +from diffpy.srfit.fitbase.restraint import Restraint from diffpy.srfit.fitbase.validatable import Validatable - -from diffpy.srfit.util.observable import Observable -from diffpy.srfit.equation import Equation -from diffpy.srfit.equation.builder import EquationFactory -from diffpy.srfit.util.nameutils import validateName from diffpy.srfit.interface import _recipeorganizer_interface from diffpy.srfit.util import _DASHEDLINE from diffpy.srfit.util import sortKeyForNumericString as numstr +from diffpy.srfit.util.nameutils import validateName +from diffpy.srfit.util.observable import Observable class RecipeContainer(Observable, Configurable, Validatable): @@ -109,7 +107,6 @@ def _iterManaged(self): """Get iterator over managed objects.""" return chain(*(d.values() for d in self.__managed)) - def iterPars(self, pattern="", recurse=True): """Iterate over the Parameters contained in this object. @@ -137,7 +134,6 @@ def iterPars(self, pattern="", recurse=True): yield par return - def __iter__(self): """Iterate over top-level parameters.""" return iter(self._parameters.values()) @@ -158,12 +154,13 @@ def __getattr__(self, name): raise AttributeError(name) return arg - # Ensure there is no __dir__ override in the base class. - assert (getattr(Observable, '__dir__', None) is - getattr(Configurable, '__dir__', None) is - getattr(Validatable, '__dir__', None) is - getattr(object, '__dir__', None)) + assert ( + getattr(Observable, "__dir__", None) + is getattr(Configurable, "__dir__", None) + is getattr(Validatable, "__dir__", None) + is getattr(object, "__dir__", None) + ) def __dir__(self): "Return sorted list of attributes for this object." @@ -175,7 +172,6 @@ def __dir__(self): rv = sorted(rv) return rv - # Needed by __setattr__ _parameters = OrderedDict() __managed = [] @@ -192,30 +188,30 @@ def __setattr__(self, name, value): m = self.get(name) if m is not None: - raise AttributeError("Cannot set '%s'"%name) + raise AttributeError("Cannot set '%s'" % name) super(RecipeContainer, self).__setattr__(name, value) return - def __delattr__(self, name): """Delete parameters with del. - This does not allow deletion of non-parameters, as this may require - configuration changes that are not yet handled in a general way. + This does not allow deletion of non-parameters, as this may + require configuration changes that are not yet handled in a + general way. """ if name in self._parameters: - self._removeParameter( self._parameters[name] ) + self._removeParameter(self._parameters[name]) return m = self.get(name) if m is not None: - raise AttributeError("Cannot delete '%s'"%name) + raise AttributeError("Cannot delete '%s'" % name) super(RecipeContainer, self).__delattr__(name) return - def get(self, name, default = None): + def get(self, name, default=None): """Get a managed object.""" for d in self.__managed: arg = d.get(name) @@ -232,7 +228,7 @@ def getValues(self): """Get the values of managed parameters.""" return [p.value for p in self._parameters.values()] - def _addObject(self, obj, d, check = True): + def _addObject(self, obj, d, check=True): """Add an object to a managed dictionary. obj -- The object to be stored. @@ -253,14 +249,18 @@ def _addObject(self, obj, d, check = True): # Check for extant object in d with same name oldobj = d.get(obj.name) if check and oldobj is not None: - message = "%s with name '%s' already exists"%\ - (obj.__class__.__name__, obj.name) + message = "%s with name '%s' already exists" % ( + obj.__class__.__name__, + obj.name, + ) raise ValueError(message) # Check for object with same name in other dictionary. if oldobj is None and self.get(obj.name) is not None: - message = "Non-%s with name '%s' already exists"%\ - (obj.__class__.__name__, obj.name) + message = "Non-%s with name '%s' already exists" % ( + obj.__class__.__name__, + obj.name, + ) raise ValueError(message) # Detach the old object, if there is one @@ -326,8 +326,8 @@ def _locateManagedObject(self, obj): def _flush(self, other): """Invalidate cached state. - This will force any observer to invalidate its state. By default this - does nothing. + This will force any observer to invalidate its state. By default + this does nothing. """ self.notify(other) return @@ -335,7 +335,8 @@ def _flush(self, other): def _validate(self): """Validate my state. - This validates that contained Parameters and managed objects are valid. + This validates that contained Parameters and managed objects are + valid. Raises AttributeError if validation fails. """ @@ -346,6 +347,7 @@ def _validate(self): # End class RecipeContainer + class RecipeOrganizer(_recipeorganizer_interface, RecipeContainer): """Extended base class for organizing pieces of a FitRecipe. @@ -391,8 +393,8 @@ def __init__(self, name): def _newParameter(self, name, value, check=True): """Add a new Parameter to the container. - This creates a new Parameter and adds it to the container using the - _addParameter method. + This creates a new Parameter and adds it to the container using + the _addParameter method. Returns the Parameter. """ @@ -424,11 +426,11 @@ def _addParameter(self, par, check=True): def _removeParameter(self, par): """Remove a parameter. - This de-registers the Parameter with the _eqfactory. The Parameter will - remain part of built equations. + This de-registers the Parameter with the _eqfactory. The + Parameter will remain part of built equations. - Note that constraints and restraints involving the Parameter are not - modified. + Note that constraints and restraints involving the Parameter are + not modified. Raises ValueError if par is not part of the RecipeOrganizer. """ @@ -436,7 +438,7 @@ def _removeParameter(self, par): self._eqfactory.deRegisterBuilder(par.name) return - def registerCalculator(self, f, argnames = None): + def registerCalculator(self, f, argnames=None): """Register a Calculator so it can be used within equation strings. A Calculator is an elaborate function that can organize Parameters. @@ -456,7 +458,7 @@ def registerCalculator(self, f, argnames = None): if argnames is None: fncode = f.__call__.__func__.__code__ argnames = list(fncode.co_varnames) - argnames = argnames[1:fncode.co_argcount] + argnames = argnames[1 : fncode.co_argcount] for pname in argnames: if pname not in self._eqfactory.builders: @@ -469,7 +471,7 @@ def registerCalculator(self, f, argnames = None): eq = self._eqfactory.makeEquation(f.name) return eq - def registerFunction(self, f, name = None, argnames = None): + def registerFunction(self, f, name=None, argnames=None): """Register a function so it can be used within equation strings. This creates a function with this class that can be used within string @@ -477,7 +479,7 @@ def registerFunction(self, f, name = None, argnames = None): passed in the equation string, as this will be handled automatically. f -- The callable to register. If this is an Equation - instance, then all that needs to be provied is a name. + instance, then all that needs to be provided is a name. name -- The name of the function to be used in equations. If this is None (default), the method will try to determine the name of the function automatically. @@ -523,12 +525,12 @@ def registerFunction(self, f, name = None, argnames = None): fncode = f.__code__ # check class method elif inspect.ismethod(f): - fncode = f.__func__.__code__ - offset = 1 + fncode = f.__func__.__code__ + offset = 1 # check functor - elif hasattr(f, "__call__") and hasattr(f.__call__, '__func__'): - fncode = f.__call__.__func__.__code__ - offset = 1 + elif hasattr(f, "__call__") and hasattr(f.__call__, "__func__"): + fncode = f.__call__.__func__.__code__ + offset = 1 else: m = "Cannot extract name or argnames" raise ValueError(m) @@ -536,14 +538,14 @@ def registerFunction(self, f, name = None, argnames = None): # Extract the name if name is None: name = fncode.co_name - if name == '': + if name == "": m = "You must supply a name name for a lambda function" raise ValueError(m) # Extract the arguments if argnames is None: argnames = list(fncode.co_varnames) - argnames = argnames[offset:fncode.co_argcount] + argnames = argnames[offset : fncode.co_argcount] #### End introspection code @@ -554,6 +556,7 @@ def registerFunction(self, f, name = None, argnames = None): # Initialize and register from diffpy.srfit.fitbase.calculator import Calculator + if isinstance(f, Calculator): for pname in argnames: par = self.get(pname) @@ -567,7 +570,7 @@ def registerFunction(self, f, name = None, argnames = None): return eq - def registerStringFunction(self, fstr, name, ns = {}): + def registerStringFunction(self, fstr, name, ns={}): """Register a string function. This creates a function with this class that can be used within string @@ -589,8 +592,7 @@ def registerStringFunction(self, fstr, name, ns = {}): """ # Build the equation instance. - eq = equationFromString(fstr, self._eqfactory, ns = ns, buildargs = - True) + eq = equationFromString(fstr, self._eqfactory, ns=ns, buildargs=True) eq.name = name # Register any new Parameters. @@ -601,8 +603,7 @@ def registerStringFunction(self, fstr, name, ns = {}): argnames = eq.argdict.keys() return self.registerFunction(eq, name, argnames) - - def evaluateEquation(self, eqstr, ns = {}): + def evaluateEquation(self, eqstr, ns={}): """Evaluate a string equation. eqstr -- A string equation to evaluate. The equation is evaluated at @@ -620,8 +621,7 @@ def evaluateEquation(self, eqstr, ns = {}): self._eqfactory.wipeout(eq) return rv - - def constrain(self, par, con, ns = {}): + def constrain(self, par, con, ns={}): """Constrain a parameter to an equation. Note that only one constraint can exist on a Parameter at a time. @@ -651,16 +651,16 @@ def constrain(self, par, con, ns = {}): raise ValueError("The parameter cannot be found") if par.const: - raise ValueError("The parameter '%s' is constant"%par) + raise ValueError("The parameter '%s' is constant" % par) if isinstance(con, six.string_types): eqstr = con eq = equationFromString(con, self._eqfactory, ns) else: - eq = Equation(root = con) + eq = Equation(root=con) eqstr = con.name - eq.name = "_constraint_%s"%par.name + eq.name = "_constraint_%s" % par.name # Make and store the constraint con = Constraint() @@ -683,7 +683,7 @@ def isConstrained(self, par): name = par par = self.get(name) - return (par in self._constraints) + return par in self._constraints def unconstrain(self, *pars): """Unconstrain a Parameter. @@ -719,16 +719,16 @@ def unconstrain(self, *pars): return - def getConstrainedPars(self, recurse = False): + def getConstrainedPars(self, recurse=False): """Get a list of constrained managed Parameters in this object. - recurse -- Recurse into managed objects and retrive their constrained + recurse -- Recurse into managed objects and retrieve their constrained Parameters as well (default False). """ const = self._getConstraints(recurse) return const.keys() - def clearConstraints(self, recurse = False): + def clearConstraints(self, recurse=False): """Clear all constraints managed by this organizer. recurse -- Recurse into managed objects and clear all constraints @@ -741,13 +741,12 @@ def clearConstraints(self, recurse = False): self.unconstrain(*self._constraints) if recurse: - f = lambda m : hasattr(m, "clearConstraints") + f = lambda m: hasattr(m, "clearConstraints") for m in filter(f, self._iterManaged()): m.clearConstraints(recurse) return - def restrain(self, res, lb = -inf, ub = inf, sig = 1, scaled = False, ns = - {}): + def restrain(self, res, lb=-inf, ub=inf, sig=1, scaled=False, ns={}): """Restrain an expression to specified bounds. res -- An equation string or Parameter to restrain. @@ -763,7 +762,7 @@ def restrain(self, res, lb = -inf, ub = inf, sig = 1, scaled = False, ns = The penalty is calculated as (max(0, lb - val, val - ub)/sig)**2 - and val is the value of the calculated equation. This is multipled by + and val is the value of the calculated equation. This is multiplied by the average chi^2 if scaled is True. Raises ValueError if ns uses a name that is already used for a @@ -778,7 +777,7 @@ def restrain(self, res, lb = -inf, ub = inf, sig = 1, scaled = False, ns = eqstr = res eq = equationFromString(res, self._eqfactory, ns) else: - eq = Equation(root = res) + eq = Equation(root=res) eqstr = res.name # Make and store the restraint @@ -816,7 +815,7 @@ def unrestrain(self, *ress): return - def clearRestraints(self, recurse = False): + def clearRestraints(self, recurse=False): """Clear all restraints. recurse -- Recurse into managed objects and clear all restraints @@ -825,41 +824,41 @@ def clearRestraints(self, recurse = False): self.unrestrain(*self._restraints) if recurse: - f = lambda m : hasattr(m, "clearRestraints") + f = lambda m: hasattr(m, "clearRestraints") for m in filter(f, self._iterManaged()): m.clearRestraints(recurse) return - def _getConstraints(self, recurse = True): + def _getConstraints(self, recurse=True): """Get the constrained Parameters for this and managed sub-objects.""" constraints = {} if recurse: - f = lambda m : hasattr(m, "_getConstraints") + f = lambda m: hasattr(m, "_getConstraints") for m in filter(f, self._iterManaged()): - constraints.update( m._getConstraints(recurse) ) + constraints.update(m._getConstraints(recurse)) - constraints.update( self._constraints) + constraints.update(self._constraints) return constraints - def _getRestraints(self, recurse = True): + def _getRestraints(self, recurse=True): """Get the Restraints for this and embedded ParameterSets. This returns a set of Restraint objects. """ restraints = set(self._restraints) if recurse: - f = lambda m : hasattr(m, "_getRestraints") + f = lambda m: hasattr(m, "_getRestraints") for m in filter(f, self._iterManaged()): - restraints.update( m._getRestraints(recurse) ) + restraints.update(m._getRestraints(recurse)) return restraints def _validate(self): """Validate my state. - This performs RecipeContainer validations. This validates contained - Restraints and Constraints. + This performs RecipeContainer validations. This validates + contained Restraints and Constraints. Raises AttributeError if validation fails. """ @@ -889,9 +888,11 @@ def _formatManaged(self, prefix=""): if self._parameters: w0 = max(len(n) for n in self._parameters) w1 = ((w0 + len(prefix) + 1) // 4 + 1) * 4 - fmt = formatstr.replace('W', str(w1)) - lines.extend(fmt.format(prefix + n, p.value) - for n, p in self._parameters.items()) + fmt = formatstr.replace("W", str(w1)) + lines.extend( + fmt.format(prefix + n, p.value) + for n, p in self._parameters.items() + ) # Recurse into managed objects. for obj in self._iterManaged(): if hasattr(obj, "_formatManaged"): @@ -901,7 +902,6 @@ def _formatManaged(self, prefix=""): lines.extend(tlines) return lines - def _formatConstraints(self): """Format constraints for showing. @@ -927,7 +927,6 @@ def _formatConstraints(self): clines.sort(key=numstr) return clines - def _formatRestraints(self): """Format restraints for showing. @@ -943,13 +942,17 @@ def _formatRestraints(self): rset = self._getRestraints() rlines = [] for res in rset: - line = "%s: lb = %f, ub = %f, sig = %f, scaled = %s"%\ - (res.eqstr, res.lb, res.ub, res.sig, res.scaled) + line = "%s: lb = %f, ub = %f, sig = %f, scaled = %s" % ( + res.eqstr, + res.lb, + res.ub, + res.sig, + res.scaled, + ) rlines.append(line) rlines.sort(key=numstr) return rlines - def show(self, pattern="", textwidth=78): """Show the configuration hierarchy on the screen. @@ -965,8 +968,9 @@ def show(self, pattern="", textwidth=78): the screen width. Do not trim when negative or 0. """ regexp = re.compile(pattern) - pmatch = lambda s : (len(s.split(None, 1)) < 2 or - regexp.search(s.split(None, 1)[0])) + pmatch = lambda s: ( + len(s.split(None, 1)) < 2 or regexp.search(s.split(None, 1)[0]) + ) # Show sub objects and their parameters lines = [] tlines = self._formatManaged() @@ -1007,10 +1011,13 @@ def show(self, pattern="", textwidth=78): print("\n".join(s[:tw] for s in lines)) return + # End RecipeOrganizer -def equationFromString(eqstr, factory, ns = {}, buildargs = False, - argclass = Parameter, argkw = {}): + +def equationFromString( + eqstr, factory, ns={}, buildargs=False, argclass=Parameter, argkw={} +): """Make an equation from a string. eqstr -- A string representation of the equation. The equation must diff --git a/src/diffpy/srfit/fitbase/restraint.py b/src/diffpy/srfit/fitbase/restraint.py index 76e0e7ff..f7f2da3c 100644 --- a/src/diffpy/srfit/fitbase/restraint.py +++ b/src/diffpy/srfit/fitbase/restraint.py @@ -12,21 +12,20 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Restraints class. Restraints are used by RecipeOrganizers to organize restraint equations. -Restraints store an Equation, bounds on its value, and the form of the penalty -function for breaking a restraint. This penalty is added to the residual -equation calculated by a FitRecipe. +Restraints store an Equation, bounds on its value, and the form of the +penalty function for breaking a restraint. This penalty is added to the +residual equation calculated by a FitRecipe. """ __all__ = ["Restraint"] from numpy import inf -from diffpy.srfit.fitbase.validatable import Validatable from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.validatable import Validatable class Restraint(Validatable): @@ -44,11 +43,11 @@ class Restraint(Validatable): The penalty is calculated as (max(0, lb - val, val - ub)/sig)**2 - and val is the value of the calculated equation. This is multipled by the + and val is the value of the calculated equation. This is multiplied by the average chi^2 if scaled is True. """ - def __init__(self, eq, lb = -inf, ub = inf, sig = 1, scaled = False): + def __init__(self, eq, lb=-inf, ub=inf, sig=1, scaled=False): """Restrain an equation to specified bounds. eq -- An equation whose evaluation is compared against the @@ -69,7 +68,7 @@ def __init__(self, eq, lb = -inf, ub = inf, sig = 1, scaled = False): self.scaled = bool(scaled) return - def penalty(self, w = 1.0): + def penalty(self, w=1.0): """Calculate the penalty of the restraint. w -- The point-average chi^2 which is optionally used to scale the @@ -78,7 +77,7 @@ def penalty(self, w = 1.0): Returns the penalty as a float """ val = self.eq() - penalty = (max(0, self.lb - val, val - self.ub) / self.sig)**2 + penalty = (max(0, self.lb - val, val - self.ub) / self.sig) ** 2 if self.scaled: penalty *= w @@ -95,6 +94,7 @@ def _validate(self): if self.eq is None: raise SrFitError("eq is None") from diffpy.srfit.equation.visitors import validate + try: validate(self.eq) except ValueError as e: @@ -111,6 +111,7 @@ def _validate(self): return + # End class Restraint # End of file diff --git a/src/diffpy/srfit/fitbase/simplerecipe.py b/src/diffpy/srfit/fitbase/simplerecipe.py index 27dec0c6..2f454fd2 100644 --- a/src/diffpy/srfit/fitbase/simplerecipe.py +++ b/src/diffpy/srfit/fitbase/simplerecipe.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Simple FitRecipe class that includes a FitContribution and Profile.""" -from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitcontribution import FitContribution +from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitresults import FitResults from diffpy.srfit.fitbase.profile import Profile @@ -66,7 +65,7 @@ class SimpleRecipe(FitRecipe): values -- Variable values (read only). See getValues. """ - def __init__(self, name = "fit", conclass = FitContribution): + def __init__(self, name="fit", conclass=FitContribution): """Initialization.""" FitRecipe.__init__(self, name) self.fithooks[0].verbose = 3 @@ -74,11 +73,14 @@ def __init__(self, name = "fit", conclass = FitContribution): self.profile = Profile() contribution.setProfile(self.profile) self.addContribution(contribution) - self.results = FitResults(self, update = False) + self.results = FitResults(self, update=False) # Adopt all the FitContribution methods - public = [aname for aname in dir(contribution) if aname not in - dir(self) and not aname.startswith("_")] + public = [ + aname + for aname in dir(contribution) + if aname not in dir(self) and not aname.startswith("_") + ] for mname in public: method = getattr(contribution, mname) setattr(self, mname, method) @@ -92,7 +94,7 @@ def loadParsedData(self, parser): """ return self.profile.loadParsedData(parser) - def setObservedProfile(self, xobs, yobs, dyobs = None): + def setObservedProfile(self, xobs, yobs, dyobs=None): """Set the observed profile. Arguments @@ -107,7 +109,6 @@ def setObservedProfile(self, xobs, yobs, dyobs = None): """ return self.profile.setObservedProfile(xobs, yobs, dyobs) - def setCalculationRange(self, xmin=None, xmax=None, dx=None): """Set epsilon-inclusive calculation range. @@ -142,7 +143,6 @@ def setCalculationRange(self, xmin=None, xmax=None, dx=None): """ return self.profile.setCalculationRange(xmin, xmax, dx) - def setCalculationPoints(self, x): """Set the calculation points. @@ -158,20 +158,21 @@ def setCalculationPoints(self, x): def loadtxt(self, *args, **kw): """Use numpy.loadtxt to load data. - Arguments are passed to numpy.loadtxt. unpack = True is enforced. - The first two arrays returned by numpy.loadtxt are assumed to be x and y. - If there is a third array, it is assumed to by dy. Any other arrays are - ignored. These are passed to setObservedProfile. + Arguments are passed to numpy.loadtxt. unpack = True is + enforced. The first two arrays returned by numpy.loadtxt are + assumed to be x and y. If there is a third array, it is assumed + to by dy. Any other arrays are ignored. These are passed to + setObservedProfile. - Raises ValueError if the call to numpy.loadtxt returns fewer than 2 - arrays. + Raises ValueError if the call to numpy.loadtxt returns fewer + than 2 arrays. Returns the x, y and dy arrays loaded from the file """ return self.profile.loadtxt(*args, **kw) # FitContribution - def setEquation(self, eqstr, ns = {}): + def setEquation(self, eqstr, ns={}): """Set the profile equation for the FitContribution. This sets the equation that will be used when generating the residual. @@ -187,11 +188,12 @@ def setEquation(self, eqstr, ns = {}): Raises ValueError if ns uses a name that is already used for a variable. """ - self.contribution.setEquation(eqstr, ns = {}) + self.contribution.setEquation(eqstr, ns={}) # Extract variables for par in self.contribution: # Skip Profile Parameters - if par.name in ("x", "y", "dy"): continue + if par.name in ("x", "y", "dy"): + continue if par.value is None: par.value = 0 if par.name not in self._parameters: @@ -204,7 +206,7 @@ def __call__(self): # FitResults methods - def printResults(self, header = "", footer = ""): + def printResults(self, header="", footer=""): """Format and print the results. header -- A header to add to the output (default "") @@ -213,7 +215,7 @@ def printResults(self, header = "", footer = ""): self.results.printResults(header, footer, True) return - def saveResults(self, filename, header = "", footer = ""): + def saveResults(self, filename, header="", footer=""): """Format and save the results. filename - Name of the save file. @@ -222,6 +224,7 @@ def saveResults(self, filename, header = "", footer = ""): """ self.results.saveResults(filename, header, footer, True) + # End class SimpleRecipe # End of file diff --git a/src/diffpy/srfit/fitbase/validatable.py b/src/diffpy/srfit/fitbase/validatable.py index e7216b1d..a3aeac66 100644 --- a/src/diffpy/srfit/fitbase/validatable.py +++ b/src/diffpy/srfit/fitbase/validatable.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Validatable class. -A Validatable has state that must be validated before a FitRecipe can first -calculate the residual. +A Validatable has state that must be validated before a FitRecipe can +first calculate the residual. """ __all__ = ["Validatable"] @@ -31,12 +30,13 @@ class Validatable(object): def _validateOthers(self, iterable): """Method to validate configuration of Validatables in iterable. - This is provided as a convenience for derived classes. No need to - overload this. Call this method from overloaded _validate with an - iterable of other Validatables. + This is provided as a convenience for derived classes. No need + to overload this. Call this method from overloaded _validate + with an iterable of other Validatables. """ for obj in iterable: - if obj is self: continue + if obj is self: + continue if isinstance(obj, Validatable): obj._validate() @@ -53,6 +53,7 @@ def _validate(self): # Then validate others. return + # End class Validatable # End of file diff --git a/src/diffpy/srfit/interface/__init__.py b/src/diffpy/srfit/interface/__init__.py index 8f6bd3a7..a611f118 100644 --- a/src/diffpy/srfit/interface/__init__.py +++ b/src/diffpy/srfit/interface/__init__.py @@ -12,22 +12,24 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Usability interface for SrFit. -The classes and functions in this package are designed to unobtrusively mix -with base SrFit objects and provide them with interface enhancements for -scripting. +The classes and functions in this package are designed to unobtrusively +mix with base SrFit objects and provide them with interface enhancements +for scripting. """ from diffpy.srfit.interface.interface import ParameterInterface + _parameter_interface = ParameterInterface from diffpy.srfit.interface.interface import RecipeOrganizerInterface + _recipeorganizer_interface = RecipeOrganizerInterface from diffpy.srfit.interface.interface import FitRecipeInterface + _fitrecipe_interface = FitRecipeInterface # End of file diff --git a/src/diffpy/srfit/interface/interface.py b/src/diffpy/srfit/interface/interface.py index b4f912e7..c9f67c57 100644 --- a/src/diffpy/srfit/interface/interface.py +++ b/src/diffpy/srfit/interface/interface.py @@ -12,17 +12,20 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Interface enhancements for Parameter-type classes. -Most interface additions can thought of by considering the classes in SrFit to -be sets of parameters. "+=" adds a new parameter, when that makes sense. "|=" -is the 'union' of these sets, and in general is used to combine different -objects. See individual interface classes for specifics. +Most interface additions can thought of by considering the classes in +SrFit to be sets of parameters. "+=" adds a new parameter, when that +makes sense. "|=" is the 'union' of these sets, and in general is used +to combine different objects. See individual interface classes for +specifics. """ -__all__ = ["ParameterInterface", "FitRecipeInterface", - "RecipeOrganizerInterface"] +__all__ = [ + "ParameterInterface", + "FitRecipeInterface", + "RecipeOrganizerInterface", +] import six @@ -45,10 +48,12 @@ def __lshift__(self, v): self.value = v return self + # End class ParameterInterface # ---------------------------------------------------------------------------- + class RecipeOrganizerInterface(object): """Mix-in class for enhancing the RecipeOrganizer interface.""" @@ -79,6 +84,7 @@ def __iadd__(self, args): This accepts arguments for a single function call. """ + # Want to detect _addParameter or _newParameter def f(*args): if isinstance(args[0], six.string_types): @@ -90,10 +96,12 @@ def f(*args): _applyargs(args, f) return self + # End class RecipeOrganizerInterface # ---------------------------------------------------------------------------- + class FitRecipeInterface(object): """Mix-in class for enhancing the FitRecipe interface.""" @@ -112,9 +120,10 @@ def __iadd__(self, args): Think of "+" as addition of a variable. - This accepts a single argument or an iterable of single arguments or - argument tuples. + This accepts a single argument or an iterable of single + arguments or argument tuples. """ + # Want to detect addVar or newVar def f(*args): if isinstance(args[0], six.string_types): @@ -126,10 +135,12 @@ def f(*args): _applymanyargs(args, f) return self + # End class FitRecipeInterface # Local helper functions ----------------------------------------------------- + def _applymanyargs(args, f): """Apply arguments to a function. @@ -138,18 +149,19 @@ def _applymanyargs(args, f): (arg1, arg2, ...) ((arg1a, arg1b, ...), ...) """ - if not hasattr(args, '__iter__'): + if not hasattr(args, "__iter__"): f(args) return for arg in args: - if hasattr(arg, '__iter__'): + if hasattr(arg, "__iter__"): f(*arg) else: f(arg) return + def _applyargs(args, f): """Apply arguments to a function. @@ -158,10 +170,11 @@ def _applyargs(args, f): (arg1, arg2, ...) ((arg1a, arg1b, ...), ...) """ - if not hasattr(args, '__iter__'): + if not hasattr(args, "__iter__"): f(args) else: f(*args) return + # End of file diff --git a/src/diffpy/srfit/pdf/__init__.py b/src/diffpy/srfit/pdf/__init__.py index eceba859..cde4d163 100644 --- a/src/diffpy/srfit/pdf/__init__.py +++ b/src/diffpy/srfit/pdf/__init__.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDF calculation tools.""" __all__ = ["PDFGenerator", "DebyePDFGenerator", "PDFContribution", "PDFParser"] -from diffpy.srfit.pdf.pdfgenerator import PDFGenerator from diffpy.srfit.pdf.debyepdfgenerator import DebyePDFGenerator from diffpy.srfit.pdf.pdfcontribution import PDFContribution +from diffpy.srfit.pdf.pdfgenerator import PDFGenerator from diffpy.srfit.pdf.pdfparser import PDFParser # End of file diff --git a/src/diffpy/srfit/pdf/basepdfgenerator.py b/src/diffpy/srfit/pdf/basepdfgenerator.py index 16e009e8..3fd58805 100644 --- a/src/diffpy/srfit/pdf/basepdfgenerator.py +++ b/src/diffpy/srfit/pdf/basepdfgenerator.py @@ -12,26 +12,25 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDF profile generator base class. -The BasePDFGenerator class interfaces with SrReal PDF calculators and is used -as a base for the PDFGenerator and DebyePDFGenerator classes. +The BasePDFGenerator class interfaces with SrReal PDF calculators and is +used as a base for the PDFGenerator and DebyePDFGenerator classes. """ __all__ = ["BasePDFGenerator"] import numpy +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase import ProfileGenerator from diffpy.srfit.fitbase.parameter import ParameterAdapter from diffpy.srfit.structure import struToParameterSet -from diffpy.srfit.exceptions import SrFitError - # FIXME - Parameter creation will have to be smarter once deeper calculator # configuration is enabled. + class BasePDFGenerator(ProfileGenerator): """Base class for calculating PDF profiles using SrReal. @@ -73,7 +72,7 @@ class BasePDFGenerator(ProfileGenerator): qdamp -- See Managed Parameters. """ - def __init__(self, name = "pdf"): + def __init__(self, name="pdf"): """Initialize the generator.""" ProfileGenerator.__init__(self, name) @@ -87,23 +86,21 @@ def __init__(self, name = "pdf"): return - _parnames = ['delta1', 'delta2', 'qbroad', 'scale', 'qdamp'] + _parnames = ["delta1", "delta2", "qbroad", "scale", "qdamp"] def _setCalculator(self, calc): - """Set the SrReal calulator instance. + """Set the SrReal calculator instance. - Setting the calculator creates Parameters from the variable attributes - of the SrReal calculator. + Setting the calculator creates Parameters from the variable + attributes of the SrReal calculator. """ self._calc = calc for pname in self.__class__._parnames: - self.addParameter( - ParameterAdapter(pname, self._calc, attr = pname) - ) + self.addParameter(ParameterAdapter(pname, self._calc, attr=pname)) self.processMetaData() return - def parallel(self, ncpu, mapfunc = None): + def parallel(self, ncpu, mapfunc=None): """Run calculation in parallel. ncpu -- Number of parallel processes. Revert to serial mode when 1. @@ -113,8 +110,9 @@ def parallel(self, ncpu, mapfunc = None): No return value. """ from diffpy.srreal.parallel import createParallelCalculator + calc_serial = self._calc - if hasattr(calc_serial, 'pqobj'): + if hasattr(calc_serial, "pqobj"): calc_serial = calc_serial.pqobj # revert to serial calculator for ncpu <= 1 if ncpu <= 1: @@ -125,6 +123,7 @@ def parallel(self, ncpu, mapfunc = None): # ncpu = min(ncpu, multiprocessing.cpu_count()) if mapfunc is None: import multiprocessing + self._pool = multiprocessing.Pool(ncpu) mapfunc = self._pool.imap_unordered @@ -155,7 +154,7 @@ def processMetaData(self): return - def setScatteringType(self, stype = "X"): + def setScatteringType(self, stype="X"): """Set the scattering type. stype -- "X" for x-ray, "N" for neutron, "E" for electrons, @@ -196,7 +195,7 @@ def getQmin(self): """Get the qmin value.""" return self._calc.qmin - def setStructure(self, stru, name = "phase", periodic = True): + def setStructure(self, stru, name="phase", periodic=True): """Set the structure that will be used to calculate the PDF. This creates a DiffpyStructureParSet, ObjCrystCrystalParSet or @@ -221,8 +220,7 @@ def setStructure(self, stru, name = "phase", periodic = True): self.setPhase(parset, periodic) return - - def setPhase(self, parset, periodic = True): + def setPhase(self, parset, periodic=True): """Set the phase that will be used to calculate the PDF. Set the phase directly with a DiffpyStructureParSet, @@ -256,7 +254,7 @@ def _prepare(self, r): ndiv = max(len(r) - 1, 1) self._calc.rstep = (hi - lo) / ndiv self._calc.rmin = lo - self._calc.rmax = hi + 0.5*self._calc.rstep + self._calc.rmax = hi + 0.5 * self._calc.rstep return def _validate(self): @@ -277,11 +275,11 @@ def _validate(self): def __call__(self, r): """Calculate the PDF. - This ProfileGenerator will be used in a fit equation that will be - optimized to fit some data. By the time this function is evaluated, - the crystal has been updated by the optimizer via the ObjCrystParSet - created in setCrystal. Thus, we need only call pdf with the internal - structure object. + This ProfileGenerator will be used in a fit equation that will + be optimized to fit some data. By the time this function is + evaluated, the crystal has been updated by the optimizer via the + ObjCrystParSet created in setCrystal. Thus, we need only call + pdf with the internal structure object. """ if not numpy.array_equal(r, self._lastr): self._prepare(r) @@ -294,4 +292,5 @@ def __call__(self, r): y = numpy.interp(r, rcalc, y) return y + # End class BasePDFGenerator diff --git a/src/diffpy/srfit/pdf/characteristicfunctions.py b/src/diffpy/srfit/pdf/characteristicfunctions.py index 24d8a824..3f8a1473 100644 --- a/src/diffpy/srfit/pdf/characteristicfunctions.py +++ b/src/diffpy/srfit/pdf/characteristicfunctions.py @@ -12,26 +12,33 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Form factors (characteristic functions) used in PDF nanoshape fitting. -These are used to calculate the attenuation of the PDF due to a finite size. -For a crystal-like nanoparticle, one can calculate the PDF via Gnano(r) = f(r) -Gcryst(r), where f(r) is the nanoparticle characteristic function and Gcryst(f) -is the crystal PDF. +These are used to calculate the attenuation of the PDF due to a finite +size. For a crystal-like nanoparticle, one can calculate the PDF via +Gnano(r) = f(r) Gcryst(r), where f(r) is the nanoparticle characteristic +function and Gcryst(f) is the crystal PDF. -These functions are meant to be imported and added to a FitContribution using -the 'registerFunction' method of that class. +These functions are meant to be imported and added to a FitContribution +using the 'registerFunction' method of that class. """ -__all__ = ["sphericalCF", "spheroidalCF", "spheroidalCF2", - "lognormalSphericalCF", "sheetCF", "shellCF", "shellCF2", "SASCF"] +__all__ = [ + "sphericalCF", + "spheroidalCF", + "spheroidalCF2", + "lognormalSphericalCF", + "sheetCF", + "shellCF", + "shellCF2", + "SASCF", +] import numpy -from numpy import pi, sqrt, log, exp, log2, ceil, sign from numpy import arctan as atan from numpy import arctanh as atanh -from numpy.fft import ifft, fftfreq +from numpy import ceil, exp, log, log2, pi, sign, sqrt +from numpy.fft import fftfreq, ifft from scipy.special import erf from diffpy.srfit.fitbase.calculator import Calculator @@ -49,11 +56,12 @@ def sphericalCF(r, psize): f = numpy.zeros(numpy.shape(r), dtype=float) if psize > 0: x = numpy.array(r, dtype=float) / psize - inside = (x < 1.0) + inside = x < 1.0 xin = x[inside] - f[inside] = 1.0 - 1.5*xin + 0.5*xin*xin*xin + f[inside] = 1.0 - 1.5 * xin + 0.5 * xin * xin * xin return f + def spheroidalCF(r, erad, prad): """Spheroidal characteristic function specified using radii. @@ -70,6 +78,7 @@ def spheroidalCF(r, erad, prad): pelpt = 1.0 * prad / erad return spheroidalCF2(r, psize, pelpt) + def spheroidalCF2(r, psize, axrat): """Spheroidal nanoparticle characteristic function. @@ -89,8 +98,8 @@ def spheroidalCF2(r, psize, axrat): # to simplify the equations v = pelpt d = 1.0 * psize - d2 = d*d - v2 = v*v + d2 = d * d + v2 = v * v if v == 1: return sphericalCF(r, psize) @@ -98,40 +107,80 @@ def spheroidalCF2(r, psize, axrat): rx = r if v < 1: - r = rx[rx <= v*psize] - r2 = r*r - f1 = 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) \ - - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(1-v2)*atanh(sqrt(1-v2)) - - r = rx[numpy.logical_and(rx > v*psize, rx <= psize)] - r2 = r*r - f2 = (3*d/(8*r)*(1+r2/(2*d2))*sqrt(1-r2/d2) \ - - 3*r/(4*d)*(1-r2/(4*d2))*atanh(sqrt(1-r2/d2)) \ - ) * v/sqrt(1-v2) + r = rx[rx <= v * psize] + r2 = r * r + f1 = ( + 1 + - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) + - 3 + * r + / (4 * d) + * (1 - r2 / (4 * d2)) + * v + / sqrt(1 - v2) + * atanh(sqrt(1 - v2)) + ) + + r = rx[numpy.logical_and(rx > v * psize, rx <= psize)] + r2 = r * r + f2 = ( + ( + 3 * d / (8 * r) * (1 + r2 / (2 * d2)) * sqrt(1 - r2 / d2) + - 3 + * r + / (4 * d) + * (1 - r2 / (4 * d2)) + * atanh(sqrt(1 - r2 / d2)) + ) + * v + / sqrt(1 - v2) + ) r = rx[rx > psize] f3 = numpy.zeros_like(r) - f = numpy.concatenate((f1,f2,f3)) + f = numpy.concatenate((f1, f2, f3)) elif v > 1: r = rx[rx <= psize] - r2 = r*r - f1 = 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) \ - - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(v2-1)*atan(sqrt(v2-1)) - - r = rx[numpy.logical_and(rx > psize, rx <= v*psize)] - r2 = r*r - f2 = 1 - 3*r/(4*d*v)*(1-r2/(4*d2)*(1+2.0/(3*v2))) \ - - 3.0/8*(1+r2/(2*d2))*sqrt(1-d2/r2)*v/sqrt(v2-1) \ - - 3*r/(4*d)*(1-r2/(4*d2))*v/sqrt(v2-1) \ - * (atan(sqrt(v2-1)) - atan(sqrt(r2/d2-1))) - - r = rx[rx > v*psize] + r2 = r * r + f1 = ( + 1 + - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) + - 3 + * r + / (4 * d) + * (1 - r2 / (4 * d2)) + * v + / sqrt(v2 - 1) + * atan(sqrt(v2 - 1)) + ) + + r = rx[numpy.logical_and(rx > psize, rx <= v * psize)] + r2 = r * r + f2 = ( + 1 + - 3 * r / (4 * d * v) * (1 - r2 / (4 * d2) * (1 + 2.0 / (3 * v2))) + - 3.0 + / 8 + * (1 + r2 / (2 * d2)) + * sqrt(1 - d2 / r2) + * v + / sqrt(v2 - 1) + - 3 + * r + / (4 * d) + * (1 - r2 / (4 * d2)) + * v + / sqrt(v2 - 1) + * (atan(sqrt(v2 - 1)) - atan(sqrt(r2 / d2 - 1))) + ) + + r = rx[rx > v * psize] f3 = numpy.zeros_like(r) - f = numpy.concatenate((f1,f2,f3)) + f = numpy.concatenate((f1, f2, f3)) return f @@ -144,7 +193,7 @@ def lognormalSphericalCF(r, psize, psig): psize -- The mean particle diameter psig -- The log-normal width of the particle diameter - Here, r is the independent variable, mu is the mean of the distrubution + Here, r is the independent variable, mu is the mean of the distribution (not of the particle size), and s is the width of the distribution. This is the characteristic function for the lognormal distribution of particle diameter: @@ -162,19 +211,33 @@ def lognormalSphericalCF(r, psize, psig): Source unknown """ - if psize <= 0: return numpy.zeros_like(r) - if psig <= 0: return sphericalCF(r, psize) + if psize <= 0: + return numpy.zeros_like(r) + if psig <= 0: + return sphericalCF(r, psize) - erfc = lambda x: 1.0-erf(x) + erfc = lambda x: 1.0 - erf(x) sqrt2 = sqrt(2.0) - s = sqrt(log(psig*psig/(1.0*psize*psize) + 1)) - mu = log(psize) - s*s/2; - if mu < 0: return numpy.zeros_like(r) + s = sqrt(log(psig * psig / (1.0 * psize * psize) + 1)) + mu = log(psize) - s * s / 2 + if mu < 0: + return numpy.zeros_like(r) + + return ( + 0.5 * erfc((-mu - 3 * s * s + log(r)) / (sqrt2 * s)) + + 0.25 + * r + * r + * r + * erfc((-mu + log(r)) / (sqrt2 * s)) + * exp(-3 * mu - 4.5 * s * s) + - 0.75 + * r + * erfc((-mu - 2 * s * s + log(r)) / (sqrt2 * s)) + * exp(-mu - 2.5 * s * s) + ) - return 0.5*erfc((-mu-3*s*s+log(r))/(sqrt2*s)) \ - + 0.25*r*r*r*erfc((-mu+log(r))/(sqrt2*s))*exp(-3*mu-4.5*s*s) \ - - 0.75*r*erfc((-mu-2*s*s+log(r))/(sqrt2*s))*exp(-mu-2.5*s*s) def sheetCF(r, sthick): """Nanosheet characteristic function. @@ -211,10 +274,11 @@ def shellCF(r, radius, thickness): From Lei et al., Phys. Rev. B, 80, 024118 (2009) """ - d = 1.0*thickness - a = 1.0*radius + d/2.0 + d = 1.0 * thickness + a = 1.0 * radius + d / 2.0 return shellCF2(r, a, d) + def shellCF2(r, a, delta): """Spherical shell characteristic function. @@ -225,22 +289,30 @@ def shellCF2(r, a, delta): From Lei et al., Phys. Rev. B, 80, 024118 (2009) """ - a = 1.0*a - d = 1.0*delta + a = 1.0 * a + d = 1.0 * delta a2 = a**2 d2 = d**2 - dmr = d-r + dmr = d - r dmr2 = dmr**2 - f = r * (16*a*a2 + 12*a*d*dmr + 36*a2*(2*d-r) + 3*dmr2*(2*d+r)) \ - + 2*dmr2 * (r*(2*d+r)-12*a2) * sign(dmr) \ - - 2*(2*a-r)**2 * (r*(4*a+r)-3*d2) * sign(2*a-r) \ - + r*(4*a-2*d+r)*(2*a-d-r)**2*sign(2*a-d-r) - - f[r > 2*a+d] = 0 - - den = 8.0*r*d*(12*a2+d2) - zmask = (den == 0.0) + f = ( + r + * ( + 16 * a * a2 + + 12 * a * d * dmr + + 36 * a2 * (2 * d - r) + + 3 * dmr2 * (2 * d + r) + ) + + 2 * dmr2 * (r * (2 * d + r) - 12 * a2) * sign(dmr) + - 2 * (2 * a - r) ** 2 * (r * (4 * a + r) - 3 * d2) * sign(2 * a - r) + + r * (4 * a - 2 * d + r) * (2 * a - d - r) ** 2 * sign(2 * a - d - r) + ) + + f[r > 2 * a + d] = 0 + + den = 8.0 * r * d * (12 * a2 + d2) + zmask = den == 0.0 vmask = ~zmask f[vmask] /= den[vmask] f[zmask] = 1 @@ -277,6 +349,7 @@ def __init__(self, name, model): self._model = model from diffpy.srfit.sas.sasparameter import SASParameter + # Wrap normal parameters for parname in model.params: par = SASParameter(parname, model) @@ -319,11 +392,11 @@ def __call__(self, r): rmax = max(ed, 2 * r[-1]) dq = pi / rmax qmax = pi / dr - numpoints = int(2**(ceil(log2(qmax/dq)))) + numpoints = int(2 ** (ceil(log2(qmax / dq)))) qmax = dq * numpoints # Calculate F(q) = q * I(q) from model - q = fftfreq(int(qmax/dq)) * qmax + q = fftfreq(int(qmax / dq)) * qmax fq = q * self._model.evalDistribution(q) # Calculate g(r) and the effective r-points @@ -332,20 +405,20 @@ def __call__(self, r): gr = ifft(fq).imag # Calculate full-fr for normalization - assert (rp[0] == 0.0) + assert rp[0] == 0.0 frp = numpy.zeros_like(gr) frp[1:] = gr[1:] / rp[1:] # Inerpolate onto requested grid, do not use data after jump in rp - assert (numpoints % 2 == 0) + assert numpoints % 2 == 0 nhalf = numpoints / 2 fr = numpy.interp(r, rp[:nhalf], gr[:nhalf]) - vmask = (r != 0) + vmask = r != 0 fr[vmask] /= r[vmask] # Normalize. We approximate fr[0] by using the fact that f(r) is linear # at low r. By definition, fr[0] should equal 1. - fr0 = 2*frp[2] - frp[1] + fr0 = 2 * frp[2] - frp[1] fr /= fr0 # Fix potential divide-by-zero issue, fr is 1 at r == 0 diff --git a/src/diffpy/srfit/pdf/debyepdfgenerator.py b/src/diffpy/srfit/pdf/debyepdfgenerator.py index 1c86bf6a..e83e2026 100644 --- a/src/diffpy/srfit/pdf/debyepdfgenerator.py +++ b/src/diffpy/srfit/pdf/debyepdfgenerator.py @@ -12,13 +12,12 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDF profile generator using the Debye equation. The DebyePDFGenerator class can take a diffpy.structure, -pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule object and calculate -the PDF from it. This generator is especially appropriate for isolated -scatterers, such as nanoparticles and molecules. +pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule object and +calculate the PDF from it. This generator is especially appropriate for +isolated scatterers, such as nanoparticles and molecules. """ __all__ = ["DebyePDFGenerator"] @@ -65,7 +64,7 @@ class DebyePDFGenerator(BasePDFGenerator): qdamp -- See Managed Parameters. """ - def setStructure(self, stru, name = "phase", periodic = False): + def setStructure(self, stru, name="phase", periodic=False): """Set the structure that will be used to calculate the PDF. This creates a DiffpyStructureParSet, ObjCrystCrystalParSet or @@ -84,8 +83,7 @@ def setStructure(self, stru, name = "phase", periodic = False): """ return BasePDFGenerator.setStructure(self, stru, name, periodic) - - def setPhase(self, parset, periodic = False): + def setPhase(self, parset, periodic=False): """Set the phase that will be used to calculate the PDF. Set the phase directly with a DiffpyStructureParSet, @@ -103,14 +101,15 @@ def setPhase(self, parset, periodic = False): """ return BasePDFGenerator.setPhase(self, parset, periodic) - - def __init__(self, name = "pdf"): + def __init__(self, name="pdf"): """Initialize the generator.""" from diffpy.srreal.pdfcalculator import DebyePDFCalculator + BasePDFGenerator.__init__(self, name) self._setCalculator(DebyePDFCalculator()) return + # End class DebyePDFGenerator # End of file diff --git a/src/diffpy/srfit/pdf/pdfcontribution.py b/src/diffpy/srfit/pdf/pdfcontribution.py index 4abc9af4..b6b0740d 100644 --- a/src/diffpy/srfit/pdf/pdfcontribution.py +++ b/src/diffpy/srfit/pdf/pdfcontribution.py @@ -12,16 +12,16 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDFContribution class. -This is a custom FitContribution that simplifies the creation of PDF fits. +This is a custom FitContribution that simplifies the creation of PDF +fits. """ __all__ = ["PDFContribution"] -from diffpy.srfit.fitbase import FitContribution -from diffpy.srfit.fitbase import Profile +from diffpy.srfit.fitbase import FitContribution, Profile + class PDFContribution(FitContribution): """PDFContribution class. @@ -70,7 +70,7 @@ def __init__(self, name): self._meta = {} # Add the profile profile = Profile() - self.setProfile(profile, xname = "r") + self.setProfile(profile, xname="r") # Need a parameter for the overall scale, in the case that this is a # multi-phase fit. @@ -86,17 +86,19 @@ def loadData(self, data): """Load the data in various formats. This uses the PDFParser to load the data and then passes it to the - build-in profile with loadParsedData. + built-in profile with loadParsedData. data -- An open file-like object, name of a file that contains data or a string containing the data. """ # Get the data into a string from diffpy.srfit.util.inpututils import inputToString + datstr = inputToString(data) # Load data with a PDFParser from diffpy.srfit.pdf.pdfparser import PDFParser + parser = PDFParser() parser.parseString(datstr) @@ -104,7 +106,6 @@ def loadData(self, data): self.profile.loadParsedData(parser) return - def setCalculationRange(self, xmin=None, xmax=None, dx=None): """Set epsilon-inclusive calculation range. @@ -139,7 +140,6 @@ def setCalculationRange(self, xmin=None, xmax=None, dx=None): """ return self.profile.setCalculationRange(xmin, xmax, dx) - def savetxt(self, fname, **kwargs): """Call numpy.savetxt with x, ycalc, y, dy. @@ -151,7 +151,7 @@ def savetxt(self, fname, **kwargs): # Phase methods - def addStructure(self, name, stru, periodic = True): + def addStructure(self, name, stru, periodic=True): """Add a phase that goes into the PDF calculation. name -- A name to give the generator that will manage the PDF @@ -176,9 +176,11 @@ def addStructure(self, name, stru, periodic = True): # Based on periodic, create the proper generator. if periodic: from diffpy.srfit.pdf.pdfgenerator import PDFGenerator + gen = PDFGenerator(name) else: from diffpy.srfit.pdf.debyepdfgenerator import DebyePDFGenerator + gen = DebyePDFGenerator(name) # Set up the generator @@ -187,7 +189,7 @@ def addStructure(self, name, stru, periodic = True): return gen.phase - def addPhase(self, name, parset, periodic = True): + def addPhase(self, name, parset, periodic=True): """Add a phase that goes into the PDF calculation. name -- A name to give the generator that will manage the PDF @@ -213,9 +215,11 @@ def addPhase(self, name, parset, periodic = True): # Based on periodic, create the proper generator. if periodic: from diffpy.srfit.pdf.pdfgenerator import PDFGenerator + gen = PDFGenerator(name) else: from diffpy.srfit.pdf.debyepdfgenerator import DebyePDFGenerator + gen = DebyePDFGenerator(name) # Set up the generator @@ -227,8 +231,8 @@ def addPhase(self, name, parset, periodic = True): def _setupGenerator(self, gen): """Setup a generator. - The generator must already have a managed SrRealParSet, added with - setStructure or setPhase. + The generator must already have a managed SrRealParSet, added + with setStructure or setPhase. """ # Add the generator to this FitContribution self.addProfileGenerator(gen) @@ -262,8 +266,7 @@ def _getMetaValue(self, kwd): val = self.profile.meta.get(kwd) return val - - def setScatteringType(self, type = "X"): + def setScatteringType(self, type="X"): """Set the scattering type. type -- "X" for x-ray or "N" for neutron @@ -304,4 +307,5 @@ def getQmin(self): """Get the qmin value.""" return self._getMetaValue("qmin") + # End of file diff --git a/src/diffpy/srfit/pdf/pdfgenerator.py b/src/diffpy/srfit/pdf/pdfgenerator.py index 819017a5..9b5b1608 100644 --- a/src/diffpy/srfit/pdf/pdfgenerator.py +++ b/src/diffpy/srfit/pdf/pdfgenerator.py @@ -12,14 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """PDF profile generator. -The PDFGenerator class can take a diffpy.structure, pyobjcryst.crystal.Crystal -or pyobjcryst.molecule.Molecule object and calculate the crystal PDF from it. -The passed structure object is wrapped in a StructureParameter set, which makes -its attributes refinable. See the class definition for more details and the -examples for its use. +The PDFGenerator class can take a diffpy.structure, +pyobjcryst.crystal.Crystal or pyobjcryst.molecule.Molecule object and +calculate the crystal PDF from it. The passed structure object is +wrapped in a StructureParameter set, which makes its attributes +refinable. See the class definition for more details and the examples +for its use. """ __all__ = ["PDFGenerator"] @@ -65,11 +65,13 @@ class PDFGenerator(BasePDFGenerator): qdamp -- See Managed Parameters. """ - def __init__(self, name = "pdf"): + def __init__(self, name="pdf"): """Initialize the generator.""" from diffpy.srreal.pdfcalculator import PDFCalculator + BasePDFGenerator.__init__(self, name) self._setCalculator(PDFCalculator()) return + # End class PDFGenerator diff --git a/src/diffpy/srfit/pdf/pdfparser.py b/src/diffpy/srfit/pdf/pdfparser.py index ae87a191..9eb5595f 100644 --- a/src/diffpy/srfit/pdf/pdfparser.py +++ b/src/diffpy/srfit/pdf/pdfparser.py @@ -12,10 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """This module contains parsers for PDF data. -PDFParser is suitable for parsing data generated from PDFGetN and PDFGetX. +PDFParser is suitable for parsing data generated from PDFGetN and +PDFGetX. See the class documentation for more information. """ @@ -23,11 +23,13 @@ __all__ = ["PDFParser"] import re + import numpy from diffpy.srfit.exceptions import ParseError from diffpy.srfit.fitbase.profileparser import ProfileParser + class PDFParser(ProfileParser): """Class for holding a diffraction pattern. @@ -48,7 +50,7 @@ class PDFParser(ProfileParser): dy -- A numpy array containing the uncertainty read from the file. This is 0 if the uncertainty cannot be read. - _x -- Indpendent variable from the chosen bank + _x -- Independent variable from the chosen bank _y -- Profile from the chosen bank _dx -- Uncertainty in independent variable from the chosen bank _dy -- Uncertainty in profile from the chosen bank @@ -89,15 +91,15 @@ def parseString(self, patstring): Raises ParseError if the string cannot be parsed """ # useful regex patterns: - rx = { 'f' : r'[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?' } + rx = {"f": r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?"} # find where does the data start - res = re.search(r'^#+ start data\s*(?:#.*\s+)*', patstring, re.M) + res = re.search(r"^#+ start data\s*(?:#.*\s+)*", patstring, re.M) # start_data is position where the first data line starts if res: start_data = res.end() else: # find line that starts with a floating point number - regexp = r'^\s*%(f)s' % rx + regexp = r"^\s*%(f)s" % rx res = re.search(regexp, patstring, re.M) if res: start_data = res.start() @@ -107,19 +109,19 @@ def parseString(self, patstring): databody = patstring[start_data:].strip() # find where the metadata starts - metadata = '' - res = re.search(r'^#+\ +metadata\b\n', header, re.M) + metadata = "" + res = re.search(r"^#+\ +metadata\b\n", header, re.M) if res: - metadata = header[res.end():] - header = header[:res.start()] + metadata = header[res.end() :] + header = header[: res.start()] # parse header meta = self._meta # stype - if re.search('(x-?ray|PDFgetX)', header, re.I): - meta["stype"] = 'X' - elif re.search('(neutron|PDFgetN)', header, re.I): - meta["stype"] = 'N' + if re.search("(x-?ray|PDFgetX)", header, re.I): + meta["stype"] = "X" + elif re.search("(neutron|PDFgetN)", header, re.I): + meta["stype"] = "N" # qmin regexp = r"\bqmin *= *(%(f)s)\b" % rx res = re.search(regexp, header, re.I) @@ -154,26 +156,26 @@ def parseString(self, patstring): regexp = r"\b(?:temp|temperature|T)\ *=\ *(%(f)s)\b" % rx res = re.search(regexp, header) if res: - meta['temperature'] = float(res.groups()[0]) + meta["temperature"] = float(res.groups()[0]) # doping regexp = r"\b(?:x|doping)\ *=\ *(%(f)s)\b" % rx res = re.search(regexp, header) if res: - meta['doping'] = float(res.groups()[0]) + meta["doping"] = float(res.groups()[0]) - # parsing gerneral metadata + # parsing general metadata if metadata: regexp = r"\b(\w+)\ *=\ *(%(f)s)\b" % rx while True: res = re.search(regexp, metadata, re.M) if res: meta[res.groups()[0]] = float(res.groups()[1]) - metadata = metadata[res.end():] + metadata = metadata[res.end() :] else: break # read actual data - robs, Gobs, drobs, dGobs - inf_or_nan = re.compile('(?i)^[+-]?(NaN|Inf)\\b') + inf_or_nan = re.compile("(?i)^[+-]?(NaN|Inf)\\b") has_drobs = True has_dGobs = True # raise ParseError if something goes wrong @@ -188,15 +190,17 @@ def parseString(self, patstring): robs.append(float(v[0])) Gobs.append(float(v[1])) # drobs is valid if all values are defined and positive - has_drobs = (has_drobs and - len(v) > 2 and not inf_or_nan.match(v[2])) + has_drobs = ( + has_drobs and len(v) > 2 and not inf_or_nan.match(v[2]) + ) if has_drobs: v2 = float(v[2]) has_drobs = v2 > 0.0 drobs.append(v2) # dGobs is valid if all values are defined and positive - has_dGobs = (has_dGobs and - len(v) > 3 and not inf_or_nan.match(v[3])) + has_dGobs = ( + has_dGobs and len(v) > 3 and not inf_or_nan.match(v[3]) + ) if has_dGobs: v3 = float(v[3]) has_dGobs = v3 > 0.0 @@ -218,4 +222,5 @@ def parseString(self, patstring): self._banks.append([robs, Gobs, drobs, dGobs]) return + # End of PDFParser diff --git a/src/diffpy/srfit/sas/__init__.py b/src/diffpy/srfit/sas/__init__.py index 42e918de..bd7024a9 100644 --- a/src/diffpy/srfit/sas/__init__.py +++ b/src/diffpy/srfit/sas/__init__.py @@ -12,15 +12,19 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """SAS calculation tools.""" -__all__ = ["SASGenerator", "SASParser", "SASProfile", "PrCalculator", - "CFCalculator"] +__all__ = [ + "SASGenerator", + "SASParser", + "SASProfile", + "PrCalculator", + "CFCalculator", +] +from .prcalculator import CFCalculator, PrCalculator from .sasgenerator import SASGenerator from .sasparser import SASParser from .sasprofile import SASProfile -from .prcalculator import PrCalculator, CFCalculator # End of file diff --git a/src/diffpy/srfit/sas/prcalculator.py b/src/diffpy/srfit/sas/prcalculator.py index 9ea540da..0af67260 100644 --- a/src/diffpy/srfit/sas/prcalculator.py +++ b/src/diffpy/srfit/sas/prcalculator.py @@ -12,13 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Nanoparticle form factor P(r) calculator. -The PrCalculator class wraps a sas.pr.invertor.Invertor object as a Calculator. -This is not wrapped as a ProfileGenerator because it will be used to share -information between SAS I(Q) to PDF G(r), but it does not use the same profile -as the PDF, which is where the calculator will be applied. +The PrCalculator class wraps a sas.pr.invertor.Invertor object as a +Calculator. This is not wrapped as a ProfileGenerator because it will be +used to share information between SAS I(Q) to PDF G(r), but it does not +use the same profile as the PDF, which is where the calculator will be +applied. """ __all__ = ["PrCalculator", "CFCalculator"] @@ -65,7 +65,8 @@ def __init__(self, name): global Invertor if Invertor is None: from diffpy.srfit.sas.sasimport import sasimport - Invertor = sasimport('sas.pr.invertor').Invertor + + Invertor = sasimport("sas.pr.invertor").Invertor self._invertor = Invertor() @@ -95,8 +96,10 @@ def __call__(self, r): pr = numpy.array(pr) return self.scale.value * pr + # End class PrCalculator + class CFCalculator(PrCalculator): """A class for calculating the characteristic function (CF) from data. @@ -127,4 +130,5 @@ def __call__(self, r): fr[0] = 1 return fr + # End class CFCalculator diff --git a/src/diffpy/srfit/sas/sasgenerator.py b/src/diffpy/srfit/sas/sasgenerator.py index 11b20c86..bb3de8cf 100644 --- a/src/diffpy/srfit/sas/sasgenerator.py +++ b/src/diffpy/srfit/sas/sasgenerator.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """SAS profile generator. The SASGenerator class wraps a sas.models.BaseModel object as a @@ -67,4 +66,5 @@ def __call__(self, q): """Calculate I(Q) for the BaseModel.""" return self._model.evalDistribution(q) + # End class SASGenerator diff --git a/src/diffpy/srfit/sas/sasimport.py b/src/diffpy/srfit/sas/sasimport.py index 8c303af1..093a67b3 100644 --- a/src/diffpy/srfit/sas/sasimport.py +++ b/src/diffpy/srfit/sas/sasimport.py @@ -12,9 +12,9 @@ # See LICENSE.txt for license information. # ############################################################################## - """Universal import functions for volatile SasView/SansViews API-s.""" + def sasimport(modname): """Import specified module from the SasView sas package. @@ -23,41 +23,44 @@ def sasimport(modname): When specified import does not work directly, try older API-s and raise DeprecationWarning. Raise ImportError if nothing works. """ - if not modname.startswith('sas.'): + if not modname.startswith("sas."): emsg = 'Module name must start with "sas."' raise ValueError(emsg) mobj = None # standard import try: - exec('import %s as mobj' % modname) + exec("import %s as mobj" % modname) except ImportError: pass else: return mobj # revert to the old sans namespace, sas --> sans - modsans = 'sans' + modname[3:] + modsans = "sans" + modname[3:] import warnings - wfmt = ("Using obsolete package %r instead of %r. Please install " - "SasView 3.1 or the srfit-sasview package from Anaconda.") + + wfmt = ( + "Using obsolete package %r instead of %r. Please install " + "SasView 3.1 or the srfit-sasview package from Anaconda." + ) wmsg = wfmt % (modsans, modname) try: - exec('import %s as mobj' % modsans) + exec("import %s as mobj" % modsans) warnings.warn(wmsg, DeprecationWarning) except ImportError: pass else: return mobj # finally check the oldest DataLoader API for sas.dataloader - if modname.startswith('sas.dataloader'): - modloader = 'DataLoader' + modname[14:] + if modname.startswith("sas.dataloader"): + modloader = "DataLoader" + modname[14:] wmsg = wfmt % (modloader, modname) try: - exec('import %s as mobj' % modloader) + exec("import %s as mobj" % modloader) warnings.warn(wmsg, DeprecationWarning) except ImportError: pass else: return mobj # Obsolete API-s failed here. Import again and let it raise ImportError. - exec('import %s as mobj' % modname) + exec("import %s as mobj" % modname) raise AssertionError("The import above was supposed to fail.") diff --git a/src/diffpy/srfit/sas/sasparameter.py b/src/diffpy/srfit/sas/sasparameter.py index b00be925..0f042772 100644 --- a/src/diffpy/srfit/sas/sasparameter.py +++ b/src/diffpy/srfit/sas/sasparameter.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """SAS profile generator. The SASGenerator class wraps a sas.models.BaseModel object as a @@ -41,7 +40,7 @@ class SASParameter(Parameter): _parname -- The name of the underlying BaseModel parameter. """ - def __init__(self, name, model, parname = None): + def __init__(self, name, model, parname=None): """Create the Parameter. name -- Name of the Parameter @@ -57,7 +56,7 @@ def __init__(self, name, model, parname = None): def getValue(self): """Get the value of the Parameter.""" - value = self._model.getParam(self._parname) + value = self._model.getParam(self._parname) return value def setValue(self, value): @@ -67,4 +66,5 @@ def setValue(self, value): self.notify() return self + # End of class SASParameter diff --git a/src/diffpy/srfit/sas/sasparser.py b/src/diffpy/srfit/sas/sasparser.py index e20807b9..69f94904 100644 --- a/src/diffpy/srfit/sas/sasparser.py +++ b/src/diffpy/srfit/sas/sasparser.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """This module contains parsers for SAS data. SASParser uses the sas DataLoader class to load data. @@ -49,7 +48,7 @@ class SASParser(ProfileParser): dy -- A numpy array containing the uncertainty read from the file. This is 0 if the uncertainty cannot be read. - _x -- Indpendent variable from the chosen bank + _x -- Independent variable from the chosen bank _y -- Profile from the chosen bank _dx -- Uncertainty in independent variable from the chosen bank _dy -- Uncertainty in profile from the chosen bank @@ -81,7 +80,7 @@ def parseFile(self, filename): Raises ParseError if the file cannot be parsed """ - Loader = sasimport('sas.dataloader.loader').Loader + Loader = sasimport("sas.dataloader.loader").Loader loader = Loader() try: @@ -114,8 +113,9 @@ def parseString(self, patstring): """ # This calls on parseFile, as that is how the sas data loader works. import tempfile + fh, fn = tempfile.mkstemp() - outfile = open(fn, 'w') + outfile = open(fn, "w") fn.write(patstring) outfile.close() self.parseFile(fn) @@ -124,6 +124,7 @@ def parseString(self, patstring): # Close the temporary file and delete it import os + os.close(fh) os.remove(fn) return diff --git a/src/diffpy/srfit/sas/sasprofile.py b/src/diffpy/srfit/sas/sasprofile.py index 9c410a06..b18dfa76 100644 --- a/src/diffpy/srfit/sas/sasprofile.py +++ b/src/diffpy/srfit/sas/sasprofile.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Class for adapting a sas DataInfo objects to the Profile interface.""" __all__ = ["SASProfile"] @@ -72,7 +71,7 @@ def __init__(self, datainfo): self._dyobs = self._datainfo.dy return - def setObservedProfile(self, xobs, yobs, dyobs = None): + def setObservedProfile(self, xobs, yobs, dyobs=None): """Set the observed profile. This is overloaded to change the value within the datainfo object. diff --git a/src/diffpy/srfit/structure/__init__.py b/src/diffpy/srfit/structure/__init__.py index 3296f0dd..d1a1ecd1 100644 --- a/src/diffpy/srfit/structure/__init__.py +++ b/src/diffpy/srfit/structure/__init__.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Modules and classes that adapt structure representations to the ParameterSet interface and automatic structure constraint generation from space group information.""" @@ -30,18 +29,22 @@ def struToParameterSet(name, stru): Raises TypeError if stru cannot be adapted """ from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet + if DiffpyStructureParSet.canAdapt(stru): return DiffpyStructureParSet(name, stru) from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + if ObjCrystCrystalParSet.canAdapt(stru): return ObjCrystCrystalParSet(name, stru) from diffpy.srfit.structure.objcrystparset import ObjCrystMoleculeParSet + if ObjCrystMoleculeParSet.canAdapt(stru): return ObjCrystMoleculeParSet(name, stru) from diffpy.srfit.structure.cctbxparset import CCTBXCrystalParSet + if CCTBXCrystalParSet.canAdapt(stru): return CCTBXCrystalParSet(name, stru) diff --git a/src/diffpy/srfit/structure/basestructureparset.py b/src/diffpy/srfit/structure/basestructureparset.py index 7fa44c6d..31c619c9 100644 --- a/src/diffpy/srfit/structure/basestructureparset.py +++ b/src/diffpy/srfit/structure/basestructureparset.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Base class for adapting structures to a ParameterSet interface. -The BaseStructureParSet is a ParameterSet with functionality required by all -structure adapters. +The BaseStructureParSet is a ParameterSet with functionality required by +all structure adapters. """ __all__ = ["BaseStructureParSet"] @@ -43,21 +42,21 @@ def canAdapt(self, stru): def getLattice(self): """Get a ParameterSet containing the lattice Parameters. - The returned ParameterSet may contain other Parameters than the lattice - Parameters. It is assumed that the lattice parameters are named "a", - "b", "c", "alpha", "beta", "gamma". + The returned ParameterSet may contain other Parameters than the + lattice Parameters. It is assumed that the lattice parameters + are named "a", "b", "c", "alpha", "beta", "gamma". - Lattice must also have the "angunits" attribute, which is either "deg" - or "rad", to signify degrees or radians. + Lattice must also have the "angunits" attribute, which is either + "deg" or "rad", to signify degrees or radians. """ raise NotImplementedError("The must be overloaded") def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ raise NotImplementedError("The must be overloaded") diff --git a/src/diffpy/srfit/structure/bvsrestraint.py b/src/diffpy/srfit/structure/bvsrestraint.py index 28294045..b9c9226c 100644 --- a/src/diffpy/srfit/structure/bvsrestraint.py +++ b/src/diffpy/srfit/structure/bvsrestraint.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Bond-valence sum calculator from SrReal wrapped as a Restraint. This can be used as an addition to a cost function during a structure @@ -21,8 +20,8 @@ __all__ = ["BVSRestraint"] -from diffpy.srfit.fitbase.restraint import Restraint from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase.restraint import Restraint class BVSRestraint(Restraint): @@ -40,7 +39,7 @@ class BVSRestraint(Restraint): (default False). """ - def __init__(self, parset, sig = 1, scaled = False): + def __init__(self, parset, sig=1, scaled=False): """Initialize the Restraint. parset -- SrRealParSet that creates this BVSRestraint. @@ -50,13 +49,14 @@ def __init__(self, parset, sig = 1, scaled = False): (chi^2/numpoints) (bool, default False). """ from diffpy.srreal.bvscalculator import BVSCalculator + self._calc = BVSCalculator() self._parset = parset self.sig = float(sig) self.scaled = bool(scaled) return - def penalty(self, w = 1.0): + def penalty(self, w=1.0): """Calculate the penalty of the restraint. w -- The point-average chi^2 which is optionally used to scale the @@ -71,7 +71,8 @@ def penalty(self, w = 1.0): penalty /= self.sig**2 # Optionally scale by w - if self.scaled: penalty *= w + if self.scaled: + penalty *= w return penalty @@ -81,16 +82,20 @@ def _validate(self): Raises SrFitError if validation fails. """ from numpy import nan + p = self.penalty() if p is None or p is nan: raise SrFitError("Cannot evaluate penalty") v = self._calc.value if len(v) > 1 and not v.any(): - emsg = ("Bond valence sums are all zero. Check atom symbols in " - "the structure or define custom bond-valence parameters.") + emsg = ( + "Bond valence sums are all zero. Check atom symbols in " + "the structure or define custom bond-valence parameters." + ) raise SrFitError(emsg) return # End of class BVSRestraint + # End of file diff --git a/src/diffpy/srfit/structure/cctbxparset.py b/src/diffpy/srfit/structure/cctbxparset.py index 2f6e9640..a61a1d50 100644 --- a/src/diffpy/srfit/structure/cctbxparset.py +++ b/src/diffpy/srfit/structure/cctbxparset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Wrappers for interfacing cctbx crystal with SrFit. This wraps a cctbx.crystal as a ParameterSet with a similar hierarchy, which @@ -32,8 +31,7 @@ from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.structure.basestructureparset import BaseStructureParSet -__all__ = ["CCTBXScattererParSet", "CCTBXUnitCellParSet", - "CCTBXCrystalParSet"] +__all__ = ["CCTBXScattererParSet", "CCTBXUnitCellParSet", "CCTBXCrystalParSet"] class CCTBXScattererParSet(ParameterSet): @@ -63,16 +61,21 @@ def __init__(self, name, strups, idx): self.idx = idx # x, y, z, occupancy - self.addParameter(ParameterAdapter("x", None, self._xyzgetter(0), - self._xyzsetter(0))) - self.addParameter(ParameterAdapter("y", None, self._xyzgetter(1), - self._xyzsetter(1))) - self.addParameter(ParameterAdapter("z", None, self._xyzgetter(2), - self._xyzsetter(2))) - self.addParameter(ParameterAdapter("occupancy", None, self._getocc, - self._setocc)) - self.addParameter(ParameterAdapter("Uiso", None, self._getuiso, - self._setuiso)) + self.addParameter( + ParameterAdapter("x", None, self._xyzgetter(0), self._xyzsetter(0)) + ) + self.addParameter( + ParameterAdapter("y", None, self._xyzgetter(1), self._xyzsetter(1)) + ) + self.addParameter( + ParameterAdapter("z", None, self._xyzgetter(2), self._xyzsetter(2)) + ) + self.addParameter( + ParameterAdapter("occupancy", None, self._getocc, self._setocc) + ) + self.addParameter( + ParameterAdapter("Uiso", None, self._getuiso, self._setuiso) + ) return # Getters and setters @@ -113,8 +116,10 @@ def _getElem(self): element = property(_getElem) + # End class CCTBXScattererParSet + class CCTBXUnitCellParSet(ParameterSet): """A wrapper for cctbx unit_cell object. @@ -133,18 +138,30 @@ def __init__(self, strups): self.strups = strups self._latpars = list(self.strups.stru.unit_cell().parameters()) - self.addParameter(ParameterAdapter("a", None, self._latgetter(0), - self._latsetter(0))) - self.addParameter(ParameterAdapter("b", None, self._latgetter(1), - self._latsetter(1))) - self.addParameter(ParameterAdapter("c", None, self._latgetter(2), - self._latsetter(2))) - self.addParameter(ParameterAdapter("alpha", None, self._latgetter(3), - self._latsetter(3))) - self.addParameter(ParameterAdapter("beta", None, self._latgetter(4), - self._latsetter(4))) - self.addParameter(ParameterAdapter("gamma", None, self._latgetter(5), - self._latsetter(5))) + self.addParameter( + ParameterAdapter("a", None, self._latgetter(0), self._latsetter(0)) + ) + self.addParameter( + ParameterAdapter("b", None, self._latgetter(1), self._latsetter(1)) + ) + self.addParameter( + ParameterAdapter("c", None, self._latgetter(2), self._latsetter(2)) + ) + self.addParameter( + ParameterAdapter( + "alpha", None, self._latgetter(3), self._latsetter(3) + ) + ) + self.addParameter( + ParameterAdapter( + "beta", None, self._latgetter(4), self._latsetter(4) + ) + ) + self.addParameter( + ParameterAdapter( + "gamma", None, self._latgetter(5), self._latsetter(5) + ) + ) return @@ -169,6 +186,7 @@ def f(dummy, value): # FIXME - Special positions should be constant. + class CCTBXCrystalParSet(BaseStructureParSet): """A wrapper for CCTBX structure. @@ -195,14 +213,15 @@ def __init__(self, name, stru): for s in stru.scatterers(): el = s.element_symbol() i = cdict.get(el, 0) - sname = "%s%i"%(el,i) - cdict[el] = i+1 + sname = "%s%i" % (el, i) + cdict[el] = i + 1 scatterer = CCTBXScattererParSet(sname, self, i) self.addParameterSet(scatterer) self.scatterers.append(scatterer) # Constrain the lattice from diffpy.srfit.structure.sgconstraints import _constrainSpaceGroup + symbol = self.getSpaceGroup() _constrainSpaceGroup(self, symbol) @@ -212,8 +231,9 @@ def update(self): """Update the unit_cell to a change in lattice parameters. This remakes the unit cell according to a change in the lattice - parameters. Call this function before using the CCTBXCrystalParSet. The - unit_cell will only be remade if necessary. + parameters. Call this function before using the + CCTBXCrystalParSet. The unit_cell will only be remade if + necessary. """ if not self._update: return @@ -224,16 +244,15 @@ def update(self): # Create the symmetry object from cctbx.crystal import symmetry + symm = symmetry( - unit_cell = self.unitcell._latpars, - space_group_symbol = sgn - ) + unit_cell=self.unitcell._latpars, space_group_symbol=sgn + ) # Now the new structure newstru = stru.__class__( - crystal_symmetry = symm, - scatterers = stru.scatterers() - ) + crystal_symmetry=symm, scatterers=stru.scatterers() + ) self.unitcell._latpars = list(newstru.unit_cell().parameters()) @@ -256,10 +275,10 @@ def getLattice(self): def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ return self.scatterers @@ -270,5 +289,4 @@ def getSpaceGroup(self): return t.lookup_symbol() - # End class CCTBXCrystalParSet diff --git a/src/diffpy/srfit/structure/diffpyparset.py b/src/diffpy/srfit/structure/diffpyparset.py index 5d6239b9..3c0f7a41 100644 --- a/src/diffpy/srfit/structure/diffpyparset.py +++ b/src/diffpy/srfit/structure/diffpyparset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Adapters for interfacing a diffpy.structure.Structure with SrFit. A diffpy.structure.Structure object is meant to be passed to a @@ -30,8 +29,7 @@ __all__ = ["DiffpyStructureParSet"] -from diffpy.srfit.fitbase.parameter import ParameterProxy -from diffpy.srfit.fitbase.parameter import ParameterAdapter +from diffpy.srfit.fitbase.parameter import ParameterAdapter, ParameterProxy from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.structure.srrealparset import SrRealParSet from diffpy.srfit.util.argbinders import bind2nd @@ -92,12 +90,15 @@ def __init__(self, name, atom): self.atom = atom a = atom # x, y, z, occupancy - self.addParameter(ParameterAdapter("x", a, - _xyzgetter(0), _xyzsetter(0))) - self.addParameter(ParameterAdapter("y", a, - _xyzgetter(1), _xyzsetter(1))) - self.addParameter(ParameterAdapter("z", a, - _xyzgetter(2), _xyzsetter(2))) + self.addParameter( + ParameterAdapter("x", a, _xyzgetter(0), _xyzsetter(0)) + ) + self.addParameter( + ParameterAdapter("y", a, _xyzgetter(1), _xyzsetter(1)) + ) + self.addParameter( + ParameterAdapter("z", a, _xyzgetter(2), _xyzsetter(2)) + ) occupancy = ParameterAdapter("occupancy", a, attr="occupancy") self.addParameter(occupancy) self.addParameter(ParameterProxy("occ", occupancy)) @@ -148,6 +149,7 @@ def _setElem(self, el): element = property(_getElem, _setElem, "type of atom") + # End class DiffpyAtomParSet @@ -183,23 +185,36 @@ def __init__(self, lattice): self.angunits = "deg" self.lattice = lattice lat = lattice - self.addParameter(ParameterAdapter("a", lat, - _latgetter("a"), _latsetter("a"))) - self.addParameter(ParameterAdapter("b", lat, - _latgetter("b"), _latsetter("b"))) - self.addParameter(ParameterAdapter("c", lat, - _latgetter("c"), _latsetter("c"))) - self.addParameter(ParameterAdapter("alpha", lat, _latgetter("alpha"), - _latsetter("alpha"))) - self.addParameter(ParameterAdapter("beta", lat, _latgetter("beta"), - _latsetter("beta"))) - self.addParameter(ParameterAdapter("gamma", lat, _latgetter("gamma"), - _latsetter("gamma"))) + self.addParameter( + ParameterAdapter("a", lat, _latgetter("a"), _latsetter("a")) + ) + self.addParameter( + ParameterAdapter("b", lat, _latgetter("b"), _latsetter("b")) + ) + self.addParameter( + ParameterAdapter("c", lat, _latgetter("c"), _latsetter("c")) + ) + self.addParameter( + ParameterAdapter( + "alpha", lat, _latgetter("alpha"), _latsetter("alpha") + ) + ) + self.addParameter( + ParameterAdapter( + "beta", lat, _latgetter("beta"), _latsetter("beta") + ) + ) + self.addParameter( + ParameterAdapter( + "gamma", lat, _latgetter("gamma"), _latsetter("gamma") + ) + ) return def __repr__(self): return repr(self.lattice) + # End class DiffpyLatticeParSet @@ -241,7 +256,7 @@ def __init__(self, name, stru): el = el.replace("-", "m") i = cdict.get(el, 0) aname = "%s%i" % (el, i) - cdict[el] = i+1 + cdict[el] = i + 1 atom = DiffpyAtomParSet(aname, a) self.addParameterSet(atom) self.atoms.append(atom) @@ -259,27 +274,30 @@ def getLattice(self): def canAdapt(self, stru): """Return whether the structure can be adapted by this class.""" from diffpy.structure import Structure + return isinstance(stru, Structure) def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ return self.atoms def _getSrRealStructure(self): """Get the structure object for use with SrReal calculators. - If this is periodic, then return the structure, otherwise, pass it - inside of a nosymmetry wrapper. This takes the extra step of wrapping - the structure in a nometa wrapper. + If this is periodic, then return the structure, otherwise, pass + it inside of a nosymmetry wrapper. This takes the extra step of + wrapping the structure in a nometa wrapper. """ from diffpy.srreal.structureadapter import nometa + stru = SrRealParSet._getSrRealStructure(self) return nometa(stru) + # End class DiffpyStructureParSet diff --git a/src/diffpy/srfit/structure/objcrystparset.py b/src/diffpy/srfit/structure/objcrystparset.py index daaa17f5..3dab67f2 100644 --- a/src/diffpy/srfit/structure/objcrystparset.py +++ b/src/diffpy/srfit/structure/objcrystparset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Wrappers for adapting pyobjcryst.crystal.Crystal to a srfit ParameterSet. This will adapt a Crystal or Molecule object from pyobjcryst into the @@ -40,13 +39,20 @@ __all__ = ["ObjCrystMoleculeParSet", "ObjCrystCrystalParSet"] import numpy - -from pyobjcryst.molecule import GetBondLength, GetBondAngle, GetDihedralAngle -from pyobjcryst.molecule import StretchModeBondLength, StretchModeBondAngle -from pyobjcryst.molecule import StretchModeTorsion - -from diffpy.srfit.fitbase.parameter import Parameter, ParameterAdapter -from diffpy.srfit.fitbase.parameter import ParameterProxy +from pyobjcryst.molecule import ( + GetBondAngle, + GetBondLength, + GetDihedralAngle, + StretchModeBondAngle, + StretchModeBondLength, + StretchModeTorsion, +) + +from diffpy.srfit.fitbase.parameter import ( + Parameter, + ParameterAdapter, + ParameterProxy, +) from diffpy.srfit.fitbase.parameterset import ParameterSet from diffpy.srfit.structure.srrealparset import SrRealParSet @@ -81,11 +87,10 @@ def __init__(self, name, scat, parent): self.parent = parent # x, y, z, occ - self.addParameter(ParameterAdapter("x", self.scat, attr = "X")) - self.addParameter(ParameterAdapter("y", self.scat, attr = "Y")) - self.addParameter(ParameterAdapter("z", self.scat, attr = "Z")) - self.addParameter(ParameterAdapter("occ", self.scat, attr = - "Occupancy")) + self.addParameter(ParameterAdapter("x", self.scat, attr="X")) + self.addParameter(ParameterAdapter("y", self.scat, attr="Y")) + self.addParameter(ParameterAdapter("z", self.scat, attr="Z")) + self.addParameter(ParameterAdapter("occ", self.scat, attr="Occupancy")) return def isDummy(self): @@ -99,6 +104,7 @@ def hasScatterers(self): # End class ObjCrystScattererParSet + class ObjCrystAtomParSet(ObjCrystScattererParSet): """A adaptor for a pyobjcryst.Atom. @@ -131,15 +137,15 @@ def __init__(self, name, atom, parent): sp = atom.GetScatteringPower() # The B-parameters - self.addParameter(ParameterAdapter("Biso", sp, attr = "Biso")) - self.addParameter(ParameterAdapter("B11", sp, attr = "B11")) - self.addParameter(ParameterAdapter("B22", sp, attr = "B22")) - self.addParameter(ParameterAdapter("B33", sp, attr = "B33")) - B12 = ParameterAdapter("B12", sp, attr = "B12") + self.addParameter(ParameterAdapter("Biso", sp, attr="Biso")) + self.addParameter(ParameterAdapter("B11", sp, attr="B11")) + self.addParameter(ParameterAdapter("B22", sp, attr="B22")) + self.addParameter(ParameterAdapter("B33", sp, attr="B33")) + B12 = ParameterAdapter("B12", sp, attr="B12") B21 = ParameterProxy("B21", B12) - B13 = ParameterAdapter("B13", sp, attr = "B13") + B13 = ParameterAdapter("B13", sp, attr="B13") B31 = ParameterProxy("B31", B13) - B23 = ParameterAdapter("B23", sp, attr = "B23") + B23 = ParameterAdapter("B23", sp, attr="B23") B32 = ParameterProxy("B32", B23) self.addParameter(B12) self.addParameter(B21) @@ -159,8 +165,10 @@ def _getElem(self): element = property(_getElem) + # End class ObjCrystAtomParSet + class ObjCrystMoleculeParSet(ObjCrystScattererParSet): """A adaptor for a pyobjcryst.Molecule. @@ -183,7 +191,7 @@ class ObjCrystMoleculeParSet(ObjCrystScattererParSet): diffpy.srfit.fitbase.parameterset.ParameterSet """ - def __init__(self, name, molecule, parent = None): + def __init__(self, name, molecule, parent=None): """Initialize. name -- The name of the scatterer @@ -193,11 +201,11 @@ def __init__(self, name, molecule, parent = None): ObjCrystScattererParSet.__init__(self, name, molecule, parent) self.stru = molecule - # Add orientiation quaternion - self.addParameter(ParameterAdapter("q0", self.scat, attr = "Q0")) - self.addParameter(ParameterAdapter("q1", self.scat, attr = "Q1")) - self.addParameter(ParameterAdapter("q2", self.scat, attr = "Q2")) - self.addParameter(ParameterAdapter("q3", self.scat, attr = "Q3")) + # Add orientation quaternion + self.addParameter(ParameterAdapter("q0", self.scat, attr="Q0")) + self.addParameter(ParameterAdapter("q1", self.scat, attr="Q1")) + self.addParameter(ParameterAdapter("q2", self.scat, attr="Q2")) + self.addParameter(ParameterAdapter("q3", self.scat, attr="Q3")) # Wrap the MolAtoms within the molecule self.atoms = [] @@ -209,7 +217,7 @@ def __init__(self, name, molecule, parent = None): if not name: raise AttributeError("Each MolAtom must have a name") if name in anames: - raise AttributeError("MolAtom name '%s' is duplicated"%name) + raise AttributeError("MolAtom name '%s' is duplicated" % name) atom = ObjCrystMolAtomParSet(name, a, self) atom.molecule = self @@ -223,10 +231,11 @@ def __init__(self, name, molecule, parent = None): def canAdapt(self, stru): """Return whether the structure can be adapted by this class.""" from pyobjcryst.molecule import Molecule + return isinstance(stru, Molecule) # Part of SrRealParSet interface - def useSymmetry(self, use = True): + def useSymmetry(self, use=True): """Set this structure to use symmetry. This structure object does not support symmetry. @@ -245,8 +254,8 @@ def usingSymmetry(self): def _getSrRealStructure(self): """Get the structure object for use with SrReal calculators. - Molecule objects are never periodic. Return the object and let the - SrReal adapters do the proper thing. + Molecule objects are never periodic. Return the object and let + the SrReal adapters do the proper thing. """ return self.stru @@ -265,18 +274,18 @@ def getLattice(self): def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ return self.atoms def wrapRestraints(self): """Wrap the restraints implicit to the molecule. - This will wrap MolBonds, MolBondAngles and MolDihedralAngles of the - Molecule as ObjCrystMoleculeRestraint objects. + This will wrap MolBonds, MolBondAngles and MolDihedralAngles of + the Molecule as ObjCrystMoleculeRestraint objects. """ # Wrap restraints. Restraints wrapped in this way cannot be modified # from within this class. @@ -316,18 +325,17 @@ def wrapStretchModeParameters(self): atom1 = getattr(self, name1) atom2 = getattr(self, name2) - par = ObjCrystBondLengthParameter(name, atom1, atom2, mode = mode) + par = ObjCrystBondLengthParameter(name, atom1, atom2, mode=mode) atoms = [] for a in mode.GetAtoms(): name = a.GetName() - atoms.append( getattr(self, name) ) + atoms.append(getattr(self, name)) par.AddAtoms(atoms) self.addParameter(par) - for mode in self.scat.GetStretchModeBondAngleList(): name1 = mode.mpAtom0.GetName() name2 = mode.mpAtom1.GetName() @@ -339,21 +347,23 @@ def wrapStretchModeParameters(self): atom2 = getattr(self, name2) atom3 = getattr(self, name3) - par = ObjCrystBondAngleParameter(name, atom1, atom2, atom3, mode = - mode) + par = ObjCrystBondAngleParameter( + name, atom1, atom2, atom3, mode=mode + ) atoms = [] for a in mode.GetAtoms(): name = a.GetName() - atoms.append( getattr(self, name) ) + atoms.append(getattr(self, name)) par.AddAtoms(atoms) self.addParameter(par) return - def restrainBondLength(self, atom1, atom2, length, sigma, delta, scaled = - False): + def restrainBondLength( + self, atom1, atom2, length, sigma, delta, scaled=False + ): """Add a bond length restraint. This creates an instance of ObjCrystBondLengthRestraint and adds it to @@ -371,13 +381,16 @@ def restrainBondLength(self, atom1, atom2, length, sigma, delta, scaled = Returns the ObjCrystBondLengthRestraint object for use with the 'unrestrain' method. """ - res = ObjCrystBondLengthRestraint(atom1, atom2, length, sigma, delta, scaled) + res = ObjCrystBondLengthRestraint( + atom1, atom2, length, sigma, delta, scaled + ) self._restraints.add(res) return res - def restrainBondLengthParameter(self, par, length, sigma, delta, scaled = - False): + def restrainBondLengthParameter( + self, par, length, sigma, delta, scaled=False + ): """Add a bond length restraint. This creates an instance of ObjCrystBondLengthRestraint and adds it to @@ -394,11 +407,13 @@ def restrainBondLengthParameter(self, par, length, sigma, delta, scaled = Returns the ObjCrystBondLengthRestraint object for use with the 'unrestrain' method. """ - return self.restrainBondLength(par.atom1, par.atom2, length, sigma, - delta, scaled) + return self.restrainBondLength( + par.atom1, par.atom2, length, sigma, delta, scaled + ) - def restrainBondAngle(self, atom1, atom2, atom3, angle, sigma, delta, - scaled = False): + def restrainBondAngle( + self, atom1, atom2, atom3, angle, sigma, delta, scaled=False + ): """Add a bond angle restraint. This creates an instance of ObjCrystBondAngleRestraint and adds it to @@ -418,14 +433,16 @@ def restrainBondAngle(self, atom1, atom2, atom3, angle, sigma, delta, Returns the ObjCrystBondAngleRestraint object for use with the 'unrestrain' method. """ - res = ObjCrystBondAngleRestraint(atom1, atom2, atom3, angle, sigma, - delta, scaled) + res = ObjCrystBondAngleRestraint( + atom1, atom2, atom3, angle, sigma, delta, scaled + ) self._restraints.add(res) return res - def restrainBondAngleParameter(self, par, angle, sigma, delta, - scaled = False): + def restrainBondAngleParameter( + self, par, angle, sigma, delta, scaled=False + ): """Add a bond angle restraint. This creates an instance of ObjCrystBondAngleRestraint and adds it to @@ -442,11 +459,13 @@ def restrainBondAngleParameter(self, par, angle, sigma, delta, Returns the ObjCrystBondAngleRestraint object for use with the 'unrestrain' method. """ - return self.restrainBondAngle(par.atom1, par.atom2, par.atom3, angle, - sigma, delta, scaled) + return self.restrainBondAngle( + par.atom1, par.atom2, par.atom3, angle, sigma, delta, scaled + ) - def restrainDihedralAngle(self, atom1, atom2, atom3, atom4, angle, sigma, - delta, scaled = False): + def restrainDihedralAngle( + self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False + ): """Add a dihedral angle restraint. This creates an instance of ObjCrystDihedralAngleRestraint and adds it @@ -466,14 +485,16 @@ def restrainDihedralAngle(self, atom1, atom2, atom3, atom4, angle, sigma, Returns the ObjCrystDihedralAngleRestraint object for use with the 'unrestrain' method. """ - res = ObjCrystDihedralAngleRestraint(atom1, atom2, atom3, atom4, angle, - sigma, delta, scaled) + res = ObjCrystDihedralAngleRestraint( + atom1, atom2, atom3, atom4, angle, sigma, delta, scaled + ) self._restraints.add(res) return res - def restrainDihedralAngleParameter(self, par, angle, sigma, delta, - scaled = False): + def restrainDihedralAngleParameter( + self, par, angle, sigma, delta, scaled=False + ): """Add a dihedral angle restraint. This creates an instance of ObjCrystDihedralAngleRestraint and adds it @@ -491,11 +512,20 @@ def restrainDihedralAngleParameter(self, par, angle, sigma, delta, Returns the ObjCrystDihedralAngleRestraint object for use with the 'unrestrain' method. """ - return self.restrainDihedralAngle(par.atom1, par.atom2, par.atom3, - par.atom4, angle, sigma, delta, scaled) - - def addBondLengthParameter(self, name, atom1, atom2, value = None, const = - False): + return self.restrainDihedralAngle( + par.atom1, + par.atom2, + par.atom3, + par.atom4, + angle, + sigma, + delta, + scaled, + ) + + def addBondLengthParameter( + self, name, atom1, atom2, value=None, const=False + ): """Add a bond length to the Molecule. This creates a ObjCrystBondLengthParameter to the @@ -518,8 +548,9 @@ def addBondLengthParameter(self, name, atom1, atom2, value = None, const = return par - def addBondAngleParameter(self, name, atom1, atom2, atom3, value = None, - const = False): + def addBondAngleParameter( + self, name, atom1, atom2, atom3, value=None, const=False + ): """Add a bond angle to the Molecule. This creates a ObjCrystBondAngleParameter to the ObjCrystMoleculeParSet @@ -539,14 +570,16 @@ def addBondAngleParameter(self, name, atom1, atom2, atom3, value = None, Returns the new ObjCrystBondAngleParameter. """ - par = ObjCrystBondAngleParameter(name, atom1, atom2, atom3, value, - const) + par = ObjCrystBondAngleParameter( + name, atom1, atom2, atom3, value, const + ) self.addParameter(par) return par - def addDihedralAngleParameter(self, name, atom1, atom2, atom3, atom4, value - = None, const = False): + def addDihedralAngleParameter( + self, name, atom1, atom2, atom3, atom4, value=None, const=False + ): """Add a dihedral angle to the Molecule. This creates a ObjCrystDihedralAngleParameter to the @@ -568,14 +601,17 @@ def addDihedralAngleParameter(self, name, atom1, atom2, atom3, atom4, value Returns the new ObjCrystDihedralAngleParameter. """ - par = ObjCrystDihedralAngleParameter(name, atom1, atom2, atom3, atom4, - value, const) + par = ObjCrystDihedralAngleParameter( + name, atom1, atom2, atom3, atom4, value, const + ) self.addParameter(par) return par + # End class ObjCrystMoleculeParSet + class ObjCrystMolAtomParSet(ObjCrystScattererParSet): """A adaptor for an pyobjcryst.molecule.MolAtom. @@ -612,15 +648,15 @@ def __init__(self, name, scat, parent): # Only wrap this if there is a scattering power if sp is not None: - self.addParameter(ParameterAdapter("Biso", sp, attr = "Biso")) - self.addParameter(ParameterAdapter("B11", sp, attr = "B11")) - self.addParameter(ParameterAdapter("B22", sp, attr = "B22")) - self.addParameter(ParameterAdapter("B33", sp, attr = "B33")) - B12 = ParameterAdapter("B12", sp, attr = "B12") + self.addParameter(ParameterAdapter("Biso", sp, attr="Biso")) + self.addParameter(ParameterAdapter("B11", sp, attr="B11")) + self.addParameter(ParameterAdapter("B22", sp, attr="B22")) + self.addParameter(ParameterAdapter("B33", sp, attr="B33")) + B12 = ParameterAdapter("B12", sp, attr="B12") B21 = ParameterProxy("B21", B12) - B13 = ParameterAdapter("B13", sp, attr = "B13") + B13 = ParameterAdapter("B13", sp, attr="B13") B31 = ParameterProxy("B31", B13) - B23 = ParameterAdapter("B23", sp, attr = "B23") + B23 = ParameterAdapter("B23", sp, attr="B23") B32 = ParameterProxy("B32", B23) self.addParameter(B12) self.addParameter(B21) @@ -645,8 +681,10 @@ def isDummy(self): """Indicate whether this atom is a dummy atom.""" return self.scat.IsDummy() + # End class ObjCrystMolAtomParSet + class ObjCrystMoleculeRestraint(object): """Base class for adapting pyobjcryst Molecule restraints to srfit. @@ -662,7 +700,7 @@ class ObjCrystMoleculeRestraint(object): False). """ - def __init__(self, res, scaled = False): + def __init__(self, res, scaled=False): """Create a Restraint-like from a pyobjcryst Molecule restraint. res -- The pyobjcryst Molecule restraint. @@ -674,7 +712,7 @@ def __init__(self, res, scaled = False): self.scaled = scaled return - def penalty(self, w = 1.0): + def penalty(self, w=1.0): """Calculate the penalty of the restraint. w -- The point-average chi^2 which is optionally used to scale the @@ -685,8 +723,10 @@ def penalty(self, w = 1.0): penalty *= w return penalty + # End class ObjCrystMoleculeRestraint + class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint): """Restrain the distance between two atoms. @@ -702,7 +742,7 @@ class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint): False) """ - def __init__(self, atom1, atom2, length, sigma, delta, scaled = False): + def __init__(self, atom1, atom2, length, sigma, delta, scaled=False): """Create a bond length restraint. atom1 -- First atom (ObjCrystMolAtomParSet) in the bond @@ -724,15 +764,23 @@ def __init__(self, atom1, atom2, length, sigma, delta, scaled = False): return # Give access to the parameters of the restraint - length = property( lambda self: self.res.GetLength0(), - lambda self, val: self.res.SetLength0(val)) - sigma = property( lambda self: self.res.GetLengthSigma(), - lambda self, val: self.res.SetLengthSigma(val)) - delta = property( lambda self: self.res.GetLengthDelta(), - lambda self, val: self.res.SetLengthDelta(val)) + length = property( + lambda self: self.res.GetLength0(), + lambda self, val: self.res.SetLength0(val), + ) + sigma = property( + lambda self: self.res.GetLengthSigma(), + lambda self, val: self.res.SetLengthSigma(val), + ) + delta = property( + lambda self: self.res.GetLengthDelta(), + lambda self, val: self.res.SetLengthDelta(val), + ) + # End class ObjCrystBondLengthRestraint + class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint): """Restrain the angle defined by three atoms. @@ -749,8 +797,7 @@ class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint): False) """ - def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled = - False): + def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled=False): """Create a bond angle restraint. atom1 -- First atom (ObjCrystMolAtomParSet) in the bond angle @@ -769,22 +816,31 @@ def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled = self.atom3 = atom3 m = self.atom1.scat.GetMolecule() - res = m.AddBondAngle(atom1.scat, atom2.scat, atom3.scat, angle, - sigma, delta) + res = m.AddBondAngle( + atom1.scat, atom2.scat, atom3.scat, angle, sigma, delta + ) ObjCrystMoleculeRestraint.__init__(self, res, scaled) return # Give access to the parameters of the restraint - angle = property( lambda self: self.res.GetAngle0(), - lambda self, val: self.res.SetAngle0(val)) - sigma = property( lambda self: self.res.GetAngleSigma(), - lambda self, val: self.res.SetAngleSigma(val)) - delta = property( lambda self: self.res.GetAngleDelta(), - lambda self, val: self.res.SetAngleDelta(val)) + angle = property( + lambda self: self.res.GetAngle0(), + lambda self, val: self.res.SetAngle0(val), + ) + sigma = property( + lambda self: self.res.GetAngleSigma(), + lambda self, val: self.res.SetAngleSigma(val), + ) + delta = property( + lambda self: self.res.GetAngleDelta(), + lambda self, val: self.res.SetAngleDelta(val), + ) + # End class ObjCrystBondAngleRestraint + class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint): """Restrain the dihedral (torsion) angle defined by four atoms. @@ -802,8 +858,9 @@ class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint): False) """ - def __init__(self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled - = False): + def __init__( + self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False + ): """Create a dihedral angle restraint. atom1 -- First atom (ObjCrystMolAtomParSet) in the angle @@ -823,22 +880,31 @@ def __init__(self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled self.atom4 = atom4 m = self.atom1.scat.GetMolecule() - res = m.AddDihedralAngle(atom1.scat, atom2.scat, atom3.scat, - atom4.scat, angle, sigma, delta) + res = m.AddDihedralAngle( + atom1.scat, atom2.scat, atom3.scat, atom4.scat, angle, sigma, delta + ) ObjCrystMoleculeRestraint.__init__(self, res, scaled) return # Give access to the parameters of the restraint - angle = property( lambda self: self.res.GetAngle0(), - lambda self, val: self.res.SetAngle0(val)) - sigma = property( lambda self: self.res.GetAngleSigma(), - lambda self, val: self.res.SetAngleSigma(val)) - delta = property( lambda self: self.res.GetAngleDelta(), - lambda self, val: self.res.SetAngleDelta(val)) + angle = property( + lambda self: self.res.GetAngle0(), + lambda self, val: self.res.SetAngle0(val), + ) + sigma = property( + lambda self: self.res.GetAngleSigma(), + lambda self, val: self.res.SetAngleSigma(val), + ) + delta = property( + lambda self: self.res.GetAngleDelta(), + lambda self, val: self.res.SetAngleDelta(val), + ) + # End class ObjCrystDihedralAngleRestraint + class StretchModeParameter(Parameter): """Partial Parameter class encapsulating pyobjcryst stretch modes. @@ -855,7 +921,7 @@ class StretchModeParameter(Parameter): value of the parameter (bool, default True). """ - def __init__(self, name, value = None, const = False): + def __init__(self, name, value=None, const=False): """Initialization. name -- The name of this Parameter (must be a valid attribute @@ -890,9 +956,10 @@ def addAtoms(self, atomlist): """Associate ObjCrystMolAtomParSets with the Parameter. This will associate additional ObjCrystMolAtomParSets with the - Parameter. These will be mutated in the exact same way as the primary - mutated ObjCrystMolAtomParSet. This is useful when a group of atoms - should move rigidly in response to a change in a bond property. + Parameter. These will be mutated in the exact same way as the + primary mutated ObjCrystMolAtomParSet. This is useful when a + group of atoms should move rigidly in response to a change in a + bond property. """ if not hasattr(atomlist, "__iter__"): atomlist = [atomlist] @@ -912,9 +979,9 @@ def addAtoms(self, atomlist): def notify(self, other=()): """Notify all mutated Parameters and observers. - Some of the mutated parameters will be observing us. At the same time - we need to observe them. Observable won't let us do both, so we notify - the Parameters that we mutate directly. + Some of the mutated parameters will be observing us. At the same + time we need to observe them. Observable won't let us do both, + so we notify the Parameters that we mutate directly. """ noneother = () # Notify the atoms that have moved @@ -931,8 +998,10 @@ def notify(self, other=()): Parameter.notify(self, other) return + # End class StretchModeParameter + class ObjCrystBondLengthParameter(StretchModeParameter): """Class for abstracting a bond length in a Molecule to a Parameter. @@ -980,8 +1049,7 @@ class ObjCrystBondLengthParameter(StretchModeParameter): used by some optimizers when the Parameter is varied. """ - def __init__(self, name, atom1, atom2, value = None, const = False, mode = - None): + def __init__(self, name, atom1, atom2, value=None, const=False, mode=None): """Create a ObjCrystBondLengthParameter. name -- The name of the ObjCrystBondLengthParameter @@ -1024,7 +1092,7 @@ def __init__(self, name, atom1, atom2, value = None, const = False, mode = return - def setConst(self, const = True, value = None): + def setConst(self, const=True, value=None): """Toggle the Parameter as constant. This sets the underlying ObjCrystMolAtomParSet positions const as well. @@ -1046,9 +1114,9 @@ def setConst(self, const = True, value = None): def getValue(self): """This calculates the value if it might have been changed. - There is no guarantee that the ObjCrystMolAtomParSets underlying the - bond won't change, so the bond length is calculated if necessary each - time this is called. + There is no guarantee that the ObjCrystMolAtomParSets underlying + the bond won't change, so the bond length is calculated if + necessary each time this is called. """ if self._value is None: val = GetBondLength(self.atom1.scat, self.atom2.scat) @@ -1059,6 +1127,7 @@ def getValue(self): # End class ObjCrystBondLengthParameter + class ObjCrystBondAngleParameter(StretchModeParameter): """Class for abstracting a bond angle in a Molecule to a Parameter. @@ -1092,8 +1161,9 @@ class ObjCrystBondAngleParameter(StretchModeParameter): used by some optimizers when the Parameter is varied. """ - def __init__(self, name, atom1, atom2, atom3, value = None, const = False, - mode = None): + def __init__( + self, name, atom1, atom2, atom3, value=None, const=False, mode=None + ): """Create a ObjCrystBondAngleParameter. name -- The name of the ObjCrystBondAngleParameter. @@ -1114,8 +1184,9 @@ def __init__(self, name, atom1, atom2, atom3, value = None, const = False, # Create the stretch mode self.mode = mode if mode is None: - self.mode = StretchModeBondAngle(atom1.scat, atom2.scat, - atom3.scat, None) + self.mode = StretchModeBondAngle( + atom1.scat, atom2.scat, atom3.scat, None + ) # We only add the last atom. This is the one that will move self.mode.AddAtom(atom3.scat) self.matoms = set([atom3]) @@ -1139,7 +1210,7 @@ def __init__(self, name, atom1, atom2, atom3, value = None, const = False, return - def setConst(self, const = True, value = None): + def setConst(self, const=True, value=None): """Toggle the Parameter as constant. This sets the underlying ObjCrystMolAtomParSet positions const as well. @@ -1160,19 +1231,22 @@ def setConst(self, const = True, value = None): def getValue(self): """This calculates the value if it might have been changed. - There is no guarantee that the MolAtoms underlying the bond angle won't - change, so the bond angle is calculated if necessary each time this is - called. + There is no guarantee that the MolAtoms underlying the bond + angle won't change, so the bond angle is calculated if necessary + each time this is called. """ if self._value is None: - val = GetBondAngle(self.atom1.scat, self.atom2.scat, - self.atom3.scat) + val = GetBondAngle( + self.atom1.scat, self.atom2.scat, self.atom3.scat + ) Parameter.setValue(self, val) return self._value + # End class ObjCrystBondAngleParameter + class ObjCrystDihedralAngleParameter(StretchModeParameter): """Class for abstracting a dihedral angle in a Molecule to a Parameter. @@ -1209,8 +1283,17 @@ class ObjCrystDihedralAngleParameter(StretchModeParameter): used by some optimizers when the Parameter is varied. """ - def __init__(self, name, atom1, atom2, atom3, atom4, value = None, const = - False, mode = None): + def __init__( + self, + name, + atom1, + atom2, + atom3, + atom4, + value=None, + const=False, + mode=None, + ): """Create a ObjCrystDihedralAngleParameter. name -- The name of the ObjCrystDihedralAngleParameter @@ -1253,14 +1336,15 @@ def __init__(self, name, atom1, atom2, atom3, atom4, value = None, const = # We do this last so the atoms are defined before we set any values. if value is None: - value = GetDihedralAngle(atom1.scat, atom2.scat, atom3.scat, - atom4.scat) + value = GetDihedralAngle( + atom1.scat, atom2.scat, atom3.scat, atom4.scat + ) StretchModeParameter.__init__(self, name, value, const) self.setConst(const) return - def setConst(self, const = True, value = None): + def setConst(self, const=True, value=None): """Toggle the Parameter as constant. This sets the underlying ObjCrystMolAtomParSet positions const as well. @@ -1281,19 +1365,25 @@ def setConst(self, const = True, value = None): def getValue(self): """This calculates the value if it might have been changed. - There is no guarantee that the ObjCrystMolAtomParSets underlying the - dihedral angle won't change from some other Parameter, so the value is - recalculated each time. + There is no guarantee that the ObjCrystMolAtomParSets underlying + the dihedral angle won't change from some other Parameter, so + the value is recalculated each time. """ if self._value is None: - val = GetDihedralAngle(self.atom1.scat, self.atom2.scat, - self.atom3.scat, self.atom4.scat) + val = GetDihedralAngle( + self.atom1.scat, + self.atom2.scat, + self.atom3.scat, + self.atom4.scat, + ) Parameter.setValue(self, val) return self._value + # End class ObjCrystDihedralAngleParameter + class ObjCrystCrystalParSet(SrRealParSet): """A adaptor for pyobjcryst.crystal.Crystal instance. @@ -1332,14 +1422,12 @@ def __init__(self, name, cryst): self.stru = cryst self._sgpars = None - self.addParameter(ParameterAdapter("a", self.stru, attr = "a")) - self.addParameter(ParameterAdapter("b", self.stru, attr = "b")) - self.addParameter(ParameterAdapter("c", self.stru, attr = "c")) - self.addParameter(ParameterAdapter("alpha", self.stru, attr = - "alpha")) - self.addParameter(ParameterAdapter("beta", self.stru, attr = "beta")) - self.addParameter(ParameterAdapter("gamma", self.stru, attr = - "gamma")) + self.addParameter(ParameterAdapter("a", self.stru, attr="a")) + self.addParameter(ParameterAdapter("b", self.stru, attr="b")) + self.addParameter(ParameterAdapter("c", self.stru, attr="c")) + self.addParameter(ParameterAdapter("alpha", self.stru, attr="alpha")) + self.addParameter(ParameterAdapter("beta", self.stru, attr="beta")) + self.addParameter(ParameterAdapter("gamma", self.stru, attr="gamma")) # Now we must loop over the scatterers and create parameter sets from # them. @@ -1352,7 +1440,7 @@ def __init__(self, name, cryst): if not name: raise ValueError("Each Scatterer must have a name") if name in snames: - raise ValueError("Scatterer name '%s' is duplicated"%name) + raise ValueError("Scatterer name '%s' is duplicated" % name) # Now create the proper object cname = s.GetClassName() @@ -1361,7 +1449,7 @@ def __init__(self, name, cryst): elif cname == "Molecule": parset = ObjCrystMoleculeParSet(name, s, self) else: - raise TypeError("Unrecognized scatterer '%s'"%cname) + raise TypeError("Unrecognized scatterer '%s'" % cname) self.addParameterSet(parset) self.scatterers.append(parset) @@ -1375,11 +1463,18 @@ def _constrainSpaceGroup(self): return self._sgpars sg = self._createSpaceGroup(self.stru.GetSpaceGroup()) from diffpy.srfit.structure.sgconstraints import _constrainAsSpaceGroup + adpsymbols = ["B11", "B22", "B33", "B12", "B13", "B23"] isosymbol = "Biso" sgoffset = [0, 0, 0] - self._sgpars = _constrainAsSpaceGroup(self, sg, self.scatterers, - sgoffset, adpsymbols = adpsymbols, isosymbol = isosymbol) + self._sgpars = _constrainAsSpaceGroup( + self, + sg, + self.scatterers, + sgoffset, + adpsymbols=adpsymbols, + isosymbol=isosymbol, + ) return self._sgpars sgpars = property(_constrainSpaceGroup) @@ -1391,15 +1486,17 @@ def _createSpaceGroup(sgobjcryst): sgobjcryst -- A pyobjcryst.spacegroup.SpaceGroup instance. This uses the actual space group operations from the - pyobjcryst.spacegroup.SpaceGroup instance so there is no abiguity about + pyobjcryst.spacegroup.SpaceGroup instance so there is no ambiguity about the actual space group. """ import copy + from diffpy.structure.spacegroups import GetSpaceGroup, SymOp + name = sgobjcryst.GetName() extnstr = ":%s" % sgobjcryst.GetExtension() if name.endswith(extnstr): - name = name[:-len(extnstr)] + name = name[: -len(extnstr)] # Get whatever spacegroup we can get by name. This will set the proper # crystal system. Creating a copy of the singleton from GetSpaceGroup, @@ -1415,7 +1512,7 @@ def _createSpaceGroup(sgobjcryst): for shift, rot in symops: tv = trans + shift tv -= numpy.floor(tv) - sg.symop_list.append( SymOp(rot, tv) ) + sg.symop_list.append(SymOp(rot, tv)) if sgobjcryst.IsCentrosymmetric(): center = sgobjcryst.GetInversionCenter() @@ -1423,7 +1520,7 @@ def _createSpaceGroup(sgobjcryst): for shift, rot in symops: tv = center - trans - shift tv -= numpy.floor(tv) - sg.symop_list.append( SymOp(-rot , tv) ) + sg.symop_list.append(SymOp(-rot, tv)) return sg @@ -1431,6 +1528,7 @@ def _createSpaceGroup(sgobjcryst): def canAdapt(self, stru): """Return whether the structure can be adapted by this class.""" from pyobjcryst.crystal import Crystal + return isinstance(stru, Crystal) def getLattice(self): @@ -1440,11 +1538,12 @@ def getLattice(self): def getScatterers(self): """Get a list of ParameterSets that represents the scatterers. - The site positions must be accessible from the list entries via the - names "x", "y", and "z". The ADPs must be accessible as well, but the - name and nature of the ADPs (U-factors, B-factors, isotropic, - anisotropic) depends on the adapted structure. + The site positions must be accessible from the list entries via + the names "x", "y", and "z". The ADPs must be accessible as + well, but the name and nature of the ADPs (U-factors, B-factors, + isotropic, anisotropic) depends on the adapted structure. """ return self.scatterers + # End class ObjCrystCrystalParSet diff --git a/src/diffpy/srfit/structure/sgconstraints.py b/src/diffpy/srfit/structure/sgconstraints.py index fe1edc15..c3b0d005 100644 --- a/src/diffpy/srfit/structure/sgconstraints.py +++ b/src/diffpy/srfit/structure/sgconstraints.py @@ -12,22 +12,29 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Code to set space group constraints for a crystal structure.""" import re + import numpy -from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer from diffpy.srfit.fitbase.parameter import ParameterProxy +from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer __all__ = ["constrainAsSpaceGroup"] -def constrainAsSpaceGroup(phase, spacegroup, scatterers = None, - sgoffset = [0, 0, 0], constrainlat = True, constrainadps = True, - adpsymbols = None, isosymbol = "Uiso"): +def constrainAsSpaceGroup( + phase, + spacegroup, + scatterers=None, + sgoffset=[0, 0, 0], + constrainlat=True, + constrainadps=True, + adpsymbols=None, + isosymbol="Uiso", +): """Constrain the structure to the space group. This applies space group constraints to a StructureParSet with P1 @@ -51,7 +58,7 @@ def constrainAsSpaceGroup(phase, spacegroup, scatterers = None, U22, etc.). The names must be given in the same order as stdUsymbols. isosymbol -- Symbol for isotropic ADP (default "Uiso"). If None, - isotropic ADPs will be constrainted via the anisotropic ADPs. + isotropic ADPs will be constrained via the anisotropic ADPs. New Parameters that are used in constraints are created within a SpaceGroupParameters object, which is returned from this function. @@ -84,14 +91,30 @@ def constrainAsSpaceGroup(phase, spacegroup, scatterers = None, sg = spacegroup if not isinstance(spacegroup, SpaceGroup): sg = GetSpaceGroup(spacegroup) - sgp = _constrainAsSpaceGroup(phase, sg, scatterers, sgoffset, - constrainlat, constrainadps, adpsymbols, isosymbol) + sgp = _constrainAsSpaceGroup( + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ) return sgp -def _constrainAsSpaceGroup(phase, sg, scatterers = None, - sgoffset = [0, 0, 0], constrainlat = True, constrainadps = True, - adpsymbols = None, isosymbol = "Uiso"): + +def _constrainAsSpaceGroup( + phase, + sg, + scatterers=None, + sgoffset=[0, 0, 0], + constrainlat=True, + constrainadps=True, + adpsymbols=None, + isosymbol="Uiso", +): """Restricted interface to constrainAsSpaceGroup. Arguments: As constrainAsSpaceGroup, except @@ -105,13 +128,23 @@ def _constrainAsSpaceGroup(phase, sg, scatterers = None, if adpsymbols is None: adpsymbols = stdUsymbols - sgp = SpaceGroupParameters(phase, sg, scatterers, sgoffset, - constrainlat, constrainadps, adpsymbols, isosymbol) + sgp = SpaceGroupParameters( + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ) return sgp + # End constrainAsSpaceGroup + class BaseSpaceGroupParameters(RecipeContainer): """Base class for holding space group Parameters. @@ -125,7 +158,7 @@ class is to make it easy to access the free variables of a structure for name -- "sgpars" """ - def __init__(self, name = "sgpars"): + def __init__(self, name="sgpars"): """Create the BaseSpaceGroupParameters object. This initializes the attributes. @@ -133,7 +166,7 @@ def __init__(self, name = "sgpars"): RecipeContainer.__init__(self, name) return - def addParameter(self, par, check = True): + def addParameter(self, par, check=True): """Store a Parameter. par -- The Parameter to be stored. @@ -146,8 +179,10 @@ def addParameter(self, par, check = True): RecipeContainer._addObject(self, par, self._parameters, check) return + # End class BaseSpaceGroupParameters + class SpaceGroupParameters(BaseSpaceGroupParameters): """Class for holding and creating space group Parameters. @@ -177,8 +212,17 @@ class SpaceGroupParameters(BaseSpaceGroupParameters): adppars -- Property that populates _adppars. """ - def __init__(self, phase, sg, scatterers, sgoffset, constrainlat, - constrainadps, adpsymbols, isosymbol): + def __init__( + self, + phase, + sg, + scatterers, + sgoffset, + constrainlat, + constrainadps, + adpsymbols, + isosymbol, + ): """Create the SpaceGroupParameters object. Arguments: @@ -195,7 +239,7 @@ def __init__(self, phase, sg, scatterers, sgoffset, constrainlat, same order as diffpy.structure.symmetryutilities.stdUsymbols. isosymbol -- Symbol for isotropic ADP (default "Uiso"). If None, - isotropic ADPs will be constrainted via the anisotropic + isotropic ADPs will be constrained via the anisotropic ADPs. """ BaseSpaceGroupParameters.__init__(self) @@ -219,13 +263,16 @@ def __init__(self, phase, sg, scatterers, sgoffset, constrainlat, def __iter__(self): """Iterate over top-level parameters.""" - if self._latpars is None or\ - self._xyzpars is None or\ - self._adppars is None: - self._makeConstraints() + if ( + self._latpars is None + or self._xyzpars is None + or self._adppars is None + ): + self._makeConstraints() return RecipeContainer.__iter__(self) latpars = property(lambda self: self._getLatPars()) + def _getLatPars(self): """Accessor for _latpars.""" if self._latpars is None: @@ -233,9 +280,10 @@ def _getLatPars(self): return self._latpars xyzpars = property(lambda self: self._getXYZPars()) + def _getXYZPars(self): """Accessor for _xyzpars.""" - positions = [] + positions = [] for scatterer in self.scatterers: xyz = [scatterer.x, scatterer.y, scatterer.z] positions.append([p.value for p in xyz]) @@ -244,9 +292,10 @@ def _getXYZPars(self): return self._xyzpars adppars = property(lambda self: self._getADPPars()) + def _getADPPars(self): """Accessor for _adppars.""" - positions = [] + positions = [] for scatterer in self.scatterers: xyz = [scatterer.x, scatterer.y, scatterer.z] positions.append([p.value for p in xyz]) @@ -266,7 +315,7 @@ def _makeConstraints(self): scatterers = self.scatterers # Prepare positions - positions = [] + positions = [] for scatterer in scatterers: xyz = [scatterer.x, scatterer.y, scatterer.z] positions.append([p.value for p in xyz]) @@ -277,11 +326,11 @@ def _makeConstraints(self): return - def _clearConstraints(self): """Clear old constraints. - This only clears constraints where new ones are going to be applied. + This only clears constraints where new ones are going to be + applied. """ phase = self.phase scatterers = self.scatterers @@ -300,8 +349,14 @@ def _clearConstraints(self): if self.constrainlat: lattice = phase.getLattice() - latpars = [lattice.a, lattice.b, lattice.c, lattice.alpha, - lattice.beta, lattice.gamma] + latpars = [ + lattice.a, + lattice.b, + lattice.c, + lattice.alpha, + lattice.beta, + lattice.gamma, + ] for par in latpars: if lattice.isConstrained(par): lattice.unconstrain(par) @@ -329,7 +384,8 @@ def _clearConstraints(self): def _constrainLattice(self): """Constrain the lattice parameters.""" - if not self.constrainlat: return + if not self.constrainlat: + return phase = self.phase sg = self.sg @@ -345,8 +401,14 @@ def _constrainLattice(self): # Now get the unconstrained, non-constant lattice pars and store them. self._latpars = BaseSpaceGroupParameters("latpars") - latpars = [lattice.a, lattice.b, lattice.c, lattice.alpha, - lattice.beta, lattice.gamma] + latpars = [ + lattice.a, + lattice.b, + lattice.c, + lattice.alpha, + lattice.beta, + lattice.gamma, + ] pars = [p for p in latpars if not p.const and not p.constrained] for par in pars: # FIXME - the original parameter will still appear as @@ -375,9 +437,9 @@ def _constrainXYZs(self, positions): self._xyzpars = BaseSpaceGroupParameters("xyzpars") # Make proxies to the free xyz parameters - xyznames = [name[:1]+"_"+name[1:] for name, val in g.pospars] + xyznames = [name[:1] + "_" + name[1:] for name, val in g.pospars] for pname in xyznames: - name, idx = pname.rsplit('_', 1) + name, idx = pname.rsplit("_", 1) idx = int(idx) par = scatterers[idx].get(name) newpar = self.__addPar(pname, par) @@ -390,8 +452,9 @@ def _constrainXYZs(self, positions): # Extract the constraint equation from the formula for parname, formula in fp.items(): - _makeconstraint(parname, formula, scatterer, idx, - self._parameters) + _makeconstraint( + parname, formula, scatterer, idx, self._parameters + ) return @@ -401,10 +464,13 @@ def _constrainADPs(self, positions): positions -- The coordinates of the scatterers. """ - from diffpy.structure.symmetryutilities import stdUsymbols - from diffpy.structure.symmetryutilities import SymmetryConstraints + from diffpy.structure.symmetryutilities import ( + SymmetryConstraints, + stdUsymbols, + ) - if not self.constrainadps: return + if not self.constrainadps: + return sg = self.sg sgoffset = self.sgoffset @@ -427,10 +493,10 @@ def _constrainADPs(self, positions): nonadps.append(sidx) continue - Uij = numpy.zeros((3,3), dtype=float) + Uij = numpy.zeros((3, 3), dtype=float) for idx, par in enumerate(pars): i, j = _idxtoij[idx] - Uij[i,j] = Uij[j,i] = par.getValue() + Uij[i, j] = Uij[j, i] = par.getValue() Uijs.append(Uij) @@ -450,7 +516,7 @@ def _constrainADPs(self, positions): isoidx = [] isonames = [] for pname in adpnames: - name, idx = pname.rsplit('_', 1) + name, idx = pname.rsplit("_", 1) idx = int(idx) # Check for isotropic ADPs scatterer = scatterers[idx] @@ -471,24 +537,26 @@ def _constrainADPs(self, positions): # Constrain dependent isotropics for idx, isoname in zip(isoidx[:], isonames): for j in g.coremap[idx]: - if j == idx: continue + if j == idx: + continue isoidx.append(j) scatterer = scatterers[j] - scatterer.constrain(isosymbol, isoname, ns = self._parameters) + scatterer.constrain(isosymbol, isoname, ns=self._parameters) fadp = g.UFormulas(adpnames) # Constrain dependent anisotropics. We use the fact that an # anisotropic cannot be dependent on an isotropic. for idx, tmp in enumerate(zip(scatterers, fadp)): - if idx in isoidx: continue + if idx in isoidx: + continue scatterer, fa = tmp # Extract the constraint equation from the formula for stdparname, formula in fa.items(): pname = adpmap[stdparname] - _makeconstraint(pname, formula, scatterer, idx, - self._parameters) - + _makeconstraint( + pname, formula, scatterer, idx, self._parameters + ) def __addPar(self, parname, par): """Constrain a parameter via proxy with a specified name. @@ -500,24 +568,28 @@ def __addPar(self, parname, par): self.addParameter(newpar) return newpar + # End class SpaceGroupParameters # crystal system rules # ref: Benjamin, W. A., Introduction to crystallography, # New York (1969), p.60 + def _constrainTriclinic(lattice): """Make constraints for Triclinic systems.""" return + def _constrainMonoclinic(lattice): """Make constraints for Monoclinic systems. - alpha and beta are fixed to 90 unless alpha != beta and alpha == gamma, in - which case alpha and gamma are constrained to 90. + alpha and beta are fixed to 90 unless alpha != beta and alpha == + gamma, in which case alpha and gamma are constrained to 90. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.setConst(True, ang90) beta = lattice.beta.getValue() @@ -529,26 +601,31 @@ def _constrainMonoclinic(lattice): lattice.beta.setConst(True, ang90) return + def _constrainOrthorhombic(lattice): """Make constraints for Orthorhombic systems. alpha, beta and gamma are constrained to 90 """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.setConst(True, ang90) lattice.beta.setConst(True, ang90) lattice.gamma.setConst(True, ang90) return + def _constrainTetragonal(lattice): """Make constraints for Tetragonal systems. - b is constrained to a and alpha, beta and gamma are constrained to 90. + b is constrained to a and alpha, beta and gamma are constrained to + 90. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor lattice.alpha.setConst(True, ang90) lattice.beta.setConst(True, ang90) @@ -556,15 +633,17 @@ def _constrainTetragonal(lattice): lattice.constrain(lattice.b, lattice.a) return + def _constrainTrigonal(lattice): """Make constraints for Trigonal systems. - If gamma == 120, then b is constrained to a, alpha and beta are constrained - to 90 and gamma is constrained to 120. Otherwise, b and c are constrained - to a, beta and gamma are constrained to alpha. + If gamma == 120, then b is constrained to a, alpha and beta are + constrained to 90 and gamma is constrained to 120. Otherwise, b and + c are constrained to a, beta and gamma are constrained to alpha. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor ang120 = 120.0 * afactor if lattice.gamma.getValue() == ang120: @@ -579,14 +658,16 @@ def _constrainTrigonal(lattice): lattice.constrain(lattice.gamma, lattice.alpha) return + def _constrainHexagonal(lattice): """Make constraints for Hexagonal systems. - b is constrained to a, alpha and beta are constrained to 90 and gamma is - constrained to 120. + b is constrained to a, alpha and beta are constrained to 90 and + gamma is constrained to 120. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor ang120 = 120.0 * afactor lattice.constrain(lattice.b, lattice.a) @@ -595,13 +676,16 @@ def _constrainHexagonal(lattice): lattice.gamma.setConst(True, ang120) return + def _constrainCubic(lattice): """Make constraints for Cubic systems. - b and c are constrained to a, alpha, beta and gamma are constrained to 90. + b and c are constrained to a, alpha, beta and gamma are constrained + to 90. """ afactor = 1 - if lattice.angunits == "rad": afactor = deg2rad + if lattice.angunits == "rad": + afactor = deg2rad ang90 = 90.0 * afactor lattice.constrain(lattice.b, lattice.a) lattice.constrain(lattice.c, lattice.a) @@ -610,19 +694,21 @@ def _constrainCubic(lattice): lattice.gamma.setConst(True, ang90) return + # This is used to map the correct crystal system to the proper constraint # function. _constraintMap = { - "Triclinic" : _constrainTriclinic, - "Monoclinic" : _constrainMonoclinic, - "Orthorhombic" : _constrainOrthorhombic, - "Tetragonal" : _constrainTetragonal, - "Trigonal" : _constrainTrigonal, - "Hexagonal" : _constrainHexagonal, - "Cubic" : _constrainCubic + "Triclinic": _constrainTriclinic, + "Monoclinic": _constrainMonoclinic, + "Orthorhombic": _constrainOrthorhombic, + "Tetragonal": _constrainTetragonal, + "Trigonal": _constrainTrigonal, + "Hexagonal": _constrainHexagonal, + "Cubic": _constrainCubic, } -def _makeconstraint(parname, formula, scatterer, idx, ns = {}): + +def _makeconstraint(parname, formula, scatterer, idx, ns={}): """Constrain a parameter according to a formula. parname -- Name of parameter @@ -638,10 +724,10 @@ def _makeconstraint(parname, formula, scatterer, idx, ns = {}): if par is None: return - compname = "%s_%i"%(parname, idx) + compname = "%s_%i" % (parname, idx) # Check to see if this parameter is free - pat = r'%s *([+-] *\d+)?$' % compname + pat = r"%s *([+-] *\d+)?$" % compname if re.match(pat, formula): return par @@ -654,9 +740,10 @@ def _makeconstraint(parname, formula, scatterer, idx, ns = {}): # If we got here, then we have a constraint equation # Fix any division issues formula = formula.replace("/", "*1.0/") - scatterer.constrain(par, formula, ns = ns) + scatterer.constrain(par, formula, ns=ns) return + def _getFloat(formula): """Get a float from a formula string, or None if this is not possible.""" try: @@ -664,6 +751,7 @@ def _getFloat(formula): except NameError: return None + # Constants needed above _idxtoij = [(0, 0), (1, 1), (2, 2), (0, 1), (0, 2), (1, 2)] deg2rad = numpy.pi / 180 diff --git a/src/diffpy/srfit/structure/srrealparset.py b/src/diffpy/srfit/structure/srrealparset.py index 6ea9a09e..4bd8b877 100644 --- a/src/diffpy/srfit/structure/srrealparset.py +++ b/src/diffpy/srfit/structure/srrealparset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Structure wrapper class for structures compatible with SrReal.""" __all__ = ["SrRealParSet"] @@ -40,7 +39,7 @@ def __init__(self, *args, **kw): self.stru = None return - def restrainBVS(self, sig = 1, scaled = False): + def restrainBVS(self, sig=1, scaled=False): """Restrain the bond-valence sum to zero. This adds a penalty to the cost function equal to @@ -67,10 +66,11 @@ def restrainBVS(self, sig = 1, scaled = False): # Return the Restraint object return res - def useSymmetry(self, use = True): + def useSymmetry(self, use=True): """Set this structure to use symmetry. - This determines how the structure is treated by SrReal calculators. + This determines how the structure is treated by SrReal + calculators. """ self._usesymmetry = bool(use) return @@ -82,10 +82,11 @@ def usingSymmetry(self): def _getSrRealStructure(self): """Get the structure object for use with SrReal calculators. - If this is periodic, then return the structure, otherwise, pass it - inside of a nosymmetry wrapper. + If this is periodic, then return the structure, otherwise, pass + it inside of a nosymmetry wrapper. """ from diffpy.srreal.structureadapter import nosymmetry + if self._usesymmetry: return self.stru return nosymmetry(self.stru) diff --git a/src/diffpy/srfit/util/__init__.py b/src/diffpy/srfit/util/__init__.py index ed39b3b6..023b4256 100644 --- a/src/diffpy/srfit/util/__init__.py +++ b/src/diffpy/srfit/util/__init__.py @@ -12,10 +12,9 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Utilities and constants used throughout SrFit.""" -_DASHEDLINE = 78 * '-' +_DASHEDLINE = 78 * "-" def sortKeyForNumericString(s): @@ -39,10 +38,10 @@ def sortKeyForNumericString(s): """ if sortKeyForNumericString._rx is None: import re - sortKeyForNumericString._rx = re.compile(r'(\d+)') + + sortKeyForNumericString._rx = re.compile(r"(\d+)") rx = sortKeyForNumericString._rx - rv = tuple((int(w) if i % 2 else w) - for i, w in enumerate(rx.split(s))) + rv = tuple((int(w) if i % 2 else w) for i, w in enumerate(rx.split(s))) return rv diff --git a/src/diffpy/srfit/util/argbinders.py b/src/diffpy/srfit/util/argbinders.py index 5c659fa4..28cd5144 100644 --- a/src/diffpy/srfit/util/argbinders.py +++ b/src/diffpy/srfit/util/argbinders.py @@ -12,12 +12,10 @@ # See LICENSE.txt for license information. # ############################################################################## - """Functions for binding arguments of callable objects.""" class bind2nd(object): - """Freeze second argument of a callable object to a given constant.""" def __init__(self, func, arg1): @@ -27,5 +25,5 @@ def __init__(self, func, arg1): return def __call__(self, *args, **kwargs): - boundargs = ((args[0], self.arg1) + args[1:]) + boundargs = (args[0], self.arg1) + args[1:] return self.func(*boundargs, **kwargs) diff --git a/src/diffpy/srfit/util/inpututils.py b/src/diffpy/srfit/util/inpututils.py index 12372eb3..0645354a 100644 --- a/src/diffpy/srfit/util/inpututils.py +++ b/src/diffpy/srfit/util/inpututils.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Input utilities.""" __all__ = ["inputToString"] @@ -20,13 +19,13 @@ import os.path -def inputToString(inpt): +def inputToString(input): """Convert input from various modes to a string. This is useful when you want a method to accept a string, open file object or file name. - inpt -- An open file-like object, name of a file + input -- An open file-like object, name of a file or a string containing the input. Returns the input in a string @@ -35,16 +34,17 @@ def inputToString(inpt): """ # Get the input into a string inptstr = "" - if hasattr(inpt, "read"): - inptstr = inpt.read() + if hasattr(input, "read"): + inptstr = input.read() # TODO remove handling of string input accept only file or filename # FIXME check for typos in the file name - elif os.path.exists(inpt) or (len(inpt) < 80 and inpt.count("\n") == 0): - with open(inpt, 'r') as infile: + elif os.path.exists(input) or (len(input) < 80 and input.count("\n") == 0): + with open(input, "r") as infile: inptstr = infile.read() else: - inptstr = inpt + inptstr = input return inptstr + # End of file diff --git a/src/diffpy/srfit/util/nameutils.py b/src/diffpy/srfit/util/nameutils.py index c9b0d85e..e11820be 100644 --- a/src/diffpy/srfit/util/nameutils.py +++ b/src/diffpy/srfit/util/nameutils.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Name utilities.""" __all__ = ["isIdentifier", "validateName"] import re -reident = re.compile(r'^[a-zA-Z_]\w*$') +reident = re.compile(r"^[a-zA-Z_]\w*$") def isIdentifier(s): @@ -42,4 +41,5 @@ def validateName(name): raise ValueError(f"Name {name} is not a valid identifier") return + # End of file diff --git a/src/diffpy/srfit/util/observable.py b/src/diffpy/srfit/util/observable.py index 9cd24d41..ff71b7d5 100644 --- a/src/diffpy/srfit/util/observable.py +++ b/src/diffpy/srfit/util/observable.py @@ -38,13 +38,14 @@ class Observable(object): the list of handlers to invoke notify: invoke the registered handlers in the order in which they were registered """ + def notify(self, other=()): """Notify all observers.""" # build a list before notification, just in case the observer's # callback behavior involves removing itself from our callback set - semaphors = (self,) + other + semaphores = (self,) + other for callable in tuple(self._observers): - callable(semaphors) + callable(semaphores) return # callback management @@ -74,17 +75,19 @@ def __init__(self, **kwds): self._observers = set() return + # end of class Observable # Local helpers -------------------------------------------------------------- -def _fbRemoveObserver(fobs, semaphors): +def _fbRemoveObserver(fobs, semaphores): # Remove WeakBoundMethod `fobs` from the observers of notifying object. # This is called from Observable.notify when the WeakBoundMethod # associated object dies. - observable = semaphors[0] + observable = semaphores[0] observable.removeObserver(fobs) return + # end of file diff --git a/src/diffpy/srfit/util/tagmanager.py b/src/diffpy/srfit/util/tagmanager.py index 30c5bcdc..65e4ab9e 100644 --- a/src/diffpy/srfit/util/tagmanager.py +++ b/src/diffpy/srfit/util/tagmanager.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """TagManager class. -The TagManager class takes hashable objects and assigns tags to them. Objects -can then be easily referenced via their assigned tags. +The TagManager class takes hashable objects and assigns tags to them. +Objects can then be easily referenced via their assigned tags. """ __all__ = ["TagManager"] @@ -135,7 +134,8 @@ def verifyTags(self, *tags): def __getObjectSet(self, tag): """Helper function for getting an object set with given tag. - Raises KeyError if a passed tag does not exist and self.silent is False + Raises KeyError if a passed tag does not exist and self.silent + is False """ oset = self._tagdict.get(str(tag)) if oset is None: @@ -144,6 +144,7 @@ def __getObjectSet(self, tag): oset = set() return oset + # End class TagManager # End of file diff --git a/src/diffpy/srfit/util/weakrefcallable.py b/src/diffpy/srfit/util/weakrefcallable.py index 0803330f..287d2e47 100644 --- a/src/diffpy/srfit/util/weakrefcallable.py +++ b/src/diffpy/srfit/util/weakrefcallable.py @@ -12,12 +12,11 @@ # See LICENSE.txt for license information. # ############################################################################## - """Picklable storage of callable objects using weak references.""" -import weakref import types +import weakref import six @@ -46,7 +45,7 @@ class WeakBoundMethod(object): This is only used for pickling. """ - __slots__ = ('function', 'fallback', '_wref', '_class') + __slots__ = ("function", "fallback", "_wref", "_class") def __init__(self, f, fallback=None): """Create a weak reference wrapper to bound method. @@ -100,9 +99,9 @@ def __hash__(self): return hash((self.function, self._wref)) def __eq__(self, other): - rv = (self.function == other.function and - (self._wref == other._wref or - None is self._wref() is other._wref())) + rv = self.function == other.function and ( + self._wref == other._wref or None is self._wref() is other._wref() + ) return rv def __ne__(self, other): @@ -138,6 +137,7 @@ def __setstate__(self, state): def __mimic_empty_ref(): return None + # end of class WeakBoundMethod diff --git a/src/diffpy/srfit/version.py b/src/diffpy/srfit/version.py index 406e31bb..a0caae47 100644 --- a/src/diffpy/srfit/version.py +++ b/src/diffpy/srfit/version.py @@ -1,54 +1,25 @@ #!/usr/bin/env python ############################################################################## # -# diffpy.srfit by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 The Trustees of Columbia University -# in the City of New York. All rights reserved. +# (c) 2008-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Chris Farrow +# File coded by: Christopher Farrow, Pavol Juhas, and members of the Billinge Group. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE_DANSE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srfit/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## +"""Definition of __version__.""" -"""Definition of __version__, __date__, __timestamp__, __git_commit__. - -Notes ------ -Variable `__gitsha__` is deprecated as of version 3.0. -Use `__git_commit__` instead. -""" - -__all__ = ['__date__', '__git_commit__', '__timestamp__', '__version__'] - -import os.path - -from pkg_resources import resource_filename - - -# obtain version information from the version.cfg file -cp = dict(version='', date='', commit='', timestamp='0') -fcfg = resource_filename(__name__, 'version.cfg') -if not os.path.isfile(fcfg): # pragma: no cover - from warnings import warn - warn('Package metadata not found, execute "./setup.py egg_info".') - fcfg = os.devnull -with open(fcfg) as fp: - kwords = [[w.strip() for w in line.split(' = ', 1)] - for line in fp if line[:1].isalpha() and ' = ' in line] -assert all(w[0] in cp for w in kwords), "received unrecognized keyword" -cp.update(kwords) - -__version__ = cp['version'] -__date__ = cp['date'] -__git_commit__ = cp['commit'] -__timestamp__ = int(cp['timestamp']) +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] -# TODO remove deprecated __gitsha__ in version 3.1. -__gitsha__ = __git_commit__ +# obtain version information +from importlib.metadata import version -del cp, fcfg, fp, kwords +__version__ = version("diffpy.srfit") # End of file diff --git a/src/diffpy/srfit/tests/__init__.py b/tests/__init__.py similarity index 90% rename from src/diffpy/srfit/tests/__init__.py rename to tests/__init__.py index 70359f35..7f498f76 100644 --- a/src/diffpy/srfit/tests/__init__.py +++ b/tests/__init__.py @@ -12,11 +12,10 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Unit tests for diffpy.srfit.""" -import unittest import logging +import unittest # create logger instance for the tests subpackage logging.basicConfig() @@ -24,7 +23,7 @@ del logging -def testsuite(pattern=''): +def testsuite(pattern=""): """Create a unit tests suite for diffpy.srfit package. Parameters @@ -40,12 +39,14 @@ def testsuite(pattern=''): The TestSuite object containing the matching tests. """ import re - from os.path import dirname from itertools import chain + from os.path import dirname + from pkg_resources import resource_filename + loader = unittest.defaultTestLoader - thisdir = resource_filename(__name__, '') - depth = __name__.count('.') + 1 + thisdir = resource_filename(__name__, "") + depth = __name__.count(".") + 1 topdir = thisdir for i in range(depth): topdir = dirname(topdir) @@ -55,12 +56,12 @@ def testsuite(pattern=''): rx = re.compile(pattern) tsuites = list(chain.from_iterable(suite_all)) tsok = all(isinstance(ts, unittest.TestSuite) for ts in tsuites) - if not tsok: # pragma: no cover + if not tsok: # pragma: no cover return suite_all tcases = chain.from_iterable(tsuites) for tc in tcases: - tcwords = tc.id().split('.') - shortname = '.'.join(tcwords[-3:]) + tcwords = tc.id().split(".") + shortname = ".".join(tcwords[-3:]) if rx.search(shortname): suite.addTest(tc) # verify all tests are found for an empty pattern. diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..e3b63139 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,19 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.fixture +def user_filesystem(tmp_path): + base_dir = Path(tmp_path) + home_dir = base_dir / "home_dir" + home_dir.mkdir(parents=True, exist_ok=True) + cwd_dir = base_dir / "cwd_dir" + cwd_dir.mkdir(parents=True, exist_ok=True) + + home_config_data = {"username": "home_username", "email": "home@email.com"} + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump(home_config_data, f) + + yield tmp_path diff --git a/src/diffpy/srfit/tests/debug.py b/tests/debug.py similarity index 90% rename from src/diffpy/srfit/tests/debug.py rename to tests/debug.py index fa1caf2f..8ebfc4ad 100644 --- a/src/diffpy/srfit/tests/debug.py +++ b/tests/debug.py @@ -12,7 +12,6 @@ # See LICENSE.txt for license information. # ############################################################################## - """Convenience module for debugging the unit tests using. python -m diffpy.srfit.tests.debug @@ -21,10 +20,12 @@ """ -if __name__ == '__main__': +if __name__ == "__main__": import sys + from diffpy.srfit.tests import testsuite - pattern = sys.argv[1] if len(sys.argv) > 1 else '' + + pattern = sys.argv[1] if len(sys.argv) > 1 else "" suite = testsuite(pattern) suite.debug() diff --git a/src/diffpy/srfit/tests/run.py b/tests/run.py similarity index 93% rename from src/diffpy/srfit/tests/run.py rename to tests/run.py index 5e337417..a375045e 100644 --- a/src/diffpy/srfit/tests/run.py +++ b/tests/run.py @@ -12,22 +12,25 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Convenience module for executing all unit tests with. python -m diffpy.srfit.tests.run """ -if __name__ == '__main__': +if __name__ == "__main__": import sys + # show warnings by default if not sys.warnoptions: - import os, warnings + import os + import warnings + warnings.simplefilter("default") # also affect subprocesses os.environ["PYTHONWARNINGS"] = "default" from diffpy.srfit.tests import test + # produce zero exit code for a successful test sys.exit(not test().wasSuccessful()) diff --git a/src/diffpy/srfit/tests/testbuilder.py b/tests/test_builder.py similarity index 81% rename from src/diffpy/srfit/tests/testbuilder.py rename to tests/test_builder.py index cb46c5ee..81f5eb9f 100644 --- a/src/diffpy/srfit/tests/testbuilder.py +++ b/tests/test_builder.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest @@ -21,8 +20,8 @@ import diffpy.srfit.equation.builder as builder import diffpy.srfit.equation.literals as literals -from diffpy.srfit.tests.utils import _makeArgs -from diffpy.srfit.tests.utils import noObserversInGlobalBuilders + +from .utils import _makeArgs, noObserversInGlobalBuilders class TestBuilder(unittest.TestCase): @@ -52,7 +51,6 @@ def testRegisterArg(self): self.assertTrue(noObserversInGlobalBuilders()) return - def testRegisterOperator(self): """Try to use an operator without arguments in an equation.""" @@ -86,13 +84,13 @@ def testRegisterOperator(self): self.assertTrue(noObserversInGlobalBuilders()) return - def testSwapping(self): def g1(v1, v2, v3, v4): return (v1 + v2) * (v3 + v4) + def g2(v1): - return 0.5*v1 + return 0.5 * v1 factory = builder.EquationFactory() v1, v2, v3, v4, v5 = _makeArgs(5) @@ -149,7 +147,7 @@ def g2(v1): def testParseEquation(self): - from numpy import sin, divide, sqrt, array_equal, e + from numpy import array_equal, divide, e, sin, sqrt factory = builder.EquationFactory() @@ -163,8 +161,8 @@ def testParseEquation(self): eq.x.setValue(x) eq.B.setValue(B) eq.C.setValue(C) - f = lambda A, x, B, C: A*sin(0.5*x)+divide(B,C) - self.assertTrue(array_equal(eq(), f(A,x,B,C))) + f = lambda A, x, B, C: A * sin(0.5 * x) + divide(B, C) + self.assertTrue(array_equal(eq(), f(A, x, B, C))) # Make sure that the arguments of eq are listed in the order in which # they appear in the equations. @@ -176,8 +174,8 @@ def testParseEquation(self): sigma = 0.1 eq.x.setValue(x) eq.sigma.setValue(sigma) - f = lambda x, sigma : sqrt(e**(-0.5*(x/sigma)**2)) - self.assertTrue(numpy.allclose(eq(), f(x,sigma))) + f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) + self.assertTrue(numpy.allclose(eq(), f(x, sigma))) self.assertEqual(eq.args, [eq.x, eq.sigma]) @@ -186,14 +184,14 @@ def testParseEquation(self): eq = factory.makeEquation("sqrt(e**(-0.5*(x/sigma)**2))") self.assertTrue("sigma" in eq.argdict) self.assertTrue("x" not in eq.argdict) - self.assertTrue(numpy.allclose(eq(sigma=sigma), f(x,sigma))) + self.assertTrue(numpy.allclose(eq(sigma=sigma), f(x, sigma))) self.assertEqual(eq.args, [eq.sigma]) # Equation with user-defined functions factory.registerFunction("myfunc", eq, ["sigma"]) eq2 = factory.makeEquation("c*myfunc(sigma)") - self.assertTrue(numpy.allclose(eq2(c=2, sigma=sigma), 2*f(x,sigma))) + self.assertTrue(numpy.allclose(eq2(c=2, sigma=sigma), 2 * f(x, sigma))) self.assertTrue("sigma" in eq2.argdict) self.assertTrue("c" in eq2.argdict) self.assertEqual(eq2.args, [eq2.c, eq2.sigma]) @@ -201,34 +199,32 @@ def testParseEquation(self): self.assertTrue(noObserversInGlobalBuilders()) return - def test_parse_constant(self): """Verify parsing of constant numeric expressions.""" factory = builder.EquationFactory() - eq = factory.makeEquation('3.12 + 2') + eq = factory.makeEquation("3.12 + 2") self.assertTrue(isinstance(eq, builder.Equation)) self.assertEqual(set(), factory.equations) self.assertEqual(5.12, eq()) self.assertRaises(ValueError, eq, 3) return - def testBuildEquation(self): from numpy import array_equal # simple equation sin = builder.getBuilder("sin") - a = builder.ArgumentBuilder(name="a", value = 1) - A = builder.ArgumentBuilder(name="A", value = 2) + a = builder.ArgumentBuilder(name="a", value=1) + A = builder.ArgumentBuilder(name="A", value=2) x = numpy.arange(0, numpy.pi, 0.1) - beq = A*sin(a*x) + beq = A * sin(a * x) eq = beq.getEquation() self.assertTrue("a" in eq.argdict) self.assertTrue("A" in eq.argdict) - self.assertTrue(array_equal(eq(), 2*numpy.sin(x))) + self.assertTrue(array_equal(eq(), 2 * numpy.sin(x))) self.assertEqual(eq.args, [eq.A, eq.a]) @@ -237,13 +233,13 @@ def testBuildEquation(self): # custom function def _f(a, b): - return (a-b)*1.0/(a+b) + return (a - b) * 1.0 / (a + b) f = builder.wrapFunction("f", _f, 2, 1) - a = builder.ArgumentBuilder(name="a", value = 2) - b = builder.ArgumentBuilder(name="b", value = 1) + a = builder.ArgumentBuilder(name="a", value=2) + b = builder.ArgumentBuilder(name="b", value=1) - beq = sin(f(a,b)) + beq = sin(f(a, b)) eq = beq.getEquation() self.assertEqual(eq(), numpy.sin(_f(2, 1))) @@ -251,32 +247,34 @@ def _f(a, b): sqrt = builder.getBuilder("sqrt") e = numpy.e _x = numpy.arange(0, 1, 0.05) - x = builder.ArgumentBuilder(name="x", value = _x, const = True) - sigma = builder.ArgumentBuilder(name="sigma", value = 0.1) - beq = sqrt(e**(-0.5*(x/sigma)**2)) + x = builder.ArgumentBuilder(name="x", value=_x, const=True) + sigma = builder.ArgumentBuilder(name="sigma", value=0.1) + beq = sqrt(e ** (-0.5 * (x / sigma) ** 2)) eq = beq.getEquation() - f = lambda x, sigma : sqrt(e**(-0.5*(x/sigma)**2)) - self.assertTrue(numpy.allclose(eq(), numpy.sqrt(e**(-0.5*(_x/0.1)**2)))) + f = lambda x, sigma: sqrt(e ** (-0.5 * (x / sigma) ** 2)) + self.assertTrue( + numpy.allclose(eq(), numpy.sqrt(e ** (-0.5 * (_x / 0.1) ** 2))) + ) # Equation with Equation - A = builder.ArgumentBuilder(name="A", value = 2) - B = builder.ArgumentBuilder(name="B", value = 4) + A = builder.ArgumentBuilder(name="A", value=2) + B = builder.ArgumentBuilder(name="B", value=4) beq = A + B eq = beq.getEquation() E = builder.wrapOperator("eq", eq) - eq2 = (2*E).getEquation() - # Make sure these evaulate to the same thing + eq2 = (2 * E).getEquation() + # Make sure these evaluate to the same thing self.assertEqual(eq.args, [A.literal, B.literal]) - self.assertEqual(2*eq(), eq2()) + self.assertEqual(2 * eq(), eq2()) # Pass new arguments to the equation - C = builder.ArgumentBuilder(name="C", value = 5) - D = builder.ArgumentBuilder(name="D", value = 6) - eq3 = (E(C, D)+1).getEquation() + C = builder.ArgumentBuilder(name="C", value=5) + D = builder.ArgumentBuilder(name="D", value=6) + eq3 = (E(C, D) + 1).getEquation() self.assertEqual(12, eq3()) # Pass old and new arguments to the equation # If things work right, A has been given the value of C in the last # evaluation (5) - eq4 = (3*E(A, D)-1).getEquation() + eq4 = (3 * E(A, D) - 1).getEquation() self.assertEqual(32, eq4()) # Try to pass the wrong number of arguments self.assertRaises(ValueError, E, A) diff --git a/src/diffpy/srfit/tests/testcharacteristicfunctions.py b/tests/test_characteristicfunctions.py similarity index 80% rename from src/diffpy/srfit/tests/testcharacteristicfunctions.py rename to tests/test_characteristicfunctions.py index 7b5eb4c4..ddd02c32 100644 --- a/src/diffpy/srfit/tests/testcharacteristicfunctions.py +++ b/tests/test_characteristicfunctions.py @@ -12,59 +12,59 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for sas package.""" import unittest import numpy -from diffpy.srfit.tests.utils import has_sas, _msg_nosas from diffpy.srfit.sas.sasimport import sasimport +from .utils import _msg_nosas, has_sas + # Global variables to be assigned in setUp cf = None # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_sas, _msg_nosas) class TestSASCF(unittest.TestCase): def setUp(self): global cf import diffpy.srfit.pdf.characteristicfunctions as cf - return + return def testSphere(self): radius = 25 # Calculate sphere cf from SphereModel - SphereModel = sasimport('sas.models.SphereModel').SphereModel + SphereModel = sasimport("sas.models.SphereModel").SphereModel model = SphereModel() model.setParam("radius", radius) ff = cf.SASCF("sphere", model) - r = numpy.arange(1, 60, 0.1, dtype = float) + r = numpy.arange(1, 60, 0.1, dtype=float) fr1 = ff(r) # Calculate sphere cf analytically - fr2 = cf.sphericalCF(r, 2*radius) + fr2 = cf.sphericalCF(r, 2 * radius) diff = fr1 - fr2 res = numpy.dot(diff, diff) res /= numpy.dot(fr2, fr2) self.assertAlmostEqual(0, res, 4) return - def testSpheroid(self): prad = 20.9 erad = 33.114 # Calculate cf from EllipsoidModel - EllipsoidModel = sasimport('sas.models.EllipsoidModel').EllipsoidModel + EllipsoidModel = sasimport("sas.models.EllipsoidModel").EllipsoidModel model = EllipsoidModel() model.setParam("radius_a", prad) model.setParam("radius_b", erad) ff = cf.SASCF("spheroid", model) - r = numpy.arange(0, 100, 1/numpy.pi, dtype = float) + r = numpy.arange(0, 100, 1 / numpy.pi, dtype=float) fr1 = ff(r) # Calculate cf analytically @@ -75,17 +75,16 @@ def testSpheroid(self): self.assertAlmostEqual(0, res, 4) return - def testShell(self): radius = 19.2 thickness = 7.8 # Calculate cf from VesicleModel - VesicleModel = sasimport('sas.models.VesicleModel').VesicleModel + VesicleModel = sasimport("sas.models.VesicleModel").VesicleModel model = VesicleModel() model.setParam("radius", radius) model.setParam("thickness", thickness) ff = cf.SASCF("vesicle", model) - r = numpy.arange(0, 99.45, 0.1, dtype = float) + r = numpy.arange(0, 99.45, 0.1, dtype=float) fr1 = ff(r) # Calculate sphere cf analytically @@ -96,23 +95,22 @@ def testShell(self): self.assertAlmostEqual(0, res, 4) return - def testCylinder(self): """Make sure cylinder works over different r-ranges.""" radius = 100 length = 30 - CylinderModel = sasimport('sas.models.CylinderModel').CylinderModel + CylinderModel = sasimport("sas.models.CylinderModel").CylinderModel model = CylinderModel() model.setParam("radius", radius) model.setParam("length", length) ff = cf.SASCF("cylinder", model) - r1 = numpy.arange(0, 10, 0.1, dtype = float) - r2 = numpy.arange(0, 50, 0.1, dtype = float) - r3 = numpy.arange(0, 100, 0.1, dtype = float) - r4 = numpy.arange(0, 500, 0.1, dtype = float) + r1 = numpy.arange(0, 10, 0.1, dtype=float) + r2 = numpy.arange(0, 50, 0.1, dtype=float) + r3 = numpy.arange(0, 100, 0.1, dtype=float) + r4 = numpy.arange(0, 500, 0.1, dtype=float) fr1 = ff(r1) fr2 = ff(r2) @@ -120,36 +118,37 @@ def testCylinder(self): fr4 = ff(r4) d = fr1 - numpy.interp(r1, r2, fr2) - res12 = numpy.dot(d,d) + res12 = numpy.dot(d, d) res12 /= numpy.dot(fr1, fr1) self.assertAlmostEqual(0, res12, 4) d = fr1 - numpy.interp(r1, r3, fr3) - res13 = numpy.dot(d,d) + res13 = numpy.dot(d, d) res13 /= numpy.dot(fr1, fr1) self.assertAlmostEqual(0, res13, 4) d = fr1 - numpy.interp(r1, r4, fr4) - res14 = numpy.dot(d,d) + res14 = numpy.dot(d, d) res14 /= numpy.dot(fr1, fr1) self.assertAlmostEqual(0, res14, 4) d = fr2 - numpy.interp(r2, r3, fr3) - res23 = numpy.dot(d,d) + res23 = numpy.dot(d, d) res23 /= numpy.dot(fr2, fr2) self.assertAlmostEqual(0, res23, 4) d = fr2 - numpy.interp(r2, r4, fr4) - res24 = numpy.dot(d,d) + res24 = numpy.dot(d, d) res24 /= numpy.dot(fr2, fr2) self.assertAlmostEqual(0, res24, 4) d = fr3 - numpy.interp(r3, r4, fr4) - res34 = numpy.dot(d,d) + res34 = numpy.dot(d, d) res34 /= numpy.dot(fr3, fr3) self.assertAlmostEqual(0, res34, 4) return + # End of class TestSASCF if __name__ == "__main__": diff --git a/src/diffpy/srfit/tests/testconstraint.py b/tests/test_constraint.py similarity index 99% rename from src/diffpy/srfit/tests/testconstraint.py rename to tests/test_constraint.py index c2501f14..5c41689f 100644 --- a/src/diffpy/srfit/tests/testconstraint.py +++ b/tests/test_constraint.py @@ -12,15 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest +from diffpy.srfit.equation.builder import EquationFactory from diffpy.srfit.fitbase.constraint import Constraint -from diffpy.srfit.fitbase.recipeorganizer import equationFromString from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.equation.builder import EquationFactory +from diffpy.srfit.fitbase.recipeorganizer import equationFromString class TestConstraint(unittest.TestCase): diff --git a/src/diffpy/srfit/tests/testcontribution.py b/tests/test_contribution.py similarity index 86% rename from src/diffpy/srfit/tests/testcontribution.py rename to tests/test_contribution.py index 19dbdaa9..60616a8b 100644 --- a/src/diffpy/srfit/tests/testcontribution.py +++ b/tests/test_contribution.py @@ -12,19 +12,19 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -from numpy import arange, dot, array_equal, sin +from numpy import arange, array_equal, dot, sin +from diffpy.srfit.exceptions import SrFitError from diffpy.srfit.fitbase.fitcontribution import FitContribution -from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator -from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.exceptions import SrFitError -from diffpy.srfit.tests.utils import noObserversInGlobalBuilders +from diffpy.srfit.fitbase.profile import Profile +from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator + +from .utils import noObserversInGlobalBuilders class TestContribution(unittest.TestCase): @@ -47,16 +47,15 @@ def testSetProfile(self): self.assertTrue(fc._eq is None) self.assertTrue(fc._reseq is None) # check type checking - fc1 = FitContribution('test1') - self.assertRaises(TypeError, fc1.setProfile, 'invalid') + fc1 = FitContribution("test1") + self.assertRaises(TypeError, fc1.setProfile, "invalid") # check if residual equation is set up when possible - fc2 = FitContribution('test2') - fc2.setEquation('A * x') + fc2 = FitContribution("test2") + fc2.setEquation("A * x") fc2.setProfile(profile) self.assertFalse(fc2._reseq is None) return - def testAddProfileGenerator(self): fc = self.fitcontribution gen = self.gen @@ -115,7 +114,7 @@ def testReplacements(self): profile = self.profile profile.setObservedProfile(xobs, yobs) xobs2 = arange(0, 10, 0.8) - yobs2 = 0.5*xobs2 + yobs2 = 0.5 * xobs2 profile2 = Profile() profile2.setObservedProfile(xobs2, yobs2) gen = self.gen @@ -145,7 +144,6 @@ def testReplacements(self): self.assertEqual(len(xobs2), len(fc.residual())) return - def testResidual(self): """Test the residual, which requires all other methods.""" fc = self.fitcontribution @@ -195,7 +193,7 @@ def testResidual(self): self.assertTrue(fc._eq._value is None) self.assertTrue(fc._reseq._value is None) xobs = arange(0, 10, 0.5) - yobs = 9*sin(xobs) + yobs = 9 * sin(xobs) profile.setObservedProfile(xobs, yobs) self.assertTrue(fc._eq._value is None) self.assertTrue(fc._reseq._value is None) @@ -207,25 +205,25 @@ def testResidual(self): fc.setEquation("2*I") fc.setResidualEquation("resv") chiv = fc.residual() - self.assertAlmostEqual(sum((2*xobs-yobs)**2)/sum(yobs**2), - dot(chiv, chiv)) + self.assertAlmostEqual( + sum((2 * xobs - yobs) ** 2) / sum(yobs**2), dot(chiv, chiv) + ) # Make a custom residual. fc.setResidualEquation("abs(eq-y)**0.5") chiv = fc.residual() - self.assertAlmostEqual(sum(abs(2*xobs-yobs)), dot(chiv, chiv)) + self.assertAlmostEqual(sum(abs(2 * xobs - yobs)), dot(chiv, chiv)) # Test configuration checks - fc1 = FitContribution('test1') - self.assertRaises(SrFitError, fc1.setResidualEquation, 'chiv') + fc1 = FitContribution("test1") + self.assertRaises(SrFitError, fc1.setResidualEquation, "chiv") fc1.setProfile(self.profile) - self.assertRaises(SrFitError, fc1.setResidualEquation, 'chiv') - fc1.setEquation('A * x') - fc1.setResidualEquation('chiv') + self.assertRaises(SrFitError, fc1.setResidualEquation, "chiv") + fc1.setEquation("A * x") + fc1.setResidualEquation("chiv") self.assertTrue(noObserversInGlobalBuilders()) return - def test_setEquation(self): """Check replacement of removed parameters.""" fc = self.fitcontribution @@ -234,59 +232,55 @@ def test_setEquation(self): self.assertEqual(7, fc.evaluate()) fc.removeParameter(fc.x) x = arange(0, 10, 0.5) - fc.newParameter('x', x) + fc.newParameter("x", x) self.assertTrue(array_equal(5 + x, fc.evaluate())) self.assertTrue(noObserversInGlobalBuilders()) return - def test_getEquation(self): """Check getting the current profile simulation formula.""" fc = self.fitcontribution - self.assertEqual('', fc.getEquation()) + self.assertEqual("", fc.getEquation()) fc.setEquation("A * sin(x + 5)") - self.assertEqual('(A * sin((x + 5)))', fc.getEquation()) + self.assertEqual("(A * sin((x + 5)))", fc.getEquation()) self.assertTrue(noObserversInGlobalBuilders()) return - def test_getResidualEquation(self): """Check getting the current formula for residual equation.""" fc = self.fitcontribution - self.assertEqual('', fc.getResidualEquation()) + self.assertEqual("", fc.getResidualEquation()) fc.setProfile(self.profile) - fc.setEquation('A * x + B') - self.assertEqual('((eq - y) / dy)', fc.getResidualEquation()) - fc.setResidualEquation('2 * (eq - y)') - self.assertEqual('(2 * (eq - y))', fc.getResidualEquation()) + fc.setEquation("A * x + B") + self.assertEqual("((eq - y) / dy)", fc.getResidualEquation()) + fc.setResidualEquation("2 * (eq - y)") + self.assertEqual("(2 * (eq - y))", fc.getResidualEquation()) return - def test_releaseOldEquations(self): """Ensure EquationFactory does not hold to obsolete Equations.""" fc = self.fitcontribution self.assertEqual(0, len(fc._eqfactory.equations)) for i in range(5): - fc.setEquation('A * x + B') + fc.setEquation("A * x + B") self.assertEqual(1, len(fc._eqfactory.equations)) fc.setProfile(self.profile) for i in range(5): - fc.setResidualEquation('chiv') + fc.setResidualEquation("chiv") self.assertEqual(2, len(fc._eqfactory.equations)) return - def test_registerFunction(self): """Ensure registered function works after second setEquation call.""" fc = self.fitcontribution - fsquare = lambda x : x**2 - fc.registerFunction(fsquare, name='fsquare') - fc.setEquation('fsquare') + fsquare = lambda x: x**2 + fc.registerFunction(fsquare, name="fsquare") + fc.setEquation("fsquare") fc.x.setValue(5) self.assertEqual(25, fc.evaluate()) fc.x << 6 self.assertEqual(36, fc.evaluate()) - fc.setEquation('fsquare + 5') + fc.setEquation("fsquare + 5") self.assertEqual(41, fc.evaluate()) fc.x << -1 self.assertEqual(6, fc.evaluate()) diff --git a/src/diffpy/srfit/tests/testdiffpyparset.py b/tests/test_diffpyparset.py similarity index 87% rename from src/diffpy/srfit/tests/testdiffpyparset.py rename to tests/test_diffpyparset.py index 71701aa6..b4868de8 100644 --- a/src/diffpy/srfit/tests/testdiffpyparset.py +++ b/tests/test_diffpyparset.py @@ -12,39 +12,39 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for diffpy.srfit.structure package.""" -import unittest import pickle +import unittest import numpy -from diffpy.srfit.tests.utils import has_structure, _msg_nostructure +from .utils import _msg_nostructure, has_structure # Global variables to be assigned in setUp Atom = Lattice = Structure = DiffpyStructureParSet = None # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_structure, _msg_nostructure) class TestParameterAdapter(unittest.TestCase): def setUp(self): global Atom, Lattice, Structure, DiffpyStructureParSet - from diffpy.structure import Atom, Lattice, Structure from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet - return + from diffpy.structure import Atom, Lattice, Structure + return def testDiffpyStructureParSet(self): """Test the structure conversion.""" - a1 = Atom("Cu", xyz = numpy.array([.0, .1, .2]), Uisoequiv = 0.003) - a2 = Atom("Ag", xyz = numpy.array([.3, .4, .5]), Uisoequiv = 0.002) + a1 = Atom("Cu", xyz=numpy.array([0.0, 0.1, 0.2]), Uisoequiv=0.003) + a2 = Atom("Ag", xyz=numpy.array([0.3, 0.4, 0.5]), Uisoequiv=0.002) l = Lattice(2.5, 2.5, 2.5, 90, 90, 90) - dsstru = Structure([a1,a2], l) + dsstru = Structure([a1, a2], l) # Structure makes copies a1 = dsstru[0] a2 = dsstru[1] @@ -63,14 +63,14 @@ def _testAtoms(): self.assertEqual(a2.Bisoequiv, s.Ag0.Biso.getValue()) for i in range(1, 4): for j in range(i, 4): - uijstru = getattr(a1, "U%i%i"%(i,j)) - uij = getattr(s.Cu0, "U%i%i"%(i,j)).getValue() - uji = getattr(s.Cu0, "U%i%i"%(j,i)).getValue() + uijstru = getattr(a1, "U%i%i" % (i, j)) + uij = getattr(s.Cu0, "U%i%i" % (i, j)).getValue() + uji = getattr(s.Cu0, "U%i%i" % (j, i)).getValue() self.assertEqual(uijstru, uij) self.assertEqual(uijstru, uji) - bijstru = getattr(a1, "B%i%i"%(i,j)) - bij = getattr(s.Cu0, "B%i%i"%(i,j)).getValue() - bji = getattr(s.Cu0, "B%i%i"%(j,i)).getValue() + bijstru = getattr(a1, "B%i%i" % (i, j)) + bij = getattr(s.Cu0, "B%i%i" % (i, j)).getValue() + bji = getattr(s.Cu0, "B%i%i" % (j, i)).getValue() self.assertEqual(bijstru, bij) self.assertEqual(bijstru, bji) @@ -79,7 +79,6 @@ def _testAtoms(): self.assertEqual(a1.xyz[2], s.Cu0.z.getValue()) return - def _testLattice(): # Test the lattice @@ -114,7 +113,6 @@ def _testLattice(): self.assertNotEqual(d, dsstru.lattice.dist(a1.xyz, a2.xyz)) return - def test___repr__(self): """Test representation of DiffpyStructureParSet objects.""" lat = Lattice(3, 3, 2, 90, 90, 90) @@ -126,7 +124,6 @@ def test___repr__(self): self.assertEqual(repr(atom), repr(dsps.atoms[0])) return - def test_pickling(self): """Test pickling of DiffpyStructureParSet.""" stru = Structure([Atom("C", [0, 0.2, 0.5])]) @@ -137,6 +134,7 @@ def test_pickling(self): self.assertEqual(0.2, dsps2.atoms[0].y.value) return + # End of class TestParameterAdapter if __name__ == "__main__": diff --git a/src/diffpy/srfit/tests/testequation.py b/tests/test_equation.py similarity index 83% rename from src/diffpy/srfit/tests/testequation.py rename to tests/test_equation.py index 34b79361..1c9fe6b9 100644 --- a/src/diffpy/srfit/tests/testequation.py +++ b/tests/test_equation.py @@ -12,15 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest import diffpy.srfit.equation.literals as literals from diffpy.srfit.equation import Equation -from diffpy.srfit.tests.utils import _makeArgs -from diffpy.srfit.tests.utils import noObserversInGlobalBuilders + +from .utils import _makeArgs, noObserversInGlobalBuilders class TestEquation(unittest.TestCase): @@ -74,18 +73,18 @@ def testSimpleFunction(self): self.assertTrue(v3 is eq.v3) self.assertTrue(v4 is eq.v4) - self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) - self.assertEqual(20, eq.getValue()) # same as above - self.assertEqual(20, eq.value) # same as above - self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) - self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) - self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) - self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) + self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) + self.assertEqual(20, eq.getValue()) # same as above + self.assertEqual(20, eq.value) # same as above + self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) + self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) + self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) + self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) # Try some swapping eq.swap(v4, v1) self.assertTrue(eq._value is None) - self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) + self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) args = eq.args self.assertTrue(v4 not in args) @@ -160,18 +159,18 @@ def testEmbeddedEquation(self): self.assertTrue(eq._value is None) v1.value = 1 - self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) - self.assertEqual(20, eq.getValue()) # same as above - self.assertEqual(20, eq.value) # same as above - self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) - self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) - self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) - self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) + self.assertEqual(20, eq()) # 20 = 2.5*(1+3)*(4-2) + self.assertEqual(20, eq.getValue()) # same as above + self.assertEqual(20, eq.value) # same as above + self.assertEqual(25, eq(v1=2)) # 25 = 2.5*(2+3)*(4-2) + self.assertEqual(50, eq(v2=0)) # 50 = 2.5*(2+3)*(4-0) + self.assertEqual(30, eq(v3=1)) # 30 = 2.5*(2+1)*(4-0) + self.assertEqual(0, eq(v4=0)) # 20 = 2.5*(2+1)*(0-0) # Try some swapping. eq.swap(v4, v1) self.assertTrue(eq._value is None) - self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) + self.assertEqual(15, eq()) # 15 = 2.5*(2+1)*(2-0) args = eq.args self.assertTrue(v4 not in args) diff --git a/src/diffpy/srfit/tests/testfitrecipe.py b/tests/test_fitrecipe.py similarity index 90% rename from src/diffpy/srfit/tests/testfitrecipe.py rename to tests/test_fitrecipe.py index 75bf7c28..1945e063 100644 --- a/src/diffpy/srfit/tests/testfitrecipe.py +++ b/tests/test_fitrecipe.py @@ -12,18 +12,18 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -from numpy import linspace, array_equal, pi, sin, dot +from numpy import array_equal, dot, linspace, pi, sin -from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitcontribution import FitContribution -from diffpy.srfit.fitbase.profile import Profile +from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.tests.utils import capturestdout +from diffpy.srfit.fitbase.profile import Profile + +from .utils import capturestdout class TestFitRecipe(unittest.TestCase): @@ -152,16 +152,17 @@ def testResidual(self): # Change the c value to 1 so that the equation evaluates as sin(x+1) x = self.profile.x - y = sin(x+1) + y = sin(x + 1) self.recipe.cont.c.setValue(1) res = self.recipe.residual() - self.assertTrue(array_equal(y-self.profile.y, res)) + self.assertTrue(array_equal(y - self.profile.y, res)) # Try some constraints # Make c = 2*A, A = Avar var = self.recipe.newVar("Avar") - self.recipe.constrain(self.fitcontribution.c, - "2*A", {"A": self.fitcontribution.A}) + self.recipe.constrain( + self.fitcontribution.c, "2*A", {"A": self.fitcontribution.A} + ) self.assertEqual(2, self.fitcontribution.c.value) self.recipe.constrain(self.fitcontribution.A, var) self.assertEqual(1, var.getValue()) @@ -170,9 +171,9 @@ def testResidual(self): self.assertEqual(2, self.fitcontribution.c.value) # The equation should evaluate to sin(x+2) x = self.profile.x - y = sin(x+2) + y = sin(x + 2) res = self.recipe.residual() - self.assertTrue(array_equal(y-self.profile.y, res)) + self.assertTrue(array_equal(y - self.profile.y, res)) # Now try some restraints. We want c to be exactly zero. It should give # a penalty of (c-0)**2, which is 4 in this case @@ -202,9 +203,9 @@ def testResidual(self): self.fitcontribution.constrain(self.fitcontribution.c, "2*A") # This should evaluate to sin(x+2) x = self.profile.x - y = sin(x+2) + y = sin(x + 2) res = self.recipe.residual() - self.assertTrue(array_equal(y-self.profile.y, res)) + self.assertTrue(array_equal(y - self.profile.y, res)) # Add a restraint at the fitcontribution level. r1 = self.fitcontribution.restrain(self.fitcontribution.c, 0, 0, 1) @@ -212,7 +213,7 @@ def testResidual(self): # The chi2 is the same as above, plus 4 res = self.recipe.residual() x = self.profile.x - y = sin(x+2) + y = sin(x + 2) chi2 = 4 + dot(y - self.profile.y, y - self.profile.y) self.assertAlmostEqual(chi2, dot(res, res)) @@ -241,25 +242,26 @@ def testResidual(self): def testPrintFitHook(self): "check output from default PrintFitHook." self.recipe.addVar(self.fitcontribution.c) - self.recipe.restrain('c', lb=5) - pfh, = self.recipe.getFitHooks() + self.recipe.restrain("c", lb=5) + (pfh,) = self.recipe.getFitHooks() out = capturestdout(self.recipe.scalarResidual) - self.assertEqual('', out) + self.assertEqual("", out) pfh.verbose = 1 out = capturestdout(self.recipe.scalarResidual) self.assertTrue(out.strip().isdigit()) - self.assertFalse('\nRestraints:' in out) + self.assertFalse("\nRestraints:" in out) pfh.verbose = 2 out = capturestdout(self.recipe.scalarResidual) - self.assertTrue('\nResidual:' in out) - self.assertTrue('\nRestraints:' in out) - self.assertFalse('\nVariables' in out) + self.assertTrue("\nResidual:" in out) + self.assertTrue("\nRestraints:" in out) + self.assertFalse("\nVariables" in out) pfh.verbose = 3 out = capturestdout(self.recipe.scalarResidual) - self.assertTrue('\nVariables' in out) - self.assertTrue('c = ' in out) + self.assertTrue("\nVariables" in out) + self.assertTrue("c = " in out) return + # End of class TestFitRecipe # ---------------------------------------------------------------------------- diff --git a/src/diffpy/srfit/tests/testfitresults.py b/tests/test_fitresults.py similarity index 93% rename from src/diffpy/srfit/tests/testfitresults.py rename to tests/test_fitresults.py index c5d6c9cc..841c7a73 100644 --- a/src/diffpy/srfit/tests/testfitresults.py +++ b/tests/test_fitresults.py @@ -12,14 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for fitresults module.""" import unittest from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.fitresults import initializeRecipe -from diffpy.srfit.tests.utils import datafile + +from .utils import datafile class TestInitializeRecipe(unittest.TestCase): @@ -33,7 +33,7 @@ def setUp(self): self.Aval = 5.77619823e-01 self.sigval = -9.22758690e-01 - self.x0val = 6.12422115e+00 + self.x0val = 6.12422115e00 return def testInitializeFromFileName(self): @@ -52,7 +52,7 @@ def testInitializeFromFileObj(self): self.assertEqual(0, recipe.A.value) self.assertEqual(0, recipe.sig.value) self.assertEqual(0, recipe.x0.value) - infile = open(self.filename, 'r') + infile = open(self.filename, "r") initializeRecipe(recipe, infile) self.assertFalse(infile.closed) infile.close() @@ -61,13 +61,12 @@ def testInitializeFromFileObj(self): self.assertAlmostEqual(self.x0val, recipe.x0.value) return - def testInitializeFromString(self): recipe = self.recipe self.assertEqual(0, recipe.A.value) self.assertEqual(0, recipe.sig.value) self.assertEqual(0, recipe.x0.value) - infile = open(self.filename, 'r') + infile = open(self.filename, "r") resstr = infile.read() infile.close() initializeRecipe(recipe, resstr) @@ -76,6 +75,7 @@ def testInitializeFromString(self): self.assertAlmostEqual(self.x0val, recipe.x0.value) return + if __name__ == "__main__": unittest.main() diff --git a/src/diffpy/srfit/tests/testliterals.py b/tests/test_literals.py similarity index 83% rename from src/diffpy/srfit/tests/testliterals.py rename to tests/test_literals.py index 6c193a61..8155d64a 100644 --- a/src/diffpy/srfit/tests/testliterals.py +++ b/tests/test_literals.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for the diffpy.srfit.equation.literals module.""" import unittest @@ -24,17 +23,17 @@ # ---------------------------------------------------------------------------- + class TestArgument(unittest.TestCase): def testInit(self): - """Test that everthing initializes as expected.""" + """Test that everything initializes as expected.""" a = literals.Argument() self.assertEqual(None, a._value) self.assertTrue(False is a.const) self.assertTrue(None is a.name) return - def testIdentity(self): """Make sure an Argument is an Argument.""" a = literals.Argument() @@ -42,7 +41,6 @@ def testIdentity(self): self.assertTrue(isinstance(a, abcs.ArgumentABC)) return - def testValue(self): """Test value setting.""" @@ -59,18 +57,20 @@ def testValue(self): self.assertAlmostEqual(3.14, a.getValue()) return + # ---------------------------------------------------------------------------- + class TestCustomOperator(unittest.TestCase): def setUp(self): self.op = literals.makeOperator( - name="add", symbol="+", operation=numpy.add, nin=2, nout=1) + name="add", symbol="+", operation=numpy.add, nin=2, nout=1 + ) return - def testInit(self): - """Test that everthing initializes as expected.""" + """Test that everything initializes as expected.""" op = self.op self.assertEqual("+", op.symbol) self.assertEqual(numpy.add, op.operation) @@ -80,7 +80,6 @@ def testInit(self): self.assertEqual([], op.args) return - def testIdentity(self): """Make sure an Argument is an Argument.""" op = self.op @@ -88,13 +87,12 @@ def testIdentity(self): self.assertTrue(isinstance(op, abcs.OperatorABC)) return - def testValue(self): """Test value.""" # Test addition and operations op = self.op - a = literals.Argument(value = 0) - b = literals.Argument(value = 0) + a = literals.Argument(value=0) + b = literals.Argument(value=0) op.addLiteral(a) op.addLiteral(b) @@ -113,21 +111,20 @@ def testValue(self): return - def testAddLiteral(self): """Test adding a literal to an operator node.""" op = self.op - self.assertRaises(ValueError, op.getValue) + self.assertRaises(TypeError, op.getValue) op._value = 1 self.assertEqual(op.getValue(), 1) # Test addition and operations - a = literals.Argument(name = "a", value = 0) - b = literals.Argument(name = "b", value = 0) + a = literals.Argument(name="a", value=0) + b = literals.Argument(name="b", value=0) op.addLiteral(a) - self.assertRaises(ValueError, op.getValue) + self.assertRaises(TypeError, op.getValue) op.addLiteral(b) self.assertAlmostEqual(0, op.value) @@ -140,21 +137,25 @@ def testAddLiteral(self): # Test for self-references # Try to add self - op1 = literals.makeOperator(name="add", symbol="+", - operation=numpy.add, nin=2, nout=1) + op1 = literals.makeOperator( + name="add", symbol="+", operation=numpy.add, nin=2, nout=1 + ) op1.addLiteral(a) self.assertRaises(ValueError, op1.addLiteral, op1) # Try to add argument that contains self op2 = literals.makeOperator( - name="sub", symbol="-", operation=numpy.subtract, nin=2, nout=1) + name="sub", symbol="-", operation=numpy.subtract, nin=2, nout=1 + ) op2.addLiteral(op1) self.assertRaises(ValueError, op1.addLiteral, op2) return + # ---------------------------------------------------------------------------- + class TestConvolutionOperator(unittest.TestCase): def testValue(self): @@ -169,10 +170,10 @@ def testValue(self): mu2 = 2.5 sig2 = 0.4 - g1 = exp(-0.5*((x-mu1)/sig1)**2) - a1 = literals.Argument(name = "g1", value = g1) - g2 = exp(-0.5*((x-mu2)/sig2)**2) - a2 = literals.Argument(name = "g2", value = g2) + g1 = exp(-0.5 * ((x - mu1) / sig1) ** 2) + a1 = literals.Argument(name="g1", value=g1) + g2 = exp(-0.5 * ((x - mu2) / sig2) ** 2) + a2 = literals.Argument(name="g2", value=g2) op = literals.ConvolutionOperator() op.addLiteral(a1) @@ -181,23 +182,25 @@ def testValue(self): g3c = op.value mu3 = mu1 - sig3 = (sig1**2 + sig2**2)**0.5 - g3 = exp(-0.5*((x-mu3)/sig3)**2) - g3 *= sum(g1)/sum(g3) + sig3 = (sig1**2 + sig2**2) ** 0.5 + g3 = exp(-0.5 * ((x - mu3) / sig3) ** 2) + g3 *= sum(g1) / sum(g3) self.assertAlmostEqual(sum(g3c), sum(g3)) - self.assertAlmostEqual(0, sum((g3-g3c)**2)) + self.assertAlmostEqual(0, sum((g3 - g3c) ** 2)) return + # ---------------------------------------------------------------------------- + class TestArrayOperator(unittest.TestCase): def test_value(self): """Check ArrayOperator.value.""" - x = literals.Argument('x', 1.0) - y = literals.Argument('y', 2.0) - z = literals.Argument('z', 3.0) + x = literals.Argument("x", 1.0) + y = literals.Argument("y", 2.0) + z = literals.Argument("z", 3.0) # check empty array op = literals.ArrayOperator() self.assertEqual(0, len(op.value)) @@ -212,6 +215,7 @@ def test_value(self): self.assertTrue(numpy.array_equal([1, 2, 7], op.value)) return + # ---------------------------------------------------------------------------- if __name__ == "__main__": diff --git a/src/diffpy/srfit/tests/testobjcrystparset.py b/tests/test_objcrystparset.py similarity index 80% rename from src/diffpy/srfit/tests/testobjcrystparset.py rename to tests/test_objcrystparset.py index a8e142fc..2e516136 100644 --- a/src/diffpy/srfit/tests/testobjcrystparset.py +++ b/tests/test_objcrystparset.py @@ -12,14 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for diffpy.srfit.structure package.""" import unittest import numpy -from diffpy.srfit.tests.utils import has_pyobjcryst, _msg_nopyobjcryst +from .utils import _msg_nopyobjcryst, has_pyobjcryst # Global variables to be assigned in setUp ObjCrystCrystalParSet = spacegroups = None @@ -89,6 +88,7 @@ -2.279809890 -2.580456608 -0.724000000 """ + def makeC60(): """Make a crystal containing the C60 molecule using pyobjcryst.""" pi = numpy.pi @@ -99,28 +99,32 @@ def makeC60(): c.AddScatterer(m) sp = ScatteringPowerAtom("C", "C") - sp.SetBiso(8*pi*pi*0.003) - #c.AddScatteringPower(sp) + sp.SetBiso(8 * pi * pi * 0.003) + # c.AddScatteringPower(sp) for i, l in enumerate(c60xyz.strip().splitlines()): x, y, z = map(float, l.split()) - m.AddAtom(x, y, z, sp, "C%i"%i) + m.AddAtom(x, y, z, sp, "C%i" % i) return c + # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) class TestParameterAdapter(unittest.TestCase): def setUp(self): global ObjCrystCrystalParSet, Crystal, Atom, Molecule global ScatteringPowerAtom - from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet - from pyobjcryst.crystal import Crystal from pyobjcryst.atom import Atom + from pyobjcryst.crystal import Crystal from pyobjcryst.molecule import Molecule from pyobjcryst.scatteringpower import ScatteringPowerAtom + + from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet + self.occryst = makeC60() self.ocmol = self.occryst.GetScatterer("c60") return @@ -130,7 +134,6 @@ def tearDown(self): del self.ocmol return - def testObjCrystParSet(self): """Test the structure conversion.""" @@ -181,7 +184,6 @@ def _testMolecule(): self.assertAlmostEqual(ocsp.Biso, a.Biso.getValue()) return - _testCrystal() _testMolecule() @@ -196,17 +198,16 @@ def _testMolecule(): _testMolecule() ## Now change values from the srfit StructureParSet - cryst.c60.C44.x.setValue( 1.1 ) - cryst.c60.C44.occ.setValue( 1.1 ) - cryst.c60.C44.Biso.setValue( 1.1 ) - cryst.c60.q3.setValue( 1.1 ) + cryst.c60.C44.x.setValue(1.1) + cryst.c60.C44.occ.setValue(1.1) + cryst.c60.C44.Biso.setValue(1.1) + cryst.c60.q3.setValue(1.1) cryst.a.setValue(1.1) _testCrystal() _testMolecule() return - def testImplicitBondLengthRestraints(self): """Test the structure with implicit bond lengths.""" occryst = self.occryst @@ -233,7 +234,6 @@ def testImplicitBondLengthRestraints(self): return - def testImplicitBondAngleRestraints(self): """Test the structure with implicit bond angles.""" occryst = self.occryst @@ -260,17 +260,18 @@ def testImplicitBondAngleRestraints(self): return - def testImplicitDihedralAngleRestraints(self): """Test the structure with implicit dihedral angles.""" occryst = self.occryst ocmol = self.ocmol # Add some bond angles to the molecule - ocmol.AddDihedralAngle(ocmol[0], ocmol[5], ocmol[8], ocmol[41], 1.1, - 0.1, 0.1) - ocmol.AddDihedralAngle(ocmol[0], ocmol[7], ocmol[44], ocmol[2], 1.3, - 0.1, 0.1) + ocmol.AddDihedralAngle( + ocmol[0], ocmol[5], ocmol[8], ocmol[41], 1.1, 0.1, 0.1 + ) + ocmol.AddDihedralAngle( + ocmol[0], ocmol[7], ocmol[44], ocmol[2], 1.3, 0.1, 0.1 + ) # make our crystal cryst = ObjCrystCrystalParSet("bucky", occryst) @@ -289,13 +290,11 @@ def testImplicitDihedralAngleRestraints(self): return - def testImplicitStretchModes(self): """Test the molecule with implicit stretch modes.""" # Not sure how to make this happen. pass - def testExplicitBondLengthRestraints(self): """Test the structure with explicit bond lengths.""" occryst = self.occryst @@ -321,12 +320,11 @@ def testExplicitBondLengthRestraints(self): return - def testExplicitBondAngleRestraints(self): """Test the structure with explicit bond angles. - Note that this cannot work with co-linear points as the direction of - rotation cannot be defined in this case. + Note that this cannot work with co-linear points as the + direction of rotation cannot be defined in this case. """ occryst = self.occryst ocmol = self.ocmol @@ -336,10 +334,12 @@ def testExplicitBondAngleRestraints(self): m = cryst.c60 # restrain some bond angles - res0 = m.restrainBondAngle(m.atoms[0], m.atoms[5], m.atoms[8], 3.3, - 0.1, 0.1) - res1 = m.restrainBondAngle(m.atoms[0], m.atoms[7], m.atoms[44], 3.3, - 0.1, 0.1) + res0 = m.restrainBondAngle( + m.atoms[0], m.atoms[5], m.atoms[8], 3.3, 0.1, 0.1 + ) + res1 = m.restrainBondAngle( + m.atoms[0], m.atoms[7], m.atoms[44], 3.3, 0.1, 0.1 + ) # make sure that we have some restraints in the molecule self.assertTrue(2, len(m._restraints)) @@ -352,7 +352,6 @@ def testExplicitBondAngleRestraints(self): return - def testExplicitDihedralAngleRestraints(self): """Test the structure with explicit dihedral angles.""" occryst = self.occryst @@ -363,11 +362,12 @@ def testExplicitDihedralAngleRestraints(self): m = cryst.c60 # Restrain some dihedral angles. - res0 = m.restrainDihedralAngle(m.atoms[0], m.atoms[5], m.atoms[8], - m.atoms[41], 1.1, 0.1, 0.1) - res1 = m.restrainDihedralAngle(m.atoms[0], m.atoms[7], m.atoms[44], - m.atoms[2], 1.1, 0.1, 0.1) - + res0 = m.restrainDihedralAngle( + m.atoms[0], m.atoms[5], m.atoms[8], m.atoms[41], 1.1, 0.1, 0.1 + ) + res1 = m.restrainDihedralAngle( + m.atoms[0], m.atoms[7], m.atoms[44], m.atoms[2], 1.1, 0.1, 0.1 + ) # make sure that we have some restraints in the molecule self.assertTrue(2, len(m._restraints)) @@ -380,7 +380,6 @@ def testExplicitDihedralAngleRestraints(self): return - def testExplicitBondLengthParameter(self): """Test adding bond length parameters to the molecule.""" occryst = self.occryst @@ -400,48 +399,53 @@ def testExplicitBondLengthParameter(self): xyz0 = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) xyz7 = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20 = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) + xyz20 = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) dd = xyz0 - xyz7 - d0 = numpy.dot(dd, dd)**0.5 + d0 = numpy.dot(dd, dd) ** 0.5 self.assertAlmostEqual(d0, p1.getValue(), 6) # Record the unit direction of change for later - u = dd/d0 + u = dd / d0 # Change the value scale = 1.05 - p1.setValue(scale*d0) + p1.setValue(scale * d0) # Verify that it has changed. - self.assertAlmostEqual(scale*d0, p1.getValue()) - - xyz0a = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) - xyz7a = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20a = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) + self.assertAlmostEqual(scale * d0, p1.getValue()) + + xyz0a = numpy.array( + [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] + ) + xyz7a = numpy.array( + [a7.x.getValue(), a7.y.getValue(), a7.z.getValue()] + ) + xyz20a = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) dda = xyz0a - xyz7a - d1 = numpy.dot(dda, dda)**0.5 + d1 = numpy.dot(dda, dda) ** 0.5 - self.assertAlmostEqual(scale*d0, d1) + self.assertAlmostEqual(scale * d0, d1) # Verify that only the second and third atoms have moved. self.assertTrue(numpy.array_equal(xyz0, xyz0a)) - xyz7calc = xyz7 + (1-scale)*d0*u + xyz7calc = xyz7 + (1 - scale) * d0 * u for i in range(3): self.assertAlmostEqual(xyz7a[i], xyz7calc[i], 6) - xyz20calc = xyz20 + (1-scale)*d0*u + xyz20calc = xyz20 + (1 - scale) * d0 * u for i in range(3): self.assertAlmostEqual(xyz20a[i], xyz20calc[i], 6) return - def testExplicitBondAngleParameter(self): """Test adding bond angle parameters to the molecule.""" occryst = self.occryst @@ -457,18 +461,19 @@ def testExplicitBondAngleParameter(self): xyz0 = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) xyz7 = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20 = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) - xyz25 = numpy.array([a25.x.getValue(), a25.y.getValue(), - a25.z.getValue()]) - + xyz20 = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) + xyz25 = numpy.array( + [a25.x.getValue(), a25.y.getValue(), a25.z.getValue()] + ) v1 = xyz7 - xyz0 - d1 = numpy.dot(v1, v1)**0.5 + d1 = numpy.dot(v1, v1) ** 0.5 v2 = xyz7 - xyz20 - d2 = numpy.dot(v2, v2)**0.5 + d2 = numpy.dot(v2, v2) ** 0.5 - angle0 = numpy.arccos(numpy.dot(v1, v2)/(d1*d2)) + angle0 = numpy.arccos(numpy.dot(v1, v2) / (d1 * d2)) # Add a parameter p1 = m.addBondAngleParameter("C0720", a0, a7, a20) @@ -479,26 +484,32 @@ def testExplicitBondAngleParameter(self): # Change the value scale = 1.05 - p1.setValue(scale*angle0) + p1.setValue(scale * angle0) # Verify that it has changed. - self.assertAlmostEqual(scale*angle0, p1.getValue(), 6) - - xyz0a = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) - xyz7a = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20a = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) - xyz25a = numpy.array([a25.x.getValue(), a25.y.getValue(), - a25.z.getValue()]) + self.assertAlmostEqual(scale * angle0, p1.getValue(), 6) + + xyz0a = numpy.array( + [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] + ) + xyz7a = numpy.array( + [a7.x.getValue(), a7.y.getValue(), a7.z.getValue()] + ) + xyz20a = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) + xyz25a = numpy.array( + [a25.x.getValue(), a25.y.getValue(), a25.z.getValue()] + ) v1a = xyz7a - xyz0a - d1a = numpy.dot(v1a, v1a)**0.5 + d1a = numpy.dot(v1a, v1a) ** 0.5 v2a = xyz7a - xyz20a - d2a = numpy.dot(v2a, v2a)**0.5 + d2a = numpy.dot(v2a, v2a) ** 0.5 - angle1 = numpy.arccos(numpy.dot(v1a, v2a)/(d1a*d2a)) + angle1 = numpy.arccos(numpy.dot(v1a, v2a) / (d1a * d2a)) - self.assertAlmostEqual(scale*angle0, angle1) + self.assertAlmostEqual(scale * angle0, angle1) # Verify that only the last two atoms have moved. @@ -509,7 +520,6 @@ def testExplicitBondAngleParameter(self): return - def testExplicitDihedralAngleParameter(self): """Test adding dihedral angle parameters to the molecule.""" occryst = self.occryst @@ -526,13 +536,15 @@ def testExplicitDihedralAngleParameter(self): xyz0 = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) xyz7 = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20 = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) - xyz25 = numpy.array([a25.x.getValue(), a25.y.getValue(), - a25.z.getValue()]) - xyz33 = numpy.array([a33.x.getValue(), a33.y.getValue(), - a33.z.getValue()]) - + xyz20 = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) + xyz25 = numpy.array( + [a25.x.getValue(), a25.y.getValue(), a25.z.getValue()] + ) + xyz33 = numpy.array( + [a33.x.getValue(), a33.y.getValue(), a33.z.getValue()] + ) v12 = xyz0 - xyz7 v23 = xyz7 - xyz20 @@ -540,9 +552,9 @@ def testExplicitDihedralAngleParameter(self): v123 = numpy.cross(v12, v23) v234 = numpy.cross(v23, v34) - d123 = numpy.dot(v123, v123)**0.5 - d234 = numpy.dot(v234, v234)**0.5 - angle0 = -numpy.arccos(numpy.dot(v123, v234)/(d123*d234)) + d123 = numpy.dot(v123, v123) ** 0.5 + d234 = numpy.dot(v234, v234) ** 0.5 + angle0 = -numpy.arccos(numpy.dot(v123, v234) / (d123 * d234)) # Add a parameter p1 = m.addDihedralAngleParameter("C072025", a0, a7, a20, a25) @@ -553,19 +565,26 @@ def testExplicitDihedralAngleParameter(self): # Change the value scale = 1.05 - p1.setValue(scale*angle0) + p1.setValue(scale * angle0) # Verify that it has changed. - self.assertAlmostEqual(scale*angle0, p1.getValue(), 6) - - xyz0a = numpy.array([a0.x.getValue(), a0.y.getValue(), a0.z.getValue()]) - xyz7a = numpy.array([a7.x.getValue(), a7.y.getValue(), a7.z.getValue()]) - xyz20a = numpy.array([a20.x.getValue(), a20.y.getValue(), - a20.z.getValue()]) - xyz25a = numpy.array([a25.x.getValue(), a25.y.getValue(), - a25.z.getValue()]) - xyz33a = numpy.array([a33.x.getValue(), a33.y.getValue(), - a33.z.getValue()]) + self.assertAlmostEqual(scale * angle0, p1.getValue(), 6) + + xyz0a = numpy.array( + [a0.x.getValue(), a0.y.getValue(), a0.z.getValue()] + ) + xyz7a = numpy.array( + [a7.x.getValue(), a7.y.getValue(), a7.z.getValue()] + ) + xyz20a = numpy.array( + [a20.x.getValue(), a20.y.getValue(), a20.z.getValue()] + ) + xyz25a = numpy.array( + [a25.x.getValue(), a25.y.getValue(), a25.z.getValue()] + ) + xyz33a = numpy.array( + [a33.x.getValue(), a33.y.getValue(), a33.z.getValue()] + ) v12a = xyz0a - xyz7a v23a = xyz7a - xyz20a @@ -573,11 +592,11 @@ def testExplicitDihedralAngleParameter(self): v123a = numpy.cross(v12a, v23a) v234a = numpy.cross(v23a, v34a) - d123a = numpy.dot(v123a, v123a)**0.5 - d234a = numpy.dot(v234a, v234a)**0.5 - angle1 = -numpy.arccos(numpy.dot(v123a, v234a)/(d123a*d234a)) + d123a = numpy.dot(v123a, v123a) ** 0.5 + d234a = numpy.dot(v234a, v234a) ** 0.5 + angle1 = -numpy.arccos(numpy.dot(v123a, v234a) / (d123a * d234a)) - self.assertAlmostEqual(scale*angle0, angle1) + self.assertAlmostEqual(scale * angle0, angle1) # Verify that only the last two atoms have moved. @@ -589,16 +608,18 @@ def testExplicitDihedralAngleParameter(self): return + # End of class TestParameterAdapter # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) class TestCreateSpaceGroup(unittest.TestCase): """Test space group creation from pyobjcryst structures. - This makes sure that the space groups created by the structure parameter - set are correct. + This makes sure that the space groups created by the structure + parameter set are correct. """ def setUp(self): @@ -610,6 +631,7 @@ def setUp(self): def getObjCrystParSetSpaceGroup(sg): """Make an ObjCrystCrystalParSet with the proper space group.""" from pyobjcryst.spacegroup import SpaceGroup + sgobjcryst = SpaceGroup(sg.short_name) sgnew = ObjCrystCrystalParSet._createSpaceGroup(sgobjcryst) return sgnew @@ -617,7 +639,7 @@ def getObjCrystParSetSpaceGroup(sg): @staticmethod def hashDiffPySpaceGroup(sg): lines = [str(sg.number % 1000)] + sorted(map(str, sg.iter_symops())) - s = '\n'.join(lines) + s = "\n".join(lines) return s def sgsEquivalent(self, sg1, sg2): @@ -638,7 +660,7 @@ def xtestCreateSpaceGroup(self): for smbls in sgtbx.space_group_symbol_iterator(): shn = smbls.hermann_mauguin() - short_name = shn.replace(' ', '') + short_name = shn.replace(" ", "") if spacegroups.IsSpaceGroupIdentifier(short_name): sg = spacegroups.GetSpaceGroup(shn) sgnew = self.getObjCrystParSetSpaceGroup(sg) @@ -646,6 +668,7 @@ def xtestCreateSpaceGroup(self): self.assertTrue(self.sgsEquivalent(sg, sgnew)) return + # End of class TestCreateSpaceGroup if __name__ == "__main__": diff --git a/src/diffpy/srfit/tests/testparameter.py b/tests/test_parameter.py similarity index 85% rename from src/diffpy/srfit/tests/testparameter.py rename to tests/test_parameter.py index 76eaee3b..56e00d4f 100644 --- a/src/diffpy/srfit/tests/testparameter.py +++ b/tests/test_parameter.py @@ -12,13 +12,15 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.fitbase.parameter import ParameterAdapter, ParameterProxy +from diffpy.srfit.fitbase.parameter import ( + Parameter, + ParameterAdapter, + ParameterProxy, +) class TestParameter(unittest.TestCase): @@ -32,16 +34,17 @@ def testSetValue(self): # Try array import numpy + x = numpy.arange(0, 10, 0.1) l.setValue(x) - self.assertTrue( l.getValue() is x ) - self.assertTrue( l.value is x ) + self.assertTrue(l.getValue() is x) + self.assertTrue(l.value is x) # Change the array y = numpy.arange(0, 10, 0.5) l.value = y - self.assertTrue( l.getValue() is y ) - self.assertTrue( l.value is y ) + self.assertTrue(l.getValue() is y) + self.assertTrue(l.value is y) # Back to scalar l.setValue(1.01) @@ -49,6 +52,7 @@ def testSetValue(self): self.assertAlmostEqual(1.01, l.value) return + class TestParameterProxy(unittest.TestCase): def testProxy(self): @@ -73,6 +77,7 @@ def testProxy(self): return + class TestParameterAdapter(unittest.TestCase): def testWrapper(self): @@ -83,8 +88,9 @@ def testWrapper(self): l = Parameter("l", 3.14) # Try Accessor adaptation - la = ParameterAdapter("l", l, getter = Parameter.getValue, setter = - Parameter.setValue) + la = ParameterAdapter( + "l", l, getter=Parameter.getValue, setter=Parameter.setValue + ) self.assertEqual(l.name, la.name) self.assertEqual(l.getValue(), la.getValue()) @@ -98,7 +104,7 @@ def testWrapper(self): self.assertEqual(l.getValue(), la.getValue()) # Try Attribute adaptation - la = ParameterAdapter("l", l, attr = "value") + la = ParameterAdapter("l", l, attr="value") self.assertEqual(l.name, la.name) self.assertEqual("value", la.attr) diff --git a/src/diffpy/srfit/tests/testparameterset.py b/tests/test_parameterset.py similarity index 99% rename from src/diffpy/srfit/tests/testparameterset.py rename to tests/test_parameterset.py index abbad180..51ffa14c 100644 --- a/src/diffpy/srfit/tests/testparameterset.py +++ b/tests/test_parameterset.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest diff --git a/src/diffpy/srfit/tests/testpdf.py b/tests/test_pdf.py similarity index 70% rename from src/diffpy/srfit/tests/testpdf.py rename to tests/test_pdf.py index 1c2695a9..f9ad04bc 100644 --- a/src/diffpy/srfit/tests/testpdf.py +++ b/tests/test_pdf.py @@ -12,29 +12,33 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for pdf package.""" -import unittest -import pickle import io +import pickle +import unittest import numpy -from diffpy.srfit.tests.utils import datafile -from diffpy.srfit.tests.utils import has_srreal, _msg_nosrreal -from diffpy.srfit.tests.utils import has_structure, _msg_nostructure -from diffpy.srfit.pdf import PDFGenerator, PDFParser, PDFContribution from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.pdf import PDFContribution, PDFGenerator, PDFParser + +from .utils import ( + _msg_nosrreal, + _msg_nostructure, + datafile, + has_srreal, + has_structure, +) # ---------------------------------------------------------------------------- + class TestPDFParset(unittest.TestCase): def setUp(self): return - def testParser1(self): data = datafile("ni-q27r100-neutron.gr") parser = PDFParser() @@ -42,16 +46,16 @@ def testParser1(self): meta = parser._meta - self.assertEqual(data, meta['filename']) - self.assertEqual(1, meta['nbanks']) - self.assertEqual('N', meta['stype']) - self.assertEqual(27, meta['qmax']) - self.assertEqual(300, meta.get('temperature')) - self.assertEqual(None, meta.get('qdamp')) - self.assertEqual(None, meta.get('qbroad')) - self.assertEqual(None, meta.get('spdiameter')) - self.assertEqual(None, meta.get('scale')) - self.assertEqual(None, meta.get('doping')) + self.assertEqual(data, meta["filename"]) + self.assertEqual(1, meta["nbanks"]) + self.assertEqual("N", meta["stype"]) + self.assertEqual(27, meta["qmax"]) + self.assertEqual(300, meta.get("temperature")) + self.assertEqual(None, meta.get("qdamp")) + self.assertEqual(None, meta.get("qbroad")) + self.assertEqual(None, meta.get("spdiameter")) + self.assertEqual(None, meta.get("scale")) + self.assertEqual(None, meta.get("doping")) x, y, dx, dy = parser.getData() self.assertTrue(dx is None) @@ -62,15 +66,26 @@ def testParser1(self): res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testy = numpy.array([1.144, 2.258, 3.312, 4.279, 5.135, 5.862, 6.445, - 6.875, 7.150, 7.272]) + testy = numpy.array( + [ + 1.144, + 2.258, + 3.312, + 4.279, + 5.135, + 5.862, + 6.445, + 6.875, + 7.150, + 7.272, + ] + ) diff = testy - y[:10] res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) return - def testParser2(self): data = datafile("si-q27r60-xray.gr") parser = PDFParser() @@ -78,16 +93,16 @@ def testParser2(self): meta = parser._meta - self.assertEqual(data, meta['filename']) - self.assertEqual(1, meta['nbanks']) - self.assertEqual('X', meta['stype']) - self.assertEqual(27, meta['qmax']) - self.assertEqual(300, meta.get('temperature')) - self.assertEqual(None, meta.get('qdamp')) - self.assertEqual(None, meta.get('qbroad')) - self.assertEqual(None, meta.get('spdiameter')) - self.assertEqual(None, meta.get('scale')) - self.assertEqual(None, meta.get('doping')) + self.assertEqual(data, meta["filename"]) + self.assertEqual(1, meta["nbanks"]) + self.assertEqual("X", meta["stype"]) + self.assertEqual(27, meta["qmax"]) + self.assertEqual(300, meta.get("temperature")) + self.assertEqual(None, meta.get("qdamp")) + self.assertEqual(None, meta.get("qbroad")) + self.assertEqual(None, meta.get("spdiameter")) + self.assertEqual(None, meta.get("scale")) + self.assertEqual(None, meta.get("doping")) x, y, dx, dy = parser.getData() testx = numpy.linspace(0.01, 60, 5999, endpoint=False) @@ -95,15 +110,38 @@ def testParser2(self): res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testy = numpy.array([0.1105784, 0.2199684, 0.3270088, 0.4305913, - 0.5296853, 0.6233606, 0.7108060, 0.7913456, 0.8644501, 0.9297440]) + testy = numpy.array( + [ + 0.1105784, + 0.2199684, + 0.3270088, + 0.4305913, + 0.5296853, + 0.6233606, + 0.7108060, + 0.7913456, + 0.8644501, + 0.9297440, + ] + ) diff = testy - y[:10] res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testdy = numpy.array([0.001802192, 0.003521449, 0.005079115, - 0.006404892, 0.007440527, 0.008142955, 0.008486813, 0.008466340, - 0.008096858, 0.007416456]) + testdy = numpy.array( + [ + 0.001802192, + 0.003521449, + 0.005079115, + 0.006404892, + 0.007440527, + 0.008142955, + 0.008486813, + 0.008466340, + 0.008096858, + 0.007416456, + ] + ) diff = testdy - dy[:10] res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) @@ -111,10 +149,12 @@ def testParser2(self): self.assertTrue(dx is None) return + # End of class TestPDFParset # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_srreal, _msg_nosrreal) @unittest.skipUnless(has_structure, _msg_nostructure) class TestPDFGenerator(unittest.TestCase): @@ -123,15 +163,15 @@ def setUp(self): self.gen = PDFGenerator() return - def testGenerator(self): qmax = 27.0 gen = self.gen - gen.setScatteringType('N') - self.assertEqual('N', gen.getScatteringType()) + gen.setScatteringType("N") + self.assertEqual("N", gen.getScatteringType()) gen.setQmax(qmax) self.assertAlmostEqual(qmax, gen.getQmax()) from diffpy.structure import PDFFitStructure + stru = PDFFitStructure() ciffile = datafile("ni.cif") stru.read(ciffile) @@ -158,12 +198,13 @@ def testGenerator(self): # output, we just have to make sure we can calculate from the # PDFGenerator interface. from diffpy.srreal.pdfcalculator import PDFCalculator + calc = PDFCalculator() calc.rstep = r[1] - r[0] calc.rmin = r[0] calc.rmax = r[-1] + 0.5 * calc.rstep calc.qmax = qmax - calc.setScatteringFactorTableByType('N') + calc.setScatteringFactorTableByType("N") calc.eval(stru) yref = calc.pdf @@ -172,7 +213,6 @@ def testGenerator(self): self.assertAlmostEqual(0, res) return - def test_setQmin(self): """Verify qmin is propagated to the calculator object.""" gen = self.gen @@ -183,35 +223,37 @@ def test_setQmin(self): self.assertEqual(0.93, gen._calc.qmin) return + # End of class TestPDFGenerator # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_srreal, _msg_nosrreal) @unittest.skipUnless(has_structure, _msg_nostructure) class TestPDFContribution(unittest.TestCase): def setUp(self): - self.pc = PDFContribution('pdf') + self.pc = PDFContribution("pdf") return - def test_setQmax(self): """Check PDFContribution.setQmax()""" from diffpy.structure import Structure + pc = self.pc pc.setQmax(21) - pc.addStructure('empty', Structure()) + pc.addStructure("empty", Structure()) self.assertEqual(21, pc.empty.getQmax()) pc.setQmax(22) self.assertEqual(22, pc.getQmax()) self.assertEqual(22, pc.empty.getQmax()) return - def test_getQmax(self): """Check PDFContribution.getQmax()""" from diffpy.structure import Structure + # cover all code branches in PDFContribution._getMetaValue # (1) contribution metadata pc1 = self.pc @@ -219,54 +261,56 @@ def test_getQmax(self): pc1.setQmax(17) self.assertEqual(17, pc1.getQmax()) # (2) contribution metadata - pc2 = PDFContribution('pdf') - pc2.addStructure('empty', Structure()) + pc2 = PDFContribution("pdf") + pc2.addStructure("empty", Structure()) pc2.empty.setQmax(18) self.assertEqual(18, pc2.getQmax()) # (3) profile metadata - pc3 = PDFContribution('pdf') - pc3.profile.meta['qmax'] = 19 + pc3 = PDFContribution("pdf") + pc3.profile.meta["qmax"] = 19 self.assertEqual(19, pc3.getQmax()) return - def test_savetxt(self): "check PDFContribution.savetxt()" from diffpy.structure import Structure + pc = self.pc pc.loadData(datafile("si-q27r60-xray.gr")) pc.setCalculationRange(0, 10) - pc.addStructure('empty', Structure()) + pc.addStructure("empty", Structure()) fp = io.BytesIO() self.assertRaises(SrFitError, pc.savetxt, fp) pc.evaluate() pc.savetxt(fp) txt = fp.getvalue().decode() - nlines = len(txt.strip().split('\n')) + nlines = len(txt.strip().split("\n")) self.assertEqual(1001, nlines) return - def test_pickling(self): "validate PDFContribution.residual() after pickling." from itertools import chain + from diffpy.structure import loadStructure + pc = self.pc pc.loadData(datafile("ni-q27r100-neutron.gr")) ni = loadStructure(datafile("ni.cif")) ni.Uisoequiv = 0.003 - pc.addStructure('ni', ni) + pc.addStructure("ni", ni) pc.setCalculationRange(0, 10) pc2 = pickle.loads(pickle.dumps(pc)) res0 = pc.residual() self.assertTrue(numpy.array_equal(res0, pc2.residual())) - for p in chain(pc.iterPars('Uiso'), pc2.iterPars('Uiso')): + for p in chain(pc.iterPars("Uiso"), pc2.iterPars("Uiso")): p.value = 0.004 res1 = pc.residual() self.assertFalse(numpy.allclose(res0, res1)) self.assertTrue(numpy.array_equal(res1, pc2.residual())) return + # End of class TestPDFContribution # ---------------------------------------------------------------------------- diff --git a/src/diffpy/srfit/tests/testprofile.py b/tests/test_profile.py similarity index 82% rename from src/diffpy/srfit/tests/testprofile.py rename to tests/test_profile.py index bd7217a8..f19adef6 100644 --- a/src/diffpy/srfit/tests/testprofile.py +++ b/tests/test_profile.py @@ -12,18 +12,18 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" -import unittest -import re import io +import re +import unittest -from numpy import array, arange, array_equal, ones_like, allclose +from numpy import allclose, arange, array, array_equal, ones_like -from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.exceptions import SrFitError -from diffpy.srfit.tests.utils import datafile +from diffpy.srfit.fitbase.profile import Profile + +from .utils import datafile class TestProfile(unittest.TestCase): @@ -55,9 +55,9 @@ def testSetObservedProfile(self): prof = self.profile prof.setObservedProfile(x, y, dy) - self.assertTrue( array_equal(x, prof.xobs) ) - self.assertTrue( array_equal(y, prof.yobs) ) - self.assertTrue( array_equal(dy, prof.dyobs) ) + self.assertTrue(array_equal(x, prof.xobs)) + self.assertTrue(array_equal(y, prof.yobs)) + self.assertTrue(array_equal(dy, prof.dyobs)) # Make a profile with undefined dy x = arange(0, 10, 0.1) @@ -66,18 +66,17 @@ def testSetObservedProfile(self): self.profile.setObservedProfile(x, y, dy) - self.assertTrue( array_equal(x, prof.xobs) ) - self.assertTrue( array_equal(y, prof.yobs) ) - self.assertTrue( array_equal(ones_like(prof.xobs), prof.dyobs)) + self.assertTrue(array_equal(x, prof.xobs)) + self.assertTrue(array_equal(y, prof.yobs)) + self.assertTrue(array_equal(ones_like(prof.xobs), prof.dyobs)) # Get the ranged profile to make sure its the same - self.assertTrue( array_equal(x, prof.x) ) - self.assertTrue( array_equal(y, prof.y) ) - self.assertTrue( array_equal(ones_like(prof.xobs), prof.dy)) + self.assertTrue(array_equal(x, prof.x)) + self.assertTrue(array_equal(y, prof.y)) + self.assertTrue(array_equal(ones_like(prof.xobs), prof.dy)) return - def testSetCalculationRange(self): """Test the setCalculationRange method.""" x = arange(2, 9.6, 0.5) @@ -110,26 +109,28 @@ def testSetCalculationRange(self): self.assertTrue(array_equal(y, prof.y)) self.assertTrue(array_equal(dy, prof.dy)) # Test xmin > xmax - self.assertRaises(ValueError, prof.setCalculationRange, - xmin=10, xmax=3) + self.assertRaises( + ValueError, prof.setCalculationRange, xmin=10, xmax=3 + ) # Test xmax - xmin < dx - self.assertRaises(ValueError, prof.setCalculationRange, - xmin=3, xmax=3.9, dx=1.0) + self.assertRaises( + ValueError, prof.setCalculationRange, xmin=3, xmax=3.9, dx=1.0 + ) # Test dx <= 0 self.assertRaises(ValueError, prof.setCalculationRange, dx=0) self.assertRaises(ValueError, prof.setCalculationRange, dx=-0.000001) # using string other than 'obs' - self.assertRaises(ValueError, prof.setCalculationRange, xmin='oobs') - self.assertRaises(ValueError, prof.setCalculationRange, xmax='oobs') - self.assertRaises(ValueError, prof.setCalculationRange, dx='oobs') + self.assertRaises(ValueError, prof.setCalculationRange, xmin="oobs") + self.assertRaises(ValueError, prof.setCalculationRange, xmax="oobs") + self.assertRaises(ValueError, prof.setCalculationRange, dx="oobs") # This should be alright prof.setCalculationRange(3, 5) - prof.setCalculationRange(xmin='obs', xmax=7, dx=0.001) + prof.setCalculationRange(xmin="obs", xmax=7, dx=0.001) self.assertEqual(5001, len(prof.x)) self.assertEqual(len(prof.x), len(prof.y)) self.assertEqual(len(prof.x), len(prof.dy)) # Test an internal bound - prof.setCalculationRange(4, 7, dx='obs') + prof.setCalculationRange(4, 7, dx="obs") self.assertTrue(array_equal(prof.x, arange(4, 7.1, 0.5))) self.assertTrue(array_equal(prof.y, arange(4, 7.1, 0.5))) self.assertTrue(array_equal(prof.y, arange(4, 7.1, 0.5))) @@ -158,7 +159,6 @@ def testSetCalculationRange(self): self.assertTrue(array_equal(prof.x, arange(4.5, 6.1, 0.5))) return - def testSetCalculationPoints(self): """Test the setCalculationPoints method.""" prof = self.profile @@ -170,11 +170,11 @@ def testSetCalculationPoints(self): # Test without data xcalc = arange(3, 12.2, 0.2) prof.setCalculationPoints(xcalc) - self.assertTrue( array_equal(xcalc, prof.x) ) + self.assertTrue(array_equal(xcalc, prof.x)) # Add the data. This should change the bounds of the calculation array. prof.setObservedProfile(x, y, dy) - self.assertTrue( array_equal(arange(3, 10.1, 0.2), prof.x ) ) + self.assertTrue(array_equal(arange(3, 10.1, 0.2), prof.x)) return @@ -190,17 +190,17 @@ def _test(p): self.assertAlmostEqual(1.802192e-3, p.dy[0]) # Test normal load - prof.loadtxt(data, usecols=(0,1,3)) + prof.loadtxt(data, usecols=(0, 1, 3)) _test(prof) # Test trying to not set unpack - prof.loadtxt(data, usecols=(0,1,3), unpack = False) + prof.loadtxt(data, usecols=(0, 1, 3), unpack=False) _test(prof) - prof.loadtxt(data, float, '#', None, None, 0, (0,1,3), False) + prof.loadtxt(data, float, "#", None, None, 0, (0, 1, 3), False) _test(prof) # Try not including dy - prof.loadtxt(data, usecols=(0,1)) + prof.loadtxt(data, usecols=(0, 1)) self.assertAlmostEqual(1e-2, prof.x[0]) self.assertAlmostEqual(1.105784e-1, prof.y[0]) self.assertAlmostEqual(1, prof.dy[0]) @@ -209,23 +209,23 @@ def _test(p): self.assertRaises(ValueError, prof.loadtxt, data, usecols=(0,)) return - def test_savetxt(self): "Check the savetxt method." prof = self.profile - self.assertRaises(SrFitError, prof.savetxt, 'foo') + self.assertRaises(SrFitError, prof.savetxt, "foo") xobs = arange(-2, 3.01, 0.25) - yobs = xobs ** 2 + yobs = xobs**2 prof.setObservedProfile(xobs, yobs) prof.ycalc = yobs.copy() fp = io.BytesIO() prof.savetxt(fp) txt = fp.getvalue().decode() - self.assertTrue(re.match(r'^# x +ycalc +y +dy\b', txt)) - nlines = len(txt.strip().split('\n')) + self.assertTrue(re.match(r"^# x +ycalc +y +dy\b", txt)) + nlines = len(txt.strip().split("\n")) self.assertEqual(22, nlines) return + # End of class TestProfile # ---------------------------------------------------------------------------- diff --git a/src/diffpy/srfit/tests/testprofilegenerator.py b/tests/test_profilegenerator.py similarity index 98% rename from src/diffpy/srfit/tests/testprofilegenerator.py rename to tests/test_profilegenerator.py index 8e67ea29..6e42e397 100644 --- a/src/diffpy/srfit/tests/testprofilegenerator.py +++ b/tests/test_profilegenerator.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for profilegenerator module.""" import pickle @@ -20,8 +19,8 @@ from numpy import arange, array_equal -from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator from diffpy.srfit.fitbase.profile import Profile +from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator class TestProfileGenerator(unittest.TestCase): @@ -34,7 +33,6 @@ def setUp(self): self.gen.setProfile(self.profile) return - def testOperation(self): """Test the operation method.""" gen = self.gen @@ -49,7 +47,6 @@ def testOperation(self): self.assertTrue(array_equal(2 * prof.x, val)) return - def testUpdate(self): """Update and change the profile to make sure generator is flushed.""" gen = self.gen @@ -77,17 +74,17 @@ def testUpdate(self): self.assertTrue(array_equal(x, gen.value)) return - def test_pickling(self): """Test pickling of ProfileGenerator.""" data = pickle.dumps(self.gen) gen2 = pickle.loads(data) - self.assertEqual('test', gen2.name) + self.assertEqual("test", gen2.name) x = self.profile.x self.assertTrue(array_equal(x, gen2.operation())) self.assertTrue(array_equal(3 * x, gen2(3 * x))) return + # End of class TestProfileGenerator if __name__ == "__main__": diff --git a/src/diffpy/srfit/tests/testrecipeorganizer.py b/tests/test_recipeorganizer.py similarity index 85% rename from src/diffpy/srfit/tests/testrecipeorganizer.py rename to tests/test_recipeorganizer.py index c39e2626..60b2e4da 100644 --- a/src/diffpy/srfit/tests/testrecipeorganizer.py +++ b/tests/test_recipeorganizer.py @@ -12,23 +12,26 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest +import numpy + from diffpy.srfit.equation.builder import EquationFactory from diffpy.srfit.fitbase.calculator import Calculator from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.fitbase.recipeorganizer import equationFromString -from diffpy.srfit.fitbase.recipeorganizer import RecipeContainer -from diffpy.srfit.fitbase.recipeorganizer import RecipeOrganizer -from diffpy.srfit.tests.utils import capturestdout +from diffpy.srfit.fitbase.recipeorganizer import ( + RecipeContainer, + RecipeOrganizer, + equationFromString, +) -import numpy +from .utils import capturestdout # ---------------------------------------------------------------------------- + class TestEquationFromString(unittest.TestCase): def testEquationFromString(self): @@ -56,7 +59,7 @@ def testEquationFromString(self): self.assertRaises(ValueError, equationFromString, "p1+p2+p3", factory) # Pass that argument in the ns dictionary - eq = equationFromString("p1+p2+p3", factory, {"p3":p3}) + eq = equationFromString("p1+p2+p3", factory, {"p3": p3}) self.assertEqual(3, len(eq.args)) self.assertTrue(p1 in eq.args) self.assertTrue(p2 in eq.args) @@ -67,17 +70,21 @@ def testEquationFromString(self): self.assertTrue("p3" not in factory.builders) # Pass and use an unregistered parameter - self.assertRaises(ValueError, equationFromString, "p1+p2+p3+p4", - factory, {"p3":p3}) + self.assertRaises( + ValueError, equationFromString, "p1+p2+p3+p4", factory, {"p3": p3} + ) # Try to overload a registered parameter - self.assertRaises(ValueError, equationFromString, "p1+p2", - factory, {"p2":p4}) + self.assertRaises( + ValueError, equationFromString, "p1+p2", factory, {"p2": p4} + ) return + # ---------------------------------------------------------------------------- + class TestRecipeContainer(unittest.TestCase): def setUp(self): @@ -107,14 +114,18 @@ def testAccessors(self): self.assertTrue(m1.m2.p2 is p2) self.assertTrue(m1[0] is p1) - self.assertTrue(m1[0:] == [p1,]) + self.assertTrue( + m1[0:] + == [ + p1, + ] + ) self.assertTrue(m2[0] is p2) self.assertEqual(1, len(m1)) self.assertEqual(1, len(m2)) return - def testLocateManagedObject(self): """Test the locateManagedObject method.""" m1 = self.m @@ -155,8 +166,10 @@ def testLocateManagedObject(self): return + # ---------------------------------------------------------------------------- + class TestRecipeOrganizer(unittest.TestCase): def setUp(self): @@ -169,7 +182,6 @@ def setUp(self): def tearDown(self): return - def testNewParameter(self): """Test the addParameter method.""" @@ -186,7 +198,6 @@ def testNewParameter(self): self.assertTrue(p2 is m.p2) return - def testAddParameter(self): """Test the addParameter method.""" @@ -261,7 +272,6 @@ def testConstrain(self): self.assertEqual(0, len(self.m._constraints)) self.m.constrain(p1, "2*p2") - self.assertTrue(p1.constrained) self.assertTrue(p1 in self.m._constraints) self.assertEqual(1, len(self.m._constraints)) @@ -273,7 +283,7 @@ def testConstrain(self): # Check errors on unregistered parameters self.assertRaises(ValueError, self.m.constrain, p1, "2*p3") - self.assertRaises(ValueError, self.m.constrain, p1, "2*p2", {"p2":p3}) + self.assertRaises(ValueError, self.m.constrain, p1, "2*p2", {"p2": p3}) # Remove the constraint self.m.unconstrain(p1) @@ -298,21 +308,21 @@ def testRestrain(self): self.m._eqfactory.registerArgument("p2", p2) self.assertEqual(0, len(self.m._restraints)) - r = self.m.restrain("p1+p2", ub = 10) + r = self.m.restrain("p1+p2", ub=10) self.assertEqual(1, len(self.m._restraints)) p2.setValue(10) self.assertEqual(1, r.penalty()) self.m.unrestrain(r) self.assertEqual(0, len(self.m._restraints)) - r = self.m.restrain(p1, ub = 10) + r = self.m.restrain(p1, ub=10) self.assertEqual(1, len(self.m._restraints)) p1.setValue(11) self.assertEqual(1, r.penalty()) # Check errors on unregistered parameters self.assertRaises(ValueError, self.m.restrain, "2*p3") - self.assertRaises(ValueError, self.m.restrain, "2*p2", ns = {"p2":p3}) + self.assertRaises(ValueError, self.m.restrain, "2*p2", ns={"p2": p3}) return def testGetConstraints(self): @@ -384,7 +394,7 @@ def __call__(self, x): A = self.A.getValue() c = self.center.getValue() w = self.width.getValue() - return A * numpy.exp(-0.5*((x-c)/w)**2) + return A * numpy.exp(-0.5 * ((x - c) / w) ** 2) # End class GCalc @@ -397,27 +407,31 @@ def __call__(self, x): self.m.g.center.setValue(3.0) - self.assertTrue(numpy.array_equal(numpy.exp(-0.5*((x-3.0)/0.1)**2), - g(x))) + self.assertTrue( + numpy.array_equal(numpy.exp(-0.5 * ((x - 3.0) / 0.1) ** 2), g(x)) + ) self.m.g.center.setValue(5.0) - self.assertTrue(numpy.array_equal(numpy.exp(-0.5*((x-5.0)/0.1)**2), - g(x))) + self.assertTrue( + numpy.array_equal(numpy.exp(-0.5 * ((x - 5.0) / 0.1) ** 2), g(x)) + ) # Use this in another equation eq = self.m.registerStringFunction("g/x - 1", "pdf") - self.assertTrue(numpy.array_equal(g(x)/x - 1, eq())) + self.assertTrue(numpy.array_equal(g(x) / x - 1, eq())) return def testRegisterFunction(self): """Test registering various functions.""" + def g1(A, c, w, x): - return A * numpy.exp(-0.5*((x-c)/w)**2) + return A * numpy.exp(-0.5 * ((x - c) / w) ** 2) + def g2(A): - return A+1 + return A + 1 eq = self.m.registerFunction(g1, "g") @@ -430,12 +444,13 @@ def g2(A): self.m.c.setValue(3.0) self.m.w.setValue(0.1) - self.assertTrue(numpy.array_equal(numpy.exp(-0.5*((x-3.0)/0.1)**2), - eq())) + self.assertTrue( + numpy.array_equal(numpy.exp(-0.5 * ((x - 3.0) / 0.1) ** 2), eq()) + ) # Use this in another equation eq2 = self.m.registerStringFunction("g/x - 1", "pdf") - self.assertTrue(numpy.array_equal(eq()/x - 1, eq2())) + self.assertTrue(numpy.array_equal(eq() / x - 1, eq2())) # Make sure we can swap out "g". self.m.registerFunction(g2, "g") @@ -443,8 +458,11 @@ def g2(A): # Try a bound method class temp(object): - def eval(self): return 1.23 - def __call__(self): return 4.56 + def eval(self): + return 1.23 + + def __call__(self): + return 4.56 t = temp() eq = self.m.registerFunction(t.eval, "eval") @@ -494,48 +512,48 @@ def testRegisterStringFunction(self): return - def test_releaseOldEquations(self): """Verify EquationFactory does not hold temporary equations.""" - self.m._newParameter('x', 12) - self.assertEqual(36, self.m.evaluateEquation('3 * x')) + self.m._newParameter("x", 12) + self.assertEqual(36, self.m.evaluateEquation("3 * x")) self.assertEqual(0, len(self.m._eqfactory.equations)) return - def test_show(self): """Verify output from the show function.""" + def capture_show(*args, **kwargs): rv = capturestdout(self.m.show, *args, **kwargs) return rv - self.assertEqual('', capture_show()) - self.m._newParameter('x', 1) - self.m._newParameter('y', 2) + + self.assertEqual("", capture_show()) + self.m._newParameter("x", 1) + self.m._newParameter("y", 2) out1 = capture_show() - lines1 = out1.strip().split('\n') + lines1 = out1.strip().split("\n") self.assertEqual(4, len(lines1)) - self.assertTrue('Parameters' in lines1) - self.assertFalse('Constraints' in lines1) - self.assertFalse('Restraints' in lines1) - self.m._newParameter('z', 7) - self.m.constrain('y', '3 * z') + self.assertTrue("Parameters" in lines1) + self.assertFalse("Constraints" in lines1) + self.assertFalse("Restraints" in lines1) + self.m._newParameter("z", 7) + self.m.constrain("y", "3 * z") out2 = capture_show() - lines2 = out2.strip().split('\n') + lines2 = out2.strip().split("\n") self.assertEqual(9, len(lines2)) - self.assertTrue('Parameters' in lines2) - self.assertTrue('Constraints' in lines2) - self.assertFalse('Restraints' in lines2) - self.m.restrain('z', lb=2, ub=3, sig=0.001) + self.assertTrue("Parameters" in lines2) + self.assertTrue("Constraints" in lines2) + self.assertFalse("Restraints" in lines2) + self.m.restrain("z", lb=2, ub=3, sig=0.001) out3 = capture_show() - lines3 = out3.strip().split('\n') + lines3 = out3.strip().split("\n") self.assertEqual(13, len(lines3)) - self.assertTrue('Parameters' in lines3) - self.assertTrue('Constraints' in lines3) - self.assertTrue('Restraints' in lines3) - out4 = capture_show(pattern='x') - lines4 = out4.strip().split('\n') + self.assertTrue("Parameters" in lines3) + self.assertTrue("Constraints" in lines3) + self.assertTrue("Restraints" in lines3) + out4 = capture_show(pattern="x") + lines4 = out4.strip().split("\n") self.assertEqual(9, len(lines4)) - out5 = capture_show(pattern='^') + out5 = capture_show(pattern="^") self.assertEqual(out3, out5) # check output with another level of hierarchy self.m._addObject(RecipeOrganizer("foo"), self.m._containers) @@ -543,11 +561,12 @@ def capture_show(*args, **kwargs): out6 = capture_show() self.assertTrue("foo.bar" in out6) # filter out foo.bar - out7 = capture_show('^(?!foo).') + out7 = capture_show("^(?!foo).") self.assertFalse("foo.bar" in out7) self.assertEqual(out3, out7) return + # ---------------------------------------------------------------------------- if __name__ == "__main__": diff --git a/src/diffpy/srfit/tests/testrestraint.py b/tests/test_restraint.py similarity index 100% rename from src/diffpy/srfit/tests/testrestraint.py rename to tests/test_restraint.py index 2968dd32..68673679 100644 --- a/src/diffpy/srfit/tests/testrestraint.py +++ b/tests/test_restraint.py @@ -12,15 +12,14 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -from diffpy.srfit.fitbase.restraint import Restraint -from diffpy.srfit.fitbase.recipeorganizer import equationFromString -from diffpy.srfit.fitbase.parameter import Parameter from diffpy.srfit.equation.builder import EquationFactory +from diffpy.srfit.fitbase.parameter import Parameter +from diffpy.srfit.fitbase.recipeorganizer import equationFromString +from diffpy.srfit.fitbase.restraint import Restraint class TestRestraint(unittest.TestCase): @@ -63,6 +62,7 @@ def testRestraint(self): # Make a really large number to check the upper bound import numpy + r.ub = numpy.inf p1.setValue(1e100) self.assertEqual(0, r.penalty()) diff --git a/src/diffpy/srfit/tests/testsas.py b/tests/test_sas.py similarity index 67% rename from src/diffpy/srfit/tests/testsas.py rename to tests/test_sas.py index 9122cf97..110c863e 100644 --- a/src/diffpy/srfit/tests/testsas.py +++ b/tests/test_sas.py @@ -12,7 +12,6 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for sas package.""" import unittest @@ -20,12 +19,13 @@ import numpy from diffpy.srfit.sas import SASGenerator, SASParser, SASProfile -from diffpy.srfit.tests.utils import datafile -from diffpy.srfit.tests.utils import has_sas, _msg_nosas from diffpy.srfit.sas.sasimport import sasimport +from .utils import _msg_nosas, datafile, has_sas + # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_sas, _msg_nosas) class TestSASParser(unittest.TestCase): @@ -36,43 +36,93 @@ def testParser(self): x, y, dx, dy = parser.getData() - testx = numpy.array([0.002618, 0.007854, 0.01309, 0.01832, 0.02356, - 0.02879, 0.03402, 0.03925, 0.04448, 0.0497]) + testx = numpy.array( + [ + 0.002618, + 0.007854, + 0.01309, + 0.01832, + 0.02356, + 0.02879, + 0.03402, + 0.03925, + 0.04448, + 0.0497, + ] + ) diff = testx - x res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testy = numpy.array([ 0.02198, 0.02201, 0.02695, 0.02645, 0.03024, - 0.3927, 7.305, 17.43, 13.43, 8.346]) + testy = numpy.array( + [ + 0.02198, + 0.02201, + 0.02695, + 0.02645, + 0.03024, + 0.3927, + 7.305, + 17.43, + 13.43, + 8.346, + ] + ) diff = testy - y res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testdy = numpy.array([ 0.002704, 0.001643, 0.002452, 0.001769, - 0.001531, 0.1697, 1.006, 0.5351, 0.3677, 0.191]) + testdy = numpy.array( + [ + 0.002704, + 0.001643, + 0.002452, + 0.001769, + 0.001531, + 0.1697, + 1.006, + 0.5351, + 0.3677, + 0.191, + ] + ) diff = testdy - dy res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) - testdx = numpy.array([0.0004091, 0.005587, 0.005598, 0.005624, - 0.005707, 0.005975, 0.006264, 0.006344, 0.006424, 0.006516]) + testdx = numpy.array( + [ + 0.0004091, + 0.005587, + 0.005598, + 0.005624, + 0.005707, + 0.005975, + 0.006264, + 0.006344, + 0.006424, + 0.006516, + ] + ) diff = testdx - dx res = numpy.dot(diff, diff) self.assertAlmostEqual(0, res) return + # End of class TestSASParser # ---------------------------------------------------------------------------- + @unittest.skipUnless(has_sas, _msg_nosas) class TestSASGenerator(unittest.TestCase): def testGenerator(self): # Test generator output - SphereModel = sasimport('sas.models.SphereModel').SphereModel + SphereModel = sasimport("sas.models.SphereModel").SphereModel model = SphereModel() gen = SASGenerator("sphere", model) @@ -88,8 +138,7 @@ def testGenerator(self): self.assertEqual(defval, par.getValue()) self.assertEqual(defval, model.getParam(pname)) - - r = numpy.arange(1, 10, 0.1, dtype = float) + r = numpy.arange(1, 10, 0.1, dtype=float) y = gen(r) refy = model.evalDistribution(r) diff = y - refy @@ -98,16 +147,15 @@ def testGenerator(self): return - def testGenerator2(self): # Test generator with a profile - EllipsoidModel = sasimport('sas.models.EllipsoidModel').EllipsoidModel + EllipsoidModel = sasimport("sas.models.EllipsoidModel").EllipsoidModel model = EllipsoidModel() gen = SASGenerator("ellipsoid", model) # Load the data using SAS tools - Loader = sasimport('sas.dataloader.loader').Loader + Loader = sasimport("sas.dataloader.loader").Loader loader = Loader() data = datafile("sas_ellipsoid_testdata.txt") datainfo = loader.load(data) @@ -125,6 +173,7 @@ def testGenerator2(self): self.assertAlmostEqual(0, res) return + # End of class TestSASGenerator if __name__ == "__main__": diff --git a/src/diffpy/srfit/tests/testsgconstraints.py b/tests/test_sgconstraints.py similarity index 84% rename from src/diffpy/srfit/tests/testsgconstraints.py rename to tests/test_sgconstraints.py index 00eafea7..f057ab5d 100644 --- a/src/diffpy/srfit/tests/testsgconstraints.py +++ b/tests/test_sgconstraints.py @@ -12,19 +12,23 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests space group constraints.""" import unittest import numpy -from diffpy.srfit.tests.utils import datafile -from diffpy.srfit.tests.utils import has_pyobjcryst, _msg_nopyobjcryst -from diffpy.srfit.tests.utils import has_structure, _msg_nostructure +from .utils import ( + _msg_nopyobjcryst, + _msg_nostructure, + datafile, + has_pyobjcryst, + has_structure, +) # ---------------------------------------------------------------------------- + class TestSGConstraints(unittest.TestCase): @unittest.skipUnless(has_pyobjcryst, _msg_nopyobjcryst) @@ -32,8 +36,8 @@ def test_ObjCryst_constrainSpaceGroup(self): """Make sure that all Parameters are constrained properly. This tests constrainSpaceGroup from - diffpy.srfit.structure.sgconstraints, which is performed automatically - when an ObjCrystCrystalParSet is created. + diffpy.srfit.structure.sgconstraints, which is performed + automatically when an ObjCrystCrystalParSet is created. """ from diffpy.srfit.structure.objcrystparset import ObjCrystCrystalParSet @@ -50,16 +54,16 @@ def test_ObjCryst_constrainSpaceGroup(self): # Check the orthorhombic lattice l = stru.getLattice() - self.assertTrue( l.alpha.const ) - self.assertTrue( l.beta.const ) - self.assertTrue( l.gamma.const ) - self.assertEqual(pi/2, l.alpha.getValue()) - self.assertEqual(pi/2, l.beta.getValue()) - self.assertEqual(pi/2, l.gamma.getValue()) - - self.assertFalse( l.a.const ) - self.assertFalse( l.b.const ) - self.assertFalse( l.c.const ) + self.assertTrue(l.alpha.const) + self.assertTrue(l.beta.const) + self.assertTrue(l.gamma.const) + self.assertEqual(pi / 2, l.alpha.getValue()) + self.assertEqual(pi / 2, l.beta.getValue()) + self.assertEqual(pi / 2, l.gamma.getValue()) + + self.assertFalse(l.a.const) + self.assertFalse(l.b.const) + self.assertFalse(l.c.const) self.assertEqual(0, len(l._constraints)) # Now make sure the scatterers are constrained properly @@ -95,12 +99,12 @@ def test_ObjCryst_constrainSpaceGroup(self): # Nor can we make them into variables from diffpy.srfit.fitbase.fitrecipe import FitRecipe + f = FitRecipe() self.assertRaises(ValueError, f.addVar, mn.x) return - @unittest.skipUnless(has_structure, _msg_nostructure) def test_DiffPy_constrainAsSpaceGroup(self): """Test the constrainAsSpaceGroup function.""" @@ -110,14 +114,17 @@ def test_DiffPy_constrainAsSpaceGroup(self): stru = makeLaMnO3_P1() parset = DiffpyStructureParSet("LaMnO3", stru) - sgpars = constrainAsSpaceGroup(parset, "P b n m", - scatterers = parset.getScatterers()[::2], - constrainadps = True) + sgpars = constrainAsSpaceGroup( + parset, + "P b n m", + scatterers=parset.getScatterers()[::2], + constrainadps=True, + ) # Make sure that the new parameters were created for par in sgpars: self.assertNotEqual(None, par) - self.assertNotEqual(None, par.getValue() ) + self.assertNotEqual(None, par.getValue()) # Test the unconstrained atoms for scatterer in parset.getScatterers()[1::2]: @@ -136,10 +143,13 @@ def test_DiffPy_constrainAsSpaceGroup(self): def _consttest(par): return par.const + def _constrainedtest(par): return par.constrained + def _proxytest(par): return par in proxied + def _alltests(par): return _consttest(par) or _constrainedtest(par) or _proxytest(par) @@ -151,25 +161,31 @@ def _alltests(par): self.assertTrue(test) test = False - for par in [scatterer.U11, scatterer.U22, scatterer.U33, - scatterer.U12, scatterer.U13, scatterer.U23]: + for par in [ + scatterer.U11, + scatterer.U22, + scatterer.U33, + scatterer.U12, + scatterer.U13, + scatterer.U23, + ]: test |= _alltests(par) self.assertTrue(test) return - @unittest.skipUnless(has_structure, _msg_nostructure) def test_ConstrainAsSpaceGroup_args(self): """Test the arguments processing of constrainAsSpaceGroup function.""" from diffpy.srfit.structure.diffpyparset import DiffpyStructureParSet from diffpy.srfit.structure.sgconstraints import constrainAsSpaceGroup from diffpy.structure.spacegroups import GetSpaceGroup + stru = makeLaMnO3_P1() parset = DiffpyStructureParSet("LaMnO3", stru) sgpars = constrainAsSpaceGroup(parset, "P b n m") - sg = GetSpaceGroup('P b n m') + sg = GetSpaceGroup("P b n m") parset2 = DiffpyStructureParSet("LMO", makeLaMnO3_P1()) sgpars2 = constrainAsSpaceGroup(parset2, sg) list(sgpars) @@ -177,20 +193,23 @@ def test_ConstrainAsSpaceGroup_args(self): self.assertEqual(sgpars.names, sgpars2.names) return + # End of class TestSGConstraints # Local helper functions ----------------------------------------------------- + def makeLaMnO3_P1(): from diffpy.structure import Structure + stru = Structure() - stru.read(datafile('LaMnO3.stru')) + stru.read(datafile("LaMnO3.stru")) return stru def makeLaMnO3(): - from pyobjcryst.crystal import Crystal from pyobjcryst.atom import Atom + from pyobjcryst.crystal import Crystal from pyobjcryst.scatteringpower import ScatteringPowerAtom pi = numpy.pi @@ -199,31 +218,32 @@ def makeLaMnO3(): crystal.SetName("LaMnO3") # La1 sp = ScatteringPowerAtom("La1", "La") - sp.SetBiso(8*pi*pi*0.003) + sp.SetBiso(8 * pi * pi * 0.003) atom = Atom(0.996096, 0.0321494, 0.25, "La1", sp) crystal.AddScatteringPower(sp) crystal.AddScatterer(atom) # Mn1 sp = ScatteringPowerAtom("Mn1", "Mn") - sp.SetBiso(8*pi*pi*0.003) + sp.SetBiso(8 * pi * pi * 0.003) atom = Atom(0, 0.5, 0, "Mn1", sp) crystal.AddScatteringPower(sp) crystal.AddScatterer(atom) # O1 sp = ScatteringPowerAtom("O1", "O") - sp.SetBiso(8*pi*pi*0.003) + sp.SetBiso(8 * pi * pi * 0.003) atom = Atom(0.0595746, 0.496164, 0.25, "O1", sp) crystal.AddScatteringPower(sp) crystal.AddScatterer(atom) # O2 sp = ScatteringPowerAtom("O2", "O") - sp.SetBiso(8*pi*pi*0.003) + sp.SetBiso(8 * pi * pi * 0.003) atom = Atom(0.720052, 0.289387, 0.0311126, "O2", sp) crystal.AddScatteringPower(sp) crystal.AddScatterer(atom) return crystal + # ---------------------------------------------------------------------------- if __name__ == "__main__": diff --git a/src/diffpy/srfit/tests/speedtest.py b/tests/test_speed.py similarity index 78% rename from src/diffpy/srfit/tests/speedtest.py rename to tests/test_speed.py index db43b77a..19af219b 100644 --- a/src/diffpy/srfit/tests/speedtest.py +++ b/tests/test_speed.py @@ -12,18 +12,18 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" from __future__ import print_function import random + import numpy -import diffpy.srfit.equation.visitors as visitors import diffpy.srfit.equation.literals as literals +import diffpy.srfit.equation.visitors as visitors -from diffpy.srfit.tests.utils import _makeArgs +from .utils import _makeArgs x = numpy.arange(0, 20, 0.05) @@ -57,7 +57,7 @@ def makeLazyEquation(): mult2.addLiteral(exp) v2.setValue(x) - v3.setValue(50*x) + v3.setValue(50 * x) v5.setValue(2.11) v6.setValue(numpy.e) @@ -75,23 +75,27 @@ def _f(a, b, c, d, e): return _f + def makeEquation1(): """Make the same equation as the lazy one.""" - y = 50*x + y = 50 * x def _f(a, b, c, d, e): - return ((a+x)*(y-b))**c * d**e + return ((a + x) * (y - b)) ** c * d**e return _f + def timeFunction(f, *args, **kw): """Time a function in ms.""" import time + t1 = time.time() f(*args, **kw) t2 = time.time() - return (t2-t1)*1000 + return (t2 - t1) * 1000 + def speedTest1(): f1 = makeLazyEquation() @@ -102,7 +106,7 @@ def speedTest1(): total1 = 0 total2 = 0 for i in range(len(args)): - args[i] = 10*random.random() + args[i] = 10 * random.random() print("Changing argument %i" % (i + 1)) t1 = timeFunction(f1, *args) t2 = timeFunction(f2, *args) @@ -114,11 +118,13 @@ def speedTest1(): print("Totals:") print("lazy", total1) print("regular", total2) - print("Ratio (lazy/regular)", total1/total2) + print("Ratio (lazy/regular)", total1 / total2) + -def speedTest2(mutate = 2): +def speedTest2(mutate=2): from diffpy.srfit.equation.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 20, 0.05) @@ -144,17 +150,20 @@ def speedTest2(mutate = 2): eq.b7.setValue(2.0) eq.b8.setValue(2.0) - from numpy import exp - from numpy import polyval + from numpy import exp, polyval + def f(A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8): - return A0*exp(-(x*qsig)**2)*(exp(-((x-1.0)/sigma1)**2)+exp(-((x-2.0)/sigma2)**2)) + polyval([b8, b7, b6, b5,b4,b3,b2,b1],x) + return A0 * exp(-((x * qsig) ** 2)) * ( + exp(-(((x - 1.0) / sigma1) ** 2)) + + exp(-(((x - 2.0) / sigma2) ** 2)) + ) + polyval([b8, b7, b6, b5, b4, b3, b2, b1], x) tnpy = 0 teq = 0 # Randomly change variables numargs = len(eq.args) choices = range(numargs) - args = [0.0]*(len(eq.args)) + args = [0.0] * (len(eq.args)) # The call-loop random.seed() @@ -174,15 +183,17 @@ def f(A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8): tnpy += timeFunction(f, *args) teq += timeFunction(eq, *args) - print("Average call time (%i calls, %i mutations/call):" % - (numcalls, mutate)) - print("numpy: ", tnpy/numcalls) - print("equation: ", teq/numcalls) - print("ratio: ", teq/tnpy) + print( + "Average call time (%i calls, %i mutations/call):" % (numcalls, mutate) + ) + print("numpy: ", tnpy / numcalls) + print("equation: ", teq / numcalls) + print("ratio: ", teq / tnpy) return -def speedTest3(mutate = 2): + +def speedTest3(mutate=2): """Test wrt sympy. Results - sympy is 10 to 24 times faster without using arrays (ouch!). @@ -191,6 +202,7 @@ def speedTest3(mutate = 2): """ from diffpy.srfit.equation.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 20, 0.05) @@ -216,17 +228,30 @@ def speedTest3(mutate = 2): eq.b7.setValue(2.0) eq.b8.setValue(2.0) - from sympy import var, exp, lambdify from numpy import polyval - A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var("A0 qsig sigma1 sigma2 b1 b2 b3 b4 b5 b6 b7 b8 xx") - f = lambdify(vars, A0*exp(-(xx*qsig)**2)*(exp(-((xx-1.0)/sigma1)**2)+exp(-((xx-2.0)/sigma2)**2)) + polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), "numpy") + from sympy import exp, lambdify, var + + A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var( + "A0 qsig sigma1 sigma2 b1 b2 b3 b4 b5 b6 b7 b8 xx" + ) + f = lambdify( + vars, + A0 + * exp(-((xx * qsig) ** 2)) + * ( + exp(-(((xx - 1.0) / sigma1) ** 2)) + + exp(-(((xx - 2.0) / sigma2) ** 2)) + ) + + polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), + "numpy", + ) tnpy = 0 teq = 0 # Randomly change variables numargs = len(eq.args) choices = range(numargs) - args = [1.0]*(len(eq.args)) + args = [1.0] * (len(eq.args)) args.append(x) # The call-loop @@ -247,15 +272,17 @@ def speedTest3(mutate = 2): teq += timeFunction(eq, *(args[:-1])) tnpy += timeFunction(f, *args) - print("Average call time (%i calls, %i mutations/call):" % - (numcalls, mutate)) - print("sympy: ", tnpy/numcalls) - print("equation: ", teq/numcalls) - print("ratio: ", teq/tnpy) + print( + "Average call time (%i calls, %i mutations/call):" % (numcalls, mutate) + ) + print("sympy: ", tnpy / numcalls) + print("equation: ", teq / numcalls) + print("ratio: ", teq / tnpy) return -def speedTest4(mutate = 2): + +def speedTest4(mutate=2): """Test wrt sympy. Results - sympy is 10 to 24 times faster without using arrays (ouch!). @@ -264,6 +291,7 @@ def speedTest4(mutate = 2): """ from diffpy.srfit.equation.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 20, 0.05) @@ -274,9 +302,12 @@ def speedTest4(mutate = 2): factory.registerConstant("x", x) eq = factory.makeEquation(eqstr) - from sympy import var, lambdify from numpy import polyval - b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var("b1 b2 b3 b4 b5 b6 b7 b8 xx") + from sympy import lambdify, var + + b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var( + "b1 b2 b3 b4 b5 b6 b7 b8 xx" + ) f = lambdify(vars, polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), "numpy") tnpy = 0 @@ -284,7 +315,7 @@ def speedTest4(mutate = 2): # Randomly change variables numargs = len(eq.args) choices = range(numargs) - args = [1.0]*(len(eq.args)) + args = [1.0] * (len(eq.args)) args.append(x) # The call-loop @@ -305,18 +336,21 @@ def speedTest4(mutate = 2): teq += timeFunction(eq, *(args[:-1])) tnpy += timeFunction(f, *args) - print("Average call time (%i calls, %i mutations/call):" % - (numcalls, mutate)) - print("sympy: ", tnpy/numcalls) - print("equation: ", teq/numcalls) - print("ratio: ", teq/tnpy) + print( + "Average call time (%i calls, %i mutations/call):" % (numcalls, mutate) + ) + print("sympy: ", tnpy / numcalls) + print("equation: ", teq / numcalls) + print("ratio: ", teq / tnpy) return -def weightedTest(mutate = 2): + +def weightedTest(mutate=2): """Show the benefits of a properly balanced equation tree.""" from diffpy.srfit.equation.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 10, 0.01) @@ -336,20 +370,21 @@ def weightedTest(mutate = 2): eq.b7.setValue(2.0) eq.b8.setValue(2.0) - #scale = visitors.NodeWeigher() - #eq.root.identify(scale) - #print(scale.output) + # scale = visitors.NodeWeigher() + # eq.root.identify(scale) + # print(scale.output) from numpy import polyval + def f(b1, b2, b3, b4, b5, b6, b7, b8): - return polyval([b8, b7, b6, b5,b4,b3,b2,b1],x) + return polyval([b8, b7, b6, b5, b4, b3, b2, b1], x) tnpy = 0 teq = 0 # Randomly change variables numargs = len(eq.args) choices = range(numargs) - args = [0.1]*numargs + args = [0.1] * numargs # The call-loop random.seed() @@ -365,23 +400,26 @@ def f(b1, b2, b3, b4, b5, b6, b7, b8): c.remove(idx) args[idx] = random.random() - #print(args) + # print(args) # Time the different functions with these arguments teq += timeFunction(eq, *args) tnpy += timeFunction(f, *args) - print("Average call time (%i calls, %i mutations/call):" % - (numcalls, mutate)) - print("numpy: ", tnpy/numcalls) - print("equation: ", teq/numcalls) - print("ratio: ", teq/tnpy) + print( + "Average call time (%i calls, %i mutations/call):" % (numcalls, mutate) + ) + print("numpy: ", tnpy / numcalls) + print("equation: ", teq / numcalls) + print("ratio: ", teq / tnpy) return + def profileTest(): from diffpy.srfit.builder import EquationFactory + factory = EquationFactory() x = numpy.arange(0, 10, 0.001) @@ -404,7 +442,7 @@ def profileTest(): mutate = 8 numargs = len(eq.args) choices = range(numargs) - args = [0.1]*numargs + args = [0.1] * numargs # The call-loop random.seed() @@ -432,15 +470,7 @@ def profileTest(): for i in range(1, 9): weightedTest(i) """ - """ - from diffpy.srfit.equation.builder import EquationFactory - import random - import cProfile - cProfile.run('profileTest()', 'prof') - import pstats - p = pstats.Stats('prof') - p.strip_dirs() - p.sort_stats('time') - p.print_stats(10) - profileTest() - """ + """From diffpy.srfit.equation.builder import EquationFactory import random + import cProfile cProfile.run('profileTest()', 'prof') import pstats p = + pstats.Stats('prof') p.strip_dirs() p.sort_stats('time') p.print_stats(10) + profileTest()""" diff --git a/src/diffpy/srfit/tests/testtagmanager.py b/tests/test_tagmanager.py similarity index 94% rename from src/diffpy/srfit/tests/testtagmanager.py rename to tests/test_tagmanager.py index d4d6c5f8..e340e2a5 100644 --- a/src/diffpy/srfit/tests/testtagmanager.py +++ b/tests/test_tagmanager.py @@ -12,13 +12,13 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Unit tests for tagmanager.py.""" import unittest from diffpy.srfit.util.tagmanager import TagManager + ############################################################################## class TestTagManager(unittest.TestCase): @@ -71,7 +71,7 @@ def test_union_and_intersection(self): m = self.m m.tag(3, "3", "number") m.tag(4, "4", "number") - objs = set([3,4]) + objs = set([3, 4]) self.assertEqual(m.union(), set()) self.assertEqual(m.union("number"), objs) self.assertEqual(m.union("3"), set([3])) @@ -95,17 +95,18 @@ def test_hasTags(self): m = self.m m.tag(3, "3", "number") m.tag(4, "4", "number") - self.assertTrue( m.hasTags(3, "3") ) - self.assertTrue( m.hasTags(3, "3", "number") ) - self.assertFalse( m.hasTags(3, "3", "4") ) + self.assertTrue(m.hasTags(3, "3")) + self.assertTrue(m.hasTags(3, "3", "number")) + self.assertFalse(m.hasTags(3, "3", "4")) self.assertRaises(KeyError, m.hasTags, 3, "fail") m.silent = True self.assertFalse(m.hasTags(3, "fail")) return + # End of class TestTagManager -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() # End of file diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 00000000..ee1efa9b --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,10 @@ +"""Unit tests for __version__.py.""" + +import diffpy.srfit # noqa + + +def test_package_version(): + """Ensure the package version is defined and not set to the initial + placeholder.""" + assert hasattr(diffpy.srfit, "__version__") + assert diffpy.srfit.__version__ != "0.0.0" diff --git a/src/diffpy/srfit/tests/testvisitors.py b/tests/test_visitors.py similarity index 98% rename from src/diffpy/srfit/tests/testvisitors.py rename to tests/test_visitors.py index 0250f064..58a7ffcd 100644 --- a/src/diffpy/srfit/tests/testvisitors.py +++ b/tests/test_visitors.py @@ -12,14 +12,15 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Tests for refinableobj module.""" import unittest -import diffpy.srfit.equation.visitors as visitors import diffpy.srfit.equation.literals as literals -from diffpy.srfit.tests.utils import _makeArgs +import diffpy.srfit.equation.visitors as visitors + +from .utils import _makeArgs + class TestValidator(unittest.TestCase): @@ -72,6 +73,7 @@ def testSimpleFunction(self): # Fix the operation of plus import numpy + plus.operation = numpy.add validator.reset() mult.identify(validator) @@ -85,6 +87,7 @@ def testSimpleFunction(self): return + class TestArgFinder(unittest.TestCase): def testSimpleFunction(self): @@ -134,6 +137,7 @@ def testArg(self): self.assertTrue(args[0] is v1) return + class TestSwapper(unittest.TestCase): def testSimpleFunction(self): @@ -194,7 +198,7 @@ def testSimpleFunction(self): self.assertTrue(plus2.hasObserver(mult._flush)) # plus2 has no arguments yet. Verify this. - self.assertRaises(ValueError, mult.getValue) + self.assertRaises(TypeError, mult.getValue) # Add the arguments to plus2. plus2.addLiteral(v4) plus2.addLiteral(v5) diff --git a/src/diffpy/srfit/tests/testweakrefcallable.py b/tests/test_weakrefcallable.py similarity index 89% rename from src/diffpy/srfit/tests/testweakrefcallable.py rename to tests/test_weakrefcallable.py index 595af03a..5d44d18b 100644 --- a/src/diffpy/srfit/tests/testweakrefcallable.py +++ b/tests/test_weakrefcallable.py @@ -12,38 +12,36 @@ # See LICENSE.txt for license information. # ############################################################################## - """Unit tests for the weakrefcallable module.""" -import unittest import pickle +import unittest from diffpy.srfit.fitbase import FitContribution from diffpy.srfit.fitbase.parameter import Parameter -from diffpy.srfit.util.weakrefcallable import weak_ref, WeakBoundMethod +from diffpy.srfit.util.weakrefcallable import WeakBoundMethod, weak_ref # ---------------------------------------------------------------------------- + class TestWeakBoundMethod(unittest.TestCase): def setUp(self): - self.f = FitContribution('f') - self.f.setEquation('7') + self.f = FitContribution("f") + self.f.setEquation("7") self.w = weak_ref(self.f._eq._flush, fallback=_fallback_example) return - def tearDown(self): self.f = None self.assertTrue(None is self.w._wref()) - obj, args, kw = self.w('any', 'argument', foo=37) + obj, args, kw = self.w("any", "argument", foo=37) self.assertTrue(obj is self.w) - self.assertEqual(('any', 'argument'), args) - self.assertEqual({'foo' : 37}, kw) + self.assertEqual(("any", "argument"), args) + self.assertEqual({"foo": 37}, kw) return - def test___init__(self): """Check WeakBoundMethod.__init__()""" self.assertTrue(self.w.fallback is _fallback_example) @@ -51,7 +49,6 @@ def test___init__(self): self.assertTrue(None is wf.fallback) return - def test___call__(self): """Check WeakBoundMethod.__call__()""" f = self.f @@ -61,17 +58,16 @@ def test___call__(self): self.w(()) self.assertTrue(None is f._eq._value) # check WeakBoundMethod behavior with no fallback - x = Parameter('x', value=3) + x = Parameter("x", value=3) wgetx = weak_ref(x.getValue) self.assertEqual(3, wgetx()) del x self.assertRaises(ReferenceError, wgetx) return - def test___hash__(self): """Check WeakBoundMethod.__hash__()""" - f1 = FitContribution('f1') + f1 = FitContribution("f1") w1 = weak_ref(f1._flush) h0 = hash(w1) del f1 @@ -82,10 +78,9 @@ def test___hash__(self): self.assertEqual(hash(w1c1), hash(w1c2)) return - def test___eq__(self): """Check WeakBoundMethod.__eq__()""" - f1 = FitContribution('f1') + f1 = FitContribution("f1") w1 = weak_ref(f1._flush) w2 = weak_ref(f1._flush) self.assertEqual(w1, w2) @@ -102,7 +97,6 @@ def test___eq__(self): self.assertEqual(w1, w1cc) return - def test_pickling(self): """Verify unpickling works when it involves __hash__ call.""" holder = set([self.w]) @@ -114,12 +108,11 @@ def test_pickling(self): self.assertTrue(feq2 is w2._wref()) return - def test_observable_deregistration(self): """Check if Observable drops dead Observer.""" f = self.f - x = f.newParameter('x', 5) - f.setEquation('3 * x') + x = f.newParameter("x", 5) + f.setEquation("3 * x") self.assertEqual(15, f.evaluate()) self.assertEqual(15, f._eq._value) # get one of the observer callables that are associated with f @@ -138,15 +131,17 @@ def test_observable_deregistration(self): self.assertEqual(0, len(x._observers)) return + # End of class TestWeakBoundMethod # Local Routines ------------------------------------------------------------- + def _fallback_example(wbm, *args, **kwargs): return (wbm, args, kwargs) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() # End of file diff --git a/src/diffpy/srfit/tests/testdata/LaMnO3.stru b/tests/testdata/LaMnO3.stru similarity index 100% rename from src/diffpy/srfit/tests/testdata/LaMnO3.stru rename to tests/testdata/LaMnO3.stru diff --git a/src/diffpy/srfit/tests/testdata/ni-q27r100-neutron.gr b/tests/testdata/ni-q27r100-neutron.gr similarity index 100% rename from src/diffpy/srfit/tests/testdata/ni-q27r100-neutron.gr rename to tests/testdata/ni-q27r100-neutron.gr diff --git a/src/diffpy/srfit/tests/testdata/ni.cif b/tests/testdata/ni.cif similarity index 100% rename from src/diffpy/srfit/tests/testdata/ni.cif rename to tests/testdata/ni.cif diff --git a/src/diffpy/srfit/tests/testdata/results.res b/tests/testdata/results.res similarity index 100% rename from src/diffpy/srfit/tests/testdata/results.res rename to tests/testdata/results.res diff --git a/src/diffpy/srfit/tests/testdata/sas_ascii_test_1.txt b/tests/testdata/sas_ascii_test_1.txt similarity index 100% rename from src/diffpy/srfit/tests/testdata/sas_ascii_test_1.txt rename to tests/testdata/sas_ascii_test_1.txt diff --git a/src/diffpy/srfit/tests/testdata/sas_ellipsoid_testdata.txt b/tests/testdata/sas_ellipsoid_testdata.txt similarity index 100% rename from src/diffpy/srfit/tests/testdata/sas_ellipsoid_testdata.txt rename to tests/testdata/sas_ellipsoid_testdata.txt diff --git a/src/diffpy/srfit/tests/testdata/si-q27r60-xray.gr b/tests/testdata/si-q27r60-xray.gr similarity index 100% rename from src/diffpy/srfit/tests/testdata/si-q27r60-xray.gr rename to tests/testdata/si-q27r60-xray.gr diff --git a/src/diffpy/srfit/tests/testdata/testdata.txt b/tests/testdata/testdata.txt similarity index 100% rename from src/diffpy/srfit/tests/testdata/testdata.txt rename to tests/testdata/testdata.txt diff --git a/src/diffpy/srfit/tests/utils.py b/tests/utils.py similarity index 80% rename from src/diffpy/srfit/tests/utils.py rename to tests/utils.py index 6ac35c1c..79a77353 100644 --- a/src/diffpy/srfit/tests/utils.py +++ b/tests/utils.py @@ -12,16 +12,16 @@ # See LICENSE_DANSE.txt for license information. # ############################################################################## - """Helper routines for testing.""" import sys + import six import diffpy.srfit.equation.literals as literals from diffpy.srfit.sas.sasimport import sasimport -from diffpy.srfit.tests import logger +from . import logger # Resolve availability of optional third-party packages. @@ -29,60 +29,69 @@ try: _msg_nosas = "No module named 'sas.pr.invertor'" - sasimport('sas.pr.invertor') + sasimport("sas.pr.invertor") _msg_nosas = "No module named 'sas.models'" - sasimport('sas.models') + sasimport("sas.models") has_sas = True except ImportError as e: has_sas = False - logger.warning('%s, SaS tests skipped.', e) + logger.warning("%s, SaS tests skipped.", e) # diffpy.structure _msg_nostructure = "No module named 'diffpy.structure'" try: - import diffpy.structure as m; del m + import diffpy.structure as m + + del m has_structure = True except ImportError: has_structure = False - logger.warning('Cannot import diffpy.structure, Structure tests skipped.') + logger.warning("Cannot import diffpy.structure, Structure tests skipped.") # pyobjcryst _msg_nopyobjcryst = "No module named 'pyobjcryst'" try: - import pyobjcryst as m; del m + import pyobjcryst as m + + del m has_pyobjcryst = True except ImportError: has_pyobjcryst = False - logger.warning('Cannot import pyobjcryst, pyobjcryst tests skipped.') + logger.warning("Cannot import pyobjcryst, pyobjcryst tests skipped.") # diffpy.srreal _msg_nosrreal = "No module named 'diffpy.srreal'" try: - import diffpy.srreal.pdfcalculator as m; del m + import diffpy.srreal.pdfcalculator as m + + del m has_srreal = True except ImportError: has_srreal = False - logger.warning('Cannot import diffpy.srreal, PDF tests skipped.') + logger.warning("Cannot import diffpy.srreal, PDF tests skipped.") # Helper functions for testing ----------------------------------------------- + def _makeArgs(num): args = [] for i in range(num): - j=i+1 - args.append(literals.Argument(name="v%i"%j, value=j)) + j = i + 1 + args.append(literals.Argument(name="v%i" % j, value=j)) return args def noObserversInGlobalBuilders(): """True if no observer function leaks to global builder objects. - Ensure objects are not immortal due to a reference from static value. + Ensure objects are not immortal due to a reference from static + value. """ from diffpy.srfit.equation.builder import _builders + rv = True for n, b in _builders.items(): if b.literal and b.literal._observers: @@ -93,6 +102,7 @@ def noObserversInGlobalBuilders(): def datafile(filename): from pkg_resources import resource_filename + rv = resource_filename(__name__, "testdata/" + filename) return rv @@ -108,4 +118,5 @@ def capturestdout(f, *args, **kwargs): sys.stdout = savestdout return fp.getvalue() + # End of file