diff --git a/.cardboardlint.yml b/.cardboardlint.yml deleted file mode 100644 index 1dd42a944..000000000 --- a/.cardboardlint.yml +++ /dev/null @@ -1,22 +0,0 @@ -linters: - - import: - packages: ['iodata'] - - namespace: - filefilter: ['- */__init__.py', '- */test_*.py', '- *setup.py', '- tools/*', - '- doc/*.py', '+ *.py', '+ *.pyx'] - - pylint: - - pycodestyle: - config: .pycodestylerc - - autopep8: - config: .pycodestylerc - line-range: [79, 100] - - pydocstyle: - - whitespace: - filefilter: ['- iodata/test/data/*', '- *Makefile', '+ *'] - - header: - extra: [] - shebang: '#!/usr/bin/env python3' - - yamllint: - filefilter: ['- *conda.recipe/meta.yaml', '+ *.yml', '+ *.yaml'] - - rst-lint: - filefilter: ['+ README.rst'] diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 000000000..fba5f6003 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,11 @@ +version = 1 + +[[analyzers]] +name = "shell" + +[[analyzers]] +name = "python" + + [analyzers.meta] + runtime_version = "3.x.x" + max_line_length = 100 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..df64623b1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 +max_line_length = 100 + +[Makefile] +indent_style = tab + +[{*.json,*.yml,*.yaml}] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 4f5d9e79e..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: CI -on: - push: - tags: - - '[1-9]+.[0-9]+.[0-9]+*' - branches: - - master - pull_request: - branches: - - master - -jobs: - # These are quick tests using Python's venv on different Python versions. - test-venv: - timeout-minutes: 30 - if: "! startsWith(github.ref, 'refs/tags')" - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - python-version: 3.7 - - os: ubuntu-latest - python-version: 3.8 - - os: ubuntu-latest - python-version: 3.9 - - os: macos-latest - python-version: 3.7 - - runs-on: ${{ matrix.os }} - env: - # Tell Roberto to upload coverage results - ROBERTO_UPLOAD_COVERAGE: 1 - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 2 - - name: Fetch base branch (usually master) - run: | - if [[ -n "${GITHUB_HEAD_REF}" ]]; then - git fetch origin ${GITHUB_BASE_REF} --depth=2 - fi - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - uses: actions/cache@v2 - with: - path: ~/.local/venvs - key: ${{ runner.os }}-Python-${{ matrix.python-version }}-venv - - name: Install Pip and Roberto - run: | - python -m pip install --upgrade pip - python -m pip install roberto>=2.0.0 - - name: Test with Roberto - run: | - if [[ -n "${GITHUB_HEAD_REF}" ]]; then - ROBERTO_GIT_MERGE_BRANCH=${GITHUB_SHA} \ - ROBERTO_GIT_BRANCH=${GITHUB_BASE_REF} \ - python -m roberto - else - ROBERTO_TESTENV_USE=venv \ - python -m roberto robot - fi - - - test-conda: - # This is a slow test in a Conda environment, including deployment of - # tagged releases. - timeout-minutes: 30 - if: (github.ref == 'refs/heads/master') || startsWith(github.ref, 'refs/tags') - strategy: - fail-fast: false - - runs-on: ubuntu-latest - env: - ROBERTO_UPLOAD_COVERAGE: 1 - ROBERTO_PACKAGE_MANAGER: conda - ROBERTO_TESTENV_USE: conda - ROBERTO_DEPLOY_NOARCH: 1 - TWINE_USERNAME: theochem - TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }} - steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 - with: - path: | - ~/miniconda3 - !~/miniconda3/conda-bld - !~/miniconda3/locks - !~/miniconda3/pkgs - !~/miniconda3/var - !~/miniconda3/envs/*/conda-bld - !~/miniconda3/envs/*/locks - !~/miniconda3/envs/*/pkgs - !~/miniconda3/envs/*/var - key: ${{ runner.os }}-conda-3 - - name: Install Roberto - run: | - python -m pip install roberto>=2.0.0 - - name: Test and deploy with Roberto - run: | - python -m roberto robot diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml new file mode 100644 index 000000000..c33ba83b8 --- /dev/null +++ b/.github/workflows/pytest.yaml @@ -0,0 +1,28 @@ +name: pytest +on: + push: + branches: + # Run tests for change on the main branch ... + - main + tags-ignore: + # ... but not for tags (avoids duplicate work). + - '**' + pull_request: + # Run tests on pull requests +jobs: + tests: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.9", "3.12"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install development version + run: pip install -e .[dev] + - name: Run Pytest + run: pytest -vv diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000..875e1684d --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,128 @@ +# This workflow is adapted from: +# https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ +# Changed made: +# - Remove emoticons +# - Update some actions to more recent versions + +name: release + +on: push + +env: + # This is not critical + # It is used only for showing URLs in GitHub Actions web interface. + PYPI_NAME: qc-iodata + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.9" + - name: Install pypa/build + run: >- + python -m pip install build + - name: Build package + run: >- + python -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: Publish Python distribution to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/${{ env.PYPI_NAME }} + permissions: + id-token: write + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: >- + Sign the Python distribution with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write + id-token: write + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' + + publish-to-testpypi: + name: Publish Python distribution to TestPyPI + if: ${{ github.ref == 'refs/heads/main' && github.repository_owner == 'theochem'}} + needs: + - build + runs-on: ubuntu-latest + + environment: + name: testpypi + url: https://test.pypi.org/p/${{ env.PYPI_NAME }} + + permissions: + id-token: write + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/.gitignore b/.gitignore index 3da101502..fb57579a7 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,9 @@ celerybeat-schedule # dotenv .env +# direnv +.envrc + # virtualenv .venv venv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..3555f8152 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +exclude: 'iodata/test/data/.*' +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-added-large-files + - id: check-ast + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-json + - id: check-yaml + - id: check-merge-conflict + - id: check-symlinks + - id: check-toml + - id: check-vcs-permalinks + - id: debug-statements + - id: detect-private-key + - id: destroyed-symlinks + - id: end-of-file-fixer + - id: fix-byte-order-marker + - id: mixed-line-ending + - id: pretty-format-json + args: ["--autofix", "--no-sort-keys"] + - id: trailing-whitespace +- repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: remove-crlf +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.4 + hooks: + - id: check-github-workflows +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.5 + hooks: + - id: ruff-format + - id: ruff + args: ["--fix", "--show-fixes"] diff --git a/.pycodestylerc b/.pycodestylerc deleted file mode 100644 index b4686f6ff..000000000 --- a/.pycodestylerc +++ /dev/null @@ -1,3 +0,0 @@ -[pycodestyle] -max-line-length=100 -ignore=E741,W503 diff --git a/.pydocstylerc b/.pydocstylerc deleted file mode 100644 index 6133249c5..000000000 --- a/.pydocstylerc +++ /dev/null @@ -1,2 +0,0 @@ -[pydocstyle] -add_ignore=D100,D101,D102,D103,D104,D105 diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index a37e77164..000000000 --- a/.pylintrc +++ /dev/null @@ -1,425 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist=numpy, iodata.overlap_accel - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,too-many-arguments,too-many-locals,too-few-public-methods,fixme,invalid-name,duplicate-code,unsubscriptable-object,no-member - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local,numpy - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules=numpy - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[BASIC] - -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,_,f - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=((^_)|(^test_)) - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..24c6d5af6 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,28 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: doc/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: doc/requirements.txt diff --git a/.roberto.yaml b/.roberto.yaml deleted file mode 100644 index 68253a049..000000000 --- a/.roberto.yaml +++ /dev/null @@ -1,20 +0,0 @@ -absolute: true # Force absolute comparison for cardboardlint -project: - name: iodata - requirements: [[sympy, sympy]] - packages: - - dist_name: qc-iodata - tools: - - write-py-version - - cardboardlint-static - - build-py-inplace - - cardboardlint-dynamic - - pytest - - upload-codecov - - build-sphinx-doc - - upload-docs-gh - - build-py-source - - build-conda - - deploy-pypi - - deploy-conda - - deploy-github diff --git a/.yamllint b/.yamllint deleted file mode 100644 index fe8f4d16e..000000000 --- a/.yamllint +++ /dev/null @@ -1,7 +0,0 @@ -extends: default - -rules: - document-start: disable - line-length: - max: 100 - truthy: disable diff --git a/README.rst b/README.rst index e4d6fa49e..82b393c9f 100644 --- a/README.rst +++ b/README.rst @@ -22,12 +22,11 @@ IOData ====== -|GithubActions| -|Conda| -|Pypi| -|Codecov| +|pytest| +|release| +|CodeFactor| +|PyPI| |Version| -|CondaVersion| |License| @@ -51,39 +50,48 @@ Please use the following citation in any publication using IOData library: Installation ------------ -To install IOData using the conda package management system, install -`miniconda `__ or -`anaconda `__ first, and then: - -.. code-block:: bash - - # Create a horton3 conda environment. (optional, recommended) - conda create -n horton3 - source activate horton3 - - # Install the stable release. - conda install -c theochem iodata +.. + : To install IOData using the conda package management system, install + : `miniconda `__ or + : `anaconda `__ first, and then: + : + : .. code-block:: bash + : + : # Create a horton3 conda environment. (optional, recommended) + : conda create -n horton3 + : source activate horton3 + : + : # Install the stable release. + : conda install -c theochem iodata + : + : To install IOData with pip, you may want to create a `virtual environment`_, + : and then: + : + : .. code-block:: bash + : + : # Install the stable release. + : pip install qc-iodata -To install IOData with pip, you may want to create a `virtual environment`_, -and then: +In anticipation of the 1.0 release of IOData, install the latest git revision +as follows: .. code-block:: bash - # Install the stable release. - pip install qc-iodata + python -m pip install git+https://github.com/theochem/iodata.git + +Add the ``--user`` argument if you are not working in a virtual or conda +environment. Note that there may be API changes between subsequent revisions. See https://iodata.readthedocs.io/en/latest/install.html for full details. -.. |GithubActions| image:: https://github.com/theochem/iodata/actions/workflows/ci.yml/badge.svg?branch=master - :target: https://github.com/theochem/iodata/actions/workflows/ci.yml -.. |Version| image:: https://img.shields.io/pypi/pyversions/iodata.svg +.. |pytest| image:: https://github.com/theochem/iodata/actions/workflows/pytest.yaml/badge.svg + :target: https://github.com/theochem/iodata/actions/workflows/pytest.yaml +.. |release| image:: https://github.com/theochem/iodata/actions/workflows/release.yaml/badge.svg + :target: https://github.com/theochem/iodata/actions/workflows/release.yaml +.. |CodeFactor| image:: https://www.codefactor.io/repository/github/tovrstra/stepup-core/badge + :target: https://www.codefactor.io/repository/github/tovrstra/stepup-core +.. |Version| image:: https://img.shields.io/pypi/pyversions/qc-iodata.svg .. |License| image:: https://img.shields.io/github/license/theochem/iodata -.. |Pypi| image:: https://img.shields.io/pypi/v/iodata.svg - :target: https://pypi.python.org/pypi/iodata/0.1.3 -.. |Codecov| image:: https://img.shields.io/codecov/c/github/theochem/iodata/master.svg - :target: https://codecov.io/gh/theochem/iodata -.. |Conda| image:: https://img.shields.io/conda/v/theochem/iodata.svg - :target: https://anaconda.org/theochem/iodata -.. |CondaVersion| image:: https://img.shields.io/conda/pn/theochem/iodata.svg - :target: https://anaconda.org/theochem/iodata +.. |PyPI| image:: https://img.shields.io/pypi/v/qc-iodata.svg + :target: https://pypi.python.org/pypi/qc-iodata/ .. _virtual environment: https://docs.python.org/3/tutorial/venv.html diff --git a/doc/conf.py b/doc/conf.py index 1a84c3c74..9931740e1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -19,21 +19,19 @@ # pylint: disable=unused-argument,redefined-builtin """Sphinxdoc configuration file.""" - import os import subprocess - # -- Fragile tricks for RTD ----------------------------------------------- # Normally sphinx-build should be called after iodata is installed somehow. # An in-place build can also work, provided the necessary environment variables # are set accordingly. -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if on_rtd: - subprocess.run(['python', '-m', 'pip', 'install', '..'], check=True) - subprocess.run(['./gen_docs.sh'], shell=True, check=True) + subprocess.run(["python", "-m", "pip", "install", ".."], check=True) + subprocess.run(["./gen_docs.sh"], shell=True, check=True) # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -41,12 +39,12 @@ # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "relations.html", # needs 'show_related': True theme option to display + "searchbox.html", + "donate.html", ] } @@ -62,57 +60,57 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'sphinx_autodoc_typehints', + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx_autodoc_typehints", ] # 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' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'IOData' -copyright = u'2019, The IODATA Development Team' -author = u'The IODATA Development Team' +project = "IOData" +copyright = "2019, The IODATA Development Team" # noqa: A001 +author = "The IODATA Development Team" -# The version info for the project you're documenting, acts as replacement for +# The version info for the project yo're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = u'' +version = "" # The full version, including alpha/beta/rc tags. -release = u'' +release = "" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -122,7 +120,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # 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 @@ -133,14 +131,14 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] -html_style = 'css/override.css' +html_static_path = ["_static"] +html_style = "css/override.css" # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'IODatadoc' +htmlhelp_basename = "IODatadoc" # -- Options for LaTeX output --------------------------------------------- @@ -148,15 +146,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -166,18 +161,14 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'IOData.tex', u'IOData Documentation', - u'The HORTON Developers', 'manual'), + (master_doc, "IOData.tex", "IOData Documentation", "The HORTON Developers", "manual"), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'iodata', u'IOData Documentation', - [author], 1) -] +man_pages = [(master_doc, "iodata", "IOData Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -185,9 +176,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'IOData', u'IOData Documentation', - author, 'IOData', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "IOData", + "IOData Documentation", + author, + "IOData", + "One line description of project.", + "Miscellaneous", + ), ] # -- Options for Epub output ---------------------------------------------- @@ -208,20 +205,16 @@ # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} - +epub_exclude_files = ["search.html"] # -- Configuration for autodoc extensions --------------------------------- autodoc_default_options = { - 'undoc-members': True, - 'show-inheritance': True, - 'members': None, - 'inherited-members': True, - 'ignore-module-all': True, + "undoc-members": True, + "show-inheritance": True, + "members": None, + "inherited-members": True, + "ignore-module-all": True, } @@ -243,22 +236,24 @@ def setup(app): # -- Configuration of mathjax extension ----------------------------------- mathjax_config = { - 'extensions': ['fast-preview.js'], + "extensions": ["fast-preview.js"], # TeX: { # Macros: { # RR: '{\\bf R}', # bold: ['{\\bf #1}', 1] # } # } - 'TeX': { - 'Macros': { - 'ket': ["{\\left\\vert { #1 } \\right\\rangle}", 1], - 'bra': ["{\\left\\langle { #1} \\right\\vert}", 1], - 'braket': ["{\\left\\langle {#1} \\mid { #2} \\right\\rangle}", 2], - 'ketbra': ["{\\left\\vert { #1 } \\right\\rangle\\left\\langle { #2} \\right\\vert}", - 2], - 'ev': ["{\\left\\langle {#2} \\vert {#1} \\vert {#2} \\right\\rangle}", 2], - 'mel': ["{\\left\\langle{ #1 }\\right\\vert{ #2 }\\left\\vert{#3}\\right\\rangle}", 3] + "TeX": { + "Macros": { + "ket": ["{\\left\\vert { #1 } \\right\\rangle}", 1], + "bra": ["{\\left\\langle { #1} \\right\\vert}", 1], + "braket": ["{\\left\\langle {#1} \\mid { #2} \\right\\rangle}", 2], + "ketbra": [ + "{\\left\\vert { #1 } \\right\\rangle\\left\\langle { #2} \\right\\vert}", + 2, + ], + "ev": ["{\\left\\langle {#2} \\vert {#1} \\vert {#2} \\right\\rangle}", 2], + "mel": ["{\\left\\langle{ #1 }\\right\\vert{ #2 }\\left\\vert{#3}\\right\\rangle}", 3], } }, } diff --git a/doc/gen_formats.py b/doc/gen_formats.py index 530e7362e..ce7d0bdcd 100755 --- a/doc/gen_formats.py +++ b/doc/gen_formats.py @@ -22,7 +22,6 @@ from iodata.api import FORMAT_MODULES - __all__ = [] @@ -40,7 +39,7 @@ def _format_words(words): - return ', '.join('``{}``'.format(word) for word in words) + return ", ".join(f"``{word}``" for word in words) def _print_section(title, linechar): @@ -61,11 +60,11 @@ def main(): break if skip: continue - lines = module.__doc__.split('\n') + lines = module.__doc__.split("\n") # add labels for cross-referencing format (e.g. in formats table) print(f".. _format_{modname}:") print() - _print_section('{} (``{}``)'.format(lines[0][:-1], modname), "=") + _print_section(f"{lines[0][:-1]} (``{modname}``)", "=") print() for line in lines[2:]: print(line) @@ -76,7 +75,7 @@ def main(): for fnname in FNNAMES: fn = getattr(module, fnname, None) if fn is not None: - _print_section(":py:func:`iodata.formats.{}.{}`".format(modname, fnname), "-") + _print_section(f":py:func:`iodata.formats.{modname}.{fnname}`", "-") if fnname.startswith("load"): print("- Always loads", _format_words(fn.guaranteed)) if fn.ifpresent: @@ -95,5 +94,5 @@ def main(): print() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/doc/gen_formats_tab.py b/doc/gen_formats_tab.py index e33d03af1..3ed19cbf7 100755 --- a/doc/gen_formats_tab.py +++ b/doc/gen_formats_tab.py @@ -20,13 +20,11 @@ # pylint: disable=unused-argument,redefined-builtin """Generate formats.rst.""" - -from collections import defaultdict import inspect +from collections import defaultdict import iodata - __all__ = [] @@ -86,13 +84,12 @@ def generate_table_rst(): table with rows of each property and columns of each format """ - fmt_names, has_load, guaranteed, ifpresent, has_dump, required, optional = \ + fmt_names, has_load, guaranteed, ifpresent, has_dump, required, optional = ( _generate_all_format_parser() + ) # Sort rows by number of times the attribute is used in decreasing order. - rows = sorted( - attr_name for attr_name in dir(iodata.IOData) - if not attr_name.startswith('_')) + rows = sorted(attr_name for attr_name in dir(iodata.IOData) if not attr_name.startswith("_")) # Order columns based on number of guaranteed and ifpresent entries for each format. # Also keep track of which format has a load_one and dump_one function. @@ -106,10 +103,9 @@ def generate_table_rst(): # Construct header with cross-referencing columns. header = ["Attribute"] for fmt_name in cols: - col_name = f':ref:`{fmt_name} `' + col_name = f":ref:`{fmt_name} `" col_name += ": {}{}".format( - "L" if fmt_name in has_load else "", - "D" if fmt_name in has_dump else "" + "L" if fmt_name in has_load else "", "D" if fmt_name in has_dump else "" ) header.append(col_name) table = [header] diff --git a/doc/gen_inputs.py b/doc/gen_inputs.py index e8a3ccbe2..df9566bbe 100755 --- a/doc/gen_inputs.py +++ b/doc/gen_inputs.py @@ -20,10 +20,9 @@ # pylint: disable=unused-argument,redefined-builtin """Generate formats.rst.""" - from gen_formats import _format_words, _print_section -from iodata.api import INPUT_MODULES +from iodata.api import INPUT_MODULES __all__ = [] @@ -54,16 +53,16 @@ def main(): for modname, module in sorted(INPUT_MODULES.items()): if not hasattr(module, "write_input"): continue - lines = module.__doc__.split('\n') + lines = module.__doc__.split("\n") # add labels for cross-referencing format (e.g. in formats table) print(f".. _input_{modname}:") print() - _print_section('{} (``{}``)'.format(lines[0][:-1], modname), "=") + _print_section(f"{lines[0][:-1]} (``{modname}``)", "=") print() for line in lines[2:]: print(line) - _print_section(":py:func:`iodata.formats.{}.write_input`".format(modname), "-") + _print_section(f":py:func:`iodata.formats.{modname}.write_input`", "-") fn = getattr(module, "write_input", None) print("- Requires", _format_words(fn.required)) if fn.optional: @@ -76,11 +75,11 @@ def main(): print() template = getattr(module, "default_template", None) if template: - code_block_lines = [" " + l for l in template.split("\n")] + code_block_lines = [" " + ell for ell in template.split("\n")] print(TEMPLATE.format(code_block_lines="\n".join(code_block_lines))) print() print() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/doc/index.rst b/doc/index.rst index f7886c0a8..d89e9c5cb 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -46,17 +46,17 @@ For the list of file formats that can be loaded or dumped by IOData, see :ref:`file_formats`. The two tables below summarize the file formats and features supported by IOData. -======= ========== -Code Definition -======= ========== -**L** loading is supported -**D** dumping is supported -*(d)* attribute may be derived from other attributes -R attribute is always read -r attribute is read if present -W attribute is always written -w attribute is is written if present -======= ========== +========= ========== +Code Definition +========= ========== +**L** loading is supported +**D** dumping is supported +*(d)* attribute may be derived from other attributes +R attribute is always read +r attribute is read if present +W attribute is always written +w attribute is is written if present +========= ========== .. include:: formats_tab.inc diff --git a/doc/install.rst b/doc/install.rst index 4ae24cece..79fbbdd54 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -63,14 +63,14 @@ To install IOData using the conda package management system, install source activate horton3 # Install the stable release. - conda install -c theochem iodata + conda install -c theochem qc-iodata # Unstable releases # (Only do this if you understand the implications.) # Install the testing release. (beta) - conda install -c theochem/label/test iodata + conda install -c theochem/label/test qc-iodata # Install the development release. (alpha) - conda install -c theochem/label/dev iodata + conda install -c theochem/label/dev qc-iodata Installation with Pip diff --git a/doc/requirements.txt b/doc/requirements.txt index 0064fa5ef..70ffff456 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,2 +1,3 @@ # Requirements for building documentation on RTD sphinx_autodoc_typehints +sphinx_rtd_theme diff --git a/environment.yml b/environment.yml deleted file mode 100644 index d4cf29bf6..000000000 --- a/environment.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: rtd-iodata -dependencies: - - python=3.7 - - numpy - - scipy - - attrs >=20.1.0 - - pip: - - sphinx-autodoc-typehints - - sphinxcontrib-napoleon diff --git a/iodata/__init__.py b/iodata/__init__.py index ef8713f2f..7e80f8cf2 100644 --- a/iodata/__init__.py +++ b/iodata/__init__.py @@ -18,12 +18,14 @@ # -- """Input and Output Module.""" - try: - from ._version import __version__ + from ._version import __version__, __version_tuple__ except ImportError: __version__ = "0.0.0.post0" + __version_tuple__ = (0, 0, 0, "a-dev") +from .api import dump_many, dump_one, load_many, load_one, write_input from .iodata import IOData -from .api import * + +__all__ = ("IOData", "load_one", "load_many", "dump_one", "dump_many", "write_input") diff --git a/iodata/__main__.py b/iodata/__main__.py index 96dcf0774..e8228ffe1 100755 --- a/iodata/__main__.py +++ b/iodata/__main__.py @@ -19,16 +19,16 @@ # -- """CLI for file conversion.""" - import argparse + import numpy as np -from .api import load_one, dump_one, load_many, dump_many, FORMAT_MODULES +from .api import FORMAT_MODULES, dump_many, dump_one, load_many, load_one try: from iodata.version import __version__ except ImportError: - __version__ = '0.0.0.post0' + __version__ = "0.0.0.post0" __all__ = [] @@ -49,36 +49,49 @@ dump_many {dump_many} """.format( - load_one=' '.join(name for name, module in sorted(FORMAT_MODULES.items()) - if hasattr(module, 'load_one')), - dump_one=' '.join(name for name, module in sorted(FORMAT_MODULES.items()) - if hasattr(module, 'dump_one')), - load_many=' '.join(name for name, module in sorted(FORMAT_MODULES.items()) - if hasattr(module, 'load_many')), - dump_many=' '.join(name for name, module in sorted(FORMAT_MODULES.items()) - if hasattr(module, 'dump_many')), + load_one=" ".join( + name for name, module in sorted(FORMAT_MODULES.items()) if hasattr(module, "load_one") + ), + dump_one=" ".join( + name for name, module in sorted(FORMAT_MODULES.items()) if hasattr(module, "dump_one") + ), + load_many=" ".join( + name for name, module in sorted(FORMAT_MODULES.items()) if hasattr(module, "load_many") + ), + dump_many=" ".join( + name for name, module in sorted(FORMAT_MODULES.items()) if hasattr(module, "dump_many") + ), ) def parse_args(): """Use argparse to to parse command-line arguments.""" parser = argparse.ArgumentParser( - prog='iodata-convert', formatter_class=argparse.RawTextHelpFormatter, - description=DESCRIPTION) + prog="iodata-convert", + formatter_class=argparse.RawTextHelpFormatter, + description=DESCRIPTION, + ) parser.add_argument( - '-V', '--version', action='version', - version="%(prog)s (IOData version {})".format(__version__)) + "-V", + "--version", + action="version", + version=f"%(prog)s (IOData version {__version__})", + ) parser.add_argument( - '-i', '--infmt', - help='Select the input format, overrides automatic detection.') + "-i", "--infmt", help="Select the input format, overrides automatic detection." + ) parser.add_argument( - '-o', '--outfmt', - help='Select the output format, overrides automatic detection.') + "-o", "--outfmt", help="Select the output format, overrides automatic detection." + ) parser.add_argument( - '-m', '--many', default=False, action='store_true', - help='Convert many frames, e.g. for trajectories.') - parser.add_argument('input', help='The input file.') - parser.add_argument('output', help='The output file.') + "-m", + "--many", + default=False, + action="store_true", + help="Convert many frames, e.g. for trajectories.", + ) + parser.add_argument("input", help="The input file.") + parser.add_argument("output", help="The output file.") return parser.parse_args() @@ -108,11 +121,11 @@ def convert(infn, outfn, many, infmt, outfmt): def main(): """Convert files between two formats using command-line arguments.""" # All, except underflows, is *not* fine. - np.seterr(divide='raise', over='raise', invalid='raise') + np.seterr(divide="raise", over="raise", invalid="raise") args = parse_args() convert(args.input, args.output, args.many, args.infmt, args.outfmt) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/iodata/api.py b/iodata/api.py index 95ce0d2ff..92f199391 100644 --- a/iodata/api.py +++ b/iodata/api.py @@ -18,28 +18,27 @@ # -- """Functions to be used by end users.""" - import os -from typing import Iterator, Callable -from types import ModuleType +from collections.abc import Iterator from fnmatch import fnmatch -from pkgutil import iter_modules from importlib import import_module +from pkgutil import iter_modules +from types import ModuleType +from typing import Callable, Optional from .iodata import IOData from .utils import LineIterator - -__all__ = ['load_one', 'load_many', 'dump_one', 'dump_many', 'write_input'] +__all__ = ["load_one", "load_many", "dump_one", "dump_many", "write_input"] def _find_format_modules(): """Return all file-format modules found with importlib.""" result = {} - for module_info in iter_modules(import_module('iodata.formats').__path__): + for module_info in iter_modules(import_module("iodata.formats").__path__): if not module_info.ispkg: - format_module = import_module('iodata.formats.' + module_info.name) - if hasattr(format_module, 'PATTERNS'): + format_module = import_module("iodata.formats." + module_info.name) + if hasattr(format_module, "PATTERNS"): result[module_info.name] = format_module return result @@ -47,7 +46,7 @@ def _find_format_modules(): FORMAT_MODULES = _find_format_modules() -def _select_format_module(filename: str, attrname: str, fmt: str = None) -> ModuleType: +def _select_format_module(filename: str, attrname: str, fmt: Optional[str] = None) -> ModuleType: """Find a file format module with the requested attribute name. Parameters @@ -69,21 +68,21 @@ def _select_format_module(filename: str, attrname: str, fmt: str = None) -> Modu basename = os.path.basename(filename) if fmt is None: for format_module in FORMAT_MODULES.values(): - if any(fnmatch(basename, pattern) for pattern in format_module.PATTERNS): - if hasattr(format_module, attrname): - return format_module + if any(fnmatch(basename, pattern) for pattern in format_module.PATTERNS) and hasattr( + format_module, attrname + ): + return format_module else: return FORMAT_MODULES[fmt] - raise ValueError('Could not find file format with feature {} for file {}'.format( - attrname, filename)) + raise ValueError(f"Could not find file format with feature {attrname} for file {filename}") def _find_input_modules(): """Return all input modules found with importlib.""" result = {} - for module_info in iter_modules(import_module('iodata.inputs').__path__): + for module_info in iter_modules(import_module("iodata.inputs").__path__): if not module_info.ispkg: - input_module = import_module('iodata.inputs.' + module_info.name) + input_module = import_module("iodata.inputs." + module_info.name) if hasattr(input_module, "write_input"): result[module_info.name] = input_module return result @@ -107,13 +106,13 @@ def _select_input_module(fmt: str) -> ModuleType: """ if fmt in INPUT_MODULES: - if not hasattr(INPUT_MODULES[fmt], 'write_input'): - raise ValueError(f'{fmt} input module does not have write_input!') + if not hasattr(INPUT_MODULES[fmt], "write_input"): + raise ValueError(f"{fmt} input module does not have write_input!") return INPUT_MODULES[fmt] raise ValueError(f"Could not find input format {fmt}!") -def load_one(filename: str, fmt: str = None, **kwargs) -> IOData: +def load_one(filename: str, fmt: Optional[str] = None, **kwargs) -> IOData: """Load data from a file. This function uses the extension or prefix of the filename to determine the @@ -136,7 +135,7 @@ def load_one(filename: str, fmt: str = None, **kwargs) -> IOData: The instance of IOData with data loaded from the input files. """ - format_module = _select_format_module(filename, 'load_one', fmt) + format_module = _select_format_module(filename, "load_one", fmt) lit = LineIterator(filename) try: iodata = IOData(**format_module.load_one(lit, **kwargs)) @@ -145,7 +144,7 @@ def load_one(filename: str, fmt: str = None, **kwargs) -> IOData: return iodata -def load_many(filename: str, fmt: str = None, **kwargs) -> Iterator[IOData]: +def load_many(filename: str, fmt: Optional[str] = None, **kwargs) -> Iterator[IOData]: """Load multiple IOData instances from a file. This function uses the extension or prefix of the filename to determine the @@ -168,16 +167,16 @@ def load_many(filename: str, fmt: str = None, **kwargs) -> Iterator[IOData]: An instance of IOData with data for one frame loaded for the file. """ - format_module = _select_format_module(filename, 'load_many', fmt) + format_module = _select_format_module(filename, "load_many", fmt) lit = LineIterator(filename) - for data in format_module.load_many(lit, **kwargs): - try: + try: + for data in format_module.load_many(lit, **kwargs): yield IOData(**data) - except StopIteration: - return + except StopIteration: + return -def dump_one(iodata: IOData, filename: str, fmt: str = None, **kwargs): +def dump_one(iodata: IOData, filename: str, fmt: Optional[str] = None, **kwargs): """Write data to a file. This routine uses the extension or prefix of the filename to determine @@ -197,12 +196,12 @@ def dump_one(iodata: IOData, filename: str, fmt: str = None, **kwargs): Keyword arguments are passed on to the format-specific dump_one function. """ - format_module = _select_format_module(filename, 'dump_one', fmt) - with open(filename, 'w') as f: + format_module = _select_format_module(filename, "dump_one", fmt) + with open(filename, "w") as f: format_module.dump_one(f, iodata, **kwargs) -def dump_many(iodatas: Iterator[IOData], filename: str, fmt: str = None, **kwargs): +def dump_many(iodatas: Iterator[IOData], filename: str, fmt: Optional[str] = None, **kwargs): """Write multiple IOData instances to a file. This routine uses the extension or prefix of the filename to determine @@ -221,13 +220,19 @@ def dump_many(iodatas: Iterator[IOData], filename: str, fmt: str = None, **kwarg Keyword arguments are passed on to the format-specific dump_many function. """ - format_module = _select_format_module(filename, 'dump_many', fmt) - with open(filename, 'w') as f: + format_module = _select_format_module(filename, "dump_many", fmt) + with open(filename, "w") as f: format_module.dump_many(f, iodatas, **kwargs) -def write_input(iodata: IOData, filename: str, fmt: str, template: str = None, - atom_line: Callable = None, **kwargs): +def write_input( + iodata: IOData, + filename: str, + fmt: str, + template: Optional[str] = None, + atom_line: Optional[Callable] = None, + **kwargs, +): """Write input file using an instance of IOData for the specified software format. Parameters @@ -240,15 +245,16 @@ def write_input(iodata: IOData, filename: str, fmt: str, template: str = None, The name of the software for which input file is generated. template The template input string. + If not given, a default template for the selected software is used. atom_line A function taking two arguments: an IOData instance, and an index of the atom. This function returns a formatted line for the corresponding - atom. When ommited, a default atom_line function for the selected + atom. When omitted, a default atom_line function for the selected input format is used. **kwargs Keyword arguments are passed on to the input-specific write_input function. """ input_module = _select_input_module(fmt) - with open(filename, 'w') as f: - input_module.write_input(f, iodata, template, atom_line, **kwargs) + with open(filename, "w") as fh: + input_module.write_input(fh, iodata, template, atom_line, **kwargs) diff --git a/iodata/attrutils.py b/iodata/attrutils.py index 41466ad3c..3e157aede 100644 --- a/iodata/attrutils.py +++ b/iodata/attrutils.py @@ -18,23 +18,22 @@ # -- """Utilities for building attr classes.""" - import numpy as np - __all__ = ["convert_array_to", "validate_shape"] def convert_array_to(dtype): """Return a function to convert arrays to the given type.""" + def converter(array): if array is None: return None return np.array(array, copy=False, dtype=dtype) + return converter -# pylint: disable=too-many-branches def validate_shape(*shape_requirements: tuple): """Return a validator for the shape of an array or the length of an iterable. @@ -73,6 +72,7 @@ def validate_shape(*shape_requirements: tuple): the expected size of the array being checked along axis ``i``. """ + def validator(obj, attribute, value): # Build the expected shape, with the rules from the docstring. expected_shape = [] @@ -85,28 +85,21 @@ def validator(obj, attribute, value): other_name, other_axis = item other = getattr(obj, other_name) if other is None: - raise TypeError( - "Other attribute '{}' is not set.".format(other_name) - ) + raise TypeError(f"Other attribute '{other_name}' is not set.") if other_axis == 0: expected_shape.append(len(other)) else: if other_axis >= other.ndim or other_axis < 0: raise TypeError( "Cannot get length along axis " - "{} of attribute {} with ndim {}.".format( - other_axis, other_name, other.ndim - ) + f"{other_axis} of attribute {other_name} with ndim {other.ndim}." ) expected_shape.append(other.shape[other_axis]) else: raise ValueError(f"Cannot interpret item in shape_requirements: {item}") expected_shape = tuple(expected_shape) # Get the actual shape - if isinstance(value, np.ndarray): - observed_shape = value.shape - else: - observed_shape = (len(value),) + observed_shape = value.shape if isinstance(value, np.ndarray) else (len(value),) # Compare match = True if len(expected_shape) != len(observed_shape): @@ -121,9 +114,8 @@ def validator(obj, attribute, value): # Raise TypeError if needed. if not match: raise TypeError( - "Expecting shape {} for attribute {}, got {}".format( - expected_shape, attribute.name, observed_shape - ) + f"Expecting shape {expected_shape} for attribute {attribute.name}, " + f"got {observed_shape}" ) return validator diff --git a/iodata/basis.py b/iodata/basis.py index 26667121e..510eec806 100644 --- a/iodata/basis.py +++ b/iodata/basis.py @@ -16,37 +16,53 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see # -- -"""Utility functions for working with basis sets.""" +"""Utility functions for working with basis sets. + +Notes +----- +Basis set conventions and terminology are documented in :ref:`basis_conventions`. + +""" from functools import wraps from numbers import Integral -from typing import List, Dict, Tuple, Union +from typing import Union -import attr +import attrs import numpy as np +from numpy.typing import NDArray from .attrutils import validate_shape +__all__ = [ + "angmom_sti", + "angmom_its", + "Shell", + "MolecularBasis", + "convert_convention_shell", + "convert_conventions", + "iter_cart_alphabet", + "HORTON2_CONVENTIONS", + "CCA_CONVENTIONS", +] -__all__ = ['angmom_sti', 'angmom_its', 'Shell', 'MolecularBasis', - 'convert_convention_shell', 'convert_conventions', - 'iter_cart_alphabet', 'HORTON2_CONVENTIONS', 'CCA_CONVENTIONS'] - -ANGMOM_CHARS = 'spdfghiklmnoqrtuvwxyzabce' +ANGMOM_CHARS = "spdfghiklmnoqrtuvwxyzabce" def _alsolist(f): """Wrap a function to accepts also list as first argument and then return list.""" + @wraps(f) def wrapper(firsts, *args, **kwargs): if isinstance(firsts, (Integral, str)): return f(firsts, *args, **kwargs) return [f(first, *args, **kwargs) for first in firsts] + return wrapper @_alsolist -def angmom_sti(char: Union[str, List[str]]) -> Union[int, List[int]]: +def angmom_sti(char: Union[str, list[str]]) -> Union[int, list[int]]: """Convert an angular momentum from string to integer format. Parameters @@ -65,7 +81,7 @@ def angmom_sti(char: Union[str, List[str]]) -> Union[int, List[int]]: @_alsolist -def angmom_its(angmom: Union[int, List[int]]) -> Union[str, List[str]]: +def angmom_its(angmom: Union[int, list[int]]) -> Union[str, list[str]]: """Convert an angular momentum from integer to string representation. Parameters @@ -85,8 +101,7 @@ def angmom_its(angmom: Union[int, List[int]]) -> Union[str, List[str]]: return ANGMOM_CHARS[angmom] -@attr.s(auto_attribs=True, slots=True, - on_setattr=[attr.setters.validate, attr.setters.convert]) +@attrs.define class Shell: """A shell in a molecular basis representing (generalized) contractions with the same exponents. @@ -112,38 +127,37 @@ class Shell: """ - icenter: int - angmoms: List[int] = attr.ib(validator=validate_shape(("coeffs", 1))) - kinds: List[str] = attr.ib(validator=validate_shape(("coeffs", 1))) - exponents: np.ndarray = attr.ib(validator=validate_shape(("coeffs", 0))) - coeffs: np.ndarray = attr.ib(validator=validate_shape(("exponents", 0), ("kinds", 0))) + icenter: int = attrs.field() + angmoms: list[int] = attrs.field(validator=validate_shape(("coeffs", 1))) + kinds: list[str] = attrs.field(validator=validate_shape(("coeffs", 1))) + exponents: NDArray = attrs.field(validator=validate_shape(("coeffs", 0))) + coeffs: NDArray = attrs.field(validator=validate_shape(("exponents", 0), ("kinds", 0))) @property - def nbasis(self) -> int: # noqa: D401 + def nbasis(self) -> int: """Number of basis functions (e.g. 3 for a P shell and 4 for an SP shell).""" result = 0 for angmom, kind in zip(self.angmoms, self.kinds): - if kind == 'c': # Cartesian + if kind == "c": # Cartesian result += ((angmom + 1) * (angmom + 2)) // 2 - elif kind == 'p' and angmom >= 2: + elif kind == "p" and angmom >= 2: result += 2 * angmom + 1 else: - raise TypeError('Unknown shell kind \'{}\'; expected \'c\' or \'p\'.'.format(kind)) + raise TypeError(f"Unknown shell kind '{kind}'; expected 'c' or 'p'.") return result @property - def nprim(self) -> int: # noqa: D401 + def nprim(self) -> int: """Number of primitives, also known as the contraction length.""" return len(self.exponents) @property - def ncon(self) -> int: # noqa: D401 + def ncon(self) -> int: """Number of contractions. This is usually 1; e.g., it would be 2 for an SP shell.""" return len(self.angmoms) -@attr.s(auto_attribs=True, slots=True, - on_setattr=[attr.setters.validate, attr.setters.convert]) +@attrs.define class MolecularBasis: """A complete molecular orbital or density basis set. @@ -192,12 +206,12 @@ class MolecularBasis: """ - shells: List[Shell] - conventions: Dict[str, str] - primitive_normalization: str + shells: list[Shell] = attrs.field() + conventions: dict[str, str] = attrs.field() + primitive_normalization: str = attrs.field() @property - def nbasis(self) -> int: # noqa: D401 + def nbasis(self) -> int: """Number of basis functions.""" return sum(shell.nbasis for shell in self.shells) @@ -206,14 +220,15 @@ def get_segmented(self): shells = [] for shell in self.shells: for angmom, kind, coeffs in zip(shell.angmoms, shell.kinds, shell.coeffs.T): - shells.append(Shell(shell.icenter, [angmom], [kind], - shell.exponents, coeffs.reshape(-1, 1))) - # pylint: disable=no-member - return attr.evolve(self, shells=shells) + shells.append( + Shell(shell.icenter, [angmom], [kind], shell.exponents, coeffs.reshape(-1, 1)) + ) + return attrs.evolve(self, shells=shells) -def convert_convention_shell(conv1: List[str], conv2: List[str], reverse=False) \ - -> Tuple[np.ndarray, np.ndarray]: +def convert_convention_shell( + conv1: list[str], conv2: list[str], reverse=False +) -> tuple[NDArray, NDArray]: """Return a permutation vector and sign changes to convert from 1 to 2. The transformation from convention 1 to convention 2 can be done applying @@ -247,20 +262,22 @@ def convert_convention_shell(conv1: List[str], conv2: List[str], reverse=False) """ if len(conv1) != len(conv2): - raise TypeError('conv1 and conv2 must contain the same number of elements.') + raise TypeError("conv1 and conv2 must contain the same number of elements.") # Get signs from both - signs1 = [1 - 2 * el1.startswith('-') for el1 in conv1] - signs2 = [1 - 2 * el2.startswith('-') for el2 in conv2] + signs1 = [1 - 2 * el1.startswith("-") for el1 in conv1] + signs2 = [1 - 2 * el2.startswith("-") for el2 in conv2] # Strip signs from both - conv1 = [el1.lstrip('-') for el1 in conv1] - conv2 = [el2.lstrip('-') for el2 in conv2] + conv1 = [el1.lstrip("-") for el1 in conv1] + conv2 = [el2.lstrip("-") for el2 in conv2] if len(conv1) != len(set(conv1)): - raise TypeError('Argument conv1 contains duplicates.') + raise TypeError("Argument conv1 contains duplicates.") if len(conv2) != len(set(conv2)): - raise TypeError('Argument conv2 contains duplicates.') + raise TypeError("Argument conv2 contains duplicates.") if set(conv1) != set(conv2): - raise TypeError('Without the minus signs, conv1 and conv2 must contain ' - 'the same elements. Got {} and {}.'.format(conv1, conv2)) + raise TypeError( + "Without the minus signs, conv1 and conv2 must contain " + f"the same elements. Got {conv1} and {conv2}." + ) # Get the permutation if reverse: permutation = [conv2.index(el1) for el1 in conv1] @@ -271,8 +288,9 @@ def convert_convention_shell(conv1: List[str], conv2: List[str], reverse=False) return permutation, signs -def convert_conventions(molbasis: MolecularBasis, new_conventions: Dict[str, List[str]], - reverse=False) -> Tuple[np.ndarray, np.ndarray]: +def convert_conventions( + molbasis: MolecularBasis, new_conventions: dict[str, list[str]], reverse=False +) -> tuple[NDArray, NDArray]: """Return a permutation vector and sign changes to convert from 1 to 2. The transformation from molbasis.convention to the new convention can be done @@ -317,13 +335,12 @@ def convert_conventions(molbasis: MolecularBasis, new_conventions: Dict[str, Lis conv2 = new_conventions[key] shell_permutation, shell_signs = convert_convention_shell(conv1, conv2, reverse) offset = len(permutation) - for i in shell_permutation: - permutation.append(i + offset) + permutation.extend(i + offset for i in shell_permutation) signs.extend(shell_signs) return np.array(permutation), np.array(signs) -def iter_cart_alphabet(n: int) -> np.ndarray: +def iter_cart_alphabet(n: int) -> NDArray: """Loop over powers of Cartesian basis functions in alphabetical order. See https://theochem.github.io/horton/2.1.1/tech_ref_gaussian_basis.html @@ -341,7 +358,7 @@ def iter_cart_alphabet(n: int) -> np.ndarray: yield np.array((nx, ny, nz), dtype=int) -def get_default_conventions() -> Tuple[Dict, Dict]: +def get_default_conventions() -> tuple[dict, dict]: """Produce conventions dictionaries compatible with HORTON2 and CCA. Do not change this! Both conventions are also used by several file formats @@ -352,7 +369,7 @@ def get_default_conventions() -> Tuple[Dict, Dict]: Kenny, J. P.; Janssen, C. L.; Valeev, E. F.; Windus, T. L. Components for Integral Evaluation in Quantum Chemistry: Components for Integral Evaluation - in Quantum Chemistry. J. Comput. Chem. 2008, 29 (4), 562–577. + in Quantum Chemistry. J. Comput. Chem. 2008, 29 (4), 562-577. https://doi.org/10.1002/jcc.20815. The ordering of the spherical harmonics within one shell is rather vague @@ -370,20 +387,19 @@ def get_default_conventions() -> Tuple[Dict, Dict]: Architecture (CCA). """ - horton2 = {(0, 'c'): ['1']} + horton2 = {(0, "c"): ["1"]} cca = horton2.copy() for angmom in range(1, 25): - conv_cart = list('x' * nx + 'y' * ny + 'z' * nz - for nx, ny, nz in iter_cart_alphabet(angmom)) - key = (angmom, 'c') + conv_cart = ["x" * nx + "y" * ny + "z" * nz for nx, ny, nz in iter_cart_alphabet(angmom)] + key = (angmom, "c") horton2[key] = conv_cart cca[key] = conv_cart if angmom > 1: - conv_pure = ['c0'] + conv_pure = ["c0"] for absm in range(1, angmom + 1): - conv_pure.append('c{}'.format(absm)) - conv_pure.append('s{}'.format(absm)) - key = (angmom, 'p') + conv_pure.append(f"c{absm}") + conv_pure.append(f"s{absm}") + key = (angmom, "p") horton2[key] = conv_pure cca[key] = conv_pure[:1:-2] + conv_pure[:1] + conv_pure[1::2] return horton2, cca diff --git a/iodata/docstrings.py b/iodata/docstrings.py index f8dccbee6..364d668c3 100644 --- a/iodata/docstrings.py +++ b/iodata/docstrings.py @@ -16,34 +16,46 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see # -- -# pylint: disable=dangerous-default-value """Docstring decorators for file format implementations.""" - -from typing import List, Dict - - -__all__ = ['document_load_one', 'document_load_many', 'document_dump_one', 'document_dump_many', - 'document_write_input'] - - -def _document_load(template: str, fmt: str, guaranteed: List[str], ifpresent: List[str] = None, - kwdocs: Dict[str, str] = {}, notes: str = None): +from typing import Optional + +__all__ = [ + "document_load_one", + "document_load_many", + "document_dump_one", + "document_dump_many", + "document_write_input", +] + + +def _document_load( + template: str, + fmt: str, + guaranteed: list[str], + ifpresent: Optional[list[str]] = None, + kwdocs: Optional[dict[str, str]] = None, + notes: Optional[str] = None, +): + if kwdocs is None: + kwdocs = {} ifpresent = ifpresent or [] def decorator(func): if ifpresent: - ifpresent_sentence = ( - " The following may be loaded if present in the file: {}.".format( - ', '.join("``{}``".format(word) for word in ifpresent))) + ifpresent_sentence = " The following may be loaded if present in the file: {}.".format( + ", ".join(f"``{word}``" for word in ifpresent) + ) else: ifpresent_sentence = "" func.__doc__ = template.format( fmt=fmt, - guaranteed=', '.join("``{}``".format(word) for word in guaranteed), + guaranteed=", ".join(f"``{word}``" for word in guaranteed), ifpresent=ifpresent_sentence, - kwdocs="\n".join("{}\n {}".format(name, docu.replace("\n", " ")) - for name, docu in sorted(kwdocs.items())), + kwdocs="\n".join( + "{}\n {}".format(name, docu.replace("\n", " ")) + for name, docu in sorted(kwdocs.items()) + ), notes=(notes or ""), ) func.fmt = fmt @@ -52,6 +64,7 @@ def decorator(func): func.kwdocs = kwdocs func.notes = notes return func + return decorator @@ -77,8 +90,13 @@ def decorator(func): """ -def document_load_one(fmt: str, guaranteed: List[str], ifpresent: List[str] = None, - kwdocs: Dict[str, str] = {}, notes: str = None): +def document_load_one( + fmt: str, + guaranteed: list[str], + ifpresent: Optional[list[str]] = None, + kwdocs: Optional[dict[str, str]] = None, + notes: Optional[str] = None, +): """Decorate a load_one function to generate a docstring. Parameters @@ -102,6 +120,8 @@ def document_load_one(fmt: str, guaranteed: List[str], ifpresent: List[str] = No A decorator function. """ + if kwdocs is None: + kwdocs = {} return _document_load(LOAD_ONE_DOC_TEMPLATE, fmt, guaranteed, ifpresent, kwdocs, notes) @@ -127,8 +147,13 @@ def document_load_one(fmt: str, guaranteed: List[str], ifpresent: List[str] = No """ -def document_load_many(fmt: str, guaranteed: List[str], ifpresent: List[str] = None, - kwdocs: Dict[str, str] = {}, notes: str = None): +def document_load_many( + fmt: str, + guaranteed: list[str], + ifpresent: Optional[list[str]] = None, + kwdocs: Optional[dict[str, str]] = None, + notes: Optional[str] = None, +): """Decorate a load_many function to generate a docstring. Parameters @@ -152,11 +177,21 @@ def document_load_many(fmt: str, guaranteed: List[str], ifpresent: List[str] = N A decorator function. """ + if kwdocs is None: + kwdocs = {} return _document_load(LOAD_MANY_DOC_TEMPLATE, fmt, guaranteed, ifpresent, kwdocs, notes) -def _document_dump(template: str, fmt: str, required: List[str], optional: List[str] = None, - kwdocs: Dict[str, str] = {}, notes: str = None): +def _document_dump( + template: str, + fmt: str, + required: list[str], + optional: Optional[list[str]] = None, + kwdocs: Optional[dict[str, str]] = None, + notes: Optional[str] = None, +): + if kwdocs is None: + kwdocs = {} optional = optional or [] def decorator(func): @@ -164,15 +199,17 @@ def decorator(func): optional_sentence = ( " If the following attributes are present, they are also dumped " "into the file: {}." - ).format(', '.join("``{}``".format(word) for word in optional)) + ).format(", ".join(f"``{word}``" for word in optional)) else: optional_sentence = "" func.__doc__ = template.format( fmt=fmt, - required=', '.join("``{}``".format(word) for word in required), + required=", ".join(f"``{word}``" for word in required), optional=optional_sentence, - kwdocs="\n".join("{}\n {}".format(name, docu.replace("\n", " ")) - for name, docu in sorted(kwdocs.items())), + kwdocs="\n".join( + "{}\n {}".format(name, docu.replace("\n", " ")) + for name, docu in sorted(kwdocs.items()) + ), notes=(notes or ""), ) func.fmt = fmt @@ -181,6 +218,7 @@ def decorator(func): func.kwdocs = kwdocs func.notes = notes return func + return decorator @@ -203,8 +241,13 @@ def decorator(func): """ -def document_dump_one(fmt: str, required: List[str], optional: List[str] = None, - kwdocs: Dict[str, str] = {}, notes: str = None): +def document_dump_one( + fmt: str, + required: list[str], + optional: Optional[list[str]] = None, + kwdocs: Optional[dict[str, str]] = None, + notes: Optional[str] = None, +): """Decorate a dump_one function to generate a docstring. Parameters @@ -228,6 +271,8 @@ def document_dump_one(fmt: str, required: List[str], optional: List[str] = None, A decorator function. """ + if kwdocs is None: + kwdocs = {} return _document_dump(DUMP_ONE_DOC_TEMPLATE, fmt, required, optional, kwdocs, notes) @@ -250,8 +295,13 @@ def document_dump_one(fmt: str, required: List[str], optional: List[str] = None, """ -def document_dump_many(fmt: str, required: List[str], optional: List[str] = None, - kwdocs: Dict[str, str] = {}, notes: str = None): +def document_dump_many( + fmt: str, + required: list[str], + optional: Optional[list[str]] = None, + kwdocs: Optional[dict[str, str]] = None, + notes: Optional[str] = None, +): """Decorate a dump_many function to generate a docstring. Parameters @@ -275,12 +325,20 @@ def document_dump_many(fmt: str, required: List[str], optional: List[str] = None A decorator function. """ + if kwdocs is None: + kwdocs = {} return _document_dump(DUMP_MANY_DOC_TEMPLATE, fmt, required, optional, kwdocs, notes) -def _document_write(template: str, fmt: str, required: List[str], optional: List[str] = None, - notes: str = None): - optional = optional or [] +def _document_write( + template: str, + fmt: str, + required: list[str], + optional: Optional[list[str]] = None, + notes: Optional[str] = None, +): + if optional is None: + optional = [] def decorator(func): if optional: @@ -288,12 +346,12 @@ def decorator(func): " If the following attributes are present, they are also written " "into the file: {}. If these attributes are not assigned, " "internal default values are used." - ).format(', '.join("``{}``".format(word) for word in optional)) + ).format(", ".join(f"``{word}``" for word in optional)) else: optional_sentence = "" func.__doc__ = template.format( fmt=fmt, - required=', '.join("``{}``".format(word) for word in required), + required=", ".join(f"``{word}``" for word in required), optional=optional_sentence, notes=(notes or ""), ) @@ -302,6 +360,7 @@ def decorator(func): func.optional = optional func.notes = notes return func + return decorator @@ -320,7 +379,7 @@ def decorator(func): atom_line A function taking two arguments: an IOData instance, and an index of the atom. This function returns a formatted line for the corresponding - atom. When ommited, a default atom_line function for the selected + atom. When omitted, a default atom_line function for the selected input format is used. **kwargs Keyword arguments are passed on to the input-specific write_input function. @@ -332,8 +391,12 @@ def decorator(func): """ -def document_write_input(fmt: str, required: List[str], optional: List[str] = None, - notes: str = None): +def document_write_input( + fmt: str, + required: list[str], + optional: Optional[list[str]] = None, + notes: Optional[str] = None, +): """Decorate a write_input function to generate a docstring. Parameters diff --git a/iodata/formats/charmm.py b/iodata/formats/charmm.py index 95f120fd8..ae9f8149a 100644 --- a/iodata/formats/charmm.py +++ b/iodata/formats/charmm.py @@ -29,26 +29,22 @@ """ - -from typing import Tuple - import numpy as np from ..docstrings import document_load_one - -from ..utils import angstrom, amu, LineIterator +from ..utils import LineIterator, amu, angstrom __all__ = [] -PATTERNS = ['*.crd'] +PATTERNS = ["*.crd"] -@document_load_one('CRD', ['atcoords', 'atffparams', 'atmasses', 'extra'], ['title']) +@document_load_one("CRD", ["atcoords", "atffparams", "atmasses", "extra"], ["title"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" # Read title section - title = '' + title = "" while True: try: line = next(lit) @@ -69,31 +65,26 @@ def load_one(lit: LineIterator) -> dict: segid = np.array(data[4]) resid = np.array(data[5]) atmasses = np.array(data[6]) - atffparams = { - 'attypes': attypes, - 'resnames': resnames, - 'resnums': resnums - } + atffparams = {"attypes": attypes, "resnames": resnames, "resnums": resnums} extra = { - 'segid': segid, - 'resid': resid, + "segid": segid, + "resid": resid, } - result = { - 'atcoords': atcoords, - 'atffparams': atffparams, - 'atmasses': atmasses, - 'extra': extra, - 'title': title, + return { + "atcoords": atcoords, + "atffparams": atffparams, + "atmasses": atmasses, + "extra": extra, + "title": title, } - return result -def _helper_read_crd(lit: LineIterator) -> Tuple: +def _helper_read_crd(lit: LineIterator) -> tuple: """Read CHARMM crd file.""" # Read the line for number of atoms. natom = next(lit) if natom is None or not natom.strip().isdigit(): - lit.error('The number of atoms must be an integer.') + lit.error("The number of atoms must be an integer.") natom = int(natom) # Read the atom lines resnums = [] diff --git a/iodata/formats/chgcar.py b/iodata/formats/chgcar.py index bdc746100..619e1c531 100644 --- a/iodata/formats/chgcar.py +++ b/iodata/formats/chgcar.py @@ -25,23 +25,20 @@ different conversions to atomic units. """ - -from typing import Tuple - import numpy as np +from numpy.typing import NDArray from ..docstrings import document_load_one from ..periodic import sym2num -from ..utils import angstrom, volume, LineIterator, Cube - +from ..utils import Cube, LineIterator, angstrom, volume __all__ = [] -PATTERNS = ['CHGCAR*', 'AECCAR*'] +PATTERNS = ["CHGCAR*", "AECCAR*"] -def _load_vasp_header(lit: LineIterator) -> Tuple[str, np.ndarray, np.ndarray, np.ndarray]: +def _load_vasp_header(lit: LineIterator) -> tuple[str, NDArray, NDArray, NDArray]: """Load the cell and atoms from a VASP file format. Parameters @@ -66,10 +63,8 @@ def _load_vasp_header(lit: LineIterator) -> Tuple[str, np.ndarray, np.ndarray, n # read cell parameters in angstrom, without the universal scaling factor. # each row is one cell vector - cellvecs = [] - for _i in range(3): - cellvecs.append([float(w) for w in next(lit).split()]) - cellvecs = np.array(cellvecs) * angstrom * scaling + cellvecs = np.array([[float(w) for w in next(lit).split()] for _ in range(3)]) + cellvecs *= angstrom * scaling # note that in older VASP version the following line might be absent vasp_atnums = [sym2num[w] for w in next(lit).split()] @@ -81,10 +76,10 @@ def _load_vasp_header(lit: LineIterator) -> Tuple[str, np.ndarray, np.ndarray, n line = next(lit) # the 7th line can optionally indicate selective dynamics - if line[0].lower() in ['s']: + if line[0].lower() in ["s"]: line = next(lit) # parse direct/cartesian switch - cartesian = line[0].lower() in ['c', 'k'] + cartesian = line[0].lower() in ["c", "k"] # read the coordinates atcoords = [] @@ -133,22 +128,21 @@ def _load_vasp_grid(lit: LineIterator) -> dict: words = next(lit).split() cube_data[i0, i1, i2] = float(words.pop(0)) - cube = Cube(origin=np.zeros(3), axes=cellvecs / shape.reshape(-1, 1), - data=cube_data) + cube = Cube(origin=np.zeros(3), axes=cellvecs / shape.reshape(-1, 1), data=cube_data) return { - 'title': title, - 'atcoords': atcoords, - 'atnums': atnums, - 'cellvecs': cellvecs, - 'cube': cube, + "title": title, + "atcoords": atcoords, + "atnums": atnums, + "cellvecs": cellvecs, + "cube": cube, } -@document_load_one("VASP 5 CHGCAR", ['atcoords', 'atnums', 'cellvecs', 'cube', 'title']) +@document_load_one("VASP 5 CHGCAR", ["atcoords", "atnums", "cellvecs", "cube", "title"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" result = _load_vasp_grid(lit) # renormalize electron density - result['cube'].data[:] /= volume(result['cellvecs']) + result["cube"].data[:] /= volume(result["cellvecs"]) return result diff --git a/iodata/formats/cp2klog.py b/iodata/formats/cp2klog.py index bf803baf0..77d74a0bd 100644 --- a/iodata/formats/cp2klog.py +++ b/iodata/formats/cp2klog.py @@ -18,34 +18,32 @@ # -- """CP2K ATOM output file format.""" - -from typing import Dict, Union, List, Tuple +from typing import Union import numpy as np +from numpy.typing import NDArray from scipy.special import factorialk -from ..basis import angmom_sti, MolecularBasis, Shell, HORTON2_CONVENTIONS +from ..basis import HORTON2_CONVENTIONS, MolecularBasis, Shell, angmom_sti from ..docstrings import document_load_one from ..orbitals import MolecularOrbitals from ..utils import LineIterator - __all__ = [] -PATTERNS = ['*.cp2k.out'] +PATTERNS = ["*.cp2k.out"] CONVENTIONS = { - (0, 'c'): HORTON2_CONVENTIONS[(0, 'c')], - (1, 'c'): HORTON2_CONVENTIONS[(1, 'c')], - (2, 'p'): HORTON2_CONVENTIONS[(2, 'p')], - (3, 'p'): HORTON2_CONVENTIONS[(3, 'p')], + (0, "c"): HORTON2_CONVENTIONS[(0, "c")], + (1, "c"): HORTON2_CONVENTIONS[(1, "c")], + (2, "p"): HORTON2_CONVENTIONS[(2, "p")], + (3, "p"): HORTON2_CONVENTIONS[(3, "p")], } -def _get_cp2k_norm_corrections(l: int, alphas: Union[float, np.ndarray]) \ - -> Union[float, np.ndarray]: +def _get_cp2k_norm_corrections(ell: int, alphas: Union[float, NDArray]) -> Union[float, NDArray]: """Compute the corrections for the normalization of the basis functions. This correction is needed because the CP2K atom code works with a different @@ -54,7 +52,7 @@ def _get_cp2k_norm_corrections(l: int, alphas: Union[float, np.ndarray]) \ Parameters ---------- - l + ell The angular momentum of the (pure) basis function. (s=0, p=1, ...) alphas The exponent or exponents of the Gaussian primitives for which the correction @@ -68,10 +66,10 @@ def _get_cp2k_norm_corrections(l: int, alphas: Union[float, np.ndarray]) \ applied to the contraction coefficients. """ - expzet = 0.25 * (2 * l + 3) - prefac = np.sqrt(np.sqrt(np.pi) / 2.0 ** (l + 2) * factorialk(2 * l + 1, 2)) + expzet = 0.25 * (2 * ell + 3) + prefac = np.sqrt(np.sqrt(np.pi) / 2.0 ** (ell + 2) * factorialk(2 * ell + 1, 2)) zeta = 2.0 * alphas - return zeta ** expzet / prefac + return zeta**expzet / prefac def _read_cp2k_contracted_obasis(lit: LineIterator) -> MolecularBasis: @@ -91,32 +89,33 @@ def _read_cp2k_contracted_obasis(lit: LineIterator) -> MolecularBasis: shells = [] while True: line = next(lit) - if line[3:12] != 'Functions': + if line[3:12] != "Functions": break angmom = angmom_sti(line[1:2]) exponents = [] coeffs = [] for line in lit: - if line[3:12] == 'Functions' or line.startswith(' *******************'): + if line[3:12] == "Functions" or line.startswith(" *******************"): break values = [float(w) for w in line.split()] # one exponent per line exponents.append(values[0]) # many contraction coefficients per line, all corresponding to the # same primitive, so rows in coeffs - coeffs.append( - np.array(values[1:]) / _get_cp2k_norm_corrections(angmom, values[0])) + coeffs.append(np.array(values[1:]) / _get_cp2k_norm_corrections(angmom, values[0])) # Push back the last line for the next iteration lit.back(line) # Build the shell exponents = np.array(exponents) coeffs = np.array(coeffs) - kind = 'c' if angmom < 2 else 'p' - shells.append(Shell(0, np.array([angmom] * coeffs.shape[1]), - [kind] * coeffs.shape[1], - exponents, coeffs)) + kind = "c" if angmom < 2 else "p" + shells.append( + Shell( + 0, np.array([angmom] * coeffs.shape[1]), [kind] * coeffs.shape[1], exponents, coeffs + ) + ) - return MolecularBasis(shells, CONVENTIONS, 'L2') + return MolecularBasis(shells, CONVENTIONS, "L2") def _read_cp2k_uncontracted_obasis(lit: LineIterator) -> MolecularBasis: @@ -139,7 +138,7 @@ def _read_cp2k_uncontracted_obasis(lit: LineIterator) -> MolecularBasis: next(lit) while True: line = next(lit) - if line[3:13] != 'Exponents:': + if line[3:13] != "Exponents:": break angmom = angmom_sti(line[1:2]) exponents = [] @@ -154,16 +153,15 @@ def _read_cp2k_uncontracted_obasis(lit: LineIterator) -> MolecularBasis: coeffs.append(1.0 / _get_cp2k_norm_corrections(angmom, exponent)) line = next(lit) # Build the shell - kind = 'c' if angmom < 2 else 'p' + kind = "c" if angmom < 2 else "p" for exponent, coeff in zip(exponents, coeffs): - shells.append(Shell( - 0, np.array([angmom]), [kind], - np.array([exponent]), np.array([[coeff]]))) + shells.append( + Shell(0, np.array([angmom]), [kind], np.array([exponent]), np.array([[coeff]])) + ) - return MolecularBasis(shells, CONVENTIONS, 'L2') + return MolecularBasis(shells, CONVENTIONS, "L2") -# pylint: disable=inconsistent-return-statements def _read_cp2k_obasis(lit: LineIterator) -> dict: """Read atomic orbital basis set from a CP2K ATOM file object. @@ -181,17 +179,21 @@ def _read_cp2k_obasis(lit: LineIterator) -> dict: """ next(lit) # Skip empty line line = next(lit) # Check for contracted versus uncontracted - if line == (' ********************** Contracted Gaussian Type Orbitals ' - '**********************\n'): + if line == ( + " ********************** Contracted Gaussian Type Orbitals **********************\n" + ): return _read_cp2k_contracted_obasis(lit) - if line == (' ********************* Uncontracted Gaussian Type Orbitals ' - '*********************\n'): + if line == ( + " ********************* Uncontracted Gaussian Type Orbitals *********************\n" + ): return _read_cp2k_uncontracted_obasis(lit) - lit.error('Could not find basis set in CP2K ATOM output.') + lit.error("Could not find basis set in CP2K ATOM output.") + return None -def _read_cp2k_occupations_energies(lit: LineIterator, restricted: bool) \ - -> List[Tuple[int, int, float, float]]: +def _read_cp2k_occupations_energies( + lit: LineIterator, restricted: bool +) -> list[tuple[int, int, float, float]]: """Read orbital occupation numbers and energies from a CP2K ATOM file object. Parameters @@ -221,18 +223,19 @@ def _read_cp2k_occupations_energies(lit: LineIterator, restricted: bool) \ continue empty = 0 s = int(words[0]) - l = int(words[2 - restricted]) + ell = int(words[2 - restricted]) occ = float(words[3 - restricted]) ener = float(words[4 - restricted]) - if restricted or words[1] == 'alpha': - oe_alpha.append((l, s, occ, ener)) + if restricted or words[1] == "alpha": + oe_alpha.append((ell, s, occ, ener)) else: - oe_beta.append((l, s, occ, ener)) + oe_beta.append((ell, s, occ, ener)) return oe_alpha, oe_beta -def _read_cp2k_orbital_coeffs(lit: LineIterator, oe: List[Tuple[int, int, float, float]]) \ - -> Dict[Tuple[int, int], np.ndarray]: +def _read_cp2k_orbital_coeffs( + lit: LineIterator, oe: list[tuple[int, int, float, float]] +) -> dict[tuple[int, int], NDArray]: """Read the expansion coefficients of the orbital from an open CP2K ATOM output. Parameters @@ -266,7 +269,7 @@ def _read_cp2k_orbital_coeffs(lit: LineIterator, oe: List[Tuple[int, int, float, return allcoeffs -def _get_norb_nel(oe: List[Tuple[int, int, float, float]]) -> Tuple[int, float]: +def _get_norb_nel(oe: list[tuple[int, int, float, float]]) -> tuple[int, float]: """Return number of orbitals and electrons. Parameters @@ -289,13 +292,15 @@ def _get_norb_nel(oe: List[Tuple[int, int, float, float]]) -> Tuple[int, float]: return norb, nel -def _fill_orbitals(orb_coeffs: np.ndarray, - orb_energies: np.ndarray, - orb_occupations: np.ndarray, - oe: List[Tuple[int, int, float, float]], - coeffs: Dict[Tuple[int, int], np.ndarray], - obasis: MolecularBasis, - restricted: bool): +def _fill_orbitals( + orb_coeffs: NDArray, + orb_energies: NDArray, + orb_occupations: NDArray, + oe: list[tuple[int, int, float, float]], + coeffs: dict[tuple[int, int], NDArray], + obasis: MolecularBasis, + restricted: bool, +): """Fill in orbital coefficients, energies and occupation numbers. The data is entered int ``orb_coeffs``, ``orb_energies``, and ``orb_occupations``. @@ -323,21 +328,21 @@ def _fill_orbitals(orb_coeffs: np.ndarray, offset = 0 offsets = [] ls = np.concatenate([shell.angmoms for shell in obasis.shells]) - for l in sorted(set(ls)): + for ell in sorted(set(ls)): offsets.append(offset) - offset += (2 * l + 1) * (l == ls).sum() + offset += (2 * ell + 1) * (ell == ls).sum() del offset # Fill in the coefficients iorb = 0 - for l, state, occ, ener in oe: - cs = coeffs.get((l, state)) - stride = 2 * l + 1 - for im in range(2 * l + 1): + for ell, state, occ, ener in oe: + cs = coeffs.get((ell, state)) + stride = 2 * ell + 1 + for im in range(2 * ell + 1): orb_energies[iorb] = ener - orb_occupations[iorb] = occ / float((restricted + 1) * (2 * l + 1)) + orb_occupations[iorb] = occ / float((restricted + 1) * (2 * ell + 1)) for ic, c in enumerate(cs): - orb_coeffs[offsets[l] + stride * ic + im, iorb] = c + orb_coeffs[offsets[ell] + stride * ic + im, iorb] = c iorb += 1 @@ -360,84 +365,85 @@ def _fill_orbitals(orb_coeffs: np.ndarray, """ -# pylint: disable=too-many-branches,too-many-statements @document_load_one( "CP2K ATOM outupt", - ['atcoords', 'atcorenums', 'atnums', 'energy', 'mo', 'obasis'], - [], {}, LOAD_ONE_NOTES) + ["atcoords", "atcorenums", "atnums", "energy", "mo", "obasis"], + [], + {}, + LOAD_ONE_NOTES, +) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" # Find the element number atnum = None for line in lit: - if line.startswith(' Atomic Energy Calculation'): + if line.startswith(" Atomic Energy Calculation"): atnum = int(line[-5:-1]) break # Go to the all-electron basis set and read it. for line in lit: - if line.startswith(' All Electron Basis'): + if line.startswith(" All Electron Basis"): break ae_obasis = _read_cp2k_obasis(lit) # Go to the pseudo basis set and read it. for line in lit: - if line.startswith(' Pseudopotential Basis'): + if line.startswith(" Pseudopotential Basis"): break pp_obasis = _read_cp2k_obasis(lit) # Search for (un)restricted restricted = None for line in lit: - if line.startswith(' METHOD |'): - if 'U' in line: + if line.startswith(" METHOD |"): + if "U" in line: restricted = False break - if 'R' in line: + if "R" in line: restricted = True break # Search for the core charge (pseudo number) atcorenum = None for line in lit: - if line.startswith(' Core Charge'): + if line.startswith(" Core Charge"): atcorenum = float(line[70:]) assert atcorenum == int(atcorenum) break - if line.startswith(' Electronic structure'): + if line.startswith(" Electronic structure"): atcorenum = float(atnum) break # Select the correct basis - if atcorenum == atnum: - obasis = ae_obasis - else: - obasis = pp_obasis + obasis = ae_obasis if atcorenum == atnum else pp_obasis # Search for energy for line in lit: - if line.startswith(' Energy components [Hartree] Total Energy ::'): + if line.startswith(" Energy components [Hartree] Total Energy ::"): energy = float(line[60:]) break # Read orbital energies and occupations for line in lit: - if line.startswith(' Orbital energies'): + if line.startswith(" Orbital energies"): break next(lit) oe_alpha, oe_beta = _read_cp2k_occupations_energies(lit, restricted) # Read orbital expansion coefficients line = next(lit) - if line not in [" Atomic orbital expansion coefficients [Alpha]\n", - " Atomic orbital expansion coefficients []\n"]: - lit.error('Could not find orbital coefficients in CP2K ATOM output.') + if line not in [ + " Atomic orbital expansion coefficients [Alpha]\n", + " Atomic orbital expansion coefficients []\n", + ]: + lit.error("Could not find orbital coefficients in CP2K ATOM output.") coeffs_alpha = _read_cp2k_orbital_coeffs(lit, oe_alpha) if not restricted: line = next(lit) if line != " Atomic orbital expansion coefficients [Beta]\n": - lit.error('Could not find beta orbital coefficient in CP2K ATOM output.') + lit.error("Could not find beta orbital coefficient in CP2K ATOM output.") coeffs_beta = _read_cp2k_orbital_coeffs(lit, oe_beta) # Turn orbital data into a MolecularOrbitals object. @@ -447,11 +453,18 @@ def load_one(lit: LineIterator) -> dict: orb_alpha_coeffs = np.zeros([obasis.nbasis, norb]) orb_alpha_energies = np.zeros(norb) orb_alpha_occs = np.zeros(norb) - _fill_orbitals(orb_alpha_coeffs, orb_alpha_energies, orb_alpha_occs, - oe_alpha, coeffs_alpha, obasis, restricted) + _fill_orbitals( + orb_alpha_coeffs, + orb_alpha_energies, + orb_alpha_occs, + oe_alpha, + coeffs_alpha, + obasis, + restricted, + ) mo = MolecularOrbitals( - 'restricted', norb, norb, 2 * orb_alpha_occs, orb_alpha_coeffs, - orb_alpha_energies) + "restricted", norb, norb, 2 * orb_alpha_occs, orb_alpha_coeffs, orb_alpha_energies + ) else: norb_alpha = _get_norb_nel(oe_alpha)[0] norb_beta = _get_norb_nel(oe_beta)[0] @@ -462,24 +475,39 @@ def load_one(lit: LineIterator) -> dict: orb_beta_coeffs = np.zeros([obasis.nbasis, norb_beta]) orb_beta_energies = np.zeros(norb_beta) orb_beta_occs = np.zeros(norb_beta) - _fill_orbitals(orb_alpha_coeffs, orb_alpha_energies, orb_alpha_occs, - oe_alpha, coeffs_alpha, obasis, restricted) - _fill_orbitals(orb_beta_coeffs, orb_beta_energies, orb_beta_occs, - oe_beta, coeffs_beta, obasis, restricted) + _fill_orbitals( + orb_alpha_coeffs, + orb_alpha_energies, + orb_alpha_occs, + oe_alpha, + coeffs_alpha, + obasis, + restricted, + ) + _fill_orbitals( + orb_beta_coeffs, + orb_beta_energies, + orb_beta_occs, + oe_beta, + coeffs_beta, + obasis, + restricted, + ) mo = MolecularOrbitals( - 'unrestricted', norb_alpha, norb_beta, + "unrestricted", + norb_alpha, + norb_beta, np.concatenate((orb_alpha_occs, orb_beta_occs), axis=0), np.concatenate((orb_alpha_coeffs, orb_beta_coeffs), axis=1), np.concatenate((orb_alpha_energies, orb_beta_energies), axis=0), ) - result = { - 'obasis': obasis, - 'mo': mo, - 'atcoords': np.zeros((1, 3), float), - 'atnums': np.array([atnum]), - 'energy': energy, - 'atcorenums': np.array([atcorenum]), + return { + "obasis": obasis, + "mo": mo, + "atcoords": np.zeros((1, 3), float), + "atnums": np.array([atnum]), + "energy": energy, + "atcorenums": np.array([atcorenum]), } - return result diff --git a/iodata/formats/cube.py b/iodata/formats/cube.py index e247686c1..df190970b 100644 --- a/iodata/formats/cube.py +++ b/iodata/formats/cube.py @@ -26,24 +26,24 @@ as the effective core charges. """ - -from typing import TextIO, Dict, Tuple +from typing import TextIO import numpy as np +from numpy.typing import NDArray +from ..docstrings import document_dump_one, document_load_one from ..iodata import IOData -from ..docstrings import document_load_one, document_dump_one -from ..utils import LineIterator, Cube - +from ..utils import Cube, LineIterator __all__ = [] -PATTERNS = ['*.cube', '*.cub'] +PATTERNS = ["*.cube", "*.cub"] -def _read_cube_header(lit: LineIterator) \ - -> Tuple[str, np.ndarray, np.ndarray, np.ndarray, Dict[str, np.ndarray], np.ndarray]: +def _read_cube_header( + lit: LineIterator, +) -> tuple[str, NDArray, NDArray, NDArray, dict[str, NDArray], NDArray]: """Load header data from a CUBE file object. Parameters @@ -63,12 +63,12 @@ def _read_cube_header(lit: LineIterator) \ # skip the second line next(lit) - def read_grid_line(line: str) -> Tuple[int, np.ndarray]: + def read_grid_line(line: str) -> tuple[int, NDArray]: """Read a grid line from the cube file.""" words = line.split() return ( int(words[0]), - np.array([float(words[1]), float(words[2]), float(words[3])], float) + np.array([float(words[1]), float(words[2]), float(words[3])], float), # all coordinates in a cube file are in atomic units ) @@ -82,14 +82,15 @@ def read_grid_line(line: str) -> Tuple[int, np.ndarray]: axes = np.array([axis0, axis1, axis2]) cellvecs = axes * shape.reshape(-1, 1) - cube = {"origin": origin, 'axes': axes, 'shape': shape} + cube = {"origin": origin, "axes": axes, "shape": shape} - def read_atom_line(line: str) -> Tuple[int, float, np.ndarray]: + def read_atom_line(line: str) -> tuple[int, float, NDArray]: """Read an atomic number and coordinate from the cube file.""" words = line.split() return ( - int(words[0]), float(words[1]), - np.array([float(words[2]), float(words[3]), float(words[4])], float) + int(words[0]), + float(words[1]), + np.array([float(words[2]), float(words[3]), float(words[4])], float), # all coordinates in a cube file are in atomic units ) @@ -106,7 +107,7 @@ def read_atom_line(line: str) -> Tuple[int, float, np.ndarray]: return title, atcoords, atnums, cellvecs, cube, atcorenums -def _read_cube_data(lit: LineIterator, cube: Dict[str, np.ndarray]): +def _read_cube_data(lit: LineIterator, cube: dict[str, NDArray]): """Load cube data from a CUBE file object. Parameters @@ -120,8 +121,8 @@ def _read_cube_data(lit: LineIterator, cube: Dict[str, np.ndarray]): The cube data array. """ - cube['data'] = np.zeros(tuple(cube['shape']), float) - tmp = cube['data'].ravel() + cube["data"] = np.zeros(tuple(cube["shape"]), float) + tmp = cube["data"].ravel() counter = 0 words = [] while counter < tmp.size: @@ -131,56 +132,62 @@ def _read_cube_data(lit: LineIterator, cube: Dict[str, np.ndarray]): counter += 1 -@document_load_one("Gaussian Cube", ['atcoords', 'atcorenums', 'atnums', 'cellvecs', 'cube']) +@document_load_one("Gaussian Cube", ["atcoords", "atcorenums", "atnums", "cellvecs", "cube"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" title, atcoords, atnums, cellvecs, cube, atcorenums = _read_cube_header(lit) _read_cube_data(lit, cube) del cube["shape"] return { - 'title': title, - 'atcoords': atcoords, - 'atnums': atnums, - 'cellvecs': cellvecs, - 'cube': Cube(**cube), - 'atcorenums': atcorenums, + "title": title, + "atcoords": atcoords, + "atnums": atnums, + "cellvecs": cellvecs, + "cube": Cube(**cube), + "atcorenums": atcorenums, } -def _write_cube_header(f: TextIO, title: str, atcoords: np.ndarray, atnums: np.ndarray, - cube: Dict[str, np.ndarray], atcorenums: np.ndarray): +def _write_cube_header( + f: TextIO, + title: str, + atcoords: NDArray, + atnums: NDArray, + cube: dict[str, NDArray], + atcorenums: NDArray, +): print(title, file=f) - print('OUTER LOOP: X, MIDDLE LOOP: Y, INNER LOOP: Z', file=f) + print("OUTER LOOP: X, MIDDLE LOOP: Y, INNER LOOP: Z", file=f) natom = len(atnums) x, y, z = cube.origin - print(f'{natom:5d} {x: 11.6f} {y: 11.6f} {z: 11.6f}', file=f) + print(f"{natom:5d} {x: 11.6f} {y: 11.6f} {z: 11.6f}", file=f) for i in range(3): x, y, z = cube.axes[i] - print(f'{cube.shape[i]:5d} {x: 11.6f} {y: 11.6f} {z: 11.6f}', file=f) + print(f"{cube.shape[i]:5d} {x: 11.6f} {y: 11.6f} {z: 11.6f}", file=f) for i in range(natom): q = atcorenums[i] x, y, z = atcoords[i] - print(f'{atnums[i]:5d} {q: 11.6f} {x: 11.6f} {y: 11.6f} {z: 11.6f}', file=f) + print(f"{atnums[i]:5d} {q: 11.6f} {x: 11.6f} {y: 11.6f} {z: 11.6f}", file=f) -def _write_cube_data(f: TextIO, cube_data: np.ndarray, block_size: int): +def _write_cube_data(f: TextIO, cube_data: NDArray, block_size: int): counter = 0 for value in cube_data.flat: - f.write(f' {value: 12.5E}') + f.write(f" {value: 12.5E}") # go to next line after adding 6 values on a line if counter % 6 == 5: - f.write('\n') + f.write("\n") # go to next line after reaching the block_size & reset counter if block_size % 6 != 0 and counter % block_size == block_size - 1: - f.write('\n') + f.write("\n") counter = 0 continue counter += 1 -@document_dump_one("Gaussian Cube", ['atcoords', 'atnums', 'cube'], ['title', 'atcorenums']) +@document_dump_one("Gaussian Cube", ["atcoords", "atnums", "cube"], ["title", "atcorenums"]) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" - title = data.title or 'Created with IOData' + title = data.title or "Created with IOData" _write_cube_header(f, title, data.atcoords, data.atnums, data.cube, data.atcorenums) _write_cube_data(f, data.cube.data, data.cube.shape[2]) diff --git a/iodata/formats/extxyz.py b/iodata/formats/extxyz.py index 40dcb2088..ca3546b98 100644 --- a/iodata/formats/extxyz.py +++ b/iodata/formats/extxyz.py @@ -27,23 +27,20 @@ """ -from distutils.util import strtobool import shlex -from typing import Iterator +from collections.abc import Iterator import numpy as np -from ..docstrings import document_load_one, document_load_many -from ..periodic import sym2num, num2sym -from ..utils import angstrom, amu, LineIterator - +from ..docstrings import document_load_many, document_load_one +from ..periodic import num2sym, sym2num +from ..utils import LineIterator, amu, angstrom, strtobool from .xyz import load_one as load_one_xyz - __all__ = [] -PATTERNS = ['*.extxyz'] +PATTERNS = ["*.extxyz"] def _convert_title_value(value: str): @@ -65,16 +62,17 @@ def _convert_title_value(value: str): else: # Do the same but return it as a numpy array try: - converted_value = np.array(list_of_splits, dtype=np.int) + converted_value = np.array(list_of_splits, dtype=int) except ValueError: try: - converted_value = np.array(list_of_splits, dtype=np.float) + converted_value = np.array(list_of_splits, dtype=float) except ValueError: try: - converted_value = np.array([strtobool(split) for split in list_of_splits], - dtype=np.bool) + converted_value = np.array( + [strtobool(split) for split in list_of_splits], dtype=bool + ) except ValueError: - converted_value = np.array(list_of_splits, dtype=np.str) + converted_value = np.array(list_of_splits, dtype=str) return converted_value @@ -82,47 +80,68 @@ def _parse_properties(properties: str): """Parse the properties into atom_columns.""" atom_columns = [] # Maps the dtype to the atom_columns dtype, load_word and dump_word - dtype_map = {"S": (np.dtype('U25'), str, "{:10s}".format), - "R": (float, float, "{:15.10f}".format), - "I": (int, int, "{:10d}".format), - "L": (bool, strtobool, lambda boolean: "T" if boolean else "F")} + dtype_map = { + "S": (np.dtype("U25"), str, "{:10s}".format), + "R": (float, float, "{:15.10f}".format), + "I": (int, int, "{:10d}".format), + "L": (bool, strtobool, lambda boolean: "T" if boolean else "F"), + } # Some predefined iodata attributes which can be mapped # pos is assumed to be in angstrom, masses in amu (ase convention) # No unit convertion takes place for the other attributes - atom_column_map = {'pos': ('atcoords', None, (3,), float, - (lambda word: float(word) * angstrom), - (lambda value: "{:15.10f}".format(value / angstrom))), - 'masses': ('atmasses', None, (), float, - (lambda word: float(word) * amu), - (lambda value: "{:15.10f}".format(value / amu))), - 'force': ('atgradient', None, (3,), float, - (lambda word: -float(word)), - (lambda value: "{:15.10f}".format(-value)))} - atnum_column = ("atnums", None, (), int, - (lambda word: int(word) if word.isdigit() else sym2num[word.title()]), - (lambda atnum: "{:2s}".format(num2sym[atnum]))) - splitted_properties = properties.split(':') + atom_column_map = { + "pos": ( + "atcoords", + None, + (3,), + float, + (lambda word: float(word) * angstrom), + (lambda value: f"{value / angstrom:15.10f}"), + ), + "masses": ( + "atmasses", + None, + (), + float, + (lambda word: float(word) * amu), + (lambda value: f"{value / amu:15.10f}"), + ), + "force": ( + "atgradient", + None, + (3,), + float, + (lambda word: -float(word)), + (lambda value: f"{-value:15.10f}"), + ), + } + atnum_column = ( + "atnums", + None, + (), + int, + (lambda word: int(word) if word.isdigit() else sym2num[word.title()]), + (lambda atnum: f"{num2sym[atnum]:2s}"), + ) + splitted_properties = properties.split(":") assert len(splitted_properties) % 3 == 0 # Each property has 3 values: its name, dtype and shape names = splitted_properties[::3] dtypes = splitted_properties[1::3] shapes = splitted_properties[2::3] - if 'Z' in names: + if "Z" in names: # Try to map 'Z' to the 'atnums' attribute - atom_column_map['Z'] = atnum_column - elif 'species' in names: + atom_column_map["Z"] = atnum_column + elif "species" in names: # If 'Z' is not present, use 'species' - atom_column_map['species'] = atnum_column + atom_column_map["species"] = atnum_column for name, dtype, shape in zip(names, dtypes, shapes): - if name in atom_column_map.keys(): + if name in atom_column_map: atom_columns.append(atom_column_map[name]) else: # Use the 'extra' attribute to store values which are not predefined in iodata - if shape == '1': - shape_suffix = () - else: - shape_suffix = (int(shape),) - atom_columns.append(('extra', name, shape_suffix, *dtype_map[dtype])) + shape_suffix = () if shape == "1" else (int(shape),) + atom_columns.append(("extra", name, shape_suffix, *dtype_map[dtype])) return atom_columns @@ -132,29 +151,34 @@ def _parse_title(title: str): # A dict of predefined iodata atrributes with their names and dtype convertion functions def load_cellvecs(word): - return np.array(word.split(), dtype=np.float).reshape([3, 3]) * angstrom - iodata_attrs = {'energy': ('energy', float), - 'Lattice': ('cellvecs', load_cellvecs), - 'charge': ('charge', float)} + return np.array(word.split(), dtype=float).reshape([3, 3]) * angstrom + + iodata_attrs = { + "energy": ("energy", float), + "Lattice": ("cellvecs", load_cellvecs), + "charge": ("charge", float), + } data = {} for key_value_pair in key_value_pairs: - if '=' in key_value_pair: - key, value = key_value_pair.split('=', 1) - if key == 'Properties': + if "=" in key_value_pair: + key, value = key_value_pair.split("=", 1) + if key == "Properties": atom_columns = _parse_properties(value) - elif key in iodata_attrs.keys(): + elif key in iodata_attrs: data[iodata_attrs[key][0]] = iodata_attrs[key][1](value) else: - data.setdefault('extra', {})[key] = _convert_title_value(value) + data.setdefault("extra", {})[key] = _convert_title_value(value) else: # If no value is given, set it True - data.setdefault('extra', {})[key_value_pair] = True + data.setdefault("extra", {})[key_value_pair] = True return atom_columns, data -@document_load_one("EXTXYZ", ['title'], - ['atcoords', 'atgradient', 'atmasses', 'atnums', 'cellvecs', - 'charge', 'energy', 'extra']) +@document_load_one( + "EXTXYZ", + ["title"], + ["atcoords", "atgradient", "atmasses", "atnums", "cellvecs", "charge", "energy", "extra"], +) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" atom_line = next(lit) @@ -165,26 +189,28 @@ def load_one(lit: LineIterator) -> dict: lit.back(atom_line) xyz_data = load_one_xyz(lit, atom_columns) # If the extra attribute is present, prevent it from overwriting itself - if 'extra' in title_data.keys() and 'extra' in xyz_data.keys(): - xyz_data['extra'].update(title_data['extra']) + if "extra" in title_data and "extra" in xyz_data: + xyz_data["extra"].update(title_data["extra"]) title_data.update(xyz_data) return title_data -@document_load_many("EXTXYZ", ['title'], - ['atcoords', 'atgradient', 'atmasses', 'atnums', 'cellvecs', - 'charge', 'energy', 'extra']) +@document_load_many( + "EXTXYZ", + ["title"], + ["atcoords", "atgradient", "atmasses", "atnums", "cellvecs", "charge", "energy", "extra"], +) def load_many(lit: LineIterator) -> Iterator[dict]: """Do not edit this docstring. It will be overwritten.""" # XYZ Trajectory files are a simple concatenation of individual XYZ files,' # making it trivial to load many frames. - while True: - try: + try: + while True: # Check for and skip empty lines at the end of file line = next(lit) if line.strip() == "": return lit.back(line) yield load_one(lit) - except StopIteration: - return + except StopIteration: + return diff --git a/iodata/formats/fchk.py b/iodata/formats/fchk.py index 0a6dd70a9..62f62b00f 100644 --- a/iodata/formats/fchk.py +++ b/iodata/formats/fchk.py @@ -18,105 +18,141 @@ # -- """Gaussian FCHK file format.""" - +from collections.abc import Iterator from fnmatch import fnmatch -from typing import List, Tuple, Iterator, TextIO +from typing import Optional, TextIO import numpy as np +from numpy.typing import NDArray +from ..basis import HORTON2_CONVENTIONS, MolecularBasis, Shell, convert_conventions +from ..docstrings import document_dump_one, document_load_many, document_load_one from ..iodata import IOData -from ..basis import MolecularBasis, Shell, HORTON2_CONVENTIONS, convert_conventions -from ..docstrings import document_load_one, document_load_many -from ..docstrings import document_dump_one from ..orbitals import MolecularOrbitals from ..utils import LineIterator, amu - __all__ = [] -PATTERNS = ['*.fchk', '*.fch'] +PATTERNS = ["*.fchk", "*.fch"] CONVENTIONS = { - (9, 'p'): HORTON2_CONVENTIONS[(9, 'p')], - (8, 'p'): HORTON2_CONVENTIONS[(8, 'p')], - (7, 'p'): HORTON2_CONVENTIONS[(7, 'p')], - (6, 'p'): HORTON2_CONVENTIONS[(6, 'p')], - (5, 'p'): HORTON2_CONVENTIONS[(5, 'p')], - (4, 'p'): HORTON2_CONVENTIONS[(4, 'p')], - (3, 'p'): HORTON2_CONVENTIONS[(3, 'p')], - (2, 'p'): HORTON2_CONVENTIONS[(2, 'p')], - (0, 'c'): ['1'], - (1, 'c'): ['x', 'y', 'z'], - (2, 'c'): ['xx', 'yy', 'zz', 'xy', 'xz', 'yz'], - (3, 'c'): ['xxx', 'yyy', 'zzz', 'xyy', 'xxy', 'xxz', 'xzz', 'yzz', 'yyz', 'xyz'], - (4, 'c'): HORTON2_CONVENTIONS[(4, 'c')][::-1], - (5, 'c'): HORTON2_CONVENTIONS[(5, 'c')][::-1], - (6, 'c'): HORTON2_CONVENTIONS[(6, 'c')][::-1], - (7, 'c'): HORTON2_CONVENTIONS[(7, 'c')][::-1], - (8, 'c'): HORTON2_CONVENTIONS[(8, 'c')][::-1], - (9, 'c'): HORTON2_CONVENTIONS[(9, 'c')][::-1], + (9, "p"): HORTON2_CONVENTIONS[(9, "p")], + (8, "p"): HORTON2_CONVENTIONS[(8, "p")], + (7, "p"): HORTON2_CONVENTIONS[(7, "p")], + (6, "p"): HORTON2_CONVENTIONS[(6, "p")], + (5, "p"): HORTON2_CONVENTIONS[(5, "p")], + (4, "p"): HORTON2_CONVENTIONS[(4, "p")], + (3, "p"): HORTON2_CONVENTIONS[(3, "p")], + (2, "p"): HORTON2_CONVENTIONS[(2, "p")], + (0, "c"): ["1"], + (1, "c"): ["x", "y", "z"], + (2, "c"): ["xx", "yy", "zz", "xy", "xz", "yz"], + (3, "c"): ["xxx", "yyy", "zzz", "xyy", "xxy", "xxz", "xzz", "yzz", "yyz", "xyz"], + (4, "c"): HORTON2_CONVENTIONS[(4, "c")][::-1], + (5, "c"): HORTON2_CONVENTIONS[(5, "c")][::-1], + (6, "c"): HORTON2_CONVENTIONS[(6, "c")][::-1], + (7, "c"): HORTON2_CONVENTIONS[(7, "c")][::-1], + (8, "c"): HORTON2_CONVENTIONS[(8, "c")][::-1], + (9, "c"): HORTON2_CONVENTIONS[(9, "c")][::-1], } -# pylint: disable=too-many-branches,too-many-statements @document_load_one( "Gaussian Formatted Checkpoint", - ['atcharges', 'atcoords', 'atnums', 'atcorenums', 'lot', 'mo', 'obasis', - 'obasis_name', 'run_type', 'title'], - ['energy', 'atfrozen', 'atgradient', 'athessian', 'atmasses', 'one_rdms', 'extra', 'moments']) + [ + "atcharges", + "atcoords", + "atnums", + "atcorenums", + "lot", + "mo", + "obasis", + "obasis_name", + "run_type", + "title", + ], + ["energy", "atfrozen", "atgradient", "athessian", "atmasses", "one_rdms", "extra", "moments"], +) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" - fchk = _load_fchk_low(lit, [ - "Number of electrons", "Number of basis functions", - "Number of alpha electrons", "Number of beta electrons", - "Atomic numbers", "Current cartesian coordinates", - "Real atomic weights", - "Shell types", "Shell to atom map", "Shell to atom map", - "Number of primitives per shell", "Primitive exponents", - "Contraction coefficients", "P(S=P) Contraction coefficients", - "Alpha Orbital Energies", "Alpha MO coefficients", - "Beta Orbital Energies", "Beta MO coefficients", - "Total Energy", "Nuclear charges", - 'Total SCF Density', 'Spin SCF Density', - 'Total MP2 Density', 'Spin MP2 Density', - 'Total MP3 Density', 'Spin MP3 Density', - 'Total CC Density', 'Spin CC Density', - 'Total CI Density', 'Spin CI Density', - 'Mulliken Charges', 'ESP Charges', 'NPA Charges', - 'Polarizability', 'Dipole Moment', 'Quadrupole Moment', - 'Cartesian Gradient', 'Cartesian Force Constants', 'MicOpt', - ]) + fchk = _load_fchk_low( + lit, + [ + "Number of electrons", + "Number of basis functions", + "Number of alpha electrons", + "Number of beta electrons", + "Atomic numbers", + "Current cartesian coordinates", + "Real atomic weights", + "Shell types", + "Shell to atom map", + "Shell to atom map", + "Number of primitives per shell", + "Primitive exponents", + "Contraction coefficients", + "P(S=P) Contraction coefficients", + "Alpha Orbital Energies", + "Alpha MO coefficients", + "Beta Orbital Energies", + "Beta MO coefficients", + "Total Energy", + "Nuclear charges", + "Total SCF Density", + "Spin SCF Density", + "Total MP2 Density", + "Spin MP2 Density", + "Total MP3 Density", + "Spin MP3 Density", + "Total CC Density", + "Spin CC Density", + "Total CI Density", + "Spin CI Density", + "Mulliken Charges", + "ESP Charges", + "NPA Charges", + "MBS Charges", + "Type 6 Charges", + "Type 7 Charges", + "Polarizability", + "Dipole Moment", + "Quadrupole Moment", + "Cartesian Gradient", + "Cartesian Force Constants", + "MicOpt", + ], + ) # A) Load a bunch of simple things result = { - 'title': fchk['title'], + "title": fchk["title"], # if "Total Energy" is not present in FCHk, None is returned. - 'energy': fchk.get('Total Energy', None), - 'lot': fchk['lot'].lower(), - 'obasis_name': fchk['obasis_name'].lower(), - 'atcoords': fchk["Current cartesian coordinates"].reshape(-1, 3), - 'atnums': fchk["Atomic numbers"], - 'atcorenums': fchk["Nuclear charges"], + "energy": fchk.get("Total Energy", None), + "lot": fchk["lot"].lower(), + "obasis_name": fchk["obasis_name"].lower(), + "atcoords": fchk["Current cartesian coordinates"].reshape(-1, 3), + "atnums": fchk["Atomic numbers"], + "atcorenums": fchk["Nuclear charges"], } atmasses = fchk.get("Real atomic weights") if atmasses is not None: - result['atmasses'] = atmasses * amu - atgradient = fchk.get('Cartesian Gradient') + result["atmasses"] = atmasses * amu + atgradient = fchk.get("Cartesian Gradient") if atgradient is not None: - result['atgradient'] = atgradient.reshape(-1, 3) - athessian = fchk.get('Cartesian Force Constants') + result["atgradient"] = atgradient.reshape(-1, 3) + athessian = fchk.get("Cartesian Force Constants") if athessian is not None: - result['athessian'] = _triangle_to_dense(athessian) + result["athessian"] = _triangle_to_dense(athessian) atfrozen = fchk.get("MicOpt") if atfrozen is not None: - result['atfrozen'] = (atfrozen == -2) - run_types = {'SP': 'energy', 'FOpt': 'opt', 'Scan': 'scan', 'Freq': 'freq'} - run_type = run_types.get(fchk['command']) + result["atfrozen"] = atfrozen == -2 + run_types = {"SP": "energy", "FOpt": "opt", "Scan": "scan", "Freq": "freq"} + run_type = run_types.get(fchk["command"]) if run_type is not None: - result['run_type'] = run_type + result["run_type"] = run_type # B) Load the orbital basis set shell_types = fchk["Shell types"] @@ -132,98 +168,112 @@ def load_one(lit: LineIterator) -> dict: for i, n in enumerate(nprims): if shell_types[i] == -1: # Special treatment for SP shell type - shells.append(Shell( - shell_map[i], - [0, 1], - ['c', 'c'], - exponents[counter:counter + n], - np.stack([ccoeffs_level1[counter:counter + n], - ccoeffs_level2[counter:counter + n]], axis=1) - )) + shells.append( + Shell( + shell_map[i], + [0, 1], + ["c", "c"], + exponents[counter : counter + n], + np.stack( + [ + ccoeffs_level1[counter : counter + n], + ccoeffs_level2[counter : counter + n], + ], + axis=1, + ), + ) + ) else: - shells.append(Shell( - shell_map[i], - [abs(shell_types[i])], - ['p' if shell_types[i] < 0 else 'c'], - exponents[counter:counter + n], - ccoeffs_level1[counter:counter + n][:, np.newaxis] - )) + shells.append( + Shell( + shell_map[i], + [abs(shell_types[i])], + ["p" if shell_types[i] < 0 else "c"], + exponents[counter : counter + n], + ccoeffs_level1[counter : counter + n][:, np.newaxis], + ) + ) counter += n del shell_map del shell_types del nprims del exponents - result['obasis'] = MolecularBasis(shells, CONVENTIONS, 'L2') + result["obasis"] = MolecularBasis(shells, CONVENTIONS, "L2") nbasis = fchk["Number of basis functions"] # C) Load density matrices one_rdms = {} - _load_dm('Total SCF Density', fchk, one_rdms, 'scf') - _load_dm('Spin SCF Density', fchk, one_rdms, 'scf_spin') + _load_dm("Total SCF Density", fchk, one_rdms, "scf") + _load_dm("Spin SCF Density", fchk, one_rdms, "scf_spin") # only one of the lots should be present, hence using the same key - for lot in 'MP2', 'MP3', 'CC', 'CI': - _load_dm('Total {} Density'.format(lot), fchk, one_rdms, 'post_scf') - _load_dm('Spin {} Density'.format(lot), fchk, one_rdms, 'post_scf_spin') + for lot in "MP2", "MP3", "CC", "CI": + _load_dm(f"Total {lot} Density", fchk, one_rdms, "post_scf_ao") + _load_dm(f"Spin {lot} Density", fchk, one_rdms, "post_scf_spin_ao") if one_rdms: - result['one_rdms'] = one_rdms + result["one_rdms"] = one_rdms # D) Load the wavefunction # Load orbitals - nalpha = fchk['Number of alpha electrons'] - nbeta = fchk['Number of beta electrons'] + nalpha = fchk["Number of alpha electrons"] + nbeta = fchk["Number of beta electrons"] if nalpha < 0 or nbeta < 0 or nalpha + nbeta <= 0: - lit.error('The number of electrons is not positive.') + lit.error("The number of electrons is not positive.") if nalpha < nbeta: - raise ValueError('n_alpha={0} < n_beta={1} is not valid!'.format(nalpha, nbeta)) + raise ValueError(f"n_alpha={nalpha} < n_beta={nbeta} is not valid!") - norba = fchk['Alpha Orbital Energies'].shape[0] - mo_coeffs = np.copy(fchk['Alpha MO coefficients'].reshape(norba, nbasis).T) - mo_energies = np.copy(fchk['Alpha Orbital Energies']) + norba = fchk["Alpha Orbital Energies"].shape[0] + mo_coeffs = np.copy(fchk["Alpha MO coefficients"].reshape(norba, nbasis).T) + mo_energies = np.copy(fchk["Alpha Orbital Energies"]) - if 'Beta Orbital Energies' in fchk: + if "Beta Orbital Energies" in fchk: # unrestricted - norbb = fchk['Beta Orbital Energies'].shape[0] - mo_coeffs_b = np.copy(fchk['Beta MO coefficients'].reshape(norbb, nbasis).T) + norbb = fchk["Beta Orbital Energies"].shape[0] + mo_coeffs_b = np.copy(fchk["Beta MO coefficients"].reshape(norbb, nbasis).T) mo_coeffs = np.concatenate((mo_coeffs, mo_coeffs_b), axis=1) - mo_energies = np.concatenate((mo_energies, np.copy(fchk['Beta Orbital Energies'])), axis=0) + mo_energies = np.concatenate((mo_energies, np.copy(fchk["Beta Orbital Energies"])), axis=0) mo_occs = np.zeros(norba + norbb) mo_occs[:nalpha] = 1.0 - mo_occs[norba: norba + nbeta] = 1.0 - mo = MolecularOrbitals('unrestricted', norba, norbb, mo_occs, mo_coeffs, mo_energies) + mo_occs[norba : norba + nbeta] = 1.0 + mo = MolecularOrbitals("unrestricted", norba, norbb, mo_occs, mo_coeffs, mo_energies) else: # restricted closed-shell and open-shell mo_occs = np.zeros(norba) mo_occs[:nalpha] = 1.0 mo_occs[:nbeta] = 2.0 - if nalpha != nbeta and 'one_rdms' in result: - # delete dm_full_scf because it is known to be buggy - if 'scf' in result['one_rdms']: - result['one_rdms'].pop('scf') - mo = MolecularOrbitals('restricted', norba, norba, mo_occs, mo_coeffs, mo_energies) - result['mo'] = mo + # delete dm_full_scf because it is known to be buggy + if nalpha != nbeta and "one_rdms" in result and "scf" in result["one_rdms"]: + result["one_rdms"].pop("scf") + mo = MolecularOrbitals("restricted", norba, norba, mo_occs, mo_coeffs, mo_energies) + result["mo"] = mo # E) Load properties - if 'Polarizability' in fchk: - result['extra'] = {'polarizability_tensor': _triangle_to_dense(fchk['Polarizability'])} + if "Polarizability" in fchk: + result["extra"] = {"polarizability_tensor": _triangle_to_dense(fchk["Polarizability"])} moments = {} - if 'Dipole Moment' in fchk: - moments[(1, 'c')] = fchk['Dipole Moment'] - if 'Quadrupole Moment' in fchk: + if "Dipole Moment" in fchk: + moments[(1, "c")] = fchk["Dipole Moment"] + if "Quadrupole Moment" in fchk: # Convert to alphabetical ordering: xx, xy, xz, yy, yz, zz - moments[(2, 'c')] = fchk['Quadrupole Moment'][[0, 3, 4, 1, 5, 2]] + moments[(2, "c")] = fchk["Quadrupole Moment"][[0, 3, 4, 1, 5, 2]] if moments: - result['moments'] = moments + result["moments"] = moments atcharges = {} - if 'Mulliken Charges' in fchk: - atcharges['mulliken'] = fchk['Mulliken Charges'] - if 'ESP Charges' in fchk: - atcharges['esp'] = fchk['ESP Charges'] - if 'NPA Charges' in fchk: - atcharges['npa'] = fchk['NPA Charges'] + if "Mulliken Charges" in fchk: + atcharges["mulliken"] = fchk["Mulliken Charges"] + if "ESP Charges" in fchk: + atcharges["esp"] = fchk["ESP Charges"] + if "NPA Charges" in fchk: + atcharges["npa"] = fchk["NPA Charges"] + if "MBS Charges" in fchk: + atcharges["mbs"] = fchk["MBS Charges"] + if "Type 6 Charges" in fchk: + atcharges["hirshfeld"] = fchk["Type 6 Charges"] + if "Type 7 Charges" in fchk: + atcharges["cm5"] = fchk["Type 7 Charges"] if atcharges: - result['atcharges'] = atcharges + result["atcharges"] = atcharges return result @@ -244,13 +294,26 @@ def load_one(lit: LineIterator) -> dict: """ -@document_load_many("XYZ", ['atcoords', 'atgradient', 'atnums', 'atcorenums', - 'energy', 'extra', 'title'], [], {}, LOAD_MANY_NOTES) +@document_load_many( + "XYZ", + ["atcoords", "atgradient", "atnums", "atcorenums", "energy", "extra", "title"], + [], + {}, + LOAD_MANY_NOTES, +) def load_many(lit: LineIterator) -> Iterator[dict]: """Do not edit this docstring. It will be overwritten.""" - fchk = _load_fchk_low(lit, [ - "Atomic numbers", "Current cartesian coordinates", "Nuclear charges", - "IRC *", "Optimization *", "Opt point *"]) + fchk = _load_fchk_low( + lit, + [ + "Atomic numbers", + "Current cartesian coordinates", + "Nuclear charges", + "IRC *", + "Optimization *", + "Opt point *", + ], + ) # Determine the type of calculation: IRC or Optimization if "IRC Number of geometries" in fchk: @@ -264,34 +327,37 @@ def load_many(lit: LineIterator) -> Iterator[dict]: natom = fchk["Atomic numbers"].size for ipoint, nstep in enumerate(nsteps): - results_geoms = fchk["{} {:7d} Results for each geome".format(prefix, ipoint + 1)] - trajectory = list(zip( - results_geoms[::2], results_geoms[1::2], - fchk["{} {:7d} Geometries".format(prefix, ipoint + 1)].reshape(-1, natom, 3), - fchk["{} {:7d} Gradient at each geome".format(prefix, ipoint + 1)].reshape(-1, natom, 3) - )) + results_geoms = fchk[f"{prefix} {ipoint + 1:7d} Results for each geome"] + trajectory = list( + zip( + results_geoms[::2], + results_geoms[1::2], + fchk[f"{prefix} {ipoint + 1:7d} Geometries"].reshape(-1, natom, 3), + fchk[f"{prefix} {ipoint + 1:7d} Gradient at each geome"].reshape(-1, natom, 3), + ) + ) assert len(trajectory) == nstep for istep, (energy, recor, atcoords, gradients) in enumerate(trajectory): data = { - 'title': fchk['title'], - 'atnums': fchk["Atomic numbers"], - 'atcorenums': fchk["Nuclear charges"], - 'energy': energy, - 'atcoords': atcoords, - 'atgradient': gradients, - 'extra': { - 'ipoint': ipoint, - 'npoint': len(nsteps), - 'istep': istep, - 'nstep': nstep, + "title": fchk["title"], + "atnums": fchk["Atomic numbers"], + "atcorenums": fchk["Nuclear charges"], + "energy": energy, + "atcoords": atcoords, + "atgradient": gradients, + "extra": { + "ipoint": ipoint, + "npoint": len(nsteps), + "istep": istep, + "nstep": nstep, }, } if prefix == "IRC point": - data['extra']['reaction_coordinate'] = recor + data["extra"]["reaction_coordinate"] = recor yield data -def _load_fchk_low(lit: LineIterator, label_patterns: List[str] = None) -> dict: +def _load_fchk_low(lit: LineIterator, label_patterns: Optional[list[str]] = None) -> dict: """Read selected fields from a formatted checkpoint file. Parameters @@ -309,14 +375,14 @@ def _load_fchk_low(lit: LineIterator, label_patterns: List[str] = None) -> dict: """ # Read the two-line header - result = {'title': next(lit).strip()} + result = {"title": next(lit).strip()} words = next(lit).split() if len(words) == 3: - result['command'], result['lot'], result['obasis_name'] = words + result["command"], result["lot"], result["obasis_name"] = words elif len(words) == 2: - result['command'], result['lot'] = words + result["command"], result["lot"] = words else: - lit.error('The second line of the FCHK file should contain two or three words.') + lit.error("The second line of the FCHK file should contain two or three words.") while True: try: @@ -328,8 +394,7 @@ def _load_fchk_low(lit: LineIterator, label_patterns: List[str] = None) -> dict: return result -# pylint: disable=too-many-branches -def _load_fchk_field(lit: LineIterator, label_patterns: List[str]) -> Tuple[str, object]: +def _load_fchk_field(lit: LineIterator, label_patterns: list[str]) -> tuple[str, object]: """Read a single field matching one of the given label_patterns. Parameters @@ -355,20 +420,22 @@ def _load_fchk_field(lit: LineIterator, label_patterns: List[str]) -> Tuple[str, words = line[43:].split() if not words: continue - if words[0] == 'I': + if words[0] == "I": datatype = int - elif words[0] == 'R': + elif words[0] == "R": datatype = float else: continue - if not (label_patterns is None - or any(fnmatch(label, label_pattern) for label_pattern in label_patterns)): + if not ( + label_patterns is None + or any(fnmatch(label, label_pattern) for label_pattern in label_patterns) + ): continue if len(words) == 2: try: return label, datatype(words[1]) except ValueError: - lit.error("Could not interpret: {}".format(words[1])) + lit.error(f"Could not interpret: {words[1]}") elif len(words) == 3: if words[1] != "N=": lit.error("Expected N= not found.") @@ -383,7 +450,7 @@ def _load_fchk_field(lit: LineIterator, label_patterns: List[str]) -> Tuple[str, try: value[counter] = datatype(word) except (ValueError, OverflowError): - lit.error('Could not interpret: {}'.format(word)) + lit.error(f"Could not interpret: {word}") counter += 1 return label, value @@ -407,7 +474,7 @@ def _load_dm(label: str, fchk: dict, result: dict, key: str): result[key] = _triangle_to_dense(fchk[label]) -def _triangle_to_dense(triangle: np.ndarray) -> np.ndarray: +def _triangle_to_dense(triangle: NDArray) -> NDArray: """Convert a symmetric matrix in triangular storage to a dense square matrix. Parameters @@ -428,8 +495,8 @@ def _triangle_to_dense(triangle: np.ndarray) -> np.ndarray: begin = 0 for irow in range(nrow): end = begin + irow + 1 - result[irow, :irow + 1] = triangle[begin:end] - result[:irow + 1, irow] = triangle[begin:end] + result[irow, : irow + 1] = triangle[begin:end] + result[: irow + 1, irow] = triangle[begin:end] begin = end return result @@ -438,38 +505,38 @@ def _triangle_to_dense(triangle: np.ndarray) -> np.ndarray: # theses functions, both scalars and arrays, integer and real(float) variables def _dump_integer_scalars(name: str, val: int, f: TextIO): """Dumper for a scalar integer.""" - print("{0:40} I {1:12d}".format(name, int(val)), file=f) + print(f"{name:40} I {int(val):12d}", file=f) def _dump_real_scalars(name: str, val: float, f: TextIO): """Dumper for a scalar float.""" - print("{0:40} R {1: 16.8E}".format(name, float(val)), file=f) + print(f"{name:40} R {float(val): 16.8E}", file=f) -def _dump_integer_arrays(name: str, val: np.ndarray, f: TextIO): +def _dump_integer_arrays(name: str, val: NDArray, f: TextIO): """Dumper for a array of integers.""" nval = val.size if nval != 0: np.reshape(val, nval) - print("{0:40} I N={1:12}".format(name, nval), file=f) + print(f"{name:40} I N={nval:12}", file=f) k = 0 for i in range(nval): - print("{0:12}".format(int(val[i])), file=f, end='') + print(f"{int(val[i]):12}", file=f, end="") k += 1 if k == 6 or i == nval - 1: print("", file=f) k = 0 -def _dump_real_arrays(name: str, val: np.ndarray, f: TextIO): +def _dump_real_arrays(name: str, val: NDArray, f: TextIO): """Dumper for a array of float.""" nval = val.size if nval != 0: np.reshape(val, nval) - print("{0:40} R N={1:12}".format(name, nval), file=f) + print(f"{name:40} R N={nval:12}", file=f) k = 0 for i in range(nval): - print("{0: 16.8E}".format(val[i]), file=f, end='') + print(f"{val[i]: 16.8E}", file=f, end="") k += 1 if k == 5 or i == nval - 1: print("", file=f) @@ -478,14 +545,28 @@ def _dump_real_arrays(name: str, val: np.ndarray, f: TextIO): @document_dump_one( "Gaussian Formatted Checkpoint", - ['atnums', 'atcorenums'], - ['atcharges', 'atcoords', 'atfrozen', 'atgradient', 'athessian', 'atmasses', - 'charge', 'energy', 'lot', 'mo', 'one_rdms', 'obasis_name', - 'extra', 'moments']) + ["atnums", "atcorenums"], + [ + "atcharges", + "atcoords", + "atfrozen", + "atgradient", + "athessian", + "atmasses", + "charge", + "energy", + "lot", + "mo", + "one_rdms", + "obasis_name", + "extra", + "moments", + ], +) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" # write title - print("{0:72}".format(data.title or "FCHK generated by IOData"), file=f) + print("{:72}".format(data.title or "FCHK generated by IOData"), file=f) # write run type, level of theory, and basis set name (all in uppercase) items = [getattr(data, item) or "NA" for item in ["run_type", "lot", "obasis_name"]] @@ -529,7 +610,6 @@ def dump_one(f: TextIO, data: IOData): # write molecular orbital basis set if data.obasis is not None: - # number of primitives per shell nprims = np.array([shell.nprim for shell in data.obasis.shells]) exponents = np.array([item for shell in data.obasis.shells for item in shell.exponents]) @@ -540,9 +620,9 @@ def dump_one(f: TextIO, data: IOData): # get list of shell types: 0=s, 1=p, -1=sp, 2=6d, -2=5d, 3=10f, -3=7f... shell_types = [] for shell in data.obasis.shells: - if shell.ncon == 1 and shell.kinds == ['c']: + if shell.ncon == 1 and shell.kinds == ["c"]: shell_types.append(shell.angmoms[0]) - elif shell.ncon == 1 and shell.kinds == ['p']: + elif shell.ncon == 1 and shell.kinds == ["p"]: shell_types.append(-1 * shell.angmoms[0]) elif shell.ncon == 2 and shell.angmoms == [0, 1]: shell_types.append(-1) @@ -570,7 +650,7 @@ def dump_one(f: TextIO, data: IOData): if -1 in shell_types: sp_coeffs = [] - for (shell, shell_type) in zip(data.obasis.shells, shell_types): + for shell, shell_type in zip(data.obasis.shells, shell_types): if shell_type == -1: sp_coeffs.extend([shell.coeffs[i][1] for i in range(shell.nprim)]) else: @@ -597,8 +677,8 @@ def dump_one(f: TextIO, data: IOData): # write reduced density matrix, if available # get level of theory, use 'NA' if not available - level = data.lot.upper() if data.lot is not None else 'NA' - for item in ['MP2', 'MP3', 'CC', 'CI']: + level = data.lot.upper() if data.lot is not None else "NA" + for item in ["MP2", "MP3", "CC", "CI"]: if item in level: level = item for key, arr in data.one_rdms.items(): @@ -610,21 +690,27 @@ def dump_one(f: TextIO, data: IOData): title = "Total SCF Density" elif key == "scf_spin": title = "Spin SCF Density" - elif key == "post_scf": - title = "Total {0} Density".format(level) - elif key == "post_scf_spin": - title = "Spin {0} Density".format(level) + elif key == "post_scf_ao": + title = f"Total {level} Density" + elif key == "post_scf_spin_ao": + title = f"Spin {level} Density" else: title = "Total SCF Density" _dump_real_arrays(title, mat, f) # write atomic charges - if 'mulliken' in data.atcharges: + if "mulliken" in data.atcharges: _dump_real_arrays("Mulliken Charges", data.atcharges["mulliken"], f) - if 'esp' in data.atcharges: + if "esp" in data.atcharges: _dump_real_arrays("ESP Charges", data.atcharges["esp"], f) - if 'npa' in data.atcharges: + if "npa" in data.atcharges: _dump_real_arrays("NPA Charges", data.atcharges["npa"], f) + if "mbs" in data.atcharges: + _dump_real_arrays("MBS Charges", data.atcharges["mbs"], f) + if "hirshfeld" in data.atcharges: + _dump_real_arrays("Type 6 Charges", data.atcharges["hirshfeld"], f) + if "cm5" in data.atcharges: + _dump_real_arrays("Type 7 Charges", data.atcharges["cm5"], f) # write atomic gradient if data.atgradient is not None: @@ -636,16 +722,16 @@ def dump_one(f: TextIO, data: IOData): _dump_real_arrays("Cartesian Force Constants", arr, f) # write moments - if (1, 'c') in data.moments: - _dump_real_arrays("Dipole Moment", data.moments[(1, 'c')], f) - if (2, 'c') in data.moments and len(data.moments[(2, 'c')]) != 0: + if (1, "c") in data.moments: + _dump_real_arrays("Dipole Moment", data.moments[(1, "c")], f) + if (2, "c") in data.moments and len(data.moments[(2, "c")]) != 0: # quadrupole moments are stored as XX, XY, XZ, YY, YZ, ZZ in IOData, so they need to # be permuted to have XX, YY, ZZ, XY, XZ, YZ order for FCHK. - quadrupole = data.moments[(2, 'c')][[0, 3, 5, 1, 2, 4]] + quadrupole = data.moments[(2, "c")][[0, 3, 5, 1, 2, 4]] _dump_real_arrays("Quadrupole Moment", quadrupole, f) # write polarizability tensor - if 'polarizability_tensor' in data.extra: + if "polarizability_tensor" in data.extra: arr = data.extra["polarizability_tensor"] arr = arr[np.tril_indices(arr.shape[0])] _dump_real_arrays("Polarizability", arr, f) diff --git a/iodata/formats/fcidump.py b/iodata/formats/fcidump.py index 6817d8041..9c17c3860 100644 --- a/iodata/formats/fcidump.py +++ b/iodata/formats/fcidump.py @@ -28,41 +28,40 @@ """ - from typing import TextIO import numpy as np -from ..docstrings import document_load_one, document_dump_one +from ..docstrings import document_dump_one, document_load_one from ..iodata import IOData -from ..utils import set_four_index_element, LineIterator - +from ..utils import LineIterator, set_four_index_element __all__ = [] -PATTERNS = ['*FCIDUMP*'] +PATTERNS = ["*FCIDUMP*"] -@document_load_one("Molpro 2012 FCIDUMP", ['core_energy', 'one_ints', 'nelec', 'spinpol', - 'two_ints']) +@document_load_one( + "Molpro 2012 FCIDUMP", ["core_energy", "one_ints", "nelec", "spinpol", "two_ints"] +) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" # check header line = next(lit) - if not line.startswith(' &FCI NORB='): - lit.error('Incorrect file header') + if not line.startswith(" &FCI NORB="): + lit.error("Incorrect file header") # read info from header - words = line[5:].split(',') + words = line[5:].split(",") header_info = {} for word in words: - if word.count('=') == 1: - key, value = word.split('=') + if word.count("=") == 1: + key, value = word.split("=") header_info[key.strip()] = value.strip() - nbasis = int(header_info['NORB']) - nelec = int(header_info['NELEC']) - spinpol = int(header_info['MS2']) + nbasis = int(header_info["NORB"]) + nelec = int(header_info["NELEC"]) + spinpol = int(header_info["MS2"]) # skip rest of header for line in lit: @@ -78,9 +77,9 @@ def load_one(lit: LineIterator) -> dict: for line in lit: words = line.split() if len(words) != 5: - lit.error('Expecting 5 fields on each data line in FCIDUMP') + lit.error("Expecting 5 fields on each data line in FCIDUMP") value = float(words[0]) - if words[3] != '0': + if words[3] != "0": ii = int(words[1]) - 1 ij = int(words[2]) - 1 ik = int(words[3]) - 1 @@ -89,7 +88,7 @@ def load_one(lit: LineIterator) -> dict: # FCIDUMP file does not contain duplicate 4-index entries. # assert two_mo.get_element(ii,ik,ij,il) == 0.0 set_four_index_element(two_mo, ii, ik, ij, il, value) - elif words[1] != '0': + elif words[1] != "0": ii = int(words[1]) - 1 ij = int(words[2]) - 1 one_mo[ii, ij] = value @@ -98,11 +97,11 @@ def load_one(lit: LineIterator) -> dict: core_energy = value return { - 'nelec': nelec, - 'spinpol': spinpol, - 'one_ints': {'core_mo': one_mo}, - 'two_ints': {'two_mo': two_mo}, - 'core_energy': core_energy, + "nelec": nelec, + "spinpol": spinpol, + "one_ints": {"core_mo": one_mo}, + "two_ints": {"two_mo": two_mo}, + "core_energy": core_energy, } @@ -112,35 +111,43 @@ def load_one(lit: LineIterator) -> dict: """ -@document_dump_one("Molpro 2012 FCIDUMP", ['one_ints', 'two_ints'], - ['core_energy', 'nelec', 'spinpol'], {}, LOAD_ONE_NOTES) +@document_dump_one( + "Molpro 2012 FCIDUMP", + ["one_ints", "two_ints"], + ["core_energy", "nelec", "spinpol"], + {}, + LOAD_ONE_NOTES, +) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" - one_mo = data.one_ints['core_mo'] + one_mo = data.one_ints["core_mo"] # Write header nactive = one_mo.shape[0] nelec = data.nelec or 0 spinpol = data.spinpol or 0 - print(f' &FCI NORB={nactive:d},NELEC={nelec:d},MS2={spinpol:d},', file=f) + print(f" &FCI NORB={nactive:d},NELEC={nelec:d},MS2={spinpol:d},", file=f) print(f" ORBSYM= {','.join('1' for v in range(nactive))},", file=f) - print(' ISYM=1', file=f) - print(' &END', file=f) + print(" ISYM=1", file=f) + print(" &END", file=f) # Write integrals and core energy - two_mo = data.two_ints['two_mo'] - for i in range(nactive): # pylint: disable=too-many-nested-blocks - for j in range(i + 1): - for k in range(nactive): - for l in range(k + 1): - if (i * (i + 1)) / 2 + j >= (k * (k + 1)) / 2 + l: - value = two_mo[i, k, j, l] + two_mo = data.two_ints["two_mo"] + for i0 in range(nactive): + for i1 in range(i0 + 1): + for i2 in range(nactive): + for i3 in range(i2 + 1): + if (i0 * (i0 + 1)) / 2 + i1 >= (i2 * (i2 + 1)) / 2 + i3: + value = two_mo[i0, i2, i1, i3] if value != 0.0: - print(f'{value:23.16e} {i+1:4d} {j+1:4d} {k+1:4d} {l+1:4d}', file=f) - for i in range(nactive): - for j in range(i + 1): - value = one_mo[i, j] + print( + f"{value:23.16e} {i0 + 1:4d} {i1 + 1:4d} {i2 + 1:4d} {i3 + 1:4d}", + file=f, + ) + for i0 in range(nactive): + for i1 in range(i0 + 1): + value = one_mo[i0, i1] if value != 0.0: - print(f'{value:23.16e} {i+1:4d} {j+1:4d} {0:4d} {0:4d}', file=f) + print(f"{value:23.16e} {i0 + 1:4d} {i1 + 1:4d} {0:4d} {0:4d}", file=f) if data.core_energy is not None: - print(f'{data.core_energy:23.16e} {0:4d} {0:4d} {0:4d} {0:4d}', file=f) + print(f"{data.core_energy:23.16e} {0:4d} {0:4d} {0:4d} {0:4d}", file=f) diff --git a/iodata/formats/gamess.py b/iodata/formats/gamess.py index 63c757d87..608bc9160 100644 --- a/iodata/formats/gamess.py +++ b/iodata/formats/gamess.py @@ -18,20 +18,19 @@ # -- """GAMESS punch file format.""" - import numpy as np +from numpy.typing import NDArray from ..docstrings import document_load_one -from ..utils import angstrom, LineIterator - +from ..utils import LineIterator, angstrom __all__ = [] -PATTERNS = ['*.dat'] +PATTERNS = ["*.dat"] -def _read_data(lit: LineIterator) -> tuple: +def _read_data(lit: LineIterator) -> tuple[str, str, list[str]]: """Extract ``title``, ``symmetry`` and ``symbols`` from the punch file.""" title = next(lit).strip() symmetry = next(lit).split()[0] @@ -48,9 +47,9 @@ def _read_data(lit: LineIterator) -> tuple: return title, symmetry, symbols -def _read_coordinates(lit: LineIterator, result: dict) -> tuple: +def _read_coordinates(lit: LineIterator, result: dict[str]) -> tuple[NDArray, NDArray]: """Extract ``numbers`` and ``coordinates`` from the punch file.""" - for i in range(2): + for _ in range(2): next(lit) natom = len(result["symbols"]) # if the data are already read before, just overwrite them @@ -69,7 +68,7 @@ def _read_coordinates(lit: LineIterator, result: dict) -> tuple: return numbers, coordinates -def _read_energy(lit: LineIterator, result: dict) -> tuple: +def _read_energy(lit: LineIterator, result: dict[str]) -> tuple[float, NDArray]: """Extract ``energy`` and ``gradient`` from the punch file.""" energy = float(next(lit).split()[1]) natom = len(result["symbols"]) @@ -83,7 +82,7 @@ def _read_energy(lit: LineIterator, result: dict) -> tuple: return energy, gradient -def _read_hessian(lit: LineIterator, result: dict) -> np.ndarray: +def _read_hessian(lit: LineIterator, result: dict[str]) -> NDArray: """Extract ``hessian`` from the punch file.""" # check that $HESS is not already parsed if "athessian" in result: @@ -99,12 +98,12 @@ def _read_hessian(lit: LineIterator, result: dict) -> np.ndarray: break line = line[5:-1] for j in range(len(line) // 15): - tmp[counter] = float(line[j * 15:(j + 1) * 15]) + tmp[counter] = float(line[j * 15 : (j + 1) * 15]) counter += 1 return hessian -def _read_masses(lit: LineIterator, result: dict) -> np.ndarray: +def _read_masses(lit: LineIterator, result: dict[str]) -> NDArray: """Extract ``masses`` from the punch file.""" natom = len(result["symbols"]) masses = np.zeros(natom, float) @@ -117,11 +116,13 @@ def _read_masses(lit: LineIterator, result: dict) -> np.ndarray: return masses -@document_load_one("PUNCH", ['title', 'energy', 'grot', 'atgradient', 'athessian', 'atmasses', - 'atnums', 'atcoords']) -def load_one(lit: LineIterator) -> dict: +@document_load_one( + "PUNCH", + ["title", "energy", "grot", "atgradient", "athessian", "atmasses", "atnums", "atcoords"], +) +def load_one(lit: LineIterator) -> dict[str]: """Do not edit this docstring. It will be overwritten.""" - result = dict() + result = {} while True: try: line = next(lit) diff --git a/iodata/formats/gaussianinput.py b/iodata/formats/gaussianinput.py index c1c2c3825..29cc7a26a 100644 --- a/iodata/formats/gaussianinput.py +++ b/iodata/formats/gaussianinput.py @@ -22,8 +22,7 @@ from ..docstrings import document_load_one from ..periodic import sym2num -from ..utils import angstrom, LineIterator - +from ..utils import LineIterator, angstrom __all__ = [] @@ -31,31 +30,31 @@ PATTERNS = ["*.com", "*.gjf"] -@document_load_one("Gaussian Input File", ['atcoords', 'atnums', 'title'], []) +@document_load_one("Gaussian Input File", ["atcoords", "atnums", "title"], []) def load_one(lit: LineIterator): """Do not edit this docstring. It will be overwritten.""" line = next(lit) # check multiple-link 0 section starts with '%' - while line.startswith(r'%'): + while line.startswith(r"%"): line = next(lit) # check multiple-line route section data = {} - route_line = '' + route_line = "" while line.strip(): - route_line += (' ' + line.strip()) + route_line += " " + line.strip() line = next(lit) route_line = route_line[1:] line = next(lit) - title_line = '' + title_line = "" while line.strip(): - title_line += (' ' + line.strip()) + title_line += " " + line.strip() line = next(lit) title_line = title_line[1:] - data['title'] = title_line + data["title"] = title_line # charge_spin_mult_line _ = next(lit) @@ -74,7 +73,7 @@ def load_one(lit: LineIterator): coor = list(map(float, contents[1:])) coordinates.append(coor) coord_line = next(lit) - data['atnums'] = np.array(numbers) - data['atcoords'] = np.array(coordinates) * angstrom + data["atnums"] = np.array(numbers) + data["atcoords"] = np.array(coordinates) * angstrom return data diff --git a/iodata/formats/gaussianlog.py b/iodata/formats/gaussianlog.py index a2b3b155d..6a2815fb4 100644 --- a/iodata/formats/gaussianlog.py +++ b/iodata/formats/gaussianlog.py @@ -27,25 +27,24 @@ """ - import numpy as np +from numpy.typing import NDArray from ..docstrings import document_load_one -from ..utils import set_four_index_element, LineIterator - +from ..utils import LineIterator, set_four_index_element __all__ = [] -PATTERNS = ['*.log'] +PATTERNS = ["*.log"] -@document_load_one("Gaussian Log", [], ['one_ints', 'two_ints']) +@document_load_one("Gaussian Log", [], ["one_ints", "two_ints"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" # First get the line with the number of orbital basis functions for line in lit: - if line.startswith(' NBasis ='): + if line.startswith(" NBasis ="): nbasis = int(line[12:18]) break @@ -58,24 +57,24 @@ def load_one(lit: LineIterator) -> dict: line = next(lit) if line.startswith(" Normal termination of Gaussian"): break - if line.startswith(' *** Overlap ***'): - one_ints['olp'] = _load_twoindex_g09(lit, nbasis) - elif line.startswith(' *** Kinetic Energy ***'): - one_ints['kin_ao'] = _load_twoindex_g09(lit, nbasis) - elif line.startswith(' ***** Potential Energy *****'): - one_ints['na_ao'] = _load_twoindex_g09(lit, nbasis) - elif line.startswith(' *** Dumping Two-Electron integrals ***'): - two_ints['er_ao'] = _load_fourindex_g09(lit, nbasis) + if line.startswith(" *** Overlap ***"): + one_ints["olp"] = _load_twoindex_g09(lit, nbasis) + elif line.startswith(" *** Kinetic Energy ***"): + one_ints["kin_ao"] = _load_twoindex_g09(lit, nbasis) + elif line.startswith(" ***** Potential Energy *****"): + one_ints["na_ao"] = _load_twoindex_g09(lit, nbasis) + elif line.startswith(" *** Dumping Two-Electron integrals ***"): + two_ints["er_ao"] = _load_fourindex_g09(lit, nbasis) result = {} if one_ints: - result['one_ints'] = one_ints + result["one_ints"] = one_ints if two_ints: - result['two_ints'] = two_ints + result["two_ints"] = two_ints return result -def _load_twoindex_g09(lit: LineIterator, nbasis: int) -> np.ndarray: +def _load_twoindex_g09(lit: LineIterator, nbasis: int) -> NDArray: """Load a two-index operator from a GAUSSIAN LOG file format. Parameters @@ -101,14 +100,14 @@ def _load_twoindex_g09(lit: LineIterator, nbasis: int) -> np.ndarray: for i in range(nrow): words = next(lit).split()[1:] for j, word in enumerate(words): - value = float(word.replace('D', 'E')) + value = float(word.replace("D", "E")) result[i + block_counter, j + block_counter] = value result[j + block_counter, i + block_counter] = value block_counter += 5 return result -def _load_fourindex_g09(lit: LineIterator, nbasis: int) -> np.ndarray: +def _load_fourindex_g09(lit: LineIterator, nbasis: int) -> NDArray: """Load a four-index operator from a GAUSSIAN LOG file. Parameters @@ -126,20 +125,20 @@ def _load_fourindex_g09(lit: LineIterator, nbasis: int) -> np.ndarray: """ result = np.zeros((nbasis, nbasis, nbasis, nbasis)) # Skip first six lines - for i in range(6): + for _i in range(6): next(lit) # Start reading elements until a line is encountered that does not start # with ' I=' for line in lit: - if not line.startswith(' I='): + if not line.startswith(" I="): break # print line[3:7], line[9:13], line[15:19], line[21:25], line[28:].replace('D', 'E') - i = int(line[3:7]) - 1 - j = int(line[9:13]) - 1 - k = int(line[15:19]) - 1 - l = int(line[21:25]) - 1 - value = float(line[29:].replace('D', 'E')) + i0 = int(line[3:7]) - 1 + i1 = int(line[9:13]) - 1 + i2 = int(line[15:19]) - 1 + i3 = int(line[21:25]) - 1 + value = float(line[29:].replace("D", "E")) # Gaussian uses the chemists notation for the 4-center indexes. IOdata # uses the physicists notation. - set_four_index_element(result, i, k, j, l, value) + set_four_index_element(result, i0, i2, i1, i3, value) return result diff --git a/iodata/formats/gromacs.py b/iodata/formats/gromacs.py index 37e1c0e90..f41905a23 100644 --- a/iodata/formats/gromacs.py +++ b/iodata/formats/gromacs.py @@ -25,22 +25,20 @@ """ - -from typing import Tuple, Iterator +from collections.abc import Iterator import numpy as np -from ..docstrings import (document_load_one, document_load_many) -from ..utils import nanometer, picosecond, LineIterator - +from ..docstrings import document_load_many, document_load_one +from ..utils import LineIterator, nanometer, picosecond __all__ = [] -PATTERNS = ['*.gro'] +PATTERNS = ["*.gro"] -@document_load_one('GRO', ['atcoords', 'atffparams', 'cellvecs', 'extra', 'title']) +@document_load_one("GRO", ["atcoords", "atffparams", "cellvecs", "extra", "title"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" while True: @@ -56,47 +54,40 @@ def load_one(lit: LineIterator) -> dict: atcoords = data[5] velocities = data[6] cellvecs = data[7] - atffparams = { - 'attypes': attypes, - 'resnames': resnames, - 'resnums': resnums - } - extra = { - 'time': time, - 'velocities': velocities + atffparams = {"attypes": attypes, "resnames": resnames, "resnums": resnums} + extra = {"time": time, "velocities": velocities} + return { + "atcoords": atcoords, + "atffparams": atffparams, + "cellvecs": cellvecs, + "extra": extra, + "title": title, } - result = { - 'atcoords': atcoords, - 'atffparams': atffparams, - 'cellvecs': cellvecs, - 'extra': extra, - 'title': title, - } - return result - lit.error('Gromacs gro file could not be read.') + lit.error("Gromacs gro file could not be read.") + return None -@document_load_many('GRO', ['atcoords', 'atffparams', 'cellvecs', 'extra', 'title']) +@document_load_many("GRO", ["atcoords", "atffparams", "cellvecs", "extra", "title"]) def load_many(lit: LineIterator) -> Iterator[dict]: """Do not edit this docstring. It will be overwritten.""" # gro files can be used as trajectory by simply concatenating files, # making it trivial to load many frames. - while True: - try: + try: + while True: yield load_one(lit) - except IOError: - return + except OSError: + return -def _helper_read_frame(lit: LineIterator) -> Tuple: +def _helper_read_frame(lit: LineIterator) -> tuple: """Read one frame.""" # Read the first line, get the title and try to get the time. # Time field is optional. line = next(lit) - title = line.split(',')[0] if 't=' in line else line[:-1] + title = line.split(",")[0] if "t=" in line else line[:-1] time = 0.0 - if 't=' in line: - time = float(line.split('t=')[1]) * picosecond + if "t=" in line: + time = float(line.split("t=")[1]) * picosecond # Read the second line for number of atoms. natoms = int(next(lit)) # Read the atom lines diff --git a/iodata/formats/json.py b/iodata/formats/json.py index 2775e3de3..e7f61c056 100644 --- a/iodata/formats/json.py +++ b/iodata/formats/json.py @@ -16,34 +16,564 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see # -- -# pylint: disable=too-many-branches, too-many-statements """QCSchema JSON file format. QCSchema defines four different subschema: -* Molecule - specifying a molecular system -* Input - specifying QC program input for a specific Molecule -* Output - specifying QC program output for a specific Molecule -* Basis - specifying a basis set for a specific Molecule -The QCSchema subschema are in various levels of maturity, and are subject to change at any time -without warning, as they are also used as the internal data representation for the QCElemental -program. IOData currently supports the Molecule subschema for both ``load_one`` and ``dump_one``. +- :ref:`Molecule `: specifying a molecular system +- :ref:`Input `: specifying QC program input for a specific Molecule +- :ref:`Output `: specifying QC program output for a specific Molecule +- Basis: specifying a basis set for a specific Molecule + +General Usage +------------- +The QCSchema format is intended to be a catch-all file format for storing and sharing QC calculation +data. Due to the wide number of possibilities of the data contained in a single file, not every +field in a QCSchema file directly corresponds to an IOData attribute. For example, +``qcschema_output`` files allow for many fields capturing different energy contributions, especially +for coupled-cluster calculations. To accommodate this fact, IOData does not always assume the intent +of the user; instead, IOData ensures that every field in the file is stored in a structured manner. +When a QCSchema field does not correspond to an IOData attribute, that data is instead stored in the +``extra`` dict, in a dictionary corresponding to the subschema where that data was found. In cases +where multiple subschema contain the relevant field (e.g. the Output subschema contains the entirety +of the Input subschema), the data will be found in the smallest subschema (for the example above, in +``IOData.extra["input"]``, not ``IOData.extra["output"]``). + +Dumping an IOData instance to a QCSchema file involves adding relevant required (and optional, if +needed) fields to the necessary dictionaries in the ``extra`` dict. One exception is the +``provenance`` field: if the only desired provenance data is the creation of the file by IOData, +that data will be added automatically. + +The following sections will describe the requirements of each subschema and the behaviour to expect +from IOData when loading in or dumping out a QCSchema file. + +Schema Definitions +------------------ + +.. _json_schema_provenance: + +Provenance Information +^^^^^^^^^^^^^^^^^^^^^^ +The provenance field contains information about how the associated QCSchema object and its +attributes were generated, provided, and manipulated. A provenance entry expects these fields: + +========= =========== +Field Description +========= =========== +creator **Required**. The program that generated, provided, or manipulated this file. +version The version of the creator. +routine The routine of the creator. +========= =========== + +In QCElemental, only a single provenance entry is permitted. When generating a QCSchema file for use +with QCElemental, the easiest way to ensure compliance is to leave the provenance field blank, to +allow the ``dump_one`` function to generate the correct provenance information. However, allowing +only one entry for provenance information limits the ability to properly trace a file through +several operations during complex workflows. With this in mind, IOData supports an enhanced +provenance field, in the form of a list of provenance entries, with new entries appended to the end +of the list. + +.. _json_schema_molecule: + +Molecule Schema +^^^^^^^^^^^^^^^ +The ``qcschema_molecule`` subschema describes a molecular system, and contains the data necessary to +specify a molecular system and support I/O and manipulation processes. + +The following is an example of a minimal ``qcschema_molecule`` file: + +.. code-block :: JSON + + { + "schema_name": "qcschema_molecule", + "schema_version": 2, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0, + "molecular_multiplicity": 1, + "provenance": { + "creator": "HORTON3", + "routine": "Manual validation" + } + } -""" + +The required fields and corresponding types for a ``qcschema_molecule`` file are: + +====================== ============ ============ ================================================= +Field Type IOData attr. Description +====================== ============ ============ ================================================= +schema_name str N/A The name of the QCSchema subschema. Fixed as + ``qcschema_molecule``. +schema_version str N/A The version of the subschema specification. + 2.0 is the current version. +symbols list(N_at) ``atnums`` An array of the atomic symbols for the system. +geometry list(3*N_at) ``atcoords`` An ordered array of XYZ atomic coordinates, + corresponding to the order of ``symbols``. The + first three elements correspond to atom one, + the second three to atom two, etc. +molecular_charge float ``charge`` The net electrostatic charge of the molecule. + Some writers assume a default of 0. +molecular_multiplicity int ``spinpol`` The total multiplicity of this molecule. + Some writers assume a default of 1. +provenance dict or list N/A Information about the file was generated, + provided, and manipulated. See + :ref:`Provenance section ` + above for more details. +====================== ============ ============ ================================================= + +Note: N_at corresponds to the number of atoms in the molecule, as defined by the length of +``symbols``. + +The optional fields and corresponding types for a ``qcschema_molecule`` file are: + +======================= ============ ============== ================================================ +Field Type IOData attr. Description +======================= ============ ============== ================================================ +atom_labels list(N_at) N/A Additional per-atom labels. Typically used for + model conversions, not user assignment. The + indices of this array correspond to the + ``symbols`` ordering. +atomic_numbers list(N_at) ``atnums`` An array of atomic numbers for each atom. + Typically inferred from ``symbols``. +comment str N/A Additional comments for this molecule. These + comments are intended for user information, not + any computational tasks. +connectivity list ``bonds`` The connectivity information between each atom + in the ``symbols`` array. Each entry in this + array is a 3-item array, + ``[index_a, index_b, bond_order]``, + where the indices correspond to the atom indices + in ``symbols``. +extras dict N/A Extra information to associate with this + molecule. +fix_symmetry str ``g_rot`` Maximal point group symmetry with which the + molecule should be treated. +fragments list(N_fr) N/A An array that designates which sets of atoms are + fragments within the molecule. This is a nested + array, with the indices of the base array + corresponding to the values in + ``fragment_charges`` and + ``fragment_multiplicities`` and the values in + the nested arrays corresponding to the indices + of ``symbols``. +fragment_charges list(N_fr) N/A The total charge of each fragment in + ``fragments``. The indices of this array + correspond to the ``fragments`` ordering. +fragment_multiplicities list(N_fr) N/A The multiplicity of each fragment in + ``fragments``. The indices of this array + correspond to the ``fragments`` ordering. +id str N/A A unique identifier for this molecule. +identifiers dict N/A Additional identifiers by which this molecule + can be referenced, such as INCHI, SMILES, etc. +real list(N_at) ``atcorenums`` An array indicating whether each atom is real + (true) or a ghost/virtual atom (false). The + indices of this array correspond to the + ``symbols`` ordering. +mass_numbers list(N_at) ``atmasses`` An array of atomic mass numbers for each atom. + The indices of this array correspond to the + ``symbols`` ordering. +masses list(N_at) ``atmasses`` An array of atomic masses [u] for each atom. + Typically inferred from ``symbols``. The indices + of this array correspond to the ``symbols`` + ordering. +name str ``title`` An arbitrary, common, or human-readable name to + assign to this molecule. +======================= ============ ============== ================================================ + +Note: N_at corresponds to the number of atoms in the molecule, as defined by the length of +``symbols``; N_fr corresponds to the number of fragments in the molecule, as defined by the length +of ``fragments``. Fragment data is stored in a sub-dictionary, ``fragments``. + +The following are additional optional keywords used in QCElemental's QCSchema implementation. These +keywords mostly correspond to specific QCElemental functionality, and may not necessarily produce +similar results in other QCSchema parsers. + +======================= ============ ================================================== +Field Type Description +======================= ============ ================================================== +fix_com bool An indicator to prevent pre-processing the + molecule by translating the COM to (0,0,0) in + Euclidean coordinate space. +fix_orientation bool An indicator to prevent pre-processing the + molecule by orienting via the inertia tensor. +validated bool An indicator that the input molecule data has been + previously checked for schema and physics (e.g. + non-overlapping atoms, feasible multiplicity) + compliance. Generally should only be true when set + by a trusted validator. +======================= ============ ================================================== + +.. _json_schema_input: + +Input Schema +^^^^^^^^^^^^ +The ``qcschema_input`` subschema describes all data necessary to generate and parse a QC program +input file for a given molecule. + +The following is an example of a minimal ``qcschema_input`` file: + +.. code-block :: JSON + + { + "schema_name": "qcschema_input", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0.0, + "molecular_multiplicity": 1, + "provenance": { + "creator": "HORTON3", + "routine": "Manual validation" + } + }, + "driver": "energy", + "model": { + "method": "B3LYP", + "basis": "Def2TZVP" + } + } + +The required fields and corresponding types for a ``qcschema_input`` file are: + +======================= ============ ============ ================================================== +Field Type IOData attr. Description +======================= ============ ============ ================================================== +schema_name str N/A The QCSchema specification to which this model + conforms. Fixed as ``qcschema_input``. +schema_version float N/A The version number of ``schema_name`` to which + this model conforms, currently 2. +molecule dict N/A :ref:`QCSchema Molecule ` + instance. +driver str N/A The type of calculation being performed. One of + ``energy``, ``gradient``, ``hessian``, or + ``properties``. +model dict N/A The quantum chemistry model specification for a + given operation to compute against. See + :ref:`Model section ` below. +======================= ============ ============ ================================================== + +The optional fields and corresponding types for a `qcschema_input` file are: + +======================= ============ ============ ================================================== +Field Type IOData attr. Description +======================= ============ ============ ================================================== +extras dict N/A Extra information associated with the input. +id str N/A An identifier for the input object. +keywords dict N/A QC program-specific keywords to be used for a + computation. See details below for IOData-specific + usages. +protocols dict N/A Protocols regarding the manipulation of the output + that results from this input. See + :ref:`Protocols section ` + below. +provenance dict or list N/A Information about the file was generated, + provided, and manipulated. See + :ref:`Provenance section ` + above for more information. +======================= ============ ============ ================================================== + +IOData currently supports the following keywords for ``qcschema_input`` files: + +======================= ============ ============ ================================================== +Keyword Type IOData attr. Description +======================= ============ ============ ================================================== +run_type str ``run_type`` The type of calculation that lead to the results + stored in IOData, which must be one of the + following: ``energy``, ``energy_force``, ``opt``, + ``scan``, ``freq`` or None. +======================= ============ ============ ================================================== + +.. _json_schema_model: + +Model Subschema +^^^^^^^^^^^^^^^ +The ``model`` dict contains the following fields: + +======================= ============ ============ ================================================== +Field Type IOData attr. Description +======================= ============ ============ ================================================== +method str ``lot`` The level of theory used for the computation (e.g. + B3LYP, PBE, CCSD(T), etc.) +basis str or dict N/A The quantum chemistry basis set to evaluate (e.g. + 6-31G, cc-pVDZ, etc.) Can be 'none' for methods + without basis sets. Must be either a string + specifying the basis set name (the same as its + name in the Basis Set Exchange, when possible) or + a qcschema_basis instance. +======================= ============ ============ ================================================== + +.. _json_schema_protocols: + +Protocols Subschema +^^^^^^^^^^^^^^^^^^^ +The ``protocols`` dict contains the following fields: + +======================= ============ ============ ================================================== +Field Type IOData attr. Description +======================= ============ ============ ================================================== +wavefunction str N/A Specification of the wavefunction properties to + keep from the resulting output. One of ``all``, + ``orbitals_and_eigenvalues``, ``return_results``, + or ``none``. +keep_stdout bool N/A An indicator to keep the output file from the + resulting output. +======================= ============ ============ ================================================== + +.. _json_schema_output: + +Output Schema +^^^^^^^^^^^^^ +The ``qcschema_output`` subschema describes all data necessary to generate and parse a QC program's +output file for a given molecule. + +The following is an example of a minimal ``qcschema_output`` file: + +.. code-block :: JSON + + { + "schema_name": "qcschema_output", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0.0, + "molecular_multiplicity": 1, + "provenance": { + "creator": "HORTON3", + "routine": "Manual validation" + } + }, + "driver": "energy", + "model": { + "method": "HF", + "basis": "STO-4G" + }, + "properties": {}, + "return_result": -464.626219879, + "success": true + } + +The required fields and corresponding types for a ``qcschema_output`` file are: + +======================= ============ ============ ================================================== +Field Type IOData attr. Description +======================= ============ ============ ================================================== +schema_name str N/A The QCSchema specification to which this model + conforms. Fixed as ``qcschema_output``. +schema_version float N/A The version number of ``schema_name`` to which + this model conforms, currently 2. +molecule dict N/A QCSchema Molecule instance. +driver str N/A The type of calculation being performed. One of + ``energy``, ``gradient``, ``hessian``, or + ``properties``. +model dict N/A The quantum chemistry model specification for a + given operation to compute against. +properties dict N/A Named properties of quantum chemistry + computations. See + :ref:`Properties section ` + below. +return_result varies N/A The result requested by the ``driver``. The type + depends on the ``driver``. +success bool N/A An indicator for the success of the QC program's + execution. +======================= ============ ============ ================================================== + +The optional fields and corresponding types for a ``qcschema_output`` file are: + +======================= ============ ============ ================================================== +Field Type IOData attr. Description +======================= ============ ============ ================================================== +error dict N/A A complete description of an error-terminated + computation. See + :ref:`Error section ` below. +extras dict N/A Extra information associated with the input. Also + specified for + :ref:`qcschema_input `. +id str N/A An identifier for the input object. Also specified + for :ref:`qcschema_input `. +keywords dict N/A QC program-specific keywords to be used for a + computation. See details below for IOData-specific + usages. Also specified for + :ref:`qcschema_input `. +protocols dict N/A Protocols regarding the manipulation of the output + that results from this input. See + :ref:`Protocols section ` + above. Also specified for + :ref:`qcschema_input `. +provenance dict or list N/A Information about the file was generated, + provided, and manipulated. See Provenance section + above for more information. Also specified for + :ref:`qcschema_input `. +stderr str N/A The standard error (stderr) of the associated + computation. +stdout str N/A The standard output (stdout) of the associated + computation. +wavefunction dict N/A The wavefunction properties of a QC computation. + All matrices appear in column-major order. See + :ref:`Wavefunction ` + section below. +======================= ============ ============ ================================================== + +.. _json_schema_properties: + +Properties Subschema +^^^^^^^^^^^^^^^^^^^^ +The ``properties`` dict contains named properties of quantum chemistry computations. Due to the +variability possible for the contents of an output file, IOData does not guess at which properties +are desired by the user, and stores all properties in the ``extra["output]["properties"]`` dict for +easy retrieval. The current QCSchema standard provides names for the following properties: + +======================================== =========================================================== +Field Description +======================================== =========================================================== +calcinfo_nbasis The number of basis functions for the computation. +calcinfo_nmo The number of molecular orbitals for the computation. +calcinfo_nalpha The number of alpha electrons in the computation. +calcinfo_nbeta The number of beta electrons in the computation. +calcinfo_natom The number of atoms in the computation. +nuclear_repulsion_energy The nuclear repulsion energy term. +return_energy The energy of the requested method, identical to + ``return_value`` for energy computations. +scf_one_electron_energy The one-electron (core Hamiltonian) energy contribution to + the total SCF energy. +scf_two_electron_energy The two-electron energy contribution to the total SCF + energy. +scf_vv10_energy The VV10 functional energy contribution to the total SCF + energy. +scf_xc_energy The functional (XC) energy contribution to the total SCF + energy. +scf_dispersion_correction_energy The dispersion correction appended to an underlying + functional when a DFT-D method is requested. +scf_dipole_moment The X, Y, and Z dipole components. +scf_total_energy The total electronic energy of the SCF stage of the + calculation. +scf_iterations The number of SCF iterations taken before convergence. +mp2_same_spin_correlation_energy The portion of MP2 doubles correlation energy from + same-spin (i.e. triplet) correlations. +mp2_opposite_spin_correlation_energy The portion of MP2 doubles correlation energy from + opposite-spin (i.e. singlet) correlations. +mp2_singles_energy The singles portion of the MP2 correlation energy. Zero + except in ROHF. +mp2_doubles_energy The doubles portion of the MP2 correlation energy including + same-spin and opposite-spin correlations. +mp2_total_correlation_energy The MP2 correlation energy. +mp2_correlation_energy The MP2 correlation energy. +mp2_total_energy The total MP2 energy (MP2 correlation energy + HF energy). +mp2_dipole_moment The MP2 X, Y, and Z dipole components. +ccsd_same_spin_correlation_energy The portion of CCSD doubles correlation energy from + same-spin (i.e. triplet) correlations. +ccsd_opposite_spin_correlation_energy The portion of CCSD doubles correlation energy from + opposite-spin (i.e. singlet) correlations +ccsd_singles_energy The singles portion of the CCSD correlation energy. Zero + except in ROHF. +ccsd_doubles_energy The doubles portion of the CCSD correlation energy + including same-spin and opposite-spin correlations. +ccsd_correlation_energy The CCSD correlation energy. +ccsd_total_energy The total CCSD energy (CCSD correlation energy + HF + energy). +ccsd_dipole_moment The CCSD X, Y, and Z dipole components. +ccsd_iterations The number of CCSD iterations taken before convergence. +ccsd_prt_pr_correlation_energy The CCSD(T) correlation energy. +ccsd_prt_pr_total_energy The total CCSD(T) energy (CCSD(T) correlation energy + HF + energy). +ccsd_prt_pr_dipole_moment The CCSD(T) X, Y, and Z dipole components. +ccsd_prt_pr_iterations The number of CCSD(T) iterations taken before convergence. +ccsdt_correlation_energy The CCSDT correlation energy. +ccsdt_total_energy The total CCSDT energy (CCSDT correlation energy + HF + energy). +ccsdt_dipole_moment The CCSDT X, Y, and Z dipole components. +ccsdt_iterations The number of CCSDT iterations taken before convergence. +ccsdtq_correlation_energy The CCSDTQ correlation energy. +ccsdtq_total_energy The total CCSDTQ energy (CCSDTQ correlation energy + HF + energy). +ccsdtq_dipole_moment The CCSDTQ X, Y, and Z dipole components. +ccsdtq_iterations The number of CCSDTQ iterations taken before convergence. +======================================== =========================================================== + +.. _json_schema_error: + +Error Subschema +^^^^^^^^^^^^^^^ +The ``error`` dict contains the following fields: + +======================= ============ ============ ================================================== +Field Type IOData attr. Description +======================= ============ ============ ================================================== +error_type str N/A The type of error raised during the computation. +error_message str N/A Additional information related to the error, such + as the backtrace. +extras dict N/A Additional data associated with the error. +======================= ============ ============ ================================================== + +.. _json_schema_wavefunction: + +Wavefunction subschema +^^^^^^^^^^^^^^^^^^^^^^ +The wavefunction subschema contains the wavefunction properties of a QC computation. All matrices +appear in column-major order. The current QCSchema standard provides names for the following +wavefunction properties: + +.. CCA_convention_source: +https://github.com/evaleev/libint/wiki/using-modern-CPlusPlus-API#solid-harmonic-gaussians-ordering-and-normalization + +======================================== =========================================================== +Field Description +======================================== =========================================================== +basis A ``qcschema_basis`` instance for the one-electron AO basis + set. AO basis functions are ordered according to the CCA + standard as implemented in + :ref:`libint `. +restricted An indicator for a restricted calculation (alpha == beta). + When true, all beta quantites are omitted, since quantity_b + == quantity_a +h_core_a Alpha-spin core (one-electron) Hamiltonian. +h_core_b Beta-spin core (one-electron) Hamiltonian. +h_effective_a Alpha-spin effective core (one-electron) Hamiltonian. +h_effective_b Beta-spin effective core (one-electron) Hamiltonian. +scf_orbitals_a Alpha-spin SCF orbitals. +scf_orbitals_b Beta-spin SCF orbitals. +scf_density_a Alpha-spin SCF density matrix. +scf_density_b Beta-spin SCF density matrix. +scf_fock_a Alpha-spin SCF Fock matrix. +scf_fock_b Beta-spin SCF Fock matrix. +scf_eigenvalues_a Alpha-spin SCF eigenvalues. +scf_eigenvalues_b Beta-spin SCF eigenvalues. +scf_occupations_a Alpha-spin SCF orbital occupations. +scf_occupations_b Beta-spin SCF orbital occupations. +orbitals_a Keyword for the primary return alpha-spin orbitals. +orbitals_b Keyword for the primary return beta-spin orbitals. +density_a Keyword for the primary return alpha-spin density. +density_b Keyword for the primary return beta-spin density. +fock_a Keyword for the primary return alpha-spin Fock matrix. +fock_b Keyword for the primary return beta-spin Fock matrix. +eigenvalues_a Keyword for the primary return alpha-spin eigenvalues. +eigenvalues_b Keyword for the primary return beta-spin eigenvalues. +occupations_a Keyword for the primary return alpha-spin orbital + occupations. +occupations_b Keyword for the primary return beta-spin orbital + occupations. +======================================== =========================================================== +""" + import json -from typing import List, TextIO, Union +from typing import TextIO, Union from warnings import warn import numpy as np +from .. import __version__ from ..docstrings import document_dump_one, document_load_one from ..iodata import IOData from ..periodic import num2sym, sym2num from ..utils import FileFormatError, FileFormatWarning, LineIterator -from .. import __version__ - __all__ = [] @@ -54,23 +584,23 @@ @document_load_one( "QCSchema", ["atnums", "atcorenums", "atcoords", "charge", "nelec", "spinpol"], - ["atmasses", "bonds", "g_rot", "title", "extra"]) + ["atmasses", "bonds", "energy", "g_rot", "lot", "obasis", "obasis_name", "title", "extra"], +) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" # Use python standard lib json module to read the file to a dict json_in = json.load(lit.f) - result = _parse_json(json_in, lit) - return result + return _parse_json(json_in, lit) def _parse_json(json_in: dict, lit: LineIterator) -> dict: """Parse data from QCSchema JSON input file. - QCSchema supports four different schema types: `qcschema_molecule`, specifying one or more - molecules in a single system; `qcschema_basis`, specifying a basis set for a molecular system, - `qcschema_input`, specifying input to a QC program for a specific system; and `qcschema_output`, - specifying results of a QC program calculation for a specific system along with the input - information. + QCSchema supports four different schema types: :ref:`qcschema_molecule `, + specifying one or more molecules in a single system; `qcschema_basis`, specifying a basis set + for a molecular system, :ref:`qcschema_input `, specifying input to a QC + program for a specific system; and :ref:`qcschema_output `, specifying + results of a QC program calculation for a specific system along with the input information. Parameters ---------- @@ -88,7 +618,7 @@ def _parse_json(json_in: dict, lit: LineIterator) -> dict: # Remove all null entries and empty dicts in json # QCEngine seems to add null entries and empty dicts even for optional and empty keys fix_keys = {k: v for k, v in json_in.items() if v is not None} - fix_subkeys = dict() + fix_subkeys = {} for key in fix_keys: if isinstance(fix_keys[key], dict): fix_subkeys[key] = {k: v for k, v in fix_keys[key].items() if v is not None} @@ -101,7 +631,9 @@ def _parse_json(json_in: dict, lit: LineIterator) -> dict: # Determine schema type if "schema_name" in result: - if result["schema_name"] not in { + # Correct for qc_schema vs. qcschema, due to inconsistencies in prior versions + schema_name = result["schema_name"].replace("qc_schema", "qcschema") + if schema_name not in { "qcschema_molecule", "qcschema_basis", "qcschema_input", @@ -111,10 +643,10 @@ def _parse_json(json_in: dict, lit: LineIterator) -> dict: if "schema_name" not in result: # Attempt to determine schema type, since some QCElemental files omit this warn( - "{}: QCSchema files should have a `schema_name` key." - "Attempting to determine schema type...".format(lit.filename), + f"{lit.filename}: QCSchema files should have a `schema_name` key." + "Attempting to determine schema type...", FileFormatWarning, - 2, + stacklevel=2, ) # Geometry is required in any molecule schema if "geometry" in result: @@ -122,26 +654,21 @@ def _parse_json(json_in: dict, lit: LineIterator) -> dict: # Check if BSE file, which is too different elif "molssi_bse_schema" in result: raise FileFormatError( - "{}: IOData does not currently support MolSSI BSE Basis JSON.".format(lit.filename) + f"{lit.filename}: IOData does not currently support MolSSI BSE Basis JSON." ) # Center_data is required in any basis schema elif "center_data" in result: schema_name = "qcschema_basis" elif "driver" in result: - if "return_result" in result: - schema_name = "qcschema_output" - else: - schema_name = "qcschema_input" + schema_name = "qcschema_output" if "return_result" in result else "qcschema_input" else: - raise FileFormatError("{}: Could not determine `schema_name`.".format(lit.filename)) - else: - schema_name = result["schema_name"] + raise FileFormatError(f"{lit.filename}: Could not determine `schema_name`.") if "schema_version" not in result: warn( - "{}: QCSchema files should have a `schema_version` key." - "Attempting to load without version number.".format(lit.filename), + f"{lit.filename}: QCSchema files should have a `schema_version` key." + "Attempting to load without version number.", FileFormatWarning, - 2, + stacklevel=2, ) if schema_name == "qcschema_molecule": @@ -159,27 +686,32 @@ def _parse_json(json_in: dict, lit: LineIterator) -> dict: def _load_qcschema_molecule(result: dict, lit: LineIterator) -> dict: - """Load qcschema_molecule properties. + """Load :ref:`qcschema_molecule ` properties. Parameters ---------- result - The JSON dict loaded from file, same as the `molecule` key in QCSchema input/output files. + The JSON dict loaded from file. lit The line iterator holding the file data. Returns ------- molecule_dict - Output dictionary containing ``atcoords``, ``atnums``, ``charge``, + Output dictionary containing ``atcoords``, ``atnums``, ``charge``, ``extra``, ``nelec`` & ``spinpol`` keys and corresponding values. - It may contain ``atmasses``, ``bonds``, ``g_rot``, ``title`` & ``extra`` - keys and corresponding values as well. + It may contain ``atmasses``, ``bonds``, ``g_rot`` & ``title`` keys and corresponding values + as well. """ # All Topology properties are found in the "molecule" key molecule_dict = _parse_topology_keys(result, lit) + # Move extra keys to molecule dict, for consistency with input/output + extra_dict = {"molecule": molecule_dict["extra"]} + molecule_dict["extra"] = extra_dict + molecule_dict["extra"]["schema_name"] = "qcschema_molecule" + return molecule_dict @@ -187,9 +719,9 @@ def _parse_topology_keys(mol: dict, lit: LineIterator) -> dict: """Load topology properties from old QCSchema Molecule specifications. The qcschema_molecule v2 specification requires a topology for every file, specified in the - `molecule` key, containing at least the keys `schema_name`, `schema_version`, `symbols`, - `geometry`, `molecular_charge`, `molecular_multiplicity`, and `provenance`. This schema is - currently used in QCElemental (and thus the QCArchive ecosystem). + ``molecule`` key, containing at least the keys ``schema_name``, ``schema_version``, ``symbols``, + ``geometry``, ``molecular_charge``, ``molecular_multiplicity``, and ``provenance``. This schema + is currently used in QCElemental (and thus the QCArchive ecosystem). qcschema_molecule v1 only exists as the specification on the QCSchema website, and seems never to have been implemented in QCArchive. It is possible to accept v1 input, since all required @@ -198,17 +730,18 @@ def _parse_topology_keys(mol: dict, lit: LineIterator) -> dict: Parameters ---------- mol - The 'molecule' key from the QCSchema file. + The 'molecule' key from the QCSchema input or output file, or the full result for a QCSchema + Molecule file. lit The line iterator holding the file data. Returns ------- topology_dict - Output dictionary containing ``atcoords``, ``atnums``, ``charge``, + Output dictionary containing ``atcoords``, ``atnums``, ``charge``, ``extra``, ``nelec`` & ``spinpol`` keys and corresponding values. - It may contain ``atmasses``, ``bonds``, ``g_rot``, ``title`` & ``extra`` - keys and corresponding values as well. + It may contain ``atmasses``, ``bonds``, ``g_rot`` & ``title`` keys and corresponding values + as well. """ # Make sure required topology properties are present @@ -223,33 +756,20 @@ def _parse_topology_keys(mol: dict, lit: LineIterator) -> dict: for key in should_be_required_keys: if key not in mol: warn( - "{}: QCSchema files should have a '{}' key.".format(lit.filename, key), + f"{lit.filename}: QCSchema files should have a '{key}' key.", FileFormatWarning, - 2, + stacklevel=2, ) for key in topology_keys: if key not in mol: - raise FileFormatError( - "{}: QCSchema topology requires '{}' key".format(lit.filename, key) - ) + raise FileFormatError(f"{lit.filename}: QCSchema topology requires '{key}' key") - topology_dict = dict() - extra_dict = dict() + topology_dict = {} + extra_dict = {} # Save schema name & version extra_dict["schema_name"] = "qcschema_molecule" - try: - version = mol["schema_version"] - except KeyError: - version = -1 - if float(version) < 0 or float(version) > 2: - warn( - "{}: Unknown `qcschema_molecule` version {}, " - "loading may produce invalid results".format(lit.filename, version), - FileFormatWarning, - 2, - ) - extra_dict["schema_version"] = version + extra_dict["schema_version"] = _version_check(mol, 2, "qcschema_molecule", lit) # Geometry is in a flattened list, convert to N x 3 topology_dict["atcoords"] = np.array(mol["geometry"]).reshape(-1, 3) @@ -264,7 +784,7 @@ def _parse_topology_keys(mol: dict, lit: LineIterator) -> dict: "Some QCSchema writers omit this key for default value 0.0," "Ensure this value is correct.", FileFormatWarning, - 2, + stacklevel=2, ) formal_charge = 0.0 else: @@ -279,7 +799,7 @@ def _parse_topology_keys(mol: dict, lit: LineIterator) -> dict: "Some QCSchema writers omit this key for default value 1," "Ensure this value is correct.", FileFormatWarning, - 2, + stacklevel=2, ) topology_dict["spinpol"] = 0 else: @@ -302,7 +822,7 @@ def _parse_topology_keys(mol: dict, lit: LineIterator) -> dict: "{}: Both `masses` and `mass_numbers` given. " "Both values will be written to `extra` dict.", FileFormatWarning, - 2, + stacklevel=2, ) extra_dict["mass_numbers"] = np.array(mol["mass_numbers"]) extra_dict["masses"] = np.array(mol["masses"]) @@ -380,37 +900,125 @@ def _parse_topology_keys(mol: dict, lit: LineIterator) -> dict: "id", "extras", } - parsed_keys = molecule_keys.intersection(mol.keys()) - for key in parsed_keys: - del mol[key] - if len(mol) > 0: - topology_dict["extra"]["unparsed"] = mol + passthrough_dict = _find_passthrough_dict(mol, molecule_keys) + if passthrough_dict: + topology_dict["extra"]["unparsed"] = passthrough_dict return topology_dict -# pylint: disable=unused-argument -def _load_qcschema_basis(result: dict, lit: LineIterator) -> dict: - """Load qcschema_basis properties. +def _version_check(result: dict, max_version: float, schema_name: str, lit: LineIterator) -> str: + """Check whether the QCSchema version is a known version. Parameters ---------- result The JSON dict loaded from file. + max_version + The highest (most recent) known version for the QCSchema type. + schema_name + The ``schema_name`` key of a QCSchema file. lit The line iterator holding the file data. + Returns + ------- + version + The version of the QCSchema file, -1 if unknown version. + """ + try: + version = result["schema_version"] + except KeyError: + version = -1 + if float(version) < 0 or float(version) > max_version: + warn( + f"{lit.filename}: Unknown {schema_name} version {version}, " + "loading may produce invalid results", + FileFormatWarning, + stacklevel=2, + ) + return version + + +def _find_passthrough_dict(result: dict, keys: set) -> dict: + """Find all keys not specified for a given schema. + + Parameters + ---------- + result + The JSON dict loaded from file. + keys + The set of expected keys for a given schema type. + + Returns + ------- + passthrough_dict + All unparsed keys remaining in the parsed dict. + """ + # Avoid altering original dict + result = result.copy() + + passthrough_dict = {} + parsed_keys = keys.intersection(result.keys()) + for key in parsed_keys: + del result[key] + if len(result) > 0: + passthrough_dict = result + + return passthrough_dict + + +def _load_qcschema_basis(_result: dict, _lit: LineIterator) -> dict: + """Load qcschema_basis properties. + + Parameters + ---------- + _result + The JSON dict loaded from file. + _lit + The line iterator holding the file data. + Returns ------- basis_dict ... + + + Raises + ------ + NotImplementedError + QCSchema Basis schema is not yet implemented in IOData. + """ - # basis_dict = dict() + # basis_dict = {} # return basis_dict raise NotImplementedError("qcschema_basis is not yet implemented in IOData.") -# pylint: disable=unused-argument +def _parse_basis_keys(_basis: dict, _lit: LineIterator) -> dict: + """Parse basis keys for a QCSchema input, output, or basis file. + + Parameters + ---------- + _basis + The basis dictionary from a QCSchema basis file or QCSchema input or output 'method' key. + _lit + The line iterator holding the file data. + + Returns + ------- + basis_dict + Dictionary containing ... + + Raises + ------ + NotImplementedError + QCSchema Basis schema is not yet implemented in IOData. + + """ + raise NotImplementedError("qcschema_basis is not yet implemented in IOData.") + + def _load_qcschema_input(result: dict, lit: LineIterator) -> dict: """Load qcschema_input properties. @@ -424,16 +1032,254 @@ def _load_qcschema_input(result: dict, lit: LineIterator) -> dict: Returns ------- input_dict - ... + Output dictionary containing ``atcoords``, ``atnums``, ``charge``, ``extra``, ``lot``, + ``nelec``, ``obasis_name`` & ``spinpol`` keys and corresponding values. + It may contain ``atmasses``, ``bonds``, ``g_rot``, ``obasis``, ``run_type`` & ``title`` + keys and corresponding values as well. """ - # basis_dict = dict() - # return basis_dict - raise NotImplementedError("qcschema_input is not yet implemented in IOData.") + extra_dict = {} + input_dict = _parse_input_keys(result, lit) + extra_dict["input"] = input_dict["extra"] + + if "molecule" not in result: + raise FileFormatError(f"{lit.filename}: QCSchema Input requires 'molecule' key") + molecule_dict = _parse_topology_keys(result["molecule"], lit) + input_dict.update(molecule_dict) + extra_dict["molecule"] = molecule_dict["extra"] + input_dict["extra"] = extra_dict + input_dict["extra"]["schema_name"] = "qcschema_input" + + return input_dict + + +def _parse_input_keys(result: dict, lit: LineIterator) -> dict: + """Parse input keys for QCSchema input or output files. + + Parameters + ---------- + result + The JSON dict loaded from file. + lit + The line iterator holding the file data. + + Returns + ------- + input_dict + Output dictionary containing ``extra``, ``lot`` and ``obasis_name`` keys and corresponding + values. + It may contain ``obasis`` & ``run_type`` keys and corresponding values as well. + + """ + # QCEngineRecords input files don't actually specify a name or version + should_be_required_keys = {"schema_name", "schema_version"} + input_keys = {"molecule", "driver", "model"} + for key in should_be_required_keys: + if key not in result: + warn( + f"{lit.filename}: QCSchema files should have a '{key}' key.", + FileFormatWarning, + stacklevel=2, + ) + for key in input_keys: + if key not in result: + raise FileFormatError( + f"{lit.filename}: QCSchema `qcschema_input` file requires '{key}' key" + ) + # Store all extra keys in extra_dict and gather at end + input_dict = {} + extra_dict = {} + + # Save schema name & version + extra_dict["schema_name"] = "qcschema_input" + extra_dict["schema_version"] = _version_check(result, 1, "qcschema_input", lit) + + # Load driver + extra_dict["driver"] = _parse_driver(result["driver"], lit) + + # Load model & call basis helper if needed + model = _parse_model(result["model"], lit) + input_dict.update(model) + extra_dict["model"] = model["extra"] + + # Load keywords & store + # Currently, only the IOData run_type attribute is specifically parsed from keywords, but this + # is a good space for passing additional IOData-specific keywords, given that the official spec + # treats this as program-specific territory. If run_type is not one of the values expected by + # IOData, it will be stored only in the extra_dict. + if "keywords" in result: + keywords_dict = result["keywords"] + if "run_type" in keywords_dict and keywords_dict["run_type"].lower() in { + "energy", + "energy_force", + "opt", + "scan", + "freq", + }: + input_dict["run_type"] = keywords_dict["run_type"] + extra_dict["keywords"] = keywords_dict + # Check for extras + if "extras" in result: + extra_dict["extras"] = result["extras"] + # Check for ID + if "id" in result: + extra_dict["id"] = result["id"] + # Load protocols + if "protocols" in result: + extra_dict["protocols"] = _parse_protocols(result["protocols"], lit) + # Check for provenance + if "provenance" in result: + extra_dict["provenance"] = _parse_provenance(result["provenance"], lit, "qcschema_input") + + input_dict["extra"] = extra_dict + + input_keys = { + "schema_name", + "schema_version", + "molecule", + "driver", + "model", + "extras", + "id", + "keywords", + "protocols", + "provenance", + } + passthrough_dict = _find_passthrough_dict(result, input_keys) + if passthrough_dict: + input_dict["extra"]["unparsed"] = passthrough_dict + + return input_dict + + +def _parse_driver(driver: str, lit: LineIterator) -> str: + """Load driver properties from QCSchema. + + Parameters + ---------- + driver + The ``driver`` key from the QCSchema input. + lit + The line iterator holding the file data. + + Returns + ------- + driver_dict + The driver for the QCSchema file, specifying what type of calculation is being performed. + + Raises + ------ + FileFormatError + If driver is not one of {"energy", "gradient", "hessian", "properties"}. + + Notes + ----- + This keyword is similar to, but not really interchangeable with, the ``run_type`` IOData + attribute. In order to specify the ``run_type``, add it to the ``keywords`` dictionary. + + """ + if driver not in ["energy", "gradient", "hessian", "properties"]: + raise FileFormatError( + f"{lit.filename}: QCSchema driver must be one of `energy`, `gradient`, `hessian`, " + "or `properties`" + ) + return driver + + +def _parse_model(model: dict, lit: LineIterator) -> dict: + """Load :ref:`model ` properties from QCSchema. + + Parameters + ---------- + model + The dictionary corresponding to the 'model' key for a QCSchema input or output file. + lit + The line iterator holding the file data. + + Returns + ------- + model_dict + Output dictionary containing ``lot`` and ``obasis_name`` keys and corresponding values. + It may contain ``obasis`` and ``extra`` keys and corresponding values as well. + + """ + model_dict = {} + extra_dict = {} + + if "method" not in model: + raise FileFormatError(f"{lit.filename}: QCSchema `model` requires a `method`") + model_dict["lot"] = model["method"] + # QCEngineRecords doesn't give an empty string for basis-free methods, omits req'd key instead + if "basis" not in model: + warn( + f"{lit.filename}: Model `basis` key should be given. Assuming basis-free method.", + stacklevel=2, + ) + elif isinstance(model["basis"], str): + if model["basis"] == "": + warn( + f"{lit.filename}: QCSchema `basis` could not be read and will be omitted." + "Unless model is for a basis-free method, check input file.", + FileFormatWarning, + stacklevel=2, + ) + else: + model_dict["obasis_name"] = model["basis"] + elif isinstance(model["basis"], dict): + basis = _parse_basis_keys(model["basis"], lit) + model_dict.update(basis) + extra_dict["basis"] = basis["extra"] + + model_dict["extra"] = extra_dict + return model_dict + + +def _parse_protocols(protocols: dict, lit: LineIterator) -> dict: + """Load :ref:`protocols ` properties from QCSchema. + + Parameters + ---------- + protocols + Protocols key from a QCSchema input or output file. + lit + The line iterator holding the file data. + + Returns + ------- + protocols_dict + Protocols dictionary containing instructions for the manipulation of output generated from + this input. + + """ + if "wavefunction" not in protocols: + warn( + "{}: Protocols `wavefunction` key not specified, no properties will be kept.", + FileFormatWarning, + stacklevel=2, + ) + wavefunction = "none" + else: + wavefunction = protocols["wavefunction"] + if "stdout" not in protocols: + warn( + "{}: Protocols `stdout` key not specified, stdout will be kept.", + FileFormatWarning, + stacklevel=2, + ) + keep_stdout = True + else: + keep_stdout = protocols["stdout"] + protocols_dict = {} + if wavefunction not in {"all", "orbitals_and_eigenvalues", "return_results", "none"}: + raise FileFormatError(f"{lit.filename}: Invalid `protocols` `wavefunction` keyword.") + protocols_dict["keep_wavefunction"] = wavefunction + if not isinstance(keep_stdout, bool): + raise FileFormatError("{}: `protocols` `stdout` option must be a boolean.") + protocols_dict["keep_stdout"] = keep_stdout + return protocols_dict -# pylint: disable=unused-argument def _load_qcschema_output(result: dict, lit: LineIterator) -> dict: - """Load qcschema_output properties. + """Load :ref:`qcschema_output ` properties. Parameters ---------- @@ -445,17 +1291,116 @@ def _load_qcschema_output(result: dict, lit: LineIterator) -> dict: Returns ------- output_dict - ... + Output dictionary containing ``atcoords``, ``atnums``, ``charge``, ``extra``, ``lot``, + ``nelec``, ``obasis_name`` & ``spinpol`` keys and corresponding values. + It may contain ``atmasses``, ``bonds``, ``energy``, ``g_rot``, ``obasis``, ``run_type`` & + ``title`` keys and corresponding values as well. + """ - # basis_dict = dict() - # return basis_dict - raise NotImplementedError("qcschema_output is not yet implemented in IOData.") + extra_dict = {} + output_dict = _parse_output_keys(result, lit) + extra_dict["output"] = output_dict["extra"] + + if "molecule" not in result: + raise FileFormatError(f"{lit.filename}: QCSchema Input requires 'molecule' key") + molecule_dict = _parse_topology_keys(result["molecule"], lit) + output_dict.update(molecule_dict) + extra_dict["molecule"] = molecule_dict["extra"] + + input_dict = _parse_input_keys(result, lit) + output_dict.update(input_dict) + extra_dict["input"] = input_dict["extra"] + output_dict["extra"] = extra_dict + output_dict["extra"]["schema_name"] = "qcschema_output" + + return output_dict + + +def _parse_output_keys(result: dict, lit: LineIterator) -> dict: + """Parse output keys for QCSchema output files. + + Parameters + ---------- + result + The JSON dict loaded from file. + lit + The line iterator holding the file data. + + Returns + ------- + output_dict + Output dictionary containing ``extra`` key and corresponding values. + It may contain ``energy`` key and corresponding values as well. + + """ + should_be_required_keys = {"schema_name", "schema_version"} + output_keys = {"provenance", "properties", "success", "return_result"} + for key in should_be_required_keys: + if key not in result: + warn( + f"{lit.filename}: QCSchema files should have a '{key}' key.", + FileFormatWarning, + stacklevel=2, + ) + for key in output_keys: + if key not in result: + raise FileFormatError( + f"{lit.filename}: QCSchema `qcschema_output` file requires '{key}' key" + ) + + # Store all extra keys in extra_dict and gather at end + output_dict = {} + extra_dict = {} + + extra_dict["schema_name"] = "qcschema_output" + extra_dict["schema_version"] = _version_check(result, 2, "qcschema_output", lit) + + extra_dict["return_result"] = result["return_result"] + extra_dict["success"] = result["success"] + + # Parse properties + properties = result["properties"] + if "return_energy" in properties: + output_dict["energy"] = properties["return_energy"] + extra_dict["properties"] = properties + + if "error" in result: + extra_dict["error"] = result["error"] + if "stderr" in result: + extra_dict["stderr"] = result["stderr"] + if "stdout" in result: + extra_dict["stderr"] = result["stdout"] + if "wavefunction" in result: + extra_dict["wavefunction"] = result["wavefunction"] + + output_dict["extra"] = extra_dict + + output_keys = { + "schema_name", + "schema_version", + "molecule", + "driver", + "model", + "extras", + "id", + "keywords", + "protocols", + "provenance", + "properties", + "success", + "return_result", + } + passthrough_dict = _find_passthrough_dict(result, output_keys) + if passthrough_dict: + output_dict["extra"]["unparsed"] = passthrough_dict + + return output_dict def _parse_provenance( - provenance: Union[List[dict], dict], lit: LineIterator, source: str, append=True -) -> Union[List[dict], dict]: - """Load provenance properties from QCSchema. + provenance: Union[list[dict], dict], lit: LineIterator, source: str, append=True +) -> Union[list[dict], dict]: + """Load :ref:`provenance ` properties from QCSchema. Parameters ---------- @@ -464,17 +1409,19 @@ def _parse_provenance( lit The line iterator holding the file data. source - The schema type {`qcschema_molecule`, `qcschema_input`, `qcschema_output`} associated + The schema type {``qcschema_molecule``, ``qcschema_input``, ``qcschema_output``} associated with this provenance data. append Append IOData provenance entry to provenance list? + Returns + ------- + base_provenance + The provenance data for a QCSchema file. """ if isinstance(provenance, dict): if "creator" not in provenance: - raise FileFormatError( - "{}: `{}` provenance requires `creator` key".format(lit.filename, source) - ) + raise FileFormatError(f"{lit.filename}: `{source}` provenance requires `creator` key") if append: base_provenance = [provenance] else: @@ -485,10 +1432,10 @@ def _parse_provenance( raise FileFormatError("{}: `{}` provenance requires `creator` key") base_provenance = provenance else: - raise FileFormatError("{}: Invalid `{}` provenance type".format(lit.filename, source)) + raise FileFormatError(f"{lit.filename}: Invalid `{source}` provenance type") if append: base_provenance.append( - {"creator": "IOData", "version": __version__, "routine": "iodata.formats.json"} + {"creator": "IOData", "version": __version__, "routine": "iodata.formats.json.load_one"} ) return base_provenance @@ -496,7 +1443,8 @@ def _parse_provenance( @document_dump_one( "QCSchema", ["atnums", "atcoords", "charge", "spinpol"], - ["title", "atcorenums", "atmasses", "bonds", "g_rot", "extra"]) + ["title", "atcorenums", "atmasses", "bonds", "g_rot", "extra"], +) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" if "schema_name" not in data.extra: @@ -506,14 +1454,12 @@ def dump_one(f: TextIO, data: IOData): if schema_name == "qcschema_molecule": return_dict = _dump_qcschema_molecule(data) elif schema_name == "qcschema_basis": - raise NotImplementedError("{} not yet implemented in IOData.".format(schema_name)) + raise NotImplementedError(f"{schema_name} not yet implemented in IOData.") # return_dict = _dump_qcschema_basis(data) elif schema_name == "qcschema_input": - raise NotImplementedError("{} not yet implemented in IOData.".format(schema_name)) - # return_dict = _dump_qcschema_input(data) - elif schema_name == "qcschema_input": - raise NotImplementedError("{} not yet implemented in IOData.".format(schema_name)) - # return_dict = _dump_qcschema_output(data) + return_dict = _dump_qcschema_input(data) + elif schema_name == "qcschema_output": + return_dict = _dump_qcschema_output(data) else: raise FileFormatError( "'schema_name' must be one of 'qcschema_molecule', 'qcschema_basis'" @@ -523,7 +1469,7 @@ def dump_one(f: TextIO, data: IOData): def _dump_qcschema_molecule(data: IOData) -> dict: - """Dump relevant attributes from IOData to qcschema_molecule. + """Dump relevant attributes from IOData to :ref:`qcschema_molecule `. Parameters ---------- @@ -536,7 +1482,7 @@ def _dump_qcschema_molecule(data: IOData) -> dict: The dict that will produce the QCSchema JSON file. """ - molecule_dict = {"schema_name": "qcschema_molecule", "schema_version": 2} + molecule_dict = {"schema_name": "qcschema_molecule", "schema_version": 2.0} # Gather required field data if data.atnums is None or data.atcoords is None: @@ -550,7 +1496,7 @@ def _dump_qcschema_molecule(data: IOData) -> dict: "`charge` and `spinpol` should be given to write qcschema_molecule file:" "QCSchema defaults to charge = 0 and multiplicity = 1 if no values given.", FileFormatWarning, - 2, + stacklevel=2, ) if data.charge is not None: molecule_dict["molecular_charge"] = data.charge @@ -570,55 +1516,221 @@ def _dump_qcschema_molecule(data: IOData) -> dict: molecule_dict["fix_symmetry"] = data.g_rot # Check for other QCSchema keys from IOData extra dict - if "qcel_validated" in data.extra: - molecule_dict["validated"] = data.extra["qcel_validated"] - if "identifiers" in data.extra: - molecule_dict["identifiers"] = data.extra["identifiers"] - if "comment" in data.extra: - molecule_dict["comment"] = data.extra["comment"] - if "atom_labels" in data.extra: - molecule_dict["atom_labels"] = data.extra["atom_labels"] - if "atomic_numbers" in data.extra: - molecule_dict["atomic_numbers"] = data.extra["atomic_numbers"].tolist() - if "masses" in data.extra: - molecule_dict["masses"] = data.extra["masses"].tolist() - if "mass_numbers" in data.extra: - molecule_dict["mass_numbers"] = data.extra["mass_numbers"].tolist() - if "fragments" in data.extra: - if "indices" in data.extra["fragments"]: + if "qcel_validated" in data.extra["molecule"]: + molecule_dict["validated"] = data.extra["molecule"]["qcel_validated"] + if "identifiers" in data.extra["molecule"]: + molecule_dict["identifiers"] = data.extra["molecule"]["identifiers"] + if "comment" in data.extra["molecule"]: + molecule_dict["comment"] = data.extra["molecule"]["comment"] + if "atom_labels" in data.extra["molecule"]: + molecule_dict["atom_labels"] = data.extra["molecule"]["atom_labels"] + if "atomic_numbers" in data.extra["molecule"]: + molecule_dict["atomic_numbers"] = data.extra["molecule"]["atomic_numbers"].tolist() + if "masses" in data.extra["molecule"]: + molecule_dict["masses"] = data.extra["molecule"]["masses"].tolist() + if "mass_numbers" in data.extra["molecule"]: + molecule_dict["mass_numbers"] = data.extra["molecule"]["mass_numbers"].tolist() + if "fragments" in data.extra["molecule"]: + if "indices" in data.extra["molecule"]["fragments"]: molecule_dict["fragments"] = [ - fragment.tolist() for fragment in data.extra["fragments"]["indices"] + fragment.tolist() for fragment in data.extra["molecule"]["fragments"]["indices"] ] - if "indices" in data.extra["fragments"]: - molecule_dict["fragment_charges"] = data.extra["fragments"]["charges"].tolist() - if "indices" in data.extra["fragments"]: - molecule_dict["fragment_multiplicities"] = \ - data.extra["fragments"]["multiplicities"].tolist() - if "fix_com" in data.extra: - molecule_dict["fix_com"] = data.extra["fix_com"] - if "fix_orientation" in data.extra: - molecule_dict["fix_orientation"] = data.extra["fix_orientation"] - if "provenance" in data.extra: - molecule_dict["provenance"] = data.extra["provenance"] - else: - molecule_dict["provenance"] = { - "creator": "IOData", - "version": __version__, - "routine": "iodata.formats.json", - } - if "id" in data.extra: - molecule_dict["id"] = data.extra["id"] - if "extras" in data.extra: - molecule_dict["extras"] = data.extra["extras"] - if "unparsed" in data.extra: - for k in data.extra["unparsed"]: - molecule_dict[k] = data.extra["unparsed"][k] - # print(molecule_dict) - # for k,v in molecule_dict.items(): - # if isinstance(v, list): - # types = "{}[{}]".format(type(v), type(v[0])) - # else: - # types = type(v) - # print("{}: {} | {}".format(k, v, types)) - # print(type(molecule_dict["connectivity"][0][0])) + if "indices" in data.extra["molecule"]["fragments"]: + molecule_dict["fragment_charges"] = data.extra["molecule"]["fragments"][ + "charges" + ].tolist() + if "indices" in data.extra["molecule"]["fragments"]: + molecule_dict["fragment_multiplicities"] = data.extra["molecule"]["fragments"][ + "multiplicities" + ].tolist() + if "fix_com" in data.extra["molecule"]: + molecule_dict["fix_com"] = data.extra["molecule"]["fix_com"] + if "fix_orientation" in data.extra["molecule"]: + molecule_dict["fix_orientation"] = data.extra["molecule"]["fix_orientation"] + molecule_dict["provenance"] = _dump_provenance(data, "molecule") + if "id" in data.extra["molecule"]: + molecule_dict["id"] = data.extra["molecule"]["id"] + if "extras" in data.extra["molecule"]: + molecule_dict["extras"] = data.extra["molecule"]["extras"] + if "unparsed" in data.extra["molecule"]: + for k in data.extra["molecule"]["unparsed"]: + molecule_dict[k] = data.extra["molecule"]["unparsed"][k] + return molecule_dict + + +def _dump_provenance(data: IOData, source: str) -> Union[list[dict], dict]: + """Generate the :ref:`provenance ` information. + + This is used when dumping an IOData instance to QCSchema. + + Parameters + ---------- + data + The IOData instance to dump to file. + source + The `extra` dict location for the dump file, to find provenance data. + + Returns + ------- + provenance + The provenance information for the IOData instance. + + """ + new_provenance = { + "creator": "IOData", + "version": __version__, + "routine": "iodata.formats.json.dump_one", + } + if "provenance" in data.extra[source]: + provenance = data.extra[source]["provenance"] + if isinstance(provenance, dict): + return [provenance, new_provenance] + if isinstance(provenance, list): + provenance.append(new_provenance) + return provenance + raise FileFormatError("QCSchema provenance must be either a dict or list of dicts.") + return new_provenance + + +def _dump_qcschema_input(data: IOData) -> dict: + """Dump relevant attributes from IOData to :ref:`qcschema_input `. + + Using this function requires keywords to be stored in two locations in the ``extra`` dict: + a ``molecule`` dict for the QCSchema Molecule extra keys, and an ``input`` dict for the QCSchema + Input extra keys. + + Parameters + ---------- + data + The IOData instance to dump to file. + + Returns + ------- + input_dict + The dict that will produce the QCSchema JSON file. + + """ + input_dict = {"schema_name": "qcschema_input", "schema_version": 2.0} + + # Gather required field data + input_dict["molecule"] = _dump_qcschema_molecule(data) + if "driver" not in data.extra["input"]: + raise FileFormatError("qcschema_input requires `driver` field in extra['input'].") + if data.extra["input"]["driver"] not in {"energy", "gradient", "hessian", "properties"}: + raise FileFormatError( + "QCSchema driver must be one of `energy`, `gradient`, `hessian`, or `properties`" + ) + input_dict["driver"] = data.extra["input"]["driver"] + if "model" not in data.extra["input"]: + raise FileFormatError("qcschema_input requires `model` field in extra['input'].") + input_dict["model"] = {} + if data.lot is None: + raise FileFormatError("qcschema_input requires specifed `lot`.") + input_dict["model"]["method"] = data.lot + if data.obasis_name is None and "basis" not in data.extra["input"]["model"]: + input_dict["model"]["basis"] = "" + if "basis" in data.extra["input"]["model"]: + raise NotImplementedError("qcschema_basis is not yet supported in IOData.") + input_dict["model"]["basis"] = data.obasis_name + if "keywords" in data.extra["input"]: + input_dict["keywords"] = data.extra["input"]["keywords"] + if "extras" in data.extra["input"]: + input_dict["extras"] = data.extra["input"]["extras"] + if "id" in data.extra["input"]: + input_dict["id"] = data.extra["input"]["id"] + if "protocols" in data.extra["input"]: + input_dict["protocols"] = {} + # Remove 'keep_' from protocols keys (added in IOData for readability) + for keep in data.extra["input"]["protocols"]: + input_dict["protocols"][keep[5:]] = data.extra["input"]["protocols"][keep] + input_dict["provenance"] = _dump_provenance(data, "input") + if "unparsed" in data.extra["input"]: + for k in data.extra["input"]["unparsed"]: + input_dict[k] = data.extra["input"]["unparsed"][k] + + return input_dict + + +def _dump_qcschema_output(data: IOData) -> dict: + """Dump relevant attributes from IOData to :ref:`qcschema_output `. + + Using this function requires keywords to be stored in three locations in the ``extra`` dict: + a ``molecule`` dict for the QCSchema Molecule extra keys, an ``input`` dict for the QCSchema + Input extra keys, and an ``output`` dict for the QCSchema Output extra keys. + + Parameters + ---------- + data + The IOData instance to dump to file. + + Returns + ------- + output_dict + The dict that will produce the QCSchema JSON file. + + """ + output_dict = {"schema_name": "qcschema_output", "schema_version": 2.0} + + # Gather required field data + # Gather required field data + output_dict["molecule"] = _dump_qcschema_molecule(data) + if "driver" not in data.extra["input"]: + raise FileFormatError("qcschema_output requires `driver` field in extra['input'].") + if data.extra["input"]["driver"] not in {"energy", "gradient", "hessian", "properties"}: + raise FileFormatError( + "QCSchema driver must be one of `energy`, `gradient`, `hessian`, or `properties`" + ) + output_dict["driver"] = data.extra["input"]["driver"] + if "model" not in data.extra["input"]: + raise FileFormatError("qcschema_output requires `model` field in extra['input'].") + output_dict["model"] = {} + if data.lot is None: + raise FileFormatError("qcschema_output requires specifed `lot`.") + output_dict["model"]["method"] = data.lot + if data.obasis_name is None and "basis" not in data.extra["input"]["model"]: + warn( + "No basis name given. QCSchema assumes this signifies a basis-free method; to" + "avoid this warning, specify `obasis_name` as an empty string.", + FileFormatWarning, + stacklevel=2, + ) + if "basis" in data.extra["input"]["model"]: + raise NotImplementedError("qcschema_basis is not yet supported in IOData.") + output_dict["model"]["basis"] = data.obasis_name + if "properties" not in data.extra["output"]: + raise FileFormatError("qcschema_output requires `properties` field in extra['output'].") + output_dict["properties"] = data.extra["output"]["properties"] + if data.energy is not None: + output_dict["properties"]["return_energy"] = data.energy + if output_dict["driver"] == "energy": + output_dict["return_result"] = data.energy + if "return_result" not in output_dict and "return_result" not in data.extra["output"]: + raise FileFormatError("qcschema_output requires `return_result` field in extra['output'].") + if "return_result" in data.extra["output"]: + output_dict["return_result"] = data.extra["output"]["return_result"] + if "keywords" in data.extra["input"]: + output_dict["keywords"] = data.extra["input"]["keywords"] + if "extras" in data.extra["input"]: + output_dict["extras"] = data.extra["input"]["extras"] + if "id" in data.extra["input"]: + output_dict["id"] = data.extra["input"]["id"] + if "protocols" in data.extra["input"]: + output_dict["protocols"] = {} + # Remove 'keep_' from protocols keys (added in IOData for readability) + for keep in data.extra["input"]["protocols"]: + output_dict["protocols"][keep[5:]] = data.extra["input"]["protocols"][keep] + if "error" in data.extra["output"]: + output_dict["error"] = data.extra["output"]["error"] + if "stderr" in data.extra["output"]: + output_dict["stderr"] = data.extra["output"]["stderr"] + if "stdout" in data.extra["output"]: + output_dict["stderr"] = data.extra["output"]["stdout"] + if "wavefunction" in data.extra["output"]: + output_dict["wavefunction"] = data.extra["output"]["wavefunction"] + output_dict["provenance"] = _dump_provenance(data, "input") + if "unparsed" in data.extra["input"]: + for k in data.extra["input"]["unparsed"]: + output_dict[k] = data.extra["input"]["unparsed"][k] + + return output_dict diff --git a/iodata/formats/locpot.py b/iodata/formats/locpot.py index 0708969e7..24b1728b4 100644 --- a/iodata/formats/locpot.py +++ b/iodata/formats/locpot.py @@ -25,22 +25,20 @@ different conversions to atomic units. """ - from ..docstrings import document_load_one -from ..utils import electronvolt, LineIterator +from ..utils import LineIterator, electronvolt from .chgcar import _load_vasp_grid - __all__ = [] -PATTERNS = ['LOCPOT*'] +PATTERNS = ["LOCPOT*"] -@document_load_one("VASP 5 LOCPOT", ['atcoords', 'atnums', 'cellvecs', 'cube', 'title']) +@document_load_one("VASP 5 LOCPOT", ["atcoords", "atnums", "cellvecs", "cube", "title"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" result = _load_vasp_grid(lit) # convert locpot to atomic units - result['cube'].data[:] *= electronvolt + result["cube"].data[:] *= electronvolt return result diff --git a/iodata/formats/mol2.py b/iodata/formats/mol2.py index 9fdd8f1de..7e481bff7 100644 --- a/iodata/formats/mol2.py +++ b/iodata/formats/mol2.py @@ -22,25 +22,29 @@ was the main objective to write out files with atomic charges used by antechamber. """ - -from typing import TextIO, Iterator, Tuple +from collections.abc import Iterator +from typing import TextIO import numpy as np - -from ..docstrings import (document_load_one, document_load_many, document_dump_one, - document_dump_many) +from numpy.typing import NDArray + +from ..docstrings import ( + document_dump_many, + document_dump_one, + document_load_many, + document_load_one, +) from ..iodata import IOData -from ..periodic import sym2num, num2sym, bond2num, num2bond -from ..utils import angstrom, LineIterator - +from ..periodic import bond2num, num2bond, num2sym, sym2num +from ..utils import LineIterator, angstrom __all__ = [] -PATTERNS = ['*.mol2'] +PATTERNS = ["*.mol2"] -@document_load_one("MOL2", ['atcoords', 'atnums', 'atcharges', 'atffparams'], ['title']) +@document_load_one("MOL2", ["atcoords", "atnums", "atcharges", "atffparams"], ["title"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" molecule_found = False @@ -65,23 +69,22 @@ def load_one(lit: LineIterator) -> dict: atcharges = {"mol2charges": atchgs} atffparams = {"attypes": attypes} result = { - 'atcoords': atcoords, - 'atnums': atnums, - 'atcharges': atcharges, - 'atffparams': atffparams, - 'title': title + "atcoords": atcoords, + "atnums": atnums, + "atcharges": atcharges, + "atffparams": atffparams, + "title": title, } molecule_found = True if words[0] == "@BOND": bonds = _load_helper_bonds(lit, nbonds) - result['bonds'] = bonds + result["bonds"] = bonds if not molecule_found: raise lit.error("Molecule could not be read") return result -def _load_helper_atoms(lit: LineIterator, natoms: int)\ - -> Tuple[np.ndarray, np.ndarray, np.ndarray, tuple]: +def _load_helper_atoms(lit: LineIterator, natoms: int) -> tuple[NDArray, NDArray, NDArray, tuple]: """Load element numbers, coordinates and atomic charges.""" atnums = np.empty(natoms) atcoords = np.empty((natoms, 3)) @@ -95,7 +98,7 @@ def _load_helper_atoms(lit: LineIterator, natoms: int)\ atnum = sym2num.get(symbol, sym2num.get(symbol[0], None)) if atnum is None: atnum = 0 - lit.warn(f'Can not convert {words[1][:2]} to elements') + lit.warn(f"Can not convert {words[1][:2]} to elements") atnums[i] = atnum attypes.append(words[5]) atcoords[i] = [float(words[2]), float(words[3]), float(words[4])] @@ -108,7 +111,7 @@ def _load_helper_atoms(lit: LineIterator, natoms: int)\ return atnums, atcoords, atchgs, attypes -def _load_helper_bonds(lit: LineIterator, nbonds: int) -> Tuple[np.ndarray]: +def _load_helper_bonds(lit: LineIterator, nbonds: int) -> NDArray: """Load bond information. Each line in a bond definition has the following structure @@ -126,60 +129,59 @@ def _load_helper_bonds(lit: LineIterator, nbonds: int) -> Tuple[np.ndarray]: int(words[1]) - 1, int(words[2]) - 1, # convert mol2 bond type to integer - bond2num.get(words[3], bond2num["un"]) + bond2num.get(words[3], bond2num["un"]), ] if bond is None: bond = [0, 0, 0] - lit.warn(f'Something wrong in the bond section: {bond}') + lit.warn(f"Something wrong in the bond section: {bond}") bonds[i] = bond return bonds -@document_load_many("MOL2", ['atcoords', 'atnums', 'atcharges', 'atffparams'], ['title']) +@document_load_many("MOL2", ["atcoords", "atnums", "atcharges", "atffparams"], ["title"]) def load_many(lit: LineIterator) -> Iterator[dict]: """Do not edit this docstring. It will be overwritten.""" # MOL2 files with more molecules are a simple concatenation of individual MOL2 files,' # making it trivial to load many frames. - while True: - try: + try: + while True: yield load_one(lit) - except IOError: - return + except OSError: + return -@document_dump_one("MOL2", ['atcoords', 'atnums'], ['atcharges', 'atffparams', 'title']) +@document_dump_one("MOL2", ["atcoords", "atnums"], ["atcharges", "atffparams", "title"]) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" # The first six lines are reserved for comments print("# Mol2 file created with Iodata", file=f) print("\n\n\n\n\n", file=f) print("@MOLECULE", file=f) - print(data.title or 'Created with IOData', file=f) + print(data.title or "Created with IOData", file=f) if data.bonds is not None: bonds = len(data.bonds) - print(f'{data.natom:5d} {bonds:6d} {0:6d} {0:6d}', file=f) + print(f"{data.natom:5d} {bonds:6d} {0:6d} {0:6d}", file=f) else: - print(f'{data.natom:5d} {0:6d} {0:6d} {0:6d}', file=f) + print(f"{data.natom:5d} {0:6d} {0:6d} {0:6d}", file=f) print("@ATOM", file=f) - atcharges = data.atcharges.get('mol2charges') - attypes = data.atffparams.get('attypes') + atcharges = data.atcharges.get("mol2charges") + attypes = data.atffparams.get("attypes") for i in range(data.natom): n = num2sym[data.atnums[i]] x, y, z = data.atcoords[i] / angstrom - out1 = f'{i+1:7d} {n:2s} {x:15.4f} {y:9.4f} {z:9.4f} ' + out1 = f"{i+1:7d} {n:2s} {x:15.4f} {y:9.4f} {z:9.4f} " atcharge = 0.0 if atcharges is None else atcharges[i] attype = n if attypes is None else attypes[i] - out2 = f'{attype:6s} {1:4d} XXX {atcharge:14.4f}' + out2 = f"{attype:6s} {1:4d} XXX {atcharge:14.4f}" print(out1 + out2, file=f) if data.bonds is not None: print("@BOND", file=f) for i, bond in enumerate(data.bonds): bondtype = num2bond.get(bond[2], "un") - print(f'{i+1:6d} {bond[0]+1:4d} {bond[1]+1:4d} {bondtype:2s}', - file=f) + print(f"{i+1:6d} {bond[0]+1:4d} {bond[1]+1:4d} {bondtype:2s}", file=f) -@document_dump_many("MOL2", ['atcoords', 'atnums', 'atcharges'], ['title']) +@document_dump_many("MOL2", ["atcoords", "atnums", "atcharges"], ["title"]) def dump_many(f: TextIO, datas: Iterator[IOData]): """Do not edit this docstring. It will be overwritten.""" # Similar to load_many, this is relatively easy. diff --git a/iodata/formats/molden.py b/iodata/formats/molden.py index 889edf361..9353f8365 100644 --- a/iodata/formats/molden.py +++ b/iodata/formats/molden.py @@ -25,27 +25,32 @@ errors are corrected when loading them with IOData. """ - -from typing import Tuple, Union, TextIO import copy +from typing import TextIO, Union -import attr +import attrs import numpy as np - -from ..basis import (angmom_its, angmom_sti, MolecularBasis, Shell, - convert_conventions, HORTON2_CONVENTIONS) -from ..docstrings import document_load_one, document_dump_one +from numpy.typing import NDArray + +from ..basis import ( + HORTON2_CONVENTIONS, + MolecularBasis, + Shell, + angmom_its, + angmom_sti, + convert_conventions, +) +from ..docstrings import document_dump_one, document_load_one from ..iodata import IOData -from ..periodic import sym2num, num2sym from ..orbitals import MolecularOrbitals from ..overlap import compute_overlap, gob_cart_normalization -from ..utils import angstrom, LineIterator - +from ..periodic import num2sym, sym2num +from ..utils import LineIterator, angstrom __all__ = [] -PATTERNS = ['*.molden.input', '*.molden'] +PATTERNS = ["*.molden.input", "*.molden"] # From the Molden format documentation: # 5D: D 0, D+1, D-1, D+2, D-2 @@ -59,30 +64,53 @@ # xxyy xxzz yyzz xxyz yyxz zzxy CONVENTIONS = { - (0, 'c'): ['1'], - (1, 'c'): ['x', 'y', 'z'], - (2, 'p'): HORTON2_CONVENTIONS[(2, 'p')], - (2, 'c'): ['xx', 'yy', 'zz', 'xy', 'xz', 'yz'], - (3, 'p'): HORTON2_CONVENTIONS[(3, 'p')], - (3, 'c'): ['xxx', 'yyy', 'zzz', 'xyy', 'xxy', 'xxz', 'xzz', 'yzz', 'yyz', 'xyz'], - (4, 'p'): HORTON2_CONVENTIONS[(4, 'p')], - (4, 'c'): ['xxxx', 'yyyy', 'zzzz', 'xxxy', 'xxxz', 'xyyy', 'yyyz', 'xzzz', - 'yzzz', 'xxyy', 'xxzz', 'yyzz', 'xxyz', 'xyyz', 'xyzz'], + (0, "c"): ["1"], + (1, "c"): ["x", "y", "z"], + (2, "p"): HORTON2_CONVENTIONS[(2, "p")], + (2, "c"): ["xx", "yy", "zz", "xy", "xz", "yz"], + (3, "p"): HORTON2_CONVENTIONS[(3, "p")], + (3, "c"): ["xxx", "yyy", "zzz", "xyy", "xxy", "xxz", "xzz", "yzz", "yyz", "xyz"], + (4, "p"): HORTON2_CONVENTIONS[(4, "p")], + (4, "c"): [ + "xxxx", + "yyyy", + "zzzz", + "xxxy", + "xxxz", + "xyyy", + "yyyz", + "xzzz", + "yzzz", + "xxyy", + "xxzz", + "yyzz", + "xxyz", + "xyyz", + "xyzz", + ], # H fubnctions are not officially supported by the Molden format but PSI4 # and ORCA write out such files anyway. - (5, 'p'): HORTON2_CONVENTIONS[(5, 'p')], + (5, "p"): HORTON2_CONVENTIONS[(5, "p")], } -@document_load_one("Molden", ['atcoords', 'atnums', 'atcorenums', 'mo', 'obasis'], ['title']) -def load_one(lit: LineIterator) -> dict: +@document_load_one( + "Molden", + ["atcoords", "atnums", "atcorenums", "mo", "obasis"], + ["title"], + { + "norm_threshold": "When the normalization of one of the orbitals exceeds " + "norm_threshold, a correction is attempted or an error " + "is raised when no suitable correction can be found." + }, +) +def load_one(lit: LineIterator, norm_threshold: float = 1e-4) -> dict: """Do not edit this docstring. It will be overwritten.""" result = _load_low(lit) - _fix_molden_from_buggy_codes(result, lit) + _fix_molden_from_buggy_codes(result, lit, norm_threshold) return result -# pylint: disable=too-many-branches,too-many-statements def _load_low(lit: LineIterator) -> dict: """Load data from a MOLDEN input file format, without trying to fix errors. @@ -99,7 +127,7 @@ def _load_low(lit: LineIterator) -> dict: ``title`` key and its corresponding value as well. """ - pure_angmoms = set([]) + pure_angmoms = set() atnums = None atcoords = None obasis = None @@ -112,8 +140,8 @@ def _load_low(lit: LineIterator) -> dict: title = None line = next(lit) - if line != '[Molden Format]\n': - lit.error('Molden header not found') + if line.strip() != "[Molden Format]": + lit.error("Molden header not found") # The order of sections, denoted by "[...]", is not fixed in the Molden # format, so we need a loop that checks for all possible sections at # each iteration. If needed, the contents of the section is read. @@ -126,36 +154,36 @@ def _load_low(lit: LineIterator) -> dict: # than reaching the end of the file. break # settings for pure or Cartesian shells. - if line.startswith('[5d]') or line.startswith('[5d7f]'): + if line.startswith(("[5d]", "[5d7f]")): pure_angmoms.add(2) pure_angmoms.add(3) - elif line.lower().startswith('[7f]'): + elif line.lower().startswith("[7f]"): pure_angmoms.add(3) - elif line.lower().startswith('[5d10f]'): + elif line.lower().startswith("[5d10f]"): pure_angmoms.add(2) - elif line.lower().startswith('[9g]'): + elif line.lower().startswith("[9g]"): pure_angmoms.add(4) # H functions are not part of the Molden standard but the # following line is compatible with files containing H functions # writen by PSI4 and ORCA. pure_angmoms.add(5) # title - elif line == '[title]': + elif line == "[title]": title = next(lit).strip() # atoms - elif line.startswith('[atoms]'): - if 'au' in line: + elif line.startswith("[atoms]"): + if "au" in line: cunit = 1.0 - elif 'angs' in line: + elif "angs" in line: cunit = angstrom atnums, atcorenums, atcoords = _load_helper_atoms(lit, cunit) # we only support Gaussian-type orbitals (gto's) - elif line == '[gto]': + elif line == "[gto]": obasis = _load_helper_obasis(lit) - elif line == '[sto]': - lit.error('Slater-type orbitals are not supported by IODATA.') + elif line == "[sto]": + lit.error("Slater-type orbitals are not supported by IODATA.") # molecular-orbital coefficients. - elif line == '[mo]': + elif line == "[mo]": data_alpha, data_beta = _load_helper_coeffs(lit) occsa, coeffsa, energiesa, irrepsa = data_alpha occsb, coeffsb, energiesb, irrepsb = data_beta @@ -165,38 +193,40 @@ def _load_low(lit: LineIterator) -> dict: for shell in obasis.shells: # Code only has to work for segmented contractions if shell.angmoms[0] in pure_angmoms: - shell.kinds[0] = 'p' + shell.kinds[0] = "p" if coeffsb is None: if coeffsa.shape[0] != obasis.nbasis: lit.error("Number of alpha orbital coefficients does not match the size of the basis.") mo = MolecularOrbitals( - 'restricted', coeffsa.shape[1], coeffsa.shape[1], - occsa, coeffsa, energiesa, irrepsa) + "restricted", coeffsa.shape[1], coeffsa.shape[1], occsa, coeffsa, energiesa, irrepsa + ) else: if coeffsb.shape[0] != obasis.nbasis: lit.error("Number of beta orbital coefficients does not match the size of the basis.") mo = MolecularOrbitals( - 'unrestricted', coeffsa.shape[1], coeffsb.shape[1], + "unrestricted", + coeffsa.shape[1], + coeffsb.shape[1], np.concatenate((occsa, occsb), axis=0), np.concatenate((coeffsa, coeffsb), axis=1), np.concatenate((energiesa, energiesb), axis=0), - irrepsa + irrepsb) + irrepsa + irrepsb, + ) result = { - 'atcoords': atcoords, - 'atnums': atnums, - 'obasis': obasis, - 'mo': mo, - 'atcorenums': atcorenums, + "atcoords": atcoords, + "atnums": atnums, + "obasis": obasis, + "mo": mo, + "atcorenums": atcorenums, } if title is not None: - result['title'] = title + result["title"] = title return result -def _load_helper_atoms(lit: LineIterator, cunit: float) -> \ - Tuple[np.ndarray, np.ndarray, np.ndarray]: +def _load_helper_atoms(lit: LineIterator, cunit: float) -> tuple[NDArray, NDArray, NDArray]: """Load element numbers and coordinates.""" atnums = [] atcorenums = [] @@ -243,14 +273,14 @@ def _load_helper_obasis(lit: LineIterator) -> MolecularBasis: coeffs = np.zeros((nprim, 1)) for iprim in range(nprim): words = next(lit).split() - exponents[iprim] = float(words[0].replace('D', 'E')) - coeffs[iprim, 0] = float(words[1].replace('D', 'E')) + exponents[iprim] = float(words[0].replace("D", "E")) + coeffs[iprim, 0] = float(words[1].replace("D", "E")) # Unless changed later, all shells are assumed to be Cartesian. - shells.append(Shell(icenter, [angmom], ['c'], exponents, coeffs)) - return MolecularBasis(shells, CONVENTIONS, 'L2') + shells.append(Shell(icenter, [angmom], ["c"], exponents, coeffs)) + return MolecularBasis(shells, CONVENTIONS, "L2") -def _load_helper_coeffs(lit: LineIterator) -> Tuple: +def _load_helper_coeffs(lit: LineIterator) -> tuple: """Load the orbital coefficients.""" occsa = [] coeffsa = [] @@ -274,24 +304,24 @@ def _load_helper_coeffs(lit: LineIterator) -> Tuple: # An bracket also means we are done and a new section has started. # Other parts of the parser may need this section line, so we push it # back. - if '[' in line: + if "[" in line: lit.back(line) break # prepare array with orbital coefficients info = {} lit.back(line) for line in lit: - if line.count('=') != 1: + if line.count("=") != 1: lit.back(line) break - key, value = line.split('=') + key, value = line.split("=") info[key.strip().lower()] = value - occ = float(info['occup']) + occ = float(info["occup"]) col = [] - energy = float(info['ene']) - irrep = info.get('sym', '??').strip() + energy = float(info["ene"]) + irrep = info.get("sym", "??").strip() # store column of coefficients, i.e. one orbital, energy and occ - if info['spin'].strip().lower() == 'alpha': + if info["spin"].strip().lower() == "alpha": occsa.append(occ) coeffsa.append(col) energiesa.append(energy) @@ -324,9 +354,13 @@ def _load_helper_coeffs(lit: LineIterator) -> Tuple: return (occsa, coeffsa, energiesa, irrepsa), (occsb, coeffsb, energiesb, irrepsb) -def _is_normalized_properly(obasis: MolecularBasis, atcoords: np.ndarray, - orb_alpha: np.ndarray, orb_beta: np.ndarray, - threshold: float = 1e-4) -> bool: +def _is_normalized_properly( + obasis: MolecularBasis, + atcoords: NDArray, + orb_alpha: NDArray, + orb_beta: NDArray, + norm_threshold: float = 1e-4, +) -> bool: """Test the normalization of the occupied and virtual orbitals. Parameters @@ -339,8 +373,8 @@ def _is_normalized_properly(obasis: MolecularBasis, atcoords: np.ndarray, The alpha orbitals coefficients orb_beta The beta orbitals (may be None). - threshold - When the maximal error on the norm is large than the threshold, + norm_threshold + When the error on one of the orbitals norm exceeds norm_threshold, the function returns False. True is returned otherwise. """ @@ -348,7 +382,6 @@ def _is_normalized_properly(obasis: MolecularBasis, atcoords: np.ndarray, # every attempt because also the primitive normalization may differ in # different cases. olp = compute_overlap(obasis, atcoords) - # Convenient code for debugging files coming from crappy QC codes. # np.set_printoptions(linewidth=5000, precision=2, suppress=True, threshold=100000) # coeffs = orb_alpha._coeffs @@ -375,7 +408,7 @@ def _is_normalized_properly(obasis: MolecularBasis, atcoords: np.ndarray, error_max = max(error_max, abs(norm - 1)) # final judgement - return error_max <= threshold + return error_max <= norm_threshold def _fix_obasis_orca(obasis: MolecularBasis) -> MolecularBasis: @@ -385,18 +418,33 @@ def _fix_obasis_orca(obasis: MolecularBasis) -> MolecularBasis: different sign conventions for some of the pure functions. """ orca_conventions = { - (0, 'c'): ['1'], - (1, 'c'): ['x', 'y', 'z'], - (2, 'p'): ['c0', 'c1', 's1', 'c2', 's2'], - (2, 'c'): ['xx', 'yy', 'zz', 'xy', 'xz', 'yz'], - (3, 'p'): ['c0', 'c1', 's1', 'c2', 's2', '-c3', '-s3'], - (3, 'c'): ['xxx', 'yyy', 'zzz', 'xyy', 'xxy', 'xxz', 'xzz', 'yzz', 'yyz', 'xyz'], - (4, 'p'): ['c0', 'c1', 's1', 'c2', 's2', '-c3', '-s3', '-c4', '-s4'], - (4, 'c'): ['xxxx', 'yyyy', 'zzzz', 'xxxy', 'xxxz', 'xyyy', 'yyyz', 'xzzz', - 'yzzz', 'xxyy', 'xxzz', 'yyzz', 'xxyz', 'xyyz', 'xyzz'], + (0, "c"): ["1"], + (1, "c"): ["x", "y", "z"], + (2, "p"): ["c0", "c1", "s1", "c2", "s2"], + (2, "c"): ["xx", "yy", "zz", "xy", "xz", "yz"], + (3, "p"): ["c0", "c1", "s1", "c2", "s2", "-c3", "-s3"], + (3, "c"): ["xxx", "yyy", "zzz", "xyy", "xxy", "xxz", "xzz", "yzz", "yyz", "xyz"], + (4, "p"): ["c0", "c1", "s1", "c2", "s2", "-c3", "-s3", "-c4", "-s4"], + (4, "c"): [ + "xxxx", + "yyyy", + "zzzz", + "xxxy", + "xxxz", + "xyyy", + "yyyz", + "xzzz", + "yzzz", + "xxyy", + "xxzz", + "yyzz", + "xxyz", + "xyyz", + "xyzz", + ], # H functions are not officialy supported by Molden, but this is how # ORCA writes Molden files anyway: - (5, 'p'): ['c0', 'c1', 's1', 'c2', 's2', '-c3', '-s3', '-c4', '-s4', 'c5', 's5'], + (5, "p"): ["c0", "c1", "s1", "c2", "s2", "-c3", "-s3", "-c4", "-s4", "c5", "s5"], } fixed_shells = [] for shell in obasis.shells: @@ -412,17 +460,16 @@ def _fix_obasis_orca(obasis: MolecularBasis) -> MolecularBasis: correction = gob_cart_normalization(exponent, np.array([0, 0, 0])) elif angmom == 1: correction = gob_cart_normalization(exponent, np.array([1, 0, 0])) - elif angmom == 2 and kind == 'p': + elif angmom == 2 and kind == "p": correction = gob_cart_normalization(exponent, np.array([1, 1, 0])) - elif angmom == 3 and kind == 'p': + elif angmom == 3 and kind == "p": correction = gob_cart_normalization(exponent, np.array([1, 1, 1])) - elif angmom == 4 and kind == 'p': + elif angmom == 4 and kind == "p": correction = gob_cart_normalization(exponent, np.array([2, 1, 1])) - elif angmom == 5 and kind == 'p': + elif angmom == 5 and kind == "p": correction = gob_cart_normalization(exponent, np.array([5, 0, 0])) if correction != 1.0: fixed_shell.coeffs[iprim, 0] /= correction - iprim += 1 return MolecularBasis(fixed_shells, orca_conventions, obasis.primitive_normalization) @@ -446,9 +493,9 @@ def _fix_obasis_psi4(obasis: MolecularBasis) -> Union[MolecularBasis, None]: correction = gob_cart_normalization(exponent, np.array([0, 0, 0])) elif angmom == 1: correction = gob_cart_normalization(exponent, np.array([1, 0, 0])) - elif angmom == 2 and kind == 'p': + elif angmom == 2 and kind == "p": correction = gob_cart_normalization(exponent, np.array([1, 1, 0])) / np.sqrt(3.0) - elif angmom == 3 and kind == 'p': + elif angmom == 3 and kind == "p": correction = gob_cart_normalization(exponent, np.array([1, 1, 1])) / np.sqrt(15.0) # elif angmom == 4 and kind == 'p': ## ! Not tested # correction = gob_cart_normalization(exponent, np.array([2, 1, 1]))/np.sqrt(105.0) @@ -476,11 +523,11 @@ def _fix_obasis_turbomole(obasis: MolecularBasis) -> Union[MolecularBasis, None] for iprim in range(shell.nprim): # Default 1.0: do not to correct anything, unless we know how to correct. correction = 1.0 - if angmom == 2 and kind == 'c': + if angmom == 2 and kind == "c": correction = 1.0 / np.sqrt(3.0) - elif angmom == 3 and kind == 'c': + elif angmom == 3 and kind == "c": correction = 1.0 / np.sqrt(15.0) - elif angmom == 4 and kind == 'c': + elif angmom == 4 and kind == "c": correction = 1.0 / np.sqrt(105.0) if correction != 1.0: corrected = True @@ -503,9 +550,7 @@ def _fix_obasis_normalize_contractions(obasis: MolecularBasis) -> MolecularBasis fixed_shells = [] for shell in obasis.shells: shell_obasis = MolecularBasis( - [attr.evolve(shell, icenter=0)], - obasis.conventions, - obasis.primitive_normalization + [attrs.evolve(shell, icenter=0)], obasis.conventions, obasis.primitive_normalization ) # 2) Get the first diagonal element of the overlap matrix olpdiag = compute_overlap(shell_obasis, np.zeros((1, 3), float))[0, 0] @@ -549,7 +594,45 @@ def _fix_mo_coeffs_psi4(obasis: MolecularBasis) -> Union[MolecularBasis, None]: return None -def _fix_molden_from_buggy_codes(result: dict, lit: LineIterator): +def _fix_mo_coeffs_cfour(obasis: MolecularBasis) -> Union[MolecularBasis, None]: + """Return correction values for the MO coefficients. + + CFOUR (up to current 2.1) uses different normalization conventions for Cartesian + AO basis functions. The coefficients need to be divided by the returned + correction factor. + """ + correction = [] + corrected = False + for shell in obasis.shells: + # We can safely assume segmented shells. + assert shell.ncon == 1 + angmom = shell.angmoms[0] + kind = shell.kinds[0] + factors = None + if kind == "c": + if angmom == 2: + factors = np.array([1.0 / np.sqrt(3.0)] * 3 + [1.0] * 3) + elif angmom == 3: + factors = np.array([1.0 / np.sqrt(15.0)] * 3 + [1.0 / (np.sqrt(3.0))] * 6 + [1.0]) + elif angmom == 4: + factors = np.array( + [1.0 / np.sqrt(105.0)] * 3 + + [1.0 / (np.sqrt(15.0))] * 6 + + [1.0 / 3.0] * 3 + + [1.0 / (np.sqrt(3.0))] * 3 + ) + if factors is None: + factors = np.ones(shell.nbasis) + else: + assert len(factors) == shell.nbasis + corrected = True + correction.append(factors) + if corrected: + return np.concatenate(correction) + return None + + +def _fix_molden_from_buggy_codes(result: dict, lit: LineIterator, norm_threshold: float = 1e-4): """Detect errors in the data loaded from a molden or mkl file and correct. This function can recognize erroneous files created by PSI4, ORCA and @@ -561,102 +644,126 @@ def _fix_molden_from_buggy_codes(result: dict, lit: LineIterator): A dictionary with the data loaded in the ``load_molden`` function. lit The line iterator to read the data from, used for warnings. + norm_threshold + When the error on one of the orbitals norm exceeds norm_threshold, + the (corrected) data loaded from the Molden file is considered to be + incorrect, in which case other corrections are tested or an exception + is raised when no more corrections can be applied. """ - obasis = result['obasis'] - atcoords = result['atcoords'] - if result['mo'].kind == 'restricted': - coeffsa = result['mo'].coeffs + obasis = result["obasis"] + atcoords = result["atcoords"] + if result["mo"].kind == "restricted": + coeffsa = result["mo"].coeffs # Skip testing coeffsb if it is the same array as coeffsa. coeffsb = None - elif result['mo'].kind == 'unrestricted': - coeffsa = result['mo'].coeffsa - coeffsb = result['mo'].coeffsb + elif result["mo"].kind == "unrestricted": + coeffsa = result["mo"].coeffsa + coeffsb = result["mo"].coeffsb else: - raise ValueError('Molecular orbital kind={0} not recognized'.format(result['mo'].kind)) + raise ValueError("Molecular orbital kind={} not recognized".format(result["mo"].kind)) - if _is_normalized_properly(obasis, atcoords, coeffsa, coeffsb): + if _is_normalized_properly(obasis, atcoords, coeffsa, coeffsb, norm_threshold): # The file is good. No need to change obasis. return # --- ORCA orca_obasis = _fix_obasis_orca(obasis) - if _is_normalized_properly(orca_obasis, atcoords, coeffsa, coeffsb): - lit.warn('Corrected for typical ORCA errors in Molden/MKL file.') - result['obasis'] = orca_obasis + if _is_normalized_properly(orca_obasis, atcoords, coeffsa, coeffsb, norm_threshold): + lit.warn("Corrected for typical ORCA errors in Molden/MKL file.") + result["obasis"] = orca_obasis return # --- PSI4 < 1.0 psi4_obasis = _fix_obasis_psi4(obasis) - if psi4_obasis is not None and \ - _is_normalized_properly(psi4_obasis, atcoords, coeffsa, coeffsb): - lit.warn('Corrected for PSI4 < 1.0 errors in Molden/MKL file.') - result['obasis'] = psi4_obasis + if psi4_obasis is not None and _is_normalized_properly( + psi4_obasis, atcoords, coeffsa, coeffsb, norm_threshold + ): + lit.warn("Corrected for PSI4 < 1.0 errors in Molden/MKL file.") + result["obasis"] = psi4_obasis return # -- Turbomole turbom_obasis = _fix_obasis_turbomole(obasis) - if turbom_obasis is not None and \ - _is_normalized_properly(turbom_obasis, atcoords, coeffsa, coeffsb): - lit.warn('Corrected for Turbomole errors in Molden/MKL file.') - result['obasis'] = turbom_obasis + if turbom_obasis is not None and _is_normalized_properly( + turbom_obasis, atcoords, coeffsa, coeffsb, norm_threshold + ): + lit.warn("Corrected for Turbomole errors in Molden/MKL file.") + result["obasis"] = turbom_obasis return + # --- CFOUR 2.1 + cfour_coeff_correction = _fix_mo_coeffs_cfour(obasis) + if cfour_coeff_correction is not None: + coeffsa_cfour = coeffsa / cfour_coeff_correction[:, np.newaxis] + coeffsb_cfour = None if coeffsb is None else coeffsb / cfour_coeff_correction[:, np.newaxis] + if _is_normalized_properly(obasis, atcoords, coeffsa_cfour, coeffsb_cfour, norm_threshold): + lit.warn("Corrected for CFOUR 2.1 errors in Molden/MKL file.") + result["obasis"] = obasis + if result["mo"].kind == "restricted": + result["mo"].coeffs[:] = coeffsa_cfour + else: + result["mo"].coeffsa[:] = coeffsa_cfour + result["mo"].coeffsb[:] = coeffsb_cfour + return + # --- Renormalized contractions normed_obasis = _fix_obasis_normalize_contractions(obasis) - if _is_normalized_properly(normed_obasis, atcoords, coeffsa, coeffsb): - lit.warn('Corrected for unnormalized contractions in Molden/MKL file.') - result['obasis'] = normed_obasis + if _is_normalized_properly(normed_obasis, atcoords, coeffsa, coeffsb, norm_threshold): + lit.warn("Corrected for unnormalized contractions in Molden/MKL file.") + result["obasis"] = normed_obasis return # --- PSI4 <= 1.3.2 psi4_coeff_correction = _fix_mo_coeffs_psi4(obasis) if psi4_coeff_correction is not None: coeffsa_psi4 = coeffsa / psi4_coeff_correction[:, np.newaxis] - if coeffsb is None: - coeffsb_psi4 = None - else: - coeffsb_psi4 = coeffsb / psi4_coeff_correction[:, np.newaxis] - if _is_normalized_properly(normed_obasis, atcoords, coeffsa_psi4, coeffsb_psi4): - lit.warn('Corrected for PSI4 <= 1.3.2 errors in Molden/MKL file.') - result['obasis'] = normed_obasis - if result['mo'].kind == 'restricted': - result['mo'].coeffs[:] = coeffsa_psi4 + coeffsb_psi4 = None if coeffsb is None else coeffsb / psi4_coeff_correction[:, np.newaxis] + if _is_normalized_properly( + normed_obasis, atcoords, coeffsa_psi4, coeffsb_psi4, norm_threshold + ): + lit.warn("Corrected for PSI4 <= 1.3.2 errors in Molden/MKL file.") + result["obasis"] = normed_obasis + if result["mo"].kind == "restricted": + result["mo"].coeffs[:] = coeffsa_psi4 else: - result['mo'].coeffsa[:] = coeffsa_psi4 - result['mo'].coeffsb[:] = coeffsb_psi4 + result["mo"].coeffsa[:] = coeffsa_psi4 + result["mo"].coeffsb[:] = coeffsb_psi4 return - lit.error('Could not correct the data read from {}. The molden or mkl file ' - 'you are trying to load contains errors. Please make an issue ' - 'here: https://github.com/theochem/iodata/issues, and attach ' - 'this file. Please provide one or more small files causing this ' - 'error. Thanks!') + lit.error( + "Could not correct the data read from {}. The molden or mkl file " + "you are trying to load contains errors. Please make an issue " + "here: https://github.com/theochem/iodata/issues, and attach " + "this file. Please provide one or more small files causing this " + "error. Thanks!" + ) -@document_dump_one("Molden", ['atcoords', 'atnums', 'mo', 'obasis'], ['atcorenums', 'title']) +@document_dump_one("Molden", ["atcoords", "atnums", "mo", "obasis"], ["atcorenums", "title"]) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" # Print the header - f.write('[Molden Format]\n') + f.write("[Molden Format]\n") if data.title is not None: - f.write('[Title]\n') - f.write(' {}\n'.format(data.title)) + f.write("[Title]\n") + f.write(f" {data.title}\n") # Print the elements numbers and the coordinates - f.write('[Atoms] AU\n') + f.write("[Atoms] AU\n") for iatom in range(data.natom): atnum = data.atnums[iatom] atcorenum = data.atcorenums[iatom] x, y, z = data.atcoords[iatom] - f.write('{:2s} {:3d} {:3.0f} {:25.18f} {:25.18f} {:25.18f}\n'.format( - num2sym[atnum].ljust(2), iatom + 1, atcorenum, x, y, z - )) - f.write('\n') + f.write( + f"{num2sym[atnum].ljust(2):2s} {iatom + 1:3d} {atcorenum:3.0f} " + f"{x:25.18f} {y:25.18f} {z:25.18f}\n" + ) + f.write("\n") # Print the basis set if data.obasis is None: - raise IOError('A Gaussian orbital basis is required to write a molden file.') + raise OSError("A Gaussian orbital basis is required to write a molden file.") obasis = data.obasis # Figure out the pure/Cartesian situation. Note that the Molden @@ -668,31 +775,31 @@ def dump_one(f: TextIO, data: IOData): for angmom, kind in zip(shell.angmoms, shell.kinds): if angmom in angmom_kinds: if kind != angmom_kinds[angmom]: - raise IOError('Molden format does not support mixed ' - 'pure+Cartesian functions for one ' - 'angular momentum.') + raise OSError( + "Molden format does not support mixed pure+Cartesian functions for one " + "angular momentum." + ) else: angmom_kinds[angmom] = kind # Fill in some defaults (Cartesian) for angmom kinds if needed. - angmom_kinds.setdefault(2, 'c') - angmom_kinds.setdefault(3, 'c') - angmom_kinds.setdefault(4, 'c') - angmom_kinds.setdefault(5, 'c') + angmom_kinds.setdefault(2, "c") + angmom_kinds.setdefault(3, "c") + angmom_kinds.setdefault(4, "c") + angmom_kinds.setdefault(5, "c") # Write out the Cartesian/Pure conventions. What a messy format... - if angmom_kinds[2] == 'p': - if angmom_kinds[3] == 'p': - f.write('[5D]\n') + if angmom_kinds[2] == "p": + if angmom_kinds[3] == "p": + f.write("[5D]\n") else: - f.write('[5D10F]\n') - else: - if angmom_kinds[3] == 'p': - f.write('[7F]\n') - if angmom_kinds[4] == 'p': - f.write('[9G]\n') + f.write("[5D10F]\n") + elif angmom_kinds[3] == "p": + f.write("[7F]\n") + if angmom_kinds[4] == "p": + f.write("[9G]\n") - f.write('[GTO]\n') + f.write("[GTO]\n") last_icenter = -1 # The shells must be sorted by center. for shell in sorted(obasis.shells, key=(lambda s: s.icenter)): @@ -700,54 +807,69 @@ def dump_one(f: TextIO, data: IOData): if last_icenter != -1: f.write("\n") last_icenter = shell.icenter - f.write('%3i 0\n' % (shell.icenter + 1)) + f.write("%3i 0\n" % (shell.icenter + 1)) # Write out as a segmented basis. Molden format does not support # generalized contractions. for iangmom, angmom in enumerate(shell.angmoms): - f.write(' {:1s} {:3d} 1.00\n'.format(angmom_its(angmom), shell.nprim)) + f.write(f" {angmom_its(angmom):1s} {shell.nprim:3d} 1.00\n") for exponent, coeff in zip(shell.exponents, shell.coeffs[:, iangmom]): - f.write('{:20.10f} {:20.10f}\n'.format(exponent, coeff)) + f.write(f"{exponent:20.10f} {coeff:20.10f}\n") f.write("\n") # Get the permutation to convert the orbital coefficients to Molden conventions. permutation, signs = convert_conventions(obasis, CONVENTIONS) # Print the mean-field orbitals - if data.mo.kind == 'unrestricted': - f.write('[MO]\n') + if data.mo.kind == "unrestricted": + f.write("[MO]\n") irrepsa = data.mo.irrepsa if irrepsa is None: - irrepsa = ['1a'] * data.mo.norba - _dump_helper_orb(f, 'Alpha', data.mo.occsa, - data.mo.coeffsa[permutation] * signs.reshape(-1, 1), - data.mo.energiesa, irrepsa) + irrepsa = ["1a"] * data.mo.norba + _dump_helper_orb( + f, + "Alpha", + data.mo.occsa, + data.mo.coeffsa[permutation] * signs.reshape(-1, 1), + data.mo.energiesa, + irrepsa, + ) irrepsb = data.mo.irrepsb if irrepsb is None: - irrepsb = ['1a'] * data.mo.norbb - _dump_helper_orb(f, 'Beta', data.mo.occsb, - data.mo.coeffsb[permutation] * signs.reshape(-1, 1), - data.mo.energiesb, irrepsb) - elif data.mo.kind == 'restricted': - f.write('[MO]\n') + irrepsb = ["1a"] * data.mo.norbb + _dump_helper_orb( + f, + "Beta", + data.mo.occsb, + data.mo.coeffsb[permutation] * signs.reshape(-1, 1), + data.mo.energiesb, + irrepsb, + ) + elif data.mo.kind == "restricted": + f.write("[MO]\n") irreps = data.mo.irreps if irreps is None: - irreps = ['1a'] * data.mo.norb - _dump_helper_orb(f, 'Alpha', data.mo.occs, - data.mo.coeffs[permutation] * signs.reshape(-1, 1), - data.mo.energies, irreps) + irreps = ["1a"] * data.mo.norb + _dump_helper_orb( + f, + "Alpha", + data.mo.occs, + data.mo.coeffs[permutation] * signs.reshape(-1, 1), + data.mo.energies, + irreps, + ) else: raise NotImplementedError def _dump_helper_orb(f, spin, occs, coeffs, energies, irreps): for ifn in range(coeffs.shape[1]): - f.write(f' Ene= {energies[ifn]:.17e}\n') - f.write(f' Sym= {irreps[ifn]}\n') - f.write(f' Spin= {spin}\n') - f.write(f' Occup= {occs[ifn]:.17e}\n') + f.write(f" Ene= {energies[ifn]:.17e}\n") + f.write(f" Sym= {irreps[ifn]}\n") + f.write(f" Spin= {spin}\n") + f.write(f" Occup= {occs[ifn]:.17e}\n") for ibasis in range(coeffs.shape[0]): # The original molden floating-point formatting is too low # precision. Molden also reads high-precision, so we use this # instead. # f.write('{:4d} {:10.6f}\n'.format(ibasis + 1, orb_coeffs[ibasis, ifn])) - f.write('{:4d} {:.17e}\n'.format(ibasis + 1, coeffs[ibasis, ifn])) + f.write(f"{ibasis + 1:4d} {coeffs[ibasis, ifn]:.17e}\n") diff --git a/iodata/formats/molekel.py b/iodata/formats/molekel.py index 243e6ad15..299a6c0f0 100644 --- a/iodata/formats/molekel.py +++ b/iodata/formats/molekel.py @@ -23,26 +23,26 @@ `Orca `_. """ -from typing import Tuple, List, TextIO +from typing import TextIO import numpy as np +from numpy.typing import NDArray -from .molden import CONVENTIONS, _fix_molden_from_buggy_codes -from ..basis import angmom_sti, angmom_its, convert_conventions, MolecularBasis, Shell -from ..docstrings import document_load_one, document_dump_one +from ..basis import MolecularBasis, Shell, angmom_its, angmom_sti, convert_conventions +from ..docstrings import document_dump_one, document_load_one from ..iodata import IOData from ..orbitals import MolecularOrbitals -from ..utils import angstrom, LineIterator - +from ..utils import LineIterator, angstrom +from .molden import CONVENTIONS, _fix_molden_from_buggy_codes __all__ = [] -PATTERNS = ['*.mkl'] +PATTERNS = ["*.mkl"] -def _load_helper_charge_spinpol(lit: LineIterator) -> List[int]: - charge, spinmult = [int(word) for word in next(lit).split()] +def _load_helper_charge_spinpol(lit: LineIterator) -> list[int]: + charge, spinmult = (int(word) for word in next(lit).split()) spinpol = spinmult - 1 return charge, spinpol @@ -50,19 +50,18 @@ def _load_helper_charge_spinpol(lit: LineIterator) -> List[int]: def _load_helper_charges(lit: LineIterator) -> dict: atcharges = [] for line in lit: - line = line.strip() - if line == '$END': + if line.strip() == "$END": break atcharges.append(float(line)) - return {'mulliken': np.array(atcharges)} + return {"mulliken": np.array(atcharges)} -def _load_helper_atoms(lit: LineIterator) -> Tuple[np.ndarray, np.ndarray]: +def _load_helper_atoms(lit: LineIterator) -> tuple[NDArray, NDArray]: atnums = [] atcoords = [] for line in lit: - if line.strip() == '$END': + if line.strip() == "$END": break words = line.split() atnums.append(int(words[0])) @@ -77,24 +76,23 @@ def _load_helper_obasis(lit: LineIterator) -> MolecularBasis: icenter = 0 while True: line = next(lit).strip() - if line == '$END': + if line == "$END": break if line == "": continue - if line == '$$': + if line == "$$": icenter += 1 continue # Shell header, always assuming pure functions words = line.split() angmom = angmom_sti(words[1]) nbasis_shell = int(words[0]) - if nbasis_shell == len(CONVENTIONS[(angmom, 'c')]): - kind = 'c' - elif nbasis_shell == len(CONVENTIONS[(angmom, 'p')]): - kind = 'p' + if nbasis_shell == len(CONVENTIONS[(angmom, "c")]): + kind = "c" + elif nbasis_shell == len(CONVENTIONS[(angmom, "p")]): + kind = "p" else: - lit.error('Cannot interpret angmom={} with nbasis_shell={}'.format( - angmom, nbasis_shell)) + lit.error(f"Cannot interpret angmom={angmom} with nbasis_shell={nbasis_shell}") exponents = [] coeffs = [] for line in lit: @@ -105,34 +103,31 @@ def _load_helper_obasis(lit: LineIterator) -> MolecularBasis: exponents.append(float(words[0])) coeffs.append([float(words[1])]) shells.append(Shell(icenter, [angmom], [kind], np.array(exponents), np.array(coeffs))) - return MolecularBasis(shells, CONVENTIONS, 'L2') + return MolecularBasis(shells, CONVENTIONS, "L2") -def _load_helper_coeffs(lit: LineIterator, nbasis: int) -> Tuple[np.ndarray, np.ndarray]: +def _load_helper_coeffs(lit: LineIterator, nbasis: int) -> tuple[NDArray, NDArray]: coeffs = [] energies = [] irreps = [] in_orb = 0 for line in lit: - line = line.strip() - if line == '$END': + if line.strip() == "$END": break if in_orb == 0: # read a1g line words = line.split() ncol = len(words) assert ncol > 0 - for word in words: - irreps.append(word) + irreps.extend(words) cols = [np.zeros((nbasis, 1), float) for _ in range(ncol)] in_orb = 1 elif in_orb == 1: # read energies words = line.split() assert len(words) == ncol - for word in words: - energies.append(float(word)) + energies.extend(float(word) for word in words) in_orb = 2 ibasis = 0 elif in_orb == 2: @@ -149,20 +144,26 @@ def _load_helper_coeffs(lit: LineIterator, nbasis: int) -> Tuple[np.ndarray, np. return np.hstack(coeffs), np.array(energies), irreps -def _load_helper_occ(lit: LineIterator) -> np.ndarray: +def _load_helper_occ(lit: LineIterator) -> NDArray: occs = [] for line in lit: - line = line.strip() - if line == '$END': + if line.strip() == "$END": break - for word in line.split(): - occs.append(float(word)) + occs.extend(float(word) for word in line.split()) return np.array(occs) -# pylint: disable=too-many-branches,too-many-statements -@document_load_one("Molekel", ['atcoords', 'atnums', 'mo', 'obasis'], ['atcharges']) -def load_one(lit: LineIterator) -> dict: +@document_load_one( + "Molekel", + ["atcoords", "atnums", "mo", "obasis"], + ["atcharges"], + { + "norm_threshold": "When the normalization of one of the orbitals exceeds " + "norm_threshold, a correction is attempted or an error " + "is raised when no suitable correction can be found." + }, +) +def load_one(lit: LineIterator, norm_threshold: float = 1e-4) -> dict: """Do not edit this docstring. It will be overwritten.""" charge = None atnums = None @@ -186,33 +187,33 @@ def load_one(lit: LineIterator) -> dict: # There is no file-end marker we can use, so we only stop when # reaching the end of the file. break - if line == '$CHAR_MULT': + if line == "$CHAR_MULT": charge, spinpol = _load_helper_charge_spinpol(lit) - elif line == '$CHARGES': + elif line == "$CHARGES": atcharges = _load_helper_charges(lit) - elif line == '$COORD': + elif line == "$COORD": atnums, atcoords = _load_helper_atoms(lit) - elif line == '$BASIS': + elif line == "$BASIS": obasis = _load_helper_obasis(lit) - elif line == '$COEFF_ALPHA': + elif line == "$COEFF_ALPHA": coeffsa, energiesa, irrepsa = _load_helper_coeffs(lit, obasis.nbasis) - elif line == '$OCC_ALPHA': + elif line == "$OCC_ALPHA": occsa = _load_helper_occ(lit) - elif line == '$COEFF_BETA': + elif line == "$COEFF_BETA": coeffsb, energiesb, irrepsb = _load_helper_coeffs(lit, obasis.nbasis) - elif line == '$OCC_BETA': + elif line == "$OCC_BETA": occsb = _load_helper_occ(lit) if charge is None: - lit.error('Charge and spin polarization not found.') + lit.error("Charge and spin polarization not found.") if atcoords is None: - lit.error('Coordinates not found.') + lit.error("Coordinates not found.") if obasis is None: - lit.error('Orbital basis not found.') + lit.error("Orbital basis not found.") if coeffsa is None: - lit.error('Alpha orbitals not found.') + lit.error("Alpha orbitals not found.") if occsa is None: - lit.error('Alpha occupation numbers not found.') + lit.error("Alpha occupation numbers not found.") nelec = atnums.sum() - charge if coeffsb is None: @@ -220,12 +221,13 @@ def load_one(lit: LineIterator) -> dict: assert nelec % 2 == 0 assert abs(occsa.sum() - nelec) < 1e-7 mo = MolecularOrbitals( - 'restricted', coeffsa.shape[1], coeffsa.shape[1], - occsa, coeffsa, energiesa, irrepsa) + "restricted", coeffsa.shape[1], coeffsa.shape[1], occsa, coeffsa, energiesa, irrepsa + ) else: if occsb is None: - lit.error('Beta occupation numbers not found in mkl file while ' - 'beta orbitals were present.') + lit.error( + "Beta occupation numbers not found in mkl file while beta orbitals were present." + ) nalpha = int(np.round(occsa.sum())) nbeta = int(np.round(occsb.sum())) assert abs(spinpol - abs(nalpha - nbeta)) < 1e-7 @@ -234,100 +236,101 @@ def load_one(lit: LineIterator) -> dict: assert energiesa.shape == energiesb.shape assert occsa.shape == occsb.shape mo = MolecularOrbitals( - 'unrestricted', + "unrestricted", coeffsa.shape[1], coeffsb.shape[1], np.concatenate((occsa, occsb), axis=0), np.concatenate((coeffsa, coeffsb), axis=1), np.concatenate((energiesa, energiesb), axis=0), - irrepsa + irrepsb) + irrepsa + irrepsb, + ) result = { - 'atcoords': atcoords, - 'atnums': atnums, - 'obasis': obasis, - 'mo': mo, - 'atcharges': atcharges, + "atcoords": atcoords, + "atnums": atnums, + "obasis": obasis, + "mo": mo, + "atcharges": atcharges, } - _fix_molden_from_buggy_codes(result, lit) + _fix_molden_from_buggy_codes(result, lit, norm_threshold) return result -@document_dump_one("Molekel", ['atcoords', 'atnums', 'mo', 'obasis'], ['atcharges']) +@document_dump_one("Molekel", ["atcoords", "atnums", "mo", "obasis"], ["atcharges"]) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" # Header - f.write('$MKL\n') - f.write('#\n') - f.write('# MKL format file produced by IOData\n') - f.write('#\n') + f.write("$MKL\n") + f.write("#\n") + f.write("# MKL format file produced by IOData\n") + f.write("#\n") # CHAR_MUL - f.write('$CHAR_MULT\n') - f.write(' {:.0f} {:.0f}\n'.format(data.charge, data.spinpol + 1)) - f.write('$END\n') - f.write('\n') + f.write("$CHAR_MULT\n") + f.write(f" {data.charge:.0f} {data.spinpol + 1:.0f}\n") + f.write("$END\n") + f.write("\n") # COORD atcoords = data.atcoords / angstrom - f.write('$COORD\n') + f.write("$COORD\n") for n, coord in zip(data.atnums, atcoords): - f.write(' {:d} {: ,.6f} {: ,.6f} {: ,.6f}\n'.format(n, coord[0], coord[1], coord[2])) - f.write('$END\n') - f.write('\n') + f.write(f" {n:d} {coord[0]: ,.6f} {coord[1]: ,.6f} {coord[2]: ,.6f}\n") + f.write("$END\n") + f.write("\n") # CHARGES - if 'mulliken' in data.atcharges: - f.write('$CHARGES\n') - for charge in data.atcharges['mulliken']: - f.write(' {: ,.6f}\n'.format(charge)) - f.write('$END\n') - f.write('\n') + if "mulliken" in data.atcharges: + f.write("$CHARGES\n") + for charge in data.atcharges["mulliken"]: + f.write(f" {charge: ,.6f}\n") + f.write("$END\n") + f.write("\n") # BASIS - f.write('$BASIS\n') + f.write("$BASIS\n") iatom_last = 0 for shell in data.obasis.shells: iatom_new = shell.icenter if iatom_new != iatom_last: - f.write('$$\n') + f.write("$$\n") for iangmom, (angmom, kind) in enumerate(zip(shell.angmoms, shell.kinds)): iatom_last = shell.icenter nbasis = len(CONVENTIONS[(angmom, kind)]) - f.write(' {} {:1s} 1.00\n'.format(nbasis, angmom_its(angmom).capitalize())) + f.write(f" {nbasis} {angmom_its(angmom).capitalize():1s} 1.00\n") for exponent, coeff in zip(shell.exponents, shell.coeffs[:, iangmom]): - f.write('{:20.10f} {:17.10f}\n'.format(exponent, coeff)) - f.write('\n') - f.write('$END\n') - f.write('\n') + f.write(f"{exponent:20.10f} {coeff:17.10f}\n") + f.write("\n") + f.write("$END\n") + f.write("\n") - if data.mo.kind == 'restricted': + if data.mo.kind == "restricted": # COEFF_ALPHA - f.write('$COEFF_ALPHA\n') - _dump_helper_coeffs(f, data, spin='a') + f.write("$COEFF_ALPHA\n") + _dump_helper_coeffs(f, data, spin="a") # OCC_ALPHA - f.write('$OCC_ALPHA\n') - _dump_helper_occ(f, data, spin='ab') + f.write("$OCC_ALPHA\n") + _dump_helper_occ(f, data, spin="ab") # Not taking into account generalized. - elif data.mo.kind == 'unrestricted': + elif data.mo.kind == "unrestricted": # COEFF_ALPHA - f.write('$COEFF_ALPHA\n') - _dump_helper_coeffs(f, data, spin='a') + f.write("$COEFF_ALPHA\n") + _dump_helper_coeffs(f, data, spin="a") # OCC_ALPHA - f.write('$OCC_ALPHA\n') - _dump_helper_occ(f, data, spin='a') - f.write('\n') + f.write("$OCC_ALPHA\n") + _dump_helper_occ(f, data, spin="a") + f.write("\n") # COEFF_BETA - f.write('$COEFF_BETA\n') - _dump_helper_coeffs(f, data, spin='b') + f.write("$COEFF_BETA\n") + _dump_helper_coeffs(f, data, spin="b") # OCC_BETA - f.write('$OCC_BETA\n') - _dump_helper_occ(f, data, spin='b') + f.write("$OCC_BETA\n") + _dump_helper_occ(f, data, spin="b") else: raise ValueError(f"The MKL format does not support {data.mo.kind} orbitals.") @@ -336,52 +339,46 @@ def dump_one(f: TextIO, data: IOData): # Defining help dumping functions def _dump_helper_coeffs(f, data, spin=None): permutation, signs = convert_conventions(data.obasis, CONVENTIONS) - if spin == 'a': + if spin == "a": norb = data.mo.norba coeff = data.mo.coeffsa[permutation] * signs.reshape(-1, 1) ener = data.mo.energiesa - if data.mo.irreps is not None: - irreps = data.mo.irreps[:norb] - else: - irreps = ['a1g'] * norb - elif spin == 'b': + irreps = data.mo.irreps[:norb] if data.mo.irreps is not None else ["a1g"] * norb + elif spin == "b": norb = data.mo.norbb coeff = data.mo.coeffsb[permutation] * signs.reshape(-1, 1) ener = data.mo.energiesb - if data.mo.irreps is not None: - irreps = data.mo.irreps[norb:] - else: - irreps = ['a1g'] * norb + irreps = data.mo.irreps[norb:] if data.mo.irreps is not None else ["a1g"] * norb else: - raise IOError('A spin must be specified') + raise OSError("A spin must be specified") for j in range(0, norb, 5): - en = ' '.join([' {: ,.12f}'.format(e) for e in ener[j:j + 5]]) - irre = ' '.join(['{}'.format(irr) for irr in irreps[j:j + 5]]) - f.write(irre + '\n') - f.write(en + '\n') - for orb in coeff[:, j:j + 5]: - coeffs = ' '.join([' {: ,.12f}'.format(c) for c in orb]) - f.write(coeffs + '\n') + en = " ".join([f" {e: ,.12f}" for e in ener[j : j + 5]]) + irre = " ".join([f"{irr}" for irr in irreps[j : j + 5]]) + f.write(irre + "\n") + f.write(en + "\n") + for orb in coeff[:, j : j + 5]: + coeffs = " ".join([f" {c: ,.12f}" for c in orb]) + f.write(coeffs + "\n") - f.write(' $END\n') - f.write('\n') + f.write(" $END\n") + f.write("\n") def _dump_helper_occ(f, data, spin=None): - if spin == 'a': + if spin == "a": norb = data.mo.norba occ = data.mo.occsa - elif spin == 'b': + elif spin == "b": norb = data.mo.norbb occ = data.mo.occsb - elif spin == 'ab': + elif spin == "ab": norb = data.mo.norba occ = data.mo.occs else: - raise IOError('A spin must be specified') + raise OSError("A spin must be specified") for j in range(0, norb, 5): - occs = ' '.join([' {: ,.7f}'.format(o) for o in occ[j:j + 5]]) - f.write(occs + '\n') - f.write(' $END\n') + occs = " ".join([f" {o: ,.7f}" for o in occ[j : j + 5]]) + f.write(occs + "\n") + f.write(" $END\n") diff --git a/iodata/formats/mwfn.py b/iodata/formats/mwfn.py index e72be2fb1..6ede364ff 100644 --- a/iodata/formats/mwfn.py +++ b/iodata/formats/mwfn.py @@ -18,19 +18,18 @@ # -- """Multiwfn MWFN file format.""" - import numpy as np +from numpy.typing import NDArray from ..basis import HORTON2_CONVENTIONS, MolecularBasis, Shell from ..docstrings import document_load_one from ..orbitals import MolecularOrbitals from ..utils import LineIterator, angstrom - __all__ = [] -PATTERNS = ['*.mwfn'] +PATTERNS = ["*.mwfn"] # From the MWFN chemrxiv paper @@ -49,6 +48,8 @@ # F shell: F 0, F+1, F-1, F+2, F-2, F+3, F-3 # G shell: G 0, G+1, G-1, G+2, G-2, G+3, G-3, G+4, G-4 + +# fmt: off CONVENTIONS = { (4, 'p'): HORTON2_CONVENTIONS[(4, 'p')], (3, 'p'): HORTON2_CONVENTIONS[(3, 'p')], @@ -63,12 +64,20 @@ 'xyyzz', 'xyyyz', 'xyyyy', 'xxzzz', 'xxyzz', 'xxyyz', 'xxyyy', 'xxxzz', 'xxxyz', 'xxxyy', 'xxxxz', 'xxxxy', 'xxxxx'], } +# fmt: on def _load_helper_opener(lit: LineIterator) -> dict: """Read initial variables at the beginning of a MWFN file.""" - keys = {"Wfntype": int, "Charge": float, "Naelec": float, "Nbelec": float, "E_tot": float, - "VT_ratio": float, "Ncenter": int} + keys = { + "Wfntype": int, + "Charge": float, + "Naelec": float, + "Nbelec": float, + "E_tot": float, + "VT_ratio": float, + "Ncenter": int, + } max_count = len(keys) count = 0 data = {} @@ -76,7 +85,7 @@ def _load_helper_opener(lit: LineIterator) -> dict: line = next(lit) for name, ftype in keys.items(): if name in line: - data[name] = ftype(line.split('=')[1].strip()) + data[name] = ftype(line.split("=")[1].strip()) count += 1 # check values parsed @@ -111,7 +120,7 @@ def _load_helper_basis(lit: LineIterator) -> dict: line = next(lit) for name in keys: if name in line: - data[name] = int(line.split('=')[1].strip()) + data[name] = int(line.split("=")[1].strip()) count += 1 break return data @@ -127,7 +136,7 @@ def _load_helper_atoms(lit: LineIterator, natom: int) -> dict: # skip lines until "$Centers" section is reached line = next(lit) - while '$Centers' not in line and line is not None: + while "$Centers" not in line and line is not None: line = next(lit) for atom in range(natom): @@ -159,14 +168,15 @@ def _load_helper_shells(lit: LineIterator, nshell: int) -> dict: for section, name in zip(sections, var_name): if not line.startswith(section): lit.error(f"Expected line to start with {section}, but got line={line}.") - data[name] = _load_helper_section(lit, nshell, ' ', 0, int) + data[name] = _load_helper_section(lit, nshell, " ", 0, int) line = next(lit) lit.back(line) return data -def _load_helper_section(lit: LineIterator, nprim: int, start: str, skip: int, - dtype: np.dtype) -> np.ndarray: +def _load_helper_section( + lit: LineIterator, nprim: int, start: str, skip: int, dtype: np.dtype +) -> NDArray: """Read single or multiple line(s) section.""" section = [] while len(section) < nprim: @@ -190,9 +200,9 @@ def _load_helper_mo(lit: LineIterator, n_basis: int, n_mo: int) -> dict: for index in range(n_mo): line = next(lit) - while 'Index' not in line: + while "Index" not in line: line = next(lit) - assert line.startswith('Index') + assert line.startswith("Index") data["mo_numbers"][index] = line.split()[1] data["mo_type"][index] = next(lit).split()[1] data["mo_energies"][index] = next(lit).split()[1] @@ -200,7 +210,7 @@ def _load_helper_mo(lit: LineIterator, n_basis: int, n_mo: int) -> dict: data["mo_sym"][index] = next(lit).split()[1] # skip "$Coeff line next(lit) - data["mo_coeffs"][:, index] = _load_helper_section(lit, n_basis, '', 0, float) + data["mo_coeffs"][:, index] = _load_helper_section(lit, n_basis, "", 0, float) return data @@ -241,10 +251,10 @@ def _load_mwfn_low(lit: LineIterator) -> dict: # load primitive exponents & coefficients if not next(lit).startswith("$Primitive exponents"): lit.error("Expected '$Primitive exponents' section!") - data["exponents"] = _load_helper_section(lit, data["Nprimshell"], '', 0, float) + data["exponents"] = _load_helper_section(lit, data["Nprimshell"], "", 0, float) if not next(lit).startswith("$Contraction coefficients"): lit.error("Expected '$Contraction coefficients' section!") - data["coeffs"] = _load_helper_section(lit, data["Nprimshell"], '', 0, float) + data["coeffs"] = _load_helper_section(lit, data["Nprimshell"], "", 0, float) # get number of basis & molecular orbitals (MO) # Note: MWFN includes virtual orbitals, so num_mo equals number independent basis functions @@ -258,36 +268,43 @@ def _load_mwfn_low(lit: LineIterator) -> dict: return data -@document_load_one("MWFN", ['atcoords', 'atnums', 'atcorenums', 'energy', - 'mo', 'obasis', 'extra', 'title']) +@document_load_one( + "MWFN", ["atcoords", "atnums", "atcorenums", "energy", "mo", "obasis", "extra", "title"] +) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" inp = _load_mwfn_low(lit) # store certain information loaded from MWFN in extra dictionary - extra = {'wfntype': inp['Wfntype'], - 'nindbasis': inp['Nindbasis'], - 'mo_sym': inp['mo_sym'], - 'full_virial_ratio': inp['VT_ratio']} + extra = { + "wfntype": inp["Wfntype"], + "nindbasis": inp["Nindbasis"], + "mo_sym": inp["mo_sym"], + "full_virial_ratio": inp["VT_ratio"], + } # Build MolecularBasis instance # Unlike WFN, MWFN does include orbital expansion coefficients. shells = [] counter = 0 - for center, stype, ncon in zip(inp['shell_centers'], inp['shell_types'], inp['shell_ncons']): - shells.append(Shell( - center, - [abs(stype)], - ['p' if stype < 0 else 'c'], - inp['exponents'][counter:counter + ncon], - inp['coeffs'][counter:counter + ncon][:, np.newaxis] - )) + for center, stype, ncon in zip(inp["shell_centers"], inp["shell_types"], inp["shell_ncons"]): + shells.append( + Shell( + center, + [abs(stype)], + ["p" if stype < 0 else "c"], + inp["exponents"][counter : counter + ncon], + inp["coeffs"][counter : counter + ncon][:, np.newaxis], + ) + ) counter += ncon - obasis = MolecularBasis(shells, CONVENTIONS, 'L2') + obasis = MolecularBasis(shells, CONVENTIONS, "L2") # check number of basis functions if obasis.nbasis != inp["Nbasis"]: - raise ValueError(f"Number of basis in MolecularBasis is not equal to the 'Nbasis'. " - f"{obasis.nbasis} != {inp['Nbasis']}") + raise ValueError( + f"Number of basis in MolecularBasis is not equal to the 'Nbasis'. " + f"{obasis.nbasis} != {inp['Nbasis']}" + ) # Determine number of orbitals of each kind. if inp["mo_kind"] == "restricted": @@ -303,27 +320,32 @@ def load_one(lit: LineIterator) -> dict: assert (inp["mo_type"] == 0).sum() == 0 # Build MolecularOrbitals instance mo = MolecularOrbitals( - inp["mo_kind"], norba, norbb, inp['mo_occs'], inp['mo_coeffs'], - inp['mo_energies'], None + inp["mo_kind"], norba, norbb, inp["mo_occs"], inp["mo_coeffs"], inp["mo_energies"], None ) # check number of electrons - if mo.nelec != inp['Naelec'] + inp['Nbelec']: - raise ValueError(f"Number of electrons in MolecularOrbitals is not equal to the sum of " - f"'Naelec' and 'Nbelec'. {mo.nelec} != {inp['Naelec']} + {inp['Nbelec']}") - if mo.occsa.sum() != inp['Naelec']: - raise ValueError(f"Number of alpha electrons in MolecularOrbitals is not equal to the " - f"'Naelec'. {mo.occsa.sum()} != {inp['Naelec']}") - if mo.occsb.sum() != inp['Nbelec']: - raise ValueError(f"Number of beta electrons in MolecularOrbitals is not equal to the " - f"'Nbelec'. {mo.occsb.sum()} != {inp['Nbelec']}") + if mo.nelec != inp["Naelec"] + inp["Nbelec"]: + raise ValueError( + f"Number of electrons in MolecularOrbitals is not equal to the sum of " + f"'Naelec' and 'Nbelec'. {mo.nelec} != {inp['Naelec']} + {inp['Nbelec']}" + ) + if mo.occsa.sum() != inp["Naelec"]: + raise ValueError( + f"Number of alpha electrons in MolecularOrbitals is not equal to the " + f"'Naelec'. {mo.occsa.sum()} != {inp['Naelec']}" + ) + if mo.occsb.sum() != inp["Nbelec"]: + raise ValueError( + f"Number of beta electrons in MolecularOrbitals is not equal to the " + f"'Nbelec'. {mo.occsb.sum()} != {inp['Nbelec']}" + ) return { - 'title': inp['title'], - 'atcoords': inp['atcoords'], - 'atnums': inp['atnums'], - 'atcorenums': inp['atcorenums'], - 'obasis': obasis, - 'mo': mo, - 'energy': inp['E_tot'], - 'extra': extra, + "title": inp["title"], + "atcoords": inp["atcoords"], + "atnums": inp["atnums"], + "atcorenums": inp["atcorenums"], + "obasis": obasis, + "mo": mo, + "energy": inp["E_tot"], + "extra": extra, } diff --git a/iodata/formats/orcalog.py b/iodata/formats/orcalog.py index 738dccf92..d6c385131 100644 --- a/iodata/formats/orcalog.py +++ b/iodata/formats/orcalog.py @@ -18,22 +18,21 @@ # -- """Orca output file format.""" - -from typing import TextIO, Tuple +from typing import TextIO import numpy as np +from numpy.typing import NDArray from ..docstrings import document_load_one from ..utils import LineIterator - __all__ = [] -PATTERNS = ['*.out'] +PATTERNS = ["*.out"] -@document_load_one("Orca output", ['atcoords', 'atnums', 'energy', 'moments', 'extra']) +@document_load_one("Orca output", ["atcoords", "atnums", "energy", "moments", "extra"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" result = {} @@ -44,25 +43,25 @@ def load_one(lit: LineIterator) -> dict: # Read until the end of the file. break # Get the total number of atoms - if line.startswith('CARTESIAN COORDINATES (ANGSTROEM)'): + if line.startswith("CARTESIAN COORDINATES (ANGSTROEM)"): natom = _helper_number_atoms(lit) # Every Cartesian coordinates found are replaced with the old ones # to maintain the ones from the final SCF iteration in e.g. optimization run - if line.startswith('CARTESIAN COORDINATES (A.U.)'): - result['atnums'], result['atcoords'] = _helper_geometry(lit, natom) + if line.startswith("CARTESIAN COORDINATES (A.U.)"): + result["atnums"], result["atcoords"] = _helper_geometry(lit, natom) # Read the energies of each SCF cycle in iodata.extra - if line.startswith('SCF ITERATIONS'): + if line.startswith("SCF ITERATIONS"): scf_energies = _helper_scf_energies(lit) - result['extra'] = {'scf_energies': scf_energies} + result["extra"] = {"scf_energies": scf_energies} # The final SCF energy is obtained - if line.startswith('FINAL SINGLE POINT ENERGY'): + if line.startswith("FINAL SINGLE POINT ENERGY"): words = line.split() - result['energy'] = float(words[4]) + result["energy"] = float(words[4]) # Read also the dipole moment - if line.startswith('Total Dipole Moment'): + if line.startswith("Total Dipole Moment"): words = line.split() dipole = np.array([float(words[4]), float(words[5]), float(words[6])]) - result['moments'] = {(1, 'c'): dipole} + result["moments"] = {(1, "c"): dipole} return result @@ -84,12 +83,12 @@ def _helper_number_atoms(lit: LineIterator) -> int: next(lit) natom = 0 # Add until an empty line is found - while next(lit).strip() != '': + while next(lit).strip() != "": natom += 1 return natom -def _helper_geometry(lit: TextIO, natom: int) -> Tuple[np.ndarray, np.ndarray]: +def _helper_geometry(lit: TextIO, natom: int) -> tuple[NDArray, NDArray]: """Load coordinates form a ORCA output file format. Parameters @@ -121,7 +120,7 @@ def _helper_geometry(lit: TextIO, natom: int) -> Tuple[np.ndarray, np.ndarray]: return atnums, atcoords -def _helper_scf_energies(lit: TextIO) -> Tuple[np.ndarray, np.ndarray]: +def _helper_scf_energies(lit: TextIO) -> tuple[NDArray, NDArray]: """Load energies from each SCF cycle from a ORCA output file format. Parameters @@ -138,7 +137,7 @@ def _helper_scf_energies(lit: TextIO) -> Tuple[np.ndarray, np.ndarray]: energies = [] line = next(lit) # read the next line until blank line - while line.strip() != '': + while line.strip() != "": words = line.split() if words[0].isdigit(): energies.append(float(words[1])) diff --git a/iodata/formats/pdb.py b/iodata/formats/pdb.py index 0f91fff40..96611de9c 100644 --- a/iodata/formats/pdb.py +++ b/iodata/formats/pdb.py @@ -23,27 +23,57 @@ http://www.wwpdb.org/documentation/file-format-content/format33/v3.3.html """ - -from typing import TextIO, Iterator +from collections.abc import Iterator +from typing import TextIO import numpy as np -from ..docstrings import (document_load_one, document_load_many, document_dump_one, - document_dump_many) +from ..docstrings import ( + document_dump_many, + document_dump_one, + document_load_many, + document_load_one, +) from ..iodata import IOData -from ..periodic import sym2num, num2sym -from ..utils import angstrom, LineIterator - +from ..periodic import bond2num, num2sym, sym2num +from ..utils import LineIterator, angstrom __all__ = [] -PATTERNS = ['*.pdb'] +PATTERNS = ["*.pdb"] -@document_load_one("PDB", ['atcoords', 'atnums', 'atffparams', 'extra'], ['title']) -def load_one(lit: LineIterator) -> dict: - """Do not edit this docstring. It will be overwritten.""" +def _parse_pdb_atom_line(line, lit): + """Parse an ATOM or HETATM line from a PDB file. + + Parameters + ---------- + line + A string with a single ATOM or HETATM line. + lit + The line iterator which read the line, used for generating warnings when needed. + + Returns + ------- + atnum + Atomic number. + atname + The atom name. + resname + The residua name. + chainid + The chain ID. + resnum + The residue number. + atcoord + Cartesian coordinates of the atomic nucleus. + occupancy + The occupancy (usually from the XRD analysis). + bfactor + The temperature factor (usually from the XRD analysis). + + """ # Overview of ATOM records # COLUMNS DATA TYPE FIELD DEFINITION # ------------------------------------------------------------------------------------- @@ -62,91 +92,191 @@ def load_one(lit: LineIterator) -> dict: # 61 - 66 Real(6.2) tempFactor Temperature factor. # 77 - 78 LString(2) element Element symbol, right-justified. # 79 - 80 LString(2) charge Charge on the atom. + + # Get element symbol from position 77:78 in pdb format + symbol = line[76:78].strip() + if len(symbol) > 0: + atnum = sym2num.get(symbol) + else: + # If not present, guess it from position 13:16 (atom name) + atname = line[12:16].strip() + atnum = sym2num.get(atname, sym2num.get(atname[:2].title(), sym2num.get(atname[0], None))) + lit.warn("Using the atom name in the PDB file to guess the chemical element.") + if atnum is None: + atnum = 0 + lit.warn(f"Failed to determine the atomic number. atname='{atname}' symbol='{symbol}'") + + # atom name, residue name, chain id, & residue sequence number + atname = line[12:16].strip() + resname = line[17:20].strip() + chainid = line[21] + resnum = int(line[22:26]) + # add x, y, and z + atcoord = [ + float(line[30:38]) * angstrom, + float(line[38:46]) * angstrom, + float(line[46:54]) * angstrom, + ] + # get occupancies & temperature factor + occupancy = float(line[54:60]) + bfactor = float(line[60:66]) + return atnum, atname, resname, chainid, resnum, atcoord, occupancy, bfactor + + +def _parse_pdb_conect_line(line): + # Overview of CONECT records + # COLUMNS DATA TYPE FIELD DEFINITION + # ------------------------------------------------------------------------- + # 1 - 6 Record name "CONECT" + # 7 - 11 Integer serial Atom serial number + # 12 - 16 Integer serial Serial number of bonded atom + # 17 - 21 Integer serial Serial number of bonded atom + # 22 - 26 Integer serial Serial number of bonded atom + # 27 - 31 Integer serial Serial number of bonded atom + iatom0 = int(line[7:12]) - 1 + for ipos in 12, 17, 22, 27: + serial_str = line[ipos : ipos + 5].strip() + if serial_str != "": + iatom1 = int(serial_str) - 1 + if iatom1 > iatom0: + yield iatom0, iatom1 + + +@document_load_one("PDB", ["atcoords", "atnums", "atffparams", "extra"], ["title", "bonds"]) +def load_one(lit: LineIterator) -> dict: + """Do not edit this docstring. It will be overwritten.""" + title_lines = [] + compnd_lines = [] atnums = [] attypes = [] restypes = [] chainids = [] resnums = [] - coords = [] + atcoords = [] occupancies = [] bfactors = [] + bonds = [] molecule_found = False end_reached = False - title = "PDB file from IOData" while True: try: line = next(lit) except StopIteration: break - # If the PDB file has a title replace it. - if line.startswith("TITLE") or line.startswith("COMPND"): - title = line[10:].rstrip() - if line.startswith("ATOM") or line.startswith("HETATM"): - # get element symbol from position 77:78 in pdb format - words = line[76:78].split() - if not words: - # If not present, guess it from position 13:16 (atom name) - words = line[12:16].split() - # assign atomic number - symbol = words[0].title() - atnums.append(sym2num.get(symbol, sym2num.get(symbol[0], None))) - # atom name, residue name, chain id, & residue sequence number - attypes.append(line[12:16].strip()) - restypes.append(line[17:20].strip()) - chainids.append(line[21]) - resnums.append(int(line[22:26])) - # add x, y, and z - coords.append([float(line[30:38]), float(line[38:46]), float(line[46:54])]) - # get occupancies & temperature factor - occupancies.append(float(line[54:60])) - bfactors.append(float(line[60:66])) + # If the PDB file has a title, replace the default. + if line.startswith("TITLE"): + title_lines.append(line[10:].strip()) + if line.startswith("COMPND"): + compnd_lines.append(line[10:].strip()) + if line.startswith(("ATOM", "HETATM")): + (atnum, attype, restype, chainid, resnum, atcoord, occupancy, bfactor) = ( + _parse_pdb_atom_line(line, lit) + ) + atnums.append(atnum) + attypes.append(attype) + restypes.append(restype) + chainids.append(chainid) + resnums.append(resnum) + atcoords.append(atcoord) + occupancies.append(occupancy) + bfactors.append(bfactor) molecule_found = True + if line.startswith("CONECT"): + for iatom0, iatom1 in _parse_pdb_conect_line(line): + bonds.append([iatom0, iatom1, bond2num["un"]]) if line.startswith("END") and molecule_found: end_reached = True break - if molecule_found is False: + if not molecule_found: lit.error("Molecule could not be read!") if not end_reached: lit.warn("The END is not found, but the parsed data is returned!") - atffparams = {"attypes": np.array(attypes), "restypes": np.array(restypes), - "resnums": np.array(resnums)} - extra = {"occupancies": np.array(occupancies), "bfactors": np.array(bfactors)} + # Data related to force fields + atffparams = { + "attypes": np.array(attypes), + "restypes": np.array(restypes), + "resnums": np.array(resnums), + } + # Extra data + extra = { + "occupancies": np.array(occupancies), + "bfactors": np.array(bfactors), + } + if len(compnd_lines) > 0: + extra["compound"] = "\n".join(compnd_lines) # add chain id, if it wasn't all empty - if not np.all(chainids == [' '] * len(chainids)): + if not np.all(chainids == [" "] * len(chainids)): extra["chainids"] = np.array(chainids) + # Set a useful title + if len(title_lines) == 0: + # Some files use COMPND instead of TITLE, in which case COMPND will be + # used as title. + if "compound" in extra: + title = extra["compound"] + del extra["compound"] + else: + title = "PDB file loaded by IOData" + else: + title = "\n".join(title_lines) result = { - 'atcoords': np.array(coords) * angstrom, - 'atnums': np.array(atnums), - 'atffparams': atffparams, - 'title': title, - 'extra': extra + "atcoords": np.array(atcoords), + "atnums": np.array(atnums), + "atffparams": atffparams, + "title": title, + "extra": extra, } + # assign bonds only if some were present + if len(bonds) > 0: + result["bonds"] = np.array(bonds) return result -@document_load_many("PDB", ['atcoords', 'atnums', 'atffparams', 'extra'], ['title']) +@document_load_many("PDB", ["atcoords", "atnums", "atffparams", "extra"], ["title"]) def load_many(lit: LineIterator) -> Iterator[dict]: """Do not edit this docstring. It will be overwritten.""" # PDB files with more molecules are a simple concatenation of individual PDB files,' # making it trivial to load many frames. - while True: - try: + try: + while True: yield load_one(lit) - except IOError: - return + except OSError: + return + + +def _dump_multiline_str(f: TextIO, key: str, value: str): + r"""Write a multiline string in PDB format. + + Parameters + ---------- + f + A file object to write to. + key + The key used to prefix the multiline string, e.g. `"TITLE"`. + value + A (multiline) string, with multiple lines separated by `\n`. + + """ + prefix = key.ljust(10) + for iline, line in enumerate(value.split("\n")): + print(prefix + line, file=f) + prefix = key + str(iline + 2).rjust(10 - len(key)) + " " -@document_dump_one("PDB", ['atcoords', 'atnums', 'extra'], ['atffparams', 'title']) +@document_dump_one("PDB", ["atcoords", "atnums", "extra"], ["atffparams", "title", "bonds"]) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" - print(str("TITLE " + data.title) or "TITLE Created with IOData", file=f) - attypes = data.atffparams.get('attypes', None) - restypes = data.atffparams.get('restypes', None) - resnums = data.atffparams.get('resnums', None) - occupancies = data.extra.get('occupancies', None) - bfactors = data.extra.get('bfactors', None) - chainids = data.extra.get('chainids', None) + _dump_multiline_str(f, "TITLE", data.title or "Created with IOData") + if "compound" in data.extra: + _dump_multiline_str(f, "COMPND", data.extra["compound"]) + # Prepare for ATOM lines. + attypes = data.atffparams.get("attypes", None) + restypes = data.atffparams.get("restypes", None) + resnums = data.atffparams.get("resnums", None) + occupancies = data.extra.get("occupancies", None) + bfactors = data.extra.get("bfactors", None) + chainids = data.extra.get("chainids", None) + # Write ATOM lines. for i in range(data.natom): n = num2sym[data.atnums[i]] resnum = -1 if resnums is None else resnums[i] @@ -156,13 +286,29 @@ def dump_one(f: TextIO, data: IOData): attype = str(n + str(i + 1)) if attypes is None else attypes[i] restype = "XXX" if restypes is None else restypes[i] chain = " " if chainids is None else chainids[i] - out1 = f'{i+1:>5d} {attype:<4s} {restype:3s} {chain:1s}{resnum:>4d} ' - out2 = f'{x:8.3f}{y:8.3f}{z:8.3f}{occ:6.2f}{b:6.2f}{n:>12s}' + out1 = f"{i+1:>5d} {attype:<4s} {restype:3s} {chain:1s}{resnum:>4d} " + out2 = f"{x:8.3f}{y:8.3f}{z:8.3f}{occ:6.2f}{b:6.2f}{n:>12s}" print("ATOM " + out1 + out2, file=f) + if data.bonds is not None: + # Prepare for CONECT lines. + connections = [[] for iatom in range(data.natom)] + for iatom0, iatom1 in data.bonds[:, :2]: + connections[iatom0].append(iatom1) + connections[iatom1].append(iatom0) + # Write CONECT lines. + for iatom0, iatoms1 in enumerate(connections): + if len(iatoms1) > 0: + # Write connection in groups of max 4 + for ichunk in range(len(iatoms1) // 4 + 1): + other_atoms_str = "".join( + f"{iatom1 + 1:5d}" for iatom1 in iatoms1[ichunk * 4 : ichunk * 4 + 4] + ) + conect_line = f"CONECT{iatom0 + 1:5d}{other_atoms_str}" + print(conect_line, file=f) print("END", file=f) -@document_dump_many("PDB", ['atcoords', 'atnums', 'extra'], ['atffparams', 'title']) +@document_dump_many("PDB", ["atcoords", "atnums", "extra"], ["atffparams", "title"]) def dump_many(f: TextIO, datas: Iterator[IOData]): """Do not edit this docstring. It will be overwritten.""" # Similar to load_many, this is relatively easy. diff --git a/iodata/formats/poscar.py b/iodata/formats/poscar.py index a2f59806d..c411c71ee 100644 --- a/iodata/formats/poscar.py +++ b/iodata/formats/poscar.py @@ -22,56 +22,54 @@ `VESTA `_. """ - from typing import TextIO import numpy as np -from ..docstrings import document_load_one, document_dump_one +from ..docstrings import document_dump_one, document_load_one from ..iodata import IOData from ..periodic import num2sym -from ..utils import angstrom, LineIterator +from ..utils import LineIterator, angstrom from .chgcar import _load_vasp_header - __all__ = [] -PATTERNS = ['POSCAR*'] +PATTERNS = ["POSCAR*"] -@document_load_one("VASP 5 POSCAR", ['atcoords', 'atnums', 'cellvecs', 'title']) +@document_load_one("VASP 5 POSCAR", ["atcoords", "atnums", "cellvecs", "title"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" # Load header title, cellvecs, atnums, atcoords = _load_vasp_header(lit) return { - 'title': title, - 'atcoords': atcoords, - 'atnums': atnums, - 'cellvecs': cellvecs, + "title": title, + "atcoords": atcoords, + "atnums": atnums, + "cellvecs": cellvecs, } -@document_dump_one("VASP 5 POSCAR", ['atcoords', 'atnums', 'cellvecs'], ['title']) +@document_dump_one("VASP 5 POSCAR", ["atcoords", "atnums", "cellvecs"], ["title"]) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" - print(data.title or 'Created with IOData', file=f) - print(' 1.00000000000000', file=f) + print(data.title or "Created with IOData", file=f) + print(" 1.00000000000000", file=f) # Write cell vectors, each row is one vector in angstrom: cellvecs = data.cellvecs for rvec in cellvecs: r = rvec / angstrom - print(f'{r[0]: 21.16f} {r[1]: 21.16f} {r[2]: 21.16f}', file=f) + print(f"{r[0]: 21.16f} {r[1]: 21.16f} {r[2]: 21.16f}", file=f) # Construct list of elements to make sure the coordinates get written # in this order. Heaviest elements are put furst. uatnums = sorted(np.unique(data.atnums))[::-1] - print(' '.join(f'{num2sym[uatnum]:5s}' for uatnum in uatnums), file=f) - print(' '.join(f'{(data.atnums == uatnum).sum():5d}' for uatnum in uatnums), file=f) - print('Selective dynamics', file=f) - print('Direct', file=f) + print(" ".join(f"{num2sym[uatnum]:5s}" for uatnum in uatnums), file=f) + print(" ".join(f"{(data.atnums == uatnum).sum():5d}" for uatnum in uatnums), file=f) + print("Selective dynamics", file=f) + print("Direct", file=f) # Write the coordinates gvecs = np.linalg.inv(data.cellvecs).T @@ -79,4 +77,4 @@ def dump_one(f: TextIO, data: IOData): indexes = (data.atnums == uatnum).nonzero()[0] for index in indexes: row = np.dot(gvecs, data.atcoords[index]) - print(f' {row[0]: 21.16f} {row[1]: 21.16f} {row[2]: 21.16f} F F F', file=f) + print(f" {row[0]: 21.16f} {row[1]: 21.16f} {row[2]: 21.16f} F F F", file=f) diff --git a/iodata/formats/qchemlog.py b/iodata/formats/qchemlog.py index 893a1e95d..320f9d466 100644 --- a/iodata/formats/qchemlog.py +++ b/iodata/formats/qchemlog.py @@ -21,32 +21,51 @@ This module will load Q-Chem log file into IODATA. """ -from typing import Tuple -from distutils.util import strtobool - import numpy as np +from numpy.typing import NDArray from ..docstrings import document_load_one from ..orbitals import MolecularOrbitals from ..periodic import sym2num -from ..utils import LineIterator, angstrom, kcalmol, calmol, kjmol +from ..utils import LineIterator, angstrom, calmol, kcalmol, kjmol, strtobool __all__ = [] -PATTERNS = ['*.qchemlog'] - - -@document_load_one("qchemlog", - ['atcoords', 'atmasses', 'atnums', 'energy', 'g_rot', 'mo', - 'lot', 'obasis_name', 'run_type', 'extra'], - ['athessian']) +PATTERNS = ["*.qchemlog"] + + +@document_load_one( + "qchemlog", + [ + "atcoords", + "atmasses", + "atnums", + "energy", + "g_rot", + "mo", + "lot", + "obasis_name", + "run_type", + "extra", + ], + ["athessian"], +) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" data = load_qchemlog_low(lit) # add these labels if they are loaded - result_labels = ['atcoords', 'atmasses', 'atnums', 'energy', 'g_rot', - 'run_type', 'athessian', 'lot', 'obasis_name'] + result_labels = [ + "atcoords", + "atmasses", + "atnums", + "energy", + "g_rot", + "run_type", + "athessian", + "lot", + "obasis_name", + ] result = {label: data[label] for label in result_labels if data.get(label) is not None} # mulliken charges @@ -55,71 +74,80 @@ def load_one(lit: LineIterator) -> dict: # build molecular orbitals # ------------------------ - if data['unrestricted']: + if data["unrestricted"]: # unrestricted case - mo_energies = np.concatenate((data['mo_a_occ'], data['mo_a_vir'], - data['mo_b_occ'], data['mo_b_vir']), axis=0) - mo_coeffs = np.full((data['nbasis'], data['norba'] + data['norbb']), np.nan) + mo_energies = np.concatenate( + (data["mo_a_occ"], data["mo_a_vir"], data["mo_b_occ"], data["mo_b_vir"]), axis=0 + ) + mo_coeffs = np.full((data["nbasis"], data["norba"] + data["norbb"]), np.nan) mo_occs = np.zeros(mo_coeffs.shape[1]) # number of alpha & beta electrons and number of alpha molecular orbitals - na, nb = data['alpha_elec'], data['beta_elec'] - na_mo = len(data['mo_a_occ']) + len(data['mo_a_vir']) + na, nb = data["alpha_elec"], data["beta_elec"] + na_mo = len(data["mo_a_occ"]) + len(data["mo_a_vir"]) mo_occs[:na] = 1.0 - mo_occs[na_mo: na_mo + nb] = 1.0 - mo = MolecularOrbitals("unrestricted", data['norba'], data['norbb'], - mo_occs, mo_coeffs, mo_energies, None) + mo_occs[na_mo : na_mo + nb] = 1.0 + mo = MolecularOrbitals( + "unrestricted", data["norba"], data["norbb"], mo_occs, mo_coeffs, mo_energies, None + ) else: # restricted case - mo_energies = np.concatenate((data['mo_a_occ'], data['mo_a_vir']), axis=0) - mo_coeffs = np.full((data['nbasis'], data['norba']), np.nan) + mo_energies = np.concatenate((data["mo_a_occ"], data["mo_a_vir"]), axis=0) + mo_coeffs = np.full((data["nbasis"], data["norba"]), np.nan) mo_occs = np.zeros(mo_coeffs.shape[1]) - mo_occs[:data['alpha_elec']] = 1.0 - mo_occs[:data['beta_elec']] += 1.0 - mo = MolecularOrbitals("restricted", data['norba'], data['norba'], - mo_occs, mo_coeffs, mo_energies, None) - result['mo'] = mo + mo_occs[: data["alpha_elec"]] = 1.0 + mo_occs[: data["beta_elec"]] += 1.0 + mo = MolecularOrbitals( + "restricted", data["norba"], data["norba"], mo_occs, mo_coeffs, mo_energies, None + ) + result["mo"] = mo # moments moments = {} - if 'dipole' in data: - moments[(1, 'c')] = data['dipole'] - if 'quadrupole' in data: + if "dipole" in data: + moments[(1, "c")] = data["dipole"] + if "quadrupole" in data: # Convert to alphabetical ordering: xx, xy, xz, yy, yz, zz - moments[(2, 'c')] = data['quadrupole'][[0, 1, 3, 2, 4, 5]] + moments[(2, "c")] = data["quadrupole"][[0, 1, 3, 2, 4, 5]] # check total dipole parsed - if 'dipole_tol' in data and 'dipole' in data: - assert abs(np.linalg.norm(data['dipole']) - data['dipole_tol']) < 1.0e-4 + if "dipole_tol" in data and "dipole" in data: + assert abs(np.linalg.norm(data["dipole"]) - data["dipole_tol"]) < 1.0e-4 if moments: - result['moments'] = moments + result["moments"] = moments # extra dictionary # ---------------- # add labels to extra dictionary if they are loaded - extra_labels = ['nuclear_repulsion_energy', 'polarizability_tensor', 'imaginary_freq', - 'vib_energy', 'eda2', 'frags'] + extra_labels = [ + "nuclear_repulsion_energy", + "polarizability_tensor", + "imaginary_freq", + "vib_energy", + "eda2", + "frags", + ] extra = {label: data[label] for label in extra_labels if data.get(label) is not None} # if present, convert vibrational energy from kcal/mol to "atomic units + K" - if 'vib_energy' in extra: - extra['vib_energy'] *= kcalmol + if "vib_energy" in extra: + extra["vib_energy"] *= kcalmol # if present, convert enthalpy terms from kcal/mol to "atomic units + K" - if 'enthalpy_dict' in data: - extra['enthalpy_dict'] = {k: v * kcalmol for k, v in data['enthalpy_dict'].items()} + if "enthalpy_dict" in data: + extra["enthalpy_dict"] = {k: v * kcalmol for k, v in data["enthalpy_dict"].items()} # if present, convert entropy terms from cal/mol.K to "atomic units + Kalvin" - if 'entropy_dict' in data: - extra['entropy_dict'] = {k: v * calmol for k, v in data['entropy_dict'].items()} + if "entropy_dict" in data: + extra["entropy_dict"] = {k: v * calmol for k, v in data["entropy_dict"].items()} # if present, convert eda terms from kj/mol to atomic units - if 'eda2' in data: - extra['eda2'] = {k: v * kjmol for k, v in data['eda2'].items()} + if "eda2" in data: + extra["eda2"] = {k: v * kjmol for k, v in data["eda2"].items()} - result['extra'] = extra + result["extra"] = extra return result -def load_qchemlog_low(lit: LineIterator) -> dict: # pylint: disable=too-many-branches +def load_qchemlog_low(lit: LineIterator) -> dict: """Load the information from Q-Chem log file.""" data = {} while True: @@ -130,94 +158,94 @@ def load_qchemlog_low(lit: LineIterator) -> dict: # pylint: disable=too-many-br break # job specifications - if line.startswith('$rem') and 'run_type' not in data: + if line.startswith("$rem") and "run_type" not in data: data.update(_helper_rem_job(lit)) # standard nuclear orientation (make sure multi-step jobs does not over-write this) - elif line.startswith('Standard Nuclear Orientation (Angstroms)') and 'atcoords' not in data: + elif line.startswith("Standard Nuclear Orientation (Angstroms)") and "atcoords" not in data: data.update(_helper_structure(lit)) # standard nuclear orientation for fragments in EDA jobs - elif line.startswith('Standard Nuclear Orientation (Angstroms)'): - if 'frags' not in data: - data['frags'] = [] - data['frags'].append(_helper_structure(lit)) + elif line.startswith("Standard Nuclear Orientation (Angstroms)"): + if "frags" not in data: + data["frags"] = [] + data["frags"].append(_helper_structure(lit)) # energy (the last energy in a multi-step job) - elif line.startswith('Total energy in the final basis set'): - data['energy'] = float(line.split()[-1]) - elif line.startswith('the SCF tolerance is set'): - data['energy'] = _helper_energy(lit) + elif line.startswith("Total energy in the final basis set"): + data["energy"] = float(line.split()[-1]) + elif line.startswith("the SCF tolerance is set"): + data["energy"] = _helper_energy(lit) # orbital energies (the last orbital energies in a multi-step job) - elif line.startswith('Orbital Energies (a.u.)') and not data['unrestricted']: + elif line.startswith("Orbital Energies (a.u.)") and not data["unrestricted"]: result = _helper_orbital_energies_restricted(lit) - data['mo_a_occ'], data['mo_a_vir'] = result + data["mo_a_occ"], data["mo_a_vir"] = result # compute number of alpha - data['norba'] = len(data['mo_a_occ']) + len(data['mo_a_vir']) + data["norba"] = len(data["mo_a_occ"]) + len(data["mo_a_vir"]) # orbital energies (the last orbital energies in a multi-step job) - elif line.startswith('Orbital Energies (a.u.)') and data['unrestricted']: + elif line.startswith("Orbital Energies (a.u.)") and data["unrestricted"]: data.update(_helper_orbital_energies_unrestricted(lit)) # compute number of alpha and beta molecular orbitals - data['norba'] = len(data['mo_a_occ']) + len(data['mo_a_vir']) - data['norbb'] = len(data['mo_b_occ']) + len(data['mo_b_vir']) + data["norba"] = len(data["mo_a_occ"]) + len(data["mo_a_vir"]) + data["norbb"] = len(data["mo_b_occ"]) + len(data["mo_b_vir"]) # mulliken charges (the last charges in a multi-step job) - elif line.startswith('Ground-State Mulliken Net Atomic Charges'): - data['mulliken_charges'] = _helper_mulliken(lit) + elif line.startswith("Ground-State Mulliken Net Atomic Charges"): + data["mulliken_charges"] = _helper_mulliken(lit) # cartesian multipole moments (the last mutipole moments in a multi-step job) - elif line.startswith('Cartesian Multipole Moments'): - data['dipole'], data['quadrupole'], data['dipole_tol'] = _helper_dipole_moments(lit) + elif line.startswith("Cartesian Multipole Moments"): + data["dipole"], data["quadrupole"], data["dipole_tol"] = _helper_dipole_moments(lit) # polarizability matrix - elif line.startswith('Polarizability Matrix (a.u.)'): - data['polarizability_tensor'] = _helper_polar(lit) + elif line.startswith("Polarizability Matrix (a.u.)"): + data["polarizability_tensor"] = _helper_polar(lit) # hessian matrix - elif line.startswith('Hessian of the SCF Energy'): - data['athessian'] = _helper_hessian(lit, len(data['atnums'])) + elif line.startswith("Hessian of the SCF Energy"): + data["athessian"] = _helper_hessian(lit, len(data["atnums"])) # vibrational analysis - elif line.startswith('** VIBRATIONAL ANALYSIS'): - data['imaginary_freq'], data['vib_energy'], data['atmasses'] = _helper_vibrational(lit) + elif line.startswith("** VIBRATIONAL ANALYSIS"): + data["imaginary_freq"], data["vib_energy"], data["atmasses"] = _helper_vibrational(lit) # rotational symmetry number - elif line.startswith('Rotational Symmetry Number'): - data['g_rot'] = int(line.split()[-1]) - data['enthalpy_dict'], data['entropy_dict'] = _helper_thermo(lit) + elif line.startswith("Rotational Symmetry Number"): + data["g_rot"] = int(line.split()[-1]) + data["enthalpy_dict"], data["entropy_dict"] = _helper_thermo(lit) # energy decomposition analysis 2 (EDA2) - elif line.startswith('Results of EDA2'): + elif line.startswith("Results of EDA2"): eda2 = _helper_eda2(lit) # add fragment energies to frags - energies = eda2.pop('energies') + energies = eda2.pop("energies") for index, energy in enumerate(energies): - data['frags'][index]['energy'] = energy - data['eda2'] = eda2 + data["frags"][index]["energy"] = energy + data["eda2"] = eda2 return data -def _helper_rem_job(lit: LineIterator) -> Tuple: +def _helper_rem_job(lit: LineIterator) -> dict: """Load job specifications from Q-Chem output file format.""" data_rem = {} for line in lit: - if line.strip() == '$end': + words = line.strip().lower().split(maxsplit=1) + if words[0] == "$end": break - line = line.strip() # parse job type section; some sections might not be available - if line.lower().startswith('jobtype'): - data_rem['run_type'] = line.split()[1].lower() - elif line.lower().startswith('method'): - data_rem['lot'] = line.split()[1].lower() - elif line.lower().startswith('unrestricted'): - data_rem['unrestricted'] = bool(strtobool(line.split()[1])) - elif line.split()[0].lower() == 'basis': - data_rem['obasis_name'] = line.split()[1].lower() - elif line.lower().startswith('symmetry'): - data_rem['symm'] = bool(strtobool(line.split()[1])) + if words[0] == "jobtype": + data_rem["run_type"] = words[1] + elif words[0] == "method": + data_rem["lot"] = words[1] + elif words[0] == "unrestricted": + data_rem["unrestricted"] = strtobool(words[1]) + elif words[0] == "basis": + data_rem["obasis_name"] = words[1] + elif words[0] == "symmetry": + data_rem["symm"] = strtobool(words[1]) return data_rem @@ -229,13 +257,15 @@ def _helper_structure(lit: LineIterator): atsymbols = [] atcoords = [] for line in lit: - if line.strip().startswith('-------------'): + if line.strip().startswith("-------------"): break atsymbols.append(line.split()[1]) atcoords.append([float(i) for i in line.split()[2:]]) - subdata = {"atnums": np.array([sym2num[i] for i in atsymbols]), - "atcoords": np.array(atcoords) * angstrom, - "nuclear_repulsion_energy": float(next(lit).split()[-2])} + subdata = { + "atnums": np.array([sym2num[i] for i in atsymbols]), + "atcoords": np.array(atcoords) * angstrom, + "nuclear_repulsion_energy": float(next(lit).split()[-2]), + } # number of alpha and beta elections line = next(lit).split() subdata["alpha_elec"] = int(line[2]) @@ -249,36 +279,36 @@ def _helper_structure(lit: LineIterator): def _helper_energy(lit: LineIterator): for line in lit: - if line.strip().endswith('Convergence criterion met'): + if line.strip().endswith("Convergence criterion met"): energy = float(line.split()[1]) break return energy -def _helper_orbital_energies_restricted(lit: LineIterator) -> Tuple: +def _helper_orbital_energies_restricted(lit: LineIterator) -> tuple: """Load occupied and virtual orbital energies for restricted calculation.""" # alpha occupied MOs - mo_a_occupied = _helper_section('-- Occupied --', '-- Virtual --', lit, backward=True) + mo_a_occupied = _helper_section("-- Occupied --", "-- Virtual --", lit, backward=True) # alpha unoccupied MOs - mo_a_unoccupied = _helper_section('-- Virtual --', '-' * 62, lit, backward=False) + mo_a_unoccupied = _helper_section("-- Virtual --", "-" * 62, lit, backward=False) return mo_a_occupied, mo_a_unoccupied -def _helper_orbital_energies_unrestricted(lit: LineIterator) -> Tuple: +def _helper_orbital_energies_unrestricted(lit: LineIterator) -> tuple: """Load occupied and virtual orbital energies for unrestricted calculation.""" - subdata = dict() + subdata = {} # alpha occupied MOs - subdata['mo_a_occ'] = _helper_section('-- Occupied --', '-- Virtual --', lit, backward=True) + subdata["mo_a_occ"] = _helper_section("-- Occupied --", "-- Virtual --", lit, backward=True) # alpha unoccupied MOs - subdata['mo_a_vir'] = _helper_section('-- Virtual --', '', lit, backward=False) + subdata["mo_a_vir"] = _helper_section("-- Virtual --", "", lit, backward=False) # beta occupied MOs - subdata['mo_b_occ'] = _helper_section('-- Occupied --', '-- Virtual --', lit, backward=True) + subdata["mo_b_occ"] = _helper_section("-- Occupied --", "-- Virtual --", lit, backward=True) # beta unoccupied MOs - subdata['mo_b_vir'] = _helper_section('-- Virtual --', '-' * 62, lit, backward=False) + subdata["mo_b_vir"] = _helper_section("-- Virtual --", "-" * 62, lit, backward=False) return subdata -def _helper_section(start: str, end: str, lit: LineIterator, backward: bool = False) -> np.ndarray: +def _helper_section(start: str, end: str, lit: LineIterator, backward: bool = False) -> NDArray: """Load data between starting and ending strings.""" data = [] for line in lit: @@ -295,26 +325,26 @@ def _helper_section(start: str, end: str, lit: LineIterator, backward: bool = Fa return np.array(data, dtype=float) -def _helper_mulliken(lit: LineIterator) -> np.ndarray: +def _helper_mulliken(lit: LineIterator) -> NDArray: """Load mulliken net atomic charges.""" # skip line between 'Ground-State Mulliken Net Atomic Charges' line & atomic charge entries while True: line = next(lit).strip() - if line.startswith('------'): + if line.startswith("------"): break # store atomic charges until enf of table is reached mulliken_charges = [] for line in lit: - if line.strip().startswith('--------'): + if line.strip().startswith("--------"): break mulliken_charges.append(line.split()[2]) return np.array(mulliken_charges, dtype=float) -def _helper_dipole_moments(lit: LineIterator) -> Tuple: +def _helper_dipole_moments(lit: LineIterator) -> tuple: """Load cartesian multiple moments.""" for line in lit: - if line.strip().startswith('Dipole Moment (Debye)'): + if line.strip().startswith("Dipole Moment (Debye)"): break # parse dipole moment (only load the float numbers) dipole = next(lit).split() @@ -329,134 +359,132 @@ def _helper_dipole_moments(lit: LineIterator) -> Tuple: return dipole, quadrupole, dipole_tol -def _helper_polar(lit: LineIterator) -> np.ndarray: +def _helper_polar(lit: LineIterator) -> NDArray: """Load polarizability matrix.""" next(lit) polarizability_tensor = [] for line in lit: - if line.strip().startswith('Calculating analytic Hessian'): + if line.strip().startswith("Calculating analytic Hessian"): break polarizability_tensor.append(line.split()[1:]) return np.array(polarizability_tensor, dtype=float) -def _helper_hessian(lit: LineIterator, natom: int) -> np.ndarray: +def _helper_hessian(lit: LineIterator, natom: int) -> NDArray: """Load hessian matrix.""" # hessian in Cartesian coordinates, shape(3 * natom, 3 * natom) col_idx = [int(i) for i in next(lit).split()] hessian = np.empty((natom * 3, natom * 3), dtype=object) for line in lit: - if line.strip().startswith('****************'): + if line.strip().startswith("****************"): break - if line.startswith(' '): + if line.startswith(" "): col_idx = [int(i) for i in line.split()] else: line_list = line.split() row_idx = int(line_list[0]) - 1 - hessian[row_idx, col_idx[0] - 1:col_idx[-1]] = line_list[1:] + hessian[row_idx, col_idx[0] - 1 : col_idx[-1]] = line_list[1:] return hessian.astype(float) -def _helper_vibrational(lit: LineIterator) -> Tuple: +def _helper_vibrational(lit: LineIterator) -> tuple: """Load vibrational analysis.""" for line in lit: - if line.strip().startswith('This Molecule has'): + if line.strip().startswith("This Molecule has"): break - # pylint: disable= W0631 imaginary_freq = int(line.split()[3]) vib_energy = float(next(lit).split()[-2]) next(lit) atmasses = [] for line in lit: - if line.strip().startswith('Molecular Mass:'): + if line.strip().startswith("Molecular Mass:"): break atmasses.append(line.split()[-1]) atmasses = np.array(atmasses, dtype=float) return imaginary_freq, vib_energy, atmasses -def _helper_thermo(lit: LineIterator) -> Tuple: +def _helper_thermo(lit: LineIterator) -> tuple: """Load thermodynamics properties.""" enthalpy_dict = {} entropy_dict = {} for line in lit: line_str = line.strip() - if line_str.startswith('Archival summary:'): + if line_str.startswith("Archival summary:"): break - if line_str.startswith('Translational Enthalpy'): - enthalpy_dict['trans_enthalpy'] = float(line_str.split()[-2]) - elif line_str.startswith('Rotational Enthalpy'): - enthalpy_dict['rot_enthalpy'] = float(line_str.split()[-2]) - elif line_str.startswith('Vibrational Enthalpy'): - enthalpy_dict['vib_enthalpy'] = float(line_str.split()[-2]) - elif line_str.startswith('Total Enthalpy'): - enthalpy_dict['enthalpy_total'] = float(line_str.split()[-2]) - elif line_str.startswith('Translational Entropy'): - entropy_dict['trans_entropy'] = float(line_str.split()[-2]) - elif line_str.startswith('Rotational Entropy'): - entropy_dict['rot_entropy'] = float(line_str.split()[-2]) - elif line_str.startswith('Vibrational Entropy'): - entropy_dict['vib_entropy'] = float(line_str.split()[-2]) - elif line_str.startswith('Total Entropy'): - entropy_dict['entropy_total'] = float(line_str.split()[-2]) + if line_str.startswith("Translational Enthalpy"): + enthalpy_dict["trans_enthalpy"] = float(line_str.split()[-2]) + elif line_str.startswith("Rotational Enthalpy"): + enthalpy_dict["rot_enthalpy"] = float(line_str.split()[-2]) + elif line_str.startswith("Vibrational Enthalpy"): + enthalpy_dict["vib_enthalpy"] = float(line_str.split()[-2]) + elif line_str.startswith("Total Enthalpy"): + enthalpy_dict["enthalpy_total"] = float(line_str.split()[-2]) + elif line_str.startswith("Translational Entropy"): + entropy_dict["trans_entropy"] = float(line_str.split()[-2]) + elif line_str.startswith("Rotational Entropy"): + entropy_dict["rot_entropy"] = float(line_str.split()[-2]) + elif line_str.startswith("Vibrational Entropy"): + entropy_dict["vib_entropy"] = float(line_str.split()[-2]) + elif line_str.startswith("Total Entropy"): + entropy_dict["entropy_total"] = float(line_str.split()[-2]) return enthalpy_dict, entropy_dict -def _helper_eda2(lit: LineIterator) -> dict: # pylint: disable=too-many-branches +def _helper_eda2(lit: LineIterator) -> dict: """Load Energy decomposition information.""" next(lit) eda2 = {} for line in lit: - - if line.strip().startswith('Fragment Energies'): + if line.strip().startswith("Fragment Energies"): for line_2 in lit: - if line_2.strip().startswith('-----'): + if line_2.strip().startswith("-----"): break - eda2.setdefault('energies', []).append(float(line_2.split()[-1])) + eda2.setdefault("energies", []).append(float(line_2.split()[-1])) - if line.strip().startswith('Orthogonal Fragment Subspace Decomposition'): + if line.strip().startswith("Orthogonal Fragment Subspace Decomposition"): next(lit) for line_2 in lit: - if line_2.strip().startswith('-----'): + if line_2.strip().startswith("-----"): break info = line_2.split() - if info[0] in ['E_elec', 'E_pauli', 'E_disp']: + if info[0] in ["E_elec", "E_pauli", "E_disp"]: eda2[info[0].lower()] = float(info[-1]) - elif line.strip().startswith('Terms summing to E_pauli'): + elif line.strip().startswith("Terms summing to E_pauli"): next(lit) for line_2 in lit: - if line_2.strip().startswith('-----'): + if line_2.strip().startswith("-----"): break info = line_2.split() - if info[0] in ['E_kep_pauli', 'E_disp_free_pauli']: + if info[0] in ["E_kep_pauli", "E_disp_free_pauli"]: eda2[info[0].lower()] = float(info[-1]) - elif line.strip().startswith('Classical Frozen Decomposition'): + elif line.strip().startswith("Classical Frozen Decomposition"): next(lit) for line_2 in lit: - if line_2.strip().startswith('-----'): + if line_2.strip().startswith("-----"): break info = line_2.split() - if info[0] in ['E_cls_elec', 'E_cls_pauli']: + if info[0] in ["E_cls_elec", "E_cls_pauli"]: eda2[info[0].lower()] = float(info[5]) - elif info[0].split("[")[1] == 'E_mod_pauli': + elif info[0].split("[")[1] == "E_mod_pauli": eda2[info[0].split("[")[1].lower()] = float(info[5]) - elif line.strip().startswith('Simplified EDA Summary'): + elif line.strip().startswith("Simplified EDA Summary"): next(lit) for line_2 in lit: - if line_2.strip().startswith('-----'): + if line_2.strip().startswith("-----"): break info = line_2.split() - if info[0] in ['PREPARATION', 'FROZEN', 'DISPERSION', 'POLARIZATION', 'TOTAL']: + if info[0] in ["PREPARATION", "FROZEN", "DISPERSION", "POLARIZATION", "TOTAL"]: eda2[info[0].lower()] = float(info[1]) - elif info[0].split("[")[-1] == 'PAULI': + elif info[0].split("[")[-1] == "PAULI": eda2[info[0].split("[")[-1].lower()] = float(info[1].split("]")[0]) - elif info[0] == 'CHARGE': - eda2[info[0].lower() + ' ' + info[1].lower()] = float(info[2]) + elif info[0] == "CHARGE": + eda2[info[0].lower() + " " + info[1].lower()] = float(info[2]) - elif line.strip().startswith('-------------------------------------------------------'): + elif line.strip().startswith("-------------------------------------------------------"): break return eda2 diff --git a/iodata/formats/sdf.py b/iodata/formats/sdf.py index a25135e1c..baa6f4424 100644 --- a/iodata/formats/sdf.py +++ b/iodata/formats/sdf.py @@ -21,27 +21,36 @@ Usually, the different frames in a trajectory describe different geometries of the same molecule, with atoms in the same order. The ``load_many`` and ``dump_many`` functions below can also handle an SDF file with different molecules, e.g. a molecular database. -""" +The SDF format is somewhat documented on the following page: +http://www.nonlinear.com/progenesis/sdf-studio/v0.9/faq/sdf-file-format-guidance.aspx + +This format is one of the chemical table file formats: +https://en.wikipedia.org/wiki/Chemical_table_file +""" -from typing import TextIO, Iterator +from collections.abc import Iterator +from typing import TextIO import numpy as np -from ..docstrings import (document_load_one, document_load_many, document_dump_one, - document_dump_many) +from ..docstrings import ( + document_dump_many, + document_dump_one, + document_load_many, + document_load_one, +) from ..iodata import IOData -from ..periodic import sym2num, num2sym -from ..utils import angstrom, LineIterator - +from ..periodic import num2sym, sym2num +from ..utils import LineIterator, angstrom __all__ = [] -PATTERNS = ['*.sdf'] +PATTERNS = ["*.sdf"] -@document_load_one("SDF", ['atcoords', 'atnums', 'title']) +@document_load_one("SDF", ["atcoords", "atnums", "bonds", "title"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" title = next(lit).strip() @@ -49,15 +58,27 @@ def load_one(lit: LineIterator) -> dict: next(lit) next(lit) words = next(lit).split() - size = int(words[0]) - atcoords = np.empty((size, 3), float) - atnums = np.empty(size, int) - for i in range(size): + natom = int(words[0]) + nbond = int(words[1]) + if words[-1].upper() != "V2000": + lit.error("Only V2000 SDF files are supported.") + atcoords = np.empty((natom, 3), float) + atnums = np.empty(natom, int) + for iatom in range(natom): words = next(lit).split() - atcoords[i, 0] = float(words[0]) * angstrom - atcoords[i, 1] = float(words[1]) * angstrom - atcoords[i, 2] = float(words[2]) * angstrom - atnums[i] = sym2num.get(words[3].title()) + atcoords[iatom, 0] = float(words[0]) * angstrom + atcoords[iatom, 1] = float(words[1]) * angstrom + atcoords[iatom, 2] = float(words[2]) * angstrom + atnums[iatom] = sym2num.get(words[3].title()) + bonds = np.empty((nbond, 3), int) + for ibond in range(nbond): + words = next(lit).split() + bonds[ibond, 0] = int(words[0]) - 1 + bonds[ibond, 1] = int(words[1]) - 1 + # Bond types 1 to 8 (inclusive) are defined in the SDF format. + # Anything outside that range is not modified, just not to lose any + # information, but could be potentially meaningless. + bonds[ibond, 2] = int(words[2]) while True: try: words = next(lit) @@ -66,39 +87,45 @@ def load_one(lit: LineIterator) -> dict: if words == "$$$$\n": break return { - 'title': title, - 'atcoords': atcoords, - 'atnums': atnums + "title": title, + "atcoords": atcoords, + "atnums": atnums, + "bonds": bonds, } -@document_load_many("SDF", ['atcoords', 'atnums', 'title']) +@document_load_many("SDF", ["atcoords", "atnums", "bonds", "title"]) def load_many(lit: LineIterator) -> Iterator[dict]: """Do not edit this docstring. It will be overwritten.""" # SDF files with more molecules are a simple concatenation of individual SDF files,' # making it travial to load many frames. - while True: - try: + try: + while True: yield load_one(lit) - except StopIteration: - return + except StopIteration: + return -@document_dump_one("SDF", ['atcoords', 'atnums'], ['title']) +@document_dump_one("SDF", ["atcoords", "atnums"], ["title", "bonds"]) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" - print(data.title or 'Created with IOData', file=f) - print('', file=f) - print('', file=f) - print(data.natom, file=f) - for i in range(data.natom): - n = num2sym[data.atnums[i]] - x, y, z = data.atcoords[i] / angstrom - print(f'{x:15.10f} {y:15.10f} {z:15.10f} {n:2s}', file=f) - print('$$$$', file=f) - - -@document_dump_many("SDF", ['atcoords', 'atnums'], ['title']) + print(data.title or "Created with IOData", file=f) + print("", file=f) + print("", file=f) + nbond = 0 if data.bonds is None else len(data.bonds) + print(f"{data.natom:3d}{nbond:3d} 0 0 0 0 0 0 0999 V2000", file=f) + for iatom in range(data.natom): + n = num2sym[data.atnums[iatom]] + x, y, z = data.atcoords[iatom] / angstrom + print(f"{x:10.4f}{y:10.4f}{z:10.4f} {n:<3s} 0 0 0 0 0 0 0 0 0 0 0 0", file=f) + if data.bonds is not None: + for iatom, jatom, bondtype in data.bonds: + print(f"{iatom + 1:3d}{jatom + 1:3d}{bondtype:3d} 0 0 0 0", file=f) + print("M END", file=f) + print("$$$$", file=f) + + +@document_dump_many("SDF", ["atcoords", "atnums"], ["title", "bonds"]) def dump_many(f: TextIO, datas: Iterator[IOData]): """Do not edit this docstring. It will be overwritten.""" # Similar to load_many, this is relatively easy. diff --git a/iodata/formats/wfn.py b/iodata/formats/wfn.py index 72dc672a9..aada13da2 100644 --- a/iodata/formats/wfn.py +++ b/iodata/formats/wfn.py @@ -25,24 +25,25 @@ Gaussian functions. """ - -from typing import List, TextIO, Tuple +import functools +import operator +from typing import TextIO import numpy as np +from numpy.typing import NDArray from ..basis import MolecularBasis, Shell, convert_conventions -from ..docstrings import document_load_one, document_dump_one +from ..docstrings import document_dump_one, document_load_one from ..iodata import IOData -from ..overlap import gob_cart_normalization from ..orbitals import MolecularOrbitals +from ..overlap import gob_cart_normalization from ..periodic import num2sym, sym2num from ..utils import LineIterator - __all__ = [] -PATTERNS = ['*.wfn'] +PATTERNS = ["*.wfn"] # From the AIMALL documentation @@ -104,6 +105,7 @@ # 56 HXXXXX (500) +# fmt: off CONVENTIONS = { (0, 'c'): ['1'], (1, 'c'): ['x', 'y', 'z'], @@ -115,17 +117,19 @@ 'xyzzz', 'xyyzz', 'xyyyz', 'xyyyy', 'xxzzz', 'xxyzz', 'xxyyz', 'xxyyy', 'xxxzz', 'xxxyz', 'xxxyy', 'xxxxz', 'xxxxy', 'xxxxx'], } - +# fmt: on # Definition of primitives in the WFN format. This is the order of the primitive # types as documented by aimall, used in the field TYPE ASSIGNMENTS. -PRIMITIVE_NAMES = sum([CONVENTIONS[(angmom, 'c')] for angmom in range(6)], []) +PRIMITIVE_NAMES = functools.reduce( + operator.iadd, [CONVENTIONS[(angmom, "c")] for angmom in range(6)], [] +) -def _load_helper_num(lit: LineIterator) -> List[int]: +def _load_helper_num(lit: LineIterator) -> list[int]: """Read number of orbitals, primitives and atoms.""" line = next(lit) - if not line.startswith('G'): + if not line.startswith("G"): lit.error("Expecting line to start with 'G'") # FORMAT (16X,I7,13X,I7,11X,I9) num_mo = int(line[16:23]) @@ -134,7 +138,7 @@ def _load_helper_num(lit: LineIterator) -> List[int]: return num_mo, nprim, num_atoms -def _load_helper_atoms(lit: LineIterator, num_atoms: int) -> Tuple[np.ndarray, np.ndarray]: +def _load_helper_atoms(lit: LineIterator, num_atoms: int) -> tuple[NDArray, NDArray]: """Read the coordinates of the atoms.""" atnums = np.empty(num_atoms, int) atcoords = np.empty((num_atoms, 3), float) @@ -153,8 +157,9 @@ def _load_helper_atoms(lit: LineIterator, num_atoms: int) -> Tuple[np.ndarray, n return atnums, atcoords -def _load_helper_section(lit: LineIterator, n: int, start: str, skip: int, step: int, - dtype: np.dtype) -> np.ndarray: +def _load_helper_section( + lit: LineIterator, n: int, start: str, skip: int, step: int, dtype: np.dtype +) -> NDArray: """Read CENTRE ASSIGNMENTS, TYPE ASSIGNMENTS, and EXPONENTS sections.""" section = [] while len(section) < n: @@ -163,31 +168,31 @@ def _load_helper_section(lit: LineIterator, n: int, start: str, skip: int, step: lit.error(f"Expecting line to start with '{start}'") line = line[skip:] while len(line) >= step: - section.append(dtype(line[:step].replace('D', 'E'))) + section.append(dtype(line[:step].replace("D", "E"))) line = line[step:] if len(section) != n: lit.error("Number of elements in section do not match 'n'") return np.array(section, dtype=dtype) -def _load_helper_mo(lit: LineIterator, nprim: int) -> Tuple[int, float, float, np.ndarray]: +def _load_helper_mo(lit: LineIterator, nprim: int) -> tuple[int, float, float, NDArray]: """Read one section of MO information.""" line = next(lit) - if not line.startswith('MO'): + if not line.startswith("MO"): lit.error("Expecting line to start with 'MO'") # FORMAT (2X,I5,27X,F13.7,15X,F12.6) number = int(line[2:7]) occ = float(line[34:47]) energy = float(line[62:74]) # FORMAT (5E16.8) - coeffs = _load_helper_section(lit, nprim, '', 0, 16, float) + coeffs = _load_helper_section(lit, nprim, "", 0, 16, float) return number, occ, energy, coeffs def _load_helper_energy(lit: LineIterator) -> float: """Read energy.""" line = next(lit) - while 'ENERGY' not in line and line is not None: + while "ENERGY" not in line and line is not None: line = next(lit) # FORMAT (17X,F20.12) # Note: this differs between *.WFN files -- in some files the energy field ends at @@ -197,16 +202,16 @@ def _load_helper_energy(lit: LineIterator) -> float: return energy, virial -def _load_helper_multiwfn(lit: LineIterator, num_mo: int) -> np.ndarray: +def _load_helper_multiwfn(lit: LineIterator, num_mo: int) -> NDArray: """Read MO spin information from MULTIWFN extension.""" for line in lit: if "$MOSPIN $END" in line: # FORMAT (40I2) - return _load_helper_section(lit, num_mo, '', 0, 2, int) + return _load_helper_section(lit, num_mo, "", 0, 2, int) return np.empty((0,), dtype=int) -def load_wfn_low(lit: LineIterator) -> Tuple: +def load_wfn_low(lit: LineIterator) -> tuple: """Load data from a WFN file into arrays. Parameters @@ -220,30 +225,42 @@ def load_wfn_low(lit: LineIterator) -> Tuple: num_mo, nprim, num_atoms = _load_helper_num(lit) atnums, atcoords = _load_helper_atoms(lit, num_atoms) # centers are indexed from zero in HORTON - icenters = _load_helper_section(lit, nprim, 'CENTRE ASSIGNMENTS', 20, 3, int) - 1 + icenters = _load_helper_section(lit, nprim, "CENTRE ASSIGNMENTS", 20, 3, int) - 1 # The type assignments are integer indices for individual basis functions, # while in IOData, only the order within shells is fixed by configurable # conventions. In principle, the wfn format makes it possible for two # shells with the same angular momentum to have a different ordering of # the basis functions. - type_assignments = _load_helper_section(lit, nprim, 'TYPE ASSIGNMENTS', 20, 3, int) - 1 - exponent = _load_helper_section(lit, nprim, 'EXPONENTS', 10, 14, float) + type_assignments = _load_helper_section(lit, nprim, "TYPE ASSIGNMENTS", 20, 3, int) - 1 + exponent = _load_helper_section(lit, nprim, "EXPONENTS", 10, 14, float) mo_numbers = np.empty(num_mo, int) mo_occs = np.empty(num_mo, float) mo_energies = np.empty(num_mo, float) mo_coeffs = np.empty([nprim, num_mo], float) for mo in range(num_mo): - mo_numbers[mo], mo_occs[mo], mo_energies[mo], mo_coeffs[:, mo] = \ - _load_helper_mo(lit, nprim) + mo_numbers[mo], mo_occs[mo], mo_energies[mo], mo_coeffs[:, mo] = _load_helper_mo(lit, nprim) energy, virial = _load_helper_energy(lit) mo_spin = _load_helper_multiwfn(lit, num_mo) - return title, atnums, atcoords, icenters, type_assignments, exponent, \ - mo_numbers, mo_occs, mo_energies, mo_coeffs, energy, virial, mo_spin - - -# pylint: disable=too-many-branches -def build_obasis(icenters: np.ndarray, type_assignments: np.ndarray, - exponents: np.ndarray, lit: LineIterator) -> Tuple[MolecularBasis, np.ndarray]: + return ( + title, + atnums, + atcoords, + icenters, + type_assignments, + exponent, + mo_numbers, + mo_occs, + mo_energies, + mo_coeffs, + energy, + virial, + mo_spin, + ) + + +def build_obasis( + icenters: NDArray, type_assignments: NDArray, exponents: NDArray, lit: LineIterator +) -> tuple[MolecularBasis, NDArray]: """Construct a basis set using the arrays read from a WFN or WFX file. Parameters @@ -268,14 +285,11 @@ def build_obasis(icenters: np.ndarray, type_assignments: np.ndarray, while ibasis < nbasis: # Determine the angular moment of the shell type_assignment = type_assignments[ibasis] - if type_assignment == 0: - angmom = 0 - else: - # multiple different type assignments (codes for individual basis - # functions) can match one angular momentum. - angmom = len(PRIMITIVE_NAMES[type_assignments[ibasis]]) + # multiple different type assignments (codes for individual basis + # functions) can match one angular momentum. + angmom = 0 if type_assignment == 0 else len(PRIMITIVE_NAMES[type_assignments[ibasis]]) # The number of cartesian functions for the current angular momentum - ncart = len(CONVENTIONS[(angmom, 'c')]) + ncart = len(CONVENTIONS[(angmom, "c")]) # Determine how many shells are to be read in one batch. E.g. for a # contracted p shell, the WFN format contains first all px basis # functions, the all py, finally all pz. These need to be regrouped into @@ -290,49 +304,55 @@ def build_obasis(icenters: np.ndarray, type_assignments: np.ndarray, if angmom > 0: # batches for s-type functions are not necessary and may result in # multiple centers being pulled into one batch. - while (ibasis + ncon < len(type_assignments) - and type_assignments[ibasis + ncon] == type_assignment): + while ( + ibasis + ncon < len(type_assignments) + and type_assignments[ibasis + ncon] == type_assignment + ): ncon += 1 # Check if the type assignment is consistent for remaining basis # functions in this batch. for ifn in range(ncart): - if not (type_assignments[ibasis + ncon * ifn: ibasis + ncon * (ifn + 1)] - == type_assignments[ibasis + ncon * ifn]).all(): + if not ( + type_assignments[ibasis + ncon * ifn : ibasis + ncon * (ifn + 1)] + == type_assignments[ibasis + ncon * ifn] + ).all(): lit.error("Inconcsistent type assignments in current batch of shells.") # Check if all basis functions in the current batch sit on # the same center. If not, IOData cannot read this file. icenter = icenters[ibasis] - if not (icenters[ibasis: ibasis + ncon * ncart] == icenter).all(): + if not (icenters[ibasis : ibasis + ncon * ncart] == icenter).all(): lit.error("Incomplete shells in WFN file not supported by IOData.") # Check if the same exponent is used for corresponding basis functions. - batch_exponents = exponents[ibasis: ibasis + ncon] + batch_exponents = exponents[ibasis : ibasis + ncon] for ifn in range(ncart): - if not (exponents[ibasis + ncon * ifn: ibasis + ncon * (ifn + 1)] - == batch_exponents).all(): + if not ( + exponents[ibasis + ncon * ifn : ibasis + ncon * (ifn + 1)] == batch_exponents + ).all(): lit.error("Exponents must be the same for corresponding basis functions.") # A permutation is needed because we need to regroup basis functions # into shells. batch_primitive_names = [ - PRIMITIVE_NAMES[type_assignments[ibasis + ifn * ncon]] - for ifn in range(ncart)] + PRIMITIVE_NAMES[type_assignments[ibasis + ifn * ncon]] for ifn in range(ncart) + ] for irep in range(ncon): for i, primitive_name in enumerate(batch_primitive_names): - ifn = CONVENTIONS[(angmom, 'c')].index(primitive_name) + ifn = CONVENTIONS[(angmom, "c")].index(primitive_name) permutation[ibasis + irep * ncart + ifn] = ibasis + irep + i * ncon # WFN uses non-normalized primitives, which will be corrected for # when processing the MO coefficients. Normalized primitives will # be used here. No attempt is made here to reconstruct the contraction. - for exponent in batch_exponents: - shells.append(Shell(icenter, [angmom], ['c'], np.array([exponent]), - np.array([[1.0]]))) + shells.extend( + Shell(icenter, [angmom], ["c"], np.array([exponent]), np.array([[1.0]])) + for exponent in batch_exponents + ) # Move on to the next contraction ibasis += ncart * ncon - obasis = MolecularBasis(shells, CONVENTIONS, 'L2') + obasis = MolecularBasis(shells, CONVENTIONS, "L2") assert obasis.nbasis == nbasis return obasis, permutation -def get_mocoeff_scales(obasis: MolecularBasis) -> np.ndarray: +def get_mocoeff_scales(obasis: MolecularBasis) -> NDArray: """Get the L2-normalization of the un-normalized Cartesian basis functions. Parameters @@ -350,26 +370,39 @@ def get_mocoeff_scales(obasis: MolecularBasis) -> np.ndarray: scales = [] for shell in obasis.shells: angmom = shell.angmoms[0] - for name in obasis.conventions[(angmom, 'c')]: - if name == '1': + for name in obasis.conventions[(angmom, "c")]: + if name == "1": nx, ny, nz = 0, 0, 0 else: - nx = name.count('x') - ny = name.count('y') - nz = name.count('z') + nx = name.count("x") + ny = name.count("y") + nz = name.count("z") scales.append(gob_cart_normalization(shell.exponents[0], np.array([nx, ny, nz]))) return np.array(scales) -@document_load_one("WFN", ['atcoords', 'atnums', 'energy', 'mo', 'obasis', 'title', 'extra']) +@document_load_one("WFN", ["atcoords", "atnums", "energy", "mo", "obasis", "title", "extra"]) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" - (title, atnums, atcoords, icenters, type_assignments, exponents, mo_numbers, mo_occs, - mo_energies, mo_coeffs, energy, virial, mo_spin) = load_wfn_low(lit) + ( + title, + atnums, + atcoords, + icenters, + type_assignments, + exponents, + mo_numbers, + mo_occs, + mo_energies, + mo_coeffs, + energy, + virial, + mo_spin, + ) = load_wfn_low(lit) # Build the basis set and the permutation needed to regroup shells. obasis, permutation = build_obasis(icenters, type_assignments, exponents, lit) # Extra dict to return. - extra = {'virial_ratio': virial} + extra = {"virial_ratio": virial} # ---------------------------- # Build the molecular orbitals # ---------------------------- @@ -385,7 +418,7 @@ def load_one(lit: LineIterator) -> dict: norb_ab = np.sum(mo_spin == 3) if norb_a + norb_b + norb_ab != norb or (norb_b and norb_ab): lit.error("Invalid orbital spin types.") - extra['mo_spin'] = mo_spin + extra["mo_spin"] = mo_spin # Determine norb_a,norb_b,norb_ab for restricted wave function by heuristic. elif mo_occs.max() > 1.0: norb_a = 0 @@ -395,10 +428,12 @@ def load_one(lit: LineIterator) -> dict: # This may still fail in some corner cases. else: norb_a = 1 - while (norb_a < mo_coeffs.shape[1] - and mo_energies[norb_a] >= mo_energies[norb_a - 1] - and mo_occs[norb_a] <= mo_occs[norb_a - 1] - and mo_numbers[norb_a] == mo_numbers[norb_a - 1] + 1): + while ( + norb_a < mo_coeffs.shape[1] + and mo_energies[norb_a] >= mo_energies[norb_a - 1] + and mo_occs[norb_a] <= mo_occs[norb_a - 1] + and mo_numbers[norb_a] == mo_numbers[norb_a - 1] + 1 + ): norb_a += 1 norb_b = norb - norb_a norb_ab = 0 @@ -406,61 +441,59 @@ def load_one(lit: LineIterator) -> dict: if norb_ab: # Restricted wavefunction. mo = MolecularOrbitals( - 'restricted', norb_a + norb_ab, norb_a + norb_ab, - mo_occs, mo_coeffs, mo_energies) + "restricted", norb_a + norb_ab, norb_a + norb_ab, mo_occs, mo_coeffs, mo_energies + ) else: # Unrestricted wavefunction. - mo = MolecularOrbitals( - 'unrestricted', norb_a, norb_b, - mo_occs, mo_coeffs, mo_energies) + mo = MolecularOrbitals("unrestricted", norb_a, norb_b, mo_occs, mo_coeffs, mo_energies) return { - 'title': title, - 'atcoords': atcoords, - 'atnums': atnums, - 'obasis': obasis, - 'mo': mo, - 'energy': energy, - 'extra': extra, + "title": title, + "atcoords": atcoords, + "atnums": atnums, + "obasis": obasis, + "mo": mo, + "energy": energy, + "extra": extra, } -def _format_helper_section(header: str, skip: int, spec: str, nline: int) -> Tuple[str, int]: +def _format_helper_section(header: str, skip: int, spec: str, nline: int) -> tuple[str, int]: """Return a format string for CENTRE_ASSIGMENTS, TYPE_ASSIGNMENTS, EXPONENTS lines.""" - return f'{header[:skip].ljust(skip)}{spec * nline}', len(spec) + return f"{header[:skip].ljust(skip)}{spec * nline}", len(spec) -def _dump_helper_section(f: TextIO, data: np.ndarray, fmt: str, skip: int, step: int, nline: int): +def _dump_helper_section(f: TextIO, data: NDArray, fmt: str, skip: int, step: int, nline: int): """Write a CENTRE_ASSIGNMENTS, TYPE_ASSIGNMENTS, or EXPONENTS section to file ``f``.""" while len(data) > 0: chunk = data[:nline] n_chunk = len(chunk) - print(fmt[:skip + n_chunk * step].format(*chunk), file=f) + print(fmt[: skip + n_chunk * step].format(*chunk), file=f) data = data[n_chunk:] # FORMAT (16X,I7,13X,I7,11X,I9) -FMT_NUM = 'GAUSSIAN {0:7d} MOL ORBITALS{1:7d} PRIMITIVES{2:9d} NUCLEI' +FMT_NUM = "GAUSSIAN {0:7d} MOL ORBITALS{1:7d} PRIMITIVES{2:9d} NUCLEI" # FORMAT (2X,A3,I3,11X,I3,2X,3F12.8,10X,F5.1) -FMT_ATM = ' {0:3s}{1:3d} (CENTRE{2:3d}) {3:12.8f}{4:12.8f}{5:12.8f} CHARGE ={6:5.1f}' +FMT_ATM = " {0:3s}{1:3d} (CENTRE{2:3d}) {3:12.8f}{4:12.8f}{5:12.8f} CHARGE ={6:5.1f}" # FORMAT (2X,5I,8X,F3.1,16X,F13.7,15X,F12.6) -FMT_MOS = 'MO{0:5d} MO {1:3.1f} OCC NO ={2:13.7f} ORB. ENERGY ={3:12.6f}' +FMT_MOS = "MO{0:5d} MO {1:3.1f} OCC NO ={2:13.7f} ORB. ENERGY ={3:12.6f}" # FORMAT (17X,F20.12,18X,F13.8) -FMT_ENERGY = ' TOTAL ENERGY = {0:20.12f} THE VIRIAL(-V/T)={1:13.8f}' +FMT_ENERGY = " TOTAL ENERGY = {0:20.12f} THE VIRIAL(-V/T)={1:13.8f}" # FORMAT (20X,20I3) -FMT_CNTR, STEP_CNTR = _format_helper_section('CENTRE ASSIGNMENTS', 20, '{:3d}', 20) +FMT_CNTR, STEP_CNTR = _format_helper_section("CENTRE ASSIGNMENTS", 20, "{:3d}", 20) # FORMAT (20X,20I3) -FMT_TYPE, STEP_TYPE = _format_helper_section('TYPE ASSIGNMENTS', 20, '{:3d}', 20) +FMT_TYPE, STEP_TYPE = _format_helper_section("TYPE ASSIGNMENTS", 20, "{:3d}", 20) # FORMAT (10X,5E14.7) -FMT_EXPN, STEP_EXPN = _format_helper_section('EXPONENTS', 10, '{:14.7E}', 5) +FMT_EXPN, STEP_EXPN = _format_helper_section("EXPONENTS", 10, "{:14.7E}", 5) # FORMAT (5E16.8) -FMT_COEF, STEP_COEF = _format_helper_section('', 0, '{:16.8E}', 5) +FMT_COEF, STEP_COEF = _format_helper_section("", 0, "{:16.8E}", 5) # FORMAT (40I2) -FMT_SPIN, STEP_SPIN = _format_helper_section('', 0, '{:2d}', 40) +FMT_SPIN, STEP_SPIN = _format_helper_section("", 0, "{:2d}", 40) # Default .WFN title DEFAULT_WFN_TTL = "WFN auto-generated by IOData" -@document_dump_one("WFN", ['atcoords', 'atnums', 'energy', 'mo', 'obasis', 'title', 'extra']) +@document_dump_one("WFN", ["atcoords", "atnums", "energy", "mo", "obasis", "title", "extra"]) def dump_one(f: TextIO, data: IOData) -> None: """Do not edit this docstring. It will be overwritten.""" # get shells for the de-contracted basis @@ -468,10 +501,13 @@ def dump_one(f: TextIO, data: IOData) -> None: for shell in data.obasis.shells: for i, (angmom, kind) in enumerate(zip(shell.angmoms, shell.kinds)): for exponent, coeff in zip(shell.exponents, shell.coeffs.T[i]): - if kind != 'c': + if kind != "c": raise ValueError("WFN can be generated only for Cartesian MolecularBasis!") - shells.append(Shell(shell.icenter, [angmom], [kind], np.array([exponent]), - coeff.reshape(-1, 1))) + shells.append( + Shell( + shell.icenter, [angmom], [kind], np.array([exponent]), coeff.reshape(-1, 1) + ) + ) # make a new instance of MolecularBasis with de-contracted basis shells; ideally for WFN we # want the primitive basis set, but IOData only supports shells. obasis = MolecularBasis(shells, data.obasis.conventions, data.obasis.primitive_normalization) @@ -484,9 +520,9 @@ def dump_one(f: TextIO, data: IOData) -> None: for shell in data.obasis.shells: for angmom, kind in zip(shell.angmoms, shell.kinds): n = len(data.obasis.conventions[angmom, kind]) - c = raw_coeffs[index_mo_old: index_mo_old + n] + c = raw_coeffs[index_mo_old : index_mo_old + n] for _ in range(shell.nprim): - mo_coeffs[index_mo_new: index_mo_new + n] = c + mo_coeffs[index_mo_new : index_mo_new + n] = c index_mo_new += n index_mo_old += n @@ -513,12 +549,12 @@ def dump_one(f: TextIO, data: IOData) -> None: angmom_prim = {} count = 1 for angmom in range(max([shell.angmoms[0] for shell in obasis.shells]) + 1): - angmom_prim[angmom] = [count + i for i in range(len(obasis.conventions[angmom, 'c']))] - count += len(obasis.conventions[angmom, 'c']) + angmom_prim[angmom] = [count + i for i in range(len(obasis.conventions[angmom, "c"]))] + count += len(obasis.conventions[angmom, "c"]) types = [item for shell in obasis.shells for item in angmom_prim[shell.angmoms[0]]] # Write header (title, # MOs, # primitives, # atoms) - print(f' {data.title if data.title else DEFAULT_WFN_TTL}', file=f) + print(f" {data.title if data.title else DEFAULT_WFN_TTL}", file=f) print(FMT_NUM.format(data.mo.norb, obasis.nbasis, data.natom), file=f) # Write atoms (symbol, atom #, centre #, x pos., y pos., z pos., charge) @@ -538,10 +574,10 @@ def dump_one(f: TextIO, data: IOData) -> None: _dump_helper_section(f, coeffs, FMT_COEF, 0, STEP_COEF, 5) # Write energy and virial coefficient - print('END DATA', file=f) - print(FMT_ENERGY.format(data.energy or np.nan, data.extra.get('virial_ratio', np.nan)), file=f) + print("END DATA", file=f) + print(FMT_ENERGY.format(data.energy or np.nan, data.extra.get("virial_ratio", np.nan)), file=f) # Write MOSPIN extension section (optional) - if data.extra.get('mo_spin') is not None: - print(' $MOSPIN $END\n\n', file=f) - _dump_helper_section(f, data.extra['mo_spin'], FMT_SPIN, 0, STEP_SPIN, 40) + if data.extra.get("mo_spin") is not None: + print(" $MOSPIN $END\n\n", file=f) + _dump_helper_section(f, data.extra["mo_spin"], FMT_SPIN, 0, STEP_SPIN, 40) diff --git a/iodata/formats/wfx.py b/iodata/formats/wfx.py index d0f862dc8..e5041b863 100644 --- a/iodata/formats/wfx.py +++ b/iodata/formats/wfx.py @@ -21,23 +21,23 @@ See http://aim.tkgristmill.com/wfxformat.html """ -from typing import TextIO, Iterator import warnings +from collections.abc import Iterator +from typing import Optional, TextIO import numpy as np -from ..docstrings import document_load_one, document_dump_one +from ..basis import MolecularBasis, Shell, convert_conventions +from ..docstrings import document_dump_one, document_load_one +from ..iodata import IOData from ..orbitals import MolecularOrbitals from ..periodic import num2sym -from ..iodata import IOData from ..utils import LineIterator -from ..basis import MolecularBasis, Shell, convert_conventions - -from .wfn import build_obasis, get_mocoeff_scales, CONVENTIONS +from .wfn import CONVENTIONS, build_obasis, get_mocoeff_scales __all__ = [] -PATTERNS = ['*.wfx'] +PATTERNS = ["*.wfx"] def _wfx_labels() -> tuple: @@ -46,67 +46,74 @@ def _wfx_labels() -> tuple: # section labels with string data types labels_str = { - '': 'title', - '<Keywords>': 'keywords', - '<Model>': 'model_name', + "<Title>": "title", + "<Keywords>": "keywords", + "<Model>": "model_name", } # section labels with integer number data types labels_int = { - '<Number of Nuclei>': 'num_atoms', - '<Number of Occupied Molecular Orbitals>': 'num_occ_mo', - '<Number of Perturbations>': 'num_perturbations', - '<Number of Electrons>': 'num_electrons', - '<Number of Core Electrons>': 'num_core_electrons', - '<Number of Alpha Electrons>': 'num_alpha_electron', - '<Number of Beta Electrons>': 'num_beta_electron', - '<Number of Primitives>': 'num_primitives', - '<Electronic Spin Multiplicity>': 'spin_multi', + "<Number of Nuclei>": "num_atoms", + "<Number of Occupied Molecular Orbitals>": "num_occ_mo", + "<Number of Perturbations>": "num_perturbations", + "<Number of Electrons>": "num_electrons", + "<Number of Core Electrons>": "num_core_electrons", + "<Number of Alpha Electrons>": "num_alpha_electron", + "<Number of Beta Electrons>": "num_beta_electron", + "<Number of Primitives>": "num_primitives", + "<Electronic Spin Multiplicity>": "spin_multi", } # section labels with float number data types labels_float = { - '<Net Charge>': 'charge', - '<Energy = T + Vne + Vee + Vnn>': 'energy', - '<Virial Ratio (-V/T)>': 'virial_ratio', - '<Nuclear Virial of Energy-Gradient-Based Forces on Nuclei, W>': 'nuc_viral', - '<Full Virial Ratio, -(V - W)/T>': 'full_virial_ratio', + "<Net Charge>": "charge", + "<Energy = T + Vne + Vee + Vnn>": "energy", + "<Virial Ratio (-V/T)>": "virial_ratio", + "<Nuclear Virial of Energy-Gradient-Based Forces on Nuclei, W>": "nuc_viral", + "<Full Virial Ratio, -(V - W)/T>": "full_virial_ratio", } # section labels with array of integer data types labels_array_int = { - '<Atomic Numbers>': 'atnums', - '<Primitive Centers>': 'centers', - '<Primitive Types>': 'types', - '<MO Numbers>': 'mo_numbers', # This is constructed in parse_wfx. + "<Atomic Numbers>": "atnums", + "<Primitive Centers>": "centers", + "<Primitive Types>": "types", + "<MO Numbers>": "mo_numbers", # This is constructed in parse_wfx. } # section labels with array of float data types labels_array_float = { - '<Nuclear Cartesian Coordinates>': 'atcoords', - '<Nuclear Charges>': 'nuclear_charge', - '<Primitive Exponents>': 'exponents', - '<Molecular Orbital Energies>': 'mo_energies', - '<Molecular Orbital Occupation Numbers>': 'mo_occs', - '<Molecular Orbital Primitive Coefficients>': 'mo_coeffs', + "<Nuclear Cartesian Coordinates>": "atcoords", + "<Nuclear Charges>": "nuclear_charge", + "<Primitive Exponents>": "exponents", + "<Molecular Orbital Energies>": "mo_energies", + "<Molecular Orbital Occupation Numbers>": "mo_occs", + "<Molecular Orbital Primitive Coefficients>": "mo_coeffs", } # section labels with other data types labels_other = { - '<Nuclear Names>': 'nuclear_names', - '<Molecular Orbital Spin Types>': 'mo_spins', - '<Nuclear Cartesian Energy Gradients>': 'nuclear_gradient', + "<Nuclear Names>": "nuclear_names", + "<Molecular Orbital Spin Types>": "mo_spins", + "<Nuclear Cartesian Energy Gradients>": "nuclear_gradient", } # list of tags corresponding to required sections based on WFX format specifications required_tags = list(labels_str) + list(labels_int) + list(labels_float) required_tags += list(labels_array_float) + list(labels_array_int) + list(labels_other) # remove tags corresponding to optional sections - required_tags.remove('<Model>') - required_tags.remove('<Number of Core Electrons>') - required_tags.remove('<Electronic Spin Multiplicity>') - required_tags.remove('<Atomic Numbers>') - required_tags.remove('<Full Virial Ratio, -(V - W)/T>') - required_tags.remove('<Nuclear Virial of Energy-Gradient-Based Forces on Nuclei, W>') - required_tags.remove('<Nuclear Cartesian Energy Gradients>') - - return (labels_str, labels_int, labels_float, labels_array_int, labels_array_float, - labels_other, required_tags) + required_tags.remove("<Model>") + required_tags.remove("<Number of Core Electrons>") + required_tags.remove("<Electronic Spin Multiplicity>") + required_tags.remove("<Atomic Numbers>") + required_tags.remove("<Full Virial Ratio, -(V - W)/T>") + required_tags.remove("<Nuclear Virial of Energy-Gradient-Based Forces on Nuclei, W>") + required_tags.remove("<Nuclear Cartesian Energy Gradients>") + + return ( + labels_str, + labels_int, + labels_float, + labels_array_int, + labels_array_float, + labels_other, + required_tags, + ) def load_data_wfx(lit: LineIterator) -> dict: @@ -129,38 +136,37 @@ def load_data_wfx(lit: LineIterator) -> dict: assert len(value) == 1 result[lbs_float[key]] = float(value[0]) elif key in lbs_afloat: - result[lbs_afloat[key]] = np.fromstring(" ".join(value), dtype=np.float, sep=" ") + result[lbs_afloat[key]] = np.fromstring(" ".join(value), dtype=float, sep=" ") elif key in lbs_aint: - result[lbs_aint[key]] = np.fromstring(" ".join(value), dtype=np.int, sep=" ") + result[lbs_aint[key]] = np.fromstring(" ".join(value), dtype=int, sep=" ") elif key in lbs_other: result[lbs_other[key]] = value else: - warnings.warn("Not recognized section label, skip {0}".format(key)) + warnings.warn(f"Not recognized section label, skip {key}", stacklevel=2) # reshape some arrays - result['atcoords'] = result['atcoords'].reshape(-1, 3) - result['mo_coeffs'] = result['mo_coeffs'].reshape(result['num_primitives'], -1, order='F') + result["atcoords"] = result["atcoords"].reshape(-1, 3) + result["mo_coeffs"] = result["mo_coeffs"].reshape(result["num_primitives"], -1, order="F") # process nuclear gradient, if present - if 'nuclear_gradient' in result: - gradient_mix = np.array([i.split() for i in result.pop('nuclear_gradient')]).reshape(-1, 4) - gradient_atoms = gradient_mix[:, 0].astype(np.unicode_) - index = [result['nuclear_names'].index(atom) for atom in gradient_atoms] - result['atgradient'] = np.full((len(result['nuclear_names']), 3), np.nan) - result['atgradient'][index] = gradient_mix[:, 1:].astype(float) + if "nuclear_gradient" in result: + gradient_mix = np.array([i.split() for i in result.pop("nuclear_gradient")]).reshape(-1, 4) + gradient_atoms = gradient_mix[:, 0].astype(np.str_) + index = [result["nuclear_names"].index(atom) for atom in gradient_atoms] + result["atgradient"] = np.full((len(result["nuclear_names"]), 3), np.nan) + result["atgradient"][index] = gradient_mix[:, 1:].astype(float) # check keywords & number of perturbations - perturbation_check = {'GTO': 0, 'GIAO': 3, 'CGST': 6} - key = result['keywords'] - num = result['num_perturbations'] - if key not in perturbation_check.keys(): + perturbation_check = {"GTO": 0, "GIAO": 3, "CGST": 6} + key = result["keywords"] + num = result["num_perturbations"] + if key not in perturbation_check: lit.error(f"The keywords is {key}, but it should be either GTO, GIAO or CGST") if num != perturbation_check[key]: lit.error(f"Number of perturbations of {key} is {num}, expected {perturbation_check[key]}") return result -def parse_wfx(lit: LineIterator, required_tags: list = None) -> dict: +def parse_wfx(lit: LineIterator, required_tags: Optional[list] = None) -> dict: """Load data in all sections existing in the given WFX file LineIterator.""" - # pylint: disable=too-many-branches data = {} mo_start = "<Molecular Orbital Primitive Coefficients>" section_start = None @@ -175,24 +181,24 @@ def parse_wfx(lit: LineIterator, required_tags: list = None) -> dict: if section_start is None and line.startswith("<"): # set start & end of the section and add it to data dictionary section_start = line - if section_start in data.keys(): - lit.error("Section with tag={} is repeated!".format(section_start)) + if section_start in data: + lit.error(f"Section with tag={section_start} is repeated!") data[section_start] = [] section_end = line[:1] + "/" + line[1:] # special handling of <Molecular Orbital Primitive Coefficients> section if section_start == mo_start: - data['<MO Numbers>'] = [] + data["<MO Numbers>"] = [] # check whether line is the (correct) end of the section elif section_start is not None and line.startswith("</"): # In some cases, closing tags have a different number of spaces. 8-[ if line.replace(" ", "") != section_end.replace(" ", ""): - lit.error("Expecting line {} but got {}.".format(section_end, line)) + lit.error(f"Expecting line {section_end} but got {line}.") # reset section_start variable to signal that section ended section_start = None # handle <MO Number> line under <Molecular Orbital Primitive Coefficients> section - elif section_start == mo_start and line == '<MO Number>': + elif section_start == mo_start and line == "<MO Number>": # add MO Number to list - data['<MO Numbers>'].append(next(lit).strip()) + data["<MO Numbers>"].append(next(lit).strip()) # skip '</MO Number>' line next(lit) # add section content to the corresponding list in data dictionary @@ -201,17 +207,18 @@ def parse_wfx(lit: LineIterator, required_tags: list = None) -> dict: # check if last section was closed if section_start is not None: - lit.error("Section {} is not closed at end of file.".format(section_start)) + lit.error(f"Section {section_start} is not closed at end of file.") # check required section tags if required_tags is not None: for section_tag in required_tags: - if section_tag not in data.keys(): - lit.error(f'Section {section_tag} is missing from loaded WFX data.') + if section_tag not in data: + lit.error(f"Section {section_tag} is missing from loaded WFX data.") return data -@document_load_one("WFX", ['atcoords', 'atgradient', 'atnums', 'energy', - 'extra', 'mo', 'obasis', 'title']) +@document_load_one( + "WFX", ["atcoords", "atgradient", "atnums", "energy", "extra", "mo", "obasis", "title"] +) def load_one(lit: LineIterator) -> dict: """Do not edit this docstring. It will be overwritten.""" # get data contained in WFX file with the proper type & shape @@ -221,7 +228,8 @@ def load_one(lit: LineIterator) -> dict: # --------------------- # build molecular basis and permutation needed to regroup shells obasis, permutation = build_obasis( - data['centers'] - 1, data['types'] - 1, data['exponents'], lit) + data["centers"] - 1, data["types"] - 1, data["exponents"], lit + ) # Build molecular orbitals # ------------------------ @@ -233,7 +241,7 @@ def load_one(lit: LineIterator) -> dict: # the px, py, pz basis functions. These shells are used by MolecularBasis (obasis) in # constructing the basis function. If that is the case for the loaded MO coefficients from WFX, # they need to be permuted to match obasis expansion of basis set (i.e. to appear in shells). - data['mo_coeffs'] = data['mo_coeffs'][permutation] + data["mo_coeffs"] = data["mo_coeffs"][permutation] # fix normalization because the loaded expansion coefficients from WFX corresponds to # un-normalized primitives for each normalized MO (which means the primitive normalization # constants has been included in the MO coefficients). However, IOData expects normalized @@ -242,20 +250,20 @@ def load_one(lit: LineIterator) -> dict: # to expansion coefficients for normalized primitives. Here, we assume primitives are # L2-normalized (as stored in obasis.primitive_normalization) which is used in scaling MO # coefficients to be stored in MolecularOrbitals instance. - data['mo_coeffs'] /= get_mocoeff_scales(obasis).reshape(-1, 1) + data["mo_coeffs"] /= get_mocoeff_scales(obasis).reshape(-1, 1) # process mo_spins and convert it into restricted or unrestricted & count alpha/beta orbitals # we do not using the <Model> section for this because it is not guaranteed to be present # check whether restricted case with "Alpha and Beta" in mo_spins - if any("and" in word for word in data['mo_spins']): + if any("and" in word for word in data["mo_spins"]): # count number of alpha & beta molecular orbitals - norbb = data['mo_spins'].count("Alpha and Beta") - norba = norbb + data['mo_spins'].count("Alpha") + norbb = data["mo_spins"].count("Alpha and Beta") + norba = norbb + data["mo_spins"].count("Alpha") # check that mo_spin list contains no surprises - if data['mo_spins'] != ["Alpha and Beta"] * norbb + ["Alpha"] * (norba - norbb): + if data["mo_spins"] != ["Alpha and Beta"] * norbb + ["Alpha"] * (norba - norbb): lit.error("Unsupported <Molecular Orbital Spin Types> values.") - if norba != data['mo_coeffs'].shape[1]: + if norba != data["mo_coeffs"].shape[1]: lit.error("Number of orbitals inconsistent with orbital spin types.") # create molecular orbitals, which requires knowing the number of alpha and beta molecular # orbitals. These are expected to be the same for 'restricted' case, however, the number of @@ -267,49 +275,65 @@ def load_one(lit: LineIterator) -> dict: # occupation numbers to identify the spin types. IOData also has different # conventions for norba and norbb, see orbitals.py for details. mo = MolecularOrbitals( - "restricted", norba, norba, # This is not a typo! - data['mo_occs'], data['mo_coeffs'], data['mo_energies']) + "restricted", + norba, + norba, # This is not a typo! + data["mo_occs"], + data["mo_coeffs"], + data["mo_energies"], + ) # unrestricted case with "Alpha" and "Beta" in mo_spins else: - norba = data['mo_spins'].count("Alpha") - norbb = data['mo_spins'].count("Beta") + norba = data["mo_spins"].count("Alpha") + norbb = data["mo_spins"].count("Beta") # check that mo_spin list contains no surprises - if data['mo_spins'] != ["Alpha"] * norba + ["Beta"] * norbb: + if data["mo_spins"] != ["Alpha"] * norba + ["Beta"] * norbb: lit.error("Unsupported molecular orbital spin types.") # check that number of orbitals match number of MO coefficients - if norba + norbb != data['mo_coeffs'].shape[1]: + if norba + norbb != data["mo_coeffs"].shape[1]: lit.error("Number of orbitals inconsistent with orbital spin types.") # Create orbitals. For unrestricted wavefunctions, IOData uses the same # conventions as WFX. mo = MolecularOrbitals( - "unrestricted", norba, norbb, - data['mo_occs'], data['mo_coeffs'], data['mo_energies']) + "unrestricted", norba, norbb, data["mo_occs"], data["mo_coeffs"], data["mo_energies"] + ) # prepare WFX-specific data for IOData - extra_labels = ['keywords', 'model_name', 'num_perturbations', 'num_core_electrons', - 'spin_multi', 'virial_ratio', 'nuc_viral', 'full_virial_ratio', 'mo_spin'] + extra_labels = [ + "keywords", + "model_name", + "num_perturbations", + "num_core_electrons", + "spin_multi", + "virial_ratio", + "nuc_viral", + "full_virial_ratio", + "mo_spin", + ] extra = {label: data.get(label, None) for label in extra_labels} extra["permutations"] = permutation return { - 'atcoords': data['atcoords'], - 'atgradient': data.get('atgradient'), - 'atnums': data['atnums'], - 'atcorenums': data['nuclear_charge'], - 'energy': data['energy'], - 'extra': extra, - 'mo': mo, - 'obasis': obasis, - 'title': data['title'], + "atcoords": data["atcoords"], + "atgradient": data.get("atgradient"), + "atnums": data["atnums"], + "atcorenums": data["nuclear_charge"], + "energy": data["energy"], + "extra": extra, + "mo": mo, + "obasis": obasis, + "title": data["title"], } -@document_dump_one("WFX", ['atcoords', 'atnums', 'atcorenums', 'mo', 'obasis', 'charge'], - ['title', 'energy', 'spinpol', 'lot', 'atgradient', 'extra']) +@document_dump_one( + "WFX", + ["atcoords", "atnums", "atcorenums", "mo", "obasis", "charge"], + ["title", "energy", "spinpol", "lot", "atgradient", "extra"], +) def dump_one(f: TextIO, data: IOData): """Do not edit this docstring. It will be overwritten.""" - # pylint: disable=too-many-branches,too-many-statements # get all tags/labels that can be written into a WFX file lbs_str, lbs_int, lbs_float, lbs_aint, lbs_afloat, lbs_other, _ = _wfx_labels() # put all labels in one dictionary and flip key and value for easier use @@ -323,10 +347,13 @@ def dump_one(f: TextIO, data: IOData): for shell in data.obasis.shells: for i, (angmom, kind) in enumerate(zip(shell.angmoms, shell.kinds)): for exponent, coeff in zip(shell.exponents, shell.coeffs.T[i]): - if kind != 'c': + if kind != "c": raise ValueError("WFX can be generated only for Cartesian MolecularBasis!") - shells.append(Shell(shell.icenter, [angmom], [kind], np.array([exponent]), - coeff.reshape(-1, 1))) + shells.append( + Shell( + shell.icenter, [angmom], [kind], np.array([exponent]), coeff.reshape(-1, 1) + ) + ) # make a new instance of MolecularBasis with de-contracted basis shells; ideally for WFX we # want the primitive basis set, but IOData only supports shells. obasis = MolecularBasis(shells, data.obasis.conventions, data.obasis.primitive_normalization) @@ -342,9 +369,9 @@ def dump_one(f: TextIO, data: IOData): for shell in data.obasis.shells: for angmom, kind in zip(shell.angmoms, shell.kinds): n = len(data.obasis.conventions[angmom, kind]) - c = raw_coeffs[index_mo_old: index_mo_old + n] - for j in range(shell.nprim): - mo_coeffs[index_mo_new: index_mo_new + n] = c + c = raw_coeffs[index_mo_old : index_mo_old + n] + for _j in range(shell.nprim): + mo_coeffs[index_mo_new : index_mo_new + n] = c index_mo_new += n index_mo_old += n # fix MO coefficients @@ -364,7 +391,7 @@ def dump_one(f: TextIO, data: IOData): mo_coeffs[:, index] *= contractions * scales # write title & keywords - _write_xml_single(tag=lbs["title"], info=data.title or '<Created with IOData>', file=f) + _write_xml_single(tag=lbs["title"], info=data.title or "<Created with IOData>", file=f) _write_xml_single(tag=lbs["keywords"], info=data.extra.get("keywords", "GTO"), file=f) # write number of nuclei & number of primitives @@ -382,8 +409,8 @@ def dump_one(f: TextIO, data: IOData): # write nuclear names, atomic numbers, and nuclear charges # add ghost atom, represented by Bq and atomic number 0 - num2sym.update({0: 'Bq'}) - nuclear_names = [f' {num2sym[num]}{index + 1}' for index, num in enumerate(data.atcorenums)] + num2sym.update({0: "Bq"}) + nuclear_names = [f" {num2sym[num]}{index + 1}" for index, num in enumerate(data.atcorenums)] _write_xml_iterator(tag=lbs["nuclear_names"], info=nuclear_names, file=f) _write_xml_iterator(tag=lbs["atnums"], info=data.atnums, file=f) _write_xml_iterator_scientific(tag=lbs["nuclear_charge"], info=data.atcorenums, file=f) @@ -391,7 +418,7 @@ def dump_one(f: TextIO, data: IOData): # write nuclear cartesian coordinates print("<Nuclear Cartesian Coordinates>", file=f) for item in data.atcoords: - print('{: ,.14E} {: ,.14E} {: ,.14E}'.format(item[0], item[1], item[2]), file=f) + print(f"{item[0]: ,.14E} {item[1]: ,.14E} {item[2]: ,.14E}", file=f) print("</Nuclear Cartesian Coordinates>", file=f) # write net charge, number of electrons, number of alpha electrons, and number beta electrons @@ -412,26 +439,26 @@ def dump_one(f: TextIO, data: IOData): prim_centers = [shell.icenter + 1 for shell in obasis.shells for _ in range(shell.nbasis)] print("<Primitive Centers>", file=f) for j in range(0, len(prim_centers), 10): - print(' '.join(['{:d}'.format(c) for c in prim_centers[j:j + 10]]), file=f) + print(" ".join([f"{c:d}" for c in prim_centers[j : j + 10]]), file=f) print("</Primitive Centers>", file=f) # write primitive types angmom_prim = {} count = 1 for angmom in range(max([shell.angmoms[0] for shell in obasis.shells]) + 1): - angmom_prim[angmom] = [count + i for i in range(len(obasis.conventions[angmom, 'c']))] - count += len(obasis.conventions[angmom, 'c']) + angmom_prim[angmom] = [count + i for i in range(len(obasis.conventions[angmom, "c"]))] + count += len(obasis.conventions[angmom, "c"]) prim_types = [item for shell in obasis.shells for item in angmom_prim[shell.angmoms[0]]] print("<Primitive Types>", file=f) for j in range(0, len(prim_types), 10): - print(' '.join(['{:d}'.format(c) for c in prim_types[j:j + 10]]), file=f) + print(" ".join([f"{c:d}" for c in prim_types[j : j + 10]]), file=f) print("</Primitive Types>", file=f) # write primitive exponents exponents = [shell.exponents[0] for shell in obasis.shells for _ in range(shell.nbasis)] print("<Primitive Exponents>", file=f) for j in range(0, len(exponents), 4): - print(' '.join(['{: ,.14E}'.format(e) for e in exponents[j:j + 4]]), file=f) + print(" ".join([f"{e: ,.14E}" for e in exponents[j : j + 4]]), file=f) print("</Primitive Exponents>", file=f) # write molecular orbital occupation numbers @@ -441,10 +468,10 @@ def dump_one(f: TextIO, data: IOData): _write_xml_iterator_scientific(tag=lbs["mo_energies"], info=data.mo.energies, file=f) # write molecular orbital spin types - if data.mo.kind == 'restricted': - mo_spin = ['Alpha and Beta '] * len(data.mo.occs) + if data.mo.kind == "restricted": + mo_spin = ["Alpha and Beta "] * len(data.mo.occs) else: - mo_spin = ['Alpha'] * len(data.mo.occsa) + ['Beta'] * len(data.mo.occsb) + mo_spin = ["Alpha"] * len(data.mo.occsa) + ["Beta"] * len(data.mo.occsb) _write_xml_iterator(tag=lbs["mo_spins"], info=mo_spin, file=f) # write MO primitive coefficients @@ -454,7 +481,7 @@ def dump_one(f: TextIO, data: IOData): print(str(mo + 1), file=f) print("</MO Number>", file=f) for j in range(0, obasis.nbasis, 4): - print(' '.join(['{: ,.14E}'.format(c) for c in mo_coeffs.T[mo][j:j + 4]]), file=f) + print(" ".join([f"{c: ,.14E}" for c in mo_coeffs.T[mo][j : j + 4]]), file=f) print("</Molecular Orbital Primitive Coefficients>", file=f) # write energy and virial ratio; use ' NAN' when None (not available) @@ -466,8 +493,11 @@ def dump_one(f: TextIO, data: IOData): nuc_cart_energy_grad = list(zip(nuclear_names, data.atgradient)) print("<Nuclear Cartesian Energy Gradients>", file=f) for atom in nuc_cart_energy_grad: - print(atom[0], '{: ,.14E} {: ,.14E} {: ,.14E}'.format(atom[1][0], atom[1][1], - atom[1][2]), file=f) + print( + atom[0], + f"{atom[1][0]: ,.14E} {atom[1][1]: ,.14E} {atom[1][2]: ,.14E}", + file=f, + ) print("</Nuclear Cartesian Energy Gradients>", file=f) # nuclear virial of energy-gradient-based forces on nuclei (optional) @@ -487,14 +517,14 @@ def _write_xml_single(tag: str, info: [str, int], file: TextIO) -> None: """Write header, tail and the data between them into the file.""" print(tag, file=file) print(info, file=file) - print('</' + tag.lstrip('<'), file=file) + print("</" + tag.lstrip("<"), file=file) def _write_xml_single_scientific(tag: str, info: float, file: TextIO) -> None: """Write header, tail and the data between them into the file.""" print(tag, file=file) - print('{: ,.14E}'.format(info), file=file) - print('</' + tag.lstrip('<'), file=file) + print(f"{info: ,.14E}", file=file) + print("</" + tag.lstrip("<"), file=file) def _write_xml_iterator(tag: str, info: Iterator, file: TextIO) -> None: @@ -502,12 +532,12 @@ def _write_xml_iterator(tag: str, info: Iterator, file: TextIO) -> None: print(tag, file=file) for info_line in info: print(info_line, file=file) - print('</' + tag.lstrip('<'), file=file) + print("</" + tag.lstrip("<"), file=file) def _write_xml_iterator_scientific(tag: str, info: Iterator, file: TextIO) -> None: """Write list of arrays to file.""" print(tag, file=file) for info_line in info: - print('{: ,.14E}'.format(info_line), file=file) - print('</' + tag.lstrip('<'), file=file) + print(f"{info_line: ,.14E}", file=file) + print("</" + tag.lstrip("<"), file=file) diff --git a/iodata/formats/xyz.py b/iodata/formats/xyz.py index 0d1edca51..b67a681b2 100644 --- a/iodata/formats/xyz.py +++ b/iodata/formats/xyz.py @@ -53,30 +53,44 @@ """ -from typing import TextIO, Iterator +from collections.abc import Iterator +from typing import TextIO import numpy as np -from ..docstrings import (document_load_one, document_load_many, document_dump_one, - document_dump_many) +from ..docstrings import ( + document_dump_many, + document_dump_one, + document_load_many, + document_load_one, +) from ..iodata import IOData -from ..periodic import sym2num, num2sym -from ..utils import angstrom, LineIterator - +from ..periodic import num2sym, sym2num +from ..utils import LineIterator, angstrom __all__ = [] -PATTERNS = ['*.xyz'] +PATTERNS = ["*.xyz"] DEFAULT_ATOM_COLUMNS = [ - ("atnums", None, (), int, - (lambda word: int(word) if word.isdigit() else sym2num[word.title()]), - (lambda atnum: "{:2s}".format(num2sym[atnum]))), - ("atcoords", None, (3,), float, - (lambda word: float(word) * angstrom), - (lambda value: "{:15.10f}".format(value / angstrom))) + ( + "atnums", + None, + (), + int, + (lambda word: int(word) if word.isdigit() else sym2num[word.title()]), + (lambda atnum: f"{num2sym[atnum]:2s}"), + ), + ( + "atcoords", + None, + (3,), + float, + (lambda word: float(word) * angstrom), + (lambda value: f"{value / angstrom:15.10f}"), + ), ] @@ -90,8 +104,7 @@ """ -@document_load_one("XYZ", ['atcoords', 'atnums', 'title'], - [], {"atom_columns": ATOM_COLUMNS_DOC}) +@document_load_one("XYZ", ["atcoords", "atnums", "title"], [], {"atom_columns": ATOM_COLUMNS_DOC}) def load_one(lit: LineIterator, atom_columns=None) -> dict: """Do not edit this docstring. It will be overwritten.""" # Load the header. @@ -99,10 +112,10 @@ def load_one(lit: LineIterator, atom_columns=None) -> dict: title = next(lit).strip() if atom_columns is None: atom_columns = DEFAULT_ATOM_COLUMNS - data = {'title': title} + data = {"title": title} # Initialize the arrays to be loaded from the XYZ file. for attrname, keyname, shapesuffix, dtype, _loadword, _dumpword in atom_columns: - array = np.zeros((natom,) + shapesuffix, dtype=dtype) + array = np.zeros((natom, *shapesuffix), dtype=dtype) if keyname is None: # Store the initial array as a normal attribute. data[attrname] = array @@ -117,10 +130,10 @@ def load_one(lit: LineIterator, atom_columns=None) -> dict: # must be stored. if keyname is None: # The array is a normal attribute. - atom_array = data[attrname][iatom: iatom + 1] + atom_array = data[attrname][iatom : iatom + 1] else: # The array is a value of a dictionary attribute. - atom_array = data[attrname][keyname][iatom: iatom + 1] + atom_array = data[attrname][keyname][iatom : iatom + 1] # Fill in array elements with atomic properties. For each new value # to be loaded, the first element of the list words is consumed and # converted to the right format for IOData. @@ -129,33 +142,31 @@ def load_one(lit: LineIterator, atom_columns=None) -> dict: return data -@document_load_many("XYZ", ['atcoords', 'atnums', 'title'], - [], {"atom_columns": ATOM_COLUMNS_DOC}) +@document_load_many("XYZ", ["atcoords", "atnums", "title"], [], {"atom_columns": ATOM_COLUMNS_DOC}) def load_many(lit: LineIterator, atom_columns=None) -> Iterator[dict]: """Do not edit this docstring. It will be overwritten.""" # XYZ Trajectory files are a simple concatenation of individual XYZ files,' # making it trivial to load many frames. - while True: - try: + try: + while True: # Check for and skip empty lines at the end of file line = next(lit) if line.strip() == "": return lit.back(line) yield load_one(lit, atom_columns) - except StopIteration: - return + except StopIteration: + return -@document_dump_one("XYZ", ['atcoords', 'atnums'], ['title'], - {"atom_columns": ATOM_COLUMNS_DOC}) +@document_dump_one("XYZ", ["atcoords", "atnums"], ["title"], {"atom_columns": ATOM_COLUMNS_DOC}) def dump_one(f: TextIO, data: IOData, atom_columns=None): """Do not edit this docstring. It will be overwritten.""" if atom_columns is None: atom_columns = DEFAULT_ATOM_COLUMNS # Write the header print(data.natom, file=f) - print(data.title or 'Created with IOData', file=f) + print(data.title or "Created with IOData", file=f) # Write the atom lines for iatom in range(data.natom): words = [] @@ -164,13 +175,11 @@ def dump_one(f: TextIO, data: IOData, atom_columns=None): if keyname is not None: # The data to be written is a value of a dictionary attribute. values = values[keyname] - for value in values[iatom].flat: - words.append(dumpword(value)) + words.extend(dumpword(value) for value in values[iatom].flat) print(" ".join(words), file=f) -@document_dump_many("XYZ", ['atcoords', 'atnums'], ['title'], - {"atom_columns": ATOM_COLUMNS_DOC}) +@document_dump_many("XYZ", ["atcoords", "atnums"], ["title"], {"atom_columns": ATOM_COLUMNS_DOC}) def dump_many(f: TextIO, datas: Iterator[IOData], atom_columns=None): """Do not edit this docstring. It will be overwritten.""" # Similar to load_many, this is relatively easy. diff --git a/iodata/inputs/common.py b/iodata/inputs/common.py index e1fb7baea..63a11e22a 100644 --- a/iodata/inputs/common.py +++ b/iodata/inputs/common.py @@ -18,31 +18,30 @@ # -- """Utilities for writing input files.""" -from typing import TextIO, Callable +from typing import Callable, TextIO -import attr +import attrs import numpy as np from ..iodata import IOData -__all__ = ['write_input_base'] +__all__ = ["write_input_base"] -def write_input_base(f: TextIO, data: IOData, template: str, atom_line: Callable, - user_fields: dict): +def write_input_base( + fh: TextIO, data: IOData, template: str, atom_line: Callable, user_fields: dict +): """Generate a dictionary with fields to replace in the template.""" - # Load IOData dict using attr.asdict because the IOData class uses __slots__. - fields = attr.asdict(data, recurse=False) + # Convert IOData instance to dict for formatting. + fields = attrs.asdict(data, recurse=False) # Set general defaults. - fields["title"] = data.title if data.title is not None else 'Input Generated by IOData' + fields["title"] = data.title if data.title is not None else "Input Generated by IOData" # Convert spin polarization to multiplicity. fields["spinmult"] = int(abs(np.round(data.spinpol))) + 1 if data.spinpol is not None else 1 fields["charge"] = int(data.charge) if data.charge is not None else 0 # User- or format-specific fields have priority. fields.update(user_fields) # Generate geometry. - geometry = [] - for iatom in range(data.natom): - geometry.append(atom_line(data, iatom)) + geometry = [atom_line(data, iatom) for iatom in range(data.natom)] fields["geometry"] = "\n".join(geometry) - print(template.format(**fields), file=f) + print(template.format(**fields), file=fh) diff --git a/iodata/inputs/gaussian.py b/iodata/inputs/gaussian.py index 9b37b6f99..ed6006b57 100644 --- a/iodata/inputs/gaussian.py +++ b/iodata/inputs/gaussian.py @@ -18,16 +18,13 @@ # -- """Gaussian Input Module.""" - -from typing import TextIO, Callable - -from .common import write_input_base +from typing import Callable, Optional, TextIO from ..docstrings import document_write_input from ..iodata import IOData from ..periodic import num2sym from ..utils import angstrom - +from .common import write_input_base __all__ = [] @@ -50,10 +47,18 @@ def default_atom_line(data: IOData, iatom: int): return f"{symbol:3s} {atcoord[0]:10.6f} {atcoord[1]:10.6f} {atcoord[2]:10.6f}" -@document_write_input("GAUSSIAN", ['atnums', 'atcoords'], - ['title', 'run_type', 'lot', 'obasis_name', 'spinmult', 'charge']) -def write_input(f: TextIO, data: IOData, template: str = None, - atom_line: Callable = None, **kwargs): +@document_write_input( + "GAUSSIAN", + ["atnums", "atcoords"], + ["title", "run_type", "lot", "obasis_name", "spinmult", "charge"], +) +def write_input( + fh: TextIO, + data: IOData, + template: Optional[str] = None, + atom_line: Optional[Callable] = None, + **kwargs, +): """Do not edit this docstring. It will be overwritten.""" # Fill in some Gaussian-specific defaults and field names. if template is None: @@ -61,14 +66,17 @@ def write_input(f: TextIO, data: IOData, template: str = None, if atom_line is None: atom_line = default_atom_line gaussian_keywords = { - "energy": "sp", "energy_force": "force", "opt": "opt", "scan": "scan", - "freq": "freq" + "energy": "sp", + "energy_force": "force", + "opt": "opt", + "scan": "scan", + "freq": "freq", } fields = { - "lot": data.lot or 'hf', - "obasis_name": data.obasis_name or 'sto-3g', + "lot": data.lot or "hf", + "obasis_name": data.obasis_name or "sto-3g", "run_type": gaussian_keywords[(data.run_type or "energy").lower()], } # User-specifield fields have priority, may overwrite default ones. fields.update(kwargs) - write_input_base(f, data, template, atom_line, fields) + write_input_base(fh, data, template, atom_line, fields) diff --git a/iodata/inputs/orca.py b/iodata/inputs/orca.py index d5b93da09..d2a34e2d1 100644 --- a/iodata/inputs/orca.py +++ b/iodata/inputs/orca.py @@ -18,15 +18,13 @@ # -- """Orca Input Module.""" - -from typing import TextIO, Callable - -from .common import write_input_base +from typing import Callable, Optional, TextIO from ..docstrings import document_write_input from ..iodata import IOData from ..periodic import num2sym from ..utils import angstrom +from .common import write_input_base __all__ = [] @@ -46,10 +44,18 @@ def default_atom_line(data: IOData, iatom: int): return f"{symbol:3s} {atcoord[0]:10.6f} {atcoord[1]:10.6f} {atcoord[2]:10.6f}" -@document_write_input("ORCA", ['atnums', 'atcoords'], - ['title', 'run_type', 'lot', 'obasis_name', 'spinmult', 'charge']) -def write_input(f: TextIO, data: IOData, template: str = None, - atom_line: Callable = None, **kwargs): +@document_write_input( + "ORCA", + ["atnums", "atcoords"], + ["title", "run_type", "lot", "obasis_name", "spinmult", "charge"], +) +def write_input( + fh: TextIO, + data: IOData, + template: Optional[str] = None, + atom_line: Optional[Callable] = None, + **kwargs, +): """Do not edit this docstring. It will be overwritten.""" # Fill in some ORCA-specific defaults and field names. if template is None: @@ -59,10 +65,10 @@ def write_input(f: TextIO, data: IOData, template: str = None, orca_keywords = {"energy": "Energy", "freq": "Freq", "opt": "Opt"} # Set format-specific defaults. fields = { - "lot": data.lot or 'HF', - "obasis_name": data.obasis_name or 'STO-3G', + "lot": data.lot or "HF", + "obasis_name": data.obasis_name or "STO-3G", "run_type": orca_keywords[(data.run_type or "energy").lower()], } # User-specifield fields have priority, may overwrite default ones. fields.update(kwargs) - write_input_base(f, data, template, atom_line, fields) + write_input_base(fh, data, template, atom_line, fields) diff --git a/iodata/iodata.py b/iodata/iodata.py index dcc4a896e..4a89d03c6 100644 --- a/iodata/iodata.py +++ b/iodata/iodata.py @@ -18,22 +18,21 @@ # -- """Module for handling input/output from different file formats.""" +from typing import Optional -import attr +import attrs import numpy as np +from numpy.typing import NDArray from .attrutils import convert_array_to, validate_shape from .basis import MolecularBasis from .orbitals import MolecularOrbitals from .utils import Cube +__all__ = ["IOData"] -__all__ = ['IOData'] - -# pylint: disable=too-many-instance-attributes -@attr.s(auto_attribs=True, slots=True, - on_setattr=[attr.setters.validate, attr.setters.convert]) +@attrs.define class IOData: """A container class for data loaded from (or to be written to) a file. @@ -129,20 +128,25 @@ class IOData: www.basissetexchange.org. one_ints Dictionary where keys are names and values are numpy arrays with - one-body operators, typically integrals of a one-body operator - with a pair of (Gaussian) basis functions. Names can start with ``olp`` + one-body operators, typically integrals of a one-body operator with + a pair of (Gaussian) basis functions. Names can start with ``olp`` (overlap), ``kin`` (kinetic energy), ``na`` (nuclear attraction), - ``core`` (core hamiltonian), etc. When relevant, these names must have a - suffix ``_ao`` or ``_mo`` to clarify in which basis the integrals are - computed. ``_ao`` is used to denote integrals in a non-orthogonal - (atomic orbital) basis. ``_mo`` is used to denote an orthogonal - (molecular orbital) basis. For the overlap integrals, this suffix can be - omitted because it is only useful to compute them in the atomic-orbital - basis. + ``core`` (core hamiltonian), etc., or ``one`` (general one-electron + integral). When relevant, these names must have a suffix ``_ao`` or + ``_mo`` to clarify in which basis the integrals are computed. ``_ao`` + is used to denote integrals in a non-orthogonal (atomic orbital) basis. + ``_mo`` is used to denote an orthogonal (molecular orbital) basis. For + the overlap integrals, this suffix can be omitted because it is only + useful to compute them in the atomic-orbital basis. one_rdms Dictionary where keys are names and values are one-particle density matrices. Names can be ``scf``, ``post_scf``, ``scf_spin``, - ``post_scf_spin``. These matrices are always expressed in the AO basis. + ``post_scf_spin``. When relevant, these names must have a suffix + ``_ao`` or ``_mo`` to clarify in which basis the RDMs are computed. + ``_ao`` is used to denote a non-orthogonal (atomic orbital) basis. + ``_mo`` is used to denote an orthogonal (molecular orbital) basis. + For the SCF RDMs, this suffix can be omitted because it is only useful + to compute them in the atomic-orbital basis. run_type The type of calculation that lead to the results stored in IOData, which must be one of the following: 'energy', 'energy_force', 'opt', 'scan', @@ -156,72 +160,93 @@ class IOData: A suitable name for the data. two_ints Dictionary where keys are names and values are numpy arrays with - two-body operators, typically integrals of two-body operator - with four of (Gaussian) basis functions. Names can start with ``er`` - (electron repulsion) or ``two`` (general pairswise interaction). When - relevant, these names must have a suffix ``_ao`` or ``_mo`` to clarify - in which basis the integrals are computed, see one_ints for more - details. Array indexes are in physicist's notation. + two-body operators, typically integrals of two-body operator with four + of (Gaussian) basis functions. Names can start with ``er`` (electron + repulsion) or ``two`` (general pairswise interaction). When relevant, + these names must have a suffix ``_ao`` or ``_mo`` to clarify in which + basis the integrals are computed. See ``one_ints`` for more details. + Array indexes are in physicist's notation. two_rdms Dictionary where keys are names and values are two-particle density - matrices. Names can be ``post_scf`` or ``post_scf_spin``. These matrices - are always expressed in the AO basis. Array indexes are in physicist's - notation. + matrices. Names can be ``post_scf`` or ``post_scf_spin``. When relevant, + these names must have a suffix ``_ao`` or ``_mo`` to clarify in which + basis the RDMs are computed. See ``one_rdms`` for more details. + Array indexes are in physicist's notation. """ - atcharges: dict = {} - atcoords: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape('natom', 3))) - _atcorenums: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape('natom'))) - atffparams: dict = {} - atfrozen: np.ndarray = attr.ib( - default=None, converter=convert_array_to(bool), - validator=attr.validators.optional(validate_shape('natom'))) - atgradient: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape('natom', 3))) - athessian: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape(None, None))) - atmasses: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape('natom'))) - atnums: np.ndarray = attr.ib( - default=None, converter=convert_array_to(int), - validator=attr.validators.optional(validate_shape('natom'))) - basisdef: str = None - bonds: np.ndarray = attr.ib( - default=None, converter=convert_array_to(int), - validator=attr.validators.optional(validate_shape(None, 3))) - cellvecs: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape(None, 3))) - _charge: float = None - core_energy: float = None - cube: Cube = None - energy: float = None - extcharges: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape(None, 4))) - extra: dict = {} - g_rot: float = None - lot: str = None - mo: MolecularOrbitals = None - moments: dict = {} - _nelec: float = None - obasis: MolecularBasis = None - obasis_name: str = None - one_ints: dict = {} - one_rdms: dict = {} - run_type: str = None - _spinpol: float = None - title: str = None - two_ints: dict = {} - two_rdms: dict = {} + atcharges: dict = attrs.field(factory=dict) + atcoords: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape("natom", 3)), + ) + _atcorenums: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape("natom")), + ) + atffparams: dict = attrs.field(factory=dict) + atfrozen: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(bool), + validator=attrs.validators.optional(validate_shape("natom")), + ) + atgradient: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape("natom", 3)), + ) + athessian: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape(None, None)), + ) + atmasses: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape("natom")), + ) + atnums: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(int), + validator=attrs.validators.optional(validate_shape("natom")), + ) + basisdef: Optional[str] = attrs.field(default=None) + bonds: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(int), + validator=attrs.validators.optional(validate_shape(None, 3)), + ) + cellvecs: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape(None, 3)), + ) + _charge: Optional[float] = attrs.field(default=None) + core_energy: Optional[float] = attrs.field(default=None) + cube: Optional[Cube] = attrs.field(default=None) + energy: Optional[float] = attrs.field(default=None) + extcharges: NDArray = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape(None, 4)), + ) + extra: dict = attrs.field(factory=dict) + g_rot: Optional[float] = attrs.field(default=None) + lot: Optional[str] = attrs.field(default=None) + mo: Optional[MolecularOrbitals] = attrs.field(default=None) + moments: dict = attrs.field(factory=dict) + _nelec: Optional[float] = attrs.field(default=None) + obasis: Optional[MolecularBasis] = attrs.field(default=None) + obasis_name: Optional[str] = attrs.field(default=None) + one_ints: dict = attrs.field(factory=dict) + one_rdms: dict = attrs.field(factory=dict) + run_type: Optional[str] = attrs.field(default=None) + _spinpol: Optional[float] = attrs.field(default=None) + title: Optional[str] = attrs.field(default=None) + two_ints: dict = attrs.field(factory=dict) + two_rdms: dict = attrs.field(factory=dict) def __attrs_post_init__(self): # Trigger setter to acchieve consistency in properties @@ -240,13 +265,9 @@ def __attrs_post_init__(self): # Public interfaces to private attributes @property - def atcorenums(self) -> np.ndarray: + def atcorenums(self) -> NDArray: """Return effective core charges.""" if self._atcorenums is None and self.atnums is not None: - # Known bug in pylint. See - # https://stackoverflow.com/questions/47972143/using-attr-with-pylint - # https://github.com/PyCQA/pylint/issues/1694 - # pylint: disable=no-member self.atcorenums = self.atnums.astype(float) return self._atcorenums diff --git a/iodata/orbitals.py b/iodata/orbitals.py index 5082474ae..9a862370c 100644 --- a/iodata/orbitals.py +++ b/iodata/orbitals.py @@ -18,14 +18,15 @@ # -- """Data structure for molecular orbitals.""" +from typing import Optional -import attr +import attrs import numpy as np +from numpy.typing import NDArray from .attrutils import convert_array_to, validate_shape - -__all__ = ['MolecularOrbitals'] +__all__ = ["MolecularOrbitals"] def validate_norbab(mo, attribute, value): @@ -44,19 +45,20 @@ def validate_norbab(mo, attribute, value): if mo.kind == "generalized": if value is not None: raise ValueError( - f"Attribute {attribute.name} must be None in case of generalized orbitals.") + f"Attribute {attribute.name} must be None in case of generalized orbitals." + ) return if value is None: raise ValueError( - f"Attribute {attribute.name} cannot be None in case of (un)restricted orbitals.") + f"Attribute {attribute.name} cannot be None in case of (un)restricted orbitals." + ) if mo.kind == "restricted": norb_other = mo.norbb if (attribute.name == "norba") else mo.norba if value != norb_other: raise ValueError("In case of restricted orbitals, norba must be equal to norbb.") -@attr.s(auto_attribs=True, slots=True, - on_setattr=[attr.setters.validate, attr.setters.convert]) +@attrs.define class MolecularOrbitals: """Class of Orthonormal Molecular Orbitals. @@ -93,22 +95,29 @@ class MolecularOrbitals: """ - kind: str = attr.ib( - validator=attr.validators.in_(["restricted", "unrestricted", "generalized"])) - norba: int = attr.ib(validator=validate_norbab) - norbb: int = attr.ib(validator=validate_norbab) - occs: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape("norb"))) - coeffs: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape(None, "norb"))) - energies: np.ndarray = attr.ib( - default=None, converter=convert_array_to(float), - validator=attr.validators.optional(validate_shape("norb"))) - irreps: np.ndarray = attr.ib( + kind: str = attrs.field( + validator=attrs.validators.in_(["restricted", "unrestricted", "generalized"]) + ) + norba: int = attrs.field(validator=validate_norbab) + norbb: int = attrs.field(validator=validate_norbab) + occs: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape("norb")), + ) + coeffs: Optional[NDArray] = attrs.field( default=None, - validator=attr.validators.optional(validate_shape("norb"))) + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape(None, "norb")), + ) + energies: Optional[NDArray] = attrs.field( + default=None, + converter=convert_array_to(float), + validator=attrs.validators.optional(validate_shape("norb")), + ) + irreps: Optional[NDArray] = attrs.field( + default=None, validator=attrs.validators.optional(validate_shape("norb")) + ) @property def nelec(self) -> float: @@ -122,12 +131,12 @@ def nbasis(self): """Return the number of spatial basis functions.""" if self.coeffs is None: return None - if self.kind == 'generalized': + if self.kind == "generalized": return self.coeffs.shape[0] // 2 return self.coeffs.shape[0] @property - def norb(self): # pylint: disable=too-many-return-statements + def norb(self): """Return the number of spatially distinct orbitals. Notes @@ -158,7 +167,7 @@ def spinpol(self) -> float: raise NotImplementedError if self.occs is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": nbeta = np.clip(self.occs, 0, 1).sum() return abs(self.nelec - 2 * nbeta) return abs(self.occsa.sum() - self.occsb.sum()) @@ -170,9 +179,9 @@ def occsa(self): raise NotImplementedError if self.occs is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": return np.clip(self.occs, 0, 1) - return self.occs[:self.norba] + return self.occs[: self.norba] @property def occsb(self): @@ -181,9 +190,9 @@ def occsb(self): raise NotImplementedError if self.occs is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": return self.occs - np.clip(self.occs, 0, 1) - return self.occs[self.norba:] + return self.occs[self.norba :] @property def coeffsa(self): @@ -192,9 +201,9 @@ def coeffsa(self): raise NotImplementedError if self.coeffs is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": return self.coeffs - return self.coeffs[:, :self.norba] + return self.coeffs[:, : self.norba] @property def coeffsb(self): @@ -203,9 +212,9 @@ def coeffsb(self): raise NotImplementedError if self.coeffs is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": return self.coeffs - return self.coeffs[:, self.norba:] + return self.coeffs[:, self.norba :] @property def energiesa(self): @@ -214,9 +223,9 @@ def energiesa(self): raise NotImplementedError if self.energies is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": return self.energies - return self.energies[:self.norba] + return self.energies[: self.norba] @property def energiesb(self): @@ -225,9 +234,9 @@ def energiesb(self): raise NotImplementedError if self.energies is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": return self.energies - return self.energies[self.norba:] + return self.energies[self.norba :] @property def irrepsa(self): @@ -236,9 +245,9 @@ def irrepsa(self): raise NotImplementedError if self.irreps is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": return self.irreps - return self.irreps[:self.norba] + return self.irreps[: self.norba] @property def irrepsb(self): @@ -247,6 +256,6 @@ def irrepsb(self): raise NotImplementedError if self.irreps is None: return None - if self.kind == 'restricted': + if self.kind == "restricted": return self.irreps - return self.irreps[self.norba:] + return self.irreps[self.norba :] diff --git a/iodata/overlap.py b/iodata/overlap.py index dd7e4b5bd..cca526334 100644 --- a/iodata/overlap.py +++ b/iodata/overlap.py @@ -18,57 +18,132 @@ # -- """Module for computing overlap of atomic orbital basis functions.""" -import attr +from typing import Optional, Union + +import attrs import numpy as np -from scipy.special import binom, factorial2 +import scipy.special +from numpy.typing import NDArray -from .overlap_cartpure import tfs -from .basis import convert_conventions, iter_cart_alphabet, MolecularBasis from .basis import HORTON2_CONVENTIONS as OVERLAP_CONVENTIONS +from .basis import MolecularBasis, Shell, convert_conventions, iter_cart_alphabet +from .overlap_cartpure import tfs + +__all__ = ["OVERLAP_CONVENTIONS", "compute_overlap", "gob_cart_normalization"] + + +def factorial2(n: Union[int, NDArray[int]]) -> Union[int, NDArray[int]]: + """Modifcied scipy.special.factorial2 that returns 1 when the input is -1. -__all__ = ['OVERLAP_CONVENTIONS', 'compute_overlap', 'gob_cart_normalization'] + This is a temporary workaround while we wait for Scipy's update. + To learn more, see https://github.com/scipy/scipy/issues/18409. + This function only supports integer (array) arguments, + because this is the only relevant use case in IOData. -# pylint: disable=too-many-nested-blocks,too-many-statements -def compute_overlap(obasis: MolecularBasis, atcoords: np.ndarray) -> np.ndarray: - r"""Compute overlap matrix for the given molecular basis set. + Parameters + ---------- + n + Values to calculate n!! for. If n==-1, the return value is 1. + For n < -1, the return value is 0. + + """ + # Handle integer inputs + if isinstance(n, (int, np.integer)): + return 1 if n == -1 else scipy.special.factorial2(n, exact=True) + + # Handle integer array inputs + if isinstance(n, np.ndarray): + if issubclass(n.dtype.type, (int, np.integer)): + result = scipy.special.factorial2(n, exact=True) + result[n == -1] = 1 + return result + raise TypeError(f"Unsupported dtype of array n: {n.dtype}") + + raise TypeError(f"Unsupported type of argument n: {type(n)}") + + +def compute_overlap( + obasis0: MolecularBasis, + atcoords0: NDArray, + obasis1: Optional[MolecularBasis] = None, + atcoords1: Optional[NDArray] = None, +) -> NDArray: + r"""Compute overlap matrix for the given molecular basis set(s). .. math:: \braket{\psi_{i}}{\psi_{j}} + When only one basis set is given, the overlap matrix of that basis (with + itself) is computed. If a second basis set (with its atomic coordinates) is + provided, the overlap between the two basis sets is computed. + This function takes into account the requested order of the basis functions - in ``obasis.conventions``. Note that only L2 normalized primitives are - supported at the moment. + in ``obasis0.conventions`` (and ``obasis1.conventions``). Note that only L2 + normalized primitives are supported at the moment. Parameters ---------- - obasis + obasis0 The orbital basis set. - atcoords + atcoords0 The atomic Cartesian coordinates (including those of ghost atoms). + obasis1 + An optional second orbital basis set. + atcoords1 + An optional second array with atomic Cartesian coordinates + (including those of ghost atoms). Returns ------- overlap - The matrix with overlap integrals, shape=(obasis.nbasis, obasis.nbasis). + The matrix with overlap integrals, ``shape=(obasis0.nbasis, obasis1.nbasis)``. """ - if obasis.primitive_normalization != 'L2': - raise ValueError('The overlap integrals are only implemented for L2 ' - 'normalization.') - - # Initialize result - overlap = np.zeros((obasis.nbasis, obasis.nbasis)) + if obasis0.primitive_normalization != "L2": + raise ValueError("The overlap integrals are only implemented for L2 normalization.") # Get a segmented basis, for simplicity - obasis = obasis.get_segmented() + obasis0 = obasis0.get_segmented() + + # Handle optional arguments + if obasis1 is None: + if atcoords1 is not None: + raise TypeError( + "When no second basis is given, no second second " + "array of atomic coordinates is expected." + ) + obasis1 = obasis0 + atcoords1 = atcoords0 + identical = True + else: + if obasis1.primitive_normalization != "L2": + raise ValueError("The overlap integrals are only implemented for L2 normalization.") + if atcoords1 is None: + raise TypeError( + "When a second basis is given, a second second " + "array of atomic coordinates is expected." + ) + # Get a segmented basis, for simplicity + obasis1 = obasis1.get_segmented() + identical = False + + # Initialize result + overlap = np.zeros((obasis0.nbasis, obasis1.nbasis)) # Compute the normalization constants of the Cartesian primitives, with the # contraction coefficients multiplied in. - scales = [_compute_cart_shell_normalizations(shell) * shell.coeffs - for shell in obasis.shells] - - n_max = np.max([np.max(shell.angmoms) for shell in obasis.shells]) + scales0 = [_compute_cart_shell_normalizations(shell) * shell.coeffs for shell in obasis0.shells] + if identical: + scales1 = scales0 + else: + scales1 = [ + _compute_cart_shell_normalizations(shell) * shell.coeffs for shell in obasis1.shells + ] + + n_max = max(np.max(shell.angmoms) for shell in obasis0.shells) + if not identical: + n_max = max(n_max, *(np.max(shell.angmoms) for shell in obasis1.shells)) go = GaussianOverlap(n_max) # define a python ufunc (numpy function) for broadcasted calling over angular momentums @@ -77,22 +152,22 @@ def compute_overlap(obasis: MolecularBasis, atcoords: np.ndarray) -> np.ndarray: # Loop over shell0 begin0 = 0 - # pylint: disable=too-many-nested-blocks - for i0, shell0 in enumerate(obasis.shells): - r0 = atcoords[shell0.icenter] + for i0, shell0 in enumerate(obasis0.shells): + r0 = atcoords0[shell0.icenter] end0 = begin0 + shell0.nbasis # Loop over shell1 (lower triangular only, including diagonal) begin1 = 0 - for i1, shell1 in enumerate(obasis.shells[:i0 + 1]): - r1 = atcoords[shell1.icenter] + nshell1 = i0 + 1 if identical else len(obasis1.shells) + for i1, shell1 in enumerate(obasis1.shells[:nshell1]): + r1 = atcoords1[shell1.icenter] end1 = begin1 + shell1.nbasis # prepare some constants to save FLOPS later on rij = r0 - r1 rij_norm_sq = np.dot(rij, rij) - # Check if the result is going to signifcant + # Check if the result is going to significant. a0_min = np.min(shell0.exponents) a1_min = np.min(shell1.exponents) prefactor_max = np.exp(-a0_min * a1_min * rij_norm_sq / (a0_min + a1_min)) @@ -102,14 +177,14 @@ def compute_overlap(obasis: MolecularBasis, atcoords: np.ndarray) -> np.ndarray: # arrays of angular momentums [[2, 0, 0], [0, 2, 0], ..., [0, 1, 1]] n0 = np.array(list(iter_cart_alphabet(shell0.angmoms[0]))) n1 = np.array(list(iter_cart_alphabet(shell1.angmoms[0]))) - result = np.zeros((n0.shape[0], n1.shape[0])) + shell_overlap = np.zeros((n0.shape[0], n1.shape[0])) # Loop over primitives in shell0 (Cartesian) - for scales0, a0 in zip(scales[i0], shell0.exponents): + for shell_scales0, a0 in zip(scales0[i0], shell0.exponents): a0_r0 = a0 * r0 # Loop over primitives in shell1 (Cartesian) - for scales1, a1 in zip(scales[i1], shell1.exponents): + for shell_scales1, a1 in zip(scales1[i1], shell1.exponents): at = a0 + a1 prefactor = np.exp(-a0 * a1 / at * rij_norm_sq) if prefactor < 1e-15: @@ -127,28 +202,35 @@ def compute_overlap(obasis: MolecularBasis, atcoords: np.ndarray) -> np.ndarray: # array operations. vs = compute_overlap_1d(rn_0, rn_1, n0[:, None, :], n1[None, :, :], two_at) v = np.prod(vs.astype(float), axis=2) - result += v * prefactor * scales0[:, None] * scales1[None, :] + v *= prefactor + v *= shell_scales0[:, None] + v *= shell_scales1[None, :] + shell_overlap += v # END of Cartesian coordinate system (if going to pure coordinates) # cart to pure - if shell0.kinds[0] == 'p': - result = np.dot(tfs[shell0.angmoms[0]], result) - if shell1.kinds[0] == 'p': - result = np.dot(result, tfs[shell1.angmoms[0]].T) + if shell0.kinds[0] == "p": + shell_overlap = np.dot(tfs[shell0.angmoms[0]], shell_overlap) + if shell1.kinds[0] == "p": + shell_overlap = np.dot(shell_overlap, tfs[shell1.angmoms[0]].T) # store lower triangular result - overlap[begin0:end0, begin1:end1] = result - # store upper triangular result - overlap[begin1:end1, begin0:end0] = result.T + overlap[begin0:end0, begin1:end1] = shell_overlap + if identical: + # store upper triangular result + overlap[begin1:end1, begin0:end0] = shell_overlap.T begin1 = end1 begin0 = end0 - permutation, signs = convert_conventions(obasis, OVERLAP_CONVENTIONS, reverse=True) - overlap = overlap[permutation] * signs.reshape(-1, 1) - overlap = overlap[:, permutation] * signs - return overlap + permutation0, signs0 = convert_conventions(obasis0, OVERLAP_CONVENTIONS, reverse=True) + overlap = overlap[permutation0] * signs0.reshape(-1, 1) + if identical: + permutation1, signs1 = permutation0, signs0 + else: + permutation1, signs1 = convert_conventions(obasis1, OVERLAP_CONVENTIONS, reverse=True) + return overlap[:, permutation1] * signs1 class GaussianOverlap: @@ -163,8 +245,10 @@ def __init__(self, n_max): Maximum angular momentum. """ - self.binomials = [[binom(n, i) for i in range(n + 1)] for n in range(n_max + 1)] - facts = [factorial2(m, 2) for m in range(2 * n_max)] + self.binomials = [ + [scipy.special.binom(n, i) for i in range(n + 1)] for n in range(n_max + 1) + ] + facts = [factorial2(m) for m in range(2 * n_max)] facts.insert(0, 1) self.facts = np.array(facts) @@ -176,12 +260,12 @@ def compute_overlap_gaussian_1d(self, x1, x2, n1, n2, two_at): pf_i = self.binomials[n1][i] * x1 ** (n1 - i) for j in range(i % 2, n2 + 1, 2): m = i + j - integ = self.facts[m] / two_at ** (m / 2) + integ = self.facts[m] / two_at ** (m / 2) # TODO // 2 value += pf_i * self.binomials[n2][j] * x2 ** (n2 - j) * integ return value -def _compute_cart_shell_normalizations(shell: 'Shell') -> np.ndarray: +def _compute_cart_shell_normalizations(shell: Shell) -> NDArray: """Return normalization constants for the primitives in a given shell. Parameters @@ -191,23 +275,21 @@ def _compute_cart_shell_normalizations(shell: 'Shell') -> np.ndarray: Returns ------- - np.ndarray + NDArray The normalization constants, always for Cartesian functions, even when shell is pure. """ - shell = attr.evolve(shell, kinds=['c'] * shell.ncon) + shell = attrs.evolve(shell, kinds=["c"] * shell.ncon) result = [] for angmom in shell.angmoms: for exponent in shell.exponents: - row = [] - for n in iter_cart_alphabet(angmom): - row.append(gob_cart_normalization(exponent, n)) + row = [gob_cart_normalization(exponent, n) for n in iter_cart_alphabet(angmom)] result.append(row) return np.array(result) -def gob_cart_normalization(alpha: np.ndarray, n: np.ndarray) -> np.ndarray: +def gob_cart_normalization(alpha: NDArray, n: NDArray) -> NDArray: """Compute normalization of exponent. Parameters @@ -219,9 +301,10 @@ def gob_cart_normalization(alpha: np.ndarray, n: np.ndarray) -> np.ndarray: Returns ------- - np.ndarray + NDArray The normalization constant for the gaussian cartesian basis. """ - return np.sqrt((4 * alpha) ** sum(n) * (2 * alpha / np.pi) ** 1.5 - / np.prod(factorial2(2 * n - 1))) + return np.sqrt( + (4 * alpha) ** sum(n) * (2 * alpha / np.pi) ** 1.5 / np.prod(factorial2(2 * n - 1)) + ) diff --git a/iodata/overlap_cartpure.py b/iodata/overlap_cartpure.py index e1317061c..fbbc6e24f 100644 --- a/iodata/overlap_cartpure.py +++ b/iodata/overlap_cartpure.py @@ -30,6 +30,7 @@ __all__ = ["tfs"] +# fmt: off tf0 = np.array([ [1.0], ]) @@ -203,5 +204,6 @@ 0, 1.96875, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.64725984928774935, 0, 0, 0, 0, 0, 0, 0], ]) +# fmt: on tfs = [tf0, tf1, tf2, tf3, tf4, tf5, tf6, tf7] diff --git a/iodata/periodic.py b/iodata/periodic.py index 5198a28dc..defe93aa9 100644 --- a/iodata/periodic.py +++ b/iodata/periodic.py @@ -18,14 +18,10 @@ # -- """Periodic table module.""" +__all__ = ["num2sym", "sym2num"] -from typing import Dict - -__all__ = ['num2sym', 'sym2num'] - - -num2sym: Dict[int, str] = { +num2sym: dict[int, str] = { 1: "H", 2: "He", 3: "Li", @@ -146,21 +142,28 @@ 118: "Og", } -sym2num: Dict[str, int] = dict((value, key) for key, value in num2sym.items()) +sym2num: dict[str, int] = {value: key for key, value in num2sym.items()} # Labels used for bond types. num2bond = { - 1: "1", - 2: "2", - 3: "3", - # The following symbols are used in the MOL2 format: - 4: "am", - 5: "ar", - 6: "du", - 7: "un", - 8: "nc" + # The following symbols are used in the SDF format. The numering is assumed + # to be consistent with the SDF spec. See + # http://www.nonlinear.com/progenesis/sdf-studio/v0.9/faq/sdf-file-format-guidance.aspx + # https://en.wikipedia.org/wiki/Chemical_table_file + 1: "1", # single + 2: "2", # double + 3: "3", # triple + 4: "ar", # aromatic + 5: "sd", # single or double + 6: "sar", # single or aromatic + 7: "dar", # double or aromatic + 8: "un", # unknown/any + # The following symbols are exclusively used in the MOL2 format. + 9: "am", # amide + 10: "du", # dummy + 11: "nc", # not connected } -bond2num: Dict[str, int] = dict((value, key) for key, value in num2bond.items()) +bond2num: dict[str, int] = {value: key for key, value in num2bond.items()} diff --git a/iodata/test/common.py b/iodata/test/common.py index 661220750..680804954 100644 --- a/iodata/test/common.py +++ b/iodata/test/common.py @@ -20,31 +20,35 @@ import os from contextlib import contextmanager +from typing import Optional import numpy as np -from numpy.testing import assert_equal, assert_allclose import pytest +from numpy.testing import assert_allclose, assert_equal from ..api import load_one -from ..overlap import compute_overlap from ..basis import convert_conventions +from ..overlap import compute_overlap from ..utils import FileFormatWarning try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path - + from importlib.resources import as_file, files -__all__ = ['compute_mulliken_charges', 'compute_1rdm', - 'compare_mols', 'check_orthonormal', 'load_one_warning'] +__all__ = [ + "compute_mulliken_charges", + "compute_1rdm", + "compare_mols", + "check_orthonormal", + "load_one_warning", +] def compute_1rdm(iodata): """Compute 1-RDM.""" coeffs, occs = iodata.mo.coeffs, iodata.mo.occs - dm = np.dot(coeffs * occs, coeffs.T) - return dm + return np.dot(coeffs * occs, coeffs.T) def compute_mulliken_charges(iodata): @@ -59,8 +63,7 @@ def compute_mulliken_charges(iodata): basis_center.extend([shell.icenter] * shell.nbasis) basis_center = np.array(basis_center) # compute atomic populations - populations = np.array([np.sum(bp[basis_center == index]) - for index in range(iodata.natom)]) + populations = np.array([np.sum(bp[basis_center == index]) for index in range(iodata.natom)]) return iodata.atcorenums - np.array(populations) @@ -80,15 +83,14 @@ def truncated_file(fn_orig, nline, nadd, tmpdir): A temporary directory where the truncated file is stored. """ - fn_truncated = '%s/truncated_%i_%s' % ( - tmpdir, nline, os.path.basename(fn_orig)) - with open(fn_orig) as f_orig, open(fn_truncated, 'w') as f_truncated: + fn_truncated = "%s/truncated_%i_%s" % (tmpdir, nline, os.path.basename(fn_orig)) + with open(fn_orig) as f_orig, open(fn_truncated, "w") as f_truncated: for counter, line in enumerate(f_orig): if counter >= nline: break f_truncated.write(line) for _ in range(nadd): - f_truncated.write('\n') + f_truncated.write("\n") yield fn_truncated @@ -129,9 +131,9 @@ def compare_mols(mol1, mol2, atol=1.0e-8, rtol=0.0): assert_equal(mol1.mo.irreps, mol2.mo.irreps) # operators and density matrices cases = [ - ('one_ints', ['olp', 'kin_ao', 'na_ao']), - ('two_ints', ['er_ao']), - ('one_rdms', ['scf', 'scf_spin', 'post_scf', 'post_scf_spin']), + ("one_ints", ["olp", "kin_ao", "na_ao"]), + ("two_ints", ["er_ao"]), + ("one_rdms", ["scf", "scf_spin", "post_scf_ao", "post_scf_spin_ao"]), ] for attrname, keys in cases: d1 = getattr(mol1, attrname) @@ -153,9 +155,9 @@ def check_orthonormal(mo_coeffs, ao_overlap, atol=1e-5): Parameters ---------- - mo_coeffs : np.ndarray, shape=(nbasis, mo_count) + mo_coeffs : NDArray, shape=(nbasis, mo_count) Molecular orbital coefficients. - ao_overlap : np.ndarray, shape=(nbasis, nbasis) + ao_overlap : NDArray, shape=(nbasis, nbasis) Atomic orbital overlap matrix. atol : float Absolute tolerance in deviation from identity matrix. @@ -164,12 +166,13 @@ def check_orthonormal(mo_coeffs, ao_overlap, atol=1e-5): # compute MO overlap & number of MO orbitals mo_overlap = np.dot(mo_coeffs.T, np.dot(ao_overlap, mo_coeffs)) mo_count = mo_coeffs.shape[1] - message = 'Molecular orbitals are not orthonormal!' - assert_allclose(mo_overlap, np.eye(mo_count), - rtol=0., atol=atol, err_msg=message) + message = "Molecular orbitals are not orthonormal!" + assert_allclose(mo_overlap, np.eye(mo_count), rtol=0.0, atol=atol, err_msg=message) -def load_one_warning(filename: str, fmt: str = None, match: str = None, **kwargs): +def load_one_warning( + filename: str, fmt: Optional[str] = None, match: Optional[str] = None, **kwargs +): """Call load_one, catching expected FileFormatWarning. Parameters @@ -191,7 +194,7 @@ def load_one_warning(filename: str, fmt: str = None, match: str = None, **kwargs The instance of IOData with data loaded from the input files. """ - with path('iodata.test.data', filename) as fn: + with as_file(files("iodata.test.data").joinpath(filename)) as fn: if match is None: return load_one(str(fn), fmt, **kwargs) with pytest.warns(FileFormatWarning, match=match): diff --git a/iodata/test/data/H2O_CCSDprTpr_STO3G_output.json b/iodata/test/data/H2O_CCSDprTpr_STO3G_output.json new file mode 100644 index 000000000..e37202048 --- /dev/null +++ b/iodata/test/data/H2O_CCSDprTpr_STO3G_output.json @@ -0,0 +1,70 @@ +{ + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": [ + "O", + "H", + "H" + ], + "geometry": [ + 0.0, + 0.0, + -0.12947694, + 0.0, + -1.49418734, + 1.02744651, + 0.0, + 1.49418734, + 1.02744651 + ], + "molecular_charge": 0.0, + "molecular_multiplicity": 1, + "connectivity": [ + [0, 1, 1], + [0, 2, 1] + ], + "fix_orientation": true, + "fix_symmetry": "c1", + "provenance": { + "creator": "HORTON3", + "routine": "Extracted from molpro_ccsd_prt_pr_water_energy_output.json" + }, + "real": [true, true, true] + }, + "driver": "energy", + "model": { + "method": "CCSD(T)", + "basis": "sto-3g" + }, + "id": "1", + "schema_name": "qcschema_output", + "schema_version": 2.0, + "provenance": [ + { + "creator": "QCElemental", + "version": "v0.2.6", + "routine": "qcelemental.models.results" + }, + { + "creator": "HORTON3", + "routine": "Manual validation" + } + ], + "success": true, + "return_result": -75.0197177197499, + "properties": { + "nuclear_repulsion_energy": 8.80146205625184, + "scf_dipole_moment": [0.0, 0.0, 0.65662910269302], + "scf_total_energy": -74.9646625166939, + "ccsd_same_spin_correlation_energy": -0.0019035720854254133, + "ccsd_opposite_spin_correlation_energy": -0.0530757170419502, + "ccsd_correlation_energy": -0.0549792917918461, + "ccsd_total_energy": -75.0196418084857, + "ccsd_prt_pr_correlation_energy": -0.0550552030560323, + "ccsd_prt_pr_total_energy": -75.0197177197499, + "calcinfo_nalpha": 5, + "calcinfo_nbeta": 5, + "calcinfo_nbasis": 7 + } +} diff --git a/iodata/test/data/H2O_HF_STO3G_Gaussian_input.json b/iodata/test/data/H2O_HF_STO3G_Gaussian_input.json new file mode 100644 index 000000000..f5b00852d --- /dev/null +++ b/iodata/test/data/H2O_HF_STO3G_Gaussian_input.json @@ -0,0 +1,36 @@ +{ + "schema_name": "qcschema_input", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["O", "H", "H"], + "geometry": [0.0, 0.0, -0.1295, 0.0, -1.4942, 1.0274, 0.0, 1.4942, 1.0274], + "molecular_charge": 0, + "molecular_multiplicity": 1, + "real": [true, true, true], + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + } + ] + }, + "driver": "energy", + "model": { + "method": "HF", + "basis": "STO-3G" + }, + "keywords": { + "program": "Gaussian", + "verbose": "normal", + "run_type": "energy" + }, + "provenance": [ + { + "creator": "IOData", + "version": "X.X.X", + "routine": "iodata.dump_one.com" + } + ] +} \ No newline at end of file diff --git a/iodata/test/data/LiCl_STO4G_Gaussian_input.json b/iodata/test/data/LiCl_STO4G_Gaussian_input.json new file mode 100644 index 000000000..60652f20c --- /dev/null +++ b/iodata/test/data/LiCl_STO4G_Gaussian_input.json @@ -0,0 +1,54 @@ +{ + "schema_name": "qcschema_input", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0, + "molecular_multiplicity": 1, + "real": [true, true], + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + }, + { + "creator": "HORTON3", + "routine": "Manual validation" + } + ] + }, + "id": "LiCl_HF_STO4G", + "driver": "energy", + "model": { + "method": "HF", + "basis": "STO-4G" + }, + "keywords": { + "program": "Gaussian", + "verbose": "normal", + "run_type": "freq", + "geom": "AllCheck", + "mem": "7GB", + "nprocshared": "2", + "oldchk": "LiCl_OPT.chk", + "chk": "LiCl_HF_STO4G.chk" + }, + "protocols": { + "wavefunction": "none", + "stdout": false + }, + "extras": { + "lewis_acid_group": "alkali_metals", + "lewis_base_group": "halides" + }, + "provenance": [ + { + "creator": "IOData", + "version": "X.X.X", + "routine": "iodata.dump_one.com" + } + ] +} \ No newline at end of file diff --git a/iodata/test/data/LiCl_STO4G_Gaussian_input_extra.json b/iodata/test/data/LiCl_STO4G_Gaussian_input_extra.json new file mode 100644 index 000000000..6e8ef63f6 --- /dev/null +++ b/iodata/test/data/LiCl_STO4G_Gaussian_input_extra.json @@ -0,0 +1,55 @@ +{ + "schema_name": "qcschema_input", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0, + "molecular_multiplicity": 1, + "real": [true, true], + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + }, + { + "creator": "HORTON3", + "routine": "Manual validation" + } + ] + }, + "id": "LiCl_HF_STO4G", + "driver": "energy", + "model": { + "method": "HF", + "basis": "STO-4G" + }, + "keywords": { + "program": "Gaussian", + "verbose": "normal", + "run_type": "freq", + "geom": "AllCheck", + "mem": "7GB", + "nprocshared": "2", + "oldchk": "LiCl_OPT.chk", + "chk": "LiCl_HF_STO4G.chk" + }, + "protocols": { + "wavefunction": "none", + "stdout": false + }, + "extras": { + "lewis_acid_group": "alkali_metals", + "lewis_base_group": "halides" + }, + "provenance": [ + { + "creator": "IOData", + "version": "X.X.X", + "routine": "iodata.dump_one.com" + } + ], + "another_field": true +} \ No newline at end of file diff --git a/iodata/test/data/LiCl_STO4G_Gaussian_input_extra_molecule.json b/iodata/test/data/LiCl_STO4G_Gaussian_input_extra_molecule.json new file mode 100644 index 000000000..7b1e6ef5d --- /dev/null +++ b/iodata/test/data/LiCl_STO4G_Gaussian_input_extra_molecule.json @@ -0,0 +1,55 @@ +{ + "schema_name": "qcschema_input", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0, + "molecular_multiplicity": 1, + "real": [true, true], + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + }, + { + "creator": "HORTON3", + "routine": "Manual validation" + } + ], + "another_field": true + }, + "id": "LiCl_HF_STO4G", + "driver": "energy", + "model": { + "method": "HF", + "basis": "STO-4G" + }, + "keywords": { + "program": "Gaussian", + "verbose": "normal", + "run_type": "freq", + "geom": "AllCheck", + "mem": "7GB", + "nprocshared": "2", + "oldchk": "LiCl_OPT.chk", + "chk": "LiCl_HF_STO4G.chk" + }, + "protocols": { + "wavefunction": "none", + "stdout": false + }, + "extras": { + "lewis_acid_group": "alkali_metals", + "lewis_base_group": "halides" + }, + "provenance": [ + { + "creator": "IOData", + "version": "X.X.X", + "routine": "iodata.dump_one.com" + } + ] +} \ No newline at end of file diff --git a/iodata/test/data/LiCl_STO4G_Gaussian_input_nested_extra.json b/iodata/test/data/LiCl_STO4G_Gaussian_input_nested_extra.json new file mode 100644 index 000000000..2575cea93 --- /dev/null +++ b/iodata/test/data/LiCl_STO4G_Gaussian_input_nested_extra.json @@ -0,0 +1,62 @@ +{ + "schema_name": "qcschema_input", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0, + "molecular_multiplicity": 1, + "real": [true, true], + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + }, + { + "creator": "HORTON3", + "routine": "Manual validation" + } + ] + }, + "id": "LiCl_HF_STO4G", + "driver": "energy", + "model": { + "method": "HF", + "basis": "STO-4G" + }, + "keywords": { + "program": "Gaussian", + "verbose": "normal", + "run_type": "freq", + "geom": "AllCheck", + "mem": "7GB", + "nprocshared": "2", + "oldchk": "LiCl_OPT.chk", + "chk": "LiCl_HF_STO4G.chk" + }, + "protocols": { + "wavefunction": "none", + "stdout": false + }, + "extras": { + "lewis_acid_group": "alkali_metals", + "lewis_base_group": "halides" + }, + "provenance": [ + { + "creator": "IOData", + "version": "X.X.X", + "routine": "iodata.dump_one.com" + } + ], + "related_projects": { + "HSAB": { + "id": "HSAB_2019_LALB" + }, + "4PB3": { + "id": "4PB3_2020_Group1" + } + } +} \ No newline at end of file diff --git a/iodata/test/data/LiCl_STO4G_Gaussian_output.json b/iodata/test/data/LiCl_STO4G_Gaussian_output.json new file mode 100644 index 000000000..5ad461ade --- /dev/null +++ b/iodata/test/data/LiCl_STO4G_Gaussian_output.json @@ -0,0 +1,90 @@ +{ + "schema_name": "qcschema_output", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0.0, + "molecular_multiplicity": 1, + "real": [true, true], + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + }, + { + "creator": "HORTON3", + "routine": "Manual validation" + } + ] + }, + "id": "LiCl_HF_STO4G", + "driver": "energy", + "return_result": -464.626219879, + "model": { + "method": "HF", + "basis": "STO-4G" + }, + "keywords": { + "program": "Gaussian", + "verbose": "normal", + "run_type": "Freq", + "geom": "AllCheck", + "mem": "7GB", + "nprocshared": "2", + "oldchk": "LiCl_OPT.chk", + "chk": "LiCl_HF_STO4G.chk" + }, + "protocols": { + "wavefunction": "none", + "stdout": false + }, + "properties": { + "calcinfo_nbasis": 14, + "calcinfo_nmo": 14, + "calcinfo_nalpha": 10, + "calcinfo_nbeta": 10, + "nuclear_repulsion_energy": 14.0583267322, + "return_energy": -464.626219879, + "scf_moments": [ + [0.0000, 0.0000, -5.3718], + [-15.0269, 0.0000, 0.0000, 0.0000, -15.0269, 0.0000, 0.0000, 0.0000, -4.4203], + [ + 0.0000, 0.0000, 0.4291, 0.0000, 0.0000, 0.0000, 0.4291, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.4291, 0.0000, 0.4291, 0.0000, + 0.4291, 0.0000, 0.0000, 0.0000, 0.4291, 0.0000, 0.0000, 0.0000, -19.3542 + ], + [ + -18.9062, 0.0000, 0.0000, 0.0000, -6.3021, 0.0000, 0.0000, 0.0000, -11.1046, + 0.0000, -6.3021, 0.0000, -6.3021, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + 0.0000, 0.0000, -11.1046, 0.0000, 0.0000, 0.0000, -11.1046, 0.0000, 0.0000, + 0.0000, -6.3021, 0.0000, -6.3021, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, + -6.3021, 0.0000, 0.0000, 0.0000, -18.9062, 0.0000, 0.0000, 0.0000, -11.1046, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, -11.1046, 0.0000, -11.1046, 0.0000, + 0.0000, 0.0000, -11.1046, 0.0000, 0.0000, 0.0000, -11.1046, 0.0000, 0.0000, + 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, -11.1046, 0.0000, -11.1046, 0.0000, + -11.1046, 0.0000, 0.0000, 0.0000, -11.1046, 0.0000, 0.0000, 0.0000, -9.9113 + ] + ], + "scf_energy": -464.626219879, + "scf_iterations": 9 + }, + "success": true, + "extras": { + "lewis_acid_group": "alkali_metals", + "lewis_base_group": "halides" + }, + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + }, + { + "creator": "IOData", + "version": "X.X.X", + "routine": "iodata.dump_one.json" + } + ] +} \ No newline at end of file diff --git a/iodata/test/data/LiCl_explicit_STO4G_input.json b/iodata/test/data/LiCl_explicit_STO4G_input.json new file mode 100644 index 000000000..61f3a07df --- /dev/null +++ b/iodata/test/data/LiCl_explicit_STO4G_input.json @@ -0,0 +1,165 @@ +{ + "schema_name": "qcschema_input", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0, + "molecular_multiplicity": 1, + "real": [true, true], + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + }, + { + "creator": "HORTON3", + "routine": "Manual validation" + } + ] + }, + "driver": "energy", + "model": { + "method": "HF", + "basis": { + "schema_name": "qcschema_basis", + "schema_version": 2.0, + "name": "STO-4G", + "description": "STO-4G Minimal Basis (4 functions/AO)", + "center_data": { + "li_STO-4G": { + "electron_shells": [ + { + "angular_momentum": [ + 0 + ], + "exponents": [ + 0.3774960873E+02, + 0.6907713307E+01, + 0.1919038397E+01, + 0.6369115922E+00 + ], + "coefficients": [ + [ + 0.5675242080E-01, + 0.2601413550E+00, + 0.5328461143E+00, + 0.2916254405E+00 + ] + ], + "function_type": "gto_cartesian" + }, + { + "angular_momentum": [ + 0, + 1 + ], + "exponents": [ + 0.1487042352E+01, + 0.3219127620E+00, + 0.1046660300E+00, + 0.4019868296E-01 + ], + "coefficients": [ + [ + -0.6220714565E-01, + 0.2976804596E-04, + 0.5588549221E+00, + 0.4977673218E+00 + ], + [ + 0.4368434884E-01, + 0.2863793984E+00, + 0.5835753141E+00, + 0.2463134378E+00 + ] + ], + "function_type": "gto_cartesian" + } + ] + }, + "cl_STO-4G": { + "electron_shells": [ + { + "angular_momentum": [ + 0 + ], + "exponents": [ + 0.1408260576E+04, + 0.2576943351E+03, + 0.7159030805E+02, + 0.2376017966E+02 + ], + "coefficients": [ + [ + 0.5675242080E-01, + 0.2601413550E+00, + 0.5328461143E+00, + 0.2916254405E+00 + ] + ], + "function_type": "gto_cartesian" + }, + { + "angular_momentum": [ + 0, + 1 + ], + "exponents": [ + 0.9105253261E+02, + 0.1971091961E+02, + 0.6408766434E+01, + 0.2461390482E+01 + ], + "coefficients": [ + [ + -0.6220714565E-01, + 0.2976804596E-04, + 0.5588549221E+00, + 0.4977673218E+00 + ], + [ + 0.4368434884E-01, + 0.2863793984E+00, + 0.5835753141E+00, + 0.2463134378E+00 + ] + ], + "function_type": "gto_cartesian" + }, + { + "angular_momentum": [ + 0, + 1 + ], + "exponents": [ + 0.4064728946E+01, + 0.1117815984E+01, + 0.4399626124E+00, + 0.1958035957E+00 + ], + "coefficients": [ + [ + -0.8529019644E-01, + -0.2132074034E+00, + 0.5920843928E+00, + 0.6115584746E+00 + ], + [ + -0.2504945181E-01, + 0.1686604461E+00, + 0.6409553151E+00, + 0.2779508957E+00 + ] + ], + "function_type": "gto_cartesian" + } + ] + } + }, + "atom_map": ["li_STO-4G", "cl_STO-4G"] + } + } +} \ No newline at end of file diff --git a/iodata/test/data/LiCl_string_STO4G_input.json b/iodata/test/data/LiCl_string_STO4G_input.json new file mode 100644 index 000000000..3f99c7de0 --- /dev/null +++ b/iodata/test/data/LiCl_string_STO4G_input.json @@ -0,0 +1,28 @@ +{ + "schema_name": "qcschema_input", + "schema_version": 2.0, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2.0, + "symbols": ["Li", "Cl"], + "geometry": [0.000000, 0.000000, -1.631761, 0.000000, 0.000000, 0.287958], + "molecular_charge": 0.0, + "molecular_multiplicity": 1, + "real": [true, true], + "provenance": [ + { + "creator": "Gaussian 16", + "version": "ES64L-G16RevC.01" + }, + { + "creator": "HORTON3", + "routine": "Manual validation" + } + ] + }, + "driver": "energy", + "model": { + "method": "B3LYP", + "basis": "Def2TZVP" + } +} \ No newline at end of file diff --git a/iodata/test/data/ch5plus.pdb b/iodata/test/data/ch5plus.pdb new file mode 100644 index 000000000..0732405f5 --- /dev/null +++ b/iodata/test/data/ch5plus.pdb @@ -0,0 +1,17 @@ +COMPND UNNAMED +AUTHOR GENERATED BY OPEN BABEL 2.4.1 +HETATM 1 C UNL 1 1.084 0.018 -0.200 1.00 0.00 C1+ +HETATM 2 H UNL 1 1.536 0.152 -1.213 1.00 0.00 H +HETATM 3 H UNL 1 0.314 0.733 -0.579 1.00 0.00 H +HETATM 4 H UNL 1 0.636 -0.992 -0.139 1.00 0.00 H +HETATM 5 H UNL 1 0.925 0.544 0.772 1.00 0.00 H +HETATM 6 H UNL 1 2.147 -0.036 0.138 1.00 0.00 H +CONECT 1 2 3 4 5 +CONECT 1 6 +CONECT 2 1 +CONECT 3 1 +CONECT 4 1 +CONECT 5 1 +CONECT 6 1 +MASTER 0 0 0 0 0 0 0 0 6 0 6 0 +END diff --git a/iodata/test/data/formamide.sdf b/iodata/test/data/formamide.sdf new file mode 100644 index 000000000..784fdbcb7 --- /dev/null +++ b/iodata/test/data/formamide.sdf @@ -0,0 +1,107 @@ +713 + -OEChem-03292115373D + + 6 5 0 0 0 0 0 0 0999 v2000 + 1.1280 0.2091 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1878 0.1791 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0598 -0.3882 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3085 1.1864 0.0001 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.0305 -0.3861 -0.0001 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.0014 -1.4883 -0.0001 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1 3 2 0 0 0 0 + 2 3 1 0 0 0 0 + 2 4 1 0 0 0 0 + 2 5 1 0 0 0 0 + 3 6 1 0 0 0 0 +M END +> <PUBCHEM_COMPOUND_CID> +713 + +> <PUBCHEM_CONFORMER_RMSD> +0.4 + +> <PUBCHEM_CONFORMER_DIVERSEORDER> +1 + +> <PUBCHEM_MMFF94_PARTIAL_CHARGES> +6 +1 -0.57 +2 -0.8 +3 0.57 +4 0.37 +5 0.37 +6 0.06 + +> <PUBCHEM_EFFECTIVE_ROTOR_COUNT> +0 + +> <PUBCHEM_PHARMACOPHORE_FEATURES> +2 +1 1 acceptor +1 2 donor + +> <PUBCHEM_HEAVY_ATOM_COUNT> +3 + +> <PUBCHEM_ATOM_DEF_STEREO_COUNT> +0 + +> <PUBCHEM_ATOM_UDEF_STEREO_COUNT> +0 + +> <PUBCHEM_BOND_DEF_STEREO_COUNT> +0 + +> <PUBCHEM_BOND_UDEF_STEREO_COUNT> +0 + +> <PUBCHEM_ISOTOPIC_ATOM_COUNT> +0 + +> <PUBCHEM_COMPONENT_COUNT> +1 + +> <PUBCHEM_CACTVS_TAUTO_COUNT> +2 + +> <PUBCHEM_CONFORMER_ID> +000002C900000001 + +> <PUBCHEM_MMFF94_ENERGY> +1.0328 + +> <PUBCHEM_FEATURE_SELFOVERLAP> +10.148 + +> <PUBCHEM_SHAPE_FINGERPRINT> +139733 1 18410856568235096357 +21015797 1 18047756184745781958 + +> <PUBCHEM_SHAPE_MULTIPOLES> +50.89 +1.35 +0.63 +0.55 +0.08 +0.06 +0 +-0.17 +0 +-0.01 +0 +0 +0.02 +0 + +> <PUBCHEM_SHAPE_SELFOVERLAP> +82.114 + +> <PUBCHEM_SHAPE_VOLUME> +35.3 + +> <PUBCHEM_COORDINATE_TYPE> +2 +5 +10 + +$$$$ diff --git a/iodata/test/data/h2o_ccpvdz_cfour.molden b/iodata/test/data/h2o_ccpvdz_cfour.molden new file mode 100644 index 000000000..4368ebc7c --- /dev/null +++ b/iodata/test/data/h2o_ccpvdz_cfour.molden @@ -0,0 +1,336 @@ +[Molden Format] +[ATOMS] AU +O 1 8 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +s 9 1.00 + 11720.0000000000 7.100000000000000E-004 + 1759.00000000000 5.470000000000000E-003 + 400.800000000000 2.783700000000000E-002 + 113.700000000000 0.104800000000000 + 37.0300000000000 0.283062000000000 + 13.2700000000000 0.448719000000000 + 5.02500000000000 0.270952000000000 + 1.01300000000000 1.545800000000000E-002 + 0.302300000000000 -2.585000000000000E-003 +s 9 1.00 + 11720.0000000000 -1.600000000000000E-004 + 1759.00000000000 -1.263000000000000E-003 + 400.800000000000 -6.267000000000000E-003 + 113.700000000000 -2.571600000000000E-002 + 37.0300000000000 -7.092400000000000E-002 + 13.2700000000000 -0.165411000000000 + 5.02500000000000 -0.116955000000000 + 1.01300000000000 0.557368000000000 + 0.302300000000000 0.572759000000000 +s 9 1.00 + 11720.0000000000 0.000000000000000E+000 + 1759.00000000000 0.000000000000000E+000 + 400.800000000000 0.000000000000000E+000 + 113.700000000000 0.000000000000000E+000 + 37.0300000000000 0.000000000000000E+000 + 13.2700000000000 0.000000000000000E+000 + 5.02500000000000 0.000000000000000E+000 + 1.01300000000000 0.000000000000000E+000 + 0.302300000000000 1.00000000000000 +p 4 1.00 + 17.7000000000000 4.301800000000000E-002 + 3.85400000000000 0.228913000000000 + 1.04600000000000 0.508728000000000 + 0.275300000000000 0.460531000000000 +p 4 1.00 + 17.7000000000000 0.000000000000000E+000 + 3.85400000000000 0.000000000000000E+000 + 1.04600000000000 0.000000000000000E+000 + 0.275300000000000 1.00000000000000 +d 1 1.00 + 1.18500000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= -20.7001505863133 + Spin= Alpha + Occup= 1.0 + 1 1.0002306751 + 2 0.0024756258 + 3 0.0002313333 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.0005641051 + 11 -0.0011828259 + 12 -0.0005641051 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= -1.25252834132722 + Spin= Alpha + Occup= 1.0 + 1 0.0019570464 + 2 1.0107190924 + 3 -0.0079117688 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.0000274822 + 11 -0.0026984315 + 12 -0.0000274822 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= -0.572399522971029 + Spin= Alpha + Occup= 1.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.9865945479 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0166013578 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= -0.572399522971027 + Spin= Alpha + Occup= 1.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.9865945479 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0166013578 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 9.400243483509207E-003 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.7967818495 + 6 0.0000000000 + 7 0.0000000000 + 8 0.2399029598 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 1.13604126310501 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 1.3645826706 + 7 0.0000000000 + 8 0.0000000000 + 9 -1.6837989968 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 1.13604126310501 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 1.3645826706 + 5 0.0000000000 + 6 0.0000000000 + 7 -1.6837989968 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 1.18661100682885 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 1.4834397026 + 6 0.0000000000 + 7 0.0000000000 + 8 -1.6667037039 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 1.20425012316674 + Spin= Alpha + Occup= 0.0 + 1 0.5600486787 + 2 2.4274856355 + 3 -3.2043851670 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.1526965089 + 11 0.1568027798 + 12 0.1526965089 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.86543398793250 + Spin= Alpha + Occup= 0.0 + 1 0.0071431511 + 2 0.0517470643 + 3 -0.0247158181 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.2799206862 + 11 -0.5860261112 + 12 0.2799206862 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.87492401567841 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 1.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.87492401567841 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 1.0000000000 + Sym= A + Ene= 2.90248478458832 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 1.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.90248478458833 + Spin= Alpha + Occup= 0.0 + 1 -0.0000000000 + 2 -0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.5000000000 + 11 0.0000000000 + 12 -0.5000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 5.44588451900774 + Spin= Alpha + Occup= 0.0 + 1 0.4265980641 + 2 3.5512321174 + 3 -0.7818339229 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.7785719737 + 11 -0.7680000366 + 12 -0.7785719737 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 diff --git a/iodata/test/data/h_donly_cart_cfour.molden b/iodata/test/data/h_donly_cart_cfour.molden new file mode 100644 index 000000000..3fb15c265 --- /dev/null +++ b/iodata/test/data/h_donly_cart_cfour.molden @@ -0,0 +1,131 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +d 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= -5.180222245094024E-002 + Spin= Alpha + Occup= 0.0 + 1 0.2581988897 + 2 0.2581988897 + 3 0.2581988897 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 1.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 1.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 1.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.5483667316 + 2 -0.4306136881 + 3 -0.1177530435 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.1806301774 + 2 0.3845844315 + 3 -0.5652146089 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= -5.180222245094024E-002 + Spin= Beta + Occup= 0.0 + 1 0.2581988897 + 2 0.2581988897 + 3 0.2581988897 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 1.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 1.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 1.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.5483667316 + 2 -0.4306136881 + 3 -0.1177530435 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.1806301774 + 2 0.3845844315 + 3 -0.5652146089 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 diff --git a/iodata/test/data/h_donly_sph_cfour.molden b/iodata/test/data/h_donly_sph_cfour.molden new file mode 100644 index 000000000..ed162f897 --- /dev/null +++ b/iodata/test/data/h_donly_sph_cfour.molden @@ -0,0 +1,111 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +d 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 -0.2886751346 + 2 -0.2886751346 + 3 0.5773502692 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.5000000000 + 2 -0.5000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 1.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 1.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 1.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 -0.2886751346 + 2 -0.2886751346 + 3 0.5773502692 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.5000000000 + 2 -0.5000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 1.0000000000 + 5 0.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 1.0000000000 + 6 0.0000000000 + Sym= A + Ene= 1.14819777754906 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 1.0000000000 diff --git a/iodata/test/data/h_fonly_cart_cfour.molden b/iodata/test/data/h_fonly_cart_cfour.molden new file mode 100644 index 000000000..5da3f1eda --- /dev/null +++ b/iodata/test/data/h_fonly_cart_cfour.molden @@ -0,0 +1,291 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +f 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= 0.305598095042051 + Spin= Alpha + Occup= 0.0 + 1 0.1690308509 + 2 0.0000000000 + 3 0.0000000000 + 4 0.1690308509 + 5 0.0000000000 + 6 0.0000000000 + 7 0.1690308509 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 0.305598095042051 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.1690308509 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1690308509 + 6 0.0000000000 + 7 0.0000000000 + 8 0.1690308509 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 0.305598095042052 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.1690308509 + 4 0.0000000000 + 5 0.0000000000 + 6 0.1690308509 + 7 0.0000000000 + 8 0.0000000000 + 9 0.1690308509 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361347 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 -0.1127525231 + 3 0.0000000000 + 4 0.0000000000 + 5 0.6189351397 + 6 0.0000000000 + 7 0.0000000000 + 8 -0.2806775704 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 -0.2322790029 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1300741823 + 6 0.0000000000 + 7 0.0000000000 + 8 0.5667628265 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.2352502570 + 2 0.0000000000 + 3 0.0000000000 + 4 -0.5589456093 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.1468051619 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.1064142059 + 2 0.0000000000 + 3 0.0000000000 + 4 0.2959388549 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.6151814728 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0365148372 + 4 0.0000000000 + 5 0.0000000000 + 6 0.4402024911 + 7 0.0000000000 + 8 0.0000000000 + 9 -0.5497470026 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 1.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 -0.2556038602 + 4 0.0000000000 + 5 0.0000000000 + 6 0.4541164684 + 7 0.0000000000 + 8 0.0000000000 + 9 0.3126951121 + 10 0.0000000000 + Sym= A + Ene= 0.305598095042051 + Spin= Beta + Occup= 0.0 + 1 0.1690308509 + 2 0.0000000000 + 3 0.0000000000 + 4 0.1690308509 + 5 0.0000000000 + 6 0.0000000000 + 7 0.1690308509 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 0.305598095042051 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.1690308509 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1690308509 + 6 0.0000000000 + 7 0.0000000000 + 8 0.1690308509 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 0.305598095042052 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.1690308509 + 4 0.0000000000 + 5 0.0000000000 + 6 0.1690308509 + 7 0.0000000000 + 8 0.0000000000 + 9 0.1690308509 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361347 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 -0.1127525231 + 3 0.0000000000 + 4 0.0000000000 + 5 0.6189351397 + 6 0.0000000000 + 7 0.0000000000 + 8 -0.2806775704 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 -0.2322790029 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1300741823 + 6 0.0000000000 + 7 0.0000000000 + 8 0.5667628265 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.2352502570 + 2 0.0000000000 + 3 0.0000000000 + 4 -0.5589456093 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.1468051619 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.1064142059 + 2 0.0000000000 + 3 0.0000000000 + 4 0.2959388549 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.6151814728 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0365148372 + 4 0.0000000000 + 5 0.0000000000 + 6 0.4402024911 + 7 0.0000000000 + 8 0.0000000000 + 9 -0.5497470026 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 1.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 -0.2556038602 + 4 0.0000000000 + 5 0.0000000000 + 6 0.4541164684 + 7 0.0000000000 + 8 0.0000000000 + 9 0.3126951121 + 10 0.0000000000 diff --git a/iodata/test/data/h_fonly_sph_cfour.molden b/iodata/test/data/h_fonly_sph_cfour.molden new file mode 100644 index 000000000..93de459c9 --- /dev/null +++ b/iodata/test/data/h_fonly_sph_cfour.molden @@ -0,0 +1,207 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +f 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.1269377852 + 3 0.0000000000 + 4 0.0000000000 + 5 -0.6258093593 + 6 0.0000000000 + 7 0.0000000000 + 8 0.2449960038 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.2581988897 + 4 0.0000000000 + 5 0.0000000000 + 6 -0.3872983346 + 7 0.0000000000 + 8 0.0000000000 + 9 -0.3872983346 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 1.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 -0.0818845520 + 2 0.0000000000 + 3 0.0000000000 + 4 -0.3513629552 + 5 0.0000000000 + 6 0.0000000000 + 7 0.5970166113 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 -0.2248409779 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0914475029 + 6 0.0000000000 + 7 0.0000000000 + 8 0.5830754309 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.5000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 -0.5000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Alpha + Occup= 0.0 + 1 -0.2448705511 + 2 0.0000000000 + 3 0.0000000000 + 4 0.5258745798 + 5 0.0000000000 + 6 0.0000000000 + 7 0.2087370735 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.1269377852 + 3 0.0000000000 + 4 0.0000000000 + 5 -0.6258093593 + 6 0.0000000000 + 7 0.0000000000 + 8 0.2449960038 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.2581988897 + 4 0.0000000000 + 5 0.0000000000 + 6 -0.3872983346 + 7 0.0000000000 + 8 0.0000000000 + 9 -0.3872983346 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 1.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 -0.0818845520 + 2 0.0000000000 + 3 0.0000000000 + 4 -0.3513629552 + 5 0.0000000000 + 6 0.0000000000 + 7 0.5970166113 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 -0.2248409779 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0914475029 + 6 0.0000000000 + 7 0.0000000000 + 8 0.5830754309 + 9 0.0000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.5000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 -0.5000000000 + 10 0.0000000000 + Sym= A + Ene= 1.73416952361348 + Spin= Beta + Occup= 0.0 + 1 -0.2448705511 + 2 0.0000000000 + 3 0.0000000000 + 4 0.5258745798 + 5 0.0000000000 + 6 0.0000000000 + 7 0.2087370735 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 diff --git a/iodata/test/data/h_gonly_cart_cfour.molden b/iodata/test/data/h_gonly_cart_cfour.molden new file mode 100644 index 000000000..b14fa06a7 --- /dev/null +++ b/iodata/test/data/h_gonly_cart_cfour.molden @@ -0,0 +1,581 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +g 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= 6.926179876753769E-002 + Spin= Alpha + Occup= 0.0 + 1 0.0325300024 + 2 0.0325300024 + 3 0.0325300024 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0650600049 + 11 0.0650600049 + 12 0.0650600049 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 0.735928465434202 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.1259881577 + 5 0.0000000000 + 6 0.1259881577 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.1259881577 + Sym= A + Ene= 0.735928465434204 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1259881577 + 6 0.0000000000 + 7 0.0000000000 + 8 0.1259881577 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.1259881577 + 15 0.0000000000 + Sym= A + Ene= 0.735928465434204 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.1259881577 + 8 0.0000000000 + 9 0.1259881577 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.1259881577 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 0.735928465434206 + Spin= Alpha + Occup= 0.0 + 1 0.0112781454 + 2 -0.0678713517 + 3 0.0565932063 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.0565932063 + 11 0.0678713517 + 12 -0.0112781454 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 0.735928465434206 + Spin= Alpha + Occup= 0.0 + 1 0.0718596460 + 2 -0.0261626626 + 3 -0.0456969834 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0456969834 + 11 0.0261626626 + 12 -0.0718596460 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098973 + Spin= Alpha + Occup= 0.0 + 1 0.0746882567 + 2 0.0794963268 + 3 0.0682150470 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.2579086098 + 11 -0.1902209306 + 12 -0.2190693512 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098975 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0828791366 + 5 0.0000000000 + 6 0.1344659269 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 -0.6520351905 + Sym= A + Ene= 2.29148402098975 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.2837763581 + 8 0.0000000000 + 9 -0.2930520903 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0278271964 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.1212809705 + 8 0.0000000000 + 9 -0.0967396900 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.6540619816 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.2418660293 + 6 0.0000000000 + 7 0.0000000000 + 8 -0.3081799507 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.1989417642 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0621565764 + 2 -0.0433665197 + 3 -0.0145640931 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.1000624496 + 11 -0.2728770089 + 12 0.3602615678 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.2972694804 + 5 0.0000000000 + 6 -0.2777715063 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 -0.0584939221 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1916739917 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0162238472 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 -0.6236935169 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098977 + Spin= Alpha + Occup= 0.0 + 1 0.0090572533 + 2 0.0363797816 + 3 -0.0682525024 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.3410686119 + 11 0.2867250921 + 12 0.1227899222 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 6.926179876753769E-002 + Spin= Beta + Occup= 0.0 + 1 0.0325300024 + 2 0.0325300024 + 3 0.0325300024 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0650600049 + 11 0.0650600049 + 12 0.0650600049 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 0.735928465434202 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.1259881577 + 5 0.0000000000 + 6 0.1259881577 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.1259881577 + Sym= A + Ene= 0.735928465434204 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1259881577 + 6 0.0000000000 + 7 0.0000000000 + 8 0.1259881577 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.1259881577 + 15 0.0000000000 + Sym= A + Ene= 0.735928465434204 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.1259881577 + 8 0.0000000000 + 9 0.1259881577 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.1259881577 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 0.735928465434206 + Spin= Beta + Occup= 0.0 + 1 0.0112781454 + 2 -0.0678713517 + 3 0.0565932063 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.0565932063 + 11 0.0678713517 + 12 -0.0112781454 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 0.735928465434206 + Spin= Beta + Occup= 0.0 + 1 0.0718596460 + 2 -0.0261626626 + 3 -0.0456969834 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0456969834 + 11 0.0261626626 + 12 -0.0718596460 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098973 + Spin= Beta + Occup= 0.0 + 1 0.0746882567 + 2 0.0794963268 + 3 0.0682150470 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.2579086098 + 11 -0.1902209306 + 12 -0.2190693512 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098975 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0828791366 + 5 0.0000000000 + 6 0.1344659269 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 -0.6520351905 + Sym= A + Ene= 2.29148402098975 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.2837763581 + 8 0.0000000000 + 9 -0.2930520903 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0278271964 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.1212809705 + 8 0.0000000000 + 9 -0.0967396900 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.6540619816 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.2418660293 + 6 0.0000000000 + 7 0.0000000000 + 8 -0.3081799507 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.1989417642 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0621565764 + 2 -0.0433665197 + 3 -0.0145640931 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.1000624496 + 11 -0.2728770089 + 12 0.3602615678 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.2972694804 + 5 0.0000000000 + 6 -0.2777715063 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 -0.0584939221 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1916739917 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0162238472 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 -0.6236935169 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098977 + Spin= Beta + Occup= 0.0 + 1 0.0090572533 + 2 0.0363797816 + 3 -0.0682525024 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 -0.3410686119 + 11 0.2867250921 + 12 0.1227899222 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 diff --git a/iodata/test/data/h_gonly_sph_cfour.molden b/iodata/test/data/h_gonly_sph_cfour.molden new file mode 100644 index 000000000..79d384fc1 --- /dev/null +++ b/iodata/test/data/h_gonly_sph_cfour.molden @@ -0,0 +1,353 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +g 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 -0.0668814914 + 2 -0.0668814914 + 3 0.0125113364 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.4388229578 + 11 -0.0375340093 + 12 -0.0375340093 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 -0.0545544726 + 2 0.0545544726 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.3273268354 + 12 -0.3273268354 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.1372586917 + 8 0.0000000000 + 9 -0.0798788169 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.6514125256 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 -0.1091089451 + 5 0.0000000000 + 6 -0.1091089451 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.6546536707 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.2886751346 + 5 0.0000000000 + 6 -0.2886751346 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 -0.2723908058 + 6 0.0000000000 + 7 0.0000000000 + 8 0.3002388602 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 -0.0835441632 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.2764021469 + 8 0.0000000000 + 9 0.2980897010 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 -0.0650626624 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1450563482 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0713773218 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 -0.6493010098 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Alpha + Occup= 0.0 + 1 0.0455465164 + 2 0.0455465164 + 3 0.0967846888 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0170749681 + 11 -0.2903540664 + 12 -0.2903540664 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 -0.0668814914 + 2 -0.0668814914 + 3 0.0125113364 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.4388229578 + 11 -0.0375340093 + 12 -0.0375340093 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 -0.0545544726 + 2 0.0545544726 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.3273268354 + 12 -0.3273268354 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.1372586917 + 8 0.0000000000 + 9 -0.0798788169 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.6514125256 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 -0.1091089451 + 5 0.0000000000 + 6 -0.1091089451 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.6546536707 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.2886751346 + 5 0.0000000000 + 6 -0.2886751346 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 -0.2723908058 + 6 0.0000000000 + 7 0.0000000000 + 8 0.3002388602 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 -0.0835441632 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 -0.2764021469 + 8 0.0000000000 + 9 0.2980897010 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 -0.0650626624 + 14 0.0000000000 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 0.0000000000 + 4 0.0000000000 + 5 0.1450563482 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0713773218 + 9 0.0000000000 + 10 0.0000000000 + 11 0.0000000000 + 12 0.0000000000 + 13 0.0000000000 + 14 -0.6493010098 + 15 0.0000000000 + Sym= A + Ene= 2.29148402098976 + Spin= Beta + Occup= 0.0 + 1 0.0455465164 + 2 0.0455465164 + 3 0.0967846888 + 4 0.0000000000 + 5 0.0000000000 + 6 0.0000000000 + 7 0.0000000000 + 8 0.0000000000 + 9 0.0000000000 + 10 0.0170749681 + 11 -0.2903540664 + 12 -0.2903540664 + 13 0.0000000000 + 14 0.0000000000 + 15 0.0000000000 diff --git a/iodata/test/data/h_ponly_cart_cfour.molden b/iodata/test/data/h_ponly_cart_cfour.molden new file mode 100644 index 000000000..de1b9ddc8 --- /dev/null +++ b/iodata/test/data/h_ponly_cart_cfour.molden @@ -0,0 +1,53 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +p 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= 0.497747221936325 + Spin= Alpha + Occup= 0.0 + 1 1.0000000000 + 2 0.0000000000 + 3 0.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 1.0000000000 + 3 0.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 1.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Beta + Occup= 0.0 + 1 1.0000000000 + 2 0.0000000000 + 3 0.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 1.0000000000 + 3 0.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 1.0000000000 diff --git a/iodata/test/data/h_ponly_sph_cfour.molden b/iodata/test/data/h_ponly_sph_cfour.molden new file mode 100644 index 000000000..de1b9ddc8 --- /dev/null +++ b/iodata/test/data/h_ponly_sph_cfour.molden @@ -0,0 +1,53 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +p 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= 0.497747221936325 + Spin= Alpha + Occup= 0.0 + 1 1.0000000000 + 2 0.0000000000 + 3 0.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 1.0000000000 + 3 0.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Alpha + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 1.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Beta + Occup= 0.0 + 1 1.0000000000 + 2 0.0000000000 + 3 0.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 1.0000000000 + 3 0.0000000000 + Sym= A + Ene= 0.497747221936325 + Spin= Beta + Occup= 0.0 + 1 0.0000000000 + 2 0.0000000000 + 3 1.0000000000 diff --git a/iodata/test/data/h_sonly_cart_cfour.molden b/iodata/test/data/h_sonly_cart_cfour.molden new file mode 100644 index 000000000..e4ce68f38 --- /dev/null +++ b/iodata/test/data/h_sonly_cart_cfour.molden @@ -0,0 +1,21 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +s 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= -0.378379167095513 + Spin= Alpha + Occup= 0.0 + 1 1.0000000000 + Sym= A + Ene= -0.378379167095513 + Spin= Beta + Occup= 0.0 + 1 1.0000000000 diff --git a/iodata/test/data/h_sonly_sph_cfour.molden b/iodata/test/data/h_sonly_sph_cfour.molden new file mode 100644 index 000000000..e4ce68f38 --- /dev/null +++ b/iodata/test/data/h_sonly_sph_cfour.molden @@ -0,0 +1,21 @@ +[Molden Format] +[ATOMS] AU +H 1 1 0.0000000000 0.0000000000 0.0000000000 +[Molden Format] +[GTO] + 1 0 +s 1 1.00 + 0.500000000000000 1.00000000000000 + + +[MO] + Sym= A + Ene= -0.378379167095513 + Spin= Alpha + Occup= 0.0 + 1 1.0000000000 + Sym= A + Ene= -0.378379167095513 + Spin= Beta + Occup= 0.0 + 1 1.0000000000 diff --git a/iodata/test/data/indomethacin-dimer.pdb b/iodata/test/data/indomethacin-dimer.pdb new file mode 100644 index 000000000..45d840865 --- /dev/null +++ b/iodata/test/data/indomethacin-dimer.pdb @@ -0,0 +1,88 @@ +TITLE indomethacin_indomethacin +REMARK THIS IS A SIMULATION BOX +CRYST1 3000.000 3000.000 3000.000 90.00 90.00 90.00 P 1 1 +MODEL 1 +ATOM 1 C1 XXX 1 1002.3891002.1441003.016 1.00 0.00 +ATOM 2 C2 XXX 1 1001.0891001.9451002.264 1.00 0.00 +ATOM 3 C3 XXX 1 999.8401002.0381002.867 1.00 0.00 +ATOM 4 C4 XXX 1 998.9231001.8711001.764 1.00 0.00 +ATOM 5 C5 XXX 1 999.6481001.6901000.605 1.00 0.00 +ATOM 6 N6 XXX 1 1001.0471001.7681000.868 1.00 0.00 +ATOM 7 C7 XXX 1 1002.1311001.735 999.982 1.00 0.00 +ATOM 8 O8 XXX 1 1003.2631001.4431000.335 1.00 0.00 +ATOM 9 C9 XXX 1 1001.8731002.152 998.556 1.00 0.00 +ATOM 10 C10 XXX 1 1002.4821001.445 997.502 1.00 0.00 +ATOM 11 C11 XXX 1 1002.2311001.801 996.167 1.00 0.00 +ATOM 12 C12 XXX 1 1001.3961002.889 995.878 1.00 0.00 +ATOM 13 C13 XXX 1 1000.8251003.634 996.921 1.00 0.00 +ATOM 14 C14 XXX 1 1001.0701003.275 998.257 1.00 0.00 +ATOM 15 CL15 XXX 1 1001.0771003.328 994.215 1.00 0.00 +ATOM 16 C16 XXX 1 998.9921001.379 999.401 1.00 0.00 +ATOM 17 C17 XXX 1 997.5841001.355 999.390 1.00 0.00 +ATOM 18 C18 XXX 1 996.8471001.5931000.568 1.00 0.00 +ATOM 19 C19 XXX 1 997.5241001.8391001.783 1.00 0.00 +ATOM 20 O20 XXX 1 995.4701001.5461000.491 1.00 0.00 +ATOM 21 C21 XXX 1 994.6241001.7061001.643 1.00 0.00 +ATOM 22 C22 XXX 1 999.5591002.2611004.338 1.00 0.00 +ATOM 23 C23 XXX 1 999.6311003.7241004.772 1.00 0.00 +ATOM 24 O24 XXX 1 1000.6441004.3881004.825 1.00 0.00 +ATOM 25 O25 XXX 1 998.4921004.2351005.231 1.00 0.00 +ATOM 26 H26 XXX 1 1003.0141001.2471002.966 1.00 0.00 +ATOM 27 H27 XXX 1 1002.2421002.3781004.075 1.00 0.00 +ATOM 28 H28 XXX 1 1002.9601002.9751002.585 1.00 0.00 +ATOM 29 H29 XXX 1 1003.1441000.613 997.724 1.00 0.00 +ATOM 30 H30 XXX 1 1002.6821001.232 995.359 1.00 0.00 +ATOM 31 H31 XXX 1 1000.1901004.486 996.692 1.00 0.00 +ATOM 32 H32 XXX 1 1000.6211003.856 999.061 1.00 0.00 +ATOM 33 H33 XXX 1 999.5391001.142 998.495 1.00 0.00 +ATOM 34 H34 XXX 1 997.0531001.139 998.468 1.00 0.00 +ATOM 35 H35 XXX 1 996.9941001.9891002.715 1.00 0.00 +ATOM 36 H36 XXX 1 994.7821002.6911002.100 1.00 0.00 +ATOM 37 H37 XXX 1 994.8381000.9261002.384 1.00 0.00 +ATOM 38 H38 XXX 1 993.5711001.6251001.346 1.00 0.00 +ATOM 39 H39 XXX 1 1000.2501001.6871004.964 1.00 0.00 +ATOM 40 H40 XXX 1 998.5591001.8821004.580 1.00 0.00 +ATOM 41 H41 XXX 1 998.8251005.1191005.462 1.00 0.00 +ATOM 42 C1 XXX 2 1000.227 998.3851002.728 1.00 0.00 +ATOM 43 C2 XXX 2 1000.499 998.3121001.240 1.00 0.00 +ATOM 44 C3 XXX 2 1001.780 998.2301000.707 1.00 0.00 +ATOM 45 C4 XXX 2 1001.547 998.162 999.286 1.00 0.00 +ATOM 46 C5 XXX 2 1000.190 998.227 999.053 1.00 0.00 +ATOM 47 N6 XXX 2 999.467 998.2451000.284 1.00 0.00 +ATOM 48 C7 XXX 2 998.090 998.1271000.528 1.00 0.00 +ATOM 49 O8 XXX 2 997.577 998.3871001.604 1.00 0.00 +ATOM 50 C9 XXX 2 997.233 997.574 999.418 1.00 0.00 +ATOM 51 C10 XXX 2 996.008 998.194 999.106 1.00 0.00 +ATOM 52 C11 XXX 2 995.197 997.685 998.078 1.00 0.00 +ATOM 53 C12 XXX 2 995.597 996.541 997.372 1.00 0.00 +ATOM 54 C13 XXX 2 996.796 995.893 997.701 1.00 0.00 +ATOM 55 C14 XXX 2 997.608 996.399 998.730 1.00 0.00 +ATOM 56 CL15 XXX 2 994.588 995.910 996.088 1.00 0.00 +ATOM 57 C16 XXX 2 999.708 998.359 997.738 1.00 0.00 +ATOM 58 C17 XXX 2 1000.633 998.316 996.675 1.00 0.00 +ATOM 59 C18 XXX 2 1002.013 998.164 996.922 1.00 0.00 +ATOM 60 C19 XXX 2 1002.484 998.098 998.251 1.00 0.00 +ATOM 61 O20 XXX 2 1002.867 998.134 995.839 1.00 0.00 +ATOM 62 C21 XXX 2 1004.285 997.924 995.971 1.00 0.00 +ATOM 63 C22 XXX 2 1003.084 998.2691001.473 1.00 0.00 +ATOM 64 C23 XXX 2 1003.474 996.9371002.105 1.00 0.00 +ATOM 65 O24 XXX 2 1003.050 996.5131003.158 1.00 0.00 +ATOM 66 O25 XXX 2 1004.401 996.2461001.445 1.00 0.00 +ATOM 67 H26 XXX 2 999.596 999.2491002.966 1.00 0.00 +ATOM 68 H27 XXX 2 1001.137 998.4801003.329 1.00 0.00 +ATOM 69 H28 XXX 2 999.705 997.4841003.070 1.00 0.00 +ATOM 70 H29 XXX 2 995.688 999.071 999.665 1.00 0.00 +ATOM 71 H30 XXX 2 994.258 998.172 997.832 1.00 0.00 +ATOM 72 H31 XXX 2 997.094 994.998 997.161 1.00 0.00 +ATOM 73 H32 XXX 2 998.535 995.890 998.986 1.00 0.00 +ATOM 74 H33 XXX 2 998.655 998.510 997.524 1.00 0.00 +ATOM 75 H34 XXX 2 1000.287 998.404 995.650 1.00 0.00 +ATOM 76 H35 XXX 2 1003.540 998.024 998.484 1.00 0.00 +ATOM 77 H36 XXX 2 1004.483 996.964 996.465 1.00 0.00 +ATOM 78 H37 XXX 2 1004.738 998.732 996.559 1.00 0.00 +ATOM 79 H38 XXX 2 1004.753 997.909 994.980 1.00 0.00 +ATOM 80 H39 XXX 2 1003.053 999.0221002.265 1.00 0.00 +ATOM 81 H40 XXX 2 1003.896 998.5821000.806 1.00 0.00 +ATOM 82 H41 XXX 2 1004.458 995.4871002.050 1.00 0.00 +TER +ENDMDL diff --git a/iodata/test/data/molv3000.sdf b/iodata/test/data/molv3000.sdf new file mode 100644 index 000000000..5370aa619 --- /dev/null +++ b/iodata/test/data/molv3000.sdf @@ -0,0 +1,257 @@ +levobupivacaine + -INDIGO-05122016302D + + 0 0 0 0 0 999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 21 22 0 0 0 +M V30 BEGIN ATOM +M V30 1 C -0.0038 -5.9956 0 0 +M V30 2 N -0.0038 -4.4556 0 0 +M V30 3 O -1.3375 -6.7637 0 0 +M V30 4 C 1.3298 -6.7637 0 0 CFG=1 +M V30 5 C -1.3336 -3.6837 0 0 +M V30 6 N 1.3298 -8.3037 0 0 +M V30 7 C 2.6634 -5.9956 0 0 +M V30 8 C -1.3336 -2.1437 0 0 +M V30 9 C -2.6711 -4.4556 0 0 +M V30 10 C -0.0038 -9.0756 0 0 +M V30 11 C 2.6634 -9.0756 0 0 +M V30 12 C 4.0009 -6.7637 0 0 +M V30 13 C -0.0038 -1.3757 0 0 +M V30 14 C -2.6711 -1.3757 0 0 +M V30 15 C -2.6711 -5.9956 0 0 +M V30 16 C -4.0009 -3.6837 0 0 +M V30 17 C -1.3375 -8.3037 0 0 +M V30 18 C 4.0009 -8.3037 0 0 +M V30 19 C -4.0009 -2.1437 0 0 +M V30 20 C -2.6711 -9.0756 0 0 +M V30 21 C -4.0047 -8.3037 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 2 1 3 +M V30 3 1 2 5 +M V30 4 1 4 1 CFG=3 +M V30 5 1 4 6 +M V30 6 1 4 7 +M V30 7 2 5 8 +M V30 8 1 5 9 +M V30 9 1 6 10 +M V30 10 1 6 11 +M V30 11 1 7 12 +M V30 12 1 8 13 +M V30 13 1 8 14 +M V30 14 1 9 15 +M V30 15 2 9 16 +M V30 16 1 10 17 +M V30 17 1 11 18 +M V30 18 1 12 18 +M V30 19 2 14 19 +M V30 20 1 16 19 +M V30 21 1 17 20 +M V30 22 1 20 21 +M V30 END BOND +M V30 END CTAB +M END +> <ID> +4 + +> <PREFERRED_NAME> +levobupivacaine + +> <CAS_RN> +27262-47-1 + +> <SYNONYMS> +levobupivacaine +chirocain +levobupivacaine hydrochloride +levobupivacaine HCl + +> <URL> +http://drugcentral.org/drugcard/4/view + +$$$$ +(S)-nicardipine + -INDIGO-05122016302D + + 0 0 0 0 0 999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 35 37 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.9985 -5.9956 0 0 +M V30 2 O 0.6649 -6.7637 0 0 +M V30 3 O 1.9985 -4.4556 0 0 +M V30 4 C 3.3322 -6.7637 0 0 +M V30 5 C -0.6687 -5.9956 0 0 +M V30 6 C 3.3322 -8.3036 0 0 CFG=2 +M V30 7 C 4.6696 -5.9956 0 0 +M V30 8 C -0.6687 -4.4556 0 0 +M V30 9 C 4.6696 -9.0756 0 0 +M V30 10 C 1.9985 -9.0756 0 0 +M V30 11 C 4.6696 -4.4556 0 0 +M V30 12 N 5.9994 -6.7637 0 0 +M V30 13 N -2.0024 -3.6837 0 0 +M V30 14 C 4.6696 -10.6155 0 0 +M V30 15 C 5.9994 -8.3036 0 0 +M V30 16 C 0.6649 -8.3036 0 0 +M V30 17 C 1.9985 -10.6155 0 0 +M V30 18 C -3.336 -4.4556 0 0 +M V30 19 C -2.0024 -2.1437 0 0 +M V30 20 O 5.9994 -11.3836 0 0 +M V30 21 O 3.3322 -11.3836 0 0 +M V30 22 C 7.333 -9.0756 0 0 +M V30 23 C -0.6687 -9.0756 0 0 +M V30 24 C 0.6649 -11.3836 0 0 +M V30 25 C -4.6658 -3.6837 0 0 +M V30 26 C 7.333 -10.6155 0 0 +M V30 27 N -2.0024 -8.3036 0 0 CHG=1 +M V30 28 C -0.6687 -10.6155 0 0 +M V30 29 C -4.6658 -2.1437 0 0 +M V30 30 C -5.9994 -4.4556 0 0 +M V30 31 O -2.0024 -6.7637 0 0 +M V30 32 O -3.336 -9.0756 0 0 CHG=-1 +M V30 33 C -5.9994 -1.3757 0 0 +M V30 34 C -7.333 -3.6837 0 0 +M V30 35 C -7.333 -2.1437 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 2 1 3 +M V30 3 1 1 4 +M V30 4 1 2 5 +M V30 5 1 6 4 +M V30 6 2 4 7 +M V30 7 1 5 8 +M V30 8 1 6 9 +M V30 9 1 6 10 CFG=3 +M V30 10 1 7 11 +M V30 11 1 7 12 +M V30 12 1 8 13 +M V30 13 1 9 14 +M V30 14 2 9 15 +M V30 15 2 10 16 +M V30 16 1 10 17 +M V30 17 1 12 15 +M V30 18 1 13 18 +M V30 19 1 13 19 +M V30 20 1 14 20 +M V30 21 2 14 21 +M V30 22 1 15 22 +M V30 23 1 16 23 +M V30 24 2 17 24 +M V30 25 1 18 25 +M V30 26 1 20 26 +M V30 27 1 23 27 +M V30 28 2 23 28 +M V30 29 1 24 28 +M V30 30 2 25 29 +M V30 31 1 25 30 +M V30 32 2 27 31 +M V30 33 1 27 32 +M V30 34 1 29 33 +M V30 35 2 30 34 +M V30 36 2 33 35 +M V30 37 1 34 35 +M V30 END BOND +M V30 END CTAB +M END +> <ID> +5 + +> <PREFERRED_NAME> +(S)-nicardipine + +> <CAS_RN> +76093-36-2 + +> <SYNONYMS> +(S)-nicardipine +(-)-Nicardipine + +> <URL> +http://drugcentral.org/drugcard/5/view + +$$$$ +(S)-nitrendipine + -INDIGO-05122016302D + + 0 0 0 0 0 999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 26 27 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 0.0038 -6.7638 0 0 +M V30 2 O 1.3375 -5.9957 0 0 +M V30 3 O 0.0038 -8.3038 0 0 +M V30 4 C -1.3298 -5.9957 0 0 +M V30 5 C 2.6864 -6.7408 0 0 +M V30 6 C -1.3298 -4.4557 0 0 CFG=2 +M V30 7 C -2.6635 -6.7638 0 0 +M V30 8 C 2.6864 -8.2808 0 0 +M V30 9 C -2.6635 -3.6838 0 0 +M V30 10 C 0.0038 -3.6838 0 0 +M V30 11 C -2.6635 -8.3038 0 0 +M V30 12 N -3.9971 -5.9957 0 0 +M V30 13 C -2.6635 -2.1438 0 0 +M V30 14 C -3.9971 -4.4557 0 0 +M V30 15 C 1.3375 -4.4557 0 0 +M V30 16 C 0.0038 -2.1438 0 0 +M V30 17 O -3.9971 -1.3757 0 0 +M V30 18 O -1.3298 -1.3757 0 0 +M V30 19 C -5.3308 -3.6838 0 0 +M V30 20 C 2.6711 -3.6838 0 0 +M V30 21 C 1.3375 -1.3757 0 0 +M V30 22 C -5.3461 -2.1208 0 0 +M V30 23 N 4.0086 -4.4557 0 0 CHG=1 +M V30 24 C 2.6711 -2.1438 0 0 +M V30 25 O 4.0086 -5.9957 0 0 CHG=-1 +M V30 26 O 5.3422 -3.6838 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 2 1 3 +M V30 3 1 1 4 +M V30 4 1 2 5 +M V30 5 1 6 4 +M V30 6 2 4 7 +M V30 7 1 5 8 +M V30 8 1 6 9 +M V30 9 1 6 10 CFG=3 +M V30 10 1 7 11 +M V30 11 1 7 12 +M V30 12 1 9 13 +M V30 13 2 9 14 +M V30 14 2 10 15 +M V30 15 1 10 16 +M V30 16 1 12 14 +M V30 17 1 13 17 +M V30 18 2 13 18 +M V30 19 1 14 19 +M V30 20 1 15 20 +M V30 21 2 16 21 +M V30 22 1 17 22 +M V30 23 1 20 23 +M V30 24 2 20 24 +M V30 25 1 21 24 +M V30 26 1 23 25 +M V30 27 2 23 26 +M V30 END BOND +M V30 END CTAB +M END +> <ID> +6 + +> <PREFERRED_NAME> +(S)-nitrendipine + +> <CAS_RN> +80873-62-7 + +> <SYNONYMS> +(S)-nitrendipine +(-)-Nitrendipine + +> <URL> +http://drugcentral.org/drugcard/6/view + +$$$$ diff --git a/iodata/test/data/turbomole_water_energy_hf_output.json b/iodata/test/data/turbomole_water_energy_hf_output.json new file mode 100644 index 000000000..6f24e1581 --- /dev/null +++ b/iodata/test/data/turbomole_water_energy_hf_output.json @@ -0,0 +1,112 @@ +{ + "id": null, + "schema_name": "qcschema_output", + "schema_version": 1, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2, + "validated": true, + "symbols": [ + "O", + "H", + "H" + ], + "geometry": [ + 0.0, + 0.0, + -0.12947694, + 0.0, + -1.49418734, + 1.02744651, + 0.0, + 1.49418734, + 1.02744651 + ], + "name": "H2O", + "identifiers": null, + "comment": null, + "molecular_charge": 0.0, + "molecular_multiplicity": 1, + "masses": [ + 15.99491461957, + 1.00782503223, + 1.00782503223 + ], + "real": [ + true, + true, + true + ], + "atom_labels": [ + "", + "", + "" + ], + "atomic_numbers": [ + 8, + 1, + 1 + ], + "mass_numbers": [ + 16, + 1, + 1 + ], + "connectivity": null, + "fragments": [ + [ + 0, + 1, + 2 + ] + ], + "fragment_charges": [ + 0.0 + ], + "fragment_multiplicities": [ + 1 + ], + "fix_com": false, + "fix_orientation": false, + "fix_symmetry": null, + "provenance": { + "creator": "QCElemental", + "version": "v0.8.0+11.g7c6a220", + "routine": "qcelemental.molparse.from_schema" + }, + "id": null, + "extras": null + }, + "driver": "energy", + "model": { + "method": "hf", + "basis": "def2-SVP" + }, + "keywords": {}, + "extras": { + "outfiles": { + "control": "$title\nQCEngine Turbomole\n$operating system unix\n$symmetry c1\n$user-defined bonds file=coord\n$coord file=coord\n$optimize\n internal off\n redundant off\n cartesian on\n global off\n basis off\n$atoms\no 1 \\\n basis =o def2-SVP\nh 2-3 \\\n basis =h def2-SVP\n$basis file=basis\n$rundimensions\n dim(fock,dens)=352\n natoms=3\n nshell=12\n nbf(CAO)=25\n dim(trafo[SAO<-->AO/CAO])=27\n rhfshells=1\n nbf(AO)=24\n$scfmo file=mos\n$closed shells\n a 1-5 ( 2 )\n$scfiterlimit 150\n$scfconv 8\n$thize 0.10000000E-04\n$thime 5\n$scfdamp start=0.300 step=0.050 min=0.100\n$scfdump\n$scfintunit\n unit=30 size=0 file=twoint\n$scfdiis\n$maxcor 500 MiB per_core\n$scforbitalshift automatic=.1\n$drvopt\n cartesian on\n basis off\n global off\n hessian on\n dipole on\n nuclear polarizability\n$interconversion off\n qconv=1.d-7\n maxiter=25\n$coordinateupdate\n dqmax=0.3\n interpolate on\n statistics 5\n$forceupdate\n ahlrichs numgeo=0 mingeo=3 maxgeo=4 modus=<g|dq> dynamic fail=0.3\n threig=0.005 reseig=0.005 thrbig=3.0 scale=1.00 damping=0.0\n$forceinit on\n diag=default\n$energy file=energy\n$grad file=gradient\n$forceapprox file=forceapprox\n$last step dscf\n$orbital_max_rnorm 0.30769395709238E-05\n$last SCF energy change = -75.955370\n$charge from dscf\n -0.000 (not to be modified here)\n$dipole from dscf\n x 0.00000000000002 y -0.00000000000000 z 0.85624894415066 a.u.\n | dipole | = 2.1763841798 debye\n$end\n", + "stderr": " dscf ended normally\n" + }, + "qcvars": { + "HF TOTAL ENERGY": "-75.95536954370", + "CURRENT ENERGY": "-75.95536954370" + } + }, + "provenance": { + "creator": "Turbomole", + "version": "7.3", + "routine": "turbomole", + "username": "johannes", + "cpu": "Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz", + "wall_time": 0.309786319732666, + "qcengine_version": "v0.10.0+80.gd0ddc48.dirty", + "hostname": "southgeorgia" + }, + "properties": {}, + "return_result": -75.9553695437, + "stdout": "\n OpenMP run-time library returned nthreads = 2\n operating system is UNIX !\n\n dscf (southgeorgia) : TURBOMOLE V7.3 ( 22142 ) 4 Jul 2018 at 18:12:42\n Copyright (C) 2018 TURBOMOLE GmbH, Karlsruhe\n\n\n 2019-10-29 09:56:50.465 \n\n\n\n d s c f - program\n\n idea & directorship : reinhart ahlrichs\n program development : marco haeser\n michael baer\n dft version : oliver treutler\n\n\n quantum chemistry group\n universitaet karlsruhe\n germany\n\n\n\n\n References \n \n TURBOMOLE: \n R. Ahlrichs, M. Baer, M. Haeser, H. Horn, and\n C. Koelmel\n Electronic structure calculations on workstation\n computers: the program system TURBOMOLE\n Chem. Phys. Lett. 162: 165 (1989)\n Density Functional: \n O. Treutler and R. Ahlrichs \n Efficient Molecular Numerical Integration Schemes\n J. Chem. Phys. 102: 346 (1995) \n Parallel Version: \n Performance of parallel TURBOMOLE for Density \n Functional Calculations \n M. v. Arnim and R. Ahlrichs \n J. Comp. Chem. 19: 1746 (1998) \n \n\n\n\n\n +--------------------------------------------------+\n | Atomic coordinate, charge and isotop information |\n +--------------------------------------------------+\n\n atomic coordinates atom charge isotop\n 0.00000000 0.00000000 -0.12947694 o 8.000 0\n 0.00000000 -1.49418734 1.02744651 h 1.000 0\n 0.00000000 1.49418734 1.02744651 h 1.000 0\n \n center of nuclear mass : 0.00000000 0.00000000 -0.00001570\n center of nuclear charge: 0.00000000 0.00000000 0.10190775\n\n *************************************************************************\n QCEngine Turbomole \n *************************************************************************\n\n\n\n +--------------------------------------------------+\n | basis set information |\n +--------------------------------------------------+\n\n we will work with the 1s 3p 5d 7f 9g ... basis set\n ...i.e. with spherical basis functions...\n\n type atoms prim cont basis\n ---------------------------------------------------------------------------\n o 1 24 14 def2-SVP [3s2p1d|7s4p1d]\n h 2 7 5 def2-SVP [2s1p|4s1p]\n ---------------------------------------------------------------------------\n total: 3 38 24\n ---------------------------------------------------------------------------\n\n total number of primitive shells : 17\n total number of contracted shells : 12\n total number of cartesian basis functions : 25\n total number of SCF-basis functions : 24\n\n\n integral neglect threshold : 0.13E-10\n integral storage threshold THIZE : 0.10E-04\n integral storage threshold THIME : 5\n\n\n symmetry group of the molecule : c1 \n\n the group has the following generators :\n c1(z)\n\n 1 symmetry operations found\n\n there are 1 real representations : a \n\n maximum number of shells which are related by symmetry : 1\n\n\n mo occupation :\n irrep mo's occupied\n a 24 5\n \n number of basis functions : 24\n number of occupied orbitals : 5\n \n\n automatic virtual orbital shift switched on \n shift if e(lumo)-e(homo) < 0.10000000 \n\n\n ------------------------\n nuclear repulsion energy : 8.80146205625 \n ------------------------\n\n\n -----------------\n -S,T+V- integrals\n -----------------\n\n 1e-integrals will be neglected if expon. factor < 0.133678E-11\n \n Difference densities algorithm switched on.\n The maximal number of linear combinations of\n difference densities is 20 .\n\n DIIS switched on: error vector is FDS-SDF\n Max. Iterations for DIIS is : 5\n DIIS matrix (see manual) \n Scaling factor of diagonals : 1.200\n threshold for scaling factor : 0.000\n\n scf convergence criterion : increment of total energy < .1000000D-07\n and increment of one-electron energy < .1000000D-04\n\n MOs are in ASCII format !\n\n\n reading orbital data $scfmo from file mos\n orbital characterization : expanded\n mo provided and orthogonalized by Cholesky decomposition\n\n DSCF restart information will be dumped onto file mos\n\n \n current damping : 0.300\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 1 -75.741527779117 -123.80966488 39.266675041 0.000D+00 0.133D-10\n max. resid. norm for Fia-block= 2.815D-01 for orbital 4a \n max. resid. fock norm = 1.548D+00 for orbital 10a \n Delta Eig. = 47.9536513414 eV \n \n current damping : 0.250\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 2 -75.933097028971 -122.13127215 37.396713062 0.518D+00 0.885D-11\n Norm of current diis error: 0.34565 \n max. resid. norm for Fia-block= 9.171D-02 for orbital 1a \n max. resid. fock norm = 1.102D-01 for orbital 18a \n Delta Eig. = 9.1913160300 eV \n \n current damping : 0.200\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 3 -75.951594723455 -122.46272320 37.709666417 0.171D+00 0.698D-11\n Norm of current diis error: 0.12813 \n max. resid. norm for Fia-block= 3.429D-02 for orbital 4a \n max. resid. fock norm = 3.703D-02 for orbital 9a \n Delta Eig. = 2.6966294125 eV \n \n current damping : 0.250\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 4 -75.954873573064 -122.31745771 37.561122081 0.353D-01 0.611D-11\n Norm of current diis error: 0.40537E-01\n max. resid. norm for Fia-block= 1.251D-02 for orbital 4a \n max. resid. fock norm = 1.304D-02 for orbital 4a \n Delta Eig. = 0.3810747590 eV \n \n current damping : 0.300\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 5 -75.955289006509 -122.34196630 37.585215233 0.766D-02 0.559D-11\n Norm of current diis error: 0.14905E-01\n max. resid. norm for Fia-block= 6.019D-03 for orbital 4a \n max. resid. fock norm = 6.167D-03 for orbital 4a \n mo-orthogonalization: Cholesky decomposition\n Delta Eig. = 0.0719638390 eV \n \n current damping : 0.350\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 6 -75.955355674500 -122.33969472 37.582876992 0.103D-02 0.515D-11\n Norm of current diis error: 0.57821E-02\n max. resid. norm for Fia-block= 2.570D-03 for orbital 4a \n max. resid. fock norm = 2.635D-03 for orbital 4a \n Delta Eig. = 0.0172270810 eV \n \n current damping : 0.400\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 7 -75.955367047288 -122.33983310 37.583003997 0.142D-03 0.479D-11\n Norm of current diis error: 0.23805E-02\n max. resid. norm for Fia-block= 1.151D-03 for orbital 4a \n max. resid. fock norm = 1.174D-03 for orbital 4a \n Delta Eig. = 0.0054038082 eV \n \n current damping : 0.450\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 8 -75.955369100318 -122.33943730 37.582606142 0.188D-04 0.449D-11\n Norm of current diis error: 0.10017E-02\n max. resid. norm for Fia-block= 5.006D-04 for orbital 4a \n max. resid. fock norm = 5.110D-04 for orbital 4a \n Delta Eig. = 0.0028635244 eV \n \n current damping : 0.500\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 9 -75.955369471234 -122.33947599 37.582644467 0.718D-05 0.424D-11\n Norm of current diis error: 0.41334E-03\n max. resid. norm for Fia-block= 2.170D-04 for orbital 4a \n max. resid. fock norm = 2.202D-04 for orbital 4a \n Delta Eig. = 0.0011916788 eV \n \n current damping : 0.550\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 10 -75.955369532338 -122.33935554 37.582523947 0.125D-05 0.404D-11\n Norm of current diis error: 0.16682E-03\n max. resid. norm for Fia-block= 8.490D-05 for orbital 4a \n max. resid. fock norm = 8.646D-05 for orbital 4a \n mo-orthogonalization: Cholesky decomposition\n Delta Eig. = 0.0004737614 eV \n \n current damping : 0.600\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 11 -75.955369541949 -122.33936379 37.582532192 0.277D-06 0.389D-11\n Norm of current diis error: 0.68636E-04\n max. resid. norm for Fia-block= 3.492D-05 for orbital 4a \n max. resid. fock norm = 3.538D-05 for orbital 4a \n Delta Eig. = 0.0001759275 eV \n \n current damping : 0.650\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 12 -75.955369543455 -122.33935314 37.582521542 0.320D-06 0.380D-11\n Norm of current diis error: 0.28699E-04\n max. resid. norm for Fia-block= 1.386D-05 for orbital 4a \n max. resid. fock norm = 1.403D-05 for orbital 4a \n Delta Eig. = 0.0000713983 eV \n\n ENERGY CONVERGED !\n\n \n current damping : 0.500\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL\n 13 -75.955369543698 -122.33935200 37.582520398 0.146D-05 0.380D-11\n max. resid. norm for Fia-block= 3.062D-06 for orbital 4a \n max. resid. fock norm = 3.077D-06 for orbital 4a \n\n convergence criteria satisfied after 13 iterations\n\n\n *************************************************************************\n QCEngine Turbomole \n *************************************************************************\n\n\n ------------------------------------------ \n | total energy = -75.95536954370 |\n ------------------------------------------ \n : kinetic energy = 75.63206605914 :\n : potential energy = -151.58743560284 :\n : virial theorem = 1.99574350719 :\n : wavefunction norm = 1.00000000000 :\n .......................................... \n\n\n <geterg> : there is no data group $energy \n\n\n <skperg> : $end is missing \n\n\n orbitals $scfmo will be written to file mos\n\n irrep 1a 2a 3a 4a 5a \n eigenvalues H -20.55250 -1.29794 -0.68127 -0.56035 -0.49510\n eV -559.2664 -35.3190 -18.5384 -15.2480 -13.4725\n occupation 2.0000 2.0000 2.0000 2.0000 2.0000 \n\n irrep 6a 7a 8a 9a 10a \n eigenvalues H 0.16932 0.24840 0.76485 0.82701 1.18642\n eV 4.6076 6.7593 20.8129 22.5044 32.2845\n \n \n \n \n ==============================================================================\n electrostatic moments\n ==============================================================================\n\n reference point for electrostatic moments: 0.00000 0.00000 0.00000\n\n \n nuc elec -> total\n ------------------------------------------------------------------------------\n charge \n ------------------------------------------------------------------------------\n 10.000000 -10.000000 -0.000000\n \n ------------------------------------------------------------------------------\n dipole moment \n ------------------------------------------------------------------------------\n x 0.000000 0.000000 0.000000\n y 0.000000 -0.000000 -0.000000\n z 1.019078 -0.162829 0.856249\n \n | dipole moment | = 0.8562 a.u. = 2.1764 debye \n \n ------------------------------------------------------------------------------\n quadrupole moment\n ------------------------------------------------------------------------------\n xx 0.000000 -5.305745 -5.305745\n yy 4.465192 -7.479074 -3.013882\n zz 2.245407 -6.491368 -4.245961\n xy 0.000000 0.000000 0.000000\n xz 0.000000 0.000000 0.000000\n yz 0.000000 -0.000000 -0.000000\n \n 1/3 trace= -4.188529\n anisotropy= 1.986680\n \n ==============================================================================\n \n\n\n ------------------------------------------------------------------------\n total cpu-time : 0.11 seconds\n total wall-time : 0.06 seconds\n ------------------------------------------------------------------------\n\n **** dscf : all done ****\n\n\n 2019-10-29 09:56:50.522 \n\n", + "stderr": null, + "success": true, + "error": null +} \ No newline at end of file diff --git a/iodata/test/data/turbomole_water_gradient_rimp2_output.json b/iodata/test/data/turbomole_water_gradient_rimp2_output.json new file mode 100644 index 000000000..5dfd9f064 --- /dev/null +++ b/iodata/test/data/turbomole_water_gradient_rimp2_output.json @@ -0,0 +1,134 @@ +{ + "id": null, + "schema_name": "qcschema_output", + "schema_version": 1, + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2, + "validated": true, + "symbols": [ + "O", + "H", + "H" + ], + "geometry": [ + 0.0, + 0.0, + -0.12947694, + 0.0, + -1.49418734, + 1.02744651, + 0.0, + 1.49418734, + 1.02744651 + ], + "name": "H2O", + "identifiers": null, + "comment": null, + "molecular_charge": 0.0, + "molecular_multiplicity": 1, + "masses": [ + 15.99491461957, + 1.00782503223, + 1.00782503223 + ], + "real": [ + true, + true, + true + ], + "atom_labels": [ + "", + "", + "" + ], + "atomic_numbers": [ + 8, + 1, + 1 + ], + "mass_numbers": [ + 16, + 1, + 1 + ], + "connectivity": null, + "fragments": [ + [ + 0, + 1, + 2 + ] + ], + "fragment_charges": [ + 0.0 + ], + "fragment_multiplicities": [ + 1 + ], + "fix_com": false, + "fix_orientation": false, + "fix_symmetry": null, + "provenance": { + "creator": "QCElemental", + "version": "v0.8.0+11.g7c6a220", + "routine": "qcelemental.molparse.from_schema" + }, + "id": null, + "extras": null + }, + "driver": "gradient", + "model": { + "method": "rimp2", + "basis": "def2-SVP" + }, + "keywords": {}, + "extras": { + "outfiles": { + "control": "$title\nQCEngine Turbomole\n$operating system unix\n$symmetry c1\n$user-defined bonds file=coord\n$coord file=coord\n$optimize\n internal off\n redundant off\n cartesian on\n global off\n basis off\n$atoms\no 1 \\\n basis =o def2-SVP \\\n cbas =o def2-SVP\nh 2-3 \\\n basis =h def2-SVP \\\n cbas =h def2-SVP\n$basis file=basis\n$rundimensions\n dim(fock,dens)=352\n natoms=3\n nshell=12\n nbf(CAO)=25\n dim(trafo[SAO<-->AO/CAO])=27\n rhfshells=1\n nbf(AO)=24\n$scfmo file=mos\n$closed shells\n a 1-5 ( 2 )\n$scfiterlimit 150\n$scfconv 8\n$thize 0.10000000E-04\n$thime 5\n$scfdamp start=0.300 step=0.050 min=0.100\n$scfdump\n$scfintunit\n unit=30 size=0 file=twoint\n$scfdiis\n$maxcor 500 MiB per_core\n$scforbitalshift automatic=.1\n$drvopt\n cartesian on\n basis off\n global off\n hessian on\n dipole on\n nuclear polarizability\n$interconversion off\n qconv=1.d-7\n maxiter=25\n$coordinateupdate\n dqmax=0.3\n interpolate on\n statistics 5\n$forceupdate\n ahlrichs numgeo=0 mingeo=3 maxgeo=4 modus=<g|dq> dynamic fail=0.3\n threig=0.005 reseig=0.005 thrbig=3.0 scale=1.00 damping=0.0\n$forceinit on\n diag=default\n$energy file=energy\n$grad file=gradient\n$forceapprox file=forceapprox\n$denconv 0.10000000E-06\n$freeze\n implicit core= 1 virt= 0\n$cbas file=auxbasis\n$ricc2\n mp2\n geoopt model=mp2 state=(x)\n$last step ricc2\n$orbital_max_rnorm 0.30769401400734E-05\n$last SCF energy change = -75.955370\n$charge from dscf\n -0.000 (not to be modified here)\n$dipole from ricc2\n x 0.00000000000001 y -0.00000000000003 z 0.81508211917860 a.u.\n | dipole | = 2.0717477569 debye\n$last MP2 energy change= -.20399186\n$end\n", + "gradient": "$grad cartesian gradients\n cycle = 1 MP2 energy = -76.1593614075 |dE/dxyz| = 0.061576\n 0.00000000000000 0.00000000000000 -0.12947694000000 o\n 0.00000000000000 -1.49418734000000 1.02744651000000 h\n 0.00000000000000 1.49418734000000 1.02744651000000 h\n 0.77638926688872D-13 0.00000000000000D+00 -.35034209636233D-01\n -.38704950363896D-13 -.31228670589283D-01 0.17517104810727D-01\n -.38933976324976D-13 0.31228670589290D-01 0.17517104810736D-01\n$end\n", + "stderr": " dscf ended normally\n ricc2 ended normally\n" + }, + "qcvars": { + "HF TOTAL ENERGY": "-75.95536954370", + "CURRENT ENERGY": "-76.159361407452", + "CURRENT GRADIENT": [ + 7.7638926688872e-14, + 0.0, + -0.035034209636233, + -3.8704950363896e-14, + -0.031228670589283, + 0.017517104810727, + -3.8933976324976e-14, + 0.03122867058929, + 0.017517104810736 + ] + } + }, + "provenance": { + "creator": "Turbomole", + "version": "7.3", + "routine": "turbomole", + "username": "johannes", + "cpu": "Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz", + "wall_time": 0.3380742073059082, + "qcengine_version": "v0.10.0+80.gd0ddc48.dirty", + "hostname": "southgeorgia" + }, + "properties": {}, + "return_result": [ + 7.7638926688872e-14, + 0.0, + -0.035034209636233, + -3.8704950363896e-14, + -0.031228670589283, + 0.017517104810727, + -3.8933976324976e-14, + 0.03122867058929, + 0.017517104810736 + ], + "stdout": "\n OpenMP run-time library returned nthreads = 2\n operating system is UNIX !\n\n dscf (southgeorgia) : TURBOMOLE V7.3 ( 22142 ) 4 Jul 2018 at 18:12:42\n Copyright (C) 2018 TURBOMOLE GmbH, Karlsruhe\n\n\n 2019-10-29 09:56:52.630 \n\n\n\n d s c f - program\n\n idea & directorship : reinhart ahlrichs\n program development : marco haeser\n michael baer\n dft version : oliver treutler\n\n\n quantum chemistry group\n universitaet karlsruhe\n germany\n\n\n\n\n References \n \n TURBOMOLE: \n R. Ahlrichs, M. Baer, M. Haeser, H. Horn, and\n C. Koelmel\n Electronic structure calculations on workstation\n computers: the program system TURBOMOLE\n Chem. Phys. Lett. 162: 165 (1989)\n Density Functional: \n O. Treutler and R. Ahlrichs \n Efficient Molecular Numerical Integration Schemes\n J. Chem. Phys. 102: 346 (1995) \n Parallel Version: \n Performance of parallel TURBOMOLE for Density \n Functional Calculations \n M. v. Arnim and R. Ahlrichs \n J. Comp. Chem. 19: 1746 (1998) \n \n\n\n\n\n +--------------------------------------------------+\n | Atomic coordinate, charge and isotop information |\n +--------------------------------------------------+\n\n atomic coordinates atom charge isotop\n 0.00000000 0.00000000 -0.12947694 o 8.000 0\n 0.00000000 -1.49418734 1.02744651 h 1.000 0\n 0.00000000 1.49418734 1.02744651 h 1.000 0\n \n center of nuclear mass : 0.00000000 0.00000000 -0.00001570\n center of nuclear charge: 0.00000000 0.00000000 0.10190775\n\n *************************************************************************\n QCEngine Turbomole \n *************************************************************************\n\n\n\n +--------------------------------------------------+\n | basis set information |\n +--------------------------------------------------+\n\n we will work with the 1s 3p 5d 7f 9g ... basis set\n ...i.e. with spherical basis functions...\n\n type atoms prim cont basis\n ---------------------------------------------------------------------------\n o 1 24 14 def2-SVP [3s2p1d|7s4p1d]\n h 2 7 5 def2-SVP [2s1p|4s1p]\n ---------------------------------------------------------------------------\n total: 3 38 24\n ---------------------------------------------------------------------------\n\n total number of primitive shells : 17\n total number of contracted shells : 12\n total number of cartesian basis functions : 25\n total number of SCF-basis functions : 24\n\n\n integral neglect threshold : 0.13E-10\n integral storage threshold THIZE : 0.10E-04\n integral storage threshold THIME : 5\n\n\n DENSITY CONVERGENCE CHECK SWITCHED ON !\n SCF CONVERGENCE IF RMS(delta[D]) < 0.1000000000E-06\n\n\n symmetry group of the molecule : c1 \n\n the group has the following generators :\n c1(z)\n\n 1 symmetry operations found\n\n there are 1 real representations : a \n\n maximum number of shells which are related by symmetry : 1\n\n\n mo occupation :\n irrep mo's occupied\n a 24 5\n \n number of basis functions : 24\n number of occupied orbitals : 5\n \n\n automatic virtual orbital shift switched on \n shift if e(lumo)-e(homo) < 0.10000000 \n\n\n ------------------------\n nuclear repulsion energy : 8.80146205625 \n ------------------------\n\n\n -----------------\n -S,T+V- integrals\n -----------------\n\n 1e-integrals will be neglected if expon. factor < 0.133678E-11\n \n Difference densities algorithm switched on.\n The maximal number of linear combinations of\n difference densities is 20 .\n\n DIIS switched on: error vector is FDS-SDF\n Max. Iterations for DIIS is : 5\n DIIS matrix (see manual) \n Scaling factor of diagonals : 1.200\n threshold for scaling factor : 0.000\n\n scf convergence criterion : increment of total energy < .1000000D-07\n and increment of one-electron energy < .1000000D-04\n\n MOs are in ASCII format !\n\n\n reading orbital data $scfmo from file mos\n orbital characterization : expanded\n mo provided and orthogonalized by Cholesky decomposition\n\n DSCF restart information will be dumped onto file mos\n\n \n current damping : 0.300\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 1 -75.741527779117 -123.80966488 39.266675041 0.000D+00 0.133D-10\n max. resid. norm for Fia-block= 2.815D-01 for orbital 4a \n max. resid. fock norm = 1.548D+00 for orbital 10a \n Delta Eig. = 47.9536513414 eV \n \n current damping : 0.250\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 2 -75.933097028971 -122.13127215 37.396713062 0.274D-01 0.885D-11\n Norm of current diis error: 0.34565 \n max. resid. norm for Fia-block= 9.171D-02 for orbital 1a \n max. resid. fock norm = 1.102D-01 for orbital 18a \n Delta Eig. = 9.1913160300 eV \n \n current damping : 0.200\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 3 -75.951594723455 -122.46272320 37.709666417 0.102D-01 0.698D-11\n Norm of current diis error: 0.12813 \n max. resid. norm for Fia-block= 3.429D-02 for orbital 4a \n max. resid. fock norm = 3.703D-02 for orbital 9a \n Delta Eig. = 2.6966294125 eV \n \n current damping : 0.250\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 4 -75.954873573064 -122.31745771 37.561122081 0.445D-02 0.611D-11\n Norm of current diis error: 0.40537E-01\n max. resid. norm for Fia-block= 1.251D-02 for orbital 4a \n max. resid. fock norm = 1.304D-02 for orbital 4a \n Delta Eig. = 0.3810747590 eV \n \n current damping : 0.300\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 5 -75.955289006509 -122.34196630 37.585215233 0.157D-02 0.559D-11\n Norm of current diis error: 0.14905E-01\n max. resid. norm for Fia-block= 6.019D-03 for orbital 4a \n max. resid. fock norm = 6.167D-03 for orbital 4a \n mo-orthogonalization: Cholesky decomposition\n Delta Eig. = 0.0719638390 eV \n \n current damping : 0.350\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 6 -75.955355674500 -122.33969472 37.582876992 0.647D-03 0.515D-11\n Norm of current diis error: 0.57821E-02\n max. resid. norm for Fia-block= 2.570D-03 for orbital 4a \n max. resid. fock norm = 2.635D-03 for orbital 4a \n Delta Eig. = 0.0172270810 eV \n \n current damping : 0.400\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 7 -75.955367047288 -122.33983310 37.583003997 0.267D-03 0.479D-11\n Norm of current diis error: 0.23805E-02\n max. resid. norm for Fia-block= 1.151D-03 for orbital 4a \n max. resid. fock norm = 1.174D-03 for orbital 4a \n Delta Eig. = 0.0054038082 eV \n \n current damping : 0.450\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 8 -75.955369100318 -122.33943730 37.582606142 0.114D-03 0.449D-11\n Norm of current diis error: 0.10017E-02\n max. resid. norm for Fia-block= 5.006D-04 for orbital 4a \n max. resid. fock norm = 5.110D-04 for orbital 4a \n Delta Eig. = 0.0028635244 eV \n \n current damping : 0.500\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 9 -75.955369471234 -122.33947599 37.582644467 0.487D-04 0.424D-11\n Norm of current diis error: 0.41334E-03\n max. resid. norm for Fia-block= 2.170D-04 for orbital 4a \n max. resid. fock norm = 2.202D-04 for orbital 4a \n Delta Eig. = 0.0011916788 eV \n \n current damping : 0.550\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 10 -75.955369532338 -122.33935554 37.582523947 0.198D-04 0.404D-11\n Norm of current diis error: 0.16682E-03\n max. resid. norm for Fia-block= 8.490D-05 for orbital 4a \n max. resid. fock norm = 8.646D-05 for orbital 4a \n mo-orthogonalization: Cholesky decomposition\n Delta Eig. = 0.0004737614 eV \n \n current damping : 0.600\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 11 -75.955369541949 -122.33936379 37.582532192 0.768D-05 0.389D-11\n Norm of current diis error: 0.68636E-04\n max. resid. norm for Fia-block= 3.492D-05 for orbital 4a \n max. resid. fock norm = 3.538D-05 for orbital 4a \n Delta Eig. = 0.0001759275 eV \n \n current damping : 0.650\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 12 -75.955369543464 -122.33935314 37.582521542 0.299D-05 0.377D-11\n Norm of current diis error: 0.28699E-04\n max. resid. norm for Fia-block= 1.386D-05 for orbital 4a \n max. resid. fock norm = 1.403D-05 for orbital 4a \n Delta Eig. = 0.0000713983 eV \n\n ENERGY & DENSITY CONVERGED !\n\n \n current damping : 0.500\n ITERATION ENERGY 1e-ENERGY 2e-ENERGY RMS[dD(SAO)] TOL\n 13 -75.955369543698 -122.33935200 37.582520398 0.116D-05 0.380D-11\n max. resid. norm for Fia-block= 3.062D-06 for orbital 4a \n max. resid. fock norm = 3.077D-06 for orbital 4a \n\n convergence criteria satisfied after 13 iterations\n\n\n *************************************************************************\n QCEngine Turbomole \n *************************************************************************\n\n\n ------------------------------------------ \n | total energy = -75.95536954370 |\n ------------------------------------------ \n : kinetic energy = 75.63206605915 :\n : potential energy = -151.58743560285 :\n : virial theorem = 1.99574350719 :\n : wavefunction norm = 1.00000000000 :\n .......................................... \n\n\n <geterg> : there is no data group $energy \n\n\n <skperg> : $end is missing \n\n\n orbitals $scfmo will be written to file mos\n\n irrep 1a 2a 3a 4a 5a \n eigenvalues H -20.55250 -1.29794 -0.68127 -0.56035 -0.49510\n eV -559.2664 -35.3190 -18.5384 -15.2480 -13.4725\n occupation 2.0000 2.0000 2.0000 2.0000 2.0000 \n\n irrep 6a 7a 8a 9a 10a \n eigenvalues H 0.16932 0.24840 0.76485 0.82701 1.18642\n eV 4.6076 6.7593 20.8129 22.5044 32.2845\n \n \n \n \n ==============================================================================\n electrostatic moments\n ==============================================================================\n\n reference point for electrostatic moments: 0.00000 0.00000 0.00000\n\n \n nuc elec -> total\n ------------------------------------------------------------------------------\n charge \n ------------------------------------------------------------------------------\n 10.000000 -10.000000 -0.000000\n \n ------------------------------------------------------------------------------\n dipole moment \n ------------------------------------------------------------------------------\n x 0.000000 0.000000 0.000000\n y 0.000000 0.000000 0.000000\n z 1.019078 -0.162829 0.856249\n \n | dipole moment | = 0.8562 a.u. = 2.1764 debye \n \n ------------------------------------------------------------------------------\n quadrupole moment\n ------------------------------------------------------------------------------\n xx 0.000000 -5.305745 -5.305745\n yy 4.465192 -7.479074 -3.013882\n zz 2.245407 -6.491368 -4.245961\n xy 0.000000 0.000000 0.000000\n xz 0.000000 0.000000 0.000000\n yz 0.000000 -0.000000 -0.000000\n \n 1/3 trace= -4.188529\n anisotropy= 1.986680\n \n ==============================================================================\n \n\n\n ------------------------------------------------------------------------\n total cpu-time : 0.11 seconds\n total wall-time : 0.07 seconds\n ------------------------------------------------------------------------\n\n **** dscf : all done ****\n\n\n 2019-10-29 09:56:52.696 \n\n\n OpenMP run-time library returned nthreads = 2\n\n ricc2 (southgeorgia) : TURBOMOLE V7.3 ( 22142 ) 4 Jul 2018 at 18:12:42\n Copyright (C) 2018 TURBOMOLE GmbH, Karlsruhe\n\n\n 2019-10-29 09:56:52.765 \n\n\n\n R I C C 2 - PROGRAM\n\n the quantum chemistry groups\n at the universities in\n Karlsruhe, Bochum & Mainz\n Germany\n\n\n\n *-----------------------------------------------------------------------*\n | program will use 2 thread(s) |\n *-----------------------------------------------------------------------*\n\n\n +--------------------------------------------------+\n | Atomic coordinate, charge and isotop information |\n +--------------------------------------------------+\n\n atomic coordinates atom charge isotop\n 0.00000000 0.00000000 -0.12947694 o 8.000 0\n 0.00000000 -1.49418734 1.02744651 h 1.000 0\n 0.00000000 1.49418734 1.02744651 h 1.000 0\n \n center of nuclear mass : 0.00000000 0.00000000 -0.00001570\n center of nuclear charge: 0.00000000 0.00000000 0.10190775\n\n *************************************************************************\n QCEngine Turbomole \n *************************************************************************\n\n\n\n +--------------------------------------------------+\n | basis set information |\n +--------------------------------------------------+\n\n we will work with the 1s 3p 5d 7f 9g ... basis set\n ...i.e. with spherical basis functions...\n\n type atoms prim cont basis\n ---------------------------------------------------------------------------\n o 1 24 14 def2-SVP [3s2p1d|7s4p1d]\n h 2 7 5 def2-SVP [2s1p|4s1p]\n ---------------------------------------------------------------------------\n total: 3 38 24\n ---------------------------------------------------------------------------\n\n total number of primitive shells : 17\n total number of contracted shells : 12\n total number of cartesian basis functions : 25\n total number of SCF-basis functions : 24\n\n\n symmetry group of the molecule : c1 \n\n the group has the following generators :\n c1(z)\n\n 1 symmetry operations found\n\n there are 1 real representations : a \n\n\n =========================================================================\n\n\n restricted closed shell calculation for the wavefunction models:\n MP2 - Second Order Moeller Plesset PT\n\n\n global parameters for ricc2 program:\n\n hard restart (reuse of interm.) : disabled\n soft restart (reuse of vectors) : disabled\n threshold for vector function : 0.100000E-05\n convergence threshold energy : 0.100000E-06\n linear dependence threshold : 0.100000E-13\n global print level : 1\n maximum number of iterations : 150\n maximum number DIIS vectors : 10\n max. dim. of reduced space : 100\n core memory limit (MB) : 1000\n\n\n this is a ground state geometry optimization run at the MP2 level\n\n Scratch Directory : \n\n\n =========================================================================\n\n der. integral neglect threshold : 0.10E-07\n integral neglect threshold : 0.13E-09\n integral storage threshold THIZE : 0.10E-04\n integral storage threshold THIME : 5\n\n\n +------------------------------------------+\n | Auxiliary basis set information |\n +------------------------------------------+\n\n we will work with the 1s 3p 5d 7f 9g ... basis set\n ...i.e. with spherical basis functions...\n\n type atoms prim cont basis\n ---------------------------------------------------------------------------\n o 1 72 48 def2-SVP [6s5p4d1f|8s6p5d3f]\n h 2 23 14 def2-SVP [3s2p1d|4s3p2d]\n ---------------------------------------------------------------------------\n total: 3 118 76\n ---------------------------------------------------------------------------\n\n total number of primitive shells : 31\n total number of contracted shells : 28\n total number of cartesian basis functions : 85\n total number of SCF-basis functions : 76\n\n\n maximum number of shells which are related by symmetry : 1\n\n\n The symmetry information takes 1 MB\n\n*---------------------------------------------------------------------*\n| simplified C1 algorithm will be applied |\n*---------------------------------------------------------------------*\n MOs are in ASCII format !\n\n\n reading orbital data $scfmo from file mos\n orbital characterization : scfconv=8\n time elapsed for calculating density matrices : 0.000 sec\n\n\n\n +--------------------------------------------+\n | list of orbitals that will be kept frozen: |\n +--------------------------------------------+\n | irrep index energy occ/vir |\n +--------------------------------------------+\n | a 1 -20.5525 occupied |\n +--------------------------------------------+\n\n\n\n Molecular Orbital Statistic:\n ============================\n\n -----------------------------\n orbitals in total:\n -----------------------------\n frozen occupied : 1\n active occupied : 4\n active virtual : 19\n frozen virtual : 0\n all together : 24\n -----------------------------\n\n\n time in riccmos cpu: 0.00 sec wall: 0.00 sec ratio: 1.6\n\n total memory allocated for calculation of (Q|P)**(-1/2) : 1 MiB\n\n\n calculation of (P|Q) ...\n time in lp2sym cpu: 0.00 sec wall: 0.00 sec ratio: 0.7\n\n\n calculation of the Cholesky decomposition of (P|Q)**(-1) ...\n time in invmetri cpu: 0.00 sec wall: 0.00 sec ratio: 2.3\n\n threshold for RMS(d[D]) in SCF was : 0.10E-06\n integral neglect threshold : 0.13E-09\n derivative integral neglect threshold : 0.10E-07\n\n\n setting up bound for integral derivative estimation\n\n increment for numerical differentiation : 0.00050000\n\n =========================================================================\n\n Energy of reference wave function is -75.9553695437000\n Maximum orbital residual is 0.3076940140073E-05\n\n\n Number of symmetry-nonredundant auxiliary basis functions: 76\n\n Block lengths for integral files:\n frozen occupied (BOI): 1 MiB\n active occupied (BJI): 1 MiB\n active virtual (BAI): 1 MiB\n frozen virtual (BGI): 0 MiB\n general (BTI): 1 MiB\n\n =========================================================================\n\n\n\n A SCF based gradient calculation with 4-index ERIs will be done.\n Solution of CPHF equation will be preoptimized using RI-CPHF.\n A \"cbas\" type auxiliary basis is used for RI-SCF and/or RI-CPHF.\n\n The semi-canonical algorithm will be used for densities\n\n\n ======== CC DENSITY MODULE ========\n\n current wave-function model: MP2 \n\n calculating CC ground state density\n\n a semicanonical algorithm will be used when possible\n\n density nr. cpu/min wall/min L R\n ------------------------------------------------------\n 1 0.00 0.00 L0 R0 \n ------------------------------------------------------\n time in cc_1den cpu: 0.01 sec wall: 0.00 sec ratio: 1.7\n\n reading orbital data $scfmo from file mos\n orbital characterization : scfconv=8\n\n EMP2 : -76.159361407452\n EMP2 from traces: -76.159361407452\n Delta : 0.000000000000\n\n --------------------------------------------------------------------------\n\n Solving 4-Index ERI based CPHF equations using a RI-CPHF\n preoptimization with a \"cbas\" type auxiliary basis\n\n\n ======== LINEAR CC RESPONSE SOLVER ========\n\n threshold for convergence: 0.32E-02\n maximum number of simultaneous jacoby matrix transformations: 1\n \n summary of start vectors generation:\n -------------------------------------------\n type of solution vectors : l0o\n symmetry : a \n number of vectors requested : 1\n number of vectors generated : 1\n -------------------------------------------\n \n\n Iter #Vectors time (min) max. residual \n --------------------------------------------\n 1 1 0.00 0.76E-01 ( 1)\n 2 1 0.00 0.25E-01 ( 1)\n 3 1 0.00 0.86E-02 ( 1)\n 4 1 0.00 0.19E-02 ( 1)\n --------------------------------------------\n converged in 4 iterations\n\n Total time 0.00 minutes\n\n\n ===========================================\n\n\n\n\n ======== LINEAR CC RESPONSE SOLVER ========\n\n threshold for convergence: 0.10E-04\n maximum number of simultaneous jacoby matrix transformations: 1\n \n summary of start vectors generation:\n -------------------------------------------\n type of solution vectors : l0o\n symmetry : a \n number of vectors requested : 1\n number of vectors generated : 1\n -------------------------------------------\n \n\n Iter #Vectors time (min) max. residual \n --------------------------------------------\n 1 1 0.00 0.19E-02 ( 1)\n 2 1 0.00 0.56E-03 ( 1)\n 3 1 0.00 0.16E-03 ( 1)\n 4 1 0.00 0.46E-04 ( 1)\n 5 1 0.00 0.81E-05 ( 1)\n --------------------------------------------\n converged in 5 iterations\n\n Total time 0.00 minutes\n\n\n ===========================================\n\n\n --------------------------------------------------------------------------\n\n 1e-integral 1st. derivatives will be neglected if expon. factor < 0.100000E-11\n\n \n\n +--------------------------------------------------------------------+\n | MP2 unrelaxed natural orbital occupation numbers |\n +--------------------------------------------------------------------+\n | natural orb. | occupation numbers |\n +---------------+----------------------------------------------------+\n | a | |\n | 1 - 5 | 2.0000 1.9862 1.9737 1.9687 1.9665 |\n | 6 - 10 | 0.0252 0.0230 0.0178 0.0110 0.0054 |\n | 11 - 15 | 0.0051 0.0045 0.0040 0.0038 0.0010 |\n +---------------+----------------------------------------------------+\n natural orbitals with occ < 0.10E-02 are not shown\n\n Maximum change in occupation number:\n occupied : -1.68 % ( 5 a )\n virtual : 1.26 % ( 6 a )\n \n\n +--------------------------------------------------------------------+\n | MP2 relaxed natural orbital occupation numbers |\n +--------------------------------------------------------------------+\n | natural orb. | occupation numbers |\n +---------------+----------------------------------------------------+\n | a | |\n | 1 - 5 | 2.0000 1.9862 1.9738 1.9690 1.9667 |\n | 6 - 10 | 0.0252 0.0229 0.0178 0.0110 0.0054 |\n | 11 - 14 | 0.0051 0.0045 0.0040 0.0038 |\n +---------------+----------------------------------------------------+\n natural orbitals with occ < 0.10E-02 are not shown\n\n Maximum change in occupation number:\n occupied : -1.66 % ( 5 a )\n virtual : 1.26 % ( 6 a )\n\n\n **************************************************************\n * *\n *<<<<<<<<<< GROUND STATE FIRST-ORDER PROPERTIES >>>>>>>>>>>*\n * *\n **************************************************************\n\n\n ------------------------------------------------\n Method : MP2 \n Total Energy : -76.1593614075\n ------------------------------------------------\n\n\n ------------------------------------------------\n moments of inertia \n 2.396 4.501 6.897 in a.u.\n\n rotational constants \n 25.12153 13.37539 8.72824 in cm**(-1)\n ------------------------------------------------\n\n\n NOTE: unrelaxed properties printed below refer to the MP1 wavefunction and\n are only correct through first order in the fluctuation potential!\n\n +===========================================================================+\n | OPERATOR | EXPECTAT. VALUE | ELECTRONIC CONT. | NUCLEAR CONTRIB. |\n +==================+==================+==================+==================+\n | | | | |\n | xdiplen (unrel) | 0.00000000 | 0.00000000 | 0.00000000 | \n | xdiplen (relax) | 0.00000000 | 0.00000000 | 0.00000000 | \n | | | | |\n | ydiplen (unrel) | -0.00000000 | -0.00000000 | 0.00000000 | \n | ydiplen (relax) | -0.00000000 | -0.00000000 | 0.00000000 | \n | | | | |\n | zdiplen (unrel) | 0.84597301 | -0.17310449 | 1.01907750 | \n | zdiplen (relax) | 0.81508212 | -0.20399538 | 1.01907750 | \n | | | | |\n | xxqudlen (unrel) | -5.33021521 | -5.33021521 | 0.00000000 | \n | xxqudlen (relax) | -5.34598293 | -5.34598293 | 0.00000000 | \n | | | | |\n | xyqudlen (unrel) | 0.00000000 | 0.00000000 | 0.00000000 | \n | xyqudlen (relax) | 0.00000000 | 0.00000000 | 0.00000000 | \n | | | | |\n | xzqudlen (unrel) | 0.00000000 | 0.00000000 | 0.00000000 | \n | xzqudlen (relax) | 0.00000000 | 0.00000000 | 0.00000000 | \n | | | | |\n | yyqudlen (unrel) | -3.06374549 | -7.52893711 | 4.46519161 | \n | yyqudlen (relax) | -3.13716994 | -7.60236156 | 4.46519161 | \n | | | | |\n | yzqudlen (unrel) | -0.00000000 | -0.00000000 | 0.00000000 | \n | yzqudlen (relax) | -0.00000000 | -0.00000000 | 0.00000000 | \n | | | | |\n | zzqudlen (unrel) | -4.28312315 | -6.52853004 | 2.24540689 | \n | zzqudlen (relax) | -4.32737024 | -6.57277712 | 2.24540689 | \n | | | | |\n +===========================================================================+\n\n\n Analysis of relaxed properties:\n ===============================\n\n\n dipole moment:\n --------------\n\n x 0.00000000\n y -0.00000000\n z 0.81508212\n\n | dipole moment | = 0.81508212 a.u. = 2.07173208 debye\n\n\n traceless quadrupole tensor:\n ----------------------------\n\n x y z\n\n x -1.61371284 0.00000000 0.00000000\n y 1.69950664 -0.00000000\n z -0.08579380\n\n\n principal axes of tensor: \n\n x' = ( 1.0000000, 0.0000000, 0.0000000 )\n y' = ( 0.0000000, 1.0000000, -0.0000000 )\n z' = ( 0.0000000, 0.0000000, 1.0000000 )\n\n < Q(x'x') > = -1.61371284 a.u.\n < Q(y'y') > = 1.69950664 a.u.\n < Q(z'z') > = -0.08579380 a.u.\n\n\n second moment of electron density:\n ----------------------------------\n\n x y z\n\n x 5.34598293 -0.00000000 -0.00000000\n y 7.60236156 0.00000000\n z 6.57277712\n\n\n principal axes of tensor: \n\n x' = ( 1.0000000, 0.0000000, 0.0000000 )\n y' = ( 0.0000000, 1.0000000, 0.0000000 )\n z' = ( 0.0000000, 0.0000000, 1.0000000 )\n\n < x'x'> = 5.34598293 a.u.\n < y'y'> = 7.60236156 a.u.\n < z'z'> = 6.57277712 a.u.\n\n Isotropic second moment: alpha = 6.50704053 a.u.\n\n Anisotropy of second moment: beta = 1.95656748 a.u.\n \n\n --------------------------------------------------------------------------\n\n\n Analysis of unrelaxed properties:\n =================================\n\n\n dipole moment:\n --------------\n\n x 0.00000000\n y -0.00000000\n z 0.84597301\n\n | dipole moment | = 0.84597301 a.u. = 2.15024890 debye\n\n\n traceless quadrupole tensor:\n ----------------------------\n\n x y z\n\n x -1.65678089 0.00000000 0.00000000\n y 1.74292369 -0.00000000\n z -0.08614280\n\n\n principal axes of tensor: \n\n x' = ( 1.0000000, 0.0000000, 0.0000000 )\n y' = ( 0.0000000, 1.0000000, -0.0000000 )\n z' = ( 0.0000000, 0.0000000, 1.0000000 )\n\n < Q(x'x') > = -1.65678089 a.u.\n < Q(y'y') > = 1.74292369 a.u.\n < Q(z'z') > = -0.08614280 a.u.\n\n\n second moment of electron density:\n ----------------------------------\n\n x y z\n\n x 5.33021521 -0.00000000 -0.00000000\n y 7.52893711 0.00000000\n z 6.52853004\n\n\n principal axes of tensor: \n\n x' = ( 1.0000000, 0.0000000, 0.0000000 )\n y' = ( 0.0000000, 1.0000000, 0.0000000 )\n z' = ( 0.0000000, 0.0000000, 1.0000000 )\n\n < x'x'> = 5.33021521 a.u.\n < y'y'> = 7.52893711 a.u.\n < z'z'> = 6.52853004 a.u.\n\n Isotropic second moment: alpha = 6.46256078 a.u.\n\n Anisotropy of second moment: beta = 1.90671848 a.u.\n \n\n ------------------------------\n total gradient of MP2 energy \n ------------------------------\n\n ------------------------------------------------\n cartesian gradient of the energy (hartree/bohr)\n ------------------------------------------------\n\n ATOM 1 o 2 h 3 h \ndE/dx 0.7763893D-13 -0.3870495D-13 -0.3893398D-13\ndE/dy 0.0000000D+00 -0.3122867D-01 0.3122867D-01\ndE/dz -0.3503421D-01 0.1751710D-01 0.1751710D-01\n \n resulting FORCE (fx,fy,fz) = (0.252D-28,0.755D-14,-.148D-10)\n resulting MOMENT (mx,my,mz) = (0.684D-14,-.898D-13,0.342D-15)\n\n\n **********************************************************************\n |maximum component of gradient| : 0.35034210E-01 (atom 1 o ) \n gradient norm : 0.61575592E-01\n **********************************************************************\n\n *** orbital-relaxed dipole moment written to <control> ***\n\n\n <getgrd> : data group $grad is missing \n\n *** cartesian gradients written onto <gradient> ***\n \n ==============================================================================\n \n Exporting ground state density\n\n\n ------------------------------------------------------------------------\n total cpu-time : 0.16 seconds\n total wall-time : 0.09 seconds\n ------------------------------------------------------------------------\n\n **** ricc2 : all done ****\n\n\n 2019-10-29 09:56:52.850 \n\n", + "stderr": null, + "success": true, + "error": null +} \ No newline at end of file diff --git a/iodata/test/data/water_atcharges.fchk b/iodata/test/data/water_atcharges.fchk new file mode 100644 index 000000000..fc7cdbba0 --- /dev/null +++ b/iodata/test/data/water_atcharges.fchk @@ -0,0 +1,1148 @@ +water atcharges +SP RHF 6-31G +Number of atoms I 3 +Info1-9 I N= 9 + 15 12 0 0 0 110 + 1 1 2 +Full Title C N= 2 +water atcharges +Route C N= 10 +#p hf/6-31G sp scf=(maxcycle=900,verytightlineq,xqc) integra +l=grid=ultrafine symmetry=None pop=(cm5,hlygat,mbs,npa) +Charge I 0 +Multiplicity I 1 +Number of electrons I 10 +Number of alpha electrons I 5 +Number of beta electrons I 5 +Number of basis functions I 13 +Number of independent functions I 13 +Number of point charges in /Mol/ I 0 +Number of translation vectors I 0 +Atomic numbers I N= 3 + 8 1 1 +Nuclear charges R N= 3 + 8.00000000E+00 1.00000000E+00 1.00000000E+00 +Current cartesian coordinates R N= 9 + -5.53874655E+00 -4.08958344E-01 0.00000000E+00 -6.90736427E+00 2.72294764E+00 + 0.00000000E+00 -2.14148544E+00 1.45385191E-01 0.00000000E+00 +Number of symbols in /Mol/ I 0 +Int Atom Types I N= 3 + 0 0 0 +Force Field I 0 +Atom Types C N= 3 + +MM charges R N= 3 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 +Integer atomic weights I N= 3 + 16 1 1 +Real atomic weights R N= 3 + 1.59949146E+01 1.00782504E+00 1.00782504E+00 +Atom fragment info I N= 3 + 0 0 0 +Atom residue num I N= 3 + 0 0 0 +Nuclear spins I N= 3 + 0 1 1 +Nuclear ZEff R N= 3 + -5.60000000E+00 -1.00000000E+00 -1.00000000E+00 +Nuclear ZNuc R N= 3 + 8.00000000E+00 1.00000000E+00 1.00000000E+00 +Nuclear QMom R N= 3 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 +Nuclear GFac R N= 3 + 0.00000000E+00 2.79284600E+00 2.79284600E+00 +MicOpt I N= 3 + -1 -1 -1 +Number of residues I 0 +Number of secondary structures I 0 +Number of contracted shells I 7 +Number of primitive shells I 18 +Pure/Cartesian d shells I 1 +Pure/Cartesian f shells I 0 +Highest angular momentum I 1 +Largest degree of contraction I 6 +Shell types I N= 7 + 0 -1 -1 0 0 0 + 0 +Number of primitives per shell I N= 7 + 6 3 1 3 1 3 + 1 +Shell to atom map I N= 7 + 1 1 1 2 2 3 + 3 +Primitive exponents R N= 18 + 5.48467166E+03 8.25234946E+02 1.88046958E+02 5.29645000E+01 1.68975704E+01 + 5.79963534E+00 1.55396162E+01 3.59993359E+00 1.01376175E+00 2.70005823E-01 + 1.87311370E+01 2.82539436E+00 6.40121692E-01 1.61277759E-01 1.87311370E+01 + 2.82539436E+00 6.40121692E-01 1.61277759E-01 +Contraction coefficients R N= 18 + 1.83107443E-03 1.39501722E-02 6.84450781E-02 2.32714336E-01 4.70192898E-01 + 3.58520853E-01 -1.10777550E-01 -1.48026263E-01 1.13076702E+00 1.00000000E+00 + 3.34946043E-02 2.34726953E-01 8.13757326E-01 1.00000000E+00 3.34946043E-02 + 2.34726953E-01 8.13757326E-01 1.00000000E+00 +P(S=P) Contraction coefficients R N= 18 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 7.08742682E-02 3.39752839E-01 7.27158577E-01 1.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 +Coordinates of each shell R N= 21 + -5.53874655E+00 -4.08958344E-01 0.00000000E+00 -5.53874655E+00 -4.08958344E-01 + 0.00000000E+00 -5.53874655E+00 -4.08958344E-01 0.00000000E+00 -6.90736427E+00 + 2.72294764E+00 0.00000000E+00 -6.90736427E+00 2.72294764E+00 0.00000000E+00 + -2.14148544E+00 1.45385191E-01 0.00000000E+00 -2.14148544E+00 1.45385191E-01 + 0.00000000E+00 +Num ILSW I 100 +ILSW I N= 100 + 0 1 0 0 2 0 + 0 0 0 0 0 -1 + 5 0 0 0 0 0 + 0 0 0 0 0 0 + 1 1 0 0 0 0 + 0 0 100000 0 -1 0 + 0 0 0 0 0 0 + 0 0 0 1 0 0 + 0 -2000000000 1 0 0 0 + 0 0 4 52 0 0 + 0 0 0 0 0 0 + 0 0 0 3 0 1 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 +Num RLSW I 52 +RLSW R N= 52 + 1.00000000E+00 1.00000000E+00 1.00000000E+00 1.00000000E+00 1.00000000E+00 + 0.00000000E+00 0.00000000E+00 1.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 1.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 1.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 1.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 1.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 1.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 1.00000000E+00 1.00000000E+00 + 0.00000000E+00 1.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 +MxBond I 1 +NBond I N= 3 + 0 0 0 +IBond I N= 3 + 0 0 0 +RBond R N= 3 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 +Virial Ratio R 2.011194871414563E+00 +SCF Energy R -7.562730180595092E+01 +Total Energy R -7.562730180595092E+01 +RMS Density R 4.182529560118482E-09 +Job Status I 1 +Nuclear derivative order I 0 +External E-field R N= 35 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 +IOpCl I 0 +IROHF I 0 +Alpha Orbital Energies R N= 13 + -2.06699649E+01 -1.20134704E+00 -5.08560476E-01 -4.46812170E-01 -4.34275776E-01 + -7.79428952E-03 3.03563420E-02 9.37600604E-01 1.00976386E+00 1.15720781E+00 + 1.22944126E+00 1.28872667E+00 1.38501542E+00 +Alpha MO coefficients R N= 169 + 9.96015699E-01 1.97756823E-02 4.45969104E-04 8.36409875E-04 7.14964859E-17 + -4.72958689E-03 -3.06820464E-04 -5.80447063E-04 -6.57624568E-17 7.74465004E-06 + 6.75326932E-04 4.70239835E-10 6.69012740E-04 -2.30016974E-01 5.35834473E-01 + 1.12353289E-04 7.84789802E-04 1.97893033E-16 5.39733498E-01 6.44372972E-03 + 1.25402167E-02 -2.54224632E-16 2.62664472E-02 1.95743996E-02 2.56661381E-02 + 1.94517716E-02 3.48674314E-19 1.18137700E-16 1.31886706E-15 -1.10358808E-15 + 6.77692819E-01 -2.45261670E-16 1.30213650E-15 -8.52938185E-16 4.70214877E-01 + -1.08712726E-15 -5.34678758E-16 8.77260361E-16 1.13965795E-15 -2.81100311E-03 + 8.57295122E-03 3.70624851E-01 -2.44604897E-01 -2.48981384E-15 9.51362478E-03 + 2.97092303E-01 -1.95618146E-01 -1.79348647E-15 -1.73132724E-01 -3.05519875E-01 + 1.48891876E-01 2.63523997E-01 3.14296588E-02 -9.76655672E-02 2.54816160E-01 + 3.95245878E-01 -1.28486806E-16 -1.02817252E-01 2.04696554E-01 3.16681162E-01 + 1.06224400E-16 1.35855622E-01 2.49416996E-01 1.61469046E-01 2.96418183E-01 + 3.90568833E-02 -8.22061201E-02 -2.06254042E-01 -3.51088724E-01 -5.59191838E-16 + -2.13903718E-01 -2.13747934E-01 -3.64686214E-01 -6.08424725E-17 1.68333215E-01 + 4.86869652E-01 1.79833513E-01 5.15538611E-01 1.51445643E-03 -2.98226064E-03 + 3.76576410E-01 -2.18879464E-01 1.29731030E-16 -9.61276733E-03 4.09259271E-01 + -2.38288888E-01 1.84533178E-17 1.87190804E-01 5.37675923E-01 -1.77017320E-01 + -5.03247416E-01 -2.28371456E-02 6.70684468E-02 -9.03858512E-02 -1.79940119E-01 + -6.33823120E-16 2.12189988E-01 1.46761802E-01 2.86870500E-01 3.82258185E-16 + 8.97142154E-01 -8.33471204E-01 8.78500330E-01 -8.10591338E-01 6.43951581E-04 + -6.15938965E-03 1.63664400E-01 -9.10921782E-02 -6.93876788E-16 5.96794350E-03 + -3.46878413E-01 1.89265277E-01 1.65638588E-16 9.03295258E-01 -9.33369845E-01 + -9.25147732E-01 9.48497595E-01 -5.21383600E-16 5.55509380E-15 -1.66136467E-15 + -8.43262857E-15 -9.36364692E-01 -5.91378960E-15 1.61376138E-15 9.12598190E-15 + 1.05590926E+00 -3.48135129E-15 1.67091546E-15 -1.57131405E-15 1.41357615E-15 + -3.41743883E-02 4.84988283E-01 -4.40757022E-01 -7.79755332E-01 9.25559241E-15 + -5.07868015E-01 4.69006563E-01 8.28600622E-01 -1.03133184E-14 -2.24907289E-01 + 1.08716461E-01 -2.24701625E-01 1.01803904E-01 1.65689286E-03 -2.40848889E-02 + -8.53488005E-01 4.72885165E-01 -2.79523806E-15 2.59332218E-02 9.66548313E-01 + -5.34689704E-01 3.13495109E-15 2.05772667E-01 7.19352746E-02 -1.88941414E-01 + -8.82426867E-02 9.49816608E-02 -1.44719384E+00 -1.60922315E-01 -3.19778127E-01 + -8.17682253E-16 1.56617574E+00 2.12659925E-01 4.19991531E-01 6.90367110E-16 + 3.45750724E-03 -3.09023763E-01 1.93143995E-02 -3.12169776E-01 +Orthonormal basis R N= 169 + 1.04260234E-01 2.75963065E-01 4.35651620E-02 8.66407424E-02 0.00000000E+00 + 3.31353614E-01 8.02535204E-02 1.59227493E-01 -2.88926248E-20 1.89342161E-01 + 2.67949531E-01 1.81948958E-01 2.59304183E-01 2.11072341E-03 4.88724237E-03 + 2.17902006E-01 -1.17214481E-01 0.00000000E+00 5.30268422E-03 3.45344586E-01 + -1.85592879E-01 -5.13956619E-17 -2.53743474E-01 -2.78150454E-01 2.59726568E-01 + 2.87271879E-01 1.97249788E-01 3.69942871E-01 -1.44335856E-01 -2.66534177E-01 + 1.60634975E-17 3.12978802E-01 -1.79658852E-01 -3.31527443E-01 1.19792514E-17 + -1.62436229E-01 -1.30162592E-01 -1.63722712E-01 -1.32481677E-01 -1.58763949E-17 + -6.94150601E-17 3.05375578E-17 -1.10194236E-17 5.77057836E-01 9.27694992E-17 + -5.93497381E-17 -1.47181958E-17 5.77057836E-01 1.19118789E-16 -2.21144309E-16 + 2.55899080E-16 -5.33940871E-17 2.03311961E-01 2.10097041E-01 2.84200955E-01 + 4.65765566E-01 2.45976908E-17 8.46966304E-02 1.83342339E-01 3.01095760E-01 + 6.13307668E-17 -2.86762358E-01 -2.00741317E-01 -3.15904695E-01 -2.20088082E-01 + -1.25032428E-02 -1.10307309E-02 5.37665021E-01 -3.21720825E-01 4.40231864E-17 + -3.58223852E-03 2.95658637E-01 -1.77242029E-01 1.12047659E-16 3.44881773E-01 + 2.21758159E-01 -3.17149187E-01 -2.02604572E-01 9.71800394E-01 -2.17341261E-01 + -3.78048830E-02 -7.51265123E-02 0.00000000E+00 -2.77185090E-01 3.81911994E-03 + 7.25364361E-03 -1.59958040E-17 1.31986726E-01 1.69774960E-02 1.29155019E-01 + 1.60407568E-02 -9.70776961E-17 5.23855301E-16 -1.11286050E-16 -2.60863873E-15 + 1.00152416E+00 -3.15595220E-16 2.25089621E-16 2.69807273E-15 -1.00152416E+00 + -8.75756371E-16 8.64214258E-17 3.13216814E-16 -6.25999499E-16 -2.89947907E-03 + 1.64935354E-02 -5.04732464E-01 -7.87738445E-01 -2.21803263E-15 -3.75937845E-02 + 5.21080358E-01 8.15512671E-01 2.51407697E-15 -3.27813406E-01 1.02307038E-01 + -3.92443787E-01 1.26633006E-01 1.24290648E-03 -5.05048024E-03 -7.50387590E-01 + 4.86960623E-01 1.52372994E-15 4.77554478E-03 7.84856645E-01 -5.11145807E-01 + -1.76798078E-15 4.88337070E-01 -1.68339811E-01 -4.34724015E-01 1.55509287E-01 + -1.28724269E-01 7.46168483E-01 -1.13523501E-01 -2.02667889E-01 -1.12387954E-15 + -3.95639500E-01 2.02406549E-01 3.66009079E-01 6.68752845E-16 6.81758833E-01 + -6.95554565E-01 6.78157755E-01 -6.92304252E-01 -7.95988645E-04 7.74696534E-03 + 3.05325027E-01 -1.64821252E-01 5.30803354E-16 -5.06217065E-03 -6.68561707E-01 + 3.62368449E-01 -3.97573002E-16 7.33723822E-01 -1.04230583E+00 -7.41958805E-01 + 1.04863611E+00 7.70837027E-02 -1.33301405E+00 -6.90107288E-02 -1.32369695E-01 + -5.36193491E-16 1.62930154E+00 1.73684949E-01 3.34709679E-01 -3.94116091E-17 + 3.63239995E-01 -7.03948810E-01 3.59126930E-01 -6.87626692E-01 +Total SCF Density R N= 91 + 2.09190161E+00 -2.13295657E-01 5.94243438E-01 1.47706128E-02 -4.32807877E-02 + 4.04588534E-01 2.75251812E-02 -8.05236815E-02 2.01176890E-02 4.32104351E-01 + 5.77794756E-17 3.57433111E-16 -1.23373093E-16 -3.78883674E-16 9.18535113E-01 + -2.64233726E-01 5.98475098E-01 -4.52299600E-02 -8.50911058E-02 -1.40434434E-16 + 6.03993028E-01 7.62130052E-03 -2.79962846E-02 3.24540735E-01 1.64800755E-02 + 2.35392908E-16 -2.94811293E-02 2.60412263E-01 1.40809340E-02 -5.17957369E-02 + 1.63913626E-02 3.46050873E-01 -2.58453090E-16 -5.53004084E-02 1.35757622E-02 + 2.77422022E-01 3.03917166E-18 -2.15445308E-16 -3.50998811E-17 -7.69954484E-17 + 6.37322491E-01 -5.60424930E-16 1.99157444E-16 -3.94726414E-17 4.42204062E-01 + -2.55488494E-03 -1.35610734E-03 -5.90922552E-02 1.92132213E-01 -6.35854478E-16 + -2.87715334E-03 -4.69159407E-02 1.54440402E-01 -3.85838238E-16 9.82432325E-02 + 9.73620090E-03 -3.29533324E-02 -9.93505536E-02 3.46657248E-01 7.40429575E-16 + -3.59784129E-02 -7.91737572E-02 2.77991942E-01 6.36010641E-16 1.74588689E-01 + 3.11869690E-01 -2.49453095E-03 -1.48144307E-03 1.92661670E-01 5.48408709E-02 + 4.16265003E-16 -2.66485530E-03 1.54904346E-01 4.46602225E-02 3.12184594E-16 + -6.33484018E-03 -9.42780734E-03 9.77995879E-02 9.53533019E-03 -3.25090231E-02 + 3.46406338E-01 1.05429260E-01 1.64047380E-16 -3.49484520E-02 2.78183737E-01 + 8.51270396E-02 1.79510259E-16 -9.68723357E-03 -1.23977551E-02 1.75196390E-01 + 3.15374910E-01 +Mulliken Charges R N= 3 + -3.91150532E-01 1.96895396E-01 1.94255137E-01 +ESP Charges R N= 3 + -4.47363368E-01 2.24922518E-01 2.22440849E-01 +NPA Charges R N= 3 + -4.98161654E-01 2.50757174E-01 2.47404480E-01 +MBS Charges R N= 3 + -2.90505882E-01 1.45850946E-01 1.44654936E-01 +Type 6 Charges R N= 3 + -3.37450356E-01 1.68988978E-01 1.68461239E-01 +Type 7 Charges R N= 3 + -3.77750403E-01 1.89459551E-01 1.88290713E-01 +ONIOM Charges I N= 16 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 +ONIOM Multiplicities I N= 16 + 1 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 +Atom Layers I N= 3 + 1 1 1 +Atom Modifiers I N= 3 + 0 0 0 +Int Atom Modified Types I N= 3 + 0 0 0 +Force Field I 0 +Atom Modified Types C N= 3 + +Link Atoms I N= 3 + 0 0 0 +Atom Modified MM Charges R N= 3 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 +Link Distances R N= 12 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 +Cartesian Gradient R N= 9 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 +Nonadiabatic coupling R N= 9 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 +Dipole Moment R N= 3 + 4.29742853E-01 7.93714042E-01 2.89424748E-16 +Quadrupole Moment R N= 6 + -1.32532272E+00 1.87449664E+00 -5.49173918E-01 -5.26004180E+00 -2.02510843E-15 + -3.22171000E-16 +QEq coupling tensors R N= 18 + -2.01609156E+00 2.29777639E-01 -1.70195699E+00 0.00000000E+00 0.00000000E+00 + 3.71804855E+00 -9.05517239E-03 -1.81615845E-02 2.21935293E-02 0.00000000E+00 + 0.00000000E+00 -1.31383569E-02 2.99640433E-02 6.95355685E-03 -1.65413888E-02 + 0.00000000E+00 0.00000000E+00 -1.34226545E-02 +ClPar MaxAn I 200 +ClPar NIntPar I 4 +ClPar NRealPar I 16 +ClPar NBits I 30 +ClPar LenBitVec I 1 +ClPar Read BitMap I N= 200 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 +ClPar Default BitMap I N= 200 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 2016 2016 2016 2016 + 2016 2016 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 480 480 480 480 + 480 480 +ClPar Int Params I N= 800 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 0 0 +ClPar Real Params R N= 3200 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 2.26000000E-01 4.43000000E-01 1.01000000E-01 1.47000000E-01 1.92000000E-01 + 2.40000000E-01 2.75000000E-01 3.03000000E-01 3.25000000E-01 4.13000000E-01 + 9.60000000E-02 1.35000000E-01 1.73000000E-01 2.11000000E-01 2.48000000E-01 + 2.86000000E-01 3.23000000E-01 3.60000000E-01 8.20000000E-02 1.04000000E-01 + 1.17000000E-01 1.23000000E-01 1.29000000E-01 1.34000000E-01 1.40000000E-01 + 1.44000000E-01 1.48000000E-01 1.52000000E-01 1.55000000E-01 1.76000000E-01 + 1.98000000E-01 2.19000000E-01 2.39000000E-01 2.60000000E-01 2.80000000E-01 + 3.00000000E-01 7.90000000E-02 9.90000000E-02 1.19000000E-01 1.26000000E-01 + 1.33000000E-01 1.40000000E-01 1.47000000E-01 1.53000000E-01 1.58000000E-01 + 1.63000000E-01 1.57000000E-01 1.70000000E-01 1.87000000E-01 2.03000000E-01 + 2.19000000E-01 2.34000000E-01 2.50000000E-01 2.65000000E-01 7.40000000E-02 + 9.20000000E-02 1.05000000E-01 1.06000000E-01 1.07000000E-01 1.08000000E-01 + 1.09000000E-01 1.10000000E-01 1.11000000E-01 1.12000000E-01 1.13000000E-01 + 1.14000000E-01 1.15000000E-01 1.16000000E-01 1.17000000E-01 1.17000000E-01 + 1.26000000E-01 1.36000000E-01 1.46000000E-01 1.55000000E-01 1.65000000E-01 + 1.73000000E-01 1.82000000E-01 1.90000000E-01 1.62000000E-01 1.78000000E-01 + 1.10000000E-01 1.35000000E-01 1.59000000E-01 1.83000000E-01 2.07000000E-01 + 2.31000000E-01 6.90000000E-02 9.40000000E-02 1.02000000E-01 1.03000000E-01 + 1.05000000E-01 1.07000000E-01 1.09000000E-01 1.11000000E-01 1.13000000E-01 + 1.15000000E-01 1.17000000E-01 1.19000000E-01 1.20000000E-01 1.22000000E-01 + 1.24000000E-01 1.26000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 1.28000000E-01 + 5.47000000E-01 5.92000000E-01 5.99000000E-01 6.68000000E-01 6.98000000E-01 + 7.18000000E-01 7.26000000E-01 7.36000000E-01 7.43000000E-01 7.45000000E-01 + 6.07000000E-01 6.66000000E-01 6.93000000E-01 7.09000000E-01 7.20000000E-01 + 7.29000000E-01 7.35000000E-01 7.40000000E-01 6.08000000E-01 7.34000000E-01 + 7.46000000E-01 7.55000000E-01 7.61000000E-01 7.65000000E-01 7.67000000E-01 + 7.60000000E-01 7.49000000E-01 7.33000000E-01 7.08000000E-01 6.48000000E-01 + 6.88000000E-01 7.09000000E-01 7.22000000E-01 7.31000000E-01 7.38000000E-01 + 7.44000000E-01 6.09000000E-01 7.30000000E-01 7.41000000E-01 7.52000000E-01 + 7.58000000E-01 7.63000000E-01 7.66000000E-01 7.58000000E-01 7.48000000E-01 + 7.33000000E-01 7.01000000E-01 6.45000000E-01 6.86000000E-01 7.08000000E-01 + 7.22000000E-01 7.31000000E-01 7.38000000E-01 7.44000000E-01 6.08000000E-01 + 7.28000000E-01 7.32000000E-01 7.32000000E-01 7.32000000E-01 7.32000000E-01 + 7.32000000E-01 7.32000000E-01 7.32000000E-01 7.32000000E-01 7.32000000E-01 + 7.31000000E-01 7.31000000E-01 7.31000000E-01 7.31000000E-01 7.46000000E-01 + 7.37000000E-01 7.46000000E-01 7.51000000E-01 7.55000000E-01 7.58000000E-01 + 7.47000000E-01 7.32000000E-01 7.11000000E-01 7.41000000E-01 7.07000000E-01 + 7.47000000E-01 7.43000000E-01 7.42000000E-01 7.42000000E-01 7.43000000E-01 + 7.44000000E-01 6.05000000E-01 7.24000000E-01 7.26000000E-01 7.26000000E-01 + 7.27000000E-01 7.27000000E-01 7.27000000E-01 7.28000000E-01 7.28000000E-01 + 7.28000000E-01 7.28000000E-01 7.28000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 7.29000000E-01 + 1.00000000E+00 1.70400000E+00 3.23000000E-01 3.89000000E-01 4.67000000E-01 + 5.67000000E-01 6.36000000E-01 6.64000000E-01 6.96000000E-01 8.79000000E-01 + 3.07000000E-01 3.48000000E-01 4.00000000E-01 4.54000000E-01 5.09000000E-01 + 5.65000000E-01 6.21000000E-01 6.77000000E-01 2.56000000E-01 3.50000000E-01 + 3.64000000E-01 3.62000000E-01 3.62000000E-01 3.62000000E-01 3.62000000E-01 + 3.65000000E-01 3.69000000E-01 3.75000000E-01 3.86000000E-01 4.46000000E-01 + 4.55000000E-01 4.74000000E-01 4.96000000E-01 5.20000000E-01 5.45000000E-01 + 5.70000000E-01 2.45000000E-01 3.22000000E-01 3.55000000E-01 3.59000000E-01 + 3.64000000E-01 3.70000000E-01 3.76000000E-01 3.85000000E-01 3.95000000E-01 + 4.08000000E-01 3.76000000E-01 4.23000000E-01 4.22000000E-01 4.32000000E-01 + 4.45000000E-01 4.59000000E-01 4.75000000E-01 4.91000000E-01 2.28000000E-01 + 2.92000000E-01 2.89000000E-01 2.92000000E-01 2.95000000E-01 2.98000000E-01 + 3.00000000E-01 3.03000000E-01 3.06000000E-01 3.09000000E-01 3.12000000E-01 + 3.15000000E-01 3.18000000E-01 3.21000000E-01 3.24000000E-01 3.61000000E-01 + 3.69000000E-01 3.70000000E-01 3.72000000E-01 3.75000000E-01 3.78000000E-01 + 3.85000000E-01 3.94000000E-01 4.07000000E-01 5.26000000E-01 6.21000000E-01 + 3.36000000E-01 3.54000000E-01 3.71000000E-01 3.86000000E-01 4.02000000E-01 + 4.17000000E-01 2.09000000E-01 2.94000000E-01 2.65000000E-01 2.72000000E-01 + 2.78000000E-01 2.84000000E-01 2.91000000E-01 2.97000000E-01 3.03000000E-01 + 3.10000000E-01 3.16000000E-01 3.22000000E-01 3.29000000E-01 3.35000000E-01 + 3.41000000E-01 3.48000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 3.54000000E-01 + 2.74000000E-01 2.96000000E-01 5.80000000E-02 4.40000000E-02 3.90000000E-02 + 3.90000000E-02 3.60000000E-02 3.20000000E-02 2.90000000E-02 3.30000000E-02 + 5.50000000E-02 3.90000000E-02 3.30000000E-02 3.00000000E-02 2.80000000E-02 + 2.60000000E-02 2.50000000E-02 2.50000000E-02 4.50000000E-02 4.40000000E-02 + 3.30000000E-02 2.50000000E-02 2.10000000E-02 1.80000000E-02 1.50000000E-02 + 1.80000000E-02 2.10000000E-02 2.50000000E-02 3.30000000E-02 5.00000000E-02 + 3.80000000E-02 3.10000000E-02 2.70000000E-02 2.40000000E-02 2.20000000E-02 + 2.10000000E-02 4.30000000E-02 4.00000000E-02 3.20000000E-02 2.50000000E-02 + 2.10000000E-02 1.80000000E-02 1.60000000E-02 1.90000000E-02 2.20000000E-02 + 2.80000000E-02 3.10000000E-02 4.70000000E-02 3.40000000E-02 2.80000000E-02 + 2.40000000E-02 2.10000000E-02 1.90000000E-02 1.80000000E-02 3.90000000E-02 + 3.60000000E-02 2.50000000E-02 2.50000000E-02 2.50000000E-02 2.60000000E-02 + 2.60000000E-02 2.60000000E-02 2.70000000E-02 2.70000000E-02 2.70000000E-02 + 2.70000000E-02 2.80000000E-02 2.80000000E-02 2.80000000E-02 3.30000000E-02 + 3.30000000E-02 2.60000000E-02 2.10000000E-02 1.80000000E-02 1.60000000E-02 + 1.80000000E-02 2.20000000E-02 2.60000000E-02 4.90000000E-02 8.20000000E-02 + 3.00000000E-02 2.40000000E-02 2.10000000E-02 1.80000000E-02 1.60000000E-02 + 1.50000000E-02 3.50000000E-02 3.60000000E-02 2.20000000E-02 2.30000000E-02 + 2.40000000E-02 2.40000000E-02 2.50000000E-02 2.50000000E-02 2.60000000E-02 + 2.70000000E-02 2.70000000E-02 2.80000000E-02 2.80000000E-02 2.90000000E-02 + 3.00000000E-02 3.00000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 3.10000000E-02 + -5.92770000E-01 -9.17860000E-01 -2.48668000E+00 -8.36430000E-01 -3.18470000E-01 + -4.55790000E-01 -5.70730000E-01 -6.77940000E-01 -7.69860000E-01 -8.51140000E-01 + -1.51908000E+00 -3.18830000E-01 -2.17900000E-01 -3.01260000E-01 -3.72750000E-01 + -4.15890000E-01 -4.45930000E-01 -5.91320000E-01 -9.56370000E-01 -2.88450000E-01 + -4.87320000E-01 -6.34280000E-01 -7.17490000E-01 -7.91980000E-01 -8.68490000E-01 + -9.17520000E-01 -9.54890000E-01 -9.88460000E-01 -1.50055000E+00 -2.92560000E-01 + -2.14960000E-01 -2.91180000E-01 -3.42080000E-01 -3.84480000E-01 -4.34940000E-01 + -5.24310000E-01 -8.10070000E-01 -2.58460000E-01 -4.35550000E-01 -5.32740000E-01 + -6.08450000E-01 -6.10260000E-01 -7.58920000E-01 -8.90130000E-01 -8.45780000E-01 + -8.69340000E-01 -1.38234000E+00 -2.64850000E-01 -1.97280000E-01 -2.65040000E-01 + -2.94470000E-01 -3.16570000E-01 -3.63590000E-01 -4.57840000E-01 -8.61830000E-01 + -1.33004000E+00 -6.82880000E-01 -6.25540000E-01 -6.35430000E-01 -8.79690000E-01 + -9.02690000E-01 -9.59650000E-01 -1.01869000E+00 -6.81170000E-01 -7.96500000E-01 + -9.04950000E-01 -8.34790000E-01 -9.01560000E-01 -9.74130000E-01 -1.03326000E+00 + -7.15490000E-01 -7.83960000E-01 -8.28380000E-01 -5.61040000E-01 -1.03841000E+00 + -8.62500000E-01 -9.59300000E-01 -1.01755000E+00 -7.64900000E-01 -1.29467000E+00 + -1.92740000E-01 -2.56010000E-01 -3.19820000E-01 -1.94660000E-01 -2.60090000E-01 + -4.27990000E-01 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 1.90440000E+00 1.36430000E+00 4.84000000E-01 7.31600000E+00 9.24030000E+00 + 8.78200000E+00 5.61290000E+00 4.83340000E+00 3.24870000E+00 2.71090000E+00 + 2.01130000E+00 1.50282000E+01 1.46788000E+01 2.13546000E+01 2.38290000E+01 + 1.98022000E+01 1.50468000E+01 1.09745000E+01 1.27219000E+01 1.51949000E+01 + 1.57342000E+01 2.79222000E+01 1.61366000E+01 2.21252000E+01 2.45676000E+01 + 1.96016000E+01 1.79818000E+01 1.72396000E+01 5.01500000E+00 1.92898000E+01 + 2.34477000E+01 2.23993000E+01 2.96586000E+01 2.60081000E+01 2.15580000E+01 + 1.69594000E+01 1.56479000E+01 1.86897000E+01 1.93530000E+01 3.43444000E+01 + 1.98480000E+01 2.72140000E+01 3.02182000E+01 2.41099000E+01 2.21176000E+01 + 2.12047000E+01 7.56690000E+00 2.37264000E+01 2.93879000E+01 3.01278000E+01 + 4.19382000E+01 3.85203000E+01 3.39511000E+01 2.75774000E+01 1.56460000E+01 + 1.04460000E+01 2.01830000E+01 4.99540000E+01 4.76430000E+01 1.18690000E+01 + 1.08670000E+01 9.96100000E+00 9.18700000E+00 3.67980000E+01 1.34070000E+01 + 9.38200000E+00 9.81700000E+00 8.90800000E+00 8.40700000E+00 8.05800000E+00 + 1.29600000E+01 2.67920000E+01 2.49300000E+01 3.45630000E+01 1.11860000E+01 + 1.09440000E+01 1.01270000E+01 9.40400000E+00 1.36660000E+01 7.81000000E+00 + 6.93600000E+01 5.86630000E+01 4.79690000E+01 4.52740000E+01 3.92290000E+01 + 3.30660000E+01 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 +Gaussian Version C N= 2 +ES64L-G16RevC.01 diff --git a/iodata/test/data/water_mp2_input.json b/iodata/test/data/water_mp2_input.json new file mode 100644 index 000000000..04701836c --- /dev/null +++ b/iodata/test/data/water_mp2_input.json @@ -0,0 +1,22 @@ +{ + "schema_name": "qc_schema_input", + "schema_version": 1, + "molecule": { + "geometry": [ + 0.0, 0.0, -0.1294, + 0.0, -1.4941, 1.0274, + 0.0, 1.4941, 1.0274 + ], + "symbols": ["O", "H", "H"] + }, + "driver": "energy", + "model": { + "method": "MP2", + "basis": "cc-pVDZ" + }, + "keywords": {}, + "provenance": { + "creator": "HORTON3", + "routine": "Copied from QCSchema docs' Water MP2 example" + } +} \ No newline at end of file diff --git a/iodata/test/data/water_single_model.pdb b/iodata/test/data/water_single_model.pdb index 78dd6b647..bd707dd41 100644 --- a/iodata/test/data/water_single_model.pdb +++ b/iodata/test/data/water_single_model.pdb @@ -1,6 +1,10 @@ MODEL +TITLE water HETATM 1 H HOH 0 0.784 -0.492 0.000 1.00 0.00 H HETATM 2 O HOH 1 0.000 0.062 0.000 1.00 0.00 O HETATM 3 H HOH 0 -0.784 -0.492 0.000 1.00 0.00 H +CONECT 1 2 +CONECT 2 1 3 +CONECT 3 2 ENDMDL END diff --git a/iodata/test/data/water_single_no_end.pdb b/iodata/test/data/water_single_no_end.pdb index 6866315c1..088fb1e8a 100644 --- a/iodata/test/data/water_single_no_end.pdb +++ b/iodata/test/data/water_single_no_end.pdb @@ -3,3 +3,6 @@ AUTHOR GENERATED BY OPEN BABEL 2.4.1 HETATM 1 H HOH 0 0.784 -0.492 0.000 1.00 0.00 H HETATM 2 O HOH 1 0.000 0.062 0.000 1.00 0.00 O HETATM 3 H HOH 0 -0.784 -0.492 0.000 1.00 0.00 H +CONECT 1 2 +CONECT 2 1 3 +CONECT 3 2 diff --git a/iodata/test/data/water_trajectory_no_model.pdb b/iodata/test/data/water_trajectory_no_model.pdb index ed1eb016e..9ce881cfe 100644 --- a/iodata/test/data/water_trajectory_no_model.pdb +++ b/iodata/test/data/water_trajectory_no_model.pdb @@ -1,20 +1,35 @@ HETATM 1 O HOH 1 3.341 0.264 2.538 1.00 0.00 O HETATM 2 H HOH 0 3.391 -0.627 2.172 1.00 0.00 H HETATM 3 H HOH 0 2.864 0.114 3.364 1.00 0.00 H +CONECT 1 2 +CONECT 2 1 3 +CONECT 3 2 END HETATM 1 O HOH 1 0.345 2.460 -3.307 1.00 0.00 O HETATM 2 H HOH 0 -0.015 3.354 -3.352 1.00 0.00 H HETATM 3 H HOH 0 0.165 2.218 -2.391 1.00 0.00 H +CONECT 1 2 +CONECT 2 1 3 +CONECT 3 2 END HETATM 1 O HOH 1 -0.233 -0.790 -3.248 1.00 0.00 O HETATM 2 H HOH 0 0.409 -0.077 -3.354 1.00 0.00 H HETATM 3 H HOH 0 -0.872 -0.395 -2.642 1.00 0.00 H +CONECT 1 2 +CONECT 2 1 3 +CONECT 3 2 END HETATM 1 O HOH 1 1.359 3.030 -0.351 1.00 0.00 O HETATM 2 H HOH 0 1.799 3.355 0.444 1.00 0.00 H HETATM 3 H HOH 0 1.936 3.363 -1.049 1.00 0.00 H +CONECT 1 2 +CONECT 2 1 3 +CONECT 3 2 END HETATM 1 O HOH 1 -1.382 -3.328 -2.737 1.00 0.00 O HETATM 2 H HOH 0 -2.123 -3.355 -3.354 1.00 0.00 H HETATM 3 H HOH 0 -0.627 -3.266 -3.336 1.00 0.00 H +CONECT 1 2 +CONECT 2 1 3 +CONECT 3 2 END diff --git a/iodata/test/data/xtb_water_no_basis.json b/iodata/test/data/xtb_water_no_basis.json new file mode 100644 index 000000000..e29064f31 --- /dev/null +++ b/iodata/test/data/xtb_water_no_basis.json @@ -0,0 +1,64 @@ +{ + "molecule": { + "schema_name": "qcschema_molecule", + "schema_version": 2, + "symbols": [ + "O", + "H", + "H" + ], + "geometry": [ + 0.0, + 0.0, + -0.12947694, + 0.0, + -1.49418734, + 1.02744651, + 0.0, + 1.49418734, + 1.02744651 + ], + "provenance": { + "creator": "QCElemental", + "routine": "unknown" + }, + "connectivity": [ + [ + 0, + 1, + 1.0 + ], + [ + 0, + 2, + 1.0 + ] + ] + }, + "driver": "energy", + "model": { + "method": "XTB", + "basis": "" + }, + "keywords": {}, + "id": null, + "schema_name": "qcschema_output", + "schema_version": 1, + "provenance": { + "creator": "QCElemental", + "version": "v0.2.6", + "routine": "qcelemental.models.results" + }, + "success": true, + "return_result": -5.7659905206521511, + "properties": { + "scf_total_energy": -5.7659905206521511, + "scf_iterations": 7, + "calcinfo_nbasis": 8, + "calcinfo_natom": 3 + }, + "extras": { + "scf_converged": true + }, + "error": null +} \ No newline at end of file diff --git a/iodata/test/test_attrutils.py b/iodata/test/test_attrutils.py index cc1f803a6..d4e999356 100644 --- a/iodata/test/test_attrutils.py +++ b/iodata/test/test_attrutils.py @@ -18,20 +18,20 @@ # -- """Unit tests for iodata.attrutils.""" - -import attr +import attrs import numpy as np -from numpy.testing import assert_allclose import pytest +from numpy.testing import assert_allclose +from numpy.typing import NDArray from ..attrutils import convert_array_to, validate_shape -@attr.s(auto_attribs=True, slots=True, on_setattr=attr.setters.convert) +@attrs.define class FooBar: """Just a silly class for testing convert_array_to.""" - spam: np.ndarray = attr.ib(converter=convert_array_to(float)) + spam: NDArray = attrs.field(converter=convert_array_to(float)) def test_convert_array_to_init(): @@ -60,24 +60,22 @@ def test_convert_array_to_assign(): assert fb.spam is None -@attr.s(auto_attribs=True, slots=True, on_setattr=attr.setters.validate) +@attrs.define class Spam: """Just a silly class for testing validate_shape.""" - egg0: np.ndarray = attr.ib(validator=validate_shape(1, None, None)) - egg1: np.ndarray = attr.ib(validator=validate_shape(("egg0", 2), ("egg2", 1))) - egg2: np.ndarray = attr.ib(validator=validate_shape(2, ("egg1", 1))) - egg3: np.ndarray = attr.ib(validator=validate_shape(("leg", 0))) - leg: str = attr.ib(validator=validate_shape(("egg3", 0))) + egg0: NDArray = attrs.field(validator=validate_shape(1, None, None)) + egg1: NDArray = attrs.field(validator=validate_shape(("egg0", 2), ("egg2", 1))) + egg2: NDArray = attrs.field(validator=validate_shape(2, ("egg1", 1))) + egg3: NDArray = attrs.field(validator=validate_shape(("leg", 0))) + leg: str = attrs.field(validator=validate_shape(("egg3", 0))) def test_validate_shape_init(): # Construct a Spam instance with valid arguments. This should just work - spam = Spam( - np.zeros((1, 7, 4)), np.zeros((4, 3)), np.zeros((2, 3)), np.zeros(5), "abcde" - ) + spam = Spam(np.zeros((1, 7, 4)), np.zeros((4, 3)), np.zeros((2, 3)), np.zeros(5), "abcde") # Double check - attr.validate(spam) + attrs.validate(spam) # Call constructor with invalid arguments with pytest.raises(TypeError): _ = Spam( @@ -132,11 +130,9 @@ def test_validate_shape_init(): def test_validate_shape_assign(): # Construct a Spam instance with valid arguments. This should just work - spam = Spam( - np.zeros((1, 7, 4)), np.zeros((4, 3)), np.zeros((2, 3)), np.zeros(5), "abcde" - ) + spam = Spam(np.zeros((1, 7, 4)), np.zeros((4, 3)), np.zeros((2, 3)), np.zeros(5), "abcde") # Double check - attr.validate(spam) + attrs.validate(spam) # assign invalid attributes with pytest.raises(TypeError): spam.egg0 = np.zeros((2, 7, 4)) @@ -150,33 +146,33 @@ def test_validate_shape_assign(): spam.leg = "abcd" -@attr.s(slots=True) +@attrs.define class NoName0: """Test exception in validate_shape: unsupported item in shape_requirements.""" - xxx: str = attr.ib(validator=validate_shape(["asdfsa", 3])) + xxx: str = attrs.field(validator=validate_shape(["asdfsa", 3])) -@attr.s(slots=True) +@attrs.define class NoName1: """Test exception in validate_shape: unsupported item in shape_requirements.""" - xxx: str = attr.ib(validator=validate_shape(("asdfsa",))) + xxx: str = attrs.field(validator=validate_shape(("asdfsa",))) -@attr.s(slots=True) +@attrs.define class NoName2: """Test exception in validate_shape: other doest not exist.""" - xxx: str = attr.ib(validator=validate_shape("other")) + xxx: str = attrs.field(validator=validate_shape("other")) -@attr.s(slots=True) +@attrs.define class NoName3: """Test exception in validate_shape: other is not an array.""" - xxx: str = attr.ib(validator=validate_shape(("other", 1))) - other = attr.ib() + xxx: str = attrs.field(validator=validate_shape(("other", 1))) + other = attrs.field() def test_validate_shape_exceptions(): diff --git a/iodata/test/test_basis.py b/iodata/test/test_basis.py index 6788e7b0b..e076e77cd 100644 --- a/iodata/test/test_basis.py +++ b/iodata/test/test_basis.py @@ -18,52 +18,58 @@ # -- """Unit tests for iodata.obasis.""" - -import attr +import attrs import numpy as np -from numpy.testing import assert_equal import pytest +from numpy.testing import assert_equal -from ..basis import (angmom_sti, angmom_its, Shell, MolecularBasis, - convert_convention_shell, convert_conventions, - iter_cart_alphabet, HORTON2_CONVENTIONS, CCA_CONVENTIONS) +from ..basis import ( + CCA_CONVENTIONS, + HORTON2_CONVENTIONS, + MolecularBasis, + Shell, + angmom_its, + angmom_sti, + convert_convention_shell, + convert_conventions, + iter_cart_alphabet, +) from ..formats.cp2klog import CONVENTIONS as CP2K_CONVENTIONS def test_angmom_sti(): - assert angmom_sti('s') == 0 - assert angmom_sti('p') == 1 - assert angmom_sti('f') == 3 - assert angmom_sti(['s']) == [0] - assert angmom_sti(['s', 's']) == [0, 0] - assert angmom_sti(['s', 's', 's']) == [0, 0, 0] - assert angmom_sti(['p']) == [1] - assert angmom_sti(['s', 'p']) == [0, 1] - assert angmom_sti(['s', 'p', 'p']) == [0, 1, 1] - assert angmom_sti(['s', 'p', 'p', 'd', 'd', 's', 'f', 'i']) == \ - [0, 1, 1, 2, 2, 0, 3, 6] - assert angmom_sti(['e', 't', 'k']) == [24, 14, 7] + assert angmom_sti("s") == 0 + assert angmom_sti("p") == 1 + assert angmom_sti("f") == 3 + assert angmom_sti(["s"]) == [0] + assert angmom_sti(["s", "s"]) == [0, 0] + assert angmom_sti(["s", "s", "s"]) == [0, 0, 0] + assert angmom_sti(["p"]) == [1] + assert angmom_sti(["s", "p"]) == [0, 1] + assert angmom_sti(["s", "p", "p"]) == [0, 1, 1] + assert angmom_sti(["s", "p", "p", "d", "d", "s", "f", "i"]) == [0, 1, 1, 2, 2, 0, 3, 6] + assert angmom_sti(["e", "t", "k"]) == [24, 14, 7] def test_angmom_sti_uppercase(): - assert angmom_sti('S') == 0 - assert angmom_sti('D') == 2 - assert angmom_sti('g') == 4 - assert angmom_sti(['P']) == [1] - assert angmom_sti(['F', 'f']) == [3, 3] - assert angmom_sti(['n', 'N', 'N']) == [10, 10, 10] - assert angmom_sti(['D', 'O']) == [2, 11] - assert angmom_sti(['S', 'p', 'P', 'D', 's', 'I']) == [0, 1, 1, 2, 0, 6] - assert angmom_sti(['E', 'T', 'k']) == [24, 14, 7] + assert angmom_sti("S") == 0 + assert angmom_sti("D") == 2 + assert angmom_sti("g") == 4 + assert angmom_sti(["P"]) == [1] + assert angmom_sti(["F", "f"]) == [3, 3] + assert angmom_sti(["n", "N", "N"]) == [10, 10, 10] + assert angmom_sti(["D", "O"]) == [2, 11] + assert angmom_sti(["S", "p", "P", "D", "s", "I"]) == [0, 1, 1, 2, 0, 6] + assert angmom_sti(["E", "T", "k"]) == [24, 14, 7] def test_angmom_its(): - assert angmom_its(0) == 's' - assert angmom_its(1) == 'p' - assert angmom_its(2) == 'd' - assert angmom_its(3) == 'f' - assert angmom_its(24) == 'e' - assert angmom_its([0, 1, 3]) == ['s', 'p', 'f'] + assert angmom_its(0) == "s" + assert angmom_its(1) == "p" + assert angmom_its(2) == "d" + assert angmom_its(3) == "f" + assert angmom_its(24) == "e" + assert angmom_its([0, 1, 3]) == ["s", "p", "f"] with pytest.raises(ValueError): angmom_its(-1) with pytest.raises(ValueError): @@ -76,11 +82,12 @@ def test_angmom_its(): def test_shell_info_propertes(): shells = [ - Shell(0, [0], ['c'], np.zeros(6), np.zeros((6, 1))), - Shell(0, [0, 1], ['c', 'c'], np.zeros(3), np.zeros((3, 2))), - Shell(0, [0, 1], ['c', 'c'], np.zeros(1), np.zeros((1, 2))), - Shell(0, [2], ['p'], np.zeros(2), np.zeros((2, 1))), - Shell(0, [2, 3, 4], ['c', 'p', 'p'], np.zeros(1), np.zeros((1, 3)))] + Shell(0, [0], ["c"], np.zeros(6), np.zeros((6, 1))), + Shell(0, [0, 1], ["c", "c"], np.zeros(3), np.zeros((3, 2))), + Shell(0, [0, 1], ["c", "c"], np.zeros(1), np.zeros((1, 2))), + Shell(0, [2], ["p"], np.zeros(2), np.zeros((2, 1))), + Shell(0, [2, 3, 4], ["c", "p", "p"], np.zeros(1), np.zeros((1, 3))), + ] assert shells[0].nbasis == 1 assert shells[1].nbasis == 4 @@ -99,62 +106,80 @@ def test_shell_info_propertes(): assert shells[4].ncon == 3 obasis = MolecularBasis( shells, - {(0, 'c'): ['s'], - (1, 'c'): ['x', 'z', '-y'], - (2, 'p'): ['dc0', 'dc1', '-ds1', 'dc2', '-ds2']}, - 'L2') + { + (0, "c"): ["s"], + (1, "c"): ["x", "z", "-y"], + (2, "p"): ["dc0", "dc1", "-ds1", "dc2", "-ds2"], + }, + "L2", + ) assert obasis.nbasis == 1 + 4 + 4 + 5 + 6 + 7 + 9 def test_shell_validators(): # The following line constructs a Shell instance with valid arguments. # It should not raise a TypeError. - shell = Shell(0, [0, 0], ['c', 'c'], np.zeros(6), np.zeros((6, 2))) + shell = Shell(0, [0, 0], ["c", "c"], np.zeros(6), np.zeros((6, 2))) # Rerun the validators as a double check. - attr.validate(shell) + attrs.validate(shell) # Tests with invalid constructor arguments. with pytest.raises(TypeError): - Shell(0, [0, 0], ['c', 'c'], np.zeros(6), np.zeros((6, 2, 2))) + Shell(0, [0, 0], ["c", "c"], np.zeros(6), np.zeros((6, 2, 2))) with pytest.raises(TypeError): - Shell(0, [0], ['c'], np.zeros(6), np.zeros(6,)) + Shell( + 0, + [0], + ["c"], + np.zeros(6), + np.zeros( + 6, + ), + ) with pytest.raises(TypeError): - Shell(0, [0], ['c'], np.zeros((6, 2)), np.zeros((6, 1))) + Shell(0, [0], ["c"], np.zeros((6, 2)), np.zeros((6, 1))) with pytest.raises(TypeError): - Shell(0, [0, 0], ['c', 'c'], np.zeros((6, 2)), np.zeros((6, 1))) + Shell(0, [0, 0], ["c", "c"], np.zeros((6, 2)), np.zeros((6, 1))) with pytest.raises(TypeError): - Shell(0, [0], ['c', 'c'], np.zeros(6), np.zeros((6, 2))) + Shell(0, [0], ["c", "c"], np.zeros(6), np.zeros((6, 2))) with pytest.raises(TypeError): - Shell(0, [0, 0], ['c'], np.zeros(6), np.zeros((6, 2))) + Shell(0, [0, 0], ["c"], np.zeros(6), np.zeros((6, 2))) def test_shell_exceptions(): - Shell(0, [0, 0, 0], ['e', 'e', 'e'], np.zeros(6), np.zeros((6, 3))) + Shell(0, [0, 0, 0], ["e", "e", "e"], np.zeros(6), np.zeros((6, 3))) with pytest.raises(TypeError): - _ = Shell(0, [0, 0, 0], ['e', 'e', 'e'], np.zeros(6), np.zeros((6, 3))).nbasis - Shell(0, [0, 0, 0], ['p', 'p', 'p'], np.zeros(6), np.zeros((6, 3))) + _ = Shell(0, [0, 0, 0], ["e", "e", "e"], np.zeros(6), np.zeros((6, 3))).nbasis + Shell(0, [0, 0, 0], ["p", "p", "p"], np.zeros(6), np.zeros((6, 3))) with pytest.raises(TypeError): - _ = Shell(0, [0, 0, 0], ['p', 'p', 'p'], np.zeros(6), np.zeros((6, 3))).nbasis - Shell(0, [1, 1, 1], ['p', 'p', 'p'], np.zeros(6), np.zeros((6, 3))) + _ = Shell(0, [0, 0, 0], ["p", "p", "p"], np.zeros(6), np.zeros((6, 3))).nbasis + Shell(0, [1, 1, 1], ["p", "p", "p"], np.zeros(6), np.zeros((6, 3))) with pytest.raises(TypeError): - _ = Shell(0, [1, 1, 1], ['p', 'p', 'p'], np.zeros(6), np.zeros((6, 3))).nbasis + _ = Shell(0, [1, 1, 1], ["p", "p", "p"], np.zeros(6), np.zeros((6, 3))).nbasis def test_nbasis1(): - obasis = MolecularBasis([ - Shell(0, [0], ['c'], np.zeros(16), np.zeros((16, 1))), - Shell(0, [1], ['c'], np.zeros(16), np.zeros((16, 1))), - Shell(0, [2], ['p'], np.zeros(16), np.zeros((16, 1))), - ], CP2K_CONVENTIONS, 'L2') + obasis = MolecularBasis( + [ + Shell(0, [0], ["c"], np.zeros(16), np.zeros((16, 1))), + Shell(0, [1], ["c"], np.zeros(16), np.zeros((16, 1))), + Shell(0, [2], ["p"], np.zeros(16), np.zeros((16, 1))), + ], + CP2K_CONVENTIONS, + "L2", + ) assert obasis.nbasis == 9 def test_get_segmented(): - obasis0 = MolecularBasis([ - Shell(0, [0, 1], ['c', 'c'], np.random.uniform(0, 1, 5), - np.random.uniform(-1, 1, (5, 2))), - Shell(1, [2, 3], ['p', 'p'], np.random.uniform(0, 1, 7), - np.random.uniform(-1, 1, (7, 2))), - ], CP2K_CONVENTIONS, 'L2') + rng = np.random.default_rng(1) + obasis0 = MolecularBasis( + [ + Shell(0, [0, 1], ["c", "c"], rng.uniform(0, 1, 5), rng.uniform(-1, 1, (5, 2))), + Shell(1, [2, 3], ["p", "p"], rng.uniform(0, 1, 7), rng.uniform(-1, 1, (7, 2))), + ], + CP2K_CONVENTIONS, + "L2", + ) assert obasis0.nbasis == 16 obasis1 = obasis0.get_segmented() assert len(obasis1.shells) == 4 @@ -163,88 +188,96 @@ def test_get_segmented(): shell0 = obasis1.shells[0] assert shell0.icenter == 0 assert_equal(shell0.angmoms, [0]) - assert shell0.kinds == ['c'] + assert shell0.kinds == ["c"] assert_equal(shell0.exponents, obasis0.shells[0].exponents) assert_equal(shell0.coeffs, obasis0.shells[0].coeffs[:, :1]) # shell 1 shell1 = obasis1.shells[1] assert shell1.icenter == 0 assert_equal(shell1.angmoms, [1]) - assert shell1.kinds == ['c'] + assert shell1.kinds == ["c"] assert_equal(shell1.exponents, obasis0.shells[0].exponents) assert_equal(shell1.coeffs, obasis0.shells[0].coeffs[:, 1:]) # shell 2 shell2 = obasis1.shells[2] assert shell2.icenter == 1 assert_equal(shell2.angmoms, [2]) - assert shell2.kinds == ['p'] + assert shell2.kinds == ["p"] assert_equal(shell2.exponents, obasis0.shells[1].exponents) assert_equal(shell2.coeffs, obasis0.shells[1].coeffs[:, :1]) # shell 0 shell3 = obasis1.shells[3] assert shell3.icenter == 1 assert_equal(shell3.angmoms, [3]) - assert shell3.kinds == ['p'] + assert shell3.kinds == ["p"] assert_equal(shell3.exponents, obasis0.shells[1].exponents) assert_equal(shell3.coeffs, obasis0.shells[1].coeffs[:, 1:]) def test_convert_convention_shell(): - assert convert_convention_shell('abc', 'cba') == ([2, 1, 0], [1, 1, 1]) - assert convert_convention_shell(['a', 'b', 'c'], ['c', 'b', 'a']) == ([2, 1, 0], [1, 1, 1]) + assert convert_convention_shell("abc", "cba") == ([2, 1, 0], [1, 1, 1]) + assert convert_convention_shell(["a", "b", "c"], ["c", "b", "a"]) == ([2, 1, 0], [1, 1, 1]) - permutation, signs = convert_convention_shell(['-a', 'b', 'c'], ['c', 'b', 'a']) + permutation, signs = convert_convention_shell(["-a", "b", "c"], ["c", "b", "a"]) assert permutation == [2, 1, 0] assert signs == [1, 1, -1] vec1 = np.array([1, 2, 3]) vec2 = np.array([3, 2, -1]) assert_equal(vec1[permutation] * signs, vec2) - permutation, signs = convert_convention_shell(['-a', 'b', 'c'], ['c', 'b', 'a'], True) + permutation, signs = convert_convention_shell(["-a", "b", "c"], ["c", "b", "a"], True) assert_equal(vec2[permutation] * signs, vec1) - permutation, signs = convert_convention_shell(['a', 'b', 'c'], ['-c', 'b', 'a']) + permutation, signs = convert_convention_shell(["a", "b", "c"], ["-c", "b", "a"]) assert permutation == [2, 1, 0] assert signs == [-1, 1, 1] vec1 = np.array([1, 2, 3]) vec2 = np.array([-3, 2, 1]) assert_equal(vec1[permutation] * signs, vec2) - permutation, signs = convert_convention_shell(['a', '-b', '-c'], ['-c', 'b', 'a']) + permutation, signs = convert_convention_shell(["a", "-b", "-c"], ["-c", "b", "a"]) assert permutation == [2, 1, 0] assert signs == [1, -1, 1] vec1 = np.array([1, 2, 3]) vec2 = np.array([3, -2, 1]) assert_equal(vec1[permutation] * signs, vec2) - permutation, signs = convert_convention_shell(['a', '-b', '-c'], ['-c', 'b', 'a'], True) + permutation, signs = convert_convention_shell(["a", "-b", "-c"], ["-c", "b", "a"], True) assert_equal(vec2[permutation] * signs, vec1) - permutation, signs = convert_convention_shell(['fo', 'ba', 'sp'], ['fo', '-sp', 'ba']) + permutation, signs = convert_convention_shell(["fo", "ba", "sp"], ["fo", "-sp", "ba"]) assert permutation == [0, 2, 1] assert signs == [1, -1, 1] vec1 = np.array([1, 2, 3]) vec2 = np.array([1, -3, 2]) assert_equal(vec1[permutation] * signs, vec2) - permutation, signs = convert_convention_shell(['fo', 'ba', 'sp'], ['fo', '-sp', 'ba'], True) + permutation, signs = convert_convention_shell(["fo", "ba", "sp"], ["fo", "-sp", "ba"], True) assert_equal(vec2[permutation] * signs, vec1) def test_convert_convention_obasis(): obasis = MolecularBasis( - [Shell(0, [0], ['c'], np.zeros(3), np.zeros((3, 1))), - Shell(0, [0, 1], ['c', 'c'], np.zeros(3), np.zeros((3, 2))), - Shell(0, [0, 1], ['c', 'c'], np.zeros(3), np.zeros((3, 2))), - Shell(0, [2], ['p'], np.zeros(3), np.zeros((3, 1)))], - {(0, 'c'): ['s'], - (1, 'c'): ['x', 'z', '-y'], - (2, 'p'): ['dc0', 'dc1', '-ds1', 'dc2', '-ds2']}, - 'L2') - new_convention = {(0, 'c'): ['-s'], - (1, 'c'): ['x', 'y', 'z'], - (2, 'p'): ['dc2', 'dc1', 'dc0', 'ds1', 'ds2']} + [ + Shell(0, [0], ["c"], np.zeros(3), np.zeros((3, 1))), + Shell(0, [0, 1], ["c", "c"], np.zeros(3), np.zeros((3, 2))), + Shell(0, [0, 1], ["c", "c"], np.zeros(3), np.zeros((3, 2))), + Shell(0, [2], ["p"], np.zeros(3), np.zeros((3, 1))), + ], + { + (0, "c"): ["s"], + (1, "c"): ["x", "z", "-y"], + (2, "p"): ["dc0", "dc1", "-ds1", "dc2", "-ds2"], + }, + "L2", + ) + new_convention = { + (0, "c"): ["-s"], + (1, "c"): ["x", "y", "z"], + (2, "p"): ["dc2", "dc1", "dc0", "ds1", "ds2"], + } permutation, signs = convert_conventions(obasis, new_convention) assert_equal(permutation, [0, 1, 2, 4, 3, 5, 6, 8, 7, 12, 10, 9, 11, 13]) assert_equal(signs, [-1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1]) - vec1 = np.random.uniform(-1, 1, obasis.nbasis) + rng = np.random.default_rng(1) + vec1 = rng.uniform(-1, 1, obasis.nbasis) vec2 = vec1[permutation] * signs permutation, signs = convert_conventions(obasis, new_convention, reverse=True) vec3 = vec2[permutation] * signs @@ -253,37 +286,41 @@ def test_convert_convention_obasis(): def test_convert_exceptions(): with pytest.raises(TypeError): - convert_convention_shell('abc', 'cb') + convert_convention_shell("abc", "cb") with pytest.raises(TypeError): - convert_convention_shell('abc', 'cbb') + convert_convention_shell("abc", "cbb") with pytest.raises(TypeError): - convert_convention_shell('aba', 'cba') + convert_convention_shell("aba", "cba") with pytest.raises(TypeError): - convert_convention_shell(['a', 'b', 'c'], ['a', 'b', 'd']) + convert_convention_shell(["a", "b", "c"], ["a", "b", "d"]) with pytest.raises(TypeError): - convert_convention_shell(['a', 'b', 'c'], ['a', 'b', '-d']) + convert_convention_shell(["a", "b", "c"], ["a", "b", "-d"]) def test_iter_cart_alphabet(): assert np.array(list(iter_cart_alphabet(0))).tolist() == [[0, 0, 0]] - assert np.array(list(iter_cart_alphabet(1))).tolist() == [ - [1, 0, 0], [0, 1, 0], [0, 0, 1]] + assert np.array(list(iter_cart_alphabet(1))).tolist() == [[1, 0, 0], [0, 1, 0], [0, 0, 1]] assert np.array(list(iter_cart_alphabet(2))).tolist() == [ - [2, 0, 0], [1, 1, 0], [1, 0, 1], - [0, 2, 0], [0, 1, 1], [0, 0, 2]] + [2, 0, 0], + [1, 1, 0], + [1, 0, 1], + [0, 2, 0], + [0, 1, 1], + [0, 0, 2], + ] def test_conventions(): for angmom in range(25): - assert HORTON2_CONVENTIONS[(angmom, 'c')] == CCA_CONVENTIONS[(angmom, 'c')] - assert HORTON2_CONVENTIONS[(0, 'c')] == ['1'] - assert HORTON2_CONVENTIONS[(1, 'c')] == ['x', 'y', 'z'] - assert HORTON2_CONVENTIONS[(2, 'c')] == ['xx', 'xy', 'xz', 'yy', 'yz', 'zz'] - assert (0, 'p') not in HORTON2_CONVENTIONS - assert (0, 'p') not in CCA_CONVENTIONS - assert (1, 'p') not in HORTON2_CONVENTIONS - assert (1, 'p') not in CCA_CONVENTIONS - assert HORTON2_CONVENTIONS[(2, 'p')] == ['c0', 'c1', 's1', 'c2', 's2'] - assert CCA_CONVENTIONS[(2, 'p')] == ['s2', 's1', 'c0', 'c1', 'c2'] - assert HORTON2_CONVENTIONS[(3, 'p')] == ['c0', 'c1', 's1', 'c2', 's2', 'c3', 's3'] - assert CCA_CONVENTIONS[(3, 'p')] == ['s3', 's2', 's1', 'c0', 'c1', 'c2', 'c3'] + assert HORTON2_CONVENTIONS[(angmom, "c")] == CCA_CONVENTIONS[(angmom, "c")] + assert HORTON2_CONVENTIONS[(0, "c")] == ["1"] + assert HORTON2_CONVENTIONS[(1, "c")] == ["x", "y", "z"] + assert HORTON2_CONVENTIONS[(2, "c")] == ["xx", "xy", "xz", "yy", "yz", "zz"] + assert (0, "p") not in HORTON2_CONVENTIONS + assert (0, "p") not in CCA_CONVENTIONS + assert (1, "p") not in HORTON2_CONVENTIONS + assert (1, "p") not in CCA_CONVENTIONS + assert HORTON2_CONVENTIONS[(2, "p")] == ["c0", "c1", "s1", "c2", "s2"] + assert CCA_CONVENTIONS[(2, "p")] == ["s2", "s1", "c0", "c1", "c2"] + assert HORTON2_CONVENTIONS[(3, "p")] == ["c0", "c1", "s1", "c2", "s2", "c3", "s3"] + assert CCA_CONVENTIONS[(3, "p")] == ["s3", "s2", "s1", "c0", "c1", "c2", "c3"] diff --git a/iodata/test/test_charmm.py b/iodata/test/test_charmm.py index 56825eaef..8a7ea6ccc 100644 --- a/iodata/test/test_charmm.py +++ b/iodata/test/test_charmm.py @@ -16,36 +16,34 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object """Test iodata.formats.orcalog module.""" -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal from ..api import load_one - -from ..utils import angstrom, amu +from ..utils import amu, angstrom try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_crambin(): # test CHARMM crd file of crambin - with path('iodata.test.data', 'crambin.crd') as fn_crd: + with as_file(files("iodata.test.data").joinpath("crambin.crd")) as fn_crd: mol = load_one(str(fn_crd)) assert len(mol.title) == 125 assert mol.atcoords.shape == (648, 3) assert_allclose(mol.atcoords[-1] / angstrom, [7.35403, -5.09628, 2.73659]) - assert mol.atffparams['attypes'].shape == (648,) - assert mol.atffparams['resnums'].shape == (648,) - assert mol.atffparams['resnames'].shape == (648,) - assert mol.atffparams['attypes'][-1] == 'OT2' - assert_equal(mol.atffparams['resnums'][46:48], [4, 4]) - assert mol.atffparams['resnames'][-1] == 'ASN' - assert mol.extra['segid'].shape == (648,) - assert mol.extra['resid'].shape == (648,) - assert mol.extra['segid'][-1] == 'MAIN' - assert mol.extra['resid'][-1] == 46 + assert mol.atffparams["attypes"].shape == (648,) + assert mol.atffparams["resnums"].shape == (648,) + assert mol.atffparams["resnames"].shape == (648,) + assert mol.atffparams["attypes"][-1] == "OT2" + assert_equal(mol.atffparams["resnums"][46:48], [4, 4]) + assert mol.atffparams["resnames"][-1] == "ASN" + assert mol.extra["segid"].shape == (648,) + assert mol.extra["resid"].shape == (648,) + assert mol.extra["segid"][-1] == "MAIN" + assert mol.extra["resid"][-1] == 46 assert mol.atmasses[-1] == 15.99900 * amu diff --git a/iodata/test/test_chgcar.py b/iodata/test/test_chgcar.py index 8074fb4a7..e9ee2df21 100644 --- a/iodata/test/test_chgcar.py +++ b/iodata/test/test_chgcar.py @@ -16,44 +16,43 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object """Test iodata.formats.chgcar module.""" import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal from ..api import load_one from ..utils import angstrom, volume try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_chgcar_oxygen(): - with path('iodata.test.data', 'CHGCAR.oxygen') as fn: + with as_file(files("iodata.test.data").joinpath("CHGCAR.oxygen")) as fn: mol = load_one(str(fn)) assert_equal(mol.atnums, 8) - assert_allclose(volume(mol.cellvecs), (10 * angstrom) ** 3, atol=1.e-10) + assert_allclose(volume(mol.cellvecs), (10 * angstrom) ** 3, atol=1.0e-10) assert_equal(mol.cube.shape, [2, 2, 2]) assert abs(mol.cube.origin).max() < 1e-10 - assert_allclose(mol.cube.axes, mol.cellvecs / 2, atol=1.e-10) + assert_allclose(mol.cube.axes, mol.cellvecs / 2, atol=1.0e-10) d = mol.cube.data - assert_allclose(d[0, 0, 0], 0.78406017013E+04 / volume(mol.cellvecs), atol=1.e-10) - assert_allclose(d[-1, -1, -1], 0.10024522914E+04 / volume(mol.cellvecs), atol=1.e-10) - assert_allclose(d[1, 0, 0], 0.76183317989E+04 / volume(mol.cellvecs), atol=1.e-10) + assert_allclose(d[0, 0, 0], 0.78406017013e04 / volume(mol.cellvecs), atol=1.0e-10) + assert_allclose(d[-1, -1, -1], 0.10024522914e04 / volume(mol.cellvecs), atol=1.0e-10) + assert_allclose(d[1, 0, 0], 0.76183317989e04 / volume(mol.cellvecs), atol=1.0e-10) def test_load_chgcar_water(): - with path('iodata.test.data', 'CHGCAR.water') as fn: + with as_file(files("iodata.test.data").joinpath("CHGCAR.water")) as fn: mol = load_one(str(fn)) - assert mol.title == 'unknown system' + assert mol.title == "unknown system" assert_equal(mol.atnums, [8, 1, 1]) coords = np.array([0.074983 * 15 + 0.903122 * 1, 0.903122 * 15, 0.000000]) - assert_allclose(mol.atcoords[1], coords, atol=1.e-7) - assert_allclose(volume(mol.cellvecs), 15 ** 3, atol=1.e-4) + assert_allclose(mol.atcoords[1], coords, atol=1.0e-7) + assert_allclose(volume(mol.cellvecs), 15**3, atol=1.0e-4) assert_equal(len(mol.cube.shape), 3) assert_equal(mol.cube.shape, (3, 3, 3)) - assert_allclose(mol.cube.axes, mol.cellvecs / 3, atol=1.e-10) + assert_allclose(mol.cube.axes, mol.cellvecs / 3, atol=1.0e-10) assert abs(mol.cube.origin).max() < 1e-10 diff --git a/iodata/test/test_cli.py b/iodata/test/test_cli.py index 5988ce5c0..110bef614 100644 --- a/iodata/test/test_cli.py +++ b/iodata/test/test_cli.py @@ -18,31 +18,30 @@ # -- """Unit tests for iodata.__main__.""" - -import os import functools +import os import subprocess +import sys -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal from ..__main__ import convert -from ..api import load_one, load_many +from ..api import load_many, load_one try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def _check_convert_one(myconvert, tmpdir): - outfn = os.path.join(tmpdir, 'tmp.xyz') - with path('iodata.test.data', 'hf_sto3g.fchk') as infn: + outfn = os.path.join(tmpdir, "tmp.xyz") + with as_file(files("iodata.test.data").joinpath("hf_sto3g.fchk")) as infn: myconvert(infn, outfn) iodata = load_one(outfn) assert iodata.natom == 2 assert_equal(iodata.atnums, [9, 1]) - assert_allclose(iodata.atcoords, - [[0.0, 0.0, 0.190484394], [0.0, 0.0, -1.71435955]]) + assert_allclose(iodata.atcoords, [[0.0, 0.0, 0.190484394], [0.0, 0.0, -1.71435955]]) def test_convert_one_autofmt(tmpdir): @@ -51,37 +50,38 @@ def test_convert_one_autofmt(tmpdir): def test_convert_one_manfmt(tmpdir): - myconvert = functools.partial(convert, many=False, infmt='fchk', outfmt='xyz') + myconvert = functools.partial(convert, many=False, infmt="fchk", outfmt="xyz") _check_convert_one(myconvert, tmpdir) def test_script_one_autofmt(tmpdir): def myconvert(infn, outfn): - subprocess.run(['python', '-m', 'iodata.__main__', infn, outfn], - check=True) + subprocess.run([sys.executable, "-m", "iodata.__main__", infn, outfn], check=True) + _check_convert_one(myconvert, tmpdir) def test_script_one_manfmt(tmpdir): def myconvert(infn, outfn): - subprocess.run(['python', '-m', 'iodata.__main__', infn, outfn, - '-i', 'fchk', '-o', 'xyz'], check=True) + subprocess.run( + [sys.executable, "-m", "iodata.__main__", infn, outfn, "-i", "fchk", "-o", "xyz"], + check=True, + ) + _check_convert_one(myconvert, tmpdir) def _check_convert_many(myconvert, tmpdir): - outfn = os.path.join(tmpdir, 'tmp.xyz') - with path('iodata.test.data', 'peroxide_relaxed_scan.fchk') as infn: + outfn = os.path.join(tmpdir, "tmp.xyz") + with as_file(files("iodata.test.data").joinpath("peroxide_relaxed_scan.fchk")) as infn: myconvert(infn, outfn) trj = list(load_many(outfn)) assert len(trj) == 13 for iodata in trj: assert iodata.natom == 4 assert_equal(iodata.atnums, [8, 8, 1, 1]) - assert_allclose(trj[1].atcoords[3], - [-1.85942837, -1.70565735, 0.0], atol=1e-5) - assert_allclose(trj[5].atcoords[0], - [0.0, 1.32466211, 0.0], atol=1e-5) + assert_allclose(trj[1].atcoords[3], [-1.85942837, -1.70565735, 0.0], atol=1e-5) + assert_allclose(trj[5].atcoords[0], [0.0, 1.32466211, 0.0], atol=1e-5) def test_convert_many_autofmt(tmpdir): @@ -90,19 +90,22 @@ def test_convert_many_autofmt(tmpdir): def test_convert_many_manfmt(tmpdir): - myconvert = functools.partial(convert, many=True, infmt='fchk', outfmt='xyz') + myconvert = functools.partial(convert, many=True, infmt="fchk", outfmt="xyz") _check_convert_many(myconvert, tmpdir) def test_script_many_autofmt(tmpdir): def myconvert(infn, outfn): - subprocess.run(['python', '-m', 'iodata.__main__', infn, outfn, '-m'], - check=True) + subprocess.run([sys.executable, "-m", "iodata.__main__", infn, outfn, "-m"], check=True) + _check_convert_many(myconvert, tmpdir) def test_script_many_manfmt(tmpdir): def myconvert(infn, outfn): - subprocess.run(['python', '-m', 'iodata.__main__', infn, outfn, - '-m', '-i', 'fchk', '-o', 'xyz'], check=True) + subprocess.run( + [sys.executable, "-m", "iodata.__main__", infn, outfn, "-m", "-i", "fchk", "-o", "xyz"], + check=True, + ) + _check_convert_many(myconvert, tmpdir) diff --git a/iodata/test/test_cp2klog.py b/iodata/test/test_cp2klog.py index 612538515..299edc735 100644 --- a/iodata/test/test_cp2klog.py +++ b/iodata/test/test_cp2klog.py @@ -19,39 +19,35 @@ """Test iodata.formats.cp2klog module.""" import pytest - -from numpy.testing import assert_equal, assert_allclose - -from .common import truncated_file, check_orthonormal +from numpy.testing import assert_allclose, assert_equal from ..api import load_one from ..overlap import compute_overlap +from .common import check_orthonormal, truncated_file try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_atom_si_uks(): - with path('iodata.test.data', 'atom_si.cp2k.out') as fn_out: + with as_file(files("iodata.test.data").joinpath("atom_si.cp2k.out")) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [14]) assert_equal(mol.atcorenums, [4]) - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert_equal(mol.mo.occsa, [1, 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0]) assert_equal(mol.mo.occsb, [1, 0, 0, 0]) - assert_allclose(mol.mo.energiesa, - [-0.398761, -0.154896, -0.154896, -0.154896], atol=1.e-4) - assert_allclose(mol.mo.energiesb, - [-0.334567, -0.092237, -0.092237, -0.092237], atol=1.e-4) - assert_allclose(mol.energy, -3.761587698067, atol=1.e-10) + assert_allclose(mol.mo.energiesa, [-0.398761, -0.154896, -0.154896, -0.154896], atol=1.0e-4) + assert_allclose(mol.mo.energiesb, [-0.334567, -0.092237, -0.092237, -0.092237], atol=1.0e-4) + assert_allclose(mol.energy, -3.761587698067, atol=1.0e-10) assert len(mol.obasis.shells) == 3 - assert mol.obasis.shells[0].kinds == ['c', 'c'] + assert mol.obasis.shells[0].kinds == ["c", "c"] assert_equal(mol.obasis.shells[1].angmoms, [1, 1]) - assert mol.obasis.shells[1].kinds == ['c', 'c'] + assert mol.obasis.shells[1].kinds == ["c", "c"] assert_equal(mol.obasis.shells[2].angmoms, [2]) - assert mol.obasis.shells[2].kinds == ['p'] + assert mol.obasis.shells[2].kinds == ["p"] # check mo normalization olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffsa, olp) @@ -59,32 +55,32 @@ def test_atom_si_uks(): def test_atom_o_rks(): - with path('iodata.test.data', 'atom_om2.cp2k.out') as fn_out: + with as_file(files("iodata.test.data").joinpath("atom_om2.cp2k.out")) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [8]) assert_equal(mol.atcorenums, [6]) - assert mol.mo.kind == 'restricted' + assert mol.mo.kind == "restricted" assert_equal(mol.mo.occs, [2, 2, 2, 2]) - assert_allclose(mol.mo.energies, [0.102709, 0.606458, 0.606458, 0.606458], atol=1.e-4) - assert_allclose(mol.energy, -15.464982778766, atol=1.e-10) + assert_allclose(mol.mo.energies, [0.102709, 0.606458, 0.606458, 0.606458], atol=1.0e-4) + assert_allclose(mol.energy, -15.464982778766, atol=1.0e-10) assert_equal(mol.obasis.shells[0].angmoms, [0, 0]) assert len(mol.obasis.shells) == 3 - assert mol.obasis.shells[0].kinds == ['c', 'c'] + assert mol.obasis.shells[0].kinds == ["c", "c"] assert_equal(mol.obasis.shells[1].angmoms, [1, 1]) - assert mol.obasis.shells[1].kinds == ['c', 'c'] + assert mol.obasis.shells[1].kinds == ["c", "c"] assert_equal(mol.obasis.shells[2].angmoms, [2]) - assert mol.obasis.shells[2].kinds == ['p'] + assert mol.obasis.shells[2].kinds == ["p"] # check mo normalization olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) def test_carbon_gs_ae_contracted(): - with path('iodata.test.data', 'carbon_gs_ae_contracted.cp2k.out') as fn_out: + with as_file(files("iodata.test.data").joinpath("carbon_gs_ae_contracted.cp2k.out")) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [6]) assert_equal(mol.atcorenums, [6]) - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert_allclose(mol.mo.occsa, [1, 1, 2 / 3, 2 / 3, 2 / 3]) assert_allclose(mol.mo.energiesa, [-10.058194, -0.526244, -0.214978, -0.214978, -0.214978]) assert_allclose(mol.mo.occsb, [1, 1, 0, 0, 0]) @@ -97,11 +93,12 @@ def test_carbon_gs_ae_contracted(): def test_carbon_gs_ae_uncontracted(): - with path('iodata.test.data', 'carbon_gs_ae_uncontracted.cp2k.out') as fn_out: + source = files("iodata.test.data").joinpath("carbon_gs_ae_uncontracted.cp2k.out") + with as_file(source) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [6]) assert_equal(mol.atcorenums, [6]) - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert_allclose(mol.mo.occsa, [1, 1, 2 / 3, 2 / 3, 2 / 3]) assert_allclose(mol.mo.energiesa, [-10.050076, -0.528162, -0.217626, -0.217626, -0.217626]) assert_allclose(mol.mo.occsb, [1, 1, 0, 0, 0]) @@ -114,11 +111,11 @@ def test_carbon_gs_ae_uncontracted(): def test_carbon_gs_pp_contracted(): - with path('iodata.test.data', 'carbon_gs_pp_contracted.cp2k.out') as fn_out: + with as_file(files("iodata.test.data").joinpath("carbon_gs_pp_contracted.cp2k.out")) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [6]) assert_equal(mol.atcorenums, [4]) - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert_allclose(mol.mo.occsa, [1, 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0]) assert_allclose(mol.mo.energiesa, [-0.528007, -0.219974, -0.219974, -0.219974]) assert_allclose(mol.mo.occsb, [1, 0, 0, 0]) @@ -131,11 +128,12 @@ def test_carbon_gs_pp_contracted(): def test_carbon_gs_pp_uncontracted(): - with path('iodata.test.data', 'carbon_gs_pp_uncontracted.cp2k.out') as fn_out: + source = files("iodata.test.data").joinpath("carbon_gs_pp_uncontracted.cp2k.out") + with as_file(source) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [6]) assert_equal(mol.atcorenums, [4]) - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert_allclose(mol.mo.occsa, [1, 2 / 3, 2 / 3, 2 / 3]) assert_allclose(mol.mo.energiesa, [-0.528146, -0.219803, -0.219803, -0.219803]) assert_allclose(mol.mo.occsb, [1, 0, 0, 0]) @@ -148,11 +146,11 @@ def test_carbon_gs_pp_uncontracted(): def test_carbon_sc_ae_contracted(): - with path('iodata.test.data', 'carbon_sc_ae_contracted.cp2k.out') as fn_out: + with as_file(files("iodata.test.data").joinpath("carbon_sc_ae_contracted.cp2k.out")) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [6]) assert_equal(mol.atcorenums, [6]) - assert mol.mo.kind == 'restricted' + assert mol.mo.kind == "restricted" assert_allclose(mol.mo.occs, [2, 2, 2 / 3, 2 / 3, 2 / 3]) assert_allclose(mol.mo.energies, [-10.067251, -0.495823, -0.187878, -0.187878, -0.187878]) assert_allclose(mol.energy, -37.793939631890) @@ -162,11 +160,12 @@ def test_carbon_sc_ae_contracted(): def test_carbon_sc_ae_uncontracted(): - with path('iodata.test.data', 'carbon_sc_ae_uncontracted.cp2k.out') as fn_out: + source = files("iodata.test.data").joinpath("carbon_sc_ae_uncontracted.cp2k.out") + with as_file(source) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [6]) assert_equal(mol.atcorenums, [6]) - assert mol.mo.kind == 'restricted' + assert mol.mo.kind == "restricted" assert_allclose(mol.mo.occs, [2, 2, 2 / 3, 2 / 3, 2 / 3]) assert_allclose(mol.mo.energies, [-10.062206, -0.499716, -0.192580, -0.192580, -0.192580]) assert_allclose(mol.energy, -37.800453482378) @@ -176,11 +175,11 @@ def test_carbon_sc_ae_uncontracted(): def test_carbon_sc_pp_contracted(): - with path('iodata.test.data', 'carbon_sc_pp_contracted.cp2k.out') as fn_out: + with as_file(files("iodata.test.data").joinpath("carbon_sc_pp_contracted.cp2k.out")) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [6]) assert_equal(mol.atcorenums, [4]) - assert mol.mo.kind == 'restricted' + assert mol.mo.kind == "restricted" assert_allclose(mol.mo.occs, [2, 2 / 3, 2 / 3, 2 / 3]) assert_allclose(mol.mo.energies, [-0.500732, -0.193138, -0.193138, -0.193138]) assert_allclose(mol.energy, -5.350765755382) @@ -190,11 +189,12 @@ def test_carbon_sc_pp_contracted(): def test_carbon_sc_pp_uncontracted(): - with path('iodata.test.data', 'carbon_sc_pp_uncontracted.cp2k.out') as fn_out: + source = files("iodata.test.data").joinpath("carbon_sc_pp_uncontracted.cp2k.out") + with as_file(source) as fn_out: mol = load_one(str(fn_out)) assert_equal(mol.atnums, [6]) assert_equal(mol.atcorenums, [4]) - assert mol.mo.kind == 'restricted' + assert mol.mo.kind == "restricted" assert_allclose(mol.mo.occs, [2, 2 / 3, 2 / 3, 2 / 3]) assert_allclose(mol.mo.energies, [-0.500238, -0.192365, -0.192365, -0.192365]) assert_allclose(mol.energy, -5.352864672201) @@ -204,20 +204,20 @@ def test_carbon_sc_pp_uncontracted(): def test_errors(tmpdir): - with path('iodata.test.data', 'carbon_sc_pp_uncontracted.cp2k.out') as fn_test: - with truncated_file(fn_test, 0, 0, tmpdir) as fn: - with pytest.raises(IOError): - load_one(fn) - with truncated_file(fn_test, 107, 10, tmpdir) as fn: - with pytest.raises(IOError): - load_one(fn) - with truncated_file(fn_test, 357, 10, tmpdir) as fn: - with pytest.raises(IOError): - load_one(fn) - with truncated_file(fn_test, 405, 10, tmpdir) as fn: - with pytest.raises(IOError): - load_one(fn) - with path('iodata.test.data', 'carbon_gs_pp_uncontracted.cp2k.out') as fn_test: - with truncated_file(fn_test, 456, 10, tmpdir) as fn: - with pytest.raises(IOError): - load_one(fn) + source = files("iodata.test.data").joinpath("carbon_sc_pp_uncontracted.cp2k.out") + with as_file(source) as fn_test: + with truncated_file(fn_test, 0, 0, tmpdir) as fn, pytest.raises(IOError): + load_one(fn) + with truncated_file(fn_test, 107, 10, tmpdir) as fn, pytest.raises(IOError): + load_one(fn) + with truncated_file(fn_test, 357, 10, tmpdir) as fn, pytest.raises(IOError): + load_one(fn) + with truncated_file(fn_test, 405, 10, tmpdir) as fn, pytest.raises(IOError): + load_one(fn) + source = files("iodata.test.data").joinpath("carbon_gs_pp_uncontracted.cp2k.out") + with ( + as_file(source) as fn_test, + truncated_file(fn_test, 456, 10, tmpdir) as fn, + pytest.raises(IOError), + ): + load_one(fn) diff --git a/iodata/test/test_cube.py b/iodata/test/test_cube.py index 4e1a605ff..543eedec9 100644 --- a/iodata/test/test_cube.py +++ b/iodata/test/test_cube.py @@ -16,69 +16,61 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object """Test iodata.formats.cube module.""" - import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from ..api import load_one, dump_one +from ..api import dump_one, load_one try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_aelta(): - with path('iodata.test.data', 'aelta.cube') as fn_cube: + with as_file(files("iodata.test.data").joinpath("aelta.cube")) as fn_cube: mol = load_one(str(fn_cube)) - assert mol.title == 'Some random cube for testing (sort of) useless data' + assert mol.title == "Some random cube for testing (sort of) useless data" assert_equal(mol.natom, 72) - assert_allclose(mol.atcoords[5, 0], 27.275511, atol=1.e-5) - assert_allclose(mol.atcoords[-2, 2], 26.460812, atol=1.e-5) + assert_allclose(mol.atcoords[5, 0], 27.275511, atol=1.0e-5) + assert_allclose(mol.atcoords[-2, 2], 26.460812, atol=1.0e-5) assert_equal(mol.cube.shape, (12, 12, 12)) - my_cellvecs = np.array([[1.8626, 0.1, 0.0], - [0.0, 1.8626, 0.0], - [0.0, 0.0, 1.8626]], dtype=np.float) * 12 - assert_allclose(mol.cellvecs, my_cellvecs, atol=1.e-5) - my_axes = np.array([[1.8626, 0.1, 0.0], - [0.0, 1.8626, 0.0], - [0.0, 0.0, 1.8626]], dtype=np.float) - assert_allclose(mol.cube.axes, my_axes, atol=1.e-5) - assert_allclose(mol.cube.origin, np.array([0.0, 1.2, 0.0]), atol=1.e-10) - - assert_allclose(mol.cube.data[0, 0, 0], 9.49232e-06, atol=1.e-12) - assert_allclose(mol.cube.data[-1, -1, -1], 2.09856e-04, atol=1.e-10) + my_cellvecs = ( + np.array([[1.8626, 0.1, 0.0], [0.0, 1.8626, 0.0], [0.0, 0.0, 1.8626]], dtype=float) * 12 + ) + assert_allclose(mol.cellvecs, my_cellvecs, atol=1.0e-5) + my_axes = np.array([[1.8626, 0.1, 0.0], [0.0, 1.8626, 0.0], [0.0, 0.0, 1.8626]], dtype=float) + assert_allclose(mol.cube.axes, my_axes, atol=1.0e-5) + assert_allclose(mol.cube.origin, np.array([0.0, 1.2, 0.0]), atol=1.0e-10) + + assert_allclose(mol.cube.data[0, 0, 0], 9.49232e-06, atol=1.0e-12) + assert_allclose(mol.cube.data[-1, -1, -1], 2.09856e-04, atol=1.0e-10) pn = mol.atcorenums - assert_allclose(pn[0], 1.0, atol=1.e-10) - assert_allclose(pn[1], 0.1, atol=1.e-10) - assert_allclose(pn[-2], 0.2, atol=1.e-10) - assert_allclose(pn[-1], mol.atnums[-1], atol=1.e-10) + assert_allclose(pn[0], 1.0, atol=1.0e-10) + assert_allclose(pn[1], 0.1, atol=1.0e-10) + assert_allclose(pn[-2], 0.2, atol=1.0e-10) + assert_allclose(pn[-1], mol.atnums[-1], atol=1.0e-10) def test_load_dump_load_aelta(tmpdir): - with path('iodata.test.data', 'aelta.cube') as fn_cube1: + with as_file(files("iodata.test.data").joinpath("aelta.cube")) as fn_cube1: mol1 = load_one(str(fn_cube1)) - fn_cube2 = '%s/%s' % (tmpdir, 'aelta.cube') + fn_cube2 = "{}/{}".format(tmpdir, "aelta.cube") dump_one(mol1, fn_cube2) mol2 = load_one(fn_cube2) with open(fn_cube2) as f: - line_counter = 0 block_counter = 0 - for line in f: - line_counter += 1 - if line_counter > 6 + len(mol2.atnums): + for iline, line in enumerate(f): + if iline > 6 + len(mol2.atnums): if mol2.cube.shape[2] % 6 == 0: assert len(line.split()) == 6 if mol2.cube.shape[2] % 6 != 0: - block_line_counter = line_counter - ( - 6 - + len(mol2.atnums) - + block_counter * (mol2.cube.shape[2] // 6 + 1) + block_line_counter = iline - ( + 6 + len(mol2.atnums) + block_counter * (mol2.cube.shape[2] // 6 + 1) ) if 1 <= block_line_counter <= mol2.cube.shape[2] // 6: assert len(line.split()) == 6 @@ -86,50 +78,53 @@ def test_load_dump_load_aelta(tmpdir): assert len(line.split()) == mol2.cube.shape[2] % 6 assert mol1.title == mol2.title - assert_allclose(mol1.atcoords, mol2.atcoords, atol=1.e-4) + assert_allclose(mol1.atcoords, mol2.atcoords, atol=1.0e-4) assert_equal(mol1.atnums, mol2.atnums) cube1 = mol1.cube cube2 = mol2.cube - assert_allclose(cube1.axes, cube2.axes, atol=1.e-4) + assert_allclose(cube1.axes, cube2.axes, atol=1.0e-4) assert_equal(cube1.shape, cube2.shape) - assert_allclose(mol1.cube.data, mol2.cube.data, atol=1.e-4) - assert_allclose(mol1.atcorenums, mol2.atcorenums, atol=1.e-4) + assert_allclose(mol1.cube.data, mol2.cube.data, atol=1.0e-4) + assert_allclose(mol1.atcorenums, mol2.atcorenums, atol=1.0e-4) def test_load_dump_h2o_5points(tmpdir): # load cube file - with path('iodata.test.data', 'cubegen_h2o_5points.cube') as fn_cube1: + with as_file(files("iodata.test.data").joinpath("cubegen_h2o_5points.cube")) as fn_cube1: mol1 = load_one(str(fn_cube1)) # write cube file in a temporary directory - fn_cube2 = tmpdir.join('iodata_h2o_5points.cube') + fn_cube2 = tmpdir.join("iodata_h2o_5points.cube") dump_one(mol1, fn_cube2) # read the contents as string (skip the first 2 lines) & compare - content1 = open(fn_cube1, "r").read().split("\n", 2)[-1] + with open(fn_cube1) as f: + content1 = f.read().split("\n", 2)[-1] content2 = fn_cube2.read().split("\n", 2)[-1] assert content1 == content2 def test_load_dump_ch4_6points(tmpdir): # load cube file - with path('iodata.test.data', 'cubegen_ch4_6points.cube') as fn_cube1: + with as_file(files("iodata.test.data").joinpath("cubegen_ch4_6points.cube")) as fn_cube1: mol1 = load_one(str(fn_cube1)) # write cube file in a temporary directory - fn_cube2 = tmpdir.join('iodata_ch4_6points.cube') + fn_cube2 = tmpdir.join("iodata_ch4_6points.cube") dump_one(mol1, fn_cube2) # read the contents as string (skip the first 2 lines) & compare - content1 = open(fn_cube1, "r").read().split("\n", 2)[-1] + with open(fn_cube1) as f: + content1 = f.read().split("\n", 2)[-1] content2 = fn_cube2.read().split("\n", 2)[-1] assert content1 == content2 def test_load_dump_nh3_7points(tmpdir): # load cube file - with path('iodata.test.data', 'cubegen_nh3_7points.cube') as fn_cube1: + with as_file(files("iodata.test.data").joinpath("cubegen_nh3_7points.cube")) as fn_cube1: mol1 = load_one(str(fn_cube1)) # write cube file in a temporary directory - fn_cube2 = tmpdir.join('iodata_nh3_7points.cube') + fn_cube2 = tmpdir.join("iodata_nh3_7points.cube") dump_one(mol1, fn_cube2) # read the contents as string (skip the first 2 lines) & compare - content1 = open(fn_cube1, "r").read().split("\n", 2)[-1] + with open(fn_cube1) as f: + content1 = f.read().split("\n", 2)[-1] content2 = fn_cube2.read().split("\n", 2)[-1] assert content1 == content2 diff --git a/iodata/test/test_extxyz.py b/iodata/test/test_extxyz.py index 5e6ac1396..9362c5fd7 100644 --- a/iodata/test/test_extxyz.py +++ b/iodata/test/test_extxyz.py @@ -19,30 +19,31 @@ """Test iodata.formats.extxyz module.""" import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from ..api import load_one, load_many +from ..api import load_many, load_one from ..utils import angstrom + try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_fcc_extended(): - with path('iodata.test.data', 'al_fcc.xyz') as fn_xyz: - mol = load_one(str(fn_xyz), fmt='extxyz') - assert hasattr(mol, 'energy') + with as_file(files("iodata.test.data").joinpath("al_fcc.xyz")) as fn_xyz: + mol = load_one(str(fn_xyz), fmt="extxyz") + assert hasattr(mol, "energy") assert isinstance(mol.energy, float) assert_allclose(mol.energy, -112.846680723) - assert hasattr(mol, 'cellvecs') + assert hasattr(mol, "cellvecs") assert mol.cellvecs.dtype == float assert_allclose(mol.cellvecs, np.eye(3) * 7.6 * angstrom) - assert 'pbc' in mol.extra - assert mol.extra['pbc'].dtype == bool - assert_equal(mol.extra['pbc'], np.array([True, False, True])) - assert 'species' in mol.extra - assert_equal(mol.extra['species'], np.array(['Al'] * 32)) + assert "pbc" in mol.extra + assert mol.extra["pbc"].dtype == bool + assert_equal(mol.extra["pbc"], np.array([True, False, True])) + assert "species" in mol.extra + assert_equal(mol.extra["species"], np.array(["Al"] * 32)) assert mol.atgradient.shape == (mol.natom, 3) assert_allclose(mol.atgradient[0, 0], -0.285831) assert_allclose(mol.atgradient[2, 1], 0.268537) @@ -51,8 +52,8 @@ def test_load_fcc_extended(): def test_load_mgo(): - with path('iodata.test.data', 'mgo.xyz') as fn_xyz: - mol = load_one(str(fn_xyz), fmt='extxyz') + with as_file(files("iodata.test.data").joinpath("mgo.xyz")) as fn_xyz: + mol = load_one(str(fn_xyz), fmt="extxyz") assert_equal(mol.atnums, [12] * 4 + [8] * 4) assert_equal(mol.atcoords[3], np.array([1, 1, 0]) * 2.10607000 * angstrom) assert_equal(mol.extra["spacegroup"], ["F", "m", "-3", "m"]) @@ -62,21 +63,22 @@ def test_load_mgo(): def test_load_many_extended(): - with path('iodata.test.data', 'water_extended_trajectory.xyz') as fn_xyz: - mols = list(load_many(str(fn_xyz), fmt='extxyz')) + with as_file(files("iodata.test.data").joinpath("water_extended_trajectory.xyz")) as fn_xyz: + mols = list(load_many(str(fn_xyz), fmt="extxyz")) assert len(mols) == 3 - assert 'some_label' in mols[0].extra - assert_equal(mols[0].extra['some_label'], - np.array([[True, True], [False, True], [False, False]])) - assert 'is_true' in mols[0].extra - assert mols[0].extra['is_true'] - assert hasattr(mols[0], 'charge') + assert "some_label" in mols[0].extra + assert_equal( + mols[0].extra["some_label"], np.array([[True, True], [False, True], [False, False]]) + ) + assert "is_true" in mols[0].extra + assert mols[0].extra["is_true"] + assert hasattr(mols[0], "charge") assert_allclose(mols[0].charge, 0) - assert 'pi' in mols[1].extra - assert_equal(mols[1].extra['pi'], 3.14) + assert "pi" in mols[1].extra + assert_equal(mols[1].extra["pi"], 3.14) assert_equal(mols[1].atnums, np.array([8, 1, 1, 1])) assert_equal(mols[1].atcoords[-1, 2], -12 * angstrom) assert_equal(mols[2].atnums, np.array([8, 1, 1])) - assert hasattr(mols[2], 'atmasses') + assert hasattr(mols[2], "atmasses") assert mols[2].atmasses.dtype == float assert_allclose(mols[2].atmasses, np.array([29164.39290107, 1837.47159474, 1837.47159474])) diff --git a/iodata/test/test_fchk.py b/iodata/test/test_fchk.py index e676371e7..54d695259 100644 --- a/iodata/test/test_fchk.py +++ b/iodata/test/test_fchk.py @@ -16,71 +16,75 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object,no-member """Test iodata.formats.fchk module.""" - import os +from typing import Optional import numpy as np -from numpy.testing import assert_equal, assert_allclose - import pytest +from numpy.testing import assert_allclose, assert_equal -from ..api import load_one, load_many, dump_one +from ..api import dump_one, load_many, load_one from ..overlap import compute_overlap from ..utils import check_dm - -from .common import check_orthonormal, compare_mols, load_one_warning, compute_1rdm +from .common import check_orthonormal, compare_mols, compute_1rdm, load_one_warning from .test_molekel import compare_mols_diff_formats try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_fchk_nonexistent(): - with pytest.raises(IOError): - with path('iodata.test.data', 'fubar_crap.fchk') as fn: - load_one(str(fn)) + with ( + pytest.raises(IOError), + as_file(files("iodata.test.data").joinpath("fubar_crap.fchk")) as fn, + ): + load_one(str(fn)) def load_fchk_helper(fn_fchk): """Load a testing fchk file with iodata.iodata.load_one.""" - with path('iodata.test.data', fn_fchk) as fn: + with as_file(files("iodata.test.data").joinpath(fn_fchk)) as fn: return load_one(fn) def test_load_fchk_hf_sto3g_num(): - mol = load_fchk_helper('hf_sto3g.fchk') - assert mol.title == 'hf_sto3g' - assert mol.run_type == 'energy' - assert mol.lot == 'rhf' - assert mol.obasis_name == 'sto-3g' - assert mol.mo.kind == 'restricted' + mol = load_fchk_helper("hf_sto3g.fchk") + assert mol.title == "hf_sto3g" + assert mol.run_type == "energy" + assert mol.lot == "rhf" + assert mol.obasis_name == "sto-3g" + assert mol.mo.kind == "restricted" assert mol.spinpol == 0 assert mol.obasis.nbasis == 6 assert len(mol.obasis.shells) == 3 shell0 = mol.obasis.shells[0] assert shell0.icenter == 0 assert shell0.angmoms == [0] - assert shell0.kinds == ['c'] - assert_allclose(shell0.exponents, np.array([1.66679134E+02, 3.03608123E+01, 8.21682067E+00])) - assert_allclose(shell0.coeffs, - np.array([[1.54328967E-01], [5.35328142E-01], [4.44634542E-01]])) + assert shell0.kinds == ["c"] + assert_allclose(shell0.exponents, np.array([1.66679134e02, 3.03608123e01, 8.21682067e00])) + assert_allclose(shell0.coeffs, np.array([[1.54328967e-01], [5.35328142e-01], [4.44634542e-01]])) assert shell0.nprim == 3 assert shell0.ncon == 1 assert shell0.nbasis == 1 shell1 = mol.obasis.shells[1] assert shell1.icenter == 0 assert shell1.angmoms == [0, 1] - assert shell1.kinds == ['c', 'c'] - assert_allclose(shell1.exponents, np.array([6.46480325E+00, 1.50228124E+00, 4.88588486E-01])) - assert_allclose(shell1.coeffs, - np.array([[-9.99672292E-02, 1.55916275E-01], - [3.99512826E-01, 6.07683719E-01], - [7.00115469E-01, 3.91957393E-01]])) + assert shell1.kinds == ["c", "c"] + assert_allclose(shell1.exponents, np.array([6.46480325e00, 1.50228124e00, 4.88588486e-01])) + assert_allclose( + shell1.coeffs, + np.array( + [ + [-9.99672292e-02, 1.55916275e-01], + [3.99512826e-01, 6.07683719e-01], + [7.00115469e-01, 3.91957393e-01], + ] + ), + ) assert shell1.nprim == 3 assert shell1.ncon == 2 assert shell1.nbasis == 4 @@ -88,21 +92,33 @@ def test_load_fchk_hf_sto3g_num(): assert shell2.nprim == 3 assert shell2.ncon == 1 assert shell2.nbasis == 1 - assert mol.obasis.primitive_normalization == 'L2' + assert mol.obasis.primitive_normalization == "L2" assert len(mol.atcoords) == len(mol.atnums) assert mol.atcoords.shape[1] == 3 assert len(mol.atnums) == 2 - assert_allclose(mol.energy, -9.856961609951867E+01) - assert_allclose(mol.atcharges['mulliken'], [0.45000000E+00, 4.22300000E+00]) - assert_allclose(mol.atcharges['npa'], [3.50000000E+00, 1.32000000E+00]) - assert_allclose(mol.atcharges['esp'], [0.77700000E+00, 0.66600000E+00]) + assert_allclose(mol.energy, -9.856961609951867e01) + assert_allclose(mol.atcharges["mulliken"], [0.45000000e00, 4.22300000e00]) + assert_allclose(mol.atcharges["npa"], [3.50000000e00, 1.32000000e00]) + assert_allclose(mol.atcharges["esp"], [0.77700000e00, 0.66600000e00]) olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) +def test_load_fchk_hf_water_atcharges(): + mol = load_fchk_helper("water_atcharges.fchk") + assert_allclose(mol.atcharges["mulliken"], [-3.91150532e-01, 1.96895396e-01, 1.94255137e-01]) + assert_allclose(mol.atcharges["npa"], [-4.98161654e-01, 2.50757174e-01, 2.47404480e-01]) + assert_allclose(mol.atcharges["esp"], [-4.47363368e-01, 2.24922518e-01, 2.22440849e-01]) + assert_allclose(mol.atcharges["mbs"], [-2.90505882e-01, 1.45850946e-01, 1.44654936e-01]) + # hirshfeld is under label `Type 6 charges` in fchk + assert_allclose(mol.atcharges["hirshfeld"], [-3.37450356e-01, 1.68988978e-01, 1.68461239e-01]) + # cm5 is under label `Type 7 charges` in fchk + assert_allclose(mol.atcharges["cm5"], [-3.77750403e-01, 1.89459551e-01, 1.88290713e-01]) + + def test_load_fchk_h_sto3g_num(): - mol = load_fchk_helper('h_sto3g.fchk') - assert mol.title == 'h_sto3g' + mol = load_fchk_helper("h_sto3g.fchk") + assert mol.title == "h_sto3g" assert len(mol.obasis.shells) == 1 assert mol.obasis.nbasis == 1 assert mol.obasis.shells[0].nprim == 3 @@ -112,27 +128,27 @@ def test_load_fchk_h_sto3g_num(): olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffsa, olp) check_orthonormal(mol.mo.coeffsb, olp) - assert_allclose(mol.energy, -4.665818503844346E-01) - assert_allclose(mol.one_rdms['scf'], mol.one_rdms['scf'].T) - assert_allclose(mol.one_rdms['scf_spin'], mol.one_rdms['scf_spin'].T) + assert_allclose(mol.energy, -4.665818503844346e-01) + assert_allclose(mol.one_rdms["scf"], mol.one_rdms["scf"].T) + assert_allclose(mol.one_rdms["scf_spin"], mol.one_rdms["scf_spin"].T) def test_load_fchk_o2_cc_pvtz_pure_num(): - mol = load_fchk_helper('o2_cc_pvtz_pure.fchk') - assert mol.run_type == 'energy' - assert mol.lot == 'rhf' - assert mol.obasis_name == 'cc-pvtz' + mol = load_fchk_helper("o2_cc_pvtz_pure.fchk") + assert mol.run_type == "energy" + assert mol.lot == "rhf" + assert mol.obasis_name == "cc-pvtz" assert len(mol.obasis.shells) == 20 assert mol.obasis.nbasis == 60 assert mol.natom == 2 olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) - assert_allclose(mol.energy, -1.495944878699246E+02) - assert_allclose(mol.one_rdms['scf'], mol.one_rdms['scf'].T) + assert_allclose(mol.energy, -1.495944878699246e02) + assert_allclose(mol.one_rdms["scf"], mol.one_rdms["scf"].T) def test_load_fchk_o2_cc_pvtz_cart_num(): - mol = load_fchk_helper('o2_cc_pvtz_cart.fchk') + mol = load_fchk_helper("o2_cc_pvtz_cart.fchk") assert len(mol.obasis.shells) == 20 assert mol.obasis.nbasis == 70 assert len(mol.atcoords) == len(mol.atnums) @@ -140,84 +156,84 @@ def test_load_fchk_o2_cc_pvtz_cart_num(): assert len(mol.atnums) == 2 olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) - assert_allclose(mol.energy, -1.495953594545721E+02) - assert_allclose(mol.one_rdms['scf'], mol.one_rdms['scf'].T) + assert_allclose(mol.energy, -1.495953594545721e02) + assert_allclose(mol.one_rdms["scf"], mol.one_rdms["scf"].T) def test_load_fchk_water_sto3g_hf(): - mol = load_fchk_helper('water_sto3g_hf_g03.fchk') + mol = load_fchk_helper("water_sto3g_hf_g03.fchk") assert len(mol.obasis.shells) == 4 assert mol.obasis.nbasis == 7 assert len(mol.atcoords) == len(mol.atnums) assert mol.atcoords.shape[1] == 3 assert len(mol.atnums) == 3 - assert_allclose(mol.mo.energies[0], -2.02333942E+01, atol=1.e-7) - assert_allclose(mol.mo.energies[-1], 7.66134805E-01, atol=1.e-7) - assert_allclose(mol.mo.coeffs[0, 0], 0.99410, atol=1.e-4) - assert_allclose(mol.mo.coeffs[1, 0], 0.02678, atol=1.e-4) - assert_allclose(mol.mo.coeffs[-1, 2], -0.44154, atol=1.e-4) + assert_allclose(mol.mo.energies[0], -2.02333942e01, atol=1.0e-7) + assert_allclose(mol.mo.energies[-1], 7.66134805e-01, atol=1.0e-7) + assert_allclose(mol.mo.coeffs[0, 0], 0.99410, atol=1.0e-4) + assert_allclose(mol.mo.coeffs[1, 0], 0.02678, atol=1.0e-4) + assert_allclose(mol.mo.coeffs[-1, 2], -0.44154, atol=1.0e-4) assert abs(mol.mo.coeffs[3, -1]) < 1e-4 - assert_allclose(mol.mo.coeffs[4, -1], -0.82381, atol=1.e-4) + assert_allclose(mol.mo.coeffs[4, -1], -0.82381, atol=1.0e-4) assert_equal(mol.mo.occs.sum(), 10) assert_equal(mol.mo.occs.min(), 0.0) assert_equal(mol.mo.occs.max(), 2.0) - assert_allclose(mol.energy, -7.495929232844363E+01) + assert_allclose(mol.energy, -7.495929232844363e01) olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) check_orthonormal(mol.mo.coeffsa, olp) - assert_allclose(mol.one_rdms['scf'], mol.one_rdms['scf'].T) + assert_allclose(mol.one_rdms["scf"], mol.one_rdms["scf"].T) def test_load_fchk_water_sto3g_hf_qchem(): # test FCHK file generated by QChem-5.2.1 which misses 'Total Energy' field - mol = load_fchk_helper('water_hf_sto3g_qchem5.2.fchk') + mol = load_fchk_helper("water_hf_sto3g_qchem5.2.fchk") assert mol.energy is None assert len(mol.obasis.shells) == 4 assert mol.obasis.nbasis == 7 assert mol.atcoords.shape == (3, 3) assert_equal(mol.atnums, [8, 1, 1]) - assert_allclose(mol.mo.energies[0], -2.02531445E+01, atol=1.e-7) - assert_allclose(mol.mo.energies[-1], 5.39983862E-01, atol=1.e-7) - assert_allclose(mol.mo.coeffs[0, 0], 9.94571479E-01, atol=1.e-7) - assert_allclose(mol.mo.coeffs[1, 0], 2.30506686E-02, atol=1.e-7) - assert_allclose(mol.mo.coeffs[-1, -1], 6.71330643E-01, atol=1.e-7) - assert_equal(mol.mo.occs, [2., 2., 2., 2., 2., 0., 0.]) + assert_allclose(mol.mo.energies[0], -2.02531445e01, atol=1.0e-7) + assert_allclose(mol.mo.energies[-1], 5.39983862e-01, atol=1.0e-7) + assert_allclose(mol.mo.coeffs[0, 0], 9.94571479e-01, atol=1.0e-7) + assert_allclose(mol.mo.coeffs[1, 0], 2.30506686e-02, atol=1.0e-7) + assert_allclose(mol.mo.coeffs[-1, -1], 6.71330643e-01, atol=1.0e-7) + assert_equal(mol.mo.occs, [2.0, 2.0, 2.0, 2.0, 2.0, 0.0, 0.0]) # check molecular orbitals are orthonormal olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) check_orthonormal(mol.mo.coeffsa, olp) # check 1-RDM - assert_allclose(mol.one_rdms['scf'], mol.one_rdms['scf'].T) - assert_allclose(mol.one_rdms['scf'], compute_1rdm(mol)) + assert_allclose(mol.one_rdms["scf"], mol.one_rdms["scf"].T) + assert_allclose(mol.one_rdms["scf"], compute_1rdm(mol)) def test_load_fchk_lih_321g_hf(): - mol = load_fchk_helper('li_h_3-21G_hf_g09.fchk') + mol = load_fchk_helper("li_h_3-21G_hf_g09.fchk") assert len(mol.obasis.shells) == 5 assert mol.obasis.nbasis == 11 assert len(mol.atcoords) == len(mol.atnums) assert mol.atcoords.shape[1] == 3 assert len(mol.atnums) == 2 - assert_allclose(mol.energy, -7.687331212191968E+00) - - assert_allclose(mol.mo.energiesa[0], (-2.76117), atol=1.e-4) - assert_allclose(mol.mo.energiesa[-1], 0.97089, atol=1.e-4) - assert_allclose(mol.mo.coeffsa[0, 0], 0.99105, atol=1.e-4) - assert_allclose(mol.mo.coeffsa[1, 0], 0.06311, atol=1.e-4) - assert mol.mo.coeffsa[3, 2] < 1.e-4 - assert_allclose(mol.mo.coeffsa[-1, 9], 0.13666, atol=1.e-4) - assert_allclose(mol.mo.coeffsa[4, -1], 0.17828, atol=1.e-4) + assert_allclose(mol.energy, -7.687331212191968e00) + + assert_allclose(mol.mo.energiesa[0], (-2.76117), atol=1.0e-4) + assert_allclose(mol.mo.energiesa[-1], 0.97089, atol=1.0e-4) + assert_allclose(mol.mo.coeffsa[0, 0], 0.99105, atol=1.0e-4) + assert_allclose(mol.mo.coeffsa[1, 0], 0.06311, atol=1.0e-4) + assert mol.mo.coeffsa[3, 2] < 1.0e-4 + assert_allclose(mol.mo.coeffsa[-1, 9], 0.13666, atol=1.0e-4) + assert_allclose(mol.mo.coeffsa[4, -1], 0.17828, atol=1.0e-4) assert_equal(mol.mo.occsa.sum(), 2) assert_equal(mol.mo.occsa.min(), 0.0) assert_equal(mol.mo.occsa.max(), 1.0) - assert_allclose(mol.mo.energiesb[0], -2.76031, atol=1.e-4) - assert_allclose(mol.mo.energiesb[-1], 1.13197, atol=1.e-4) - assert_allclose(mol.mo.coeffsb[0, 0], 0.99108, atol=1.e-4) - assert_allclose(mol.mo.coeffsb[1, 0], 0.06295, atol=1.e-4) + assert_allclose(mol.mo.energiesb[0], -2.76031, atol=1.0e-4) + assert_allclose(mol.mo.energiesb[-1], 1.13197, atol=1.0e-4) + assert_allclose(mol.mo.coeffsb[0, 0], 0.99108, atol=1.0e-4) + assert_allclose(mol.mo.coeffsb[1, 0], 0.06295, atol=1.0e-4) assert abs(mol.mo.coeffsb[3, 2]) < 1e-4 - assert_allclose(mol.mo.coeffsb[-1, 9], 0.80875, atol=1.e-4) - assert_allclose(mol.mo.coeffsb[4, -1], -0.15503, atol=1.e-4) + assert_allclose(mol.mo.coeffsb[-1, 9], 0.80875, atol=1.0e-4) + assert_allclose(mol.mo.coeffsb[4, -1], -0.15503, atol=1.0e-4) assert_equal(mol.mo.occsb.sum(), 1) assert_equal(mol.mo.occsb.min(), 0.0) assert_equal(mol.mo.occsb.max(), 1.0) @@ -227,30 +243,30 @@ def test_load_fchk_lih_321g_hf(): olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffsa, olp) check_orthonormal(mol.mo.coeffsb, olp) - assert_allclose(mol.one_rdms['scf'], mol.one_rdms['scf'].T) - assert_allclose(mol.one_rdms['scf_spin'], mol.one_rdms['scf_spin'].T) + assert_allclose(mol.one_rdms["scf"], mol.one_rdms["scf"].T) + assert_allclose(mol.one_rdms["scf_spin"], mol.one_rdms["scf_spin"].T) def test_load_fchk_ghost_atoms(): # Load fchk file with ghost atoms - mol = load_fchk_helper('water_dimer_ghost.fchk') + mol = load_fchk_helper("water_dimer_ghost.fchk") # There should be 3 real atoms and 3 ghost atoms assert mol.natom == 6 assert_equal(mol.atnums, [1, 8, 1, 1, 8, 1]) assert_equal(mol.atcorenums, [1.0, 8.0, 1.0, 0.0, 0.0, 0.0]) assert_equal(mol.atcoords.shape[0], 6) - assert_equal(mol.atcharges['mulliken'].shape[0], 6) + assert_equal(mol.atcharges["mulliken"].shape[0], 6) olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) def test_load_fchk_ch3_rohf_g03(): - mol = load_fchk_helper('ch3_rohf_sto3g_g03.fchk') + mol = load_fchk_helper("ch3_rohf_sto3g_g03.fchk") assert_equal(mol.mo.occs.shape[0], mol.mo.coeffs.shape[0]) assert_equal(mol.mo.occs.sum(), 9.0) assert_equal(mol.mo.occs.min(), 0.0) assert_equal(mol.mo.occs.max(), 2.0) - assert 'scf' not in mol.one_rdms # It should be skipped when loading fchk. + assert "scf" not in mol.one_rdms # It should be skipped when loading fchk. olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffsa, olp) check_orthonormal(mol.mo.coeffsb, olp) @@ -258,9 +274,9 @@ def test_load_fchk_ch3_rohf_g03(): def check_load_azirine(key, numbers): """Perform some basic checks on a azirine fchk file.""" - mol = load_fchk_helper('2h-azirine-{}.fchk'.format(key)) + mol = load_fchk_helper(f"2h-azirine-{key}.fchk") assert mol.obasis.nbasis == 33 - dm = mol.one_rdms['post_scf'] + dm = mol.one_rdms["post_scf_ao"] assert_equal(dm[0, 0], numbers[0]) assert_equal(dm[32, 32], numbers[1]) olp = compute_overlap(mol.obasis, mol.atcoords) @@ -268,108 +284,114 @@ def check_load_azirine(key, numbers): def test_load_azirine_cc(): - check_load_azirine('cc', [2.08221382E+00, 1.03516466E-01]) + check_load_azirine("cc", [2.08221382e00, 1.03516466e-01]) def test_load_azirine_ci(): - check_load_azirine('ci', [2.08058265E+00, 6.12011064E-02]) + check_load_azirine("ci", [2.08058265e00, 6.12011064e-02]) def test_load_azirine_mp2(): - check_load_azirine('mp2', [2.08253448E+00, 1.09305208E-01]) + check_load_azirine("mp2", [2.08253448e00, 1.09305208e-01]) def test_load_azirine_mp3(): - check_load_azirine('mp3', [2.08243417E+00, 1.02590815E-01]) + check_load_azirine("mp3", [2.08243417e00, 1.02590815e-01]) def check_load_nitrogen(key, numbers, numbers_spin): """Perform some basic checks on a nitrogen fchk file.""" - mol = load_fchk_helper('nitrogen-{}.fchk'.format(key)) + mol = load_fchk_helper(f"nitrogen-{key}.fchk") assert mol.obasis.nbasis == 9 - dm = mol.one_rdms['post_scf'] + dm = mol.one_rdms["post_scf_ao"] assert_equal(dm[0, 0], numbers[0]) assert_equal(dm[8, 8], numbers[1]) olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffsa, olp) check_orthonormal(mol.mo.coeffsb, olp) check_dm(dm, olp, eps=1e-3, occ_max=2) - assert_allclose(np.einsum('ab,ba', olp, dm), 7.0, atol=1.e-7, rtol=0) - dm_spin = mol.one_rdms['post_scf_spin'] + assert_allclose(np.einsum("ab,ba", olp, dm), 7.0, atol=1.0e-7, rtol=0) + dm_spin = mol.one_rdms["post_scf_spin_ao"] assert_equal(dm_spin[0, 0], numbers_spin[0]) assert_equal(dm_spin[8, 8], numbers_spin[1]) def test_load_nitrogen_cc(): - check_load_nitrogen('cc', [2.08709209E+00, 3.74723580E-01], [7.25882619E-04, -1.38368575E-02]) + check_load_nitrogen("cc", [2.08709209e00, 3.74723580e-01], [7.25882619e-04, -1.38368575e-02]) def test_load_nitrogen_ci(): - check_load_nitrogen('ci', [2.08741410E+00, 2.09292886E-01], [7.41998558E-04, -6.67582215E-03]) + check_load_nitrogen("ci", [2.08741410e00, 2.09292886e-01], [7.41998558e-04, -6.67582215e-03]) def test_load_nitrogen_mp2(): - check_load_nitrogen('mp2', [2.08710027E+00, 4.86472609E-01], [7.31802950E-04, -2.00028488E-02]) + check_load_nitrogen("mp2", [2.08710027e00, 4.86472609e-01], [7.31802950e-04, -2.00028488e-02]) def test_load_nitrogen_mp3(): - check_load_nitrogen('mp3', [2.08674302E+00, 4.91149023E-01], [7.06941101E-04, -1.96276763E-02]) + check_load_nitrogen("mp3", [2.08674302e00, 4.91149023e-01], [7.06941101e-04, -1.96276763e-02]) def check_normalization_dm_azirine(key): """Perform some basic checks on a 2h-azirine fchk file.""" - mol = load_fchk_helper('2h-azirine-{}.fchk'.format(key)) + mol = load_fchk_helper(f"2h-azirine-{key}.fchk") olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) - dm = mol.one_rdms['post_scf'] + dm = mol.one_rdms["post_scf_ao"] check_dm(dm, olp, eps=1e-2, occ_max=2) - assert_allclose(np.einsum('ab,ba', olp, dm), 22.0, atol=1.e-8, rtol=0) + assert_allclose(np.einsum("ab,ba", olp, dm), 22.0, atol=1.0e-8, rtol=0) def test_normalization_dm_azirine_cc(): - check_normalization_dm_azirine('cc') + check_normalization_dm_azirine("cc") def test_normalization_dm_azirine_ci(): - check_normalization_dm_azirine('ci') + check_normalization_dm_azirine("ci") def test_normalization_dm_azirine_mp2(): - check_normalization_dm_azirine('mp2') + check_normalization_dm_azirine("mp2") def test_normalization_dm_azirine_mp3(): - check_normalization_dm_azirine('mp3') + check_normalization_dm_azirine("mp3") def test_load_water_hfs_321g(): - mol = load_fchk_helper('water_hfs_321g.fchk') - pol = mol.extra['polarizability_tensor'] - assert_allclose(pol[0, 0], 7.23806684E+00) - assert_allclose(pol[1, 1], 8.04213953E+00) - assert_allclose(pol[1, 2], 1.20021770E-10) - assert_allclose(mol.moments[(1, 'c')], - [-5.82654324E-17, 0.00000000E+00, -8.60777067E-01]) - assert_allclose(mol.moments[(2, 'c')], - [-8.89536026E-01, # xx - 8.28408371E-17, # xy - 4.89353090E-17, # xz - 1.14114241E+00, # yy - -5.47382213E-48, # yz - -2.51606382E-01]) # zz + mol = load_fchk_helper("water_hfs_321g.fchk") + pol = mol.extra["polarizability_tensor"] + assert_allclose(pol[0, 0], 7.23806684e00) + assert_allclose(pol[1, 1], 8.04213953e00) + assert_allclose(pol[1, 2], 1.20021770e-10) + assert_allclose(mol.moments[(1, "c")], [-5.82654324e-17, 0.00000000e00, -8.60777067e-01]) + assert_allclose( + mol.moments[(2, "c")], + [ + -8.89536026e-01, # xx + 8.28408371e-17, # xy + 4.89353090e-17, # xz + 1.14114241e00, # yy + -5.47382213e-48, # yz + -2.51606382e-01, + ], + ) # zz def test_load_monosilicic_acid_hf_lan(): - mol = load_fchk_helper('monosilicic_acid_hf_lan.fchk') - assert_allclose(mol.moments[(1, 'c')], - [-6.05823053E-01, -9.39656399E-03, 4.18948869E-01]) - assert_allclose(mol.moments[(2, 'c')], - [2.73609152E+00, # xx - -6.65787832E-02, # xy - 2.11973730E-01, # xz - 8.97029351E-01, # yy - -1.38159653E-02, # yz - -3.63312087E+00]) # zz + mol = load_fchk_helper("monosilicic_acid_hf_lan.fchk") + assert_allclose(mol.moments[(1, "c")], [-6.05823053e-01, -9.39656399e-03, 4.18948869e-01]) + assert_allclose( + mol.moments[(2, "c")], + [ + 2.73609152e00, # xx + -6.65787832e-02, # xy + 2.11973730e-01, # xz + 8.97029351e-01, # yy + -1.38159653e-02, # yz + -3.63312087e00, + ], + ) # zz assert_allclose(mol.atgradient[0], [0.0, 0.0, 0.0]) olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) @@ -377,7 +399,7 @@ def test_load_monosilicic_acid_hf_lan(): def load_fchk_trj_helper(fn_fchk): """Load a trajectory from a testing fchk file with iodata.iodata.load_many.""" - with path('iodata.test.data', fn_fchk) as fn: + with as_file(files("iodata.test.data").joinpath(fn_fchk)) as fn: return list(load_many(fn)) @@ -390,134 +412,118 @@ def check_trj_basics(trj, nsteps, title, irc): for ipoint, nstep in enumerate(nsteps): for istep in range(nstep): mol = trj.pop(0) - assert mol.extra['ipoint'] == ipoint - assert mol.extra['npoint'] == len(nsteps) - assert mol.extra['istep'] == istep - assert mol.extra['nstep'] == nstep + assert mol.extra["ipoint"] == ipoint + assert mol.extra["npoint"] == len(nsteps) + assert mol.extra["istep"] == istep + assert mol.extra["nstep"] == nstep assert mol.natom == natom - assert mol.atnums.shape == (natom, ) + assert mol.atnums.shape == (natom,) assert_equal(mol.atnums, [8, 8, 1, 1]) - assert mol.atcorenums.shape == (natom, ) + assert mol.atcorenums.shape == (natom,) assert mol.atcoords.shape == (natom, 3) assert mol.atgradient.shape == (natom, 3) assert mol.title == title assert mol.energy is not None - assert ('reaction_coordinate' in mol.extra) ^ (not irc) + assert ("reaction_coordinate" in mol.extra) ^ (not irc) def test_peroxide_opt(): trj = load_fchk_trj_helper("peroxide_opt.fchk") - check_trj_basics(trj, [5], 'opt', False) - assert_allclose(trj[0].energy, -1.48759755E+02) - assert_allclose(trj[1].energy, -1.48763504E+02) - assert_allclose(trj[-1].energy, -1.48764883E+02) - assert_allclose(trj[0].atcoords[1], - [9.02056208E-17, -1.37317707E+00, 0.00000000E+00]) - assert_allclose(trj[-1].atcoords[-1], - [-1.85970174E+00, -1.64631025E+00, 0.00000000E+00]) - assert_allclose(trj[2].atgradient[0], - [-5.19698814E-03, -1.17503170E-03, -1.06165077E-15]) - assert_allclose(trj[3].atgradient[2], - [-8.70435823E-04, 1.44609443E-03, -3.79091290E-16]) + check_trj_basics(trj, [5], "opt", False) + assert_allclose(trj[0].energy, -1.48759755e02) + assert_allclose(trj[1].energy, -1.48763504e02) + assert_allclose(trj[-1].energy, -1.48764883e02) + assert_allclose(trj[0].atcoords[1], [9.02056208e-17, -1.37317707e00, 0.00000000e00]) + assert_allclose(trj[-1].atcoords[-1], [-1.85970174e00, -1.64631025e00, 0.00000000e00]) + assert_allclose(trj[2].atgradient[0], [-5.19698814e-03, -1.17503170e-03, -1.06165077e-15]) + assert_allclose(trj[3].atgradient[2], [-8.70435823e-04, 1.44609443e-03, -3.79091290e-16]) def test_peroxide_tsopt(): trj = load_fchk_trj_helper("peroxide_tsopt.fchk") - check_trj_basics(trj, [3], 'tsopt', False) - assert_allclose(trj[0].energy, -1.48741996E+02) - assert_allclose(trj[1].energy, -1.48750392E+02) - assert_allclose(trj[2].energy, -1.48750432E+02) - assert_allclose(trj[0].atcoords[3], - [-2.40150648E-01, -1.58431001E+00, 1.61489448E+00]) - assert_allclose(trj[2].atcoords[2], - [1.26945011E-03, 1.81554334E+00, 1.62426250E+00]) - assert_allclose(trj[1].atgradient[1], - [-8.38752120E-04, 3.46889422E-03, 1.96559245E-03]) - assert_allclose(trj[-1].atgradient[0], - [2.77986102E-05, -1.74709101E-05, 2.45875530E-05]) + check_trj_basics(trj, [3], "tsopt", False) + assert_allclose(trj[0].energy, -1.48741996e02) + assert_allclose(trj[1].energy, -1.48750392e02) + assert_allclose(trj[2].energy, -1.48750432e02) + assert_allclose(trj[0].atcoords[3], [-2.40150648e-01, -1.58431001e00, 1.61489448e00]) + assert_allclose(trj[2].atcoords[2], [1.26945011e-03, 1.81554334e00, 1.62426250e00]) + assert_allclose(trj[1].atgradient[1], [-8.38752120e-04, 3.46889422e-03, 1.96559245e-03]) + assert_allclose(trj[-1].atgradient[0], [2.77986102e-05, -1.74709101e-05, 2.45875530e-05]) def test_peroxide_relaxed_scan(): trj = load_fchk_trj_helper("peroxide_relaxed_scan.fchk") - check_trj_basics(trj, [6, 1, 1, 1, 2, 2], 'relaxed scan', False) - assert_allclose(trj[0].energy, -1.48759755E+02) - assert_allclose(trj[10].energy, -1.48764896E+02) - assert_allclose(trj[-1].energy, -1.48764905E+02) - assert_allclose(trj[1].atcoords[3], - [-1.85942837E+00, -1.70565735E+00, -1.11022302E-16]) - assert_allclose(trj[5].atcoords[0], - [-1.21430643E-16, 1.32466211E+00, 3.46944695E-17]) - assert_allclose(trj[8].atgradient[1], - [2.46088230E-04, -4.46299289E-04, -3.21529658E-05]) - assert_allclose(trj[9].atgradient[2], - [-1.02574260E-04, -3.33214833E-04, 5.27406641E-05]) + check_trj_basics(trj, [6, 1, 1, 1, 2, 2], "relaxed scan", False) + assert_allclose(trj[0].energy, -1.48759755e02) + assert_allclose(trj[10].energy, -1.48764896e02) + assert_allclose(trj[-1].energy, -1.48764905e02) + assert_allclose(trj[1].atcoords[3], [-1.85942837e00, -1.70565735e00, -1.11022302e-16]) + assert_allclose(trj[5].atcoords[0], [-1.21430643e-16, 1.32466211e00, 3.46944695e-17]) + assert_allclose(trj[8].atgradient[1], [2.46088230e-04, -4.46299289e-04, -3.21529658e-05]) + assert_allclose(trj[9].atgradient[2], [-1.02574260e-04, -3.33214833e-04, 5.27406641e-05]) def test_peroxide_irc(): trj = load_fchk_trj_helper("peroxide_irc.fchk") - check_trj_basics(trj, [21], 'irc', True) - assert_allclose(trj[0].energy, -1.48750432E+02) - assert_allclose(trj[5].energy, -1.48752713E+02) - assert_allclose(trj[-1].energy, -1.48757803E+02) - assert trj[0].extra['reaction_coordinate'] == 0.0 - assert_allclose(trj[1].extra['reaction_coordinate'], 1.05689581E-01) - assert_allclose(trj[10].extra['reaction_coordinate'], 1.05686037E+00) - assert_allclose(trj[-1].extra['reaction_coordinate'], -1.05685760E+00) - assert_allclose(trj[0].atcoords[2], - [-1.94749866E+00, -5.22905491E-01, -1.47814774E+00]) - assert_allclose(trj[10].atcoords[1], - [1.31447798E+00, 1.55994117E-01, -5.02320861E-02]) - assert_allclose(trj[15].atgradient[3], - [4.73066407E-04, -5.36135653E-03, 2.16301508E-04]) - assert_allclose(trj[-1].atgradient[0], - [-1.27710420E-03, -6.90543903E-03, 4.49870405E-03]) + check_trj_basics(trj, [21], "irc", True) + assert_allclose(trj[0].energy, -1.48750432e02) + assert_allclose(trj[5].energy, -1.48752713e02) + assert_allclose(trj[-1].energy, -1.48757803e02) + assert trj[0].extra["reaction_coordinate"] == 0.0 + assert_allclose(trj[1].extra["reaction_coordinate"], 1.05689581e-01) + assert_allclose(trj[10].extra["reaction_coordinate"], 1.05686037e00) + assert_allclose(trj[-1].extra["reaction_coordinate"], -1.05685760e00) + assert_allclose(trj[0].atcoords[2], [-1.94749866e00, -5.22905491e-01, -1.47814774e00]) + assert_allclose(trj[10].atcoords[1], [1.31447798e00, 1.55994117e-01, -5.02320861e-02]) + assert_allclose(trj[15].atgradient[3], [4.73066407e-04, -5.36135653e-03, 2.16301508e-04]) + assert_allclose(trj[-1].atgradient[0], [-1.27710420e-03, -6.90543903e-03, 4.49870405e-03]) def test_atgradient(): - mol = load_fchk_helper('peroxide_tsopt.fchk') - assert_allclose(mol.atgradient[0], [2.77986102E-05, -1.74709101E-05, 2.45875530E-05]) - assert_allclose(mol.atgradient[-1], [2.03469628E-05, 1.49353694E-05, -2.45875530E-05]) + mol = load_fchk_helper("peroxide_tsopt.fchk") + assert_allclose(mol.atgradient[0], [2.77986102e-05, -1.74709101e-05, 2.45875530e-05]) + assert_allclose(mol.atgradient[-1], [2.03469628e-05, 1.49353694e-05, -2.45875530e-05]) def test_athessian(): - mol = load_fchk_helper('peroxide_tsopt.fchk') - assert mol.run_type == 'freq' - assert mol.lot == 'rhf' - assert mol.obasis_name == 'sto-3g' - assert_allclose(mol.athessian[0, 0], -1.49799052E-02) - assert_allclose(mol.athessian[-1, -1], 5.83032386E-01) - assert_allclose(mol.athessian[0, 1], 5.07295215E-05) - assert_allclose(mol.athessian[1, 0], 5.07295215E-05) + mol = load_fchk_helper("peroxide_tsopt.fchk") + assert mol.run_type == "freq" + assert mol.lot == "rhf" + assert mol.obasis_name == "sto-3g" + assert_allclose(mol.athessian[0, 0], -1.49799052e-02) + assert_allclose(mol.athessian[-1, -1], 5.83032386e-01) + assert_allclose(mol.athessian[0, 1], 5.07295215e-05) + assert_allclose(mol.athessian[1, 0], 5.07295215e-05) assert mol.athessian.shape == (3 * mol.natom, 3 * mol.natom) def test_atfrozen(): - mol = load_fchk_helper('peroxide_tsopt.fchk') + mol = load_fchk_helper("peroxide_tsopt.fchk") assert_equal(mol.atfrozen, [False, False, False, True]) def test_atmasses(): - mol = load_fchk_helper('peroxide_tsopt.fchk') + mol = load_fchk_helper("peroxide_tsopt.fchk") assert_allclose(mol.atmasses[0], 29156.94, atol=0.1) assert_allclose(mol.atmasses[-1], 1837.15, atol=0.1) def test_spinpol(): - mol1 = load_fchk_helper('ch3_rohf_sto3g_g03.fchk') - assert mol1.mo.kind == 'restricted' + mol1 = load_fchk_helper("ch3_rohf_sto3g_g03.fchk") + assert mol1.mo.kind == "restricted" assert mol1.spinpol == 1 - mol2 = load_fchk_helper('li_h_3-21G_hf_g09.fchk') - assert mol2.mo.kind == 'unrestricted' + mol2 = load_fchk_helper("li_h_3-21G_hf_g09.fchk") + assert mol2.mo.kind == "unrestricted" assert mol2.spinpol == 1 with pytest.raises(TypeError): mol2.spinpol = 2 def test_nelec_charge(): - mol1 = load_fchk_helper('ch3_rohf_sto3g_g03.fchk') + mol1 = load_fchk_helper("ch3_rohf_sto3g_g03.fchk") assert mol1.nelec == 9 assert mol1.charge == 0 - mol2 = load_fchk_helper('li_h_3-21G_hf_g09.fchk') + mol2 = load_fchk_helper("li_h_3-21G_hf_g09.fchk") assert mol2.nelec == 3 assert mol2.charge == 1 with pytest.raises(TypeError): @@ -528,11 +534,11 @@ def test_nelec_charge(): def test_load_nbasis_indep(tmpdir): # Normal case - mol1 = load_fchk_helper('li2_g09_nbasis_indep.fchk') + mol1 = load_fchk_helper("li2_g09_nbasis_indep.fchk") assert mol1.mo.coeffs.shape == (38, 37) # Fake an old g03 fchk file by rewriting one line - with path('iodata.test.data', 'li2_g09_nbasis_indep.fchk') as fnin: - fnout = os.path.join(tmpdir, 'tmpg03.fchk') + with as_file(files("iodata.test.data").joinpath("li2_g09_nbasis_indep.fchk")) as fnin: + fnout = os.path.join(tmpdir, "tmpg03.fchk") with open(fnin) as fin, open(fnout, "w") as fout: for line in fin: fout.write(line.replace("independent", "independant")) @@ -540,7 +546,7 @@ def test_load_nbasis_indep(tmpdir): assert mol2.mo.coeffs.shape == (38, 37) -def check_load_dump_consistency(tmpdir: str, fn: str, match: str = None): +def check_load_dump_consistency(tmpdir: str, fn: str, match: Optional[str] = None): """Check if dumping and loading an FCHK file results in the same data. Parameters @@ -555,170 +561,174 @@ def check_load_dump_consistency(tmpdir: str, fn: str, match: str = None): """ mol1 = load_one_warning(fn, match=match) - fn_tmp = os.path.join(tmpdir, 'foo.bar') - dump_one(mol1, fn_tmp, fmt='fchk') - mol2 = load_one(fn_tmp, fmt='fchk') + fn_tmp = os.path.join(tmpdir, "foo.bar") + dump_one(mol1, fn_tmp, fmt="fchk") + mol2 = load_one(fn_tmp, fmt="fchk") # compare molecules - if fn.endswith('fchk'): + if fn.endswith("fchk"): compare_mols(mol1, mol2) else: compare_mols_diff_formats(mol1, mol2) def test_dump_fchk_from_fchk_hf(tmpdir): - check_load_dump_consistency(tmpdir, 'hf_sto3g.fchk') + check_load_dump_consistency(tmpdir, "hf_sto3g.fchk") def test_dump_fchk_from_fchk_h2o(tmpdir): - check_load_dump_consistency(tmpdir, 'h2o_sto3g.fchk') + check_load_dump_consistency(tmpdir, "h2o_sto3g.fchk") + + +def test_dump_fchk_from_fchk_water_atcharges(tmpdir): + check_load_dump_consistency(tmpdir, "water_atcharges.fchk") def test_dump_fchk_from_fchk_h2o_qchem(tmpdir): # test FCHK file generated by QChem-5.2.1 which misses 'Total Energy' field - check_load_dump_consistency(tmpdir, 'water_hf_sto3g_qchem5.2.fchk') + check_load_dump_consistency(tmpdir, "water_hf_sto3g_qchem5.2.fchk") def test_dump_fchk_from_fchk_peroxide_irc(tmpdir): - check_load_dump_consistency(tmpdir, 'peroxide_irc.fchk') + check_load_dump_consistency(tmpdir, "peroxide_irc.fchk") def test_dump_fchk_from_fchk_he(tmpdir): - check_load_dump_consistency(tmpdir, 'he_s_orbital.fchk') - check_load_dump_consistency(tmpdir, 'he_sp_orbital.fchk') - check_load_dump_consistency(tmpdir, 'he_spd_orbital.fchk') - check_load_dump_consistency(tmpdir, 'he_spdf_orbital.fchk') - check_load_dump_consistency(tmpdir, 'he_spdfgh_orbital.fchk') - check_load_dump_consistency(tmpdir, 'he_s_virtual.fchk') - check_load_dump_consistency(tmpdir, 'he_spdfgh_virtual.fchk') + check_load_dump_consistency(tmpdir, "he_s_orbital.fchk") + check_load_dump_consistency(tmpdir, "he_sp_orbital.fchk") + check_load_dump_consistency(tmpdir, "he_spd_orbital.fchk") + check_load_dump_consistency(tmpdir, "he_spdf_orbital.fchk") + check_load_dump_consistency(tmpdir, "he_spdfgh_orbital.fchk") + check_load_dump_consistency(tmpdir, "he_s_virtual.fchk") + check_load_dump_consistency(tmpdir, "he_spdfgh_virtual.fchk") def test_dump_fchk_from_fchk_o2(tmpdir): - check_load_dump_consistency(tmpdir, 'o2_cc_pvtz_cart.fchk') - check_load_dump_consistency(tmpdir, 'o2_cc_pvtz_pure.fchk') + check_load_dump_consistency(tmpdir, "o2_cc_pvtz_cart.fchk") + check_load_dump_consistency(tmpdir, "o2_cc_pvtz_pure.fchk") def test_dump_fchk_from_fchk_water_dimer_ghost(tmpdir): - check_load_dump_consistency(tmpdir, 'water_dimer_ghost.fchk') + check_load_dump_consistency(tmpdir, "water_dimer_ghost.fchk") def test_dump_fchk_from_molden_f(tmpdir): - check_load_dump_consistency(tmpdir, 'F.molden', "PSI4") + check_load_dump_consistency(tmpdir, "F.molden", "PSI4") def test_dump_fchk_from_molden_ne(tmpdir): - check_load_dump_consistency(tmpdir, 'neon_turbomole_def2-qzvp.molden', "Turbomole") + check_load_dump_consistency(tmpdir, "neon_turbomole_def2-qzvp.molden", "Turbomole") def test_dump_fchk_from_molden_he2(tmpdir): - check_load_dump_consistency(tmpdir, 'he2_ghost_psi4_1.0.molden') + check_load_dump_consistency(tmpdir, "he2_ghost_psi4_1.0.molden") def test_dump_fchk_from_molden_nh3(tmpdir): - check_load_dump_consistency(tmpdir, 'nh3_orca.molden', "ORCA") - check_load_dump_consistency(tmpdir, 'nh3_psi4.molden', "PSI4") - check_load_dump_consistency(tmpdir, 'nh3_psi4_1.0.molden', "unnormalized") - check_load_dump_consistency(tmpdir, 'nh3_molpro2012.molden') - check_load_dump_consistency(tmpdir, 'nh3_molden_cart.molden') - check_load_dump_consistency(tmpdir, 'nh3_molden_pure.molden') - check_load_dump_consistency(tmpdir, 'nh3_turbomole.molden', "Turbomole") + check_load_dump_consistency(tmpdir, "nh3_orca.molden", "ORCA") + check_load_dump_consistency(tmpdir, "nh3_psi4.molden", "PSI4") + check_load_dump_consistency(tmpdir, "nh3_psi4_1.0.molden", "unnormalized") + check_load_dump_consistency(tmpdir, "nh3_molpro2012.molden") + check_load_dump_consistency(tmpdir, "nh3_molden_cart.molden") + check_load_dump_consistency(tmpdir, "nh3_molden_pure.molden") + check_load_dump_consistency(tmpdir, "nh3_turbomole.molden", "Turbomole") def test_dump_fchk_from_wfn_he(tmpdir): - check_load_dump_consistency(tmpdir, 'he_s_virtual.wfn') - check_load_dump_consistency(tmpdir, 'he_s_orbital.wfn') - check_load_dump_consistency(tmpdir, 'he_p_orbital.wfn') - check_load_dump_consistency(tmpdir, 'he_d_orbital.wfn') - check_load_dump_consistency(tmpdir, 'he_sp_orbital.wfn') - check_load_dump_consistency(tmpdir, 'he_spd_orbital.wfn') - check_load_dump_consistency(tmpdir, 'he_spdf_orbital.wfn') - check_load_dump_consistency(tmpdir, 'he_spdfgh_orbital.wfn') - check_load_dump_consistency(tmpdir, 'he_spdfgh_virtual.wfn') + check_load_dump_consistency(tmpdir, "he_s_virtual.wfn") + check_load_dump_consistency(tmpdir, "he_s_orbital.wfn") + check_load_dump_consistency(tmpdir, "he_p_orbital.wfn") + check_load_dump_consistency(tmpdir, "he_d_orbital.wfn") + check_load_dump_consistency(tmpdir, "he_sp_orbital.wfn") + check_load_dump_consistency(tmpdir, "he_spd_orbital.wfn") + check_load_dump_consistency(tmpdir, "he_spdf_orbital.wfn") + check_load_dump_consistency(tmpdir, "he_spdfgh_orbital.wfn") + check_load_dump_consistency(tmpdir, "he_spdfgh_virtual.wfn") def test_dump_fchk_from_wfn_li(tmpdir): - check_load_dump_consistency(tmpdir, 'li_sp_virtual.wfn') - check_load_dump_consistency(tmpdir, 'li_sp_orbital.wfn') + check_load_dump_consistency(tmpdir, "li_sp_virtual.wfn") + check_load_dump_consistency(tmpdir, "li_sp_orbital.wfn") def test_dump_fchk_from_wfn_lih_cation(tmpdir): - check_load_dump_consistency(tmpdir, 'lih_cation_uhf.wfn') - check_load_dump_consistency(tmpdir, 'lih_cation_rohf.wfn') + check_load_dump_consistency(tmpdir, "lih_cation_uhf.wfn") + check_load_dump_consistency(tmpdir, "lih_cation_rohf.wfn") def test_dump_fchk_from_wfn_cisd_lih_cation(tmpdir): - check_load_dump_consistency(tmpdir, 'lih_cation_cisd.wfn') + check_load_dump_consistency(tmpdir, "lih_cation_cisd.wfn") def test_dump_fchk_from_wfn_fci_lih_cation(tmpdir): # Fractional occupations are not supported in FCHK and we have no # alternative for solution for this yet. with pytest.raises(ValueError): - check_load_dump_consistency(tmpdir, 'lih_cation_fci.wfn') + check_load_dump_consistency(tmpdir, "lih_cation_fci.wfn") def test_dump_fchk_from_wfn_fci_lif(tmpdir): # Fractional occupations are not supported in FCHK and we have no # alternative for solution for this yet. with pytest.raises(ValueError): - check_load_dump_consistency(tmpdir, 'lif_fci.wfn') + check_load_dump_consistency(tmpdir, "lif_fci.wfn") def test_dump_fchk_from_wfn_h2(tmpdir): - check_load_dump_consistency(tmpdir, 'h2_ccpvqz.wfn') + check_load_dump_consistency(tmpdir, "h2_ccpvqz.wfn") def test_dump_fchk_from_wfn_o2(tmpdir): - check_load_dump_consistency(tmpdir, 'o2_uhf_virtual.wfn') - check_load_dump_consistency(tmpdir, 'o2_uhf.wfn') + check_load_dump_consistency(tmpdir, "o2_uhf_virtual.wfn") + check_load_dump_consistency(tmpdir, "o2_uhf.wfn") def test_dump_fchk_from_wfn_h2o(tmpdir): - check_load_dump_consistency(tmpdir, 'h2o_sto3g.wfn') - check_load_dump_consistency(tmpdir, 'h2o_sto3g_decontracted.wfn') + check_load_dump_consistency(tmpdir, "h2o_sto3g.wfn") + check_load_dump_consistency(tmpdir, "h2o_sto3g_decontracted.wfn") def test_dump_fchk_from_wfn_ch3(tmpdir): - check_load_dump_consistency(tmpdir, 'ch3_rohf_sto3g_g03.fchk') + check_load_dump_consistency(tmpdir, "ch3_rohf_sto3g_g03.fchk") def test_dump_fchk_from_wfn_cah110(tmpdir): - check_load_dump_consistency(tmpdir, 'cah110_hf_sto3g_g09.wfn') + check_load_dump_consistency(tmpdir, "cah110_hf_sto3g_g09.wfn") def test_dump_fchk_from_wfx_h2(tmpdir): - check_load_dump_consistency(tmpdir, 'h2_ub3lyp_ccpvtz.wfx') + check_load_dump_consistency(tmpdir, "h2_ub3lyp_ccpvtz.wfx") def test_dump_fchk_from_wfx_water(tmpdir): - check_load_dump_consistency(tmpdir, 'water_sto3g_hf.wfx') + check_load_dump_consistency(tmpdir, "water_sto3g_hf.wfx") def test_dump_fchk_from_wfx_lih_cation(tmpdir): - check_load_dump_consistency(tmpdir, 'lih_cation_uhf.wfx') - check_load_dump_consistency(tmpdir, 'lih_cation_rohf.wfx') + check_load_dump_consistency(tmpdir, "lih_cation_uhf.wfx") + check_load_dump_consistency(tmpdir, "lih_cation_rohf.wfx") def test_dump_fchk_from_wfx_lih_cisd_cation(tmpdir): # Fractional occupations are not supported in FCHK and we have no # alternative for solution for this yet. with pytest.raises(ValueError): - check_load_dump_consistency(tmpdir, 'lih_cation_cisd.wfx') + check_load_dump_consistency(tmpdir, "lih_cation_cisd.wfx") def test_dump_fchk_from_wfx_cah110(tmpdir): - check_load_dump_consistency(tmpdir, 'cah110_hf_sto3g_g09.wfx') + check_load_dump_consistency(tmpdir, "cah110_hf_sto3g_g09.wfx") def test_dump_fchk_from_molekel_h2(tmpdir): - check_load_dump_consistency(tmpdir, 'h2_sto3g.mkl', "ORCA") + check_load_dump_consistency(tmpdir, "h2_sto3g.mkl", "ORCA") def test_dump_fchk_from_molekel_ethanol(tmpdir): - check_load_dump_consistency(tmpdir, 'ethanol.mkl', "ORCA") + check_load_dump_consistency(tmpdir, "ethanol.mkl", "ORCA") def test_dump_fchk_from_molekel_li2(tmpdir): - check_load_dump_consistency(tmpdir, 'li2.mkl', "ORCA") + check_load_dump_consistency(tmpdir, "li2.mkl", "ORCA") def test_dump_fchk_rdms_cc_nitrogen(tmpdir): diff --git a/iodata/test/test_fcidump.py b/iodata/test/test_fcidump.py index 810ac02a7..c06545a45 100644 --- a/iodata/test/test_fcidump.py +++ b/iodata/test/test_fcidump.py @@ -21,88 +21,88 @@ import os import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from ..api import load_one, dump_one +from ..api import dump_one, load_one try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_fcidump_psi4_h2(): - with path('iodata.test.data', 'FCIDUMP.psi4.h2') as fn: + with as_file(files("iodata.test.data").joinpath("FCIDUMP.psi4.h2")) as fn: mol = load_one(str(fn)) - assert_allclose(mol.core_energy, 0.7151043364864863E+00) + assert_allclose(mol.core_energy, 0.7151043364864863e00) assert_equal(mol.nelec, 2) assert_equal(mol.spinpol, 0) - core_mo = mol.one_ints['core_mo'] + core_mo = mol.one_ints["core_mo"] assert_equal(core_mo.shape, (10, 10)) - assert_allclose(core_mo[0, 0], -0.1251399119550580E+01) - assert_allclose(core_mo[2, 1], 0.9292454365115077E-01) - assert_allclose(core_mo[1, 2], 0.9292454365115077E-01) - assert_allclose(core_mo[9, 9], 0.9035054979531029E+00) - two_mo = mol.two_ints['two_mo'] + assert_allclose(core_mo[0, 0], -0.1251399119550580e01) + assert_allclose(core_mo[2, 1], 0.9292454365115077e-01) + assert_allclose(core_mo[1, 2], 0.9292454365115077e-01) + assert_allclose(core_mo[9, 9], 0.9035054979531029e00) + two_mo = mol.two_ints["two_mo"] assert_allclose(two_mo.shape, (10, 10, 10, 10)) - assert_allclose(two_mo[0, 0, 0, 0], 0.6589928924251115E+00) + assert_allclose(two_mo[0, 0, 0, 0], 0.6589928924251115e00) # Check physicist's notation and symmetry - assert_allclose(two_mo[6, 1, 5, 0], 0.5335846565304321E-01) - assert_allclose(two_mo[5, 1, 6, 0], 0.5335846565304321E-01) - assert_allclose(two_mo[6, 0, 5, 1], 0.5335846565304321E-01) - assert_allclose(two_mo[5, 0, 6, 1], 0.5335846565304321E-01) - assert_allclose(two_mo[1, 6, 0, 5], 0.5335846565304321E-01) - assert_allclose(two_mo[1, 5, 0, 6], 0.5335846565304321E-01) - assert_allclose(two_mo[0, 6, 1, 5], 0.5335846565304321E-01) - assert_allclose(two_mo[0, 5, 1, 6], 0.5335846565304321E-01) - assert_allclose(two_mo[9, 9, 9, 9], 0.6273759381091796E+00) + assert_allclose(two_mo[6, 1, 5, 0], 0.5335846565304321e-01) + assert_allclose(two_mo[5, 1, 6, 0], 0.5335846565304321e-01) + assert_allclose(two_mo[6, 0, 5, 1], 0.5335846565304321e-01) + assert_allclose(two_mo[5, 0, 6, 1], 0.5335846565304321e-01) + assert_allclose(two_mo[1, 6, 0, 5], 0.5335846565304321e-01) + assert_allclose(two_mo[1, 5, 0, 6], 0.5335846565304321e-01) + assert_allclose(two_mo[0, 6, 1, 5], 0.5335846565304321e-01) + assert_allclose(two_mo[0, 5, 1, 6], 0.5335846565304321e-01) + assert_allclose(two_mo[9, 9, 9, 9], 0.6273759381091796e00) def test_load_fcidump_molpro_h2(): - with path('iodata.test.data', 'FCIDUMP.molpro.h2') as fn: + with as_file(files("iodata.test.data").joinpath("FCIDUMP.molpro.h2")) as fn: mol = load_one(str(fn)) - assert_allclose(mol.core_energy, 0.7151043364864863E+00) + assert_allclose(mol.core_energy, 0.7151043364864863e00) assert_equal(mol.nelec, 2) assert_equal(mol.spinpol, 0) - core_mo = mol.one_ints['core_mo'] + core_mo = mol.one_ints["core_mo"] assert_equal(core_mo.shape, (4, 4)) - assert_allclose(core_mo[0, 0], -0.1245406261597530E+01) - assert_allclose(core_mo[0, 1], -0.1666402467335385E+00) - assert_allclose(core_mo[1, 0], -0.1666402467335385E+00) - assert_allclose(core_mo[3, 3], 0.3216193420753873E+00) - two_mo = mol.two_ints['two_mo'] + assert_allclose(core_mo[0, 0], -0.1245406261597530e01) + assert_allclose(core_mo[0, 1], -0.1666402467335385e00) + assert_allclose(core_mo[1, 0], -0.1666402467335385e00) + assert_allclose(core_mo[3, 3], 0.3216193420753873e00) + two_mo = mol.two_ints["two_mo"] assert_allclose(two_mo.shape, (4, 4, 4, 4)) - assert_allclose(two_mo[0, 0, 0, 0], 0.6527679278914691E+00) + assert_allclose(two_mo[0, 0, 0, 0], 0.6527679278914691e00) # Check physicist's notation and symmetry - assert_allclose(two_mo[3, 0, 2, 1], 0.7756042287284058E-01) - assert_allclose(two_mo[2, 0, 3, 1], 0.7756042287284058E-01) - assert_allclose(two_mo[3, 1, 2, 0], 0.7756042287284058E-01) - assert_allclose(two_mo[2, 1, 3, 0], 0.7756042287284058E-01) - assert_allclose(two_mo[0, 3, 1, 2], 0.7756042287284058E-01) - assert_allclose(two_mo[0, 2, 1, 3], 0.7756042287284058E-01) - assert_allclose(two_mo[1, 3, 0, 2], 0.7756042287284058E-01) - assert_allclose(two_mo[1, 2, 0, 3], 0.7756042287284058E-01) - assert_allclose(two_mo[3, 3, 3, 3], 0.7484308847738417E+00) + assert_allclose(two_mo[3, 0, 2, 1], 0.7756042287284058e-01) + assert_allclose(two_mo[2, 0, 3, 1], 0.7756042287284058e-01) + assert_allclose(two_mo[3, 1, 2, 0], 0.7756042287284058e-01) + assert_allclose(two_mo[2, 1, 3, 0], 0.7756042287284058e-01) + assert_allclose(two_mo[0, 3, 1, 2], 0.7756042287284058e-01) + assert_allclose(two_mo[0, 2, 1, 3], 0.7756042287284058e-01) + assert_allclose(two_mo[1, 3, 0, 2], 0.7756042287284058e-01) + assert_allclose(two_mo[1, 2, 0, 3], 0.7756042287284058e-01) + assert_allclose(two_mo[3, 3, 3, 3], 0.7484308847738417e00) def test_dump_load_fcidimp_consistency_ao(tmpdir): # Setup IOData - with path('iodata.test.data', 'water.xyz') as fn: + with as_file(files("iodata.test.data").joinpath("water.xyz")) as fn: mol0 = load_one(str(fn)) mol0.nelec = 10 mol0.spinpol = 0 - with path('iodata.test.data', 'psi4_h2_one.npy') as fn: - mol0.one_ints = {'core_mo': np.load(str(fn))} - with path('iodata.test.data', 'psi4_h2_two.npy') as fn: - mol0.two_ints = {'two_mo': np.load(str(fn))} + with as_file(files("iodata.test.data").joinpath("psi4_h2_one.npy")) as fn: + mol0.one_ints = {"core_mo": np.load(str(fn))} + with as_file(files("iodata.test.data").joinpath("psi4_h2_two.npy")) as fn: + mol0.two_ints = {"two_mo": np.load(str(fn))} # Dump to a file and load it again - fn_tmp = os.path.join(tmpdir, 'FCIDUMP') + fn_tmp = os.path.join(tmpdir, "FCIDUMP") dump_one(mol0, fn_tmp) mol1 = load_one(fn_tmp) # Compare results assert_equal(mol0.nelec, mol1.nelec) assert_equal(mol0.spinpol, mol1.spinpol) - assert_allclose(mol0.one_ints['core_mo'], mol1.one_ints['core_mo']) - assert_allclose(mol0.two_ints['two_mo'], mol1.two_ints['two_mo']) + assert_allclose(mol0.one_ints["core_mo"], mol1.one_ints["core_mo"]) + assert_allclose(mol0.two_ints["two_mo"], mol1.two_ints["two_mo"]) diff --git a/iodata/test/test_gamess.py b/iodata/test/test_gamess.py index 4c48eeaf3..072082557 100644 --- a/iodata/test/test_gamess.py +++ b/iodata/test/test_gamess.py @@ -18,44 +18,43 @@ # -- """Test iodata.formats.gamess module.""" - -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal from ..api import load_one from ..utils import angstrom try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_one_gamess_punch(): - with path('iodata.test.data', 'PCGamess_PUNCH.dat') as f: + with as_file(files("iodata.test.data").joinpath("PCGamess_PUNCH.dat")) as f: data = load_one(str(f)) - N = len(["CL", "H", "H", "H", "H", "F", "F", "F", "F", "H", "F"]) + size = len(["CL", "H", "H", "H", "H", "F", "F", "F", "F", "H", "F"]) assert data.title == "Simple example sample optimization with Hessian output for Toon" assert data.g_rot == "C1" - assert_equal(data.atnums.shape, (N,)) + assert_equal(data.atnums.shape, (size,)) assert_equal(data.atnums[0], 17) assert_equal(data.atnums[1], 1) assert_equal(data.atnums[-1], 9) - assert_equal(data.atcoords.shape, (N, 3)) + assert_equal(data.atcoords.shape, (size, 3)) assert_allclose(data.atcoords[0, 1] / angstrom, -0.1843157808) assert_allclose(data.atcoords[3, -1] / angstrom, 1.2926708150) assert_allclose(data.atcoords[-1, 0] / angstrom, 3.8608437748) assert_allclose(data.energy, -959.9675629527) - assert_equal(data.atgradient.shape, (N, 3)) - assert data.atgradient[0, 1] - 1.5314677838E-05 < 1e-10 - assert abs(data.atgradient[3, -1] - 8.5221217336E-06) < 1e-10 - assert abs(data.atgradient[-1, 0] - 2.1211421041E-05) < 1e-10 - assert_equal(data.athessian.shape, (3 * N, 3 * N)) + assert_equal(data.atgradient.shape, (size, 3)) + assert data.atgradient[0, 1] - 1.5314677838e-05 < 1e-10 + assert abs(data.atgradient[3, -1] - 8.5221217336e-06) < 1e-10 + assert abs(data.atgradient[-1, 0] - 2.1211421041e-05) < 1e-10 + assert_equal(data.athessian.shape, (3 * size, 3 * size)) assert abs(data.athessian - data.athessian.transpose()).max() < 1e-10 - assert abs(data.athessian[0, 0] - 2.51645239E-02) < 1e-10 - assert abs(data.athessian[0, -1] - -1.27201108E-04) < 1e-10 - assert abs(data.athessian[-1, 0] - -1.27201108E-04) < 1e-10 - assert abs(data.athessian[-1, -1] - 7.34538698E-03) < 1e-10 - assert_equal(data.atmasses.shape, (N,)) + assert abs(data.athessian[0, 0] - 2.51645239e-02) < 1e-10 + assert abs(data.athessian[0, -1] - -1.27201108e-04) < 1e-10 + assert abs(data.athessian[-1, 0] - -1.27201108e-04) < 1e-10 + assert abs(data.athessian[-1, -1] - 7.34538698e-03) < 1e-10 + assert_equal(data.atmasses.shape, (size,)) assert_allclose(data.atmasses[0], 34.96885) assert_allclose(data.atmasses[3], 1.00782) assert_allclose(data.atmasses[-1], 18.99840) diff --git a/iodata/test/test_gaussianinput.py b/iodata/test/test_gaussianinput.py index 0732b69c8..330e06daf 100644 --- a/iodata/test/test_gaussianinput.py +++ b/iodata/test/test_gaussianinput.py @@ -19,56 +19,59 @@ """Test iodata.formats.gaussianinput module.""" import numpy as np -from numpy.testing import assert_equal, assert_allclose, assert_raises +from numpy.testing import assert_allclose, assert_equal, assert_raises from ..api import load_one from ..utils import angstrom + try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_water_com(): # test .com with Link 0 section - with path('iodata.test.data', 'water.com') as fn_xyz: - mol = load_one(str(fn_xyz)) - check_water(mol, 'water') + with as_file(files("iodata.test.data").joinpath("water.com")) as fn: + mol = load_one(str(fn)) + check_water(mol, "water") def test_load_water_gjf(): # test .com without Link 0 section - with path('iodata.test.data', 'water.gjf') as fn_xyz: - mol = load_one(str(fn_xyz)) - check_water(mol, 'water') + with as_file(files("iodata.test.data").joinpath("water.gjf")) as fn: + mol = load_one(str(fn)) + check_water(mol, "water") def test_load_multi_link(): # test .com with multiple #link 0 contents - with path('iodata.test.data', 'water_multi_link.com') as fn_xyz: - mol = load_one(str(fn_xyz)) - check_water(mol, 'water') + with as_file(files("iodata.test.data").joinpath("water_multi_link.com")) as fn: + mol = load_one(str(fn)) + check_water(mol, "water") def test_load_multi_route(): # test .com with multiple route contents - with path('iodata.test.data', 'water_multi_route.com') as fn_xyz: - mol = load_one(str(fn_xyz)) - check_water(mol, 'water') + with as_file(files("iodata.test.data").joinpath("water_multi_route.com")) as fn: + mol = load_one(str(fn)) + check_water(mol, "water") def test_load_multi_title(): # test .com with multiple title and concatenate - with path('iodata.test.data', 'water_multi_title.com') as fn_xyz: - mol = load_one(str(fn_xyz)) - check_water(mol, 'water water') + with as_file(files("iodata.test.data").joinpath("water_multi_title.com")) as fn: + mol = load_one(str(fn)) + check_water(mol, "water water") def test_load_error(): # test error raises when loading .com with z-matrix - with assert_raises(ValueError): - with path('iodata.test.data', 'water_z.com') as fn_xyz: - load_one(str(fn_xyz)) + with ( + assert_raises(ValueError), + as_file(files("iodata.test.data").joinpath("water_z.com")) as fn, + ): + load_one(str(fn)) def check_water(mol, title): @@ -76,9 +79,12 @@ def check_water(mol, title): assert mol.title == title assert_equal(mol.atnums, [1, 8, 1]) # check bond length - assert_allclose(np.linalg.norm( - mol.atcoords[0] - mol.atcoords[1]) / angstrom, 0.960, atol=1.e-5) - assert_allclose(np.linalg.norm( - mol.atcoords[2] - mol.atcoords[1]) / angstrom, 0.960, atol=1.e-5) - assert_allclose(np.linalg.norm( - mol.atcoords[0] - mol.atcoords[2]) / angstrom, 1.568, atol=1.e-3) + assert_allclose( + np.linalg.norm(mol.atcoords[0] - mol.atcoords[1]) / angstrom, 0.960, atol=1.0e-5 + ) + assert_allclose( + np.linalg.norm(mol.atcoords[2] - mol.atcoords[1]) / angstrom, 0.960, atol=1.0e-5 + ) + assert_allclose( + np.linalg.norm(mol.atcoords[0] - mol.atcoords[2]) / angstrom, 1.568, atol=1.0e-3 + ) diff --git a/iodata/test/test_gaussianlog.py b/iodata/test/test_gaussianlog.py index 4c0850e81..fe29aaea5 100644 --- a/iodata/test/test_gaussianlog.py +++ b/iodata/test/test_gaussianlog.py @@ -18,30 +18,30 @@ # -- """Test iodata.formats.log module.""" -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal from ..api import load_one try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def load_log_helper(fn_log): """Load a testing Gaussian log file with iodata.load_one.""" - with path('iodata.test.data', fn_log) as fn: + with as_file(files("iodata.test.data").joinpath(fn_log)) as fn: return load_one(fn) def test_load_operators_water_sto3g_hf_g03(): eps = 1e-5 - mol = load_log_helper('water_sto3g_hf_g03.log') + mol = load_log_helper("water_sto3g_hf_g03.log") - olp = mol.one_ints['olp'] - kin_ao = mol.one_ints['kin_ao'] - na_ao = mol.one_ints['na_ao'] - er_ao = mol.two_ints['er_ao'] + olp = mol.one_ints["olp"] + kin_ao = mol.one_ints["kin_ao"] + na_ao = mol.one_ints["na_ao"] + er_ao = mol.two_ints["er_ao"] assert_equal(olp.shape, (7, 7)) assert_equal(kin_ao.shape, (7, 7)) @@ -71,12 +71,12 @@ def test_load_operators_water_sto3g_hf_g03(): def test_load_operators_water_ccpvdz_pure_hf_g03(): eps = 1e-5 - mol = load_log_helper('water_ccpvdz_pure_hf_g03.log') + mol = load_log_helper("water_ccpvdz_pure_hf_g03.log") - olp = mol.one_ints['olp'] - kin_ao = mol.one_ints['kin_ao'] - na_ao = mol.one_ints['na_ao'] - er_ao = mol.two_ints['er_ao'] + olp = mol.one_ints["olp"] + kin_ao = mol.one_ints["kin_ao"] + na_ao = mol.one_ints["na_ao"] + er_ao = mol.two_ints["er_ao"] assert_equal(olp.shape, (24, 24)) assert_equal(kin_ao.shape, (24, 24)) diff --git a/iodata/test/test_gromacs.py b/iodata/test/test_gromacs.py index b5d174d9b..4ef1eb24c 100644 --- a/iodata/test/test_gromacs.py +++ b/iodata/test/test_gromacs.py @@ -16,46 +16,45 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object """Test iodata.formats.gromacs module.""" -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from ..api import load_one, load_many +from ..api import load_many, load_one from ..utils import nanometer, picosecond try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_water(): # test gro file of one water - with path('iodata.test.data', 'water.gro') as fn_gro: + with as_file(files("iodata.test.data").joinpath("water.gro")) as fn_gro: mol = load_one(str(fn_gro)) check_water(mol) def check_water(mol): """Test some things on a water file.""" - assert mol.title == 'MD of 2 waters' + assert mol.title == "MD of 2 waters" assert mol.atcoords.shape == (6, 3) assert_allclose(mol.atcoords[-1] / nanometer, [1.326, 0.120, 0.568]) - assert mol.atffparams['attypes'][2] == 'HW3' - assert mol.atffparams['resnames'][-1] == 'WATER' - assert_equal(mol.atffparams['resnums'][2:4], [1, 2]) - assert_allclose(mol.cellvecs[0][0], 1.82060 * nanometer, atol=1.e-5) - assert mol.extra['velocities'].shape == (6, 3) - vel = mol.extra['velocities'][-1] + assert mol.atffparams["attypes"][2] == "HW3" + assert mol.atffparams["resnames"][-1] == "WATER" + assert_equal(mol.atffparams["resnums"][2:4], [1, 2]) + assert_allclose(mol.cellvecs[0][0], 1.82060 * nanometer, atol=1.0e-5) + assert mol.extra["velocities"].shape == (6, 3) + vel = mol.extra["velocities"][-1] assert_allclose(vel * (picosecond / nanometer), [1.9427, -0.8216, -0.0244]) def test_load_many(): - with path('iodata.test.data', 'water2.gro') as fn_gro: + with as_file(files("iodata.test.data").joinpath("water2.gro")) as fn_gro: mols = list(load_many(str(fn_gro))) assert len(mols) == 2 - assert mols[0].extra['time'] == 0.0 * picosecond - assert mols[1].extra['time'] == 1.0 * picosecond + assert mols[0].extra["time"] == 0.0 * picosecond + assert mols[1].extra["time"] == 1.0 * picosecond for mol in mols: check_water(mol) diff --git a/iodata/test/test_inputs.py b/iodata/test/test_inputs.py index ae5ba9854..2efc4fafa 100644 --- a/iodata/test/test_inputs.py +++ b/iodata/test/test_inputs.py @@ -18,21 +18,20 @@ # -- """Test iodata.inputs module.""" - import os -import numpy as np +import numpy as np import pytest from ..api import load_one, write_input from ..iodata import IOData from ..periodic import num2sym -from ..utils import angstrom, FileFormatWarning +from ..utils import FileFormatWarning, angstrom try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def check_load_input_and_compare(fname: str, fname_expected: str): @@ -46,22 +45,22 @@ def check_load_input_and_compare(fname: str, fname_expected: str): Path to expected input file to load. """ - with open(fname, 'r') as ifn: + with open(fname) as ifn: content = "".join(ifn.readlines()) - with open(fname_expected, 'r') as efn: + with open(fname_expected) as efn: expected = "".join(efn.readlines()) assert content == expected def test_input_gaussian_from_xyz(tmpdir): # load geometry from xyz file & add level of theory & basis set - with path('iodata.test.data', 'water_number.xyz') as fn: + with as_file(files("iodata.test.data").joinpath("water_number.xyz")) as fn: mol = load_one(fn) mol.nelec = 10 - mol.lot = 'ub3lyp' - mol.obasis_name = '6-31g*' + mol.lot = "ub3lyp" + mol.obasis_name = "6-31g*" # write input in a temporary folder using the user-template - fname = os.path.join(tmpdir, 'input_from_xyz.com') + fname = os.path.join(tmpdir, "input_from_xyz.com") template = """\ %chk=gaussian.chk %mem=3500MB @@ -83,34 +82,42 @@ def test_input_gaussian_from_xyz(tmpdir): """ - write_input(mol, fname, fmt='gaussian', template=template, extra_cmd="nosymmetry") + write_input(mol, fname, fmt="gaussian", template=template, extra_cmd="nosymmetry") # compare saved input to expected input - with path('iodata.test.data', 'input_gaussian_h2o_opt_ub3lyp.txt') as fname_expected: + source = files("iodata.test.data").joinpath("input_gaussian_h2o_opt_ub3lyp.txt") + with as_file(source) as fname_expected: check_load_input_and_compare(fname, fname_expected) def test_input_gaussian_from_iodata(tmpdir): # make an instance of IOData for HCl anion - data = {"atcoords": np.array([[0.0, 0.0, 0.0], [angstrom, 0.0, 0.0]]), - "atnums": np.array([1, 17]), "nelec": 19, "run_type": 'opt', "spinpol": 1} + data = { + "atcoords": np.array([[0.0, 0.0, 0.0], [angstrom, 0.0, 0.0]]), + "atnums": np.array([1, 17]), + "nelec": 19, + "run_type": "opt", + "spinpol": 1, + } mol = IOData(**data) # write input in a temporary file - fname = os.path.join(tmpdir, 'input_from_iodata.com') - write_input(mol, fname, fmt='gaussian') + fname = os.path.join(tmpdir, "input_from_iodata.com") + write_input(mol, fname, fmt="gaussian") # compare saved input to expected input - with path('iodata.test.data', 'input_gaussian_hcl_anion_opt_hf.txt') as fname_expected: + source = files("iodata.test.data").joinpath("input_gaussian_hcl_anion_opt_hf.txt") + with as_file(source) as fname_expected: check_load_input_and_compare(fname, fname_expected) def test_input_gaussian_from_fchk(tmpdir): # load fchk - with path('iodata.test.data', 'water_hfs_321g.fchk') as fn: + with as_file(files("iodata.test.data").joinpath("water_hfs_321g.fchk")) as fn: mol = load_one(fn) # write input in a temporary file - fname = os.path.join(tmpdir, 'input_from_fchk.in') - write_input(mol, fname, fmt='gaussian') + fname = os.path.join(tmpdir, "input_from_fchk.in") + write_input(mol, fname, fmt="gaussian") # compare saved input to expected input - with path('iodata.test.data', 'input_gaussian_hcl_sp_rhf.txt') as fname_expected: + source = files("iodata.test.data").joinpath("input_gaussian_hcl_sp_rhf.txt") + with as_file(source) as fname_expected: check_load_input_and_compare(fname, fname_expected) @@ -131,24 +138,24 @@ def atom_line(data, iatom): fid = data.extra["fragment_ids"][iatom] return f"{symbol}(Fragment={fid}) {atcoord[0]:10.6f} {atcoord[1]:10.6f} {atcoord[2]:10.6f}" - with path('iodata.test.data', 's66_4114_02WaterMeOH.xyz') as fn: + with as_file(files("iodata.test.data").joinpath("s66_4114_02WaterMeOH.xyz")) as fn: mol = load_one(fn, "extxyz") - fn_com = os.path.join(tmpdir, 'input_bsse.com') - write_input(mol, fn_com, 'gaussian', template, atom_line) - with path('iodata.test.data', 'input_gaussian_bsse.com') as fn_expected: + fn_com = os.path.join(tmpdir, "input_bsse.com") + write_input(mol, fn_com, "gaussian", template, atom_line) + with as_file(files("iodata.test.data").joinpath("input_gaussian_bsse.com")) as fn_expected: check_load_input_and_compare(fn_com, fn_expected) def test_input_orca_from_xyz(tmpdir): # load geometry from xyz file & add level of theory & basis set - with path('iodata.test.data', 'water_number.xyz') as fn: + with as_file(files("iodata.test.data").joinpath("water_number.xyz")) as fn: mol = load_one(fn) mol.nelec = 10 - mol.lot = 'B3LYP' - mol.obasis_name = 'def2-SVP' + mol.lot = "B3LYP" + mol.obasis_name = "def2-SVP" # write input in a temporary folder using the user-template - fname = os.path.join(tmpdir, 'input_from_xyz.com') + fname = os.path.join(tmpdir, "input_from_xyz.com") template = """\ ! {lot} {obasis_name} {grid_stuff} KeepDens # {title} @@ -172,33 +179,43 @@ def atom_line(data, iatom): return f" {symbol:3s} {atcoord[0]:10.6f} {atcoord[1]:10.6f} {atcoord[2]:10.6f}" grid_stuff = "Grid4 TightSCF NOFINALGRID" - write_input(mol, fname, 'orca', template, atom_line, grid_stuff=grid_stuff) + write_input(mol, fname, "orca", template, atom_line, grid_stuff=grid_stuff) # compare saved input to expected input - with path('iodata.test.data', 'input_orca_h2o_sp_b3lyp.txt') as fname_expected: + source = files("iodata.test.data").joinpath("input_orca_h2o_sp_b3lyp.txt") + with as_file(source) as fname_expected: check_load_input_and_compare(fname, fname_expected) def test_input_orca_from_iodata(tmpdir): # make an instance of IOData for HCl anion - data = {"atcoords": np.array([[0.0, 0.0, 0.0], [angstrom, 0.0, 0.0]]), - "atnums": np.array([1, 17]), "nelec": 19, "run_type": 'opt', "spinpol": 1} + data = { + "atcoords": np.array([[0.0, 0.0, 0.0], [angstrom, 0.0, 0.0]]), + "atnums": np.array([1, 17]), + "nelec": 19, + "run_type": "opt", + "spinpol": 1, + } mol = IOData(**data) # write input in a temporary file - fname = os.path.join(tmpdir, 'input_from_iodata.com') - write_input(mol, fname, fmt='orca') + fname = os.path.join(tmpdir, "input_from_iodata.com") + write_input(mol, fname, fmt="orca") # compare saved input to expected input - with path('iodata.test.data', 'input_orca_hcl_anion_opt_hf.txt') as fname_expected: + source = files("iodata.test.data").joinpath("input_orca_hcl_anion_opt_hf.txt") + with as_file(source) as fname_expected: check_load_input_and_compare(fname, fname_expected) def test_input_orca_from_molden(tmpdir): # load orca molden - with path('iodata.test.data', 'nh3_orca.molden') as fn: - with pytest.warns(FileFormatWarning): - mol = load_one(str(fn)) + with ( + as_file(files("iodata.test.data").joinpath("nh3_orca.molden")) as fn, + pytest.warns(FileFormatWarning), + ): + mol = load_one(fn) # write input in a temporary file - fname = os.path.join(tmpdir, 'input_from_molden.in') - write_input(mol, fname, fmt='orca') + fname = os.path.join(tmpdir, "input_from_molden.in") + write_input(mol, fname, fmt="orca") # compare saved input to expected input - with path('iodata.test.data', 'input_orca_nh3_sp_hf.txt') as fname_expected: + source = files("iodata.test.data").joinpath("input_orca_nh3_sp_hf.txt") + with as_file(source) as fname_expected: check_load_input_and_compare(fname, fname_expected) diff --git a/iodata/test/test_iodata.py b/iodata/test/test_iodata.py index 8718de69c..a71240742 100644 --- a/iodata/test/test_iodata.py +++ b/iodata/test/test_iodata.py @@ -16,28 +16,32 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- +# ruff: noqa: SLF001 """Test iodata.iodata module.""" - import numpy as np -from numpy.testing import assert_allclose, assert_equal import pytest +from numpy.testing import assert_allclose, assert_equal -from .common import compute_1rdm -from ..api import load_one, IOData +from ..api import IOData, load_one from ..overlap import compute_overlap +from .common import compute_1rdm + try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_typecheck(): m = IOData(atcoords=np.array([[1, 2, 3], [2, 3, 1]])) assert np.issubdtype(m.atcoords.dtype, np.floating) assert m.atnums is None - m = IOData(atnums=np.array([2.0, 3.0]), atcorenums=np.array([1, 1]), - atcoords=np.array([[1, 2, 3], [2, 3, 1]])) + m = IOData( + atnums=np.array([2.0, 3.0]), + atcorenums=np.array([1, 1]), + atcoords=np.array([[1, 2, 3], [2, 3, 1]]), + ) assert np.issubdtype(m.atnums.dtype, np.integer) assert np.issubdtype(m.atcorenums.dtype, np.floating) assert m.atnums is not None @@ -50,53 +54,50 @@ def test_typecheck_raises(): pytest.raises(TypeError, IOData, atcoords=np.array([[1, 2], [2, 3]])) pytest.raises(TypeError, IOData, atnums=np.array([[1, 2], [2, 3]])) # check inconsistency between various attributes - atnums, atcorenums, atcoords = np.array( - [2, 3]), np.array([1]), np.array([[1, 2, 3]]) - pytest.raises(TypeError, IOData, atnums=atnums, - atcorenums=atcorenums) + atnums, atcorenums, atcoords = np.array([2, 3]), np.array([1]), np.array([[1, 2, 3]]) + pytest.raises(TypeError, IOData, atnums=atnums, atcorenums=atcorenums) pytest.raises(TypeError, IOData, atnums=atnums, atcoords=atcoords) def test_unknown_format(): - pytest.raises(ValueError, load_one, 'foo.unknown_file_extension') + pytest.raises(ValueError, load_one, "foo.unknown_file_extension") def test_dm_water_sto3g_hf(): - with path('iodata.test.data', 'water_sto3g_hf_g03.fchk') as fn_fchk: + with as_file(files("iodata.test.data").joinpath("water_sto3g_hf_g03.fchk")) as fn_fchk: mol = load_one(str(fn_fchk)) - dm = mol.one_rdms['scf'] - assert_allclose(dm[0, 0], 2.10503807, atol=1.e-7) - assert_allclose(dm[0, 1], -0.439115917, atol=1.e-7) - assert_allclose(dm[1, 1], 1.93312061, atol=1.e-7) + dm = mol.one_rdms["scf"] + assert_allclose(dm[0, 0], 2.10503807, atol=1.0e-7) + assert_allclose(dm[0, 1], -0.439115917, atol=1.0e-7) + assert_allclose(dm[1, 1], 1.93312061, atol=1.0e-7) def test_dm_lih_sto3g_hf(): - with path('iodata.test.data', 'li_h_3-21G_hf_g09.fchk') as fn_fchk: + with as_file(files("iodata.test.data").joinpath("li_h_3-21G_hf_g09.fchk")) as fn_fchk: mol = load_one(str(fn_fchk)) - dm = mol.one_rdms['scf'] - assert_allclose(dm[0, 0], 1.96589709, atol=1.e-7) - assert_allclose(dm[0, 1], 0.122114249, atol=1.e-7) - assert_allclose(dm[1, 1], 0.0133112081, atol=1.e-7) - assert_allclose(dm[10, 10], 4.23924688E-01, atol=1.e-7) + dm = mol.one_rdms["scf"] + assert_allclose(dm[0, 0], 1.96589709, atol=1.0e-7) + assert_allclose(dm[0, 1], 0.122114249, atol=1.0e-7) + assert_allclose(dm[1, 1], 0.0133112081, atol=1.0e-7) + assert_allclose(dm[10, 10], 4.23924688e-01, atol=1.0e-7) - dm_spin = mol.one_rdms['scf_spin'] - assert_allclose(dm_spin[0, 0], 1.40210760E-03, atol=1.e-9) - assert_allclose(dm_spin[0, 1], -2.65370873E-03, atol=1.e-9) - assert_allclose(dm_spin[1, 1], 5.38701212E-03, atol=1.e-9) - assert_allclose(dm_spin[10, 10], 4.23889148E-01, atol=1.e-7) + dm_spin = mol.one_rdms["scf_spin"] + assert_allclose(dm_spin[0, 0], 1.40210760e-03, atol=1.0e-9) + assert_allclose(dm_spin[0, 1], -2.65370873e-03, atol=1.0e-9) + assert_allclose(dm_spin[1, 1], 5.38701212e-03, atol=1.0e-9) + assert_allclose(dm_spin[10, 10], 4.23889148e-01, atol=1.0e-7) def test_dm_ch3_rohf_g03(): - with path('iodata.test.data', 'ch3_rohf_sto3g_g03.fchk') as fn_fchk: + with as_file(files("iodata.test.data").joinpath("ch3_rohf_sto3g_g03.fchk")) as fn_fchk: mol = load_one(str(fn_fchk)) olp = compute_overlap(mol.obasis, mol.atcoords) dm = compute_1rdm(mol) - assert_allclose(np.einsum('ab,ba', olp, dm), 9, atol=1.e-6) + assert_allclose(np.einsum("ab,ba", olp, dm), 9, atol=1.0e-6) def test_charge_nelec1(): - # pylint: disable=protected-access # One a blank IOData object, charge and nelec can be set independently. mol = IOData() mol.nelec = 4 @@ -109,7 +110,6 @@ def test_charge_nelec1(): def test_charge_nelec2(): - # pylint: disable=protected-access # When atcorenums is set, nelec and charge become coupled. mol = IOData() mol.atcorenums = np.array([6.0, 1.0, 1.0, 1.0, 1.0]) @@ -123,7 +123,6 @@ def test_charge_nelec2(): def test_charge_nelec3(): - # pylint: disable=protected-access # When atcorenums is set, nelec and charge become coupled. mol = IOData() mol.atnums = np.array([6, 1, 1, 1, 1]) @@ -141,7 +140,6 @@ def test_charge_nelec3(): def test_charge_nelec4(): - # pylint: disable=protected-access # When atcorenums is set, nelec and charge become coupled. mol = IOData() mol.atnums = np.array([6, 1, 1, 1, 1]) @@ -154,7 +152,6 @@ def test_charge_nelec4(): def test_charge_nelec5(): - # pylint: disable=protected-access # When atcorenums is set, nelec and charge become coupled. mol = IOData() mol.charge = 1 @@ -169,7 +166,6 @@ def test_charge_nelec5(): def test_charge_nelec6(): - # pylint: disable=protected-access # When atcorenums is set, nelec and charge become coupled. mol = IOData() mol.nelec = 8 @@ -184,7 +180,6 @@ def test_charge_nelec6(): def test_charge_nelec7(): - # pylint: disable=protected-access # When atcorenums is set, nelec and charge become coupled. mol = IOData() mol.nelec = 8 @@ -209,7 +204,6 @@ def test_charge_nelec8(): def test_charge_nelec9(): - # pylint: disable=protected-access mol = IOData() mol.charge = 1.0 mol.atcorenums = np.array([8.0, 1.0, 1.0]) @@ -222,7 +216,6 @@ def test_charge_nelec9(): def test_charge_nelec10(): - # pylint: disable=protected-access mol = IOData() mol.charge = 1.0 mol.atnums = np.array([8, 1, 1]) @@ -263,7 +256,6 @@ def test_charge_nelec13(): def test_charge_nelec14(): - # pylint: disable=protected-access mol = IOData() mol.nelec = 8 mol.atcorenums = None @@ -313,10 +305,9 @@ def test_spinpol2(): def test_derived1(): - # pylint: disable=protected-access # When loading a file with molecular orbitals, nelec, charge and spinpol are # derived from the mo object: - with path('iodata.test.data', 'ch3_rohf_sto3g_g03.fchk') as fn_fchk: + with as_file(files("iodata.test.data").joinpath("ch3_rohf_sto3g_g03.fchk")) as fn_fchk: mol = load_one(str(fn_fchk)) assert mol.nelec == mol.mo.nelec assert mol.charge == mol.atcorenums.sum() - mol.mo.nelec @@ -333,7 +324,6 @@ def test_derived1(): def test_derived2(): - # pylint: disable=protected-access mol = IOData(atnums=[1, 1, 8], charge=1) assert mol._charge is None assert mol._nelec == mol.atcorenums.sum() - 1 diff --git a/iodata/test/test_json.py b/iodata/test/test_json.py index 54ada5685..4b205c20e 100644 --- a/iodata/test/test_json.py +++ b/iodata/test/test_json.py @@ -25,16 +25,16 @@ import pytest from ..api import dump_one, load_one -from ..utils import FileFormatWarning - +from ..utils import FileFormatError, FileFormatWarning try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files + # Tests for qcschema_molecule -# GEOMS: dict of str: np.ndarray(N, 3) +# GEOMS: dict of str: NDArray(N, 3) GEOMS = { "LiCl": np.array([[0.000000, 0.000000, -1.631761], [0.000000, 0.000000, 0.287958]]), "OHr": np.array([[0.0, 0.0, -0.12947694], [0.0, -1.49418734, 1.02744651]]), @@ -46,6 +46,8 @@ [4.253724, -2.762010, 0.382764], ] ), + "H2O": np.array([[0.0, 0.0, -0.1295], [0.0, -1.4942, 1.0274], [0.0, 1.4942, 1.0274]]), + "H2O_MP2": np.array([[0.0, 0.0, -0.1294], [0.0, -1.4941, 1.0274], [0.0, 1.4941, 1.0274]]), } # These molecule examples were manually generated for testing # MOL_FILES: (filename, atnums, charge, spinpol, geometry) @@ -59,10 +61,12 @@ ] -@pytest.mark.parametrize("filename, atnums, charge, spinpol, geometry, nwarn", MOL_FILES) +@pytest.mark.parametrize( + ("filename", "atnums", "charge", "spinpol", "geometry", "nwarn"), MOL_FILES +) def test_qcschema_molecule(filename, atnums, charge, spinpol, geometry, nwarn): """Test qcschema_molecule parsing using manually generated files.""" - with path("iodata.test.data", filename) as qcschema_molecule: + with as_file(files("iodata.test.data").joinpath(filename)) as qcschema_molecule: if nwarn == 0: mol = load_one(str(qcschema_molecule)) else: @@ -103,12 +107,14 @@ def test_qcschema_molecule(filename, atnums, charge, spinpol, geometry, nwarn): ] -@pytest.mark.parametrize("filename, atnums, charge, spinpol, nwarn", MOLSSI_MOL_FILES) +@pytest.mark.parametrize(("filename", "atnums", "charge", "spinpol", "nwarn"), MOLSSI_MOL_FILES) def test_molssi_qcschema_molecule(filename, atnums, charge, spinpol, nwarn): """Test qcschema_molecule parsing using MolSSI-sourced files.""" - with path("iodata.test.data", filename) as qcschema_molecule: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(qcschema_molecule)) + with ( + as_file(files("iodata.test.data").joinpath(filename)) as qcschema_molecule, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(qcschema_molecule)) np.testing.assert_equal(mol.atnums, atnums) assert mol.charge == charge @@ -131,17 +137,32 @@ def test_molssi_qcschema_molecule(filename, atnums, charge, spinpol, nwarn): ] -@pytest.mark.parametrize("filename, unparsed_dict", PASSTHROUGH_MOL_FILES) +@pytest.mark.parametrize(("filename", "unparsed_dict"), PASSTHROUGH_MOL_FILES) def test_passthrough_qcschema_molecule(filename, unparsed_dict): """Test qcschema_molecule parsing for passthrough of unparsed keys.""" - with path("iodata.test.data", filename) as qcschema_molecule: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(qcschema_molecule)) + with ( + as_file(files("iodata.test.data").joinpath(filename)) as qcschema_molecule, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(qcschema_molecule)) - assert mol.extra["unparsed"] == unparsed_dict + assert mol.extra["molecule"]["unparsed"] == unparsed_dict assert len(record) == 1 +def _check_provenance(mol1, mol2): + """Test the provenance information, if available, to avoid updating version on test files.""" + if "provenance" not in mol1: + return isinstance(mol2["provenance"], dict) + if isinstance(mol1["provenance"], dict): + return mol1["provenance"] in mol2["provenance"] + if isinstance(mol1["provenance"], list): + for entry in mol1["provenance"]: + assert entry in mol2["provenance"] + return True + return False + + INOUT_MOL_FILES = [ ("LiCl_molecule.json", 0), ("Hydroxyl_radical_molecule.json", 0), @@ -151,10 +172,10 @@ def test_passthrough_qcschema_molecule(filename, unparsed_dict): ] -@pytest.mark.parametrize("filename, nwarn", INOUT_MOL_FILES) +@pytest.mark.parametrize(("filename", "nwarn"), INOUT_MOL_FILES) def test_inout_qcschema_molecule(tmpdir, filename, nwarn): """Test that loading and dumping qcschema_molecule files retains all data.""" - with path("iodata.test.data", filename) as qcschema_molecule: + with as_file(files("iodata.test.data").joinpath(filename)) as qcschema_molecule: if nwarn == 0: mol = load_one(str(qcschema_molecule)) else: @@ -163,14 +184,18 @@ def test_inout_qcschema_molecule(tmpdir, filename, nwarn): assert len(record) == nwarn mol1 = json.loads(qcschema_molecule.read_bytes()) - fn_tmp = os.path.join(tmpdir, 'test_qcschema_mol.json') + fn_tmp = os.path.join(tmpdir, "test_qcschema_mol.json") dump_one(mol, fn_tmp) - with open(fn_tmp, "r") as mol2_in: + with open(fn_tmp) as mol2_in: mol2 = json.load(mol2_in) - # print(mol1) - # print(mol2) + # Check that prior provenance info is kept + assert _check_provenance(mol1, mol2) + if "provenance" in mol1: + del mol1["provenance"] + if "provenance" in mol2: + del mol2["provenance"] assert mol1 == mol2 @@ -183,23 +208,23 @@ def test_inout_qcschema_molecule(tmpdir, filename, nwarn): @pytest.mark.parametrize("filename", INOUT_MOLSSI_MOL_FILES) def test_inout_molssi_qcschema_molecule(tmpdir, filename): """Test that loading and dumping qcschema_molecule files retains all relevant data.""" - with path("iodata.test.data", filename) as qcschema_molecule: + with as_file(files("iodata.test.data").joinpath(filename)) as qcschema_molecule: with pytest.warns(FileFormatWarning) as record: mol = load_one(str(qcschema_molecule)) mol1_preproc = json.loads(qcschema_molecule.read_bytes()) assert len(record) == 1 - fn_tmp = os.path.join(tmpdir, 'test_qcschema_mol.json') + fn_tmp = os.path.join(tmpdir, "test_qcschema_mol.json") dump_one(mol, fn_tmp) - with open(fn_tmp, "r") as mol2_in: + with open(fn_tmp) as mol2_in: mol2 = json.load(mol2_in) # Extra processing for testing: # Remove all null entries and empty dicts in json # QCEngine seems to add null entries and empty dicts even for optional and empty keys fix_keys = {k: v for k, v in mol1_preproc.items() if v is not None} - fix_subkeys = dict() + fix_subkeys = {} for key in fix_keys: if isinstance(fix_keys[key], dict): fix_subkeys[key] = {k: v for k, v in fix_keys[key].items() if v is not None} @@ -209,15 +234,183 @@ def test_inout_molssi_qcschema_molecule(tmpdir, filename): for key in keys: if isinstance(mol1[key], dict) and not bool(mol1[key]): del mol1[key] + # Check that prior provenance info is kept + assert _check_provenance(mol1, mol2) + if "provenance" in mol1: + del mol1["provenance"] + if "provenance" in mol2: + del mol2["provenance"] assert mol1 == mol2 def test_ghost(tmpdir): - with path("iodata.test.data", "water_cluster_ghost.json") as qcschema_molecule: + source = files("iodata.test.data").joinpath("water_cluster_ghost.json") + with as_file(source) as qcschema_molecule: mol = load_one(str(qcschema_molecule)) np.testing.assert_allclose(mol.atcorenums, [8, 1, 1, 0, 0, 0, 0, 0, 0]) - fn_tmp = os.path.join(tmpdir, 'test_ghost.json') + fn_tmp = os.path.join(tmpdir, "test_ghost.json") dump_one(mol, fn_tmp) - with open(fn_tmp, "r") as mol2_in: + with open(fn_tmp) as mol2_in: mol2 = json.load(mol2_in) assert mol2["real"] == [True] * 3 + [False] * 6 + + +# input_files: (filename, explicit_basis, lot, obasis_name, run_type, geometry) +INPUT_FILES = [ + ("H2O_HF_STO3G_Gaussian_input.json", False, "HF", "STO-3G", "energy", GEOMS["H2O"]), + ("LiCl_string_STO4G_input.json", False, "B3LYP", "Def2TZVP", None, GEOMS["LiCl"]), + ("LiCl_explicit_STO4G_input.json", True, "HF", None, None, GEOMS["LiCl"]), + ("LiCl_STO4G_Gaussian_input.json", False, "HF", "STO-4G", "freq", GEOMS["LiCl"]), + ("water_mp2_input.json", False, "MP2", "cc-pVDZ", None, GEOMS["H2O_MP2"]), +] + + +@pytest.mark.parametrize( + ("filename", "explicit_basis", "lot", "obasis_name", "run_type", "geometry"), INPUT_FILES +) +def test_qcschema_input(filename, explicit_basis, lot, obasis_name, run_type, geometry): + with as_file(files("iodata.test.data").joinpath(filename)) as qcschema_input: + try: + mol = load_one(str(qcschema_input)) + assert mol.lot == lot + if obasis_name: + assert mol.obasis_name == obasis_name + if run_type: + assert mol.run_type == run_type + np.testing.assert_allclose(mol.atcoords, geometry) + # This will change if QCSchema Basis gets supported + except NotImplementedError: + assert explicit_basis + + +# Test passthrough for input files using modified versions of CuSCN_molecule.json +# PASSTHROUGH_INPUT_FILES: {filename, unparsed_dict, location} +PASSTHROUGH_INPUT_FILES = [ + ("LiCl_STO4G_Gaussian_input_extra.json", UNPARSED["extra"], "input"), + ("LiCl_STO4G_Gaussian_input_nested_extra.json", UNPARSED["nested_extra"], "input"), + ("LiCl_STO4G_Gaussian_input_extra_molecule.json", UNPARSED["extra"], "molecule"), +] + + +@pytest.mark.parametrize(("filename", "unparsed_dict", "location"), PASSTHROUGH_INPUT_FILES) +def test_passthrough_qcschema_input(filename, unparsed_dict, location): + """Test qcschema_molecule parsing for passthrough of unparsed keys.""" + with as_file(files("iodata.test.data").joinpath(filename)) as qcschema_input: + mol = load_one(str(qcschema_input)) + + assert mol.extra[location]["unparsed"] == unparsed_dict + + +INOUT_INPUT_FILES = [ + ("H2O_HF_STO3G_Gaussian_input.json", 0), + ("LiCl_string_STO4G_input.json", 0), + ("LiCl_STO4G_Gaussian_input.json", 0), + ("LiCl_STO4G_Gaussian_input_extra.json", 0), + ("LiCl_STO4G_Gaussian_input_nested_extra.json", 0), + ("LiCl_STO4G_Gaussian_input_extra_molecule.json", 0), +] + + +@pytest.mark.parametrize(("filename", "nwarn"), INOUT_INPUT_FILES) +def test_inout_qcschema_input(tmpdir, filename, nwarn): + """Test that loading and dumping qcschema_molecule files retains all data.""" + with as_file(files("iodata.test.data").joinpath(filename)) as qcschema_input: + if nwarn == 0: + mol = load_one(str(qcschema_input)) + else: + with pytest.warns(FileFormatWarning) as record: + mol = load_one(str(qcschema_input)) + assert len(record) == nwarn + mol1 = json.loads(qcschema_input.read_bytes()) + + fn_tmp = os.path.join(tmpdir, "test_input_mol.json") + dump_one(mol, fn_tmp) + + with open(fn_tmp) as mol2_in: + mol2 = json.load(mol2_in) + + # Check that prior provenance info is kept + assert _check_provenance(mol1, mol2) + if "provenance" in mol1: + del mol1["provenance"] + if "provenance" in mol1["molecule"]: + del mol1["molecule"]["provenance"] + if "provenance" in mol2: + del mol2["provenance"] + if "provenance" in mol2["molecule"]: + del mol2["molecule"]["provenance"] + assert mol1 == mol2 + + +# output_files: (filename, lot, obasis_name, run_type, nwarn) +OUTPUT_FILES = [ + ("H2O_CCSDprTpr_STO3G_output.json", "CCSD(T)", "sto-3g", None, 0), + ("LiCl_STO4G_Gaussian_output.json", "HF", "STO-4G", "Freq", 0), + ("xtb_water_no_basis.json", "XTB", None, None, 3), +] + + +@pytest.mark.parametrize(("filename", "lot", "obasis_name", "run_type", "nwarn"), OUTPUT_FILES) +def test_qcschema_output(filename, lot, obasis_name, run_type, nwarn): + with as_file(files("iodata.test.data").joinpath(filename)) as qcschema_output: + if nwarn == 0: + mol = load_one(str(qcschema_output)) + else: + with pytest.warns(FileFormatWarning) as record: + mol = load_one(str(qcschema_output)) + assert len(record) == nwarn + + assert mol.lot == lot + assert mol.obasis_name == obasis_name + assert mol.run_type == run_type + + +# Not a single valid example of qcschema_molecule is easily found for anything but water +# Some of these files have been manually validated, as reflected in the provenance +# bad_mol_files: (filename, error) +BAD_OUTPUT_FILES = [ + ("turbomole_water_energy_hf_output.json", FileFormatError), + ("turbomole_water_gradient_rimp2_output.json", FileFormatError), +] + + +@pytest.mark.parametrize(("filename", "error"), BAD_OUTPUT_FILES) +def test_bad_qcschema_files(filename, error): + # FIXME: these will move + with ( + as_file(files("iodata.test.data").joinpath(filename)) as qcschema_input, + pytest.raises(error), + ): + load_one(str(qcschema_input)) + + +INOUT_OUTPUT_FILES = [ + "H2O_CCSDprTpr_STO3G_output.json", + "LiCl_STO4G_Gaussian_output.json", +] + + +@pytest.mark.parametrize("filename", INOUT_OUTPUT_FILES) +def test_inout_qcschema_output(tmpdir, filename): + """Test that loading and dumping qcschema_molecule files retains all data.""" + with as_file(files("iodata.test.data").joinpath(filename)) as qcschema_input: + mol = load_one(str(qcschema_input)) + mol1 = json.loads(qcschema_input.read_bytes()) + + fn_tmp = os.path.join(tmpdir, "test_input_mol.json") + dump_one(mol, fn_tmp) + + with open(fn_tmp) as mol2_in: + mol2 = json.load(mol2_in) + + # Check that prior provenance info is kept + assert _check_provenance(mol1, mol2) + if "provenance" in mol1: + del mol1["provenance"] + if "provenance" in mol1["molecule"]: + del mol1["molecule"]["provenance"] + if "provenance" in mol2: + del mol2["provenance"] + if "provenance" in mol2["molecule"]: + del mol2["molecule"]["provenance"] + assert mol1 == mol2 diff --git a/iodata/test/test_locpot.py b/iodata/test/test_locpot.py index 7a4de1530..814a9fb4b 100644 --- a/iodata/test/test_locpot.py +++ b/iodata/test/test_locpot.py @@ -16,30 +16,30 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object """Test iodata.formats.locpot module.""" -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal from ..api import load_one from ..utils import angstrom, electronvolt, volume + try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_locpot_oxygen(): - with path('iodata.test.data', 'LOCPOT.oxygen') as fn: + with as_file(files("iodata.test.data").joinpath("LOCPOT.oxygen")) as fn: mol = load_one(str(fn)) - assert mol.title == 'O atom in a box' + assert mol.title == "O atom in a box" assert_equal(mol.atnums[0], 8) - assert_allclose(volume(mol.cellvecs), (10 * angstrom) ** 3, atol=1.e-10) + assert_allclose(volume(mol.cellvecs), (10 * angstrom) ** 3, atol=1.0e-10) assert_equal(len(mol.cube.shape), 3) assert_equal(mol.cube.shape, [1, 4, 2]) assert abs(mol.cube.origin).max() < 1e-10 d = mol.cube.data - assert_allclose(d[0, 0, 0] / electronvolt, 0.35046350435E+01, 1.e-10) - assert_allclose(d[0, 1, 0] / electronvolt, 0.213732132354E+01, 1.e-10) - assert_allclose(d[0, 2, 0] / electronvolt, -.65465465497E+01, 1.e-10) - assert_allclose(d[0, 2, 1] / electronvolt, -.546876467887E+01, 1.e-10) + assert_allclose(d[0, 0, 0] / electronvolt, 0.35046350435e01, 1.0e-10) + assert_allclose(d[0, 1, 0] / electronvolt, 0.213732132354e01, 1.0e-10) + assert_allclose(d[0, 2, 0] / electronvolt, -0.65465465497e01, 1.0e-10) + assert_allclose(d[0, 2, 1] / electronvolt, -0.546876467887e01, 1.0e-10) diff --git a/iodata/test/test_mol2.py b/iodata/test/test_mol2.py index 707af6c81..babebe5c4 100644 --- a/iodata/test/test_mol2.py +++ b/iodata/test/test_mol2.py @@ -21,62 +21,65 @@ import os import pytest -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from .common import truncated_file -from ..api import load_one, load_many, dump_one, dump_many -from ..utils import angstrom +from ..api import dump_many, dump_one, load_many, load_one from ..periodic import bond2num +from ..utils import angstrom +from .common import truncated_file try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_mol2_load_one(): # test mol2 one structure - with path('iodata.test.data', 'caffeine.mol2') as fn_mol: + with as_file(files("iodata.test.data").joinpath("caffeine.mol2")) as fn_mol: mol = load_one(str(fn_mol)) check_example(mol) def test_mol2_formaterror(tmpdir): # test if mol2 file has the wrong ending - with path('iodata.test.data', 'caffeine.mol2') as fn_test: - with truncated_file(fn_test, 2, 0, tmpdir) as fn: - with pytest.raises(IOError): - load_one(str(fn)) + with ( + as_file(files("iodata.test.data").joinpath("caffeine.mol2")) as fn_test, + truncated_file(fn_test, 2, 0, tmpdir) as fn, + pytest.raises(IOError), + ): + load_one(str(fn)) def test_mol2_symbol(): # test mol2 files with element symbols with two characters - with path('iodata.test.data', 'silioh3.mol2') as fn_mol: + with as_file(files("iodata.test.data").joinpath("silioh3.mol2")) as fn_mol: mol = load_one(str(fn_mol)) assert_equal(mol.atnums, [14, 3, 8, 1, 8, 1, 8, 1]) def test_bondtypes_benzene(): - with path('iodata.test.data', 'benzene.mol2') as fn_test: + with as_file(files("iodata.test.data").joinpath("benzene.mol2")) as fn_test: mol = load_one(str(fn_test)) assert_equal(mol.bonds[:, 2], [bond2num["ar"]] * 6 + [bond2num["1"]] * 6) def check_example(mol): """Test some things on example file.""" - assert mol.title == 'ZINC00001084' + assert mol.title == "ZINC00001084" assert_equal(mol.natom, 24) - assert_equal(mol.atnums, [6, 7, 6, 7, 6, 6, 6, 8, 7, 6, 8, 7, 6, 6, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) - assert mol.atffparams['attypes'][0] == 'C.3' + assert_equal( + mol.atnums, [6, 7, 6, 7, 6, 6, 6, 8, 7, 6, 8, 7, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ) + assert mol.atffparams["attypes"][0] == "C.3" # check coordinates atcoords_ang = mol.atcoords / angstrom assert_allclose(atcoords_ang[0], [-0.0178, 1.4608, 0.0101]) assert_allclose(atcoords_ang[1], [0.0021, -0.0041, 0.0020]) assert_allclose(atcoords_ang[22], [0.5971, -2.2951, 5.2627]) assert_allclose(atcoords_ang[23], [0.5705, -0.5340, 5.0055]) - assert_allclose(mol.atcharges['mol2charges'][0], 0.0684) - assert_allclose(mol.atcharges['mol2charges'][23], 0.0949) + assert_allclose(mol.atcharges["mol2charges"][0], 0.0684) + assert_allclose(mol.atcharges["mol2charges"][23], 0.0949) bonds = mol.bonds assert len(bonds) == 25 assert_equal(bonds[0], [0, 1, bond2num["1"]]) @@ -89,54 +92,54 @@ def check_load_dump_consistency(tmpdir, fn): """Check if dumping and loading an MOL2 file results in the same data.""" mol0 = load_one(str(fn)) # write mol2 file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test.mol2') - dump_one(mol0, fn_tmp, fmt='mol2') + fn_tmp = os.path.join(tmpdir, "test.mol2") + dump_one(mol0, fn_tmp, fmt="mol2") mol1 = load_one(fn_tmp) # check two mol2 files assert mol0.title == mol1.title assert_equal(mol0.atnums, mol1.atnums) - assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.e-5) + assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.0e-5) assert_equal(mol0.bonds, mol1.bonds) def test_load_dump_consistency(tmpdir): - with path('iodata.test.data', 'caffeine.mol2') as fn_mol2: + with as_file(files("iodata.test.data").joinpath("caffeine.mol2")) as fn_mol2: check_load_dump_consistency(tmpdir, fn_mol2) - with path('iodata.test.data', 'benzene.mol2') as fn_mol2: + with as_file(files("iodata.test.data").joinpath("benzene.mol2")) as fn_mol2: check_load_dump_consistency(tmpdir, fn_mol2) def test_load_many(): - with path('iodata.test.data', 'caffeine.mol2') as fn_mol2: + with as_file(files("iodata.test.data").joinpath("caffeine.mol2")) as fn_mol2: mols = list(load_many(str(fn_mol2))) assert len(mols) == 2 check_example(mols[0]) - assert mols[1].title == 'ZINC00001085' + assert mols[1].title == "ZINC00001085" assert mols[1].natom == 24 assert_allclose(mols[0].atcoords[0] / angstrom, [-0.0178, 1.4608, 0.0101]) assert_allclose(mols[1].atcoords[0] / angstrom, [-0.0100, 1.5608, 0.0201]) def test_load_dump_many_consistency(tmpdir): - with path('iodata.test.data', 'caffeine.mol2') as fn_mol2: + with as_file(files("iodata.test.data").joinpath("caffeine.mol2")) as fn_mol2: mols0 = list(load_many(str(fn_mol2))) # write mol2 file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test') - dump_many(mols0, fn_tmp, fmt='mol2') - mols1 = list(load_many(fn_tmp, fmt='mol2')) + fn_tmp = os.path.join(tmpdir, "test") + dump_many(mols0, fn_tmp, fmt="mol2") + mols1 = list(load_many(fn_tmp, fmt="mol2")) assert len(mols0) == len(mols1) for mol0, mol1 in zip(mols0, mols1): assert mol0.title == mol1.title assert_equal(mol0.atnums, mol1.atnums) - assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.e-5) + assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.0e-5) assert_equal(mol0.bonds, mol1.bonds) def test_load_dump_wrong_bond_num(tmpdir): - with path('iodata.test.data', 'silioh3.mol2') as fn_mol: + with as_file(files("iodata.test.data").joinpath("silioh3.mol2")) as fn_mol: mol = load_one(str(fn_mol)) mol.bonds[0][2] = -1 - fn_tmp = os.path.join(tmpdir, 'test.mol2') + fn_tmp = os.path.join(tmpdir, "test.mol2") dump_one(mol, fn_tmp) mol2 = load_one(fn_tmp) assert mol2.bonds[0][2] == bond2num["un"] diff --git a/iodata/test/test_molden.py b/iodata/test/test_molden.py index cce5955e0..bab0e8df9 100644 --- a/iodata/test/test_molden.py +++ b/iodata/test/test_molden.py @@ -16,47 +16,48 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object """Test iodata.formats.molden module.""" import os +import warnings -import attr +import attrs import numpy as np -from numpy.testing import assert_allclose, assert_equal import pytest +from numpy.testing import assert_allclose, assert_equal -from .common import compute_mulliken_charges, compare_mols, check_orthonormal -from ..api import load_one, dump_one +from ..api import dump_one, load_one from ..basis import convert_conventions from ..formats.molden import _load_low -from ..overlap import compute_overlap, OVERLAP_CONVENTIONS -from ..utils import LineIterator, angstrom, FileFormatWarning - +from ..overlap import OVERLAP_CONVENTIONS, compute_overlap +from ..utils import FileFormatWarning, LineIterator, angstrom +from .common import check_orthonormal, compare_mols, compute_mulliken_charges try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_molden_li2_orca(): - with path('iodata.test.data', 'li2.molden.input') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + with ( + as_file(files("iodata.test.data").joinpath("li2.molden.input")) as fn_molden, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "ORCA" in record[0].message.args[0] # Checkt title - assert mol.title == 'Molden file created by orca_2mkl for BaseName=li2' + assert mol.title == "Molden file created by orca_2mkl for BaseName=li2" # Check geometry assert_equal(mol.atnums, [3, 3]) assert_allclose(mol.mo.occsa[:4], [1, 1, 1, 0]) assert_allclose(mol.mo.occsb[:4], [1, 1, 0, 0]) - assert_equal(mol.mo.irreps, ['1a'] * mol.mo.norb) - assert_equal(mol.mo.irrepsa, ['1a'] * mol.mo.norba) - assert_equal(mol.mo.irrepsb, ['1a'] * mol.mo.norbb) + assert_equal(mol.mo.irreps, ["1a"] * mol.mo.norb) + assert_equal(mol.mo.irrepsa, ["1a"] * mol.mo.norba) + assert_equal(mol.mo.irrepsb, ["1a"] * mol.mo.norbb) assert_allclose(mol.atcoords[1], [5.2912331750, 0.0, 0.0]) # Check normalization @@ -67,23 +68,35 @@ def test_load_molden_li2_orca(): # Check Mulliken charges charges = compute_mulliken_charges(mol) expected_charges = np.array([0.5, 0.5]) - assert_allclose(charges, expected_charges, atol=1.e-5) + assert_allclose(charges, expected_charges, atol=1.0e-5) + + +def test_load_molden_li2_orca_huge_threshold(): + with ( + as_file(files("iodata.test.data").joinpath("li2.molden.input")) as fn_molden, + warnings.catch_warnings(), + ): + warnings.simplefilter("error") + # The threshold is set very high, which skip a correction for ORCA. + load_one(str(fn_molden), norm_threshold=1e4) def test_load_molden_h2o_orca(): - with path('iodata.test.data', 'h2o.molden.input') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + with ( + as_file(files("iodata.test.data").joinpath("h2o.molden.input")) as fn_molden, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "ORCA" in record[0].message.args[0] # Checkt title - assert mol.title == 'Molden file created by orca_2mkl for BaseName=h2o' + assert mol.title == "Molden file created by orca_2mkl for BaseName=h2o" # Check geometry assert_equal(mol.atnums, [8, 1, 1]) assert_allclose(mol.mo.occs[:6], [2, 2, 2, 2, 2, 0]) - assert_equal(mol.mo.irreps, ['1a'] * mol.mo.norb) + assert_equal(mol.mo.irreps, ["1a"] * mol.mo.norb) assert_allclose(mol.atcoords[2], [0.0, -0.1808833432, 1.9123825806]) # Check normalization @@ -93,13 +106,13 @@ def test_load_molden_h2o_orca(): # Check Mulliken charges charges = compute_mulliken_charges(mol) expected_charges = np.array([-0.816308, 0.408154, 0.408154]) - assert_allclose(charges, expected_charges, atol=1.e-5) + assert_allclose(charges, expected_charges, atol=1.0e-5) def test_load_molden_nh3_molden_pure(): # The file tested here is created with molden. It should be read in # properly without altering normalization and sign conventions. - with path('iodata.test.data', 'nh3_molden_pure.molden') as fn_molden: + with as_file(files("iodata.test.data").joinpath("nh3_molden_pure.molden")) as fn_molden: mol = load_one(str(fn_molden)) # Check geometry assert_equal(mol.atnums, [7, 1, 1, 1]) @@ -114,18 +127,18 @@ def test_load_molden_nh3_molden_pure(): # Comparison with numbers from the Molden program output. charges = compute_mulliken_charges(mol) molden_charges = np.array([0.0381, -0.2742, 0.0121, 0.2242]) - assert_allclose(charges, molden_charges, atol=1.e-3) + assert_allclose(charges, molden_charges, atol=1.0e-3) def test_load_molden_low_nh3_molden_cart(): - with path('iodata.test.data', 'nh3_molden_cart.molden') as fn_molden: + with as_file(files("iodata.test.data").joinpath("nh3_molden_cart.molden")) as fn_molden: lit = LineIterator(str(fn_molden)) data = _load_low(lit) - obasis = data['obasis'] + obasis = data["obasis"] assert obasis.nbasis == 52 assert len(obasis.shells) == 24 for shell in obasis.shells: - assert shell.kinds == ['c'] + assert shell.kinds == ["c"] assert shell.ncon == 1 for ishell in [0, 1, 2, 3, 9, 10, 11, 14, 15, 16, 19, 20, 21]: shell = obasis.shells[ishell] @@ -147,46 +160,97 @@ def test_load_molden_low_nh3_molden_cart(): shell0 = obasis.shells[0] assert shell0.nprim == 8 - assert shell0.exponents.shape == (8, ) - assert_allclose(shell0.exponents[4], 0.2856000000E+02) + assert shell0.exponents.shape == (8,) + assert_allclose(shell0.exponents[4], 0.2856000000e02) assert shell0.coeffs.shape == (8, 1) - assert_allclose(shell0.coeffs[4, 0], 0.2785706633E+00) + assert_allclose(shell0.coeffs[4, 0], 0.2785706633e00) shell7 = obasis.shells[7] assert shell7.nprim == 1 - assert shell7.exponents.shape == (1, ) - assert_allclose(shell7.exponents, [0.8170000000E+00]) + assert shell7.exponents.shape == (1,) + assert_allclose(shell7.exponents, [0.8170000000e00]) assert_allclose(shell7.coeffs, [[1.0]]) assert shell7.coeffs.shape == (1, 1) shell19 = obasis.shells[19] assert shell19.nprim == 3 - assert shell19.exponents.shape == (3, ) - assert_allclose(shell19.exponents, [ - 0.1301000000E+02, 0.1962000000E+01, 0.4446000000E+00]) - assert_allclose(shell19.coeffs, [ - [0.3349872639E-01], [0.2348008012E+00], [0.8136829579E+00]]) + assert shell19.exponents.shape == (3,) + assert_allclose(shell19.exponents, [0.1301000000e02, 0.1962000000e01, 0.4446000000e00]) + assert_allclose(shell19.coeffs, [[0.3349872639e-01], [0.2348008012e00], [0.8136829579e00]]) assert shell19.coeffs.shape == (3, 1) - assert data['mo'].coeffs.shape == (52, 52) - assert_allclose(data['mo'].coeffs[:2, 0], [1.002730, 0.005420]) - assert_allclose(data['mo'].coeffs[-2:, 1], [0.003310, -0.011620]) - assert_allclose(data['mo'].coeffs[-4:-2, -1], [-0.116400, 0.098220]) + assert data["mo"].coeffs.shape == (52, 52) + assert_allclose(data["mo"].coeffs[:2, 0], [1.002730, 0.005420]) + assert_allclose(data["mo"].coeffs[-2:, 1], [0.003310, -0.011620]) + assert_allclose(data["mo"].coeffs[-4:-2, -1], [-0.116400, 0.098220]) permutation, signs = convert_conventions(obasis, OVERLAP_CONVENTIONS) - assert_equal(permutation, [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 14, 18, 15, 19, - 22, 23, 20, 24, 21, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]) + assert_equal( + permutation, + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 16, + 17, + 14, + 18, + 15, + 19, + 22, + 23, + 20, + 24, + 21, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + ], + ) assert_equal(signs, [1] * 52) # Check normalization - olp = compute_overlap(obasis, data['atcoords']) - check_orthonormal(data['mo'].coeffs, olp, atol=1e-4) # low precision in file + olp = compute_overlap(obasis, data["atcoords"]) + check_orthonormal(data["mo"].coeffs, olp, atol=1e-4) # low precision in file def test_load_molden_nh3_molden_cart(): # The file tested here is created with molden. It should be read in # properly without altering normalization and sign conventions. - with path('iodata.test.data', 'nh3_molden_cart.molden') as fn_molden: + with as_file(files("iodata.test.data").joinpath("nh3_molden_cart.molden")) as fn_molden: mol = load_one(str(fn_molden)) # Check normalization @@ -197,15 +261,43 @@ def test_load_molden_nh3_molden_cart(): # Comparison with numbers from the Molden program output. charges = compute_mulliken_charges(mol) molden_charges = np.array([0.3138, -0.4300, -0.0667, 0.1829]) - assert_allclose(charges, molden_charges, atol=1.e-3) + assert_allclose(charges, molden_charges, atol=1.0e-3) + + +def test_load_molden_cfour(): + # The file tested here is created with CFOUR 2.1. + file_list = [ + "h_sonly_sph_cfour.molden", + "h_ponly_sph_cfour.molden", + "h_donly_sph_cfour.molden", + "h_fonly_sph_cfour.molden", + "h_gonly_sph_cfour.molden", + "h_sonly_cart_cfour.molden", + "h_ponly_cart_cfour.molden", + "h_donly_cart_cfour.molden", + "h_fonly_cart_cfour.molden", + "h_gonly_cart_cfour.molden", + "h2o_ccpvdz_cfour.molden", + ] + + for i in file_list: + with as_file(files("iodata.test.data").joinpath(i)) as fn_molden: + print(str(fn_molden)) + mol = load_one(str(fn_molden)) + # Check normalization + olp = compute_overlap(mol.obasis, mol.atcoords) + check_orthonormal(mol.mo.coeffsa, olp) + check_orthonormal(mol.mo.coeffsb, olp) def test_load_molden_nh3_orca(): # The file tested here is created with ORCA. It should be read in # properly by altering normalization and sign conventions. - with path('iodata.test.data', 'nh3_orca.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + with ( + as_file(files("iodata.test.data").joinpath("nh3_orca.molden")) as fn_molden, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "ORCA" in record[0].message.args[0] @@ -217,15 +309,17 @@ def test_load_molden_nh3_orca(): # Comparison with numbers from the Molden program output. charges = compute_mulliken_charges(mol) molden_charges = np.array([0.0381, -0.2742, 0.0121, 0.2242]) - assert_allclose(charges, molden_charges, atol=1.e-3) + assert_allclose(charges, molden_charges, atol=1.0e-3) def test_load_molden_nh3_psi4(): # The file tested here is created with PSI4 (pre 1.0). It should be read in # properly by altering normalization conventions. - with path('iodata.test.data', 'nh3_psi4.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + with ( + as_file(files("iodata.test.data").joinpath("nh3_psi4.molden")) as fn_molden, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "PSI4 < 1.0" in record[0].message.args[0] @@ -237,15 +331,17 @@ def test_load_molden_nh3_psi4(): # Comparison with numbers from the Molden program output. charges = compute_mulliken_charges(mol) molden_charges = np.array([0.0381, -0.2742, 0.0121, 0.2242]) - assert_allclose(charges, molden_charges, atol=1.e-3) + assert_allclose(charges, molden_charges, atol=1.0e-3) def test_load_molden_nh3_psi4_1(): # The file tested here is created with PSI4 (version 1.0). # It should be read in properly by renormalizing the contractions. - with path('iodata.test.data', 'nh3_psi4_1.0.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + with ( + as_file(files("iodata.test.data").joinpath("nh3_psi4_1.0.molden")) as fn_molden, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "unnormalized" in record[0].message.args[0] @@ -257,7 +353,7 @@ def test_load_molden_nh3_psi4_1(): # Comparison with numbers from the Molden program output. charges = compute_mulliken_charges(mol) molden_charges = np.array([0.0381, -0.2742, 0.0121, 0.2242]) - assert_allclose(charges, molden_charges, atol=1.e-3) + assert_allclose(charges, molden_charges, atol=1.0e-3) @pytest.mark.parametrize("case", ["zn", "mn", "cuh"]) @@ -265,9 +361,9 @@ def test_load_molden_high_am_psi4(case): # The file tested here is created with PSI4 1.3.2. # This is a special case because it contains higher angular momenta than # officially supported by the Molden format. Most virtual orbitals were removed. - with path('iodata.test.data', f'psi4_{case}_cc_pvqz_pure.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + source = files("iodata.test.data").joinpath(f"psi4_{case}_cc_pvqz_pure.molden") + with as_file(source) as fn_molden, pytest.warns(FileFormatWarning) as record: + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "unnormalized" in record[0].message.args[0] # Check normalization @@ -286,9 +382,9 @@ def test_load_molden_high_am_orca(case): # The file tested here is created with ORCA. # This is a special case because it contains higher angular momenta than # officially supported by the Molden format. Most virtual orbitals were removed. - with path('iodata.test.data', f'orca_{case}_cc_pvqz_pure.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + source = files("iodata.test.data").joinpath(f"orca_{case}_cc_pvqz_pure.molden") + with as_file(source) as fn_molden, pytest.warns(FileFormatWarning) as record: + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "ORCA" in record[0].message.args[0] # Check normalization @@ -299,7 +395,7 @@ def test_load_molden_high_am_orca(case): def test_load_molden_he2_ghost_psi4_1(): # The file tested here is created with PSI4 (version 1.0). - with path('iodata.test.data', 'he2_ghost_psi4_1.0.molden') as fn_molden: + with as_file(files("iodata.test.data").joinpath("he2_ghost_psi4_1.0.molden")) as fn_molden: mol = load_one(str(fn_molden)) np.testing.assert_equal(mol.atcorenums, np.array([0.0, 2.0])) @@ -317,9 +413,9 @@ def test_load_molden_he2_ghost_psi4_1(): def test_load_molden_h2o_6_31g_d_cart_psi4(): # The file tested here is created with PSI4 1.3.2. It should be read in # properly after fixing for errors in AO normalization conventions. - with path('iodata.test.data', 'h2o_psi4_1.3.2_6-31G_d_cart.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + source = files("iodata.test.data").joinpath("h2o_psi4_1.3.2_6-31G_d_cart.molden") + with as_file(source) as fn_molden, pytest.warns(FileFormatWarning) as record: + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "PSI4 <= 1.3.2" in record[0].message.args[0] @@ -331,15 +427,15 @@ def test_load_molden_h2o_6_31g_d_cart_psi4(): # Comparison with numbers from PSI4 output. charges = compute_mulliken_charges(mol) molden_charges = np.array([-0.86514, 0.43227, 0.43288]) - assert_allclose(charges, molden_charges, atol=1.e-5) + assert_allclose(charges, molden_charges, atol=1.0e-5) def test_load_molden_nh3_aug_cc_pvqz_cart_psi4(): # The file tested here is created with PSI4 1.3.2. It should be read in # properly after fixing for errors in AO normalization conventions. - with path('iodata.test.data', 'nh3_psi4_1.3.2_aug_cc_pvqz_cart.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + source = files("iodata.test.data").joinpath("nh3_psi4_1.3.2_aug_cc_pvqz_cart.molden") + with as_file(source) as fn_molden, pytest.warns(FileFormatWarning) as record: + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "PSI4 <= 1.3.2" in record[0].message.args[0] @@ -351,12 +447,12 @@ def test_load_molden_nh3_aug_cc_pvqz_cart_psi4(): # Comparison with numbers from PSI4 output. charges = compute_mulliken_charges(mol) molden_charges = np.array([-0.74507, 0.35743, 0.24197, 0.14567]) - assert_allclose(charges, molden_charges, atol=1.e-5) + assert_allclose(charges, molden_charges, atol=1.0e-5) def test_load_molden_nh3_molpro2012(): # The file tested here is created with MOLPRO2012. - with path('iodata.test.data', 'nh3_molpro2012.molden') as fn_molden: + with as_file(files("iodata.test.data").joinpath("nh3_molpro2012.molden")) as fn_molden: mol = load_one(str(fn_molden)) # Check normalization @@ -367,14 +463,14 @@ def test_load_molden_nh3_molpro2012(): # Comparison with numbers from the Molden program output. charges = compute_mulliken_charges(mol) molden_charges = np.array([0.0381, -0.2742, 0.0121, 0.2242]) - assert_allclose(charges, molden_charges, atol=1.e-3) + assert_allclose(charges, molden_charges, atol=1.0e-3) def test_load_molden_neon_turbomole(): # The file tested here is created with Turbomole 7.1. - with path('iodata.test.data', 'neon_turbomole_def2-qzvp.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + source = files("iodata.test.data").joinpath("neon_turbomole_def2-qzvp.molden") + with as_file(source) as fn_molden, pytest.warns(FileFormatWarning) as record: + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "Turbomole" in record[0].message.args[0] @@ -389,9 +485,11 @@ def test_load_molden_neon_turbomole(): def test_load_molden_nh3_turbomole(): # The file tested here is created with Turbomole 7.1 - with path('iodata.test.data', 'nh3_turbomole.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + with ( + as_file(files("iodata.test.data").joinpath("nh3_turbomole.molden")) as fn_molden, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "Turbomole" in record[0].message.args[0] @@ -405,13 +503,15 @@ def test_load_molden_nh3_turbomole(): # Cartesian functions. charges = compute_mulliken_charges(mol) molden_charges = np.array([0.03801, -0.27428, 0.01206, 0.22421]) - assert_allclose(charges, molden_charges, atol=1.e-3) + assert_allclose(charges, molden_charges, atol=1.0e-3) def test_load_molden_f(): - with path('iodata.test.data', 'F.molden') as fn_molden: - with pytest.warns(FileFormatWarning) as record: - mol = load_one(str(fn_molden)) + with ( + as_file(files("iodata.test.data").joinpath("F.molden")) as fn_molden, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(str(fn_molden)) assert len(record) == 1 assert "PSI4" in record[0].message.args[0] @@ -422,39 +522,42 @@ def test_load_molden_f(): assert_allclose(mol.mo.occsa[:6], [1, 1, 1, 1, 1, 0]) assert_allclose(mol.mo.occsb[:6], [1, 1, 1, 1, 0, 0]) - assert_equal(mol.mo.irrepsa[:6], ['Ag', 'Ag', 'B3u', 'B2u', 'B1u', 'B3u']) - assert_equal(mol.mo.irrepsb[:6], ['Ag', 'Ag', 'B3u', 'B2u', 'B1u', 'B3u']) - - -@pytest.mark.parametrize("fn,match", [ - ("h2o.molden.input", "ORCA"), - ("li2.molden.input", "ORCA"), - ("F.molden", "PSI4"), - ("nh3_molden_pure.molden", None), - ("nh3_molden_cart.molden", None), - ("he2_ghost_psi4_1.0.molden", None), - ("psi4_cuh_cc_pvqz_pure.molden", "unnormalized"), - ("hf_sto3g.fchk", None), - ("h_sto3g.fchk", None), - ("ch3_rohf_sto3g_g03.fchk", None), -]) + assert_equal(mol.mo.irrepsa[:6], ["Ag", "Ag", "B3u", "B2u", "B1u", "B3u"]) + assert_equal(mol.mo.irrepsb[:6], ["Ag", "Ag", "B3u", "B2u", "B1u", "B3u"]) + + +@pytest.mark.parametrize( + ("fn", "match"), + [ + ("h2o.molden.input", "ORCA"), + ("li2.molden.input", "ORCA"), + ("F.molden", "PSI4"), + ("nh3_molden_pure.molden", None), + ("nh3_molden_cart.molden", None), + ("he2_ghost_psi4_1.0.molden", None), + ("psi4_cuh_cc_pvqz_pure.molden", "unnormalized"), + ("hf_sto3g.fchk", None), + ("h_sto3g.fchk", None), + ("ch3_rohf_sto3g_g03.fchk", None), + ], +) def test_load_dump_consistency(tmpdir, fn, match): - with path('iodata.test.data', fn) as file_name: + with as_file(files("iodata.test.data").joinpath(fn)) as file_name: if match is None: mol1 = load_one(str(file_name)) else: with pytest.warns(FileFormatWarning, match=match): mol1 = load_one(str(file_name)) - fn_tmp = os.path.join(tmpdir, 'foo.bar') - dump_one(mol1, fn_tmp, fmt='molden') - mol2 = load_one(fn_tmp, fmt='molden') + fn_tmp = os.path.join(tmpdir, "foo.bar") + dump_one(mol1, fn_tmp, fmt="molden") + mol2 = load_one(fn_tmp, fmt="molden") # Remove and or fix some things in mol1 to make it compatible with what # can be read from a Molden file: # - Change basis of mol1 to segmented. mol1.obasis = mol1.obasis.get_segmented() # - Set default irreps in mol1, if not present. if mol1.mo.irreps is None: - mol1.mo = attr.evolve(mol1.mo, irreps=['1a'] * mol1.mo.norb) + mol1.mo = attrs.evolve(mol1.mo, irreps=["1a"] * mol1.mo.norb) # - Remove the one_rdms from mol1. mol1.one_rdms = {} compare_mols(mol1, mol2) diff --git a/iodata/test/test_molekel.py b/iodata/test/test_molekel.py index 1eb418201..da4cd20b2 100644 --- a/iodata/test/test_molekel.py +++ b/iodata/test/test_molekel.py @@ -16,19 +16,24 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object,no-member """Test iodata.formats.molekel module.""" import os +import warnings +from typing import Optional -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from .common import (check_orthonormal, compare_mols, compute_mulliken_charges, - load_one_warning) +from ..api import dump_one, load_one from ..basis import convert_conventions -from ..api import load_one, dump_one from ..overlap import compute_overlap from ..utils import angstrom +from .common import check_orthonormal, compare_mols, compute_mulliken_charges, load_one_warning + +try: + from importlib_resources import as_file, files +except ImportError: + from importlib.resources import as_file, files def compare_mols_diff_formats(mol1, mol2): @@ -51,7 +56,7 @@ def compare_mols_diff_formats(mol1, mol2): assert_allclose(charges1, charges2, rtol=0.0, atol=1.0e-6) -def check_load_dump_consistency(fn: str, tmpdir: str, match: str = None): +def check_load_dump_consistency(fn: str, tmpdir: str, match: Optional[str] = None): """Check if data is preserved after dumping and loading a Molekel file. Parameters @@ -66,36 +71,34 @@ def check_load_dump_consistency(fn: str, tmpdir: str, match: str = None): """ mol1 = load_one_warning(fn, match=match) - fn_tmp = os.path.join(tmpdir, 'foo.bar') - dump_one(mol1, fn_tmp, fmt='molekel') - mol2 = load_one(fn_tmp, fmt='molekel') - form = fn.split('.') - if 'molden' in form: - compare_mols_diff_formats(mol1, mol2) - elif 'fchk' in form: + fn_tmp = os.path.join(tmpdir, "foo.bar") + dump_one(mol1, fn_tmp, fmt="molekel") + mol2 = load_one(fn_tmp, fmt="molekel") + form = fn.split(".") + if "molden" in form or "fchk" in form: compare_mols_diff_formats(mol1, mol2) else: compare_mols(mol1, mol2) def test_load_dump_consistency_h2(tmpdir): - check_load_dump_consistency('h2_sto3g.mkl', tmpdir, match="ORCA") + check_load_dump_consistency("h2_sto3g.mkl", tmpdir, match="ORCA") def test_load_dump_consistency_ethanol(tmpdir): - check_load_dump_consistency('ethanol.mkl', tmpdir, match="ORCA") + check_load_dump_consistency("ethanol.mkl", tmpdir, match="ORCA") def test_load_dump_consistency_li2(tmpdir): - check_load_dump_consistency('li2.mkl', tmpdir, match="ORCA") + check_load_dump_consistency("li2.mkl", tmpdir, match="ORCA") def test_load_molden_dump_molekel_li2(tmpdir): - check_load_dump_consistency('li2.molden.input', tmpdir, match="ORCA") + check_load_dump_consistency("li2.molden.input", tmpdir, match="ORCA") def test_load_fchk_dump_molekel_li2(tmpdir): - check_load_dump_consistency('li2_g09_nbasis_indep.fchk', tmpdir) + check_load_dump_consistency("li2_g09_nbasis_indep.fchk", tmpdir) def test_load_mkl_ethanol(): @@ -106,11 +109,11 @@ def test_load_mkl_ethanol(): assert_equal(mol.atnums[0], 1) assert_equal(mol.atnums[4], 6) assert_equal(mol.atcoords.shape, (9, 3)) - assert_allclose(mol.atcoords[2, 1] / angstrom, 2.239037, atol=1.e-5) - assert_allclose(mol.atcoords[5, 2] / angstrom, 0.948420, atol=1.e-5) - assert_equal(mol.atcharges['mulliken'].shape, (9,)) + assert_allclose(mol.atcoords[2, 1] / angstrom, 2.239037, atol=1.0e-5) + assert_allclose(mol.atcoords[5, 2] / angstrom, 0.948420, atol=1.0e-5) + assert_equal(mol.atcharges["mulliken"].shape, (9,)) q = [0.143316, -0.445861, 0.173045, 0.173021, 0.024542, 0.143066, 0.143080, -0.754230, 0.400021] - assert_allclose(mol.atcharges['mulliken'], q) + assert_allclose(mol.atcharges["mulliken"], q) assert mol.obasis.nbasis == 39 assert_allclose(mol.obasis.shells[0].exponents[0], 18.731137000) assert_allclose(mol.obasis.shells[4].exponents[0], 7.868272400) @@ -137,8 +140,8 @@ def test_load_mkl_ethanol(): def test_load_mkl_li2(): mol = load_one_warning("li2.mkl", match="ORCA") - assert_equal(mol.atcharges['mulliken'].shape, (2,)) - assert_allclose(mol.atcharges['mulliken'], [0.5, 0.5]) + assert_equal(mol.atcharges["mulliken"].shape, (2,)) + assert_allclose(mol.atcharges["mulliken"], [0.5, 0.5]) # check mo normalization olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffsa, olp) @@ -147,8 +150,18 @@ def test_load_mkl_li2(): def test_load_mkl_h2(): mol = load_one_warning("h2_sto3g.mkl", match="ORCA") - assert_equal(mol.atcharges['mulliken'].shape, (2,)) - assert_allclose(mol.atcharges['mulliken'], [0, 0]) + assert_equal(mol.atcharges["mulliken"].shape, (2,)) + assert_allclose(mol.atcharges["mulliken"], [0, 0]) # check mo normalization olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffs, olp) + + +def test_load_mkl_h2_huge_threshold(): + with ( + as_file(files("iodata.test.data").joinpath("h2_sto3g.mkl")) as fn_molekel, + warnings.catch_warnings(), + ): + warnings.simplefilter("error") + # The threshold is set very high, which skip a correction for ORCA. + load_one(str(fn_molekel), norm_threshold=1e4) diff --git a/iodata/test/test_mwfn.py b/iodata/test/test_mwfn.py index c97226c09..d4e08a00d 100644 --- a/iodata/test/test_mwfn.py +++ b/iodata/test/test_mwfn.py @@ -18,56 +18,55 @@ # -- """Test iodata.formats.mwfn module.""" - import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal + from ..api import load_one from ..overlap import compute_overlap try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def load_helper(fn): """Load a test file with iodata.iodata.load_one.""" - with path('iodata.test.data', fn) as absfn: + with as_file(files("iodata.test.data").joinpath(fn)) as absfn: return load_one(absfn) -# pylint: disable=too-many-statements def test_load_mwfn_ch3_rohf_g03(): - mol = load_helper('ch3_rohf_sto3g_g03_fchk_multiwfn3.7.mwfn') + mol = load_helper("ch3_rohf_sto3g_g03_fchk_multiwfn3.7.mwfn") assert_equal(mol.mo.occs.shape[0], mol.mo.coeffs.shape[1]) assert_equal(mol.mo.occs.min(), 0.0) assert_equal(mol.mo.occs.max(), 2.0) - assert_equal(mol.extra['full_virial_ratio'], 2.00174844) - assert_equal(mol.extra['nindbasis'], 8) + assert_equal(mol.extra["full_virial_ratio"], 2.00174844) + assert_equal(mol.extra["nindbasis"], 8) assert_equal(np.sum([shell.nprim * shell.nbasis for shell in mol.obasis.shells]), 24) assert_equal(len(mol.obasis.shells), 6) assert_equal(np.sum([shell.nprim for shell in mol.obasis.shells]), 18) assert_equal(mol.charge, 0.0) assert_equal(mol.nelec, 9) assert_equal(mol.natom, 4) - assert_equal(mol.energy, -3.90732095E+01) + assert_equal(mol.energy, -3.90732095e01) assert_allclose([shell.angmoms[0] for shell in mol.obasis.shells], [0, 0, 1, 0, 0, 0]) assert_allclose([shell.icenter for shell in mol.obasis.shells], [0, 0, 0, 1, 2, 3]) assert_allclose([shell.nprim for shell in mol.obasis.shells], [3, 3, 3, 3, 3, 3]) - exponents1 = np.array([7.16168373E+01, 1.30450963E+01, 3.53051216E+00]) - exponents2 = np.array([2.94124936E+00, 6.83483096E-01, 2.22289916E-01]) - exponents3 = np.array([2.94124936E+00, 6.83483096E-01, 2.22289916E-01]) - exponents4 = np.array([3.42525091E+00, 6.23913730E-01, 1.68855404E-01]) + exponents1 = np.array([7.16168373e01, 1.30450963e01, 3.53051216e00]) + exponents2 = np.array([2.94124936e00, 6.83483096e-01, 2.22289916e-01]) + exponents3 = np.array([2.94124936e00, 6.83483096e-01, 2.22289916e-01]) + exponents4 = np.array([3.42525091e00, 6.23913730e-01, 1.68855404e-01]) assert_allclose(mol.obasis.shells[0].exponents, exponents1) assert_allclose(mol.obasis.shells[1].exponents, exponents2) assert_allclose(mol.obasis.shells[2].exponents, exponents3) assert_allclose(mol.obasis.shells[3].exponents, exponents4) assert_allclose(mol.obasis.shells[4].exponents, exponents4) assert_allclose(mol.obasis.shells[5].exponents, exponents4) - coeffs1 = np.array([[1.54328967E-01], [5.35328142E-01], [4.44634542E-01]]) - coeffs2 = np.array([[-9.99672292E-02], [3.99512826E-01], [7.00115469E-01]]) - coeffs3 = np.array([[1.55916275E-01], [6.07683719E-01], [3.91957393E-01]]) - coeffs4 = np.array([[1.54328967E-01], [5.35328142E-01], [4.44634542E-01]]) + coeffs1 = np.array([[1.54328967e-01], [5.35328142e-01], [4.44634542e-01]]) + coeffs2 = np.array([[-9.99672292e-02], [3.99512826e-01], [7.00115469e-01]]) + coeffs3 = np.array([[1.55916275e-01], [6.07683719e-01], [3.91957393e-01]]) + coeffs4 = np.array([[1.54328967e-01], [5.35328142e-01], [4.44634542e-01]]) assert_allclose(mol.obasis.shells[0].coeffs, coeffs1) assert_allclose(mol.obasis.shells[1].coeffs, coeffs2) assert_allclose(mol.obasis.shells[2].coeffs, coeffs3) @@ -75,101 +74,215 @@ def test_load_mwfn_ch3_rohf_g03(): assert_allclose(mol.obasis.shells[4].coeffs, coeffs4) assert_allclose(mol.obasis.shells[5].coeffs, coeffs4) # test first molecular orbital information - coeff = np.array([9.92532359E-01, 3.42148679E-02, 3.30477771E-06, - 1.97321450E-03, - 0.00000000E+00, -6.94439001E-03, - 6.94439001E-03, - 6.94539905E-03]) + coeff = np.array( + [ + 9.92532359e-01, + 3.42148679e-02, + 3.30477771e-06, + -1.97321450e-03, + 0.00000000e00, + -6.94439001e-03, + -6.94439001e-03, + -6.94539905e-03, + ] + ) assert_equal(mol.mo.coeffs[:, 0], coeff) - mo_energies = np.array([-1.09902284E+01, -8.36918686E-01, -5.24254982E-01, -5.23802785E-01, - -1.26686819E-02, 6.64707810E-01, 7.68278159E-01, 7.69362712E-01]) + mo_energies = np.array( + [ + -1.09902284e01, + -8.36918686e-01, + -5.24254982e-01, + -5.23802785e-01, + -1.26686819e-02, + 6.64707810e-01, + 7.68278159e-01, + 7.69362712e-01, + ] + ) assert_allclose(mol.mo.energies, mo_energies) assert_equal(mol.mo.occs[0], 2.000000) - assert_equal(mol.extra['mo_sym'][0], '?') + assert_equal(mol.extra["mo_sym"][0], "?") # test that for the same molecule fchk and mwfn generate the same objects. olp = compute_overlap(mol.obasis, mol.atcoords) - mol2 = load_helper('ch3_rohf_sto3g_g03.fchk') + mol2 = load_helper("ch3_rohf_sto3g_g03.fchk") olp_fchk = compute_overlap(mol2.obasis, mol2.atcoords) - assert_allclose(mol.atcoords, mol2.atcoords, atol=1E-7, rtol=1E-7) + assert_allclose(mol.atcoords, mol2.atcoords, atol=1e-7, rtol=1e-7) assert_allclose(mol2.obasis.shells[0].coeffs, coeffs1) # Mind the gap, I mean... the SP contraction assert_allclose(mol2.obasis.shells[1].coeffs[:, 0], np.squeeze(coeffs2.T)) assert_allclose(mol2.obasis.shells[1].coeffs[:, 1], np.squeeze(coeffs3.T)) assert_allclose(mol2.obasis.shells[3].coeffs, coeffs4) assert_allclose(mol2.obasis.shells[4].coeffs, coeffs4) - assert_allclose(olp, olp_fchk, atol=1E-7, rtol=1E-7) + assert_allclose(olp, olp_fchk, atol=1e-7, rtol=1e-7) def test_load_mwfn_ch3_hf_g03(): - mol = load_helper('ch3_hf_sto3g_fchk_multiwfn3.7.mwfn') + mol = load_helper("ch3_hf_sto3g_fchk_multiwfn3.7.mwfn") assert_equal(mol.mo.occs.shape[0], mol.mo.coeffs.shape[1]) - assert_equal(mol.extra['wfntype'], 1) + assert_equal(mol.extra["wfntype"], 1) # test first molecular orbital information - coeff = np.array([9.91912304E-01, 3.68365244E-02, 9.23239012E-04, 9.05953703E-04, - 9.05953703E-04, -7.36810756E-03, - 7.36810756E-03, - 7.36919429E-03]) + coeff = np.array( + [ + 9.91912304e-01, + 3.68365244e-02, + 9.23239012e-04, + 9.05953703e-04, + 9.05953703e-04, + -7.36810756e-03, + -7.36810756e-03, + -7.36919429e-03, + ] + ) assert_equal(mol.mo.coeffs[:, 0], coeff) - mo_energies = np.array([-1.10094534E+01, -9.07622407E-01, -5.37709620E-01, -5.37273275E-01, - -3.63936540E-01, 6.48361367E-01, 7.58140704E-01, 7.59223157E-01, - -1.09780991E+01, -8.01569083E-01, -5.19454722E-01, -5.18988806E-01, - 3.28562907E-01, 7.04456296E-01, 7.88139770E-01, 7.89228899E-01]) + mo_energies = np.array( + [ + -1.10094534e01, + -9.07622407e-01, + -5.37709620e-01, + -5.37273275e-01, + -3.63936540e-01, + 6.48361367e-01, + 7.58140704e-01, + 7.59223157e-01, + -1.09780991e01, + -8.01569083e-01, + -5.19454722e-01, + -5.18988806e-01, + 3.28562907e-01, + 7.04456296e-01, + 7.88139770e-01, + 7.89228899e-01, + ] + ) assert_allclose(mol.mo.energies, mo_energies) assert_equal(mol.mo.occs[0], 1.000000) - assert_equal(mol.extra['mo_sym'][0], '?') + assert_equal(mol.extra["mo_sym"][0], "?") # test that for the same molecule fchk and mwfn generate the same objects. olp = compute_overlap(mol.obasis, mol.atcoords) - mol2 = load_helper('ch3_hf_sto3g.fchk') + mol2 = load_helper("ch3_hf_sto3g.fchk") olp_fchk = compute_overlap(mol2.obasis, mol2.atcoords) - assert_allclose(mol.atcoords, mol2.atcoords, atol=1E-7, rtol=1E-7) - assert_allclose(olp, olp_fchk, atol=1E-7, rtol=1E-7) + assert_allclose(mol.atcoords, mol2.atcoords, atol=1e-7, rtol=1e-7) + assert_allclose(olp, olp_fchk, atol=1e-7, rtol=1e-7) def test_nelec_charge(): - mol1 = load_helper('ch3_rohf_sto3g_g03_fchk_multiwfn3.7.mwfn') + mol1 = load_helper("ch3_rohf_sto3g_g03_fchk_multiwfn3.7.mwfn") assert mol1.nelec == 9 assert mol1.charge == 0 - mol2 = load_helper('he_spdfgh_virtual_fchk_multiwfn3.7.mwfn') + mol2 = load_helper("he_spdfgh_virtual_fchk_multiwfn3.7.mwfn") assert mol2.nelec == 2 assert mol2.charge == 0 - mol3 = load_helper('ch3_hf_sto3g_fchk_multiwfn3.7.mwfn') + mol3 = load_helper("ch3_hf_sto3g_fchk_multiwfn3.7.mwfn") assert mol3.nelec == 9 assert mol3.charge == 0 def test_load_mwfn_he_spdfgh_g03(): - mol = load_helper('he_spdfgh_virtual_fchk_multiwfn3.7.mwfn') + mol = load_helper("he_spdfgh_virtual_fchk_multiwfn3.7.mwfn") assert_equal(mol.mo.occs.shape[0], mol.mo.coeffs.shape[1]) - assert_equal(mol.extra['wfntype'], 0) + assert_equal(mol.extra["wfntype"], 0) # test first molecular orbital information - coeff = np.array([ - 8.17125208E-01, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 1.58772965E-02, - 1.58772965E-02, 1.58772965E-02, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, - 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, - 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, - 7.73667846E-02, 0.00000000E+00, 4.53013505E-02, 0.00000000E+00, 7.73667846E-02, - 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 4.53013505E-02, - 0.00000000E+00, 4.53013505E-02, 0.00000000E+00, 0.00000000E+00, 7.73667846E-02, - 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, - 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, - 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, - 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, - 0.00000000E+00]) + coeff = np.array( + [ + 8.17125208e-01, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 1.58772965e-02, + 1.58772965e-02, + 1.58772965e-02, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 7.73667846e-02, + 0.00000000e00, + 4.53013505e-02, + 0.00000000e00, + 7.73667846e-02, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 4.53013505e-02, + 0.00000000e00, + 4.53013505e-02, + 0.00000000e00, + 0.00000000e00, + 7.73667846e-02, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + ] + ) assert_equal(mol.mo.coeffs[:, 0], coeff) - mo_energies = np.array([-3.83109139E-01, 6.72890652E-02, 6.72890652E-02, 6.72890652E-02, - 3.33282755E-01, 5.51389775E-01, 5.51389775E-01, 5.51389775E-01, - 5.51389775E-01, 5.51389775E-01, 8.85311032E-01, 8.85311032E-01, - 8.85311032E-01, 1.19945800E+00, 1.37176438E+00, 1.37176438E+00, - 1.37176438E+00, 1.37176438E+00, 1.37176438E+00, 1.37176438E+00, - 1.37176438E+00, 1.89666973E+00, 1.89666973E+00, 1.89666973E+00, - ]) + mo_energies = np.array( + [ + -3.83109139e-01, + 6.72890652e-02, + 6.72890652e-02, + 6.72890652e-02, + 3.33282755e-01, + 5.51389775e-01, + 5.51389775e-01, + 5.51389775e-01, + 5.51389775e-01, + 5.51389775e-01, + 8.85311032e-01, + 8.85311032e-01, + 8.85311032e-01, + 1.19945800e00, + 1.37176438e00, + 1.37176438e00, + 1.37176438e00, + 1.37176438e00, + 1.37176438e00, + 1.37176438e00, + 1.37176438e00, + 1.89666973e00, + 1.89666973e00, + 1.89666973e00, + ] + ) assert_allclose(mol.mo.energies[:24], mo_energies) # energies were truncated at 24 entries, this checks the last energy entry - assert mol.mo.energies[55] == 6.12473238E+00 + assert mol.mo.energies[55] == 6.12473238e00 assert_equal(mol.mo.occs[0], 2.000000) - assert_equal(mol.extra['mo_sym'][0], '?') + assert_equal(mol.extra["mo_sym"][0], "?") # this tests thhe last of the molecular orbital entries assert_equal(mol.mo.occs[55], 0.000000) - assert_equal(mol.extra['mo_sym'][55], '?') + assert_equal(mol.extra["mo_sym"][55], "?") # test that for the same molecule fchk and mwfn generate the same objects. olp = compute_overlap(mol.obasis, mol.atcoords) - mol2 = load_helper('he_spdfgh_virtual.fchk') + mol2 = load_helper("he_spdfgh_virtual.fchk") olp_fchk = compute_overlap(mol2.obasis, mol2.atcoords) - assert_allclose(mol.atcoords, mol2.atcoords, atol=1E-7, rtol=1E-7) - assert_allclose(olp, olp_fchk, atol=1E-7, rtol=1E-7) + assert_allclose(mol.atcoords, mol2.atcoords, atol=1e-7, rtol=1e-7) + assert_allclose(olp, olp_fchk, atol=1e-7, rtol=1e-7) diff --git a/iodata/test/test_orbitals.py b/iodata/test/test_orbitals.py index f1adcf712..78652d7ea 100644 --- a/iodata/test/test_orbitals.py +++ b/iodata/test/test_orbitals.py @@ -16,12 +16,10 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=pointless-statement """Unit tests for iodata.orbitals.""" - -import pytest import numpy as np +import pytest from numpy.testing import assert_equal from ..orbitals import MolecularOrbitals @@ -80,7 +78,8 @@ def test_restricted_occs(): def test_restricted_coeffs(): - coeffs = np.random.uniform(-1, 1, (7, 5)) + rng = np.random.default_rng(1) + coeffs = rng.uniform(-1, 1, (7, 5)) with pytest.raises(TypeError): MolecularOrbitals("restricted", 3, 3, coeffs=coeffs) mo = MolecularOrbitals("restricted", 5, 5, coeffs=coeffs) @@ -101,7 +100,8 @@ def test_restricted_coeffs(): def test_restricted_energies(): - energies = np.random.uniform(-1, 1, 5) + rng = np.random.default_rng(1) + energies = rng.uniform(-1, 1, 5) with pytest.raises(TypeError): MolecularOrbitals("restricted", 3, 3, energies=energies) mo = MolecularOrbitals("restricted", 5, 5, energies=energies) @@ -188,7 +188,8 @@ def test_unrestricted_occs(): def test_unrestricted_coeffs(): - coeffs = np.random.uniform(-1, 1, (7, 8)) + rng = np.random.default_rng(1) + coeffs = rng.uniform(-1, 1, (7, 8)) with pytest.raises(TypeError): MolecularOrbitals("unrestricted", 3, 2, coeffs=coeffs) mo = MolecularOrbitals("unrestricted", 5, 3, coeffs=coeffs) @@ -209,7 +210,8 @@ def test_unrestricted_coeffs(): def test_unrestricted_energies(): - energies = np.random.uniform(-1, 1, 8) + rng = np.random.default_rng(1) + energies = rng.uniform(-1, 1, 8) with pytest.raises(TypeError): MolecularOrbitals("unrestricted", 3, 2, energies=energies) mo = MolecularOrbitals("unrestricted", 5, 3, energies=energies) @@ -265,23 +267,23 @@ def test_generalized_empty(): assert mo.nbasis is None assert mo.norb is None with pytest.raises(NotImplementedError): - mo.spinpol + _ = mo.spinpol with pytest.raises(NotImplementedError): - mo.occsa + _ = mo.occsa with pytest.raises(NotImplementedError): - mo.occsb + _ = mo.occsb with pytest.raises(NotImplementedError): - mo.coeffsa + _ = mo.coeffsa with pytest.raises(NotImplementedError): - mo.coeffsb + _ = mo.coeffsb with pytest.raises(NotImplementedError): - mo.energiesa + _ = mo.energiesa with pytest.raises(NotImplementedError): - mo.energiesb + _ = mo.energiesb with pytest.raises(NotImplementedError): - mo.irrepsa + _ = mo.irrepsa with pytest.raises(NotImplementedError): - mo.irrepsb + _ = mo.irrepsb def test_generalized_occs(): @@ -292,27 +294,28 @@ def test_generalized_occs(): assert mo.nbasis is None assert mo.norb == 7 with pytest.raises(NotImplementedError): - mo.spinpol + _ = mo.spinpol with pytest.raises(NotImplementedError): - mo.occsa + _ = mo.occsa with pytest.raises(NotImplementedError): - mo.occsb + _ = mo.occsb with pytest.raises(NotImplementedError): - mo.coeffsa + _ = mo.coeffsa with pytest.raises(NotImplementedError): - mo.coeffsb + _ = mo.coeffsb with pytest.raises(NotImplementedError): - mo.energiesa + _ = mo.energiesa with pytest.raises(NotImplementedError): - mo.energiesb + _ = mo.energiesb with pytest.raises(NotImplementedError): - mo.irrepsa + _ = mo.irrepsa with pytest.raises(NotImplementedError): - mo.irrepsb + _ = mo.irrepsb def test_generalized_coeffs(): - coeffs = np.random.uniform(-1, 1, (10, 7)) + rng = np.random.default_rng(1) + coeffs = rng.uniform(-1, 1, (10, 7)) mo = MolecularOrbitals("generalized", None, None, coeffs=coeffs) assert mo.norba is None assert mo.norbb is None @@ -320,27 +323,28 @@ def test_generalized_coeffs(): assert mo.nbasis == 5 # 5 *spatial* basis functions! assert mo.norb == 7 with pytest.raises(NotImplementedError): - mo.spinpol + _ = mo.spinpol with pytest.raises(NotImplementedError): - mo.occsa + _ = mo.occsa with pytest.raises(NotImplementedError): - mo.occsb + _ = mo.occsb with pytest.raises(NotImplementedError): - mo.coeffsa + _ = mo.coeffsa with pytest.raises(NotImplementedError): - mo.coeffsb + _ = mo.coeffsb with pytest.raises(NotImplementedError): - mo.energiesa + _ = mo.energiesa with pytest.raises(NotImplementedError): - mo.energiesb + _ = mo.energiesb with pytest.raises(NotImplementedError): - mo.irrepsa + _ = mo.irrepsa with pytest.raises(NotImplementedError): - mo.irrepsb + _ = mo.irrepsb def test_generalized_energies(): - energies = np.random.uniform(-1, 1, 7) + rng = np.random.default_rng(1) + energies = rng.uniform(-1, 1, 7) mo = MolecularOrbitals("generalized", None, None, energies=energies) assert mo.norba is None assert mo.norbb is None @@ -348,23 +352,23 @@ def test_generalized_energies(): assert mo.nbasis is None assert mo.norb == 7 with pytest.raises(NotImplementedError): - mo.spinpol + _ = mo.spinpol with pytest.raises(NotImplementedError): - mo.occsa + _ = mo.occsa with pytest.raises(NotImplementedError): - mo.occsb + _ = mo.occsb with pytest.raises(NotImplementedError): - mo.coeffsa + _ = mo.coeffsa with pytest.raises(NotImplementedError): - mo.coeffsb + _ = mo.coeffsb with pytest.raises(NotImplementedError): - mo.energiesa + _ = mo.energiesa with pytest.raises(NotImplementedError): - mo.energiesb + _ = mo.energiesb with pytest.raises(NotImplementedError): - mo.irrepsa + _ = mo.irrepsa with pytest.raises(NotImplementedError): - mo.irrepsb + _ = mo.irrepsb def test_generalized_irreps(): @@ -376,20 +380,20 @@ def test_generalized_irreps(): assert mo.nbasis is None assert mo.norb == 7 with pytest.raises(NotImplementedError): - mo.spinpol + _ = mo.spinpol with pytest.raises(NotImplementedError): - mo.occsa + _ = mo.occsa with pytest.raises(NotImplementedError): - mo.occsb + _ = mo.occsb with pytest.raises(NotImplementedError): - mo.coeffsa + _ = mo.coeffsa with pytest.raises(NotImplementedError): - mo.coeffsb + _ = mo.coeffsb with pytest.raises(NotImplementedError): - mo.energiesa + _ = mo.energiesa with pytest.raises(NotImplementedError): - mo.energiesb + _ = mo.energiesb with pytest.raises(NotImplementedError): - mo.irrepsa + _ = mo.irrepsa with pytest.raises(NotImplementedError): - mo.irrepsb + _ = mo.irrepsb diff --git a/iodata/test/test_orcalog.py b/iodata/test/test_orcalog.py index 90cb8605b..60c5e8192 100644 --- a/iodata/test/test_orcalog.py +++ b/iodata/test/test_orcalog.py @@ -16,38 +16,40 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object """Test iodata.formats.orcalog module.""" import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal from ..api import load_one from ..utils import angstrom try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_water_number(): - with path('iodata.test.data', 'water_orca.out') as fn: + with as_file(files("iodata.test.data").joinpath("water_orca.out")) as fn: mol = load_one(fn) # Test atomic numbers and number of atoms assert mol.natom == 3 assert_equal(mol.atnums, [8, 1, 1]) # check bond length - assert_allclose(np.linalg.norm( - mol.atcoords[0] - mol.atcoords[1]) / angstrom, 0.9500, atol=1.e-5) - assert_allclose(np.linalg.norm( - mol.atcoords[0] - mol.atcoords[2]) / angstrom, 0.9500, atol=1.e-5) - assert_allclose(np.linalg.norm( - mol.atcoords[1] - mol.atcoords[2]) / angstrom, 1.5513, atol=1.e-4) + assert_allclose( + np.linalg.norm(mol.atcoords[0] - mol.atcoords[1]) / angstrom, 0.9500, atol=1.0e-5 + ) + assert_allclose( + np.linalg.norm(mol.atcoords[0] - mol.atcoords[2]) / angstrom, 0.9500, atol=1.0e-5 + ) + assert_allclose( + np.linalg.norm(mol.atcoords[1] - mol.atcoords[2]) / angstrom, 1.5513, atol=1.0e-4 + ) # check energies of scf cycles energies = np.array([-76.34739931, -76.34740001, -76.34740005, -76.34740029]) - assert_allclose(mol.extra['scf_energies'], energies) + assert_allclose(mol.extra["scf_energies"], energies) # check scf energy assert_allclose(mol.energy, -76.347791524303, atol=1e-8) # check dipole moment - assert_allclose(mol.moments[(1, 'c')], [0.76499, 0.00000, 0.54230]) + assert_allclose(mol.moments[(1, "c")], [0.76499, 0.00000, 0.54230]) diff --git a/iodata/test/test_overlap.py b/iodata/test/test_overlap.py index 797d7de49..6ad705a2f 100644 --- a/iodata/test/test_overlap.py +++ b/iodata/test/test_overlap.py @@ -18,27 +18,53 @@ # -- """Test iodata.overlap & iodata.overlap_accel modules.""" -import attr +import itertools + +import attrs import numpy as np +import pytest from numpy.testing import assert_allclose -from pytest import raises from ..api import load_one -from ..basis import MolecularBasis, Shell -from ..overlap import compute_overlap, OVERLAP_CONVENTIONS +from ..basis import MolecularBasis, Shell, convert_conventions +from ..overlap import OVERLAP_CONVENTIONS, compute_overlap, factorial2 try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files + + +@pytest.mark.parametrize( + ("inp", "out"), [(0, 1), (1, 1), (2, 2), (3, 3), (4, 8), (5, 15), (-1, 1), (-2, 0)] +) +def test_factorial2_integer_arguments(inp, out): + assert factorial2(inp) == out + assert isinstance(factorial2(inp), int) + + +def test_factorial2_float_arguments(): + with pytest.raises(TypeError): + factorial2(1.0) + + +def test_factorial2_integer_array_argument(): + assert (factorial2(np.array([-2, -1, 4, 5])) == np.array([0, 1, 8, 15])).all() + assert (factorial2(np.array([[-2, -1], [4, 5]])) == np.array([[0, 1], [8, 15]])).all() + assert issubclass(factorial2(np.array([-2, -1, 4, 5])).dtype.type, np.integer) + + +def test_factorial2_float_array_argument(): + with pytest.raises(TypeError): + factorial2(np.array([0.0, 1.0, 2.0, 3.0])) def test_normalization_basics_segmented(): for angmom in range(7): - shells = [Shell(0, [angmom], ['c'], np.array([0.23]), np.array([[1.0]]))] + shells = [Shell(0, [angmom], ["c"], np.array([0.23]), np.array([[1.0]]))] if angmom >= 2: - shells.append(Shell(0, [angmom], ['p'], np.array([0.23]), np.array([[1.0]]))) - obasis = MolecularBasis(shells, OVERLAP_CONVENTIONS, 'L2') + shells.append(Shell(0, [angmom], ["p"], np.array([0.23]), np.array([[1.0]]))) + obasis = MolecularBasis(shells, OVERLAP_CONVENTIONS, "L2") atcoords = np.zeros((1, 3)) overlap = compute_overlap(obasis, atcoords) assert_allclose(np.diag(overlap), np.ones(obasis.nbasis)) @@ -46,42 +72,98 @@ def test_normalization_basics_segmented(): def test_normalization_basics_generalized(): for angmom in range(2, 7): - shells = [Shell(0, [angmom] * 2, ['c', 'p'], np.array([0.23]), np.array([[1.0, 1.0]]))] - obasis = MolecularBasis(shells, OVERLAP_CONVENTIONS, 'L2') + shells = [Shell(0, [angmom] * 2, ["c", "p"], np.array([0.23]), np.array([[1.0, 1.0]]))] + obasis = MolecularBasis(shells, OVERLAP_CONVENTIONS, "L2") atcoords = np.zeros((1, 3)) overlap = compute_overlap(obasis, atcoords) assert_allclose(np.diag(overlap), np.ones(obasis.nbasis)) def test_load_fchk_hf_sto3g_num(): - with path('iodata.test.data', 'load_fchk_hf_sto3g_num.npy') as fn_npy: + with as_file(files("iodata.test.data").joinpath("load_fchk_hf_sto3g_num.npy")) as fn_npy: ref = np.load(str(fn_npy)) - with path('iodata.test.data', 'hf_sto3g.fchk') as fn_fchk: + with as_file(files("iodata.test.data").joinpath("hf_sto3g.fchk")) as fn_fchk: data = load_one(fn_fchk) - assert_allclose(ref, compute_overlap(data.obasis, data.atcoords), rtol=1.e-5, atol=1.e-8) + assert_allclose(ref, compute_overlap(data.obasis, data.atcoords), rtol=1.0e-5, atol=1.0e-8) def test_load_fchk_o2_cc_pvtz_pure_num(): - with path('iodata.test.data', - 'load_fchk_o2_cc_pvtz_pure_num.npy') as fn_npy: + source = files("iodata.test.data").joinpath("load_fchk_o2_cc_pvtz_pure_num.npy") + with as_file(source) as fn_npy: ref = np.load(str(fn_npy)) - with path('iodata.test.data', 'o2_cc_pvtz_pure.fchk') as fn_fchk: + with as_file(files("iodata.test.data").joinpath("o2_cc_pvtz_pure.fchk")) as fn_fchk: data = load_one(fn_fchk) - assert_allclose(ref, compute_overlap(data.obasis, data.atcoords), rtol=1.e-5, atol=1.e-8) + assert_allclose(ref, compute_overlap(data.obasis, data.atcoords), rtol=1.0e-5, atol=1.0e-8) def test_load_fchk_o2_cc_pvtz_cart_num(): - with path('iodata.test.data', - 'load_fchk_o2_cc_pvtz_cart_num.npy') as fn_npy: + source = files("iodata.test.data").joinpath("load_fchk_o2_cc_pvtz_cart_num.npy") + with as_file(source) as fn_npy: ref = np.load(str(fn_npy)) - with path('iodata.test.data', 'o2_cc_pvtz_cart.fchk') as fn_fchk: + with as_file(files("iodata.test.data").joinpath("o2_cc_pvtz_cart.fchk")) as fn_fchk: data = load_one(fn_fchk) - obasis = attr.evolve(data.obasis, conventions=OVERLAP_CONVENTIONS) - assert_allclose(ref, compute_overlap(obasis, data.atcoords), rtol=1.e-5, atol=1.e-8) + obasis = attrs.evolve(data.obasis, conventions=OVERLAP_CONVENTIONS) + assert_allclose(ref, compute_overlap(obasis, data.atcoords), rtol=1.0e-5, atol=1.0e-8) def test_overlap_l1(): - dbasis = MolecularBasis([], {}, 'L1') + dbasis = MolecularBasis([], {}, "L1") atcoords = np.zeros((1, 3)) - with raises(ValueError): + with pytest.raises(ValueError): _ = compute_overlap(dbasis, atcoords) + obasis = MolecularBasis([], {}, "L2") + with pytest.raises(ValueError): + _ = compute_overlap(obasis, atcoords, dbasis, atcoords) + + +def test_overlap_two_basis_exceptions(): + with as_file(files("iodata.test.data").joinpath("hf_sto3g.fchk")) as fn_fchk: + data = load_one(fn_fchk) + with pytest.raises(TypeError): + compute_overlap(data.obasis, data.atcoords, data.obasis, None) + with pytest.raises(TypeError): + compute_overlap(data.obasis, data.atcoords, None, data.atcoords) + + +FNS_TWO_BASIS = [ + "h_sto3g.fchk", + "hf_sto3g.fchk", + "2h-azirine-cc.fchk", + "water_ccpvdz_pure_hf_g03.fchk", +] + + +@pytest.mark.parametrize("fn", FNS_TWO_BASIS) +def test_overlap_two_basis_same(fn): + with as_file(files("iodata.test.data").joinpath(fn)) as pth: + mol = load_one(pth) + olp_a = compute_overlap(mol.obasis, mol.atcoords, mol.obasis, mol.atcoords) + olp_b = compute_overlap(mol.obasis, mol.atcoords) + assert_allclose(olp_a, olp_b, rtol=0, atol=1e-14) + + +@pytest.mark.parametrize(("fn0", "fn1"), itertools.combinations_with_replacement(FNS_TWO_BASIS, 2)) +def test_overlap_two_basis_different(fn0, fn1): + with as_file(files("iodata.test.data").joinpath(fn0)) as pth0: + mol0 = load_one(pth0) + with as_file(files("iodata.test.data").joinpath(fn1)) as pth1: + mol1 = load_one(pth1) + # Direct computation of the off-diagonal block. + olp_a = compute_overlap(mol0.obasis, mol0.atcoords, mol1.obasis, mol1.atcoords) + # Poor-man's approach: combine two molecules into one and compute its + # overlap matrix. + atcoords = np.concatenate([mol0.atcoords, mol1.atcoords]) + shells = mol0.obasis.shells + [ + attrs.evolve(shell, icenter=shell.icenter + mol0.natom) for shell in mol1.obasis.shells + ] + obasis = MolecularBasis(shells, OVERLAP_CONVENTIONS, "L2") + olp_big = compute_overlap(obasis, atcoords) + # Get the off-diagonal block and reorder. + olp_b = olp_big[: olp_a.shape[0], olp_a.shape[0] :] + assert olp_a.shape == olp_b.shape + permutation0, signs0 = convert_conventions(mol0.obasis, OVERLAP_CONVENTIONS, reverse=True) + olp_b = olp_b[permutation0] * signs0.reshape(-1, 1) + permutation1, signs1 = convert_conventions(mol1.obasis, OVERLAP_CONVENTIONS, reverse=True) + olp_b = olp_b[:, permutation1] * signs1 + # Finally compare the numbers. + assert_allclose(olp_a, olp_b, rtol=0, atol=1e-14) diff --git a/iodata/test/test_pdb.py b/iodata/test/test_pdb.py index 0f8ca9322..956a1c8fd 100644 --- a/iodata/test/test_pdb.py +++ b/iodata/test/test_pdb.py @@ -21,170 +21,231 @@ import os import numpy as np -from numpy.testing import assert_equal, assert_allclose import pytest +from numpy.testing import assert_allclose, assert_equal + +from ..api import dump_many, dump_one, load_many, load_one +from ..utils import FileFormatWarning, angstrom -from ..api import load_one, load_many, dump_one, dump_many -from ..utils import angstrom, FileFormatWarning try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files @pytest.mark.parametrize("case", ["single", "single_model"]) def test_load_water(case): # test pdb of water - with path('iodata.test.data', f'water_{case}.pdb') as fn_pdb: + with as_file(files("iodata.test.data").joinpath(f"water_{case}.pdb")) as fn_pdb: mol = load_one(str(fn_pdb)) check_water(mol) def test_load_water_no_end(): # test pdb of water - with path('iodata.test.data', 'water_single_no_end.pdb') as fn_pdb: - with pytest.warns(FileFormatWarning, match="The END is not found"): - mol = load_one(str(fn_pdb)) + with ( + as_file(files("iodata.test.data").joinpath("water_single_no_end.pdb")) as fn_pdb, + pytest.warns(FileFormatWarning, match="The END is not found"), + ): + mol = load_one(str(fn_pdb)) check_water(mol) def check_water(mol): """Test some things on a water file.""" + assert mol.title == "water" assert_equal(mol.atnums, [1, 8, 1]) # check bond length - assert_allclose(np.linalg.norm( - mol.atcoords[0] - mol.atcoords[1]) / angstrom, 0.9599, atol=1.e-4) - assert_allclose(np.linalg.norm( - mol.atcoords[2] - mol.atcoords[1]) / angstrom, 0.9599, atol=1.e-4) - assert_allclose(np.linalg.norm( - mol.atcoords[0] - mol.atcoords[2]) / angstrom, 1.568, atol=1.e-3) - + assert_allclose( + np.linalg.norm(mol.atcoords[0] - mol.atcoords[1]) / angstrom, 0.9599, atol=1.0e-4 + ) + assert_allclose( + np.linalg.norm(mol.atcoords[2] - mol.atcoords[1]) / angstrom, 0.9599, atol=1.0e-4 + ) + assert_allclose( + np.linalg.norm(mol.atcoords[0] - mol.atcoords[2]) / angstrom, 1.568, atol=1.0e-3 + ) + assert_equal(mol.bonds[:, :2], [[0, 1], [1, 2]]) + + +@pytest.mark.parametrize( + ("fn_base", "should_warn"), + [ + ("water_single.pdb", False), + ("water_single_model.pdb", False), + ("ch5plus.pdb", False), + ("2luv.pdb", True), + ("2bcw.pdb", False), + ], +) +def test_load_dump_consistency(fn_base, should_warn, tmpdir): + with as_file(files("iodata.test.data").joinpath(fn_base)) as fn_pdb: + if should_warn: + with pytest.warns(FileFormatWarning) as record: + mol0 = load_one(str(fn_pdb)) + assert len(record) > 1 + else: + mol0 = load_one(str(fn_pdb)) -def check_load_dump_consistency(tmpdir, fn): - """Check if dumping and loading an PDB file results in the same data.""" - mol0 = load_one(str(fn)) # write pdb file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test.pdb') + fn_tmp = os.path.join(tmpdir, "test.pdb") dump_one(mol0, fn_tmp) mol1 = load_one(fn_tmp) # check two pdb files assert mol0.title == mol1.title assert_equal(mol0.atnums, mol1.atnums) - assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.e-5) + assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.0e-5) if mol0.atffparams is not None: - assert_equal(mol0.atffparams.get('attypes'), mol1.atffparams.get('attypes')) - assert_equal(mol0.atffparams.get('restypes'), mol1.atffparams.get('restypes')) - assert_equal(mol0.atffparams.get('resnums'), mol1.atffparams.get('resnums')) + assert_equal(mol0.atffparams.get("attypes"), mol1.atffparams.get("attypes")) + assert_equal(mol0.atffparams.get("restypes"), mol1.atffparams.get("restypes")) + assert_equal(mol0.atffparams.get("resnums"), mol1.atffparams.get("resnums")) if mol0.extra is not None: - assert_equal(mol0.extra.get('occupancies'), mol1.extra.get('occupancies')) - assert_equal(mol0.extra.get('bfactors'), mol1.extra.get('bfactors')) - assert_equal(mol0.extra.get('chainids'), mol1.extra.get('chainids')) + assert_equal(mol0.extra.get("occupancies"), mol1.extra.get("occupancies")) + assert_equal(mol0.extra.get("bfactors"), mol1.extra.get("bfactors")) + assert_equal(mol0.extra.get("chainids"), mol1.extra.get("chainids")) + if mol0.bonds is None: + assert mol1.bonds is None + else: + assert_equal(mol0.bonds, mol1.bonds) -@pytest.mark.parametrize("case", ["single", "single_model"]) -def test_load_dump_consistency(case, tmpdir): - with path('iodata.test.data', f'water_{case}.pdb') as fn_pdb: - check_load_dump_consistency(tmpdir, fn_pdb) - +def test_load_dump_xyz_consistency(tmpdir): + with as_file(files("iodata.test.data").joinpath("water.xyz")) as fn_xyz: + mol0 = load_one(str(fn_xyz)) -def check_load_dump_xyz_consistency(tmpdir, fn): - """Check if dumping PDB from an xyz file results in the same data.""" - mol0 = load_one(str(fn)) # write xyz file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test.pdb') + fn_tmp = os.path.join(tmpdir, "test.pdb") dump_one(mol0, fn_tmp) mol1 = load_one(fn_tmp) # check two molecule classes to be the same assert mol0.title == mol1.title assert_equal(mol0.atnums, mol1.atnums) - assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.e-2) + assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.0e-2) # check if the general restype and attype are correct - restypes = mol1.atffparams.get('restypes') - attypes = mol1.atffparams.get('attypes') + restypes = mol1.atffparams.get("restypes") + attypes = mol1.atffparams.get("attypes") assert restypes[0] == "XXX" assert attypes[0] == "H1" assert mol1.extra.get("chainids") is None # check if resnums are correct - resnums = mol1.atffparams.get('resnums') + resnums = mol1.atffparams.get("resnums") assert_equal(resnums[0], -1) - - -def test_load_dump_xyz_consistency(tmpdir): - with path('iodata.test.data', 'water.xyz') as fn_xyz: - check_load_dump_xyz_consistency(tmpdir, fn_xyz) + # There should be no bonds + assert mol1.bonds is None def test_load_peptide_2luv(): # test pdb of small peptide - with path('iodata.test.data', '2luv.pdb') as fn_pdb: + with ( + as_file(files("iodata.test.data").joinpath("2luv.pdb")) as fn_pdb, + pytest.warns(FileFormatWarning) as record, + ): mol = load_one(str(fn_pdb)) + assert len(record) == 271 assert mol.title.startswith("INTEGRIN") assert_equal(len(mol.atnums), 547) - restypes = mol.atffparams.get('restypes') + restypes = mol.atffparams.get("restypes") assert restypes[0] == "LYS" assert restypes[-1] == "LYS" - attypes = mol.atffparams.get('attypes') + attypes = mol.atffparams.get("attypes") assert attypes[0] == "N" assert attypes[-1] == "O" - resnums = mol.atffparams.get('resnums') + resnums = mol.atffparams.get("resnums") assert_equal(resnums[0], 1) assert_equal(resnums[-1], 35) - assert_allclose(mol.extra.get('occupancies'), np.ones(mol.natom)) - assert_allclose(mol.extra.get('bfactors'), np.zeros(mol.natom)) - assert_equal(mol.extra.get('chainids'), ['A'] * mol.natom) + assert_allclose(mol.extra.get("occupancies"), np.ones(mol.natom)) + assert_allclose(mol.extra.get("bfactors"), np.zeros(mol.natom)) + assert_equal(mol.extra.get("chainids"), ["A"] * mol.natom) -@pytest.mark.parametrize("case", ['trajectory', 'trajectory_no_model']) +@pytest.mark.parametrize("case", ["trajectory", "trajectory_no_model"]) def test_load_many(case): - with path('iodata.test.data', f"water_{case}.pdb") as fn_pdb: + with as_file(files("iodata.test.data").joinpath(f"water_{case}.pdb")) as fn_pdb: mols = list(load_many(str(fn_pdb))) assert len(mols) == 5 for mol in mols: assert_equal(mol.atnums, [8, 1, 1]) assert mol.atcoords.shape == (3, 3) - assert mol.extra.get('chainids') is None - assert_allclose(mol.extra.get('occupancies'), np.ones(3)) - assert_allclose(mol.extra.get('bfactors'), np.zeros(3)) + assert mol.extra.get("chainids") is None + assert_allclose(mol.extra.get("occupancies"), np.ones(3)) + assert_allclose(mol.extra.get("bfactors"), np.zeros(3)) assert_allclose(mols[0].atcoords[2] / angstrom, [2.864, 0.114, 3.364]) assert_allclose(mols[2].atcoords[0] / angstrom, [-0.233, -0.790, -3.248]) assert_allclose(mols[-1].atcoords[1] / angstrom, [-2.123, -3.355, -3.354]) -@pytest.mark.parametrize("case", ['trajectory', 'trajectory_no_model']) +@pytest.mark.parametrize("case", ["trajectory", "trajectory_no_model"]) def test_load_dump_many_consistency(case, tmpdir): - with path('iodata.test.data', f"water_{case}.pdb") as fn_pdb: + with as_file(files("iodata.test.data").joinpath(f"water_{case}.pdb")) as fn_pdb: mols0 = list(load_many(str(fn_pdb))) # write pdb file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test') - dump_many(mols0, fn_tmp, fmt='pdb') - mols1 = list(load_many(fn_tmp, fmt='pdb')) + fn_tmp = os.path.join(tmpdir, "test") + dump_many(mols0, fn_tmp, fmt="pdb") + mols1 = list(load_many(fn_tmp, fmt="pdb")) assert len(mols0) == len(mols1) for mol0, mol1 in zip(mols0, mols1): assert mol0.title == mol1.title assert_equal(mol0.atnums, mol1.atnums) - assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.e-5) + assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.0e-5) def test_load_2bcw(): # test pdb with multiple chains - with path("iodata.test.data", "2bcw.pdb") as fn_pdb: + with as_file(files("iodata.test.data").joinpath("2bcw.pdb")) as fn_pdb: mol = load_one(fn_pdb) + assert ( + mol.title + == """\ +COORDINATES OF THE N-TERMINAL DOMAIN OF RIBOSOMAL PROTEIN L11,C- +TERMINAL DOMAIN OF RIBOSOMAL PROTEIN L7/L12 AND A PORTION OF THE G' +DOMAIN OF ELONGATION FACTOR G, AS FITTED INTO CRYO-EM MAP OF AN +ESCHERICHIA COLI 70S*EF-G*GDP*FUSIDIC ACID COMPLEX""" + ) + assert ( + mol.extra["compound"] + == """\ +MOL_ID: 1; +MOLECULE: 50S RIBOSOMAL PROTEIN L11; +CHAIN: A; +FRAGMENT: N-TERMINAL DOMAIN; +MOL_ID: 2; +MOLECULE: 50S RIBOSOMAL PROTEIN L7/L12; +CHAIN: B; +FRAGMENT: C-TERMINAL DOMAIN; +SYNONYM: L8; +MOL_ID: 3; +MOLECULE: ELONGATION FACTOR G; +CHAIN: C; +FRAGMENT: A PORTION OF G' DOMAIN'; +SYNONYM: EF-G""" + ) assert mol.natom == 191 assert (mol.atnums == 6).all() assert (mol.atffparams["attypes"] == ["CA"] * mol.natom).all() - assert (mol.atffparams["restypes"][:3] == ['GLN', 'ILE', 'LYS']).all() - assert (mol.atffparams["restypes"][-4:] == ['LYS', 'ILE', 'THR', 'PRO']).all() + assert (mol.atffparams["restypes"][:3] == ["GLN", "ILE", "LYS"]).all() + assert (mol.atffparams["restypes"][-4:] == ["LYS", "ILE", "THR", "PRO"]).all() assert_allclose(mol.atcoords[0, 2] / angstrom, -86.956) assert_allclose(mol.atcoords[190, 0] / angstrom, -24.547) - assert_allclose(mol.extra.get('occupancies'), np.ones(mol.natom)) + assert_allclose(mol.extra.get("occupancies"), np.ones(mol.natom)) assert (mol.extra["chainids"] == ["A"] * 65 + ["B"] * 68 + ["C"] * 58).all() -def test_load_pdb_dump_pdb(tmpdir): - # test dump pdb with single chain - with path("iodata.test.data", "2luv.pdb") as fn_pdb: - check_load_dump_consistency(tmpdir, fn_pdb) +def test_load_ch5plus_bonds(): + with as_file(files("iodata.test.data").joinpath("ch5plus.pdb")) as fn_pdb: + mol = load_one(fn_pdb) + assert_equal(mol.bonds[:, :2], [[0, 1], [0, 2], [0, 3], [0, 4], [0, 5]]) + - # test dump pdb with multiple chain - with path('iodata.test.data', "2bcw.pdb") as fn_pdb: - check_load_dump_consistency(tmpdir, fn_pdb) +def test_indomethacin_dimer(): + with ( + as_file(files("iodata.test.data").joinpath("indomethacin-dimer.pdb")) as fn_pdb, + pytest.warns(FileFormatWarning) as record, + ): + mol = load_one(fn_pdb) + assert len(record) == 82 + for item in record: + assert "Using the atom name in the PDB" in item.message.args[0] + assert mol.atnums[13] == 6 + assert mol.atnums[14] == 17 + assert mol.atnums[15] == 6 + assert mol.atnums[55] == 17 diff --git a/iodata/test/test_poscar.py b/iodata/test/test_poscar.py index 0c79496ab..13979e7e1 100644 --- a/iodata/test/test_poscar.py +++ b/iodata/test/test_poscar.py @@ -16,68 +16,69 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see <http://www.gnu.org/licenses/> # -- -# pylint: disable=unsubscriptable-object """Test iodata.formats.poscar module.""" import os import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from ..api import load_one, dump_one +from ..api import dump_one, load_one from ..utils import angstrom, volume try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_poscar_water(): - with path('iodata.test.data', 'POSCAR.water') as fn: + with as_file(files("iodata.test.data").joinpath("POSCAR.water")) as fn: mol = load_one(str(fn)) - assert mol.title == 'Water molecule in a box' + assert mol.title == "Water molecule in a box" assert_equal(mol.atnums, [8, 1, 1]) coords = np.array([0.074983 * 15, 0.903122 * 15, 0.000000]) assert_allclose(mol.atcoords[1], coords, atol=1e-7) - assert_allclose(volume(mol.cellvecs), 15 ** 3, atol=1.e-4) + assert_allclose(volume(mol.cellvecs), 15**3, atol=1.0e-4) def test_load_poscar_cubicbn_cartesian(): - with path('iodata.test.data', 'POSCAR.cubicbn_cartesian') as fn: + with as_file(files("iodata.test.data").joinpath("POSCAR.cubicbn_cartesian")) as fn: mol = load_one(str(fn)) - assert mol.title == 'Cubic BN' + assert mol.title == "Cubic BN" assert_equal(mol.atnums, [5, 7]) - assert_allclose(mol.atcoords[1], - np.array([0.25] * 3) * 3.57 * angstrom, atol=1.e-10) - assert_allclose(volume(mol.cellvecs), (3.57 * angstrom) ** 3 / 4, atol=1.e-10) + assert_allclose(mol.atcoords[1], np.array([0.25] * 3) * 3.57 * angstrom, atol=1.0e-10) + assert_allclose(volume(mol.cellvecs), (3.57 * angstrom) ** 3 / 4, atol=1.0e-10) def test_load_poscar_cubicbn_direct(): - with path('iodata.test.data', 'POSCAR.cubicbn_direct') as fn: + with as_file(files("iodata.test.data").joinpath("POSCAR.cubicbn_direct")) as fn: mol = load_one(str(fn)) - assert mol.title == 'Cubic BN' + assert mol.title == "Cubic BN" assert_equal(mol.atnums, [5, 7]) - assert_allclose(mol.atcoords[1], - np.array([0.25] * 3) * 3.57 * angstrom, atol=1.e-10) - assert_allclose(volume(mol.cellvecs), (3.57 * angstrom) ** 3 / 4, 1.e-10) + assert_allclose(mol.atcoords[1], np.array([0.25] * 3) * 3.57 * angstrom, atol=1.0e-10) + assert_allclose(volume(mol.cellvecs), (3.57 * angstrom) ** 3 / 4, 1.0e-10) def test_load_dump_consistency(tmpdir): - with path('iodata.test.data', 'water_element.xyz') as fn: + with as_file(files("iodata.test.data").joinpath("water_element.xyz")) as fn: mol0 = load_one(str(fn)) # random matrix generated from a uniform distribution on [0., 5.0) - mol0.cellvecs = np.array([[2.05278155, 0.23284023, 1.59024118], - [4.96430141, 4.73044423, 4.67590975], - [3.48374425, 0.67931228, 0.66281160]]) + mol0.cellvecs = np.array( + [ + [2.05278155, 0.23284023, 1.59024118], + [4.96430141, 4.73044423, 4.67590975], + [3.48374425, 0.67931228, 0.66281160], + ] + ) - fn_tmp = os.path.join(tmpdir, 'POSCAR') + fn_tmp = os.path.join(tmpdir, "POSCAR") dump_one(mol0, fn_tmp) mol1 = load_one(fn_tmp) assert mol0.title == mol1.title assert_equal(mol1.atnums, [8, 1, 1]) - assert_allclose(mol0.atcoords[1], mol1.atcoords[0], atol=1.e-10) - assert_allclose(mol0.atcoords[0], mol1.atcoords[1], atol=1.e-10) - assert_allclose(mol0.atcoords[2], mol1.atcoords[2], atol=1.e-10) - assert_allclose(mol0.cellvecs, mol1.cellvecs, atol=1.e-10) + assert_allclose(mol0.atcoords[1], mol1.atcoords[0], atol=1.0e-10) + assert_allclose(mol0.atcoords[0], mol1.atcoords[1], atol=1.0e-10) + assert_allclose(mol0.atcoords[2], mol1.atcoords[2], atol=1.0e-10) + assert_allclose(mol0.cellvecs, mol1.cellvecs, atol=1.0e-10) diff --git a/iodata/test/test_qchemlog.py b/iodata/test/test_qchemlog.py index 2f2775816..e7d78fd83 100644 --- a/iodata/test/test_qchemlog.py +++ b/iodata/test/test_qchemlog.py @@ -19,157 +19,450 @@ """Test iodata.formats.qchemlog module.""" import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal from ..api import load_one from ..formats.qchemlog import load_qchemlog_low from ..utils import LineIterator, angstrom, kjmol try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_qchemlog_low_h2o(): """Test load_qchemlog_low with water_hf_ccpvtz_freq_qchem.out.""" - with path('iodata.test.data', 'water_hf_ccpvtz_freq_qchem.out') as fq: + with as_file(files("iodata.test.data").joinpath("water_hf_ccpvtz_freq_qchem.out")) as fq: data = load_qchemlog_low(LineIterator(str(fq))) # check loaded data - assert data['run_type'] == 'freq' - assert data['lot'] == 'hf' - assert data['obasis_name'] == 'cc-pvtz' - assert data['unrestricted'] == 1 - assert data['symm'] == 0 - assert data['g_rot'] == 1 - assert data['alpha_elec'] == 5 - assert data['beta_elec'] == 5 - assert_allclose(data['nuclear_repulsion_energy'], 9.19775748) - assert_allclose(data['energy'], -76.0571936393) - assert data['norba'] == 58 - assert data['norbb'] == 58 - assert data['dipole_tol'] == 2.0231 - assert_allclose(data['enthalpy_dict']['trans_enthalpy'], 0.889) - assert_allclose(data['enthalpy_dict']['rot_enthalpy'], 0.889) - assert_allclose(data['enthalpy_dict']['vib_enthalpy'], 13.883) - assert_allclose(data['enthalpy_dict']['enthalpy_total'], 16.253) - assert_allclose(data['entropy_dict']['trans_entropy'], 34.608) - assert_allclose(data['entropy_dict']['rot_entropy'], 11.82) - assert_allclose(data['entropy_dict']['vib_entropy'], 0.003) - assert_allclose(data['entropy_dict']['entropy_total'], 46.432) - assert data['imaginary_freq'] == 0 - assert_allclose(data['vib_energy'], 13.882) - assert_equal(data['atnums'], np.array([8, 1, 1])) - assert_equal(data['atmasses'], [15.99491, 1.00783, 1.00783]) - atcoords = np.array([[0.00575, 0.00426, -0.00301], - [0.27588, 0.88612, 0.25191], - [0.60257, -0.23578, -0.7114]]) * angstrom - assert_equal(data['atcoords'], atcoords) - assert_equal(data['mo_a_occ'], np.array([-20.5546, -1.3458, -0.7102, -0.5776, -0.5045])) - assert_equal(data['mo_b_occ'], np.array([-20.5546, -1.3458, -0.7102, -0.5776, -0.5045])) - alpha_mo_unoccupied = np.array([0.1423, 0.2041, 0.5445, 0.6021, 0.6682, 0.7874, 0.8014, - 0.8052, 0.861, 0.9557, 1.1314, 1.197, 1.5276, 1.5667, - 2.0366, 2.052, 2.0664, 2.1712, 2.2342, 2.591, 2.9639, - 3.3568, 3.4919, 3.5814, 3.6562, 3.8012, 3.8795, 3.8849, - 3.9617, 4.0196, 4.0768, 4.1932, 4.3149, 4.39, 4.5839, - 4.6857, 4.8666, 5.1595, 5.2529, 5.5288, 6.0522, 6.5707, - 6.9264, 6.9442, 7.0027, 7.0224, 7.068, 7.1668, 7.2377, - 7.4574, 7.7953, 8.2906, 12.8843]) - assert_allclose(data['mo_a_vir'], alpha_mo_unoccupied) - beta_mo_unoccupied = np.array([0.1423, 0.2041, 0.5445, 0.6021, 0.6682, 0.7874, 0.8014, - 0.8052, 0.861, 0.9557, 1.1314, 1.197, 1.5276, 1.5667, - 2.0366, 2.052, 2.0664, 2.1712, 2.2342, 2.591, 2.9639, - 3.3568, 3.4919, 3.5814, 3.6562, 3.8012, 3.8795, 3.8849, - 3.9617, 4.0196, 4.0768, 4.1932, 4.3149, 4.39, 4.5839, - 4.6857, 4.8666, 5.1595, 5.2529, 5.5288, 6.0522, 6.5707, - 6.9264, 6.9442, 7.0027, 7.0224, 7.068, 7.1668, 7.2377, - 7.4574, 7.7953, 8.2906, 12.8843]) - assert_allclose(data['mo_b_vir'], beta_mo_unoccupied) - assert_allclose(data['mulliken_charges'], np.array([-0.482641, 0.241321, 0.241321])) - assert_allclose(data['dipole'], np.array([1.4989, 1.1097, -0.784])) - assert_allclose(data['quadrupole'], [-6.1922, 0.2058, -5.0469, -0.9308, 1.1096, -5.762]) - assert_allclose(data['polarizability_tensor'], [[-6.1256608, -0.1911917, 0.8593603], - [-0.1911917, -7.180854, -1.0224452], - [0.8593603, -1.0224452, -6.52088]]) - hessian = np.array([[3.162861e-01, 8.366060e-02, -2.326701e-01, -8.253820e-02, - -1.226155e-01, -2.676000e-03, -2.337479e-01, 3.895480e-02, 2.353461e-01], - [8.366060e-02, 5.460341e-01, 2.252114e-01, -1.647100e-01, - -4.652302e-01, -1.071603e-01, 8.104940e-02, -8.080390e-02, -1.180510e-01], - [-2.326701e-01, 2.252114e-01, 3.738573e-01, -2.713570e-02, - -1.472865e-01, -7.031900e-02, 2.598057e-01, -7.792490e-02, -3.035382e-01], - [-8.253820e-02, -1.647100e-01, -2.713570e-02, 7.455040e-02, - 1.315365e-01, 1.474740e-02, 7.987800e-03, 3.317350e-02, 1.238830e-02], - [-1.226155e-01, -4.652302e-01, -1.472865e-01, 1.315365e-01, - 4.787640e-01, 1.470895e-01, -8.921000e-03, -1.353380e-02, 1.970000e-04], - [-2.676000e-03, -1.071603e-01, -7.031900e-02, 1.474740e-02, - 1.470895e-01, 8.125140e-02, -1.207140e-02, -3.992910e-02, -1.093230e-02], - [-2.337479e-01, 8.104940e-02, 2.598057e-01, 7.987800e-03, - -8.921000e-03, -1.207140e-02, 2.257601e-01, -7.212840e-02, -2.477343e-01], - [3.895480e-02, -8.080390e-02, -7.792490e-02, 3.317350e-02, - -1.353380e-02, -3.992910e-02, -7.212840e-02, 9.433770e-02, 1.178541e-01], - [2.353461e-01, -1.180510e-01, -3.035382e-01, 1.238830e-02, - 1.970000e-04, -1.093230e-02, -2.477343e-01, 1.178541e-01, 3.144706e-01]]) - assert_allclose(data['athessian'], hessian) + assert data["run_type"] == "freq" + assert data["lot"] == "hf" + assert data["obasis_name"] == "cc-pvtz" + assert data["unrestricted"] == 1 + assert data["symm"] == 0 + assert data["g_rot"] == 1 + assert data["alpha_elec"] == 5 + assert data["beta_elec"] == 5 + assert_allclose(data["nuclear_repulsion_energy"], 9.19775748) + assert_allclose(data["energy"], -76.0571936393) + assert data["norba"] == 58 + assert data["norbb"] == 58 + assert data["dipole_tol"] == 2.0231 + assert_allclose(data["enthalpy_dict"]["trans_enthalpy"], 0.889) + assert_allclose(data["enthalpy_dict"]["rot_enthalpy"], 0.889) + assert_allclose(data["enthalpy_dict"]["vib_enthalpy"], 13.883) + assert_allclose(data["enthalpy_dict"]["enthalpy_total"], 16.253) + assert_allclose(data["entropy_dict"]["trans_entropy"], 34.608) + assert_allclose(data["entropy_dict"]["rot_entropy"], 11.82) + assert_allclose(data["entropy_dict"]["vib_entropy"], 0.003) + assert_allclose(data["entropy_dict"]["entropy_total"], 46.432) + assert data["imaginary_freq"] == 0 + assert_allclose(data["vib_energy"], 13.882) + assert_equal(data["atnums"], np.array([8, 1, 1])) + assert_equal(data["atmasses"], [15.99491, 1.00783, 1.00783]) + atcoords = ( + np.array( + [ + [0.00575, 0.00426, -0.00301], + [0.27588, 0.88612, 0.25191], + [0.60257, -0.23578, -0.7114], + ] + ) + * angstrom + ) + assert_equal(data["atcoords"], atcoords) + assert_equal(data["mo_a_occ"], np.array([-20.5546, -1.3458, -0.7102, -0.5776, -0.5045])) + assert_equal(data["mo_b_occ"], np.array([-20.5546, -1.3458, -0.7102, -0.5776, -0.5045])) + alpha_mo_unoccupied = np.array( + [ + 0.1423, + 0.2041, + 0.5445, + 0.6021, + 0.6682, + 0.7874, + 0.8014, + 0.8052, + 0.861, + 0.9557, + 1.1314, + 1.197, + 1.5276, + 1.5667, + 2.0366, + 2.052, + 2.0664, + 2.1712, + 2.2342, + 2.591, + 2.9639, + 3.3568, + 3.4919, + 3.5814, + 3.6562, + 3.8012, + 3.8795, + 3.8849, + 3.9617, + 4.0196, + 4.0768, + 4.1932, + 4.3149, + 4.39, + 4.5839, + 4.6857, + 4.8666, + 5.1595, + 5.2529, + 5.5288, + 6.0522, + 6.5707, + 6.9264, + 6.9442, + 7.0027, + 7.0224, + 7.068, + 7.1668, + 7.2377, + 7.4574, + 7.7953, + 8.2906, + 12.8843, + ] + ) + assert_allclose(data["mo_a_vir"], alpha_mo_unoccupied) + beta_mo_unoccupied = np.array( + [ + 0.1423, + 0.2041, + 0.5445, + 0.6021, + 0.6682, + 0.7874, + 0.8014, + 0.8052, + 0.861, + 0.9557, + 1.1314, + 1.197, + 1.5276, + 1.5667, + 2.0366, + 2.052, + 2.0664, + 2.1712, + 2.2342, + 2.591, + 2.9639, + 3.3568, + 3.4919, + 3.5814, + 3.6562, + 3.8012, + 3.8795, + 3.8849, + 3.9617, + 4.0196, + 4.0768, + 4.1932, + 4.3149, + 4.39, + 4.5839, + 4.6857, + 4.8666, + 5.1595, + 5.2529, + 5.5288, + 6.0522, + 6.5707, + 6.9264, + 6.9442, + 7.0027, + 7.0224, + 7.068, + 7.1668, + 7.2377, + 7.4574, + 7.7953, + 8.2906, + 12.8843, + ] + ) + assert_allclose(data["mo_b_vir"], beta_mo_unoccupied) + assert_allclose(data["mulliken_charges"], np.array([-0.482641, 0.241321, 0.241321])) + assert_allclose(data["dipole"], np.array([1.4989, 1.1097, -0.784])) + assert_allclose(data["quadrupole"], [-6.1922, 0.2058, -5.0469, -0.9308, 1.1096, -5.762]) + assert_allclose( + data["polarizability_tensor"], + [ + [-6.1256608, -0.1911917, 0.8593603], + [-0.1911917, -7.180854, -1.0224452], + [0.8593603, -1.0224452, -6.52088], + ], + ) + hessian = np.array( + [ + [ + 3.162861e-01, + 8.366060e-02, + -2.326701e-01, + -8.253820e-02, + -1.226155e-01, + -2.676000e-03, + -2.337479e-01, + 3.895480e-02, + 2.353461e-01, + ], + [ + 8.366060e-02, + 5.460341e-01, + 2.252114e-01, + -1.647100e-01, + -4.652302e-01, + -1.071603e-01, + 8.104940e-02, + -8.080390e-02, + -1.180510e-01, + ], + [ + -2.326701e-01, + 2.252114e-01, + 3.738573e-01, + -2.713570e-02, + -1.472865e-01, + -7.031900e-02, + 2.598057e-01, + -7.792490e-02, + -3.035382e-01, + ], + [ + -8.253820e-02, + -1.647100e-01, + -2.713570e-02, + 7.455040e-02, + 1.315365e-01, + 1.474740e-02, + 7.987800e-03, + 3.317350e-02, + 1.238830e-02, + ], + [ + -1.226155e-01, + -4.652302e-01, + -1.472865e-01, + 1.315365e-01, + 4.787640e-01, + 1.470895e-01, + -8.921000e-03, + -1.353380e-02, + 1.970000e-04, + ], + [ + -2.676000e-03, + -1.071603e-01, + -7.031900e-02, + 1.474740e-02, + 1.470895e-01, + 8.125140e-02, + -1.207140e-02, + -3.992910e-02, + -1.093230e-02, + ], + [ + -2.337479e-01, + 8.104940e-02, + 2.598057e-01, + 7.987800e-03, + -8.921000e-03, + -1.207140e-02, + 2.257601e-01, + -7.212840e-02, + -2.477343e-01, + ], + [ + 3.895480e-02, + -8.080390e-02, + -7.792490e-02, + 3.317350e-02, + -1.353380e-02, + -3.992910e-02, + -7.212840e-02, + 9.433770e-02, + 1.178541e-01, + ], + [ + 2.353461e-01, + -1.180510e-01, + -3.035382e-01, + 1.238830e-02, + 1.970000e-04, + -1.093230e-02, + -2.477343e-01, + 1.178541e-01, + 3.144706e-01, + ], + ] + ) + assert_allclose(data["athessian"], hessian) def test_load_one_qchemlog_freq(): - with path('iodata.test.data', 'water_hf_ccpvtz_freq_qchem.out') as fn_qchemlog: - mol = load_one(str(fn_qchemlog), fmt='qchemlog') + source = files("iodata.test.data").joinpath("water_hf_ccpvtz_freq_qchem.out") + with as_file(source) as fn_qchemlog: + mol = load_one(str(fn_qchemlog), fmt="qchemlog") assert_allclose(mol.energy, -76.0571936393) assert mol.g_rot == 1 assert mol.nelec == 10 - assert mol.run_type == 'freq' - assert mol.lot == 'hf' - assert mol.obasis_name == 'cc-pvtz' - assert_allclose(mol.atcharges['mulliken'], np.array([-0.482641, 0.241321, 0.241321])) - assert_equal(mol.moments[(1, 'c')], np.array([1.4989, 1.1097, -0.784])) - assert_equal(mol.moments[(2, 'c')], - np.array([-6.1922, 0.2058, -0.9308, -5.0469, 1.1096, -5.762])) - hessian = np.array([[3.162861e-01, 8.366060e-02, -2.326701e-01, -8.253820e-02, - -1.226155e-01, -2.676000e-03, -2.337479e-01, 3.895480e-02, 2.353461e-01], - [8.366060e-02, 5.460341e-01, 2.252114e-01, -1.647100e-01, - -4.652302e-01, -1.071603e-01, 8.104940e-02, -8.080390e-02, -1.180510e-01], - [-2.326701e-01, 2.252114e-01, 3.738573e-01, -2.713570e-02, - -1.472865e-01, -7.031900e-02, 2.598057e-01, -7.792490e-02, -3.035382e-01], - [-8.253820e-02, -1.647100e-01, -2.713570e-02, 7.455040e-02, - 1.315365e-01, 1.474740e-02, 7.987800e-03, 3.317350e-02, 1.238830e-02], - [-1.226155e-01, -4.652302e-01, -1.472865e-01, 1.315365e-01, - 4.787640e-01, 1.470895e-01, -8.921000e-03, -1.353380e-02, 1.970000e-04], - [-2.676000e-03, -1.071603e-01, -7.031900e-02, 1.474740e-02, - 1.470895e-01, 8.125140e-02, -1.207140e-02, -3.992910e-02, -1.093230e-02], - [-2.337479e-01, 8.104940e-02, 2.598057e-01, 7.987800e-03, - -8.921000e-03, -1.207140e-02, 2.257601e-01, -7.212840e-02, -2.477343e-01], - [3.895480e-02, -8.080390e-02, -7.792490e-02, 3.317350e-02, - -1.353380e-02, -3.992910e-02, -7.212840e-02, 9.433770e-02, 1.178541e-01], - [2.353461e-01, -1.180510e-01, -3.035382e-01, 1.238830e-02, - 1.970000e-04, -1.093230e-02, -2.477343e-01, 1.178541e-01, 3.144706e-01]]) + assert mol.run_type == "freq" + assert mol.lot == "hf" + assert mol.obasis_name == "cc-pvtz" + assert_allclose(mol.atcharges["mulliken"], np.array([-0.482641, 0.241321, 0.241321])) + assert_equal(mol.moments[(1, "c")], np.array([1.4989, 1.1097, -0.784])) + assert_equal( + mol.moments[(2, "c")], np.array([-6.1922, 0.2058, -0.9308, -5.0469, 1.1096, -5.762]) + ) + hessian = np.array( + [ + [ + 3.162861e-01, + 8.366060e-02, + -2.326701e-01, + -8.253820e-02, + -1.226155e-01, + -2.676000e-03, + -2.337479e-01, + 3.895480e-02, + 2.353461e-01, + ], + [ + 8.366060e-02, + 5.460341e-01, + 2.252114e-01, + -1.647100e-01, + -4.652302e-01, + -1.071603e-01, + 8.104940e-02, + -8.080390e-02, + -1.180510e-01, + ], + [ + -2.326701e-01, + 2.252114e-01, + 3.738573e-01, + -2.713570e-02, + -1.472865e-01, + -7.031900e-02, + 2.598057e-01, + -7.792490e-02, + -3.035382e-01, + ], + [ + -8.253820e-02, + -1.647100e-01, + -2.713570e-02, + 7.455040e-02, + 1.315365e-01, + 1.474740e-02, + 7.987800e-03, + 3.317350e-02, + 1.238830e-02, + ], + [ + -1.226155e-01, + -4.652302e-01, + -1.472865e-01, + 1.315365e-01, + 4.787640e-01, + 1.470895e-01, + -8.921000e-03, + -1.353380e-02, + 1.970000e-04, + ], + [ + -2.676000e-03, + -1.071603e-01, + -7.031900e-02, + 1.474740e-02, + 1.470895e-01, + 8.125140e-02, + -1.207140e-02, + -3.992910e-02, + -1.093230e-02, + ], + [ + -2.337479e-01, + 8.104940e-02, + 2.598057e-01, + 7.987800e-03, + -8.921000e-03, + -1.207140e-02, + 2.257601e-01, + -7.212840e-02, + -2.477343e-01, + ], + [ + 3.895480e-02, + -8.080390e-02, + -7.792490e-02, + 3.317350e-02, + -1.353380e-02, + -3.992910e-02, + -7.212840e-02, + 9.433770e-02, + 1.178541e-01, + ], + [ + 2.353461e-01, + -1.180510e-01, + -3.035382e-01, + 1.238830e-02, + 1.970000e-04, + -1.093230e-02, + -2.477343e-01, + 1.178541e-01, + 3.144706e-01, + ], + ] + ) assert_equal(mol.athessian, hessian) - assert mol.extra['nuclear_repulsion_energy'] == 9.19775748 - assert mol.extra['imaginary_freq'] == 0 + assert mol.extra["nuclear_repulsion_energy"] == 9.19775748 + assert mol.extra["imaginary_freq"] == 0 # unit conversion for entropy terms, used atomic units + Kalvin - assert_allclose(mol.extra['entropy_dict']['trans_entropy'], 34.608 * 1.593601437640628e-06) - assert_allclose(mol.extra['entropy_dict']['rot_entropy'], 11.82 * 1.593601437640628e-06) - assert_allclose(mol.extra['entropy_dict']['vib_entropy'], 0.003 * 1.593601437640628e-06) - assert_allclose(mol.extra['entropy_dict']['entropy_total'], 46.432 * 1.593601437640628e-06) - assert_allclose(mol.extra['vib_energy'], 0.022122375167392933) - assert_allclose(mol.extra['enthalpy_dict']['trans_enthalpy'], 0.0014167116787071256) - assert_allclose(mol.extra['enthalpy_dict']['rot_enthalpy'], 0.0014167116787071256) - assert_allclose(mol.extra['enthalpy_dict']['vib_enthalpy'], 0.022123968768831298) - assert_allclose(mol.extra['enthalpy_dict']['enthalpy_total'], 0.025900804177758054) - polarizability_tensor = np.array([[-6.1256608, -0.1911917, 0.8593603], - [-0.1911917, -7.180854, -1.0224452], - [0.8593603, -1.0224452, -6.52088]]) - assert_equal(mol.extra['polarizability_tensor'], polarizability_tensor) - atcoords = np.array([[0.00575, 0.00426, -0.00301], - [0.27588, 0.88612, 0.25191], - [0.60257, -0.23578, -0.7114]]) * angstrom + assert_allclose(mol.extra["entropy_dict"]["trans_entropy"], 34.608 * 1.593601437640628e-06) + assert_allclose(mol.extra["entropy_dict"]["rot_entropy"], 11.82 * 1.593601437640628e-06) + assert_allclose(mol.extra["entropy_dict"]["vib_entropy"], 0.003 * 1.593601437640628e-06) + assert_allclose(mol.extra["entropy_dict"]["entropy_total"], 46.432 * 1.593601437640628e-06) + assert_allclose(mol.extra["vib_energy"], 0.022122375167392933) + assert_allclose(mol.extra["enthalpy_dict"]["trans_enthalpy"], 0.0014167116787071256) + assert_allclose(mol.extra["enthalpy_dict"]["rot_enthalpy"], 0.0014167116787071256) + assert_allclose(mol.extra["enthalpy_dict"]["vib_enthalpy"], 0.022123968768831298) + assert_allclose(mol.extra["enthalpy_dict"]["enthalpy_total"], 0.025900804177758054) + polarizability_tensor = np.array( + [ + [-6.1256608, -0.1911917, 0.8593603], + [-0.1911917, -7.180854, -1.0224452], + [0.8593603, -1.0224452, -6.52088], + ] + ) + assert_equal(mol.extra["polarizability_tensor"], polarizability_tensor) + atcoords = ( + np.array( + [ + [0.00575, 0.00426, -0.00301], + [0.27588, 0.88612, 0.25191], + [0.60257, -0.23578, -0.7114], + ] + ) + * angstrom + ) assert_equal(mol.atcoords, atcoords) assert_equal(mol.atmasses, np.array([15.99491, 1.00783, 1.00783])) assert_equal(mol.atnums, np.array([8, 1, 1])) # molecule orbital related # check number of orbitals and occupation numbers - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert mol.mo.norba == 58 assert mol.mo.norbb == 58 assert mol.mo.norb == 116 @@ -181,13 +474,63 @@ def test_load_one_qchemlog_freq(): # beta occupied orbital energies assert_allclose(mol.mo.energies[58:63], occupied) # alpha virtual orbital energies - virtual = np.array([0.1423, 0.2041, 0.5445, 0.6021, 0.6682, 0.7874, 0.8014, 0.8052, - 0.8610, 0.9557, 1.1314, 1.1970, 1.5276, 1.5667, 2.0366, 2.0520, - 2.0664, 2.1712, 2.2342, 2.5910, 2.9639, 3.3568, 3.4919, 3.5814, - 3.6562, 3.8012, 3.8795, 3.8849, 3.9617, 4.0196, 4.0768, 4.1932, - 4.3149, 4.3900, 4.5839, 4.6857, 4.8666, 5.1595, 5.2529, 5.5288, - 6.0522, 6.5707, 6.9264, 6.9442, 7.0027, 7.0224, 7.0680, 7.1668, - 7.2377, 7.4574, 7.7953, 8.2906, 12.8843]) + virtual = np.array( + [ + 0.1423, + 0.2041, + 0.5445, + 0.6021, + 0.6682, + 0.7874, + 0.8014, + 0.8052, + 0.8610, + 0.9557, + 1.1314, + 1.1970, + 1.5276, + 1.5667, + 2.0366, + 2.0520, + 2.0664, + 2.1712, + 2.2342, + 2.5910, + 2.9639, + 3.3568, + 3.4919, + 3.5814, + 3.6562, + 3.8012, + 3.8795, + 3.8849, + 3.9617, + 4.0196, + 4.0768, + 4.1932, + 4.3149, + 4.3900, + 4.5839, + 4.6857, + 4.8666, + 5.1595, + 5.2529, + 5.5288, + 6.0522, + 6.5707, + 6.9264, + 6.9442, + 7.0027, + 7.0224, + 7.0680, + 7.1668, + 7.2377, + 7.4574, + 7.7953, + 8.2906, + 12.8843, + ] + ) assert_allclose(mol.mo.energies[5:58], virtual) # beta virtual orbital energies assert_allclose(mol.mo.energies[63:], virtual) @@ -195,177 +538,416 @@ def test_load_one_qchemlog_freq(): def test_load_qchemlog_low_qchemlog_h2o_dimer_eda2(): """Test load_qchemlog_low with h2o_dimer_eda_qchem5.3.out.""" - # pylint: disable=too-many-statements - with path('iodata.test.data', 'h2o_dimer_eda_qchem5.3.out') as fq: + with as_file(files("iodata.test.data").joinpath("h2o_dimer_eda_qchem5.3.out")) as fq: data = load_qchemlog_low(LineIterator(str(fq))) # check loaded data - assert data['run_type'] == 'eda' - assert data['lot'] == 'wb97x-v' - assert data['obasis_name'] == 'def2-tzvpd' - assert not data['unrestricted'] - assert not data['symm'] - assert data['alpha_elec'] == 10 - assert data['beta_elec'] == 10 - assert_allclose(data['nuclear_repulsion_energy'], 36.66284801) - assert_allclose(data['energy'], -152.8772543727) - assert data['norba'] == 116 - assert_allclose(data['dipole_tol'], 2.5701) - assert_equal(data['atnums'], np.array([8, 1, 1, 8, 1, 1])) - atcoords = np.array([[-1.5510070000, -0.1145200000, 0.0000000000], - [-1.9342590000, 0.7625030000, 0.0000000000], - [-0.5996770000, 0.0407120000, 0.0000000000], - [1.3506250000, 0.1114690000, 0.0000000000], - [1.6803980000, -0.3737410000, -0.7585610000], - [1.6803980000, -0.3737410000, 0.7585610000]]) * angstrom - assert_allclose(data['atcoords'], atcoords) - assert_allclose(data['mo_a_occ'], np.array([-19.2455, -19.1897, -1.1734, -1.1173, -0.6729, - -0.6242, -0.5373, -0.4825, -0.4530, -0.4045])) + assert data["run_type"] == "eda" + assert data["lot"] == "wb97x-v" + assert data["obasis_name"] == "def2-tzvpd" + assert not data["unrestricted"] + assert not data["symm"] + assert data["alpha_elec"] == 10 + assert data["beta_elec"] == 10 + assert_allclose(data["nuclear_repulsion_energy"], 36.66284801) + assert_allclose(data["energy"], -152.8772543727) + assert data["norba"] == 116 + assert_allclose(data["dipole_tol"], 2.5701) + assert_equal(data["atnums"], np.array([8, 1, 1, 8, 1, 1])) + atcoords = ( + np.array( + [ + [-1.5510070000, -0.1145200000, 0.0000000000], + [-1.9342590000, 0.7625030000, 0.0000000000], + [-0.5996770000, 0.0407120000, 0.0000000000], + [1.3506250000, 0.1114690000, 0.0000000000], + [1.6803980000, -0.3737410000, -0.7585610000], + [1.6803980000, -0.3737410000, 0.7585610000], + ] + ) + * angstrom + ) + assert_allclose(data["atcoords"], atcoords) + assert_allclose( + data["mo_a_occ"], + np.array( + [ + -19.2455, + -19.1897, + -1.1734, + -1.1173, + -0.6729, + -0.6242, + -0.5373, + -0.4825, + -0.4530, + -0.4045, + ] + ), + ) - alpha_mo_unoccupied = np.array([0.0485, 0.0863, 0.0927, 0.1035, 0.1344, 0.1474, 0.1539, 0.1880, - 0.1982, 0.2280, 0.2507, 0.2532, 0.2732, 0.2865, 0.2992, 0.3216, - 0.3260, 0.3454, 0.3542, 0.3850, 0.3991, 0.4016, 0.4155, 0.4831, - 0.5016, 0.5133, 0.5502, 0.5505, 0.5745, 0.5992, 0.6275, 0.6454, - 0.6664, 0.6869, 0.7423, 0.7874, 0.8039, 0.8204, 0.8457, 0.9021, - 0.9149, 0.9749, 1.0168, 1.0490, 1.1274, 1.2009, 1.6233, 1.6642, - 1.6723, 1.6877, 1.7314, 1.7347, 1.8246, 1.8635, 1.8877, 1.9254, - 2.0091, 2.1339, 2.2139, 2.2489, 2.2799, 2.3420, 2.3777, 2.5255, - 2.6135, 2.6373, 2.6727, 2.7228, 2.8765, 2.8841, 2.9076, 2.9624, - 3.0377, 3.0978, 3.2509, 3.3613, 3.8767, 3.9603, 4.0824, 4.1424, - 5.1826, 5.2283, 5.3319, 5.3817, 5.4919, 5.5386, 5.5584, 5.5648, - 5.6049, 5.6226, 6.1591, 6.2079, 6.3862, 6.4446, 6.5805, 6.5926, - 6.6092, 6.6315, 6.6557, 6.6703, 7.0400, 7.1334, 7.1456, 7.2547, - 43.7457, 43.9166]) - assert_allclose(data['mo_a_vir'], alpha_mo_unoccupied) - assert_allclose(data['mulliken_charges'], np.array([-0.610250, 0.304021, 0.337060, - -0.663525, 0.316347, 0.316347])) - assert_allclose(data['dipole'], np.array([2.5689, 0.0770, 0.0000])) - assert_allclose(data['quadrupole'], [-12.0581, -6.2544, -12.8954, -0.0000, -0.0000, -12.2310]) + alpha_mo_unoccupied = np.array( + [ + 0.0485, + 0.0863, + 0.0927, + 0.1035, + 0.1344, + 0.1474, + 0.1539, + 0.1880, + 0.1982, + 0.2280, + 0.2507, + 0.2532, + 0.2732, + 0.2865, + 0.2992, + 0.3216, + 0.3260, + 0.3454, + 0.3542, + 0.3850, + 0.3991, + 0.4016, + 0.4155, + 0.4831, + 0.5016, + 0.5133, + 0.5502, + 0.5505, + 0.5745, + 0.5992, + 0.6275, + 0.6454, + 0.6664, + 0.6869, + 0.7423, + 0.7874, + 0.8039, + 0.8204, + 0.8457, + 0.9021, + 0.9149, + 0.9749, + 1.0168, + 1.0490, + 1.1274, + 1.2009, + 1.6233, + 1.6642, + 1.6723, + 1.6877, + 1.7314, + 1.7347, + 1.8246, + 1.8635, + 1.8877, + 1.9254, + 2.0091, + 2.1339, + 2.2139, + 2.2489, + 2.2799, + 2.3420, + 2.3777, + 2.5255, + 2.6135, + 2.6373, + 2.6727, + 2.7228, + 2.8765, + 2.8841, + 2.9076, + 2.9624, + 3.0377, + 3.0978, + 3.2509, + 3.3613, + 3.8767, + 3.9603, + 4.0824, + 4.1424, + 5.1826, + 5.2283, + 5.3319, + 5.3817, + 5.4919, + 5.5386, + 5.5584, + 5.5648, + 5.6049, + 5.6226, + 6.1591, + 6.2079, + 6.3862, + 6.4446, + 6.5805, + 6.5926, + 6.6092, + 6.6315, + 6.6557, + 6.6703, + 7.0400, + 7.1334, + 7.1456, + 7.2547, + 43.7457, + 43.9166, + ] + ) + assert_allclose(data["mo_a_vir"], alpha_mo_unoccupied) + assert_allclose( + data["mulliken_charges"], + np.array([-0.610250, 0.304021, 0.337060, -0.663525, 0.316347, 0.316347]), + ) + assert_allclose(data["dipole"], np.array([2.5689, 0.0770, 0.0000])) + assert_allclose(data["quadrupole"], [-12.0581, -6.2544, -12.8954, -0.0000, -0.0000, -12.2310]) # check eda2 info - assert_allclose(data['eda2']['e_elec'], -65.9887) - assert_allclose(data['eda2']['e_kep_pauli'], 78.5700) - assert_allclose(data['eda2']['e_disp_free_pauli'], -14.2495) - assert_allclose(data['eda2']['e_disp'], -7.7384) - assert_allclose(data['eda2']['e_cls_elec'], -35.1257) - assert_allclose(data['eda2']['e_cls_pauli'], 25.7192) - assert_allclose(data['eda2']['e_mod_pauli'], 33.4576) - assert_allclose(data['eda2']['preparation'], 0.0000) - assert_allclose(data['eda2']['frozen'], -1.6681) - assert_allclose(data['eda2']['pauli'], 64.3205) - assert_allclose(data['eda2']['dispersion'], -7.7384) - assert_allclose(data['eda2']['polarization'], -4.6371) - assert_allclose(data['eda2']['charge transfer'], -7.0689) - assert_allclose(data['eda2']['total'], -21.1126) + assert_allclose(data["eda2"]["e_elec"], -65.9887) + assert_allclose(data["eda2"]["e_kep_pauli"], 78.5700) + assert_allclose(data["eda2"]["e_disp_free_pauli"], -14.2495) + assert_allclose(data["eda2"]["e_disp"], -7.7384) + assert_allclose(data["eda2"]["e_cls_elec"], -35.1257) + assert_allclose(data["eda2"]["e_cls_pauli"], 25.7192) + assert_allclose(data["eda2"]["e_mod_pauli"], 33.4576) + assert_allclose(data["eda2"]["preparation"], 0.0000) + assert_allclose(data["eda2"]["frozen"], -1.6681) + assert_allclose(data["eda2"]["pauli"], 64.3205) + assert_allclose(data["eda2"]["dispersion"], -7.7384) + assert_allclose(data["eda2"]["polarization"], -4.6371) + assert_allclose(data["eda2"]["charge transfer"], -7.0689) + assert_allclose(data["eda2"]["total"], -21.1126) # check fragments coords1 = [[-1.551007, -0.11452, 0.0], [-1.934259, 0.762503, 0.0], [-0.599677, 0.040712, 0.0]] - coords2 = [[1.350625, 0.111469, 0.0], [1.680398, -0.373741, -0.758561], - [1.680398, -0.373741, 0.758561]] - assert_equal(len(data['frags']), 2) + coords2 = [ + [1.350625, 0.111469, 0.0], + [1.680398, -0.373741, -0.758561], + [1.680398, -0.373741, 0.758561], + ] + assert_equal(len(data["frags"]), 2) - assert_equal(data['frags'][0]['atnums'], [8, 1, 1]) - assert_equal(data['frags'][0]['alpha_elec'], 5) - assert_equal(data['frags'][0]['beta_elec'], 5) - assert_equal(data['frags'][0]['nbasis'], 58) - assert_allclose(data['frags'][0]['atcoords'], np.array(coords1) * angstrom) - assert_allclose(data['frags'][0]['nuclear_repulsion_energy'], 9.16383018) - assert_allclose(data['frags'][0]['energy'], -76.4345994141) + assert_equal(data["frags"][0]["atnums"], [8, 1, 1]) + assert_equal(data["frags"][0]["alpha_elec"], 5) + assert_equal(data["frags"][0]["beta_elec"], 5) + assert_equal(data["frags"][0]["nbasis"], 58) + assert_allclose(data["frags"][0]["atcoords"], np.array(coords1) * angstrom) + assert_allclose(data["frags"][0]["nuclear_repulsion_energy"], 9.16383018) + assert_allclose(data["frags"][0]["energy"], -76.4345994141) - assert_equal(data['frags'][1]['atnums'], [8, 1, 1]) - assert_equal(data['frags'][1]['alpha_elec'], 5) - assert_equal(data['frags'][1]['beta_elec'], 5) - assert_equal(data['frags'][1]['nbasis'], 58) - assert_allclose(data['frags'][1]['atcoords'], np.array(coords2) * angstrom) - assert_allclose(data['frags'][1]['nuclear_repulsion_energy'], 9.17803894) - assert_allclose(data['frags'][1]['energy'], -76.4346136883) + assert_equal(data["frags"][1]["atnums"], [8, 1, 1]) + assert_equal(data["frags"][1]["alpha_elec"], 5) + assert_equal(data["frags"][1]["beta_elec"], 5) + assert_equal(data["frags"][1]["nbasis"], 58) + assert_allclose(data["frags"][1]["atcoords"], np.array(coords2) * angstrom) + assert_allclose(data["frags"][1]["nuclear_repulsion_energy"], 9.17803894) + assert_allclose(data["frags"][1]["energy"], -76.4346136883) def test_load_one_h2o_dimer_eda2(): """Test load_one with h2o_dimer_eda_qchem5.3.out.""" - # pylint: disable=too-many-statements - with path('iodata.test.data', 'h2o_dimer_eda_qchem5.3.out') as fn_qchemlog: - mol = load_one(str(fn_qchemlog), fmt='qchemlog') + with as_file(files("iodata.test.data").joinpath("h2o_dimer_eda_qchem5.3.out")) as fn_qchemlog: + mol = load_one(str(fn_qchemlog), fmt="qchemlog") # check loaded data - assert mol.run_type == 'eda' - assert mol.lot == 'wb97x-v' - assert mol.obasis_name == 'def2-tzvpd' - assert mol.mo.kind == 'restricted' + assert mol.run_type == "eda" + assert mol.lot == "wb97x-v" + assert mol.obasis_name == "def2-tzvpd" + assert mol.mo.kind == "restricted" # assert not data['symm'] # # assert data['g_rot'] == 1 # assert data['alpha_elec'] == 10 # assert data['beta_elec'] == 10 - assert_allclose(mol.extra['nuclear_repulsion_energy'], 36.66284801) + assert_allclose(mol.extra["nuclear_repulsion_energy"], 36.66284801) assert_allclose(mol.energy, -152.8772543727) assert mol.mo.norba == 116 assert mol.mo.norbb == 116 assert_equal(mol.atnums, np.array([8, 1, 1, 8, 1, 1])) # assert_equal(data['atmasses'], [15.99491, 1.00783, 1.00783]) - atcoords = np.array([[-1.5510070000, -0.1145200000, 0.0000000000], - [-1.9342590000, 0.7625030000, 0.0000000000], - [-0.5996770000, 0.0407120000, 0.0000000000], - [1.3506250000, 0.1114690000, 0.0000000000], - [1.6803980000, -0.3737410000, -0.7585610000], - [1.6803980000, -0.3737410000, 0.7585610000]]) * angstrom + atcoords = ( + np.array( + [ + [-1.5510070000, -0.1145200000, 0.0000000000], + [-1.9342590000, 0.7625030000, 0.0000000000], + [-0.5996770000, 0.0407120000, 0.0000000000], + [1.3506250000, 0.1114690000, 0.0000000000], + [1.6803980000, -0.3737410000, -0.7585610000], + [1.6803980000, -0.3737410000, 0.7585610000], + ] + ) + * angstrom + ) assert_equal(mol.atcoords, atcoords) # check MO energies - mo_energies_a = [-19.2455, -19.1897, -1.1734, -1.1173, -0.6729, -0.6242, -0.5373, -0.4825, - -0.4530, -0.4045, 0.0485, 0.0863, 0.0927, 0.1035, 0.1344, 0.1474, 0.1539, - 0.1880, 0.1982, 0.2280, 0.2507, 0.2532, 0.2732, 0.2865, 0.2992, 0.3216, - 0.3260, 0.3454, 0.3542, 0.3850, 0.3991, 0.4016, 0.4155, 0.4831, 0.5016, - 0.5133, 0.5502, 0.5505, 0.5745, 0.5992, 0.6275, 0.6454, 0.6664, 0.6869, - 0.7423, 0.7874, 0.8039, 0.8204, 0.8457, 0.9021, 0.9149, 0.9749, 1.0168, - 1.0490, 1.1274, 1.2009, 1.6233, 1.6642, 1.6723, 1.6877, 1.7314, 1.7347, - 1.8246, 1.8635, 1.8877, 1.9254, 2.0091, 2.1339, 2.2139, 2.2489, 2.2799, - 2.3420, 2.3777, 2.5255, 2.6135, 2.6373, 2.6727, 2.7228, 2.8765, 2.8841, - 2.9076, 2.9624, 3.0377, 3.0978, 3.2509, 3.3613, 3.8767, 3.9603, 4.0824, - 4.1424, 5.1826, 5.2283, 5.3319, 5.3817, 5.4919, 5.5386, 5.5584, 5.5648, - 5.6049, 5.6226, 6.1591, 6.2079, 6.3862, 6.4446, 6.5805, 6.5926, 6.6092, - 6.6315, 6.6557, 6.6703, 7.0400, 7.1334, 7.1456, 7.2547, 43.7457, 43.9166] + mo_energies_a = [ + -19.2455, + -19.1897, + -1.1734, + -1.1173, + -0.6729, + -0.6242, + -0.5373, + -0.4825, + -0.4530, + -0.4045, + 0.0485, + 0.0863, + 0.0927, + 0.1035, + 0.1344, + 0.1474, + 0.1539, + 0.1880, + 0.1982, + 0.2280, + 0.2507, + 0.2532, + 0.2732, + 0.2865, + 0.2992, + 0.3216, + 0.3260, + 0.3454, + 0.3542, + 0.3850, + 0.3991, + 0.4016, + 0.4155, + 0.4831, + 0.5016, + 0.5133, + 0.5502, + 0.5505, + 0.5745, + 0.5992, + 0.6275, + 0.6454, + 0.6664, + 0.6869, + 0.7423, + 0.7874, + 0.8039, + 0.8204, + 0.8457, + 0.9021, + 0.9149, + 0.9749, + 1.0168, + 1.0490, + 1.1274, + 1.2009, + 1.6233, + 1.6642, + 1.6723, + 1.6877, + 1.7314, + 1.7347, + 1.8246, + 1.8635, + 1.8877, + 1.9254, + 2.0091, + 2.1339, + 2.2139, + 2.2489, + 2.2799, + 2.3420, + 2.3777, + 2.5255, + 2.6135, + 2.6373, + 2.6727, + 2.7228, + 2.8765, + 2.8841, + 2.9076, + 2.9624, + 3.0377, + 3.0978, + 3.2509, + 3.3613, + 3.8767, + 3.9603, + 4.0824, + 4.1424, + 5.1826, + 5.2283, + 5.3319, + 5.3817, + 5.4919, + 5.5386, + 5.5584, + 5.5648, + 5.6049, + 5.6226, + 6.1591, + 6.2079, + 6.3862, + 6.4446, + 6.5805, + 6.5926, + 6.6092, + 6.6315, + 6.6557, + 6.6703, + 7.0400, + 7.1334, + 7.1456, + 7.2547, + 43.7457, + 43.9166, + ] assert_allclose(mol.mo.energiesa, mo_energies_a) assert_allclose(mol.mo.energiesb, mo_energies_a) - assert_equal(mol.atcharges['mulliken'], np.array([-0.610250, 0.304021, 0.337060, - -0.663525, 0.316347, 0.316347])) - assert_allclose(mol.moments[(1, 'c')], np.array([2.5689, 0.0770, 0.0000])) - assert_allclose(mol.moments[(2, 'c')], - [-12.0581, -6.2544, -0.0000, -12.8954, -0.0000, -12.2310]) + assert_equal( + mol.atcharges["mulliken"], + np.array([-0.610250, 0.304021, 0.337060, -0.663525, 0.316347, 0.316347]), + ) + assert_allclose(mol.moments[(1, "c")], np.array([2.5689, 0.0770, 0.0000])) + assert_allclose( + mol.moments[(2, "c")], [-12.0581, -6.2544, -0.0000, -12.8954, -0.0000, -12.2310] + ) # check eda2 info - assert_equal(mol.extra['eda2']['e_elec'], -65.9887 * kjmol) - assert_equal(mol.extra['eda2']['e_kep_pauli'], 78.5700 * kjmol) - assert_equal(mol.extra['eda2']['e_disp_free_pauli'], -14.2495 * kjmol) - assert_equal(mol.extra['eda2']['e_disp'], -7.7384 * kjmol) - assert_equal(mol.extra['eda2']['e_cls_elec'], -35.1257 * kjmol) - assert_equal(mol.extra['eda2']['e_cls_pauli'], 25.7192 * kjmol) - assert_equal(mol.extra['eda2']['e_mod_pauli'], 33.4576 * kjmol) - assert_equal(mol.extra['eda2']['preparation'], 0.0000) - assert_equal(mol.extra['eda2']['frozen'], -1.6681 * kjmol) - assert_equal(mol.extra['eda2']['pauli'], 64.3205 * kjmol) - assert_equal(mol.extra['eda2']['dispersion'], -7.7384 * kjmol) - assert_equal(mol.extra['eda2']['polarization'], -4.6371 * kjmol) - assert_equal(mol.extra['eda2']['charge transfer'], -7.0689 * kjmol) - assert_equal(mol.extra['eda2']['total'], -21.1126 * kjmol) + assert_equal(mol.extra["eda2"]["e_elec"], -65.9887 * kjmol) + assert_equal(mol.extra["eda2"]["e_kep_pauli"], 78.5700 * kjmol) + assert_equal(mol.extra["eda2"]["e_disp_free_pauli"], -14.2495 * kjmol) + assert_equal(mol.extra["eda2"]["e_disp"], -7.7384 * kjmol) + assert_equal(mol.extra["eda2"]["e_cls_elec"], -35.1257 * kjmol) + assert_equal(mol.extra["eda2"]["e_cls_pauli"], 25.7192 * kjmol) + assert_equal(mol.extra["eda2"]["e_mod_pauli"], 33.4576 * kjmol) + assert_equal(mol.extra["eda2"]["preparation"], 0.0000) + assert_equal(mol.extra["eda2"]["frozen"], -1.6681 * kjmol) + assert_equal(mol.extra["eda2"]["pauli"], 64.3205 * kjmol) + assert_equal(mol.extra["eda2"]["dispersion"], -7.7384 * kjmol) + assert_equal(mol.extra["eda2"]["polarization"], -4.6371 * kjmol) + assert_equal(mol.extra["eda2"]["charge transfer"], -7.0689 * kjmol) + assert_equal(mol.extra["eda2"]["total"], -21.1126 * kjmol) # check fragments coords1 = [[-1.551007, -0.11452, 0.0], [-1.934259, 0.762503, 0.0], [-0.599677, 0.040712, 0.0]] - coords2 = [[1.350625, 0.111469, 0.0], [1.680398, -0.373741, -0.758561], - [1.680398, -0.373741, 0.758561]] - assert_equal(len(mol.extra['frags']), 2) + coords2 = [ + [1.350625, 0.111469, 0.0], + [1.680398, -0.373741, -0.758561], + [1.680398, -0.373741, 0.758561], + ] + assert_equal(len(mol.extra["frags"]), 2) - assert_equal(mol.extra['frags'][0]['atnums'], [8, 1, 1]) - assert_equal(mol.extra['frags'][0]['alpha_elec'], 5) - assert_equal(mol.extra['frags'][0]['beta_elec'], 5) - assert_equal(mol.extra['frags'][0]['nbasis'], 58) - assert_allclose(mol.extra['frags'][0]['atcoords'], np.array(coords1) * angstrom) - assert_allclose(mol.extra['frags'][0]['nuclear_repulsion_energy'], 9.16383018) - assert_allclose(mol.extra['frags'][0]['energy'], -76.4345994141) + assert_equal(mol.extra["frags"][0]["atnums"], [8, 1, 1]) + assert_equal(mol.extra["frags"][0]["alpha_elec"], 5) + assert_equal(mol.extra["frags"][0]["beta_elec"], 5) + assert_equal(mol.extra["frags"][0]["nbasis"], 58) + assert_allclose(mol.extra["frags"][0]["atcoords"], np.array(coords1) * angstrom) + assert_allclose(mol.extra["frags"][0]["nuclear_repulsion_energy"], 9.16383018) + assert_allclose(mol.extra["frags"][0]["energy"], -76.4345994141) - assert_equal(mol.extra['frags'][1]['atnums'], [8, 1, 1]) - assert_equal(mol.extra['frags'][1]['alpha_elec'], 5) - assert_equal(mol.extra['frags'][1]['beta_elec'], 5) - assert_equal(mol.extra['frags'][1]['nbasis'], 58) - assert_allclose(mol.extra['frags'][1]['atcoords'], np.array(coords2) * angstrom) - assert_allclose(mol.extra['frags'][1]['nuclear_repulsion_energy'], 9.17803894) - assert_allclose(mol.extra['frags'][1]['energy'], -76.4346136883) + assert_equal(mol.extra["frags"][1]["atnums"], [8, 1, 1]) + assert_equal(mol.extra["frags"][1]["alpha_elec"], 5) + assert_equal(mol.extra["frags"][1]["beta_elec"], 5) + assert_equal(mol.extra["frags"][1]["nbasis"], 58) + assert_allclose(mol.extra["frags"][1]["atcoords"], np.array(coords2) * angstrom) + assert_allclose(mol.extra["frags"][1]["nuclear_repulsion_energy"], 9.17803894) + assert_allclose(mol.extra["frags"][1]["energy"], -76.4346136883) diff --git a/iodata/test/test_sdf.py b/iodata/test/test_sdf.py index 74eaf201c..04554dde6 100644 --- a/iodata/test/test_sdf.py +++ b/iodata/test/test_sdf.py @@ -21,36 +21,51 @@ import os import pytest -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal +from ..api import dump_many, dump_one, load_many, load_one +from ..utils import FileFormatError, angstrom from .common import truncated_file -from ..api import load_one, load_many, dump_one, dump_many -from ..utils import angstrom + try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files -def test_sdf_load_one(): +def test_sdf_load_one_example(): # test sdf one structure - with path('iodata.test.data', 'example.sdf') as fn_sdf: + with as_file(files("iodata.test.data").joinpath("example.sdf")) as fn_sdf: mol = load_one(str(fn_sdf)) check_example(mol) +def test_sdf_load_one_formamide(): + # test sdf one structure + with as_file(files("iodata.test.data").joinpath("formamide.sdf")) as fn_sdf: + mol = load_one(str(fn_sdf)) + assert mol.title == "713" + assert mol.natom == 6 + assert len(mol.bonds) == 5 + assert_equal(mol.atnums, [8, 7, 6, 1, 1, 1]) + assert_equal(mol.bonds, [[0, 2, 2], [1, 2, 1], [1, 3, 1], [1, 4, 1], [2, 5, 1]]) + + def test_sdf_formaterror(tmpdir): # test if sdf file has the wrong ending without $$$$ - with path('iodata.test.data', 'example.sdf') as fn_test: - with truncated_file(fn_test, 36, 0, tmpdir) as fn: - with pytest.raises(IOError): - load_one(str(fn)) + with ( + as_file(files("iodata.test.data").joinpath("example.sdf")) as fn_test, + truncated_file(fn_test, 36, 0, tmpdir) as fn, + pytest.raises(IOError), + ): + load_one(str(fn)) def check_example(mol): """Test some things on example file.""" - assert mol.title == '24978498' - assert_equal(mol.natom, 16) + assert mol.title == "24978498" + assert mol.natom == 16 + assert len(mol.bonds) == 15 assert_equal(mol.atnums, [16, 8, 8, 8, 8, 7, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1]) # check coordinates atcoords_ang = mol.atcoords / angstrom @@ -58,46 +73,64 @@ def check_example(mol): assert_allclose(atcoords_ang[1], [5.4641, 1.0600, 0.0000]) assert_allclose(atcoords_ang[14], [6.0010, 1.3700, 0.0000]) assert_allclose(atcoords_ang[15], [2.0000, -2.5600, 0.0000]) + assert_equal(mol.bonds[0], [0, 3, 1]) + assert_equal(mol.bonds[4], [2, 8, 2]) + assert_equal(mol.bonds[14], [7, 11, 1]) def check_load_dump_consistency(tmpdir, fn): """Check if dumping and loading an SDF file results in the same data.""" mol0 = load_one(str(fn)) # write sdf file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test.sdf') + fn_tmp = os.path.join(tmpdir, "test.sdf") dump_one(mol0, fn_tmp) mol1 = load_one(fn_tmp) # check two sdf files assert mol0.title == mol1.title assert_equal(mol0.atnums, mol1.atnums) - assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.e-5) + assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.0e-5) + assert_equal(mol0.bonds, mol1.bonds) def test_load_dump_consistency(tmpdir): - with path('iodata.test.data', 'example.sdf') as fn_sdf: + with as_file(files("iodata.test.data").joinpath("example.sdf")) as fn_sdf: + check_load_dump_consistency(tmpdir, fn_sdf) + with as_file(files("iodata.test.data").joinpath("formamide.sdf")) as fn_sdf: + check_load_dump_consistency(tmpdir, fn_sdf) + # The benzene mol2 file has aromatic bonds, which are less common in SDF files. + with as_file(files("iodata.test.data").joinpath("benzene.mol2")) as fn_sdf: check_load_dump_consistency(tmpdir, fn_sdf) def test_load_many(): - with path('iodata.test.data', 'example.sdf') as fn_sdf: + with as_file(files("iodata.test.data").joinpath("example.sdf")) as fn_sdf: mols = list(load_many(str(fn_sdf))) assert len(mols) == 2 check_example(mols[0]) - assert mols[1].title == '24978481' + assert mols[1].title == "24978481" assert_equal(mols[1].natom, 21) assert_allclose(mols[0].atcoords[0] / angstrom, [2.8660, -0.4400, 0.0000]) assert_allclose(mols[1].atcoords[1] / angstrom, [1.4030, 1.4030, 0.0000]) def test_load_dump_many_consistency(tmpdir): - with path('iodata.test.data', 'example.sdf') as fn_sdf: + with as_file(files("iodata.test.data").joinpath("example.sdf")) as fn_sdf: mols0 = list(load_many(str(fn_sdf))) # write sdf file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test') - dump_many(mols0, fn_tmp, fmt='sdf') - mols1 = list(load_many(fn_tmp, fmt='sdf')) + fn_tmp = os.path.join(tmpdir, "test") + dump_many(mols0, fn_tmp, fmt="sdf") + mols1 = list(load_many(fn_tmp, fmt="sdf")) assert len(mols0) == len(mols1) for mol0, mol1 in zip(mols0, mols1): assert mol0.title == mol1.title assert_equal(mol0.atnums, mol1.atnums) - assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.e-5) + assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.0e-5) + assert_equal(mol0.bonds, mol1.bonds) + + +def test_v2000_check(): + with ( + as_file(files("iodata.test.data").joinpath("molv3000.sdf")) as fn_sdf, + pytest.raises(FileFormatError), + ): + load_one(fn_sdf) diff --git a/iodata/test/test_utils.py b/iodata/test/test_utils.py index 42b582b51..d3b55536a 100644 --- a/iodata/test/test_utils.py +++ b/iodata/test/test_utils.py @@ -18,9 +18,18 @@ # -- """Unit tests for iodata.utils.""" +import pytest -from ..utils import amu +from ..utils import amu, strtobool def test_amu(): assert abs(amu * 1.008 - 1837.47) < 1e-1 + + +def test_strtobool(): + assert strtobool("T") is True + assert strtobool("false") is False + assert strtobool("y") is True + with pytest.raises(ValueError): + strtobool("whatever") diff --git a/iodata/test/test_wfn.py b/iodata/test/test_wfn.py index 17a821a53..538095c0f 100644 --- a/iodata/test/test_wfn.py +++ b/iodata/test/test_wfn.py @@ -21,36 +21,36 @@ import os import numpy as np +from numpy.testing import assert_allclose, assert_equal -from numpy.testing import assert_equal, assert_allclose - -from .common import compute_mulliken_charges, check_orthonormal, compare_mols -from ..api import load_one, dump_one +from ..api import dump_one, load_one from ..formats.wfn import load_wfn_low from ..overlap import compute_overlap from ..utils import LineIterator +from .common import check_orthonormal, compare_mols, compute_mulliken_charges try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files + # TODO: removed density, kin, nucnuc checks def helper_load_wfn_low(fn_wfn): """Load a testing Gaussian log file with iodata.formats.wfn.load_wfn_low.""" - with path('iodata.test.data', fn_wfn) as fn: + with as_file(files("iodata.test.data").joinpath(fn_wfn)) as fn: lit = LineIterator(str(fn)) return load_wfn_low(lit) def test_load_wfn_low_he_s(): - data = helper_load_wfn_low('he_s_orbital.wfn') + data = helper_load_wfn_low("he_s_orbital.wfn") # unpack data title, atnums, atcoords, centers, type_assignments = data[:5] exponents, mo_count, occ_num, mo_energy, coefficients, energy, virial, _ = data[5:] - assert title == 'He atom - decontracted 6-31G basis set' + assert title == "He atom - decontracted 6-31G basis set" assert_equal(atnums.shape, (1,)) assert_equal(atnums, [2]) assert_equal(atcoords.shape, (1, 3)) @@ -66,22 +66,20 @@ def test_load_wfn_low_he_s(): assert_equal(centers, [0, 0, 0, 0]) assert_equal(occ_num, [2.0]) assert_allclose(atcoords, np.array([[0.00, 0.00, 0.00]])) - assert_allclose(exponents, [0.3842163E+02, 0.5778030E+01, - 0.1241774E+01, 0.2979640E+00]) + assert_allclose(exponents, [0.3842163e02, 0.5778030e01, 0.1241774e01, 0.2979640e00]) assert_allclose(mo_energy, [-0.914127]) - expected = np.array([0.26139500E+00, 0.41084277E+00, - 0.39372947E+00, 0.14762025E+00]) + expected = np.array([0.26139500e00, 0.41084277e00, 0.39372947e00, 0.14762025e00]) assert_allclose(coefficients, expected.reshape(4, 1)) - assert_allclose(energy, -2.855160426155, atol=1.e-5) - assert_allclose(virial, 1.99994256, atol=1.e-6) + assert_allclose(energy, -2.855160426155, atol=1.0e-5) + assert_allclose(virial, 1.99994256, atol=1.0e-6) def test_load_wfn_low_h2o(): - data = helper_load_wfn_low('h2o_sto3g.wfn') + data = helper_load_wfn_low("h2o_sto3g.wfn") # unpack data title, atnums, atcoords, centers, type_assignments = data[:5] exponents, mo_count, occ_num, mo_energy, coefficients, energy, virial, _ = data[5:] - assert title == 'H2O Optimization' + assert title == "H2O Optimization" assert_equal(atnums.shape, (3,)) assert_equal(atcoords.shape, (3, 3)) assert_equal(centers.shape, (21,)) @@ -95,158 +93,153 @@ def test_load_wfn_low_h2o(): assert_equal(centers[:15], np.zeros(15, int)) assert_equal(centers[15:], np.array([1, 1, 1, 2, 2, 2])) assert_equal(type_assignments[:6], np.zeros(6)) - assert_equal(type_assignments[6:15], np.array( - [1, 1, 1, 2, 2, 2, 3, 3, 3])) + assert_equal(type_assignments[6:15], np.array([1, 1, 1, 2, 2, 2, 3, 3, 3])) assert_equal(type_assignments[15:], np.zeros(6)) assert_equal(mo_count, [1, 2, 3, 4, 5]) assert_equal(np.sum(occ_num), 10.0) assert_equal(occ_num, [2.0, 2.0, 2.0, 2.0, 2.0]) - assert_allclose(atcoords, np.array([ - [-4.44734101, 3.39697999, 0.00000000], - [-2.58401495, 3.55136194, 0.00000000], - [-4.92380519, 5.20496220, 0.00000000]])) - assert_allclose(exponents[:3], - [0.1307093E+03, 0.2380887E+02, 0.6443608E+01]) - assert_allclose(exponents[5:8], - [0.3803890E+00, 0.5033151E+01, 0.1169596E+01]) - assert_allclose(exponents[13:16], - [0.1169596E+01, 0.3803890E+00, 0.3425251E+01]) - assert_allclose(exponents[-1], 0.1688554E+00) + assert_allclose( + atcoords, + np.array( + [ + [-4.44734101, 3.39697999, 0.00000000], + [-2.58401495, 3.55136194, 0.00000000], + [-4.92380519, 5.20496220, 0.00000000], + ] + ), + ) + assert_allclose(exponents[:3], [0.1307093e03, 0.2380887e02, 0.6443608e01]) + assert_allclose(exponents[5:8], [0.3803890e00, 0.5033151e01, 0.1169596e01]) + assert_allclose(exponents[13:16], [0.1169596e01, 0.3803890e00, 0.3425251e01]) + assert_allclose(exponents[-1], 0.1688554e00) assert_allclose(mo_energy, np.sort(mo_energy)) assert_allclose(mo_energy[:3], [-20.251576, -1.257549, -0.593857]) assert_allclose(mo_energy[3:], [-0.459729, -0.392617]) - expected = [0.42273517E+01, -0.99395832E+00, - 0.19183487E-11, 0.44235381E+00, -0.57941668E-14] + expected = [0.42273517e01, -0.99395832e00, 0.19183487e-11, 0.44235381e00, -0.57941668e-14] assert_allclose(coefficients[0], expected) - assert_allclose(coefficients[6, 2], 0.83831599E+00) - assert_allclose(coefficients[10, 3], 0.65034846E+00) - assert_allclose(coefficients[17, 1], 0.12988055E-01) - assert_allclose(coefficients[-1, 0], -0.46610858E-03) - assert_allclose(coefficients[-1, -1], -0.33277355E-15) - assert_allclose(energy, -74.965901217080, atol=1.e-6) - assert_allclose(virial, 2.00600239, atol=1.e-6) + assert_allclose(coefficients[6, 2], 0.83831599e00) + assert_allclose(coefficients[10, 3], 0.65034846e00) + assert_allclose(coefficients[17, 1], 0.12988055e-01) + assert_allclose(coefficients[-1, 0], -0.46610858e-03) + assert_allclose(coefficients[-1, -1], -0.33277355e-15) + assert_allclose(energy, -74.965901217080, atol=1.0e-6) + assert_allclose(virial, 2.00600239, atol=1.0e-6) def check_wfn(fn_wfn, nbasis, energy, charges_mulliken): """Check that MO are orthonormal & energy and charges match expected values.""" # load file - with path('iodata.test.data', fn_wfn) as file_wfn: + with as_file(files("iodata.test.data").joinpath(fn_wfn)) as file_wfn: mol = load_one(str(file_wfn)) # check number of basis functions assert mol.obasis.nbasis == nbasis # check orthonormal mo olp = compute_overlap(mol.obasis, mol.atcoords) - check_orthonormal(mol.mo.coeffsa, olp, 1.e-5) - if mol.mo.kind == 'unrestricted': - check_orthonormal(mol.mo.coeffsb, olp, 1.e-5) + check_orthonormal(mol.mo.coeffsa, olp, 1.0e-5) + if mol.mo.kind == "unrestricted": + check_orthonormal(mol.mo.coeffsb, olp, 1.0e-5) # check energy & atomic charges if energy is not None: - assert_allclose(mol.energy, energy, rtol=0., atol=1.e-5) + assert_allclose(mol.energy, energy, rtol=0.0, atol=1.0e-5) if charges_mulliken is not None: charges = compute_mulliken_charges(mol) - assert_allclose(charges_mulliken, charges, rtol=0., atol=1.e-5) + assert_allclose(charges_mulliken, charges, rtol=0.0, atol=1.0e-5) return mol def test_load_wfn_h2o_sto3g_decontracted(): charges = np.array([-0.546656, 0.273328, 0.273328]) - check_wfn('h2o_sto3g_decontracted.wfn', 21, -75.162231674351, charges) + check_wfn("h2o_sto3g_decontracted.wfn", 21, -75.162231674351, charges) def test_load_wfn_h2_ccpvqz_virtual(): - mol = check_wfn('h2_ccpvqz.wfn', 74, -1.133504568400, np.array([0.0, 0.0])) + mol = check_wfn("h2_ccpvqz.wfn", 74, -1.133504568400, np.array([0.0, 0.0])) expect = [82.64000, 12.41000, 2.824000, 0.7977000, 0.2581000] - assert_allclose([shell.exponents[0] for shell in mol.obasis.shells[:5]], - expect, rtol=0., atol=1.e-6) + assert_allclose( + [shell.exponents[0] for shell in mol.obasis.shells[:5]], expect, rtol=0.0, atol=1.0e-6 + ) expect = [-0.596838, 0.144565, 0.209605, 0.460401, 0.460401] - assert_allclose(mol.mo.energies[:5], expect, rtol=0., atol=1.e-6) + assert_allclose(mol.mo.energies[:5], expect, rtol=0.0, atol=1.0e-6) expect = [12.859067, 13.017471, 16.405834, 25.824716, 26.100443] - assert_allclose(mol.mo.energies[-5:], expect, rtol=0., atol=1.e-6) + assert_allclose(mol.mo.energies[-5:], expect, rtol=0.0, atol=1.0e-6) assert_equal(mol.mo.occs[:5], [2, 0, 0, 0, 0]) assert_equal(mol.mo.occs.sum(), 2) def test_load_wfn_h2o_sto3g(): - check_wfn('h2o_sto3g.wfn', 21, -74.96590121708, - np.array([-0.330532, 0.165266, 0.165266])) + check_wfn("h2o_sto3g.wfn", 21, -74.96590121708, np.array([-0.330532, 0.165266, 0.165266])) def test_load_wfn_li_sp_virtual(): - mol = check_wfn('li_sp_virtual.wfn', 8, -3.712905542719, np.array([0.0])) + mol = check_wfn("li_sp_virtual.wfn", 8, -3.712905542719, np.array([0.0])) assert_equal(mol.mo.occs[:8], [1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) assert_equal(mol.mo.occs[8:], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - expect = [-0.087492, -0.080310, 0.158784, 0.158784, - 1.078773, 1.090891, 1.090891, 49.643670] - assert_allclose(mol.mo.energies[:8], expect, rtol=0., atol=1.e-6) - expect = [-0.079905, 0.176681, 0.176681, 0.212494, - 1.096631, 1.096631, 1.122821, 49.643827] - assert_allclose(mol.mo.energies[8:], expect, rtol=0., atol=1.e-6) + expect = [-0.087492, -0.080310, 0.158784, 0.158784, 1.078773, 1.090891, 1.090891, 49.643670] + assert_allclose(mol.mo.energies[:8], expect, rtol=0.0, atol=1.0e-6) + expect = [-0.079905, 0.176681, 0.176681, 0.212494, 1.096631, 1.096631, 1.122821, 49.643827] + assert_allclose(mol.mo.energies[8:], expect, rtol=0.0, atol=1.0e-6) assert_equal(mol.mo.coeffs.shape, (8, 16)) def test_load_wfn_li_sp(): - mol = check_wfn('li_sp_orbital.wfn', 8, -3.712905542719, None) - assert mol.title == 'Li atom - using s & p orbitals' + mol = check_wfn("li_sp_orbital.wfn", 8, -3.712905542719, None) + assert mol.title == "Li atom - using s & p orbitals" assert_equal([mol.mo.norba, mol.mo.norbb], [2, 1]) - assert_allclose(mol.mo.energies, - [-0.087492, -0.080310, -0.079905], rtol=0., atol=1.e-6) + assert_allclose(mol.mo.energies, [-0.087492, -0.080310, -0.079905], rtol=0.0, atol=1.0e-6) def test_load_wfn_o2(): - mol = check_wfn('o2_uhf.wfn', 72, -149.664140769678, np.array([0.0, 0.0])) + mol = check_wfn("o2_uhf.wfn", 72, -149.664140769678, np.array([0.0, 0.0])) assert_equal([mol.mo.norba, mol.mo.norbb], [9, 7]) def test_load_wfn_o2_virtual(): - mol = check_wfn('o2_uhf_virtual.wfn', 72, - -149.664140769678, np.array([0.0, 0.0])) + mol = check_wfn("o2_uhf_virtual.wfn", 72, -149.664140769678, np.array([0.0, 0.0])) # check MO occupation assert_equal(mol.mo.occs.shape, (88,)) - assert_allclose(mol.mo.occsa, [1.] * 9 + [0.] * 35) - assert_allclose(mol.mo.occsb, [1.] * 7 + [0.] * 37) + assert_allclose(mol.mo.occsa, [1.0] * 9 + [0.0] * 35) + assert_allclose(mol.mo.occsb, [1.0] * 7 + [0.0] * 37) # check MO energies assert_equal(mol.mo.energies.shape, (88,)) mo_energies_a = mol.mo.energiesa - assert_allclose(mo_energies_a[0], -20.752000, rtol=0, atol=1.e-6) - assert_allclose(mo_energies_a[10], 0.179578, rtol=0, atol=1.e-6) - assert_allclose(mo_energies_a[-1], 51.503193, rtol=0, atol=1.e-6) + assert_allclose(mo_energies_a[0], -20.752000, rtol=0, atol=1.0e-6) + assert_allclose(mo_energies_a[10], 0.179578, rtol=0, atol=1.0e-6) + assert_allclose(mo_energies_a[-1], 51.503193, rtol=0, atol=1.0e-6) mo_energies_b = mol.mo.energiesb - assert_allclose(mo_energies_b[0], -20.697027, rtol=0, atol=1.e-6) - assert_allclose(mo_energies_b[15], 0.322590, rtol=0, atol=1.e-6) - assert_allclose(mo_energies_b[-1], 51.535258, rtol=0, atol=1.e-6) + assert_allclose(mo_energies_b[0], -20.697027, rtol=0, atol=1.0e-6) + assert_allclose(mo_energies_b[15], 0.322590, rtol=0, atol=1.0e-6) + assert_allclose(mo_energies_b[-1], 51.535258, rtol=0, atol=1.0e-6) # check MO coefficients assert_equal(mol.mo.coeffs.shape, (72, 88)) def test_load_wfn_lif_fci(): - mol = check_wfn('lif_fci.wfn', 44, -107.0575700853, - np.array([-0.645282, 0.645282])) + mol = check_wfn("lif_fci.wfn", 44, -107.0575700853, np.array([-0.645282, 0.645282])) assert_equal(mol.mo.occs.shape, (18,)) - assert_allclose(mol.mo.occs.sum(), 12.0, rtol=0., atol=1.e-6) - assert_allclose(mol.mo.occs[0], 2.0, rtol=0., atol=1.e-6) - assert_allclose(mol.mo.occs[10], 0.00128021, rtol=0., atol=1.e-6) - assert_allclose(mol.mo.occs[-1], 0.00000054, rtol=0., atol=1.e-6) + assert_allclose(mol.mo.occs.sum(), 12.0, rtol=0.0, atol=1.0e-6) + assert_allclose(mol.mo.occs[0], 2.0, rtol=0.0, atol=1.0e-6) + assert_allclose(mol.mo.occs[10], 0.00128021, rtol=0.0, atol=1.0e-6) + assert_allclose(mol.mo.occs[-1], 0.00000054, rtol=0.0, atol=1.0e-6) assert_equal(mol.mo.energies.shape, (18,)) - assert_allclose(mol.mo.energies[0], -26.09321253, rtol=0., atol=1.e-7) - assert_allclose(mol.mo.energies[15], 1.70096290, rtol=0., atol=1.e-7) - assert_allclose(mol.mo.energies[-1], 2.17434072, rtol=0., atol=1.e-7) + assert_allclose(mol.mo.energies[0], -26.09321253, rtol=0.0, atol=1.0e-7) + assert_allclose(mol.mo.energies[15], 1.70096290, rtol=0.0, atol=1.0e-7) + assert_allclose(mol.mo.energies[-1], 2.17434072, rtol=0.0, atol=1.0e-7) assert_equal(mol.mo.coeffs.shape, (44, 18)) def test_load_wfn_lih_cation_fci(): - mol = check_wfn('lih_cation_fci.wfn', 26, -7.7214366383, - np.array([0.913206, 0.086794])) + mol = check_wfn("lih_cation_fci.wfn", 26, -7.7214366383, np.array([0.913206, 0.086794])) assert_equal(mol.atnums, [3, 1]) assert_equal(mol.mo.occs.shape, (11,)) - assert_allclose(mol.mo.occs.sum(), 3., rtol=0., atol=1.e-6) + assert_allclose(mol.mo.occs.sum(), 3.0, rtol=0.0, atol=1.0e-6) # assert abs(mol.mo.occsa.sum() - 1.5) < 1.e-6 def test_load_one_lih_cation_cisd(): - with path('iodata.test.data', 'lih_cation_cisd.wfn') as file_wfn: + with as_file(files("iodata.test.data").joinpath("lih_cation_cisd.wfn")) as file_wfn: mol = load_one(str(file_wfn)) # check number of orbitals and occupation numbers - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert mol.mo.norba == 11 assert mol.mo.norbb == 11 assert mol.mo.norb == 22 @@ -259,10 +252,10 @@ def test_load_one_lih_cation_cisd(): def test_load_one_lih_cation_uhf(): - with path('iodata.test.data', 'lih_cation_uhf.wfn') as file_wfn: + with as_file(files("iodata.test.data").joinpath("lih_cation_uhf.wfn")) as file_wfn: mol = load_one(str(file_wfn)) # check number of orbitals and occupation numbers - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert mol.mo.norba == 2 assert mol.mo.norbb == 1 assert mol.mo.norb == 3 @@ -275,10 +268,10 @@ def test_load_one_lih_cation_uhf(): def test_load_one_lih_cation_rohf(): - with path('iodata.test.data', 'lih_cation_rohf.wfn') as file_wfn: + with as_file(files("iodata.test.data").joinpath("lih_cation_rohf.wfn")) as file_wfn: mol = load_one(str(file_wfn)) # check number of orbitals and occupation numbers - assert mol.mo.kind == 'restricted' + assert mol.mo.kind == "restricted" assert mol.mo.norba == 2 assert mol.mo.norbb == 2 assert mol.mo.norb == 2 @@ -292,10 +285,10 @@ def test_load_one_lih_cation_rohf(): def test_load_one_cah110_hf_sto3g_g09(): - with path('iodata.test.data', 'cah110_hf_sto3g_g09.wfn') as file_wfn: + with as_file(files("iodata.test.data").joinpath("cah110_hf_sto3g_g09.wfn")) as file_wfn: mol = load_one(str(file_wfn)) # check number of orbitals and occupation numbers - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert mol.mo.norba == 123 assert mol.mo.norbb == 123 assert mol.mo.norb == 246 @@ -311,7 +304,7 @@ def test_load_one_cah110_hf_sto3g_g09(): check_orthonormal(mol.mo.coeffsb, olp, 1e-5) -def check_load_dump_consistency(fn, tmpdir, fmt_from='wfn', fmt_to='wfn', atol=1.0e-6): +def check_load_dump_consistency(fn, tmpdir, fmt_from="wfn", fmt_to="wfn", atol=1.0e-6): """Check if data is preserved after dumping and loading a WFN file. Parameters @@ -326,9 +319,9 @@ def check_load_dump_consistency(fn, tmpdir, fmt_from='wfn', fmt_to='wfn', atol=1 Format of filename to dump and then load again. """ - with path('iodata.test.data', fn) as file_name: + with as_file(files("iodata.test.data").joinpath(fn)) as file_name: mol1 = load_one(str(file_name), fmt=fmt_from) - fn_tmp = os.path.join(tmpdir, 'foo.bar') + fn_tmp = os.path.join(tmpdir, "foo.bar") dump_one(mol1, fn_tmp, fmt=fmt_to) mol2 = load_one(fn_tmp, fmt=fmt_to) # compare Mulliken charges @@ -340,59 +333,59 @@ def check_load_dump_consistency(fn, tmpdir, fmt_from='wfn', fmt_to='wfn', atol=1 def test_load_dump_consistency_lih_cation_cisd(tmpdir): - check_load_dump_consistency('lih_cation_cisd.wfn', tmpdir) + check_load_dump_consistency("lih_cation_cisd.wfn", tmpdir) def test_load_dump_consistency_lih_cation_uhf(tmpdir): - check_load_dump_consistency('lih_cation_uhf.wfn', tmpdir) + check_load_dump_consistency("lih_cation_uhf.wfn", tmpdir) def test_load_dump_consistency_lih_cation_rohf(tmpdir): - check_load_dump_consistency('lih_cation_rohf.wfn', tmpdir) + check_load_dump_consistency("lih_cation_rohf.wfn", tmpdir) def test_load_dump_consistency_h2o(tmpdir): - check_load_dump_consistency('h2o_sto3g.wfn', tmpdir) - check_load_dump_consistency('h2o_sto3g_decontracted.wfn', tmpdir) + check_load_dump_consistency("h2o_sto3g.wfn", tmpdir) + check_load_dump_consistency("h2o_sto3g_decontracted.wfn", tmpdir) def test_load_dump_consistency_lif(tmpdir): - check_load_dump_consistency('lif_fci.wfn', tmpdir, atol=1.0e-6) + check_load_dump_consistency("lif_fci.wfn", tmpdir, atol=1.0e-6) def test_load_dump_consistency_cah110(tmpdir): - check_load_dump_consistency('cah110_hf_sto3g_g09.wfn', tmpdir) + check_load_dump_consistency("cah110_hf_sto3g_g09.wfn", tmpdir) def test_load_dump_consistency_li(tmpdir): - check_load_dump_consistency('li_sp_orbital.wfn', tmpdir) - check_load_dump_consistency('li_sp_virtual.wfn', tmpdir) + check_load_dump_consistency("li_sp_orbital.wfn", tmpdir) + check_load_dump_consistency("li_sp_virtual.wfn", tmpdir) def test_load_dump_consistency_he(tmpdir): - check_load_dump_consistency('he_s_orbital.wfn', tmpdir) - check_load_dump_consistency('he_s_virtual.wfn', tmpdir) - check_load_dump_consistency('he_p_orbital.wfn', tmpdir) - check_load_dump_consistency('he_d_orbital.wfn', tmpdir) - check_load_dump_consistency('he_sp_orbital.wfn', tmpdir) - check_load_dump_consistency('he_spd_orbital.wfn', tmpdir) - check_load_dump_consistency('he_spdf_orbital.wfn', tmpdir) - check_load_dump_consistency('he_spdfgh_orbital.wfn', tmpdir) - check_load_dump_consistency('he_spdfgh_virtual.wfn', tmpdir) + check_load_dump_consistency("he_s_orbital.wfn", tmpdir) + check_load_dump_consistency("he_s_virtual.wfn", tmpdir) + check_load_dump_consistency("he_p_orbital.wfn", tmpdir) + check_load_dump_consistency("he_d_orbital.wfn", tmpdir) + check_load_dump_consistency("he_sp_orbital.wfn", tmpdir) + check_load_dump_consistency("he_spd_orbital.wfn", tmpdir) + check_load_dump_consistency("he_spdf_orbital.wfn", tmpdir) + check_load_dump_consistency("he_spdfgh_orbital.wfn", tmpdir) + check_load_dump_consistency("he_spdfgh_virtual.wfn", tmpdir) def test_load_dump_consistency_h2(tmpdir): - check_load_dump_consistency('h2_ccpvqz.wfn', tmpdir) + check_load_dump_consistency("h2_ccpvqz.wfn", tmpdir) def test_load_dump_consistency_o2(tmpdir): - check_load_dump_consistency('o2_uhf.wfn', tmpdir) - check_load_dump_consistency('o2_uhf_virtual.wfn', tmpdir) + check_load_dump_consistency("o2_uhf.wfn", tmpdir) + check_load_dump_consistency("o2_uhf_virtual.wfn", tmpdir) def test_load_dump_consistency_from_fchk_h2o(tmpdir): - check_load_dump_consistency('h2o_sto3g.fchk', tmpdir, fmt_from='fchk', fmt_to='wfn') + check_load_dump_consistency("h2o_sto3g.fchk", tmpdir, fmt_from="fchk", fmt_to="wfn") def test_load_dump_consistency_from_molden_nh3(tmpdir): - check_load_dump_consistency('nh3_molden_cart.molden', tmpdir, fmt_from='molden', fmt_to='wfn') + check_load_dump_consistency("nh3_molden_cart.molden", tmpdir, fmt_from="molden", fmt_to="wfn") diff --git a/iodata/test/test_wfx.py b/iodata/test/test_wfx.py index 5a3ebaf51..67106034c 100644 --- a/iodata/test/test_wfx.py +++ b/iodata/test/test_wfx.py @@ -19,28 +19,33 @@ """Test iodata.formats.wfn module.""" import os +from typing import Optional -import pytest import numpy as np -from numpy.testing import assert_equal, assert_allclose +import pytest +from numpy.testing import assert_allclose, assert_equal -from ..api import load_one, dump_one +from ..api import dump_one, load_one from ..formats.wfx import load_data_wfx, parse_wfx from ..overlap import compute_overlap from ..utils import LineIterator - -from .common import (check_orthonormal, truncated_file, compare_mols, - compute_mulliken_charges, load_one_warning) +from .common import ( + check_orthonormal, + compare_mols, + compute_mulliken_charges, + load_one_warning, + truncated_file, +) try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def helper_load_data_wfx(fn_wfx): """Load a testing WFX file with iodata.formats.wfx.load_data_wfx.""" - with path('iodata.test.data', fn_wfx) as fx: + with as_file(files("iodata.test.data").joinpath(fn_wfx)) as fx: lit = LineIterator(str(fx)) return load_data_wfx(lit) @@ -55,11 +60,11 @@ def check_load_dump_consistency(fn, tmpdir): tmpdir : str The temporary directory to dump and load the file. """ - with path('iodata.test.data', fn) as file_name: - mol1 = load_one(str(file_name), fmt='wfx') - fn_tmp = os.path.join(tmpdir, 'foo.bar') - dump_one(mol1, fn_tmp, fmt='wfx') - mol2 = load_one(fn_tmp, fmt='wfx') + with as_file(files("iodata.test.data").joinpath(fn)) as file_name: + mol1 = load_one(str(file_name), fmt="wfx") + fn_tmp = os.path.join(tmpdir, "foo.bar") + dump_one(mol1, fn_tmp, fmt="wfx") + mol2 = load_one(fn_tmp, fmt="wfx") compare_mols(mol1, mol2) # compare Mulliken charges charges1 = compute_mulliken_charges(mol1) @@ -68,28 +73,28 @@ def check_load_dump_consistency(fn, tmpdir): def test_load_dump_consistency_water(tmpdir): - check_load_dump_consistency('water_sto3g_hf.wfx', tmpdir) + check_load_dump_consistency("water_sto3g_hf.wfx", tmpdir) def test_load_dump_consistency_h2(tmpdir): - check_load_dump_consistency('h2_ub3lyp_ccpvtz.wfx', tmpdir) + check_load_dump_consistency("h2_ub3lyp_ccpvtz.wfx", tmpdir) def test_load_dump_consistency_lih_cation_cisd(tmpdir): - check_load_dump_consistency('lih_cation_cisd.wfx', tmpdir) + check_load_dump_consistency("lih_cation_cisd.wfx", tmpdir) def test_load_dump_consistency_lih_cation_uhf(tmpdir): - check_load_dump_consistency('lih_cation_uhf.wfx', tmpdir) + check_load_dump_consistency("lih_cation_uhf.wfx", tmpdir) def test_load_dump_consistency_lih_cation_rohf(tmpdir): - check_load_dump_consistency('lih_cation_rohf.wfx', tmpdir) + check_load_dump_consistency("lih_cation_rohf.wfx", tmpdir) def compare_mulliken_charges( - fname: str, tmpdir: str, rtol: float = 1.0e-7, atol: float = 0.0, - match: str = None): + fname: str, tmpdir: str, rtol: float = 1.0e-7, atol: float = 0.0, match: Optional[str] = None +): """Check if charges are computed correctly after dumping and loading WFX file format. Parameters @@ -120,81 +125,82 @@ def compare_mulliken_charges( def test_dump_one_from_fchk_h2o(tmpdir): - compare_mulliken_charges('h2o_sto3g.fchk', tmpdir) - compare_mulliken_charges('water_hfs_321g.fchk', tmpdir) - compare_mulliken_charges('water_sto3g_hf_g03.fchk', tmpdir) + compare_mulliken_charges("h2o_sto3g.fchk", tmpdir) + compare_mulliken_charges("water_hfs_321g.fchk", tmpdir) + compare_mulliken_charges("water_sto3g_hf_g03.fchk", tmpdir) def test_dump_one_from_fchk_ch3_restricted(tmpdir): - compare_mulliken_charges('ch3_hf_sto3g.fchk', tmpdir) + compare_mulliken_charges("ch3_hf_sto3g.fchk", tmpdir) def test_dump_one_from_fchk_ch3_unrestricted(tmpdir): - compare_mulliken_charges('ch3_rohf_sto3g_g03.fchk', tmpdir) + compare_mulliken_charges("ch3_rohf_sto3g_g03.fchk", tmpdir) def test_dump_one_from_wfn_h2o(tmpdir): - compare_mulliken_charges('h2o_sto3g.wfn', tmpdir) - compare_mulliken_charges('h2o_sto3g_decontracted.wfn', tmpdir) + compare_mulliken_charges("h2o_sto3g.wfn", tmpdir) + compare_mulliken_charges("h2o_sto3g_decontracted.wfn", tmpdir) def test_dump_one_from_wfn_o2(tmpdir): - compare_mulliken_charges('o2_uhf.wfn', tmpdir) - compare_mulliken_charges('o2_uhf_virtual.wfn', tmpdir) + compare_mulliken_charges("o2_uhf.wfn", tmpdir) + compare_mulliken_charges("o2_uhf_virtual.wfn", tmpdir) def test_dump_one_from_wfn_atom(tmpdir): # Li atom - compare_mulliken_charges('li_sp_orbital.wfn', tmpdir) - compare_mulliken_charges('li_sp_virtual.wfn', tmpdir) + compare_mulliken_charges("li_sp_orbital.wfn", tmpdir) + compare_mulliken_charges("li_sp_virtual.wfn", tmpdir) # He atom - compare_mulliken_charges('he_s_orbital.wfn', tmpdir) - compare_mulliken_charges('he_s_virtual.wfn', tmpdir) - compare_mulliken_charges('he_p_orbital.wfn', tmpdir) - compare_mulliken_charges('he_d_orbital.wfn', tmpdir) - compare_mulliken_charges('he_sp_orbital.wfn', tmpdir) - compare_mulliken_charges('he_spd_orbital.wfn', tmpdir) - compare_mulliken_charges('he_spdf_orbital.wfn', tmpdir) - compare_mulliken_charges('he_spdfgh_orbital.wfn', tmpdir) - compare_mulliken_charges('he_spdfgh_virtual.wfn', tmpdir) + compare_mulliken_charges("he_s_orbital.wfn", tmpdir) + compare_mulliken_charges("he_s_virtual.wfn", tmpdir) + compare_mulliken_charges("he_p_orbital.wfn", tmpdir) + compare_mulliken_charges("he_d_orbital.wfn", tmpdir) + compare_mulliken_charges("he_sp_orbital.wfn", tmpdir) + compare_mulliken_charges("he_spd_orbital.wfn", tmpdir) + compare_mulliken_charges("he_spdf_orbital.wfn", tmpdir) + compare_mulliken_charges("he_spdfgh_orbital.wfn", tmpdir) + compare_mulliken_charges("he_spdfgh_virtual.wfn", tmpdir) def test_dump_one_from_wfn_lih(tmpdir): - compare_mulliken_charges('lih_cation_uhf.wfn', tmpdir) - compare_mulliken_charges('lih_cation_rohf.wfn', tmpdir) - compare_mulliken_charges('lih_cation_cisd.wfn', tmpdir) - compare_mulliken_charges('lih_cation_fci.wfn', tmpdir) + compare_mulliken_charges("lih_cation_uhf.wfn", tmpdir) + compare_mulliken_charges("lih_cation_rohf.wfn", tmpdir) + compare_mulliken_charges("lih_cation_cisd.wfn", tmpdir) + compare_mulliken_charges("lih_cation_fci.wfn", tmpdir) def test_dump_one_from_wfn_lif(tmpdir): - compare_mulliken_charges('lif_fci.wfn', tmpdir) + compare_mulliken_charges("lif_fci.wfn", tmpdir) def test_dump_one_from_molden_h2o(tmpdir): - compare_mulliken_charges('h2o.molden.input', tmpdir, match="ORCA") + compare_mulliken_charges("h2o.molden.input", tmpdir, match="ORCA") def test_dump_one_from_molden_he2(tmpdir): - compare_mulliken_charges('he2_ghost_psi4_1.0.molden', tmpdir) + compare_mulliken_charges("he2_ghost_psi4_1.0.molden", tmpdir) def test_dump_one_from_molden_neon(tmpdir): compare_mulliken_charges( - 'neon_turbomole_def2-qzvp.molden', tmpdir, atol=1.0e-10, match="Turbomole") + "neon_turbomole_def2-qzvp.molden", tmpdir, atol=1.0e-10, match="Turbomole" + ) def test_dump_one_from_molden_nh3(tmpdir): - compare_mulliken_charges('nh3_molden_cart.molden', tmpdir) - compare_mulliken_charges('nh3_molpro2012.molden', tmpdir) - compare_mulliken_charges('nh3_turbomole.molden', tmpdir, match="Turbomole") + compare_mulliken_charges("nh3_molden_cart.molden", tmpdir) + compare_mulliken_charges("nh3_molpro2012.molden", tmpdir) + compare_mulliken_charges("nh3_turbomole.molden", tmpdir, match="Turbomole") def test_dump_one_from_mkl_methanol(tmpdir): - compare_mulliken_charges('ethanol.mkl', tmpdir, match="ORCA") + compare_mulliken_charges("ethanol.mkl", tmpdir, match="ORCA") def test_dump_one_from_mkl_h2(tmpdir): - compare_mulliken_charges('h2_sto3g.mkl', tmpdir, match="ORCA") + compare_mulliken_charges("h2_sto3g.mkl", tmpdir, match="ORCA") # add this test when pure to Cartesian basis set conversion is supported @@ -204,156 +210,399 @@ def test_dump_one_from_mkl_h2(tmpdir): def test_load_data_wfx_h2(): """Test load_data_wfx with h2_ub3lyp_ccpvtz.wfx.""" - data = helper_load_data_wfx('h2_ub3lyp_ccpvtz.wfx') + data = helper_load_data_wfx("h2_ub3lyp_ccpvtz.wfx") # check loaded data - assert data['title'] == 'h2 ub3lyp/cc-pvtz opt-stable-freq' - assert data['keywords'] == 'GTO' + assert data["title"] == "h2 ub3lyp/cc-pvtz opt-stable-freq" + assert data["keywords"] == "GTO" # assert model_name is None - assert data['num_atoms'] == 2 - assert data['num_primitives'] == 34 - assert data['num_occ_mo'] == 56 - assert data['num_perturbations'] == 0 - assert data['num_electrons'] == 2 - assert data['num_alpha_electron'] == 1 - assert data['num_beta_electron'] == 1 - assert data['charge'] == 0.0 - assert data['spin_multi'] == 1 - assert_allclose(data['energy'], -1.179998789924e+00) - assert_allclose(data['virial_ratio'], 2.036441983763e+00) - assert_allclose(data['nuc_viral'], 1.008787649881e-08) - assert_allclose(data['full_virial_ratio'], 2.036441992623e+00) - assert_equal(data['nuclear_names'], ['H1', 'H2']) - assert_equal(data['atnums'], np.array([1, 1])) - assert_equal(data['mo_spins'], np.array(['Alpha'] * 28 + ['Beta'] * 28).T) - coords = np.array([[0., 0., 0.7019452462164], [0., 0., -0.7019452462164]]) - assert_allclose(data['atcoords'], coords) - assert_allclose(data['centers'], np.array([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])) - assert_allclose(data['types'], np.array([ - 1, 1, 1, 1, 1, 2, 3, 4, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 1, 1, 1, 1, 1, 2, 3, 4, 2, 3, 4, 5, 6, 7, 8, 9, 10])) - assert_allclose(data['exponents'], np.array([ - 3.387000000000e+01, 5.095000000000e+00, 1.159000000000e+00, - 3.258000000000e-01, 1.027000000000e-01, 1.407000000000e+00, - 1.407000000000e+00, 1.407000000000e+00, 3.880000000000e-01, - 3.880000000000e-01, 3.880000000000e-01, 1.057000000000e+00, - 1.057000000000e+00, 1.057000000000e+00, 1.057000000000e+00, - 1.057000000000e+00, 1.057000000000e+00, 3.387000000000e+01, - 5.095000000000e+00, 1.159000000000e+00, 3.258000000000e-01, - 1.027000000000e-01, 1.407000000000e+00, 1.407000000000e+00, - 1.407000000000e+00, 3.880000000000e-01, 3.880000000000e-01, - 3.880000000000e-01, 1.057000000000e+00, 1.057000000000e+00, - 1.057000000000e+00, 1.057000000000e+00, 1.057000000000e+00, - 1.057000000000e+00])) - assert_allclose(data['mo_occs'], ([1] + [0] * 27) * 2) - assert_allclose(data['mo_energies'], np.array([ - -4.340830854172e-01, 5.810590098068e-02, 1.957476339319e-01, - 4.705943952631e-01, 5.116003517961e-01, 5.116003517961e-01, - 9.109680450208e-01, 9.372078887497e-01, 9.372078887497e-01, - 1.367198523024e+00, 2.035656924620e+00, 2.093459617091e+00, - 2.882582109554e+00, 2.882582109559e+00, 3.079758295551e+00, - 3.079758295551e+00, 3.356387932344e+00, 3.600856684661e+00, - 3.600856684661e+00, 3.793185027287e+00, 3.793185027400e+00, - 3.807665977092e+00, 3.807665977092e+00, 4.345665616275e+00, - 5.386560784523e+00, 5.386560784523e+00, 5.448122593462e+00, - 6.522366660004e+00, -4.340830854172e-01, 5.810590098068e-02, - 1.957476339319e-01, 4.705943952631e-01, 5.116003517961e-01, - 5.116003517961e-01, 9.109680450208e-01, 9.372078887497e-01, - 9.372078887497e-01, 1.367198523024e+00, 2.035656924620e+00, - 2.093459617091e+00, 2.882582109554e+00, 2.882582109559e+00, - 3.079758295551e+00, 3.079758295551e+00, 3.356387932344e+00, - 3.600856684661e+00, 3.600856684661e+00, 3.793185027287e+00, - 3.793185027400e+00, 3.807665977092e+00, 3.807665977092e+00, - 4.345665616275e+00, 5.386560784523e+00, 5.386560784523e+00, - 5.448122593462e+00, 6.522366660004e+00])) - assert_allclose(data['atgradient'], [ - [9.744384163503e-17, -2.088844408785e-16, -7.185657679987e-09], - [-9.744384163503e-17, 2.088844408785e-16, 7.185657679987e-09]]) - assert data['mo_coeffs'].shape == (34, 56) - assert_allclose(data['mo_coeffs'][:, 0], [ - 5.054717669172e-02, 9.116391481072e-02, 1.344211391235e-01, - 8.321037376208e-02, 1.854203733451e-02, -5.552096650015e-17, - 1.685043781907e-17, -2.493514848195e-02, 5.367769875676e-18, - -8.640401342563e-21, -4.805966923740e-03, -3.124765025063e-04, - -3.124765025063e-04, 6.249530050126e-04, 6.560467295881e-16, - -8.389003686496e-17, 1.457172009403e-16, 5.054717669172e-02, - 9.116391481072e-02, 1.344211391235e-01, 8.321037376215e-02, - 1.854203733451e-02, 1.377812848830e-16, -5.365229184139e-18, - 2.493514848197e-02, -2.522774106094e-17, 2.213188439119e-17, - 4.805966923784e-03, -3.124765025186e-04, -3.124765025186e-04, - 6.249530050373e-04, -6.548275062740e-16, 4.865003740982e-17, - -1.099855647247e-16]) - assert_allclose(data['mo_coeffs'][2, 9], 1.779549601504e-02) - assert_allclose(data['mo_coeffs'][19, 14], -1.027984391469e-15) - assert_allclose(data['mo_coeffs'][26, 36], -5.700424557682e-01) + assert data["num_atoms"] == 2 + assert data["num_primitives"] == 34 + assert data["num_occ_mo"] == 56 + assert data["num_perturbations"] == 0 + assert data["num_electrons"] == 2 + assert data["num_alpha_electron"] == 1 + assert data["num_beta_electron"] == 1 + assert data["charge"] == 0.0 + assert data["spin_multi"] == 1 + assert_allclose(data["energy"], -1.179998789924e00) + assert_allclose(data["virial_ratio"], 2.036441983763e00) + assert_allclose(data["nuc_viral"], 1.008787649881e-08) + assert_allclose(data["full_virial_ratio"], 2.036441992623e00) + assert_equal(data["nuclear_names"], ["H1", "H2"]) + assert_equal(data["atnums"], np.array([1, 1])) + assert_equal(data["mo_spins"], np.array(["Alpha"] * 28 + ["Beta"] * 28).T) + coords = np.array([[0.0, 0.0, 0.7019452462164], [0.0, 0.0, -0.7019452462164]]) + assert_allclose(data["atcoords"], coords) + assert_allclose( + data["centers"], + np.array( + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + ] + ), + ) + assert_allclose( + data["types"], + np.array( + [ + 1, + 1, + 1, + 1, + 1, + 2, + 3, + 4, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 1, + 1, + 1, + 1, + 1, + 2, + 3, + 4, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + ] + ), + ) + assert_allclose( + data["exponents"], + np.array( + [ + 3.387000000000e01, + 5.095000000000e00, + 1.159000000000e00, + 3.258000000000e-01, + 1.027000000000e-01, + 1.407000000000e00, + 1.407000000000e00, + 1.407000000000e00, + 3.880000000000e-01, + 3.880000000000e-01, + 3.880000000000e-01, + 1.057000000000e00, + 1.057000000000e00, + 1.057000000000e00, + 1.057000000000e00, + 1.057000000000e00, + 1.057000000000e00, + 3.387000000000e01, + 5.095000000000e00, + 1.159000000000e00, + 3.258000000000e-01, + 1.027000000000e-01, + 1.407000000000e00, + 1.407000000000e00, + 1.407000000000e00, + 3.880000000000e-01, + 3.880000000000e-01, + 3.880000000000e-01, + 1.057000000000e00, + 1.057000000000e00, + 1.057000000000e00, + 1.057000000000e00, + 1.057000000000e00, + 1.057000000000e00, + ] + ), + ) + assert_allclose(data["mo_occs"], ([1] + [0] * 27) * 2) + assert_allclose( + data["mo_energies"], + np.array( + [ + -4.340830854172e-01, + 5.810590098068e-02, + 1.957476339319e-01, + 4.705943952631e-01, + 5.116003517961e-01, + 5.116003517961e-01, + 9.109680450208e-01, + 9.372078887497e-01, + 9.372078887497e-01, + 1.367198523024e00, + 2.035656924620e00, + 2.093459617091e00, + 2.882582109554e00, + 2.882582109559e00, + 3.079758295551e00, + 3.079758295551e00, + 3.356387932344e00, + 3.600856684661e00, + 3.600856684661e00, + 3.793185027287e00, + 3.793185027400e00, + 3.807665977092e00, + 3.807665977092e00, + 4.345665616275e00, + 5.386560784523e00, + 5.386560784523e00, + 5.448122593462e00, + 6.522366660004e00, + -4.340830854172e-01, + 5.810590098068e-02, + 1.957476339319e-01, + 4.705943952631e-01, + 5.116003517961e-01, + 5.116003517961e-01, + 9.109680450208e-01, + 9.372078887497e-01, + 9.372078887497e-01, + 1.367198523024e00, + 2.035656924620e00, + 2.093459617091e00, + 2.882582109554e00, + 2.882582109559e00, + 3.079758295551e00, + 3.079758295551e00, + 3.356387932344e00, + 3.600856684661e00, + 3.600856684661e00, + 3.793185027287e00, + 3.793185027400e00, + 3.807665977092e00, + 3.807665977092e00, + 4.345665616275e00, + 5.386560784523e00, + 5.386560784523e00, + 5.448122593462e00, + 6.522366660004e00, + ] + ), + ) + assert_allclose( + data["atgradient"], + [ + [9.744384163503e-17, -2.088844408785e-16, -7.185657679987e-09], + [-9.744384163503e-17, 2.088844408785e-16, 7.185657679987e-09], + ], + ) + assert data["mo_coeffs"].shape == (34, 56) + assert_allclose( + data["mo_coeffs"][:, 0], + [ + 5.054717669172e-02, + 9.116391481072e-02, + 1.344211391235e-01, + 8.321037376208e-02, + 1.854203733451e-02, + -5.552096650015e-17, + 1.685043781907e-17, + -2.493514848195e-02, + 5.367769875676e-18, + -8.640401342563e-21, + -4.805966923740e-03, + -3.124765025063e-04, + -3.124765025063e-04, + 6.249530050126e-04, + 6.560467295881e-16, + -8.389003686496e-17, + 1.457172009403e-16, + 5.054717669172e-02, + 9.116391481072e-02, + 1.344211391235e-01, + 8.321037376215e-02, + 1.854203733451e-02, + 1.377812848830e-16, + -5.365229184139e-18, + 2.493514848197e-02, + -2.522774106094e-17, + 2.213188439119e-17, + 4.805966923784e-03, + -3.124765025186e-04, + -3.124765025186e-04, + 6.249530050373e-04, + -6.548275062740e-16, + 4.865003740982e-17, + -1.099855647247e-16, + ], + ) + assert_allclose(data["mo_coeffs"][2, 9], 1.779549601504e-02) + assert_allclose(data["mo_coeffs"][19, 14], -1.027984391469e-15) + assert_allclose(data["mo_coeffs"][26, 36], -5.700424557682e-01) def test_load_data_wfx_water(): """Test load_data_wfx with water_sto3g_hf.wfx.""" - data = helper_load_data_wfx('water_sto3g_hf.wfx') + data = helper_load_data_wfx("water_sto3g_hf.wfx") # check loaded data - assert data['title'] == 'H2O HF/STO-3G//HF/STO-3G' - assert data['keywords'] == 'GTO' - assert data['model_name'] == 'Restricted HF' - assert data['num_atoms'] == 3 - assert data['num_primitives'] == 21 - assert data['num_occ_mo'] == 5 - assert data['num_perturbations'] == 0 - assert data['num_electrons'] == 10 - assert data['num_alpha_electron'] == 5 - assert data['num_beta_electron'] == 5 - assert data['charge'] == 0.0 + assert data["title"] == "H2O HF/STO-3G//HF/STO-3G" + assert data["keywords"] == "GTO" + assert data["model_name"] == "Restricted HF" + assert data["num_atoms"] == 3 + assert data["num_primitives"] == 21 + assert data["num_occ_mo"] == 5 + assert data["num_perturbations"] == 0 + assert data["num_electrons"] == 10 + assert data["num_alpha_electron"] == 5 + assert data["num_beta_electron"] == 5 + assert data["charge"] == 0.0 # assert_equal(num_spin_multi, np.array(None)) - assert_allclose(data['energy'], -7.49659011707870E+001) - assert_allclose(data['virial_ratio'], 2.00599838291596E+000) + assert_allclose(data["energy"], -7.49659011707870e001) + assert_allclose(data["virial_ratio"], 2.00599838291596e000) # assert_allclose(data['nuclear_virial'], np.array(None)) - assert_allclose(data['full_virial_ratio'], 2.00600662884992E+000) - assert_equal(data['nuclear_names'], ['O1', 'H2', 'H3']) - assert_equal(data['atnums'], np.array([8, 1, 1])) - assert_equal(data['mo_spins'], np.array(['Alpha and Beta'] * 5).T) - assert_allclose(data['atcoords'], [ - [0.00000000000000, 0.00000000000000, 2.40242907000000E-1], - [0.00000000000000, 1.43244242000000, -9.60971627000000E-1], - [-1.75417809000000e-16, -1.43244242000000, -9.60971627000000E-1]]) - assert_allclose(data['centers'], np.array([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3])) - assert_allclose(data['types'], np.array( - [1, 1, 1, 1, 1, 1, 2, 3, 4, 2, 3, 4, 2, 3, 4, 1, 1, 1, 1, 1, 1])) - assert_allclose(data['exponents'], np.array([ - 1.30709321000000E+002, 2.38088661000000E+001, 6.44360831000000E+000, - 5.03315132000000E+000, 1.16959612000000E+000, 3.80388960000000E-001, - 5.03315132000000E+000, 5.03315132000000E+000, 5.03315132000000E+000, - 1.16959612000000E+000, 1.16959612000000E+000, 1.16959612000000E+000, - 3.80388960000000E-001, 3.80388960000000E-001, 3.80388960000000E-001, - 3.42525091000000E+000, 6.23913730000000E-001, 1.68855404000000E-001, - 3.42525091000000E+000, 6.23913730000000E-001, 1.68855404000000E-001])) - assert_allclose(data['mo_occs'], np.array([ - 2.00000000000000E+000, 2.00000000000000E+000, - 2.00000000000000E+000, 2.00000000000000E+000, - 2.00000000000000E+000])) - assert_allclose(data['mo_energies'], np.array([ - -2.02515479000000E+001, -1.25760928000000E+000, -5.93941119000000E-001, - -4.59728723000000E-001, -3.92618460000000E-001])) - assert_allclose(data['atgradient'][:2, :], [ - [6.09070231000000E-016, -5.55187875000000E-016, -2.29270172000000E-004], - [-2.46849911000000E-016, -1.18355659000000E-004, 1.14635086000000E-004]]) - assert data['mo_coeffs'].shape == (21, 5) - assert_allclose(data['mo_coeffs'][:, 0], [ - 4.22735025664585E+000, 4.08850914632625E+000, 1.27420971692421E+000, - -6.18883321546465E-003, 8.27806436882009E-003, 6.24757868903820E-003, - 0.00000000000000E+000, 0.00000000000000E+000, -6.97905144921135E-003, - 0.00000000000000E+000, 0.00000000000000E+000, -4.38861481239680E-003, - 0.00000000000000E+000, 0.00000000000000E+000, -6.95230322147800E-004, - -1.54680714141406E-003, -1.49600452906993E-003, -4.66239267760156E-004, - -1.54680714141406E-003, -1.49600452906993E-003, -4.66239267760156E-004 - ], rtol=0.0, atol=1.e-8) - assert_allclose(data['mo_coeffs'][1, 3], -4.27845789719456E-001) + assert_allclose(data["full_virial_ratio"], 2.00600662884992e000) + assert_equal(data["nuclear_names"], ["O1", "H2", "H3"]) + assert_equal(data["atnums"], np.array([8, 1, 1])) + assert_equal(data["mo_spins"], np.array(["Alpha and Beta"] * 5).T) + assert_allclose( + data["atcoords"], + [ + [0.00000000000000, 0.00000000000000, 2.40242907000000e-1], + [0.00000000000000, 1.43244242000000, -9.60971627000000e-1], + [-1.75417809000000e-16, -1.43244242000000, -9.60971627000000e-1], + ], + ) + assert_allclose( + data["centers"], np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3]) + ) + assert_allclose( + data["types"], np.array([1, 1, 1, 1, 1, 1, 2, 3, 4, 2, 3, 4, 2, 3, 4, 1, 1, 1, 1, 1, 1]) + ) + assert_allclose( + data["exponents"], + np.array( + [ + 1.30709321000000e002, + 2.38088661000000e001, + 6.44360831000000e000, + 5.03315132000000e000, + 1.16959612000000e000, + 3.80388960000000e-001, + 5.03315132000000e000, + 5.03315132000000e000, + 5.03315132000000e000, + 1.16959612000000e000, + 1.16959612000000e000, + 1.16959612000000e000, + 3.80388960000000e-001, + 3.80388960000000e-001, + 3.80388960000000e-001, + 3.42525091000000e000, + 6.23913730000000e-001, + 1.68855404000000e-001, + 3.42525091000000e000, + 6.23913730000000e-001, + 1.68855404000000e-001, + ] + ), + ) + assert_allclose( + data["mo_occs"], + np.array( + [ + 2.00000000000000e000, + 2.00000000000000e000, + 2.00000000000000e000, + 2.00000000000000e000, + 2.00000000000000e000, + ] + ), + ) + assert_allclose( + data["mo_energies"], + np.array( + [ + -2.02515479000000e001, + -1.25760928000000e000, + -5.93941119000000e-001, + -4.59728723000000e-001, + -3.92618460000000e-001, + ] + ), + ) + assert_allclose( + data["atgradient"][:2, :], + [ + [6.09070231000000e-016, -5.55187875000000e-016, -2.29270172000000e-004], + [-2.46849911000000e-016, -1.18355659000000e-004, 1.14635086000000e-004], + ], + ) + assert data["mo_coeffs"].shape == (21, 5) + assert_allclose( + data["mo_coeffs"][:, 0], + [ + 4.22735025664585e000, + 4.08850914632625e000, + 1.27420971692421e000, + -6.18883321546465e-003, + 8.27806436882009e-003, + 6.24757868903820e-003, + 0.00000000000000e000, + 0.00000000000000e000, + -6.97905144921135e-003, + 0.00000000000000e000, + 0.00000000000000e000, + -4.38861481239680e-003, + 0.00000000000000e000, + 0.00000000000000e000, + -6.95230322147800e-004, + -1.54680714141406e-003, + -1.49600452906993e-003, + -4.66239267760156e-004, + -1.54680714141406e-003, + -1.49600452906993e-003, + -4.66239267760156e-004, + ], + rtol=0.0, + atol=1.0e-8, + ) + assert_allclose(data["mo_coeffs"][1, 3], -4.27845789719456e-001) def test_parse_wfx_missing_tag_h2o(): """Check that missing sections result in an exception.""" - with path('iodata.test.data', 'water_sto3g_hf.wfx') as fn_wfx: + with as_file(files("iodata.test.data").joinpath("water_sto3g_hf.wfx")) as fn_wfx: lit = LineIterator(fn_wfx) with pytest.raises(IOError) as error: parse_wfx(lit, required_tags=["<Foo Bar>"]) @@ -362,105 +611,147 @@ def test_parse_wfx_missing_tag_h2o(): def test_load_data_wfx_h2o_error(): """Check that sections without a closing tag result in an exception.""" - with path('iodata.test.data', 'h2o_error.wfx') as fn_wfx: - with pytest.raises(IOError) as error: - load_one(str(fn_wfx)) + with ( + as_file(files("iodata.test.data").joinpath("h2o_error.wfx")) as fn_wfx, + pytest.raises(IOError) as error, + ): + load_one(str(fn_wfx)) assert str(error.value).endswith( - "Expecting line </Number of Nuclei> but got </Number of Primitives>.") + "Expecting line </Number of Nuclei> but got </Number of Primitives>." + ) def test_load_truncated_h2o(tmpdir): """Check that a truncated file raises an exception.""" - with path('iodata.test.data', 'water_sto3g_hf.wfx') as fn_wfx: - with truncated_file(str(fn_wfx), 152, 0, tmpdir) as fn_truncated: - with pytest.raises(IOError) as error: - load_one(str(fn_truncated)) + with ( + as_file(files("iodata.test.data").joinpath("water_sto3g_hf.wfx")) as fn_wfx, + truncated_file(str(fn_wfx), 152, 0, tmpdir) as fn_truncated, + pytest.raises(IOError) as error, + ): + load_one(str(fn_truncated)) assert str(error.value).endswith( - "Section <Full Virial Ratio, -(V - W)/T> is not closed at end of file.") + "Section <Full Virial Ratio, -(V - W)/T> is not closed at end of file." + ) def test_load_one_h2o(): """Test load_one with h2o sto-3g WFX input.""" - with path('iodata.test.data', 'water_sto3g_hf.wfx') as file_wfx: + with as_file(files("iodata.test.data").joinpath("water_sto3g_hf.wfx")) as file_wfx: mol = load_one(str(file_wfx)) - assert_allclose(mol.atcoords, - np.array([[0.00000000e+00, 0.00000000e+00, 2.40242907e-01], - [0.00000000e+00, 1.43244242e+00, -9.60971627e-01], - [-1.75417809e-16, -1.43244242e+00, -9.60971627e-01]]), - rtol=0., atol=1.e-6) - assert_allclose(mol.atgradient, - np.array([[6.09070231e-16, -5.55187875e-16, -2.29270172e-04], - [-2.46849911e-16, -1.18355659e-04, 1.14635086e-04], - [-3.62220320e-16, 1.18355659e-04, 1.14635086e-04]]), - rtol=0, atol=1.e-6) + assert_allclose( + mol.atcoords, + np.array( + [ + [0.00000000e00, 0.00000000e00, 2.40242907e-01], + [0.00000000e00, 1.43244242e00, -9.60971627e-01], + [-1.75417809e-16, -1.43244242e00, -9.60971627e-01], + ] + ), + rtol=0.0, + atol=1.0e-6, + ) + assert_allclose( + mol.atgradient, + np.array( + [ + [6.09070231e-16, -5.55187875e-16, -2.29270172e-04], + [-2.46849911e-16, -1.18355659e-04, 1.14635086e-04], + [-3.62220320e-16, 1.18355659e-04, 1.14635086e-04], + ] + ), + rtol=0, + atol=1.0e-6, + ) assert_equal(mol.atnums, np.array([8, 1, 1])) assert mol.mo.coeffs.shape == (21, 5) - assert_allclose(mol.energy, -74.965901170787, rtol=0, atol=1.e-6) - assert mol.extra['keywords'] == 'GTO' - assert mol.extra['virial_ratio'] == 2.00599838291596 + assert_allclose(mol.energy, -74.965901170787, rtol=0, atol=1.0e-6) + assert mol.extra["keywords"] == "GTO" + assert mol.extra["virial_ratio"] == 2.00599838291596 assert mol.mo.kind == "restricted" - assert_allclose(mol.mo.energies, - np.array([-20.2515479, -1.25760928, - -0.59394112, -0.45972872, - -0.39261846]), rtol=0, atol=1.e-6) - assert_equal(mol.mo.occs, np.array([2., 2., 2., 2., 2.])) - assert_equal(mol.mo.occsa, np.array([1., 1., 1., 1., 1.])) + assert_allclose( + mol.mo.energies, + np.array([-20.2515479, -1.25760928, -0.59394112, -0.45972872, -0.39261846]), + rtol=0, + atol=1.0e-6, + ) + assert_equal(mol.mo.occs, np.array([2.0, 2.0, 2.0, 2.0, 2.0])) + assert_equal(mol.mo.occsa, np.array([1.0, 1.0, 1.0, 1.0, 1.0])) assert mol.mo.spinpol == 0.0 assert mol.mo.nbasis == 21 assert mol.obasis.nbasis == 21 - assert mol.obasis.primitive_normalization == 'L2' + assert mol.obasis.primitive_normalization == "L2" assert [shell.icenter for shell in mol.obasis.shells] == [0] * 9 + [1] * 3 + [2] * 3 - assert [shell.kinds for shell in mol.obasis.shells] == [['c']] * 15 - assert_allclose([shell.exponents for shell in mol.obasis.shells[:6]], - [[130.709321], [23.8088661], [6.44360831], [5.03315132], - [1.16959612], [0.38038896]]) - assert_allclose([shell.exponents for shell in mol.obasis.shells[6:9]], - [[5.03315132], [1.16959612], [0.38038896]]) - assert_allclose([shell.exponents for shell in mol.obasis.shells[9:15]], - [[3.42525091], [0.62391373], [0.168855404], - [3.42525091], [0.62391373], [0.168855404]]) + assert [shell.kinds for shell in mol.obasis.shells] == [["c"]] * 15 + assert_allclose( + [shell.exponents for shell in mol.obasis.shells[:6]], + [[130.709321], [23.8088661], [6.44360831], [5.03315132], [1.16959612], [0.38038896]], + ) + assert_allclose( + [shell.exponents for shell in mol.obasis.shells[6:9]], + [[5.03315132], [1.16959612], [0.38038896]], + ) + assert_allclose( + [shell.exponents for shell in mol.obasis.shells[9:15]], + [[3.42525091], [0.62391373], [0.168855404], [3.42525091], [0.62391373], [0.168855404]], + ) assert_allclose([shell.coeffs for shell in mol.obasis.shells], [[[1]]] * 15) assert mol.obasis_name is None - assert mol.title == 'H2O HF/STO-3G//HF/STO-3G' + assert mol.title == "H2O HF/STO-3G//HF/STO-3G" # check orthonormal mo olp = compute_overlap(mol.obasis, mol.atcoords) - check_orthonormal(mol.mo.coeffsa, olp, 1.e-5) + check_orthonormal(mol.mo.coeffsa, olp, 1.0e-5) def test_load_one_h2(): """Test load_one with h2 ub3lyp_ccpvtz WFX input.""" - with path('iodata.test.data', 'h2_ub3lyp_ccpvtz.wfx') as file_wfx: + with as_file(files("iodata.test.data").joinpath("h2_ub3lyp_ccpvtz.wfx")) as file_wfx: mol = load_one(str(file_wfx)) - assert_allclose(mol.atcoords, - np.array([[0.0, 0.0, 0.7019452462164], - [0.0, 0.0, -0.7019452462164]]), - rtol=0, atol=1.e-6) - assert_allclose(mol.atgradient, - np.array([[9.74438416e-17, -2.08884441e-16, -7.18565768e-09], - [-9.74438416e-17, 2.08884441e-16, 7.18565768e-09]]), - rtol=0, atol=1.e-6) + assert_allclose( + mol.atcoords, + np.array([[0.0, 0.0, 0.7019452462164], [0.0, 0.0, -0.7019452462164]]), + rtol=0, + atol=1.0e-6, + ) + assert_allclose( + mol.atgradient, + np.array( + [ + [9.74438416e-17, -2.08884441e-16, -7.18565768e-09], + [-9.74438416e-17, 2.08884441e-16, 7.18565768e-09], + ] + ), + rtol=0, + atol=1.0e-6, + ) assert_equal(mol.atnums, np.array([1, 1])) - assert_allclose(mol.energy, -1.179998789924, rtol=0, atol=1.e-6) - assert mol.extra['keywords'] == 'GTO' - assert mol.extra['num_perturbations'] == 0 + assert_allclose(mol.energy, -1.179998789924, rtol=0, atol=1.0e-6) + assert mol.extra["keywords"] == "GTO" + assert mol.extra["num_perturbations"] == 0 assert mol.mo.coeffs.shape == (34, 56) - assert_allclose(mol.mo.energies[:7], - np.array([-0.43408309, 0.0581059, 0.19574763, 0.4705944, - 0.51160035, 0.51160035, 0.91096805]), rtol=0, atol=1.e-6) + assert_allclose( + mol.mo.energies[:7], + np.array( + [-0.43408309, 0.0581059, 0.19574763, 0.4705944, 0.51160035, 0.51160035, 0.91096805] + ), + rtol=0, + atol=1.0e-6, + ) assert mol.mo.occs.sum() == 2.0 assert mol.mo.occsa.sum() == 1.0 assert mol.mo.spinpol == 0.0 assert mol.mo.nbasis == 34 assert mol.mo.kind == "unrestricted" assert mol.obasis.nbasis == 34 - assert mol.obasis.primitive_normalization == 'L2' + assert mol.obasis.primitive_normalization == "L2" assert [shell.icenter for shell in mol.obasis.shells] == [0] * 8 + [1] * 8 - assert [shell.kinds for shell in mol.obasis.shells] == [['c']] * 16 - assert_allclose([shell.exponents for shell in mol.obasis.shells], - 2 * [[33.87], [5.095], [1.159], [0.3258], [0.1027], [1.407], [0.388], [1.057]]) + assert [shell.kinds for shell in mol.obasis.shells] == [["c"]] * 16 + assert_allclose( + [shell.exponents for shell in mol.obasis.shells], + 2 * [[33.87], [5.095], [1.159], [0.3258], [0.1027], [1.407], [0.388], [1.057]], + ) assert_allclose([shell.coeffs for shell in mol.obasis.shells], [[[1]]] * 16) assert mol.obasis_name is None - assert mol.title == 'h2 ub3lyp/cc-pvtz opt-stable-freq' + assert mol.title == "h2 ub3lyp/cc-pvtz opt-stable-freq" # check orthonormal mo olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffsa, olp, 1e-5) @@ -468,22 +759,45 @@ def test_load_one_h2(): def test_load_one_lih_cation_cisd(): - with path('iodata.test.data', 'lih_cation_cisd.wfx') as file_wfx: + with as_file(files("iodata.test.data").joinpath("lih_cation_cisd.wfx")) as file_wfx: mol = load_one(str(file_wfx)) # check number of orbitals and occupation numbers - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert mol.mo.norba == 11 assert mol.mo.norbb == 11 assert mol.mo.norb == 22 - assert_allclose(mol.mo.occsa, [ - 9.99999999804784E-1, 9.99999998539235E-1, 2.26431690664012E-10, - 5.38480519435475E-11, 0.0, 0.0, 0.0, 0.0, -5.97822590358596E-13, - -6.01428138426375E-12, -9.12834417514434E-12]) - assert_allclose(mol.mo.occsb, [ - 9.99999997403326E-1, 6.03380142010587E-11, 4.36865874834240E-12, - 4.14106040987552E-13, 0.0, 0.0, 0.0, 0.0, -5.69902692731031E-13, - -1.60216470544366E-11, -3.25430470734432E-10, - ]) + assert_allclose( + mol.mo.occsa, + [ + 9.99999999804784e-1, + 9.99999998539235e-1, + 2.26431690664012e-10, + 5.38480519435475e-11, + 0.0, + 0.0, + 0.0, + 0.0, + -5.97822590358596e-13, + -6.01428138426375e-12, + -9.12834417514434e-12, + ], + ) + assert_allclose( + mol.mo.occsb, + [ + 9.99999997403326e-1, + 6.03380142010587e-11, + 4.36865874834240e-12, + 4.14106040987552e-13, + 0.0, + 0.0, + 0.0, + 0.0, + -5.69902692731031e-13, + -1.60216470544366e-11, + -3.25430470734432e-10, + ], + ) # check orthonormal mo olp = compute_overlap(mol.obasis, mol.atcoords) check_orthonormal(mol.mo.coeffsa, olp, 1e-5) @@ -491,10 +805,10 @@ def test_load_one_lih_cation_cisd(): def test_load_one_lih_cation_uhf(): - with path('iodata.test.data', 'lih_cation_uhf.wfx') as file_wfx: + with as_file(files("iodata.test.data").joinpath("lih_cation_uhf.wfx")) as file_wfx: mol = load_one(str(file_wfx)) # check number of orbitals and occupation numbers - assert mol.mo.kind == 'unrestricted' + assert mol.mo.kind == "unrestricted" assert mol.mo.norba == 2 assert mol.mo.norbb == 1 assert mol.mo.norb == 3 @@ -507,10 +821,10 @@ def test_load_one_lih_cation_uhf(): def test_load_one_lih_cation_rohf(): - with path('iodata.test.data', 'lih_cation_rohf.wfx') as file_wfx: + with as_file(files("iodata.test.data").joinpath("lih_cation_rohf.wfx")) as file_wfx: mol = load_one(str(file_wfx)) # check number of orbitals and occupation numbers - assert mol.mo.kind == 'restricted' + assert mol.mo.kind == "restricted" assert mol.mo.norba == 2 assert mol.mo.norbb == 2 assert mol.mo.norb == 2 diff --git a/iodata/test/test_xyz.py b/iodata/test/test_xyz.py index 4b768ee27..c0b3afe39 100644 --- a/iodata/test/test_xyz.py +++ b/iodata/test/test_xyz.py @@ -21,59 +21,69 @@ import os import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_allclose, assert_equal -from ..api import load_one, load_many, dump_one, dump_many -from ..utils import angstrom +from ..api import dump_many, dump_one, load_many, load_one from ..formats.xyz import DEFAULT_ATOM_COLUMNS +from ..utils import angstrom + try: - from importlib_resources import path + from importlib_resources import as_file, files except ImportError: - from importlib.resources import path + from importlib.resources import as_file, files def test_load_water_number(): # test xyz with atomic numbers - with path('iodata.test.data', 'water_number.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("water_number.xyz")) as fn_xyz: mol = load_one(str(fn_xyz)) check_water(mol) def test_load_water_element(): # test xyz file with atomic symbols - with path('iodata.test.data', 'water_element.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("water_element.xyz")) as fn_xyz: mol = load_one(str(fn_xyz)) check_water(mol) def check_water(mol): """Test some things on a water file.""" - assert mol.title == 'Water' + assert mol.title == "Water" assert_equal(mol.atnums, [1, 8, 1]) # check bond length print(np.linalg.norm(mol.atcoords[0] - mol.atcoords[2]) / angstrom) - assert_allclose(np.linalg.norm( - mol.atcoords[0] - mol.atcoords[1]) / angstrom, 0.960, atol=1.e-5) - assert_allclose(np.linalg.norm( - mol.atcoords[2] - mol.atcoords[1]) / angstrom, 0.960, atol=1.e-5) - assert_allclose(np.linalg.norm( - mol.atcoords[0] - mol.atcoords[2]) / angstrom, 1.568, atol=1.e-3) - - -FCC_ATOM_COLUMNS = DEFAULT_ATOM_COLUMNS + [ + assert_allclose( + np.linalg.norm(mol.atcoords[0] - mol.atcoords[1]) / angstrom, 0.960, atol=1.0e-5 + ) + assert_allclose( + np.linalg.norm(mol.atcoords[2] - mol.atcoords[1]) / angstrom, 0.960, atol=1.0e-5 + ) + assert_allclose( + np.linalg.norm(mol.atcoords[0] - mol.atcoords[2]) / angstrom, 1.568, atol=1.0e-3 + ) + + +FCC_ATOM_COLUMNS = [ + *DEFAULT_ATOM_COLUMNS, # Storing the atomic numbers as zs in the extras attribute makes sense # for testing. ("extra", "zs", (), int, int, "{:2d}".format), # Note that in IOData, the energy gradient is stored, which contains the # negative forces. - ("atgradient", None, (3,), float, - (lambda word: -float(word)), - (lambda value: "{:15.10f}".format(-value))) + ( + "atgradient", + None, + (3,), + float, + (lambda word: -float(word)), + (lambda value: f"{-value:15.10f}"), + ), ] def test_load_fcc_columns(): - with path('iodata.test.data', 'al_fcc.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("al_fcc.xyz")) as fn_xyz: mol = load_one(str(fn_xyz), atom_columns=FCC_ATOM_COLUMNS) assert "zs" in mol.extra assert mol.extra["zs"].dtype == int @@ -93,7 +103,7 @@ def check_load_dump_consistency(tmpdir, fn, atom_columns=None): else: mol0 = load_one(str(fn)) # write xyz file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test.xyz') + fn_tmp = os.path.join(tmpdir, "test.xyz") dump_one(mol0, fn_tmp, atom_columns=atom_columns) mol1 = load_one(fn_tmp, atom_columns=atom_columns) # check two xyz files @@ -108,31 +118,31 @@ def check_load_dump_consistency(tmpdir, fn, atom_columns=None): def test_load_dump_consistency(tmpdir): - with path('iodata.test.data', 'ch3_hf_sto3g.fchk') as fn_fchk: + with as_file(files("iodata.test.data").joinpath("ch3_hf_sto3g.fchk")) as fn_fchk: check_load_dump_consistency(tmpdir, str(fn_fchk)) def test_dump_xyz_water_element(tmpdir): - with path('iodata.test.data', 'water_element.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("water_element.xyz")) as fn_xyz: check_load_dump_consistency(tmpdir, str(fn_xyz)) def test_dump_xyz_water_number(tmpdir): - with path('iodata.test.data', 'water_number.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("water_number.xyz")) as fn_xyz: check_load_dump_consistency(tmpdir, str(fn_xyz)) def test_dump_xyz_fcc(tmpdir): - with path('iodata.test.data', 'al_fcc.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("al_fcc.xyz")) as fn_xyz: check_load_dump_consistency(tmpdir, str(fn_xyz), FCC_ATOM_COLUMNS) def test_load_many(): - with path('iodata.test.data', 'water_trajectory.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("water_trajectory.xyz")) as fn_xyz: mols = list(load_many(str(fn_xyz))) assert len(mols) == 5 for imol, mol in enumerate(mols): - assert mol.title == "Frame {}".format(imol) + assert mol.title == f"Frame {imol}" assert_equal(mol.atnums, [8, 1, 1]) assert mol.atcoords.shape == (3, 3) assert_allclose(mols[0].atcoords[2] / angstrom, [2.864329, 0.114369, 3.3635]) @@ -141,7 +151,7 @@ def test_load_many(): def test_load_many_dataset_emptylines(): - with path('iodata.test.data', 'dataset_blanklines.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("dataset_blanklines.xyz")) as fn_xyz: mols = list(load_many(str(fn_xyz))) assert len(mols) == 3 # Check 1st item @@ -165,14 +175,14 @@ def test_load_many_dataset_emptylines(): def test_load_dump_many_consistency(tmpdir): - with path('iodata.test.data', 'water_trajectory.xyz') as fn_xyz: + with as_file(files("iodata.test.data").joinpath("water_trajectory.xyz")) as fn_xyz: mols0 = list(load_many(str(fn_xyz))) # write xyz file in a temporary folder & then read it - fn_tmp = os.path.join(tmpdir, 'test') - dump_many(mols0, fn_tmp, fmt='xyz') - mols1 = list(load_many(fn_tmp, fmt='xyz')) + fn_tmp = os.path.join(tmpdir, "test") + dump_many(mols0, fn_tmp, fmt="xyz") + mols1 = list(load_many(fn_tmp, fmt="xyz")) assert len(mols0) == len(mols1) for mol0, mol1 in zip(mols0, mols1): assert mol0.title == mol1.title assert_equal(mol0.atnums, mol1.atnums) - assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.e-5) + assert_allclose(mol0.atcoords, mol1.atcoords, atol=1.0e-5) diff --git a/iodata/utils.py b/iodata/utils.py index 10156c8da..8cf238e85 100644 --- a/iodata/utils.py +++ b/iodata/utils.py @@ -18,37 +18,42 @@ # -- """Utility functions module.""" - -from typing import Tuple import warnings -import attr +import attrs import numpy as np import scipy.constants as spc +from numpy.typing import NDArray from scipy.linalg import eigh from .attrutils import validate_shape - -__all__ = ['LineIterator', 'Cube', 'set_four_index_element', 'volume', - 'derive_naturals', 'check_dm'] +__all__ = [ + "LineIterator", + "Cube", + "set_four_index_element", + "volume", + "derive_naturals", + "check_dm", + "strtobool", +] # The unit conversion factors below can be used as follows: # - Conversion to atomic units: distance = 5*angstrom # - Conversion from atomic units: print(distance/angstrom) -angstrom: float = spc.angstrom / spc.value(u'atomic unit of length') -electronvolt: float = 1 / spc.value(u'hartree-electron volt relationship') +angstrom: float = spc.angstrom / spc.value("atomic unit of length") +electronvolt: float = 1 / spc.value("hartree-electron volt relationship") # Unit conversion for Gromacs gro files -meter: float = 1 / spc.value(u'Bohr radius') +meter: float = 1 / spc.value("Bohr radius") nanometer: float = 1e-9 * meter -second: float = 1 / spc.value(u'atomic unit of time') +second: float = 1 / spc.value("atomic unit of time") picosecond: float = 1e-12 * second # atomic mass unit (not atomic unit of mass!) -amu: float = 1e-3 / (spc.value(u'electron mass') * spc.value(u'Avogadro constant')) -kcalmol: float = 1e3 * spc.calorie / spc.value('Avogadro constant') / spc.value('Hartree energy') -calmol: float = spc.calorie / spc.value('Avogadro constant') / spc.value('Hartree energy') -kjmol: float = 1e3 / spc.value('Avogadro constant') / spc.value('Hartree energy') +amu: float = 1e-3 / (spc.value("electron mass") * spc.value("Avogadro constant")) +kcalmol: float = 1e3 * spc.calorie / spc.value("Avogadro constant") / spc.value("Hartree energy") +calmol: float = spc.calorie / spc.value("Avogadro constant") / spc.value("Hartree energy") +kjmol: float = 1e3 / spc.value("Avogadro constant") / spc.value("Hartree energy") class FileFormatError(IOError): @@ -72,7 +77,7 @@ def __init__(self, filename: str): """ self.filename = filename - self.f = open(filename) + self.f = open(filename) # noqa self.lineno = 0 self.stack = [] @@ -84,12 +89,8 @@ def __iter__(self): def __next__(self): """Return the next line and increase the lineno attribute by one.""" - if self.stack: - line = self.stack.pop() - else: - line = next(self.f) self.lineno += 1 - return line + return self.stack.pop() if self.stack else next(self.f) def error(self, msg: str): """Raise an error while reading a file. @@ -100,7 +101,7 @@ def error(self, msg: str): Message to raise alongside filename and line number. """ - raise FileFormatError("{}:{} {}".format(self.filename, self.lineno, msg)) + raise FileFormatError(f"{self.filename}:{self.lineno} {msg}") def warn(self, msg: str): """Raise a warning while reading a file. @@ -111,8 +112,7 @@ def warn(self, msg: str): Message to raise alongside filename and line number. """ - warnings.warn("{}:{} {}".format(self.filename, self.lineno, msg), - FileFormatWarning, 2) + warnings.warn(f"{self.filename}:{self.lineno} {msg}", FileFormatWarning, stacklevel=2) def back(self, line): """Go one line back and decrease the lineno attribute by one.""" @@ -120,8 +120,7 @@ def back(self, line): self.lineno -= 1 -@attr.s(auto_attribs=True, slots=True, - on_setattr=[attr.setters.validate, attr.setters.convert]) +@attrs.define class Cube: """The volumetric data from a cube (or similar) file. @@ -138,18 +137,19 @@ class Cube: """ - origin: np.ndarray = attr.ib(validator=validate_shape(3)) - axes: np.ndarray = attr.ib(validator=validate_shape(3, 3)) - data: np.ndarray = attr.ib(validator=validate_shape(None, None, None)) + origin: NDArray = attrs.field(validator=validate_shape(3)) + axes: NDArray = attrs.field(validator=validate_shape(3, 3)) + data: NDArray = attrs.field(validator=validate_shape(None, None, None)) @property def shape(self): - """Shape of the rectangular grid.""" # noqa: D401 + """Shape of the rectangular grid.""" return self.data.shape -def set_four_index_element(four_index_object: np.ndarray, i: int, j: int, k: int, l: int, - value: float): +def set_four_index_element( + four_index_object: NDArray, i0: int, i1: int, i2: int, i3: int, value: float +): """Assign values to a four index object, account for 8-fold index symmetry. This function assumes physicists' notation. @@ -159,23 +159,23 @@ def set_four_index_element(four_index_object: np.ndarray, i: int, j: int, k: int four_index_object The four-index object. It will be written to. shape=(nbasis, nbasis, nbasis, nbasis), dtype=float - i, j, k, l + i0, i1, i2, i3 The indices to assign to. value The value of the matrix element to store. """ - four_index_object[i, j, k, l] = value - four_index_object[j, i, l, k] = value - four_index_object[k, j, i, l] = value - four_index_object[i, l, k, j] = value - four_index_object[k, l, i, j] = value - four_index_object[l, k, j, i] = value - four_index_object[j, k, l, i] = value - four_index_object[l, i, j, k] = value + four_index_object[i0, i1, i2, i3] = value + four_index_object[i1, i0, i3, i2] = value + four_index_object[i2, i1, i0, i3] = value + four_index_object[i0, i3, i2, i1] = value + four_index_object[i2, i3, i0, i1] = value + four_index_object[i3, i2, i1, i0] = value + four_index_object[i1, i2, i3, i0] = value + four_index_object[i3, i0, i1, i2] = value -def volume(cellvecs: np.ndarray) -> float: +def volume(cellvecs: NDArray) -> float: """Calculate the (generalized) cell volume. Parameters @@ -201,7 +201,7 @@ def volume(cellvecs: np.ndarray) -> float: raise ValueError("Argument cellvecs should be of shape (x, 3), where x is in {1, 2, 3}") -def derive_naturals(dm: np.ndarray, overlap: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: +def derive_naturals(dm: NDArray, overlap: NDArray) -> tuple[NDArray, NDArray]: """Derive natural orbitals from a given density matrix. Parameters @@ -228,12 +228,12 @@ def derive_naturals(dm: np.ndarray, overlap: np.ndarray) -> Tuple[np.ndarray, np # Diagonalize and compute eigenvalues evals, evecs = eigh(sds, overlap) coeffs = np.zeros_like(overlap) - coeffs = evecs[:, :coeffs.shape[1]] + coeffs = evecs[:, : coeffs.shape[1]] occs = evals return coeffs, occs -def check_dm(dm: np.ndarray, overlap: np.ndarray, eps: float = 1e-4, occ_max: float = 1.0): +def check_dm(dm: NDArray, overlap: NDArray, eps: float = 1e-4, occ_max: float = 1.0): """Check if the density matrix has eigenvalues in the proper range. Parameters @@ -258,8 +258,36 @@ def check_dm(dm: np.ndarray, overlap: np.ndarray, eps: float = 1e-4, occ_max: fl # construct natural orbitals occupations = derive_naturals(dm, overlap)[1] if occupations.min() < -eps: - raise ValueError('The density matrix has eigenvalues considerably smaller than ' - 'zero. error=%e' % (occupations.min())) + raise ValueError( + "The density matrix has eigenvalues considerably smaller than " + f"zero. error={occupations.min():e}" + ) if occupations.max() > occ_max + eps: - raise ValueError('The density matrix has eigenvalues considerably larger than ' - 'max. error=%e' % (occupations.max() - 1)) + raise ValueError( + "The density matrix has eigenvalues considerably larger than " + "max. error=%e" % (occupations.max() - 1) + ) + + +STRTOBOOL = { + "y": True, + "yes": True, + "t": True, + "true": True, + "on": True, + "1": True, + "n": False, + "no": False, + "f": False, + "false": False, + "off": False, + "0": False, +} + + +def strtobool(value: str) -> bool: + """Interpret string as a boolean.""" + result = STRTOBOOL.get(value.lower()) + if result is None: + raise ValueError(f"'{value}' cannot be converted to boolean") + return result diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..c95057402 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,81 @@ +[build-system] +requires = ["setuptools>=65.0", "setuptools_scm[toml]>=7.1.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "qc-iodata" +authors = [ + { name="HORTON-ChemTools Dev Team", email="horton.chemtools@gmail.com" }, +] +description = "Python Input and Output Library for Quantum Chemistry" +readme = "README.rst" +license = {file = "LICENSE"} +requires-python = ">=3.9" +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Physics", + "Topic :: Scientific/Engineering :: Chemistry", +] +dependencies = [ + "numpy>=1.0", + "scipy", + "attrs>=20.1.0", +] +dynamic = ["version"] + +[project.optional-dependencies] +dev = ["pytest", "pytest-xdist", "sphinx", "sphinx_autodoc_typehints", "sphinx-rtd-theme"] + +[project.urls] +Documentation = "https://iodata.readthedocs.io/en/latest/" +Issues = "https://github.com/theochem/iodata/issues/" +Source = "https://github.com/theochem/iodata/" + +[project.scripts] +iodata-convert = "iodata.__main__:main" + +[tool.pytest.ini_options] +addopts = "-n auto" + +[tool.setuptools] +packages = ["iodata"] + +[tool.setuptools_scm] +write_to = "iodata/_version.py" +version_scheme = "post-release" +local_scheme = "no-local-version" + +[tool.ruff] +line-length = 100 +target-version = "py39" + +[tool.ruff.lint] +select = [ + "A", "B", "BLE", "C4", "E", "EXE", "F", "I", "ICN", "ISC", "N", "NPY", "PERF", "PIE", + "PL", "PT", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TRY", "UP", "W" +] +ignore = [ + "PLR0904", # https://docs.astral.sh/ruff/rules/too-many-public-methods/ + "PLR0911", # https://docs.astral.sh/ruff/rules/too-many-return-statements/ + "PLR0912", # https://docs.astral.sh/ruff/rules/too-many-branches/ + "PLR0913", # https://docs.astral.sh/ruff/rules/too-many-arguments/ + "PLR0914", # https://docs.astral.sh/ruff/rules/too-many-locals/ + "PLR0915", # https://docs.astral.sh/ruff/rules/too-many-statements/ + "PLR0916", # https://docs.astral.sh/ruff/rules/too-many-boolean-expressions/ + "PLR0917", # https://docs.astral.sh/ruff/rules/too-many-positional/ + "PLR2004", # https://docs.astral.sh/ruff/rules/magic-value-comparison/ + "PT011", # https://docs.astral.sh/ruff/rules/pytest-raises-too-broad/ + "TRY003", # https://docs.astral.sh/ruff/rules/raise-vanilla-args/ +] diff --git a/readthedocs.yml b/readthedocs.yml deleted file mode 100644 index 57ce308c4..000000000 --- a/readthedocs.yml +++ /dev/null @@ -1,5 +0,0 @@ -build: - image: latest - -conda: - file: environment.yml diff --git a/setup.py b/setup.py deleted file mode 100755 index 128d9e85b..000000000 --- a/setup.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 -# IODATA is an input and output module for quantum chemistry. -# Copyright (C) 2011-2019 The IODATA Development Team -# -# This file is part of IODATA. -# -# IODATA is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# IODATA is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, see <http://www.gnu.org/licenses/> -# -- -"""Installation script for IOData. - -Directly calling this script is only needed by IOData developers in special -circumstances. End users are recommended to install IOData with pip or conda. -Developers are recommended to use Roberto. -""" - - -import os - -from setuptools import setup - - -def get_version_info(): - """Read __version__ and DEV_CLASSIFIER from version.py, using exec, not import.""" - fn_version = os.path.join("iodata", "_version.py") - if os.path.isfile(fn_version): - myglobals = {} - with open(fn_version, "r") as f: - exec(f.read(), myglobals) # pylint: disable=exec-used - return myglobals["__version__"], myglobals["DEV_CLASSIFIER"] - return "0.0.0.post0", "Development Status :: 2 - Pre-Alpha" - - -def get_readme(): - """Load README.rst for display on PyPI.""" - with open('README.rst') as fhandle: - return fhandle.read() - - -VERSION, DEV_CLASSIFIER = get_version_info() - -setup( - name='qc-iodata', - version=VERSION, - description='Python Input and Output Library for Quantum Chemistry.', - long_description=get_readme(), - author='HORTON-ChemTools Dev Team', - author_email='horton.chemtools@gmail.com', - url='https://github.com/theochem/iodata', - package_dir={'iodata': 'iodata'}, - packages=['iodata', 'iodata.formats', 'iodata.inputs', 'iodata.test', 'iodata.test.data'], - include_package_data=True, - entry_points={ - 'console_scripts': ['iodata-convert = iodata.__main__:main'] - }, - classifiers=[ - DEV_CLASSIFIER, - 'Environment :: Console', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python :: 3', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Scientific/Engineering :: Chemistry', - 'Intended Audience :: Science/Research', - ], - setup_requires=['numpy>=1.0'], - install_requires=['numpy>=1.0', 'scipy', 'attrs>=20.1.0', - 'importlib_resources; python_version < "3.7"'], -) diff --git a/tools/conda.recipe/meta.yaml b/tools/conda.recipe/meta.yaml deleted file mode 100644 index 21c7eb629..000000000 --- a/tools/conda.recipe/meta.yaml +++ /dev/null @@ -1,43 +0,0 @@ -package: - version: "{{ PROJECT_VERSION }}" - name: 'iodata' - -source: - path: ../../ - -build: - number: 0 - noarch: python - script: "{{ PYTHON }} -m pip install . --no-deps" - entry_points: - - iodata-convert = iodata.__main__:main - -requirements: - host: - - python - - numpy >=1.0 - - setuptools - run: - - python - - scipy - - attrs >=20.1.0 - - importlib_resources # [py<37] - -test: - requires: - - python - - pytest - - pytest-xdist - imports: - - iodata - commands: - - pytest --pyargs iodata -v -n auto - -about: - description: Input and output module for quantum chemistry - home: https://iodata.readthedocs.io/en/latest - doc_url: https://iodata.readthedocs.io/en/latest - dev_url: https://github.com/theochem/iodata - license: GNU Version 3 - license_family: GPL - license_file: LICENSE diff --git a/tools/harmonics.py b/tools/harmonics.py old mode 100644 new mode 100755 index 3c22f57b3..7c4922349 --- a/tools/harmonics.py +++ b/tools/harmonics.py @@ -19,7 +19,6 @@ # -- """Build transformation matrices from Cartesian to pure basis functions.""" - import argparse import numpy as np @@ -31,7 +30,7 @@ def main(): args = parse_args() # Build the bare transformation matrices, without normalization - tfs = get_bare_transforms(args.lmax) + tfs = get_bare_transforms(args.ellmax) if args.norm == "none": # No changes needed. @@ -57,17 +56,17 @@ def parse_args(): parser = argparse.ArgumentParser(prog="harmonics") parser.add_argument("norm", help="Normalization convention", choices=["none", "L2"]) parser.add_argument("format", help="Output format", choices=["latex", "python"]) - parser.add_argument("lmax", help="Maximum angular momentum", type=int) + parser.add_argument("ellmax", help="Maximum angular momentum", type=int) return parser.parse_args() -def get_bare_transforms(lmax: int): - """Build transformation matrices up to lmax, without normalization constants. +def get_bare_transforms(ellmax: int): + """Build transformation matrices up to ellmax, without normalization constants. Parameters ---------- - lmax - Matrices for l going from 0 to lmax (inclusive) are returned. + ellmax + Matrices for ell going from 0 to ellmax (inclusive) are returned. Returns ------- @@ -77,41 +76,41 @@ def get_bare_transforms(lmax: int): """ x, y, z = sp.symbols("x y z", real=True) - rrsh = real_regular_solid_harmonics(x, y, z, lmax, sqrt=sp.sqrt) + rrsh = real_regular_solid_harmonics(x, y, z, ellmax, sqrt=sp.sqrt) rrsh.pop(0) tfs = [np.array([[sp.Integer(1)]])] - for l in range(1, lmax + 1): + for ell in range(1, ellmax + 1): tf = [] - for _m in range(2 * l + 1): + for _m in range(2 * ell + 1): row = [] poly = sp.Poly(rrsh.pop(0), x, y, z) - lookup = dict( - ((int(nx), int(ny), int(nz)), coeff) + lookup = { + (int(nx), int(ny), int(nz)): coeff for (nx, ny, nz), coeff in zip(poly.monoms(), poly.coeffs()) - ) - for nx, ny, nz in iter_mononomials(l): + } + for nx, ny, nz in iter_mononomials(ell): row.append(sp.sympify(lookup.get((nx, ny, nz), 0))) tf.append(row) tfs.append(np.array(tf)) return tfs -def iter_mononomials(l): +def iter_mononomials(ell): """Iterate over Cartesian mononomials of given angular momentum.""" - for nx in range(l, -1, -1): - for ny in range(l - nx, -1, -1): - nz = l - nx - ny + for nx in range(ell, -1, -1): + for ny in range(ell - nx, -1, -1): + nz = ell - nx - ny yield nx, ny, nz -def real_regular_solid_harmonics(x, y, z, lmax, sqrt=None): - """Evaluate the real regular solid harmonics up the l=lmax. +def real_regular_solid_harmonics(x, y, z, ellmax, sqrt=None): + """Evaluate the real regular solid harmonics up the ell=ellmax. Parameters ---------- x, y and z Cartesian coordinates where the function must be evaluated. - lmax + ellmax Maximum angular monentum quantum number to consider. sqrt An alternative sqrt function to use, e.g. sympy.sqrt. The default is @@ -120,7 +119,7 @@ def real_regular_solid_harmonics(x, y, z, lmax, sqrt=None): Returns ------- result - List of real regular solid harmonics up to angular momentum lmax. + List of real regular solid harmonics up to angular momentum ellmax. The order is following HORTON2 conventions, i.e. .. code-block:: @@ -139,32 +138,32 @@ def real_regular_solid_harmonics(x, y, z, lmax, sqrt=None): r2 = x * x + y * y + z * z offset2 = 0 offset1 = 1 - for l in range(2, lmax + 1): + for ell in range(2, ellmax + 1): # case m = 0 (cosine only) - p1 = 2 * l - 1 - p2 = l - 1 - p3 = l - c_l2_0 = result[offset2] # C_{l-2, 0} - c_l1_0 = result[offset1] # C_{l-1, 0} + p1 = 2 * ell - 1 + p2 = ell - 1 + p3 = ell + c_l2_0 = result[offset2] # C_{ell-2, 0} + c_l1_0 = result[offset1] # C_{ell-1, 0} result.append((p1 * z * c_l1_0 - p2 * r2 * c_l2_0) / p3) - # case m = 1 ... l - 2 - for m in range(1, l - 1): - p2 = sqrt((l + m - 1) * (l - m - 1)) - p3 = sqrt((l + m) * (l - m)) - c_l2_m = result[offset2 + 2 * m - 1] # C_{l-2, m} - c_l1_m = result[offset1 + 2 * m - 1] # C_{l-1, m} + # case m = 1 ... ell - 2 + for m in range(1, ell - 1): + p2 = sqrt((ell + m - 1) * (ell - m - 1)) + p3 = sqrt((ell + m) * (ell - m)) + c_l2_m = result[offset2 + 2 * m - 1] # C_{ell-2, m} + c_l1_m = result[offset1 + 2 * m - 1] # C_{ell-1, m} result.append((p1 * z * c_l1_m - p2 * r2 * c_l2_m) / p3) - s_l2_m = result[offset2 + 2 * m] # S_{l-2, m} - s_l1_m = result[offset1 + 2 * m] # S_{l-1, m} + s_l2_m = result[offset2 + 2 * m] # S_{ell-2, m} + s_l1_m = result[offset1 + 2 * m] # S_{ell-1, m} result.append((p1 * z * s_l1_m - p2 * r2 * s_l2_m) / p3) - # case m = l - 1 + # case m = ell - 1 p4 = sqrt(p1) - c_l1_m = result[offset1 + 2 * l - 3] # C_{l-1, l-1} + c_l1_m = result[offset1 + 2 * ell - 3] # C_{ell-1, ell-1} result.append(p4 * z * c_l1_m) - s_l1_m = result[offset1 + 2 * l - 2] # S_{l-1, l-1} + s_l1_m = result[offset1 + 2 * ell - 2] # S_{ell-1, ell-1} result.append(p4 * z * s_l1_m) - # case m = l - p5 = p4 / sqrt(2 * l) + # case m = ell + p5 = p4 / sqrt(2 * ell) result.append(p5 * (x * c_l1_m - y * s_l1_m)) result.append(p5 * (x * s_l1_m + y * c_l1_m)) # shift offsets @@ -187,27 +186,25 @@ def get_cart_l2_norm(alpha, nx, ny, nz): ) -def get_pure_l2_norm(alpha, l): +def get_pure_l2_norm(alpha, ell): """Compute the norm of a pure gaussian primitive.""" return sp.sqrt( - int(fac2(2 * l - 1)) - / (2 * alpha / sp.pi) ** sp.Rational(3, 2) - / (4 * alpha) ** l + int(fac2(2 * ell - 1)) / (2 * alpha / sp.pi) ** sp.Rational(3, 2) / (4 * alpha) ** ell ) def include_l2_norm(tfs): """Correct the transformation matrices to work for L2 normalized functions.""" - for l, tf in enumerate(tfs): + for ell, tf in enumerate(tfs): # Multiply each columbn with the norm of a Cartesian function, to go # from normalized to unnormalized Cartesian functions, after which the # transform is applied. The choice of exponent is irrelevant, as long # as it is consistent - for i, (nx, ny, nz) in enumerate(iter_mononomials(l)): + for i, (nx, ny, nz) in enumerate(iter_mononomials(ell)): tf[:, i] *= get_cart_l2_norm(1, nx, ny, nz) # Divide everything by the norm of a pure function, to get results # for normalized pure functions - tf[:] /= get_pure_l2_norm(1, l) + tf[:] /= get_pure_l2_norm(1, ell) # Run simplify on each element for i in range(tf.size): tf.flat[i] = sp.simplify(tf.flat[i]) @@ -215,12 +212,13 @@ def include_l2_norm(tfs): def print_latex(tfs): """Print transformation matrices in Latex code.""" - def iter_pure_labels(l): + + def iter_pure_labels(ell): """Iterate over labels for pure functions.""" - yield "C_{{{}0}}".format(l) - for m in range(1, l + 1): - yield "C_{{{}{}}}".format(l, m) - yield "S_{{{}{}}}".format(l, m) + yield f"C_{{{ell}0}}" + for m in range(1, ell + 1): + yield f"C_{{{ell}{m}}}" + yield f"S_{{{ell}{m}}}" def tostr(v): """Format an sympy expression as Latex code.""" @@ -228,10 +226,10 @@ def tostr(v): return r"\cdot" return sp.latex(v) - for l, tf in enumerate(tfs): + for ell, tf in enumerate(tfs): npure, ncart = tf.shape print(r"\left(\begin{array}{c}") - print(" ", r" \\ ".join(["b({})".format(label) for label in iter_pure_labels(l)])) + print(" ", r" \\ ".join([f"b({label})" for label in iter_pure_labels(ell)])) print(r"\end{array}\right)") print(" &=") print(r"\left(\begin{array}{" + ("c" * ncart) + "}") @@ -244,19 +242,20 @@ def tostr(v): print(r"\end{array}\right)") print(r"\left(\begin{array}{c}") els = [] - for nx, ny, nz in iter_mononomials(l): + for nx, ny, nz in iter_mononomials(ell): spoly = "x" * nx + "y" * ny + "z" * nz if spoly == "": spoly = "1" - els.append("b({})".format(spoly)) + els.append(f"b({spoly})") print(" ", r" \\ ".join(els)) print(r"\end{array}\right)") - if l != len(tfs) - 1: + if ell != len(tfs) - 1: print(r"\\") def print_python(tfs): """Print transformation matrices in Python code.""" + def tostr(v): """Format an sympy expression as a float with sufficient digits.""" s = repr(v.evalf(17)) @@ -264,17 +263,15 @@ def tostr(v): s = s[:-1] return s - for l, tf in enumerate(tfs): + for ell, tf in enumerate(tfs): npure, ncart = tf.shape - print("tf{} = np.array([".format(l)) + print(f"tf{ell} = np.array([") for ipure in range(npure): print( - " [{}],".format( - ", ".join([tostr(tf[ipure, icart]) for icart in range(ncart)]) - ) + " [{}],".format(", ".join([tostr(tf[ipure, icart]) for icart in range(ncart)])) ) print("])") - print("tfs = [{}]".format(", ".join("tf{}".format(l) for l in range(len(tfs))))) + print("tfs = [{}]".format(", ".join(f"tf{ell}" for ell in range(len(tfs))))) def test_manual(): @@ -285,31 +282,24 @@ def test_manual(): assert rrsh[1] == z assert rrsh[2] == x assert rrsh[3] == y - assert rrsh[4].expand() == (sp.Rational(3, 2) * z ** 2 - r2 / 2).expand() + assert rrsh[4].expand() == (sp.Rational(3, 2) * z**2 - r2 / 2).expand() assert rrsh[5].expand() == (sp.sqrt(3) * x * z) assert rrsh[6].expand() == (sp.sqrt(3) * y * z) - assert rrsh[7].expand() == (sp.sqrt(3) / 2 * (x ** 2 - y ** 2)).expand() + assert rrsh[7].expand() == (sp.sqrt(3) / 2 * (x**2 - y**2)).expand() assert rrsh[8].expand() == (sp.sqrt(3) * x * y) - assert ( - rrsh[9].expand() - == (sp.Rational(5, 2) * z ** 3 - sp.Rational(3, 2) * r2 * z).expand() - ) + assert rrsh[9].expand() == (sp.Rational(5, 2) * z**3 - sp.Rational(3, 2) * r2 * z).expand() assert ( rrsh[10].expand() - == ( - x / sp.sqrt(6) * (sp.Rational(15, 2) * z ** 2 - sp.Rational(3, 2) * r2) - ).expand() + == (x / sp.sqrt(6) * (sp.Rational(15, 2) * z**2 - sp.Rational(3, 2) * r2)).expand() ) assert ( rrsh[11].expand() - == ( - y / sp.sqrt(6) * (sp.Rational(15, 2) * z ** 2 - sp.Rational(3, 2) * r2) - ).expand() + == (y / sp.sqrt(6) * (sp.Rational(15, 2) * z**2 - sp.Rational(3, 2) * r2)).expand() ) - assert rrsh[12].expand() == (sp.sqrt(15) * z / 2 * (x ** 2 - y ** 2)).expand() + assert rrsh[12].expand() == (sp.sqrt(15) * z / 2 * (x**2 - y**2)).expand() assert rrsh[13].expand() == (sp.sqrt(15) * x * y * z).expand() - assert rrsh[14].expand() == (sp.sqrt(10) / 4 * (x ** 3 - 3 * x * y ** 2)).expand() - assert rrsh[15].expand() == (sp.sqrt(10) / 4 * (3 * x ** 2 * y - y ** 3)).expand() + assert rrsh[14].expand() == (sp.sqrt(10) / 4 * (x**3 - 3 * x * y**2)).expand() + assert rrsh[15].expand() == (sp.sqrt(10) / 4 * (3 * x**2 * y - y**3)).expand() def test_library(): @@ -317,14 +307,14 @@ def test_library(): phi = sp.Symbol("phi", real=True) x, y, z, r = sp.symbols("x y z r", real=True) r2 = x * x + y * y + z * z - lmax = 5 - rrsh = real_regular_solid_harmonics(x, y, z, lmax, sqrt=sp.sqrt) + ellmax = 5 + rrsh = real_regular_solid_harmonics(x, y, z, ellmax, sqrt=sp.sqrt) - def _get_cartfn(l, m): + def _get_cartfn(ell, m): """Return the reference result in Cartesian coordinates with SymPy.""" # Take the complex spherical harmonics from SymPy and write in terms # of simple trigoniometric functions. - ref = sp.Ynm(l, abs(m), theta, phi) + ref = sp.Ynm(ell, abs(m), theta, phi) ref = ref.expand(func=True) ref = ref.subs(sp.exp(sp.I * phi), sp.cos(phi) + sp.I * sp.sin(phi)) # Take the definition of real functions from Wikipedia, not from @@ -337,7 +327,7 @@ def _get_cartfn(l, m): # Undo the Condon-Shortley phase ref *= (-sp.Integer(1)) ** abs(m) # Convert to regular solid harmonics - ref = (sp.sqrt(4 * sp.pi / (2 * l + 1)) * ref * r ** l).expand() + ref = (sp.sqrt(4 * sp.pi / (2 * ell + 1)) * ref * r**ell).expand() # From spherical to Cartesian coordinates ref = ref.subs(sp.cos(phi), x / (r * sp.sin(theta))) ref = ref.subs(sp.sin(phi), y / (r * sp.sin(theta))) @@ -347,13 +337,13 @@ def _get_cartfn(l, m): return sp.simplify(ref).expand() assert rrsh.pop(0) == 1 - for l in range(1, lmax + 1): - for m in range(l + 1): + for ell in range(1, ellmax + 1): + for m in range(ell + 1): # cosine line - assert rrsh.pop(0).expand() == _get_cartfn(l, m), (l, m) + assert rrsh.pop(0).expand() == _get_cartfn(ell, m), (ell, m) if m > 0: # sine like - assert rrsh.pop(0).expand() == _get_cartfn(l, -m), (l, m) + assert rrsh.pop(0).expand() == _get_cartfn(ell, -m), (ell, m) if __name__ == "__main__":