diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b3ee6f08dc3..251f6e2d28f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,29 +16,28 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.11' cache: pip cache-dependency-path: setup.py + - name: Install dependencies run: | - pip install --upgrade pip pip install -e '.[dev]' - - name: pylint + + - name: ruff run: | - pylint pymatgen + ruff --version + ruff check . --ignore 'D,SIM' + - name: black run: | black --version black --check --diff --color pymatgen - - name: flake8 - run: | - flake8 --version - flake8 --count --show-source --statistics pymatgen - # exit-zero treats all errors as warnings. - flake8 --count --exit-zero --max-complexity=20 --statistics pymatgen + - name: mypy run: | mypy --version diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2aa7b1c378d..3d37a895ff4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,19 +2,14 @@ exclude: ^(docs|.*test_files|cmd_line|tasks.py) ci: autoupdate_schedule: monthly - skip: [mypy, pylint, codespell, pydocstyle] + skip: [mypy, codespell] repos: - - repo: https://github.com/PyCQA/autoflake - rev: v2.0.1 + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.247 hooks: - - id: autoflake - - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: [--py38-plus] + - id: ruff + args: [--fix, --ignore, 'D,SIM'] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 @@ -24,38 +19,16 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - args: [--profile, black] - - repo: https://github.com/psf/black rev: 23.1.0 hooks: - id: black - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - additional_dependencies: [flake8-bugbear] - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 + rev: v1.0.1 hooks: - id: mypy - - repo: local - hooks: - - id: pylint - name: pylint - entry: pylint - language: python - types: [python] - args: [-sn, --rcfile=pylintrc] - additional_dependencies: [pylint] - - repo: https://github.com/codespell-project/codespell rev: v2.2.2 hooks: @@ -64,12 +37,6 @@ repos: exclude_types: [html] args: [--ignore-words-list, 'titel,titels,reson,rute,pres,kno,coo'] - - repo: https://github.com/PyCQA/pydocstyle - rev: 6.3.0 - hooks: - - id: pydocstyle - exclude: tests - - repo: https://github.com/MarcoGorelli/cython-lint rev: v0.12.4 hooks: diff --git a/ADMIN.md b/ADMIN.md index 39957126a00..9ee6f00f91f 100644 --- a/ADMIN.md +++ b/ADMIN.md @@ -1,42 +1,42 @@ # Introduction -This documentation provides a guide for pymatgen administrators. The -following assumes you are using miniconda or Anaconda. +This documentation provides a guide for `pymatgen` administrators. The +following assumes you are using `miniconda` or Anaconda. ## Releases -The general procedure to releasing pymatgen comprises the following +The general procedure to releasing `pymatgen` comprises the following steps: 1. Make sure all CI checks are green. We don't want to release known bugs. 2. Update and edit change log. 3. Release PyPI versions + doc. -4. Release conda versions. +4. Release `conda` versions. 5. Release Dash documentation. ## Initial setup -Install some conda tools first: +Install some `conda` tools first: ```sh conda install --yes conda-build anaconda-client ``` -Pymatgen uses [invoke](http://www.pyinvoke.org/) to automate releases. +Pymatgen uses [invoke](http://pyinvoke.org) to automate releases. You will also need sphinx and doc2dash. Install these using: ```sh pip install --upgrade invoke sphinx doc2dash ``` -Create environment for py38 using conda: +Create environment for py38 using `conda`: ```sh conda create --yes -n py38 python=3.8 ``` -For each env, install some packages using conda followed by dev install -for pymatgen: +For each env, install some packages using `conda` followed by dev install +for `pymatgen`: ```sh conda install --yes numpy scipy sympy matplotlib cython @@ -105,7 +105,7 @@ generated by Appveyor. Fork and clone the [materials.sh](https://github.com/materialsvirtuallab/materials.sh). -This repo contains the conda skeletons to build the conda versions for +This repo contains the `conda` skeletons to build the `conda` versions for various matsci codes on the Anaconda [matsci channel](https://anaconda.org/matsci). diff --git a/dev_scripts/chemenv/get_plane_permutations_optimized.py b/dev_scripts/chemenv/get_plane_permutations_optimized.py index 028c807d5ae..d65b439d872 100644 --- a/dev_scripts/chemenv/get_plane_permutations_optimized.py +++ b/dev_scripts/chemenv/get_plane_permutations_optimized.py @@ -293,7 +293,7 @@ def random_permutations_iterator(initial_permutation, n_permutations): exit() # 2. Optimization of the permutations print(f"Getting explicit optimized permutations for geometry {cg.name!r} (symbol : {cg_symbol!r})\n") - perms_used_algos = [dict() for algo in cg.algorithms] + perms_used_algos = [{} for algo in cg.algorithms] # Loop on algorithms for ialgo, algo in enumerate(cg.algorithms): @@ -357,7 +357,7 @@ def random_permutations_iterator(initial_permutation, n_permutations): points_perfect = lgf.perfect_geometry.points_wcs_ctwcc() # Loop on the facets - separation_permutations = list() + separation_permutations = [] for iplane, plane_point_indices in enumerate(all_planes_point_indices): prt2( string=f"In plane {iplane:d} ({'-'.join(str(pp) for pp in plane_point_indices)})", diff --git a/dev_scripts/chemenv/strategies/multi_weights_strategy_parameters.py b/dev_scripts/chemenv/strategies/multi_weights_strategy_parameters.py index be309c17537..3a025b12a82 100644 --- a/dev_scripts/chemenv/strategies/multi_weights_strategy_parameters.py +++ b/dev_scripts/chemenv/strategies/multi_weights_strategy_parameters.py @@ -287,11 +287,11 @@ def get_weights(self, weights_options): alldeltacsmmins = [] all_cn_pairs = [] for ii in range(1, 14): - self_weight_max_csms_per_cn[str(ii)] = list() + self_weight_max_csms_per_cn[str(ii)] = [] for jj in range(ii + 1, 14): cn_pair = f"{ii:d}_{jj:d}" - self_weight_max_csms[cn_pair] = list() - delta_csm_mins[cn_pair] = list() + self_weight_max_csms[cn_pair] = [] + delta_csm_mins[cn_pair] = [] all_cn_pairs.append(cn_pair) for ce_pair_dict in ce_pairs: ce1 = ce_pair_dict["initial_environment_symbol"] diff --git a/dev_scripts/chemenv/test_algos_all_geoms.py b/dev_scripts/chemenv/test_algos_all_geoms.py index d9e7a852a99..49a930e639c 100644 --- a/dev_scripts/chemenv/test_algos_all_geoms.py +++ b/dev_scripts/chemenv/test_algos_all_geoms.py @@ -46,10 +46,7 @@ symbol_name_mapping = allcg.get_symbol_name_mapping(coordination=coordination) if perms_def == "standard": - if coordination > 6: - test = "500" - else: - test = "all" + test = "500" if coordination > 6 else "all" elif perms_def == "ndefined": test = nperms # type: ignore[assignment] else: diff --git a/dev_scripts/update_pt_data.py b/dev_scripts/update_pt_data.py index f9ce9e286f6..ba85b2c8fe1 100644 --- a/dev_scripts/update_pt_data.py +++ b/dev_scripts/update_pt_data.py @@ -11,8 +11,8 @@ import re from itertools import product -import ruamel.yaml as yaml from monty.serialization import dumpfn, loadfn +from ruamel import yaml from pymatgen.core import Element from pymatgen.core.periodic_table import get_el_sp @@ -176,16 +176,13 @@ def parse_shannon_radii(): el = sheet[f"A{i}"].value if sheet[f"B{i}"].value: charge = int(sheet[f"B{i}"].value) - radii[el][charge] = dict() + radii[el][charge] = {} if sheet[f"C{i}"].value: cn = sheet[f"C{i}"].value if cn not in radii[el][charge]: - radii[el][charge][cn] = dict() + radii[el][charge][cn] = {} - if sheet[f"D{i}"].value is not None: - spin = sheet[f"D{i}"].value - else: - spin = "" + spin = sheet[f"D{i}"].value if sheet[f"D{i}"].value is not None else "" radii[el][charge][cn][spin] = { "crystal_radius": float(sheet[f"E{i}"].value), @@ -300,10 +297,7 @@ def add_ionization_energies(): if row: Z = int(row[0]) val = re.sub(r"\s", "", row[8].strip("()[]")) - if val == "": - val = None - else: - val = float(val) + val = None if val == "" else float(val) data[Z].append(val) print(data) print(data[51]) diff --git a/docs_rst/conf-docset.py b/docs_rst/conf-docset.py index 4fb909fc3c3..4aa65550845 100644 --- a/docs_rst/conf-docset.py +++ b/docs_rst/conf-docset.py @@ -9,10 +9,13 @@ # # All configuration values have a default; values that are commented out # serve to show the default. +from __future__ import annotations import os import sys +from pymatgen.core import __author__, __version__ + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -21,7 +24,6 @@ sys.path.insert(0, os.path.dirname("../pymatgen")) sys.path.insert(0, os.path.dirname("../..")) -from pymatgen.core import __author__, __version__ # -- General configuration ----------------------------------------------------- diff --git a/docs_rst/conf-normal.py b/docs_rst/conf-normal.py index 3eb0e573584..789594bdbbf 100644 --- a/docs_rst/conf-normal.py +++ b/docs_rst/conf-normal.py @@ -9,10 +9,13 @@ # # All configuration values have a default; values that are commented out # serve to show the default. +from __future__ import annotations import os import sys +from pymatgen.core import __author__, __file__, __version__ + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -21,8 +24,6 @@ sys.path.insert(0, os.path.dirname("../pymatgen")) sys.path.insert(0, os.path.dirname("../..")) -from pymatgen.core import __author__, __file__, __version__ - # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -318,9 +319,11 @@ # Allow duplicate toc entries. # epub_tocdup = True -# Resolve function for the linkcode extension. -# Thanks to https://github.com/Lasagne/Lasagne/blob/master/docs/conf.py + def linkcode_resolve(domain, info): + # Resolve function for the linkcode extension. + # Thanks to https://github.com/Lasagne/Lasagne/blob/master/docs/conf.py + def find_source(): # try to find the file and line number, based on code from numpy: # https://github.com/numpy/numpy/blob/master/doc/source/conf.py#L286 diff --git a/docs_rst/conf.py b/docs_rst/conf.py index 3eb0e573584..b14d045eaa9 100644 --- a/docs_rst/conf.py +++ b/docs_rst/conf.py @@ -9,10 +9,13 @@ # # All configuration values have a default; values that are commented out # serve to show the default. +from __future__ import annotations import os import sys +from pymatgen.core import __author__, __file__, __version__ + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -21,7 +24,6 @@ sys.path.insert(0, os.path.dirname("../pymatgen")) sys.path.insert(0, os.path.dirname("../..")) -from pymatgen.core import __author__, __file__, __version__ # -- General configuration ----------------------------------------------------- @@ -346,5 +348,4 @@ def find_source(): # no need to be relative to core here as module includes full path. filename = info["module"].replace(".", "/") + ".py" - tag = "v" + __version__ - return f"https://github.com/materialsproject/pymatgen/blob/{tag}/{filename}" + return f"https://github.com/materialsproject/pymatgen/blob/v{__version__}/{filename}" diff --git a/pylintrc b/pylintrc deleted file mode 100644 index becb8d2b6f5..00000000000 --- a/pylintrc +++ /dev/null @@ -1,631 +0,0 @@ -[MASTER] - -# 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 - -# Load and enable all available extensions. Use --list-extensions to see a list -# all available extensions. -#enable-all-extensions= - -# In error mode, checkers without error messages are disabled and for others, -# only the ERROR messages are displayed, and no reports are done by default. -#errors-only= - -# Always return a 0 (non-error) status code, even if lint errors are found. -# This is primarily useful in continuous integration scripts. -#exit-zero= - -# 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-allow-list= - -# 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. (This is an alternative name to extension-pkg-allow-list -# for backward compatibility.) -extension-pkg-whitelist= - -# Return non-zero exit code if any of these messages/categories are detected, -# even if score is above --fail-under value. Syntax same as enable. Messages -# specified are enabled, while categories only check already-enabled messages. -fail-on= - -# Specify a score threshold to be exceeded before program exits with error. -fail-under=10 - -# Interpret the stdin as a python script, whose filename needs to be passed as -# the module_or_package argument. -#from-stdin= - -# Files or directories to be skipped. They should be base names, not paths. -ignore=CVS,tests,chemenv,abinit,defects - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against paths and can be in Posix or Windows format. -ignore-paths= - -# Files or directories matching the regex patterns are skipped. The regex -# matches against base names, not paths. The default value ignores Emacs file -# locks -ignore-patterns=^\.# - -# 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=scipy.special -# reason: scipy.special is a C extension, causes: -# pymatgen/io/vasp/optics.py:266:8: E1101: Module 'scipy.special' has no 'eval_hermite' member (no-member) -# pymatgen/io/vasp/optics.py:278:8: E1101: Module 'scipy.special' has no 'eval_hermite' member (no-member) -# pymatgen/io/vasp/optics.py:279:18: E1101: Module 'scipy.special' has no 'erf' member (no-member) -# pymatgen/io/vasp/optics.py:300:27: E1101: Module 'scipy.special' has no 'erf' member (no-member) - - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python module names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Minimum Python version to use for version dependent checks. Will default to -# the version used to run pylint. -py-version=3.9 - -# Discover python modules and packages in the file system subtree. -recursive=no - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# In verbose mode, extra non-checker-related info will be displayed. -#verbose= - - -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'fatal', 'error', 'warning', 'refactor', -# 'convention', and 'info' which contain the number of messages in each -# category, as well as 'statement' which is the total number of statements -# analyzed. This score is used by the global evaluation report (RP0004). -evaluation=max(0, 0 if fatal else 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, e.g. -# mypackage.mymodule.MyReporterClass. -#output-format= - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, -# UNDEFINED. -confidence=HIGH, - CONTROL_FLOW, - INFERENCE, - INFERENCE_FAILURE, - UNDEFINED - -# 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 re-enable 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=raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - use-implicit-booleaness-not-comparison, - C0103, - W, - R, - E1120, - E1121, - E1123, - C0201, - E0401, - E0611, - C0415, - C0114, - C0115, - C0116, - C0414, - C0302, - # C0209 - -# 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=c-extension-no-member - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it work, -# install the 'python-enchant' package. -spelling-dict= - -# List of comma separated words that should be considered directives if they -# appear at the beginning of a comment and should not be checked. -spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -notes-rgx= - - -[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 to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=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 symbolic message names to ignore for Mixin members. -ignored-checks-for-mixins=no-member, - not-async-context-manager, - not-context-manager, - attribute-defined-outside-init - -# 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,argparse.Namespace - -# 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 - -# Regex pattern to define which classes are considered mixins. -mixin-class-rgx=.*[Mm]ixin - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[CLASSES] - -# Warn about protected attribute access inside special methods -check-protected-access-in-special-methods=no - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ - -# 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=cls - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of names allowed to shadow builtins -allowed-redefined-builtins= - -# 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. expected to -# not be 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,past.builtins,future.builtins,builtins,io - - -[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=120 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# 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 - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level= - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules= - -# Output a graph (.gv or any supported image format) of external dependencies -# to the given file (report RP0402 must not be disabled). -ext-import-graph= - -# Output a graph (.gv or any supported image format) of all (i.e. internal and -# external) dependencies to the given file (report RP0402 must not be -# disabled). -import-graph= - -# Output a graph (.gv or any supported image format) of internal dependencies -# to 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 - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - builtins.Exception - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit,argparse.parse_error - - -[SIMILARITIES] - -# Comments are removed from the similarity computation -ignore-comments=yes - -# Docstrings are removed from the similarity computation -ignore-docstrings=yes - -# Imports are removed from the similarity computation -ignore-imports=yes - -# Signatures are removed from the similarity computation -ignore-signatures=yes - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[DESIGN] - -# List of regular expressions of class ancestor names to ignore when counting -# public methods (see R0903) -exclude-too-few-public-methods= - -# List of qualified class names to ignore when counting class parents (see -# R0901) -ignored-parents= - -# 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 an if statement (see R0916). -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 - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=no - -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=no - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. If left empty, argument names will be checked with the set -# naming style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. If left empty, attribute names will be checked with the set naming -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -bad-names-rgxs= - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. If left empty, class attribute names will be checked -# with the set naming style. -#class-attribute-rgx= - -# Naming style matching correct class constant names. -class-const-naming-style=UPPER_CASE - -# Regular expression matching correct class constant names. Overrides class- -# const-naming-style. If left empty, class constant names will be checked with -# the set naming style. -#class-const-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. If left empty, class names will be checked with the set naming style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. If left empty, constant names will be checked with the set naming -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. If left empty, function names will be checked with the set -# naming style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -good-names-rgxs= - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. If left empty, inline iteration names will be checked -# with the set naming style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. If left empty, method names will be checked with the set naming style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. If left empty, module names will be checked with the set naming style. -#module-rgx= - -# 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=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Regular expression matching correct type variable names. If left empty, type -# variable names will be checked with the set naming style. -#typevar-rgx= - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. If left empty, variable names will be checked with the set -# naming style. -#variable-rgx= diff --git a/pymatgen/alchemy/filters.py b/pymatgen/alchemy/filters.py index 92acc47adb5..25faeae39fb 100644 --- a/pymatgen/alchemy/filters.py +++ b/pymatgen/alchemy/filters.py @@ -161,9 +161,8 @@ def test(self, structure): nn = structure.get_neighbors(site, max_r) for sp in sp_to_test: for nn_site, dist, *_ in nn: - if sp in nn_site.species: - if dist < self.specie_and_min_dist[sp]: - return False + if sp in nn_site.species and dist < self.specie_and_min_dist[sp]: + return False return True def as_dict(self): diff --git a/pymatgen/alchemy/materials.py b/pymatgen/alchemy/materials.py index 0aebc8cb6c9..3cbfa8f5961 100644 --- a/pymatgen/alchemy/materials.py +++ b/pymatgen/alchemy/materials.py @@ -247,7 +247,7 @@ def was_modified(self) -> bool: is in the case of performing a substitution transformation on the structure when the specie to replace isn't in the structure. """ - return not self.final_structure == self.structures[-2] + return self.final_structure != self.structures[-2] @property def structures(self) -> list[Structure]: @@ -256,7 +256,7 @@ def structures(self) -> list[Structure]: structure is stored after every single transformation. """ h_structs = [Structure.from_dict(s["input_structure"]) for s in self.history if "input_structure" in s] - return h_structs + [self.final_structure] + return [*h_structs, self.final_structure] @staticmethod def from_cif_string( diff --git a/pymatgen/alchemy/tests/test_materials.py b/pymatgen/alchemy/tests/test_materials.py index a89c4ba2fef..35419e1f375 100644 --- a/pymatgen/alchemy/tests/test_materials.py +++ b/pymatgen/alchemy/tests/test_materials.py @@ -34,7 +34,7 @@ def setUp(self): def test_append_transformation(self): t = SubstitutionTransformation({"Fe": "Mn"}) self.trans.append_transformation(t) - assert "NaMnPO4" == self.trans.final_structure.composition.reduced_formula + assert self.trans.final_structure.composition.reduced_formula == "NaMnPO4" assert len(self.trans.structures) == 3 coords = [] coords.append([0, 0, 0]) @@ -59,11 +59,11 @@ def test_append_filter(self): def test_get_vasp_input(self): SETTINGS["PMG_VASP_PSP_DIR"] = PymatgenTest.TEST_FILES_DIR potcar = self.trans.get_vasp_input(MPRelaxSet)["POTCAR"] - assert "Na_pv\nFe_pv\nP\nO" == "\n".join(p.symbol for p in potcar) + assert "\n".join(p.symbol for p in potcar) == "Na_pv\nFe_pv\nP\nO" assert len(self.trans.structures) == 2 def test_final_structure(self): - assert "NaFePO4" == self.trans.final_structure.composition.reduced_formula + assert self.trans.final_structure.composition.reduced_formula == "NaFePO4" def test_from_dict(self): d = json.load(open(os.path.join(PymatgenTest.TEST_FILES_DIR, "transformations.json"))) @@ -71,7 +71,7 @@ def test_from_dict(self): ts = TransformedStructure.from_dict(d) ts.other_parameters["author"] = "Will" ts.append_transformation(SubstitutionTransformation({"Fe": "Mn"})) - assert "MnPO4" == ts.final_structure.composition.reduced_formula + assert ts.final_structure.composition.reduced_formula == "MnPO4" assert ts.other_parameters == {"author": "Will", "tags": ["test"]} def test_undo_and_redo_last_change(self): @@ -80,17 +80,17 @@ def test_undo_and_redo_last_change(self): SubstitutionTransformation({"Fe": "Mn"}), ] ts = TransformedStructure(self.structure, trans) - assert "NaMnPO4" == ts.final_structure.composition.reduced_formula + assert ts.final_structure.composition.reduced_formula == "NaMnPO4" ts.undo_last_change() - assert "NaFePO4" == ts.final_structure.composition.reduced_formula + assert ts.final_structure.composition.reduced_formula == "NaFePO4" ts.undo_last_change() - assert "LiFePO4" == ts.final_structure.composition.reduced_formula + assert ts.final_structure.composition.reduced_formula == "LiFePO4" with pytest.raises(IndexError): ts.undo_last_change() ts.redo_next_change() - assert "NaFePO4" == ts.final_structure.composition.reduced_formula + assert ts.final_structure.composition.reduced_formula == "NaFePO4" ts.redo_next_change() - assert "NaMnPO4" == ts.final_structure.composition.reduced_formula + assert ts.final_structure.composition.reduced_formula == "NaMnPO4" with pytest.raises(IndexError): ts.redo_next_change() # Make sure that this works with filters. diff --git a/pymatgen/alchemy/transmuters.py b/pymatgen/alchemy/transmuters.py index 8bc57c1b5a9..923104a8b93 100644 --- a/pymatgen/alchemy/transmuters.py +++ b/pymatgen/alchemy/transmuters.py @@ -121,10 +121,7 @@ def append_transformation(self, transformation, extend_collection=False, clear_r if self.ncores and transformation.use_multiprocessing: with Pool(self.ncores) as p: # need to condense arguments into single tuple to use map - z = map( - lambda x: (x, transformation, extend_collection, clear_redo), - self.transformed_structures, - ) + z = ((x, transformation, extend_collection, clear_redo) for x in self.transformed_structures) new_tstructs = p.map(_apply_transformation, z, 1) self.transformed_structures = [] for ts in new_tstructs: diff --git a/pymatgen/analysis/adsorption.py b/pymatgen/analysis/adsorption.py index ed1669864fd..57501d64cf2 100644 --- a/pymatgen/analysis/adsorption.py +++ b/pymatgen/analysis/adsorption.py @@ -2,7 +2,8 @@ # Distributed under the terms of the MIT License. """This module provides classes used to enumerate surface sites and to find -adsorption sites on slabs.""" +adsorption sites on slabs. +""" from __future__ import annotations @@ -590,7 +591,8 @@ def substitute(site, i): def get_mi_vec(slab): """Convenience function which returns the unit vector aligned with the - miller index.""" + miller index. + """ mvec = np.cross(slab.lattice.matrix[0], slab.lattice.matrix[1]) return mvec / np.linalg.norm(mvec) @@ -616,7 +618,8 @@ def put_coord_inside(lattice, cart_coordinate): def reorient_z(structure): """reorients a structure such that the z axis is concurrent with the normal - to the A-B plane.""" + to the A-B plane. + """ struct = structure.copy() sop = get_rot(struct) struct.apply_operation(sop) @@ -702,7 +705,7 @@ def plot_slab( if draw_unit_cell: verts = np.insert(verts, 1, lattsum, axis=0).tolist() verts += [[0.0, 0.0]] - verts = [[0.0, 0.0]] + verts + verts = [[0.0, 0.0], *verts] codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY] verts = [(np.array(vert) + corner).tolist() for vert in verts] path = Path(verts, codes) diff --git a/pymatgen/analysis/bond_dissociation.py b/pymatgen/analysis/bond_dissociation.py index 743f4e54669..52f3e1ab598 100644 --- a/pymatgen/analysis/bond_dissociation.py +++ b/pymatgen/analysis/bond_dissociation.py @@ -207,10 +207,9 @@ def fragment_and_process(self, bonds): if frag_pair[1].isomorphic_to(frags[1]): frags_done = True break - elif frag_pair[1].isomorphic_to(frags[0]): - if frag_pair[0].isomorphic_to(frags[1]): - frags_done = True - break + elif frag_pair[1].isomorphic_to(frags[0]) and frag_pair[0].isomorphic_to(frags[1]): + frags_done = True + break if not frags_done: # If we haven't, we save this pair and search for the relevant fragment entries: self.done_frag_pairs += [frags] @@ -343,20 +342,19 @@ def filter_fragment_entries(self, fragment_entries): found_similar_entry = False # Check for uniqueness for ii, filtered_entry in enumerate(self.filtered_entries): - if filtered_entry["formula_pretty"] == entry["formula_pretty"]: - if ( - filtered_entry["initial_molgraph"].isomorphic_to(entry["initial_molgraph"]) - and filtered_entry["final_molgraph"].isomorphic_to(entry["final_molgraph"]) - and filtered_entry["initial_molecule"]["charge"] == entry["initial_molecule"]["charge"] - ): - found_similar_entry = True - # If two entries are found that pass the above similarity check, take the one with the lower - # energy: - if entry["final_energy"] < filtered_entry["final_energy"]: - self.filtered_entries[ii] = entry - # Note that this will essentially choose between singlet and triplet entries assuming both have - # the same structural details - break + if filtered_entry["formula_pretty"] == entry["formula_pretty"] and ( + filtered_entry["initial_molgraph"].isomorphic_to(entry["initial_molgraph"]) + and filtered_entry["final_molgraph"].isomorphic_to(entry["final_molgraph"]) + and filtered_entry["initial_molecule"]["charge"] == entry["initial_molecule"]["charge"] + ): + found_similar_entry = True + # If two entries are found that pass the above similarity check, take the one with the lower + # energy: + if entry["final_energy"] < filtered_entry["final_energy"]: + self.filtered_entries[ii] = entry + # Note that this will essentially choose between singlet and triplet entries assuming both have + # the same structural details + break if not found_similar_entry: self.filtered_entries += [entry] diff --git a/pymatgen/analysis/bond_valence.py b/pymatgen/analysis/bond_valence.py index 95b8cf930ae..04e6a921c3f 100644 --- a/pymatgen/analysis/bond_valence.py +++ b/pymatgen/analysis/bond_valence.py @@ -353,7 +353,7 @@ def _recurse(assigned=None): return None for v in valences[i]: new_assigned = list(assigned) - _recurse(new_assigned + [v]) + _recurse([*new_assigned, v]) return None else: @@ -434,7 +434,7 @@ def _recurse(assigned=None): for v in new_valences[i]: new_assigned = list(assigned) - _recurse(new_assigned + [v]) + _recurse([*new_assigned, v]) return None diff --git a/pymatgen/analysis/chemenv/connectivity/connected_components.py b/pymatgen/analysis/chemenv/connectivity/connected_components.py index 0e6e158b26e..88d53f8593d 100644 --- a/pymatgen/analysis/chemenv/connectivity/connected_components.py +++ b/pymatgen/analysis/chemenv/connectivity/connected_components.py @@ -240,10 +240,7 @@ def __init__( for edge in links: env_node1 = edge[0] env_node2 = edge[1] - if len(edge) == 2: - key = None - else: - key = edge[2] + key = None if len(edge) == 2 else edge[2] if (not self._connected_subgraph.has_node(env_node1)) or ( not self._connected_subgraph.has_node(env_node2) ): @@ -334,10 +331,10 @@ def coordination_sequence(self, source_node, path_size=5, coordination="number", # One possible quotient graph of this periodic net : # __ __ # (0,1,0) / \ / \ (0,1,0) - # `<--A--->---B--<´ + # `<--A--->---B--<` # / (0,0,0) \ # \ / - # `--->---´ + # `--->---` # (1,0,0) # # The "number" coordination sequence starting from any environment is : 4-8-12-16-... @@ -550,10 +547,7 @@ def show_graph(self, graph=None, save_file=None, drawing_type="internal", pltsho """ import matplotlib.pyplot as plt - if graph is None: - shown_graph = self._connected_subgraph - else: - shown_graph = graph + shown_graph = self._connected_subgraph if graph is None else graph plt.figure() # pos = nx.spring_layout(shown_graph) @@ -837,7 +831,7 @@ def _retuplify_edgedata(edata): dict: Edge data dictionary with the lists transformed back into tuples when applicable. """ edata["delta"] = tuple(edata["delta"]) - edata["ligands"] = [tuple([lig[0], tuple(lig[1]), tuple(lig[2])]) for lig in edata["ligands"]] + edata["ligands"] = [(lig[0], tuple(lig[1]), tuple(lig[2])) for lig in edata["ligands"]] return edata def as_dict(self): @@ -901,6 +895,7 @@ def from_graph(cls, g): Args: g (MultiGraph): Graph of the connected component. + Returns: ConnectedComponent: The connected component representing the links of a given set of environments. """ diff --git a/pymatgen/analysis/chemenv/connectivity/connectivity_finder.py b/pymatgen/analysis/chemenv/connectivity/connectivity_finder.py index 7d2054bcb08..36012229117 100644 --- a/pymatgen/analysis/chemenv/connectivity/connectivity_finder.py +++ b/pymatgen/analysis/chemenv/connectivity/connectivity_finder.py @@ -8,9 +8,7 @@ import numpy as np -from pymatgen.analysis.chemenv.connectivity.structure_connectivity import ( - StructureConnectivity, -) +from pymatgen.analysis.chemenv.connectivity.structure_connectivity import StructureConnectivity __author__ = "David Waroquiers" __copyright__ = "Copyright 2012, The Materials Project" @@ -73,9 +71,6 @@ def setup_parameters(self, multiple_environments_choice): """ Setup of the parameters for the connectivity finder. """ - if multiple_environments_choice is not None: - if multiple_environments_choice not in ["TAKE_HIGHEST_FRACTION"]: - raise ValueError( - f"Option {multiple_environments_choice!r} for multiple_environments_choice is not allowed" - ) + if multiple_environments_choice is not None and multiple_environments_choice not in ["TAKE_HIGHEST_FRACTION"]: + raise ValueError(f"Option {multiple_environments_choice!r} for multiple_environments_choice is not allowed") self.multiple_environments_choice = multiple_environments_choice diff --git a/pymatgen/analysis/chemenv/connectivity/environment_nodes.py b/pymatgen/analysis/chemenv/connectivity/environment_nodes.py index b7562b20d74..5e57d37d3ae 100644 --- a/pymatgen/analysis/chemenv/connectivity/environment_nodes.py +++ b/pymatgen/analysis/chemenv/connectivity/environment_nodes.py @@ -65,7 +65,8 @@ def __lt__(self, other): def everything_equal(self, other): """Checks equality with respect to another AbstractEnvironmentNode using the index of the central site - as well as the central site itself.""" + as well as the central site itself. + """ return self == other and self.central_site == other.central_site @property @@ -169,6 +170,7 @@ def get_environment_node(central_site, i_central_site, ce_symbol): central_site (Site or subclass of Site): Central site of the environment. i_central_site (int): Index of the central site in the structure. ce_symbol: Symbol of the environment. + Returns: An EnvironmentNode object. """ diff --git a/pymatgen/analysis/chemenv/connectivity/structure_connectivity.py b/pymatgen/analysis/chemenv/connectivity/structure_connectivity.py index dcaf4b1758a..ac82d08565e 100644 --- a/pymatgen/analysis/chemenv/connectivity/structure_connectivity.py +++ b/pymatgen/analysis/chemenv/connectivity/structure_connectivity.py @@ -123,12 +123,11 @@ def add_bonds(self, isite, site_neighbors_set): else: if isite == nb_index_unitcell: for isite1, ineighb1, data1 in existing_edges: - if isite1 == ineighb1: - if np.allclose(data1["delta"], nb_image_cell) or np.allclose( - data1["delta"], -nb_image_cell - ): - exists = True - break + if isite1 == ineighb1 and ( + np.allclose(data1["delta"], nb_image_cell) or np.allclose(data1["delta"], -nb_image_cell) + ): + exists = True + break else: for _, ineighb1, data1 in existing_edges: if nb_index_unitcell == ineighb1: diff --git a/pymatgen/analysis/chemenv/connectivity/tests/test_connected_components.py b/pymatgen/analysis/chemenv/connectivity/tests/test_connected_components.py index 86db371fc90..91f19c1ceb9 100644 --- a/pymatgen/analysis/chemenv/connectivity/tests/test_connected_components.py +++ b/pymatgen/analysis/chemenv/connectivity/tests/test_connected_components.py @@ -86,8 +86,8 @@ def test_init(self): assert isinstance(mygraph, nx.MultiGraph) # Check that it is indeed the same type of graph cc2 = ConnectedComponent(graph=mygraph) - assert set(list(mygraph.nodes())) == set(list(cc2.graph.nodes())) - assert set(list(mygraph.edges())) == set(list(cc2.graph.edges())) + assert set(mygraph.nodes()) == set(cc2.graph.nodes()) + assert set(mygraph.edges()) == set(cc2.graph.edges()) assert len(cc2.graph) == 6 def test_serialization(self): @@ -143,7 +143,7 @@ def test_serialization(self): for loaded_cc in loaded_cc_list: assert loaded_cc.graph.number_of_nodes() == 3 assert loaded_cc.graph.number_of_edges() == 2 - assert set(list(cc.graph.nodes())) == set(list(loaded_cc.graph.nodes())) + assert set(cc.graph.nodes()) == set(loaded_cc.graph.nodes()) assert sorted_edges == sorted(sorted(e) for e in loaded_cc.graph.edges()) for e in sorted_edges: diff --git a/pymatgen/analysis/chemenv/coordination_environments/chemenv_strategies.py b/pymatgen/analysis/chemenv/coordination_environments/chemenv_strategies.py index cb42041a31b..419fcc23f4d 100644 --- a/pymatgen/analysis/chemenv/coordination_environments/chemenv_strategies.py +++ b/pymatgen/analysis/chemenv/coordination_environments/chemenv_strategies.py @@ -91,7 +91,8 @@ def as_dict(self): def from_dict(cls, d): """Initialize distance cutoff from dict. - :param d: Dict representation of the distance cutoff.""" + :param d: Dict representation of the distance cutoff. + """ return cls(d["value"]) @@ -103,7 +104,8 @@ class AngleCutoffFloat(float, StrategyOption): def __new__(cls, myfloat): """Special float that should be between 0.0 and 1.0. - :param myfloat: Angle cutoff.""" + :param myfloat: Angle cutoff. + """ flt = float.__new__(cls, myfloat) if flt < 0.0 or flt > 1.0: raise ValueError("Angle cutoff should be between 0.0 and 1.0") @@ -134,7 +136,8 @@ class CSMFloat(float, StrategyOption): def __new__(cls, myfloat): """Special float that should be between 0.0 and 100.0. - :param myfloat: CSM.""" + :param myfloat: CSM. + """ flt = float.__new__(cls, myfloat) if flt < 0.0 or flt > 100.0: raise ValueError("Continuous symmetry measure limits should be between 0.0 and 100.0") @@ -2722,10 +2725,7 @@ def get_site_coordination_environments_fractions( dict_fractions["Fraction"] = nb_set_fraction * fraction ce_dict_fractions.append(dict_fractions) ce_maps.append(cn_map) - if ordered: - indices = np.argsort(ce_fractions)[::-1] - else: - indices = list(range(len(ce_fractions))) + indices = np.argsort(ce_fractions)[::-1] if ordered else list(range(len(ce_fractions))) fractions_info_list = [ { diff --git a/pymatgen/analysis/chemenv/coordination_environments/coordination_geometries.py b/pymatgen/analysis/chemenv/coordination_environments/coordination_geometries.py index 74fe3bd7a72..48c3935b9b8 100644 --- a/pymatgen/analysis/chemenv/coordination_environments/coordination_geometries.py +++ b/pymatgen/analysis/chemenv/coordination_environments/coordination_geometries.py @@ -241,6 +241,7 @@ def safe_separation_permutations(self, ordered_plane=False, ordered_point_groups Simple and safe permutations for this separation plane. This is not meant to be used in production. Default configuration for ChemEnv does not use this method. + Args: ordered_plane: Whether the order of the points in the plane can be used to reduce the number of permutations. @@ -857,10 +858,7 @@ def faces(self, sites, permutation=None): Returns the list of faces of this coordination geometry. Each face is given as a list of its vertices coordinates. """ - if permutation is None: - coords = [site.coords for site in sites] - else: - coords = [sites[ii].coords for ii in permutation] + coords = [site.coords for site in sites] if permutation is None else [sites[ii].coords for ii in permutation] return [[coords[ii] for ii in f] for f in self._faces] def edges(self, sites, permutation=None, input="sites"): @@ -895,10 +893,7 @@ def get_pmeshes(self, sites, permutation=None): """ pmeshes = [] # _vertices = [site.coords for site in sites] - if permutation is None: - _vertices = [site.coords for site in sites] - else: - _vertices = [sites[ii].coords for ii in permutation] + _vertices = [site.coords for site in sites] if permutation is None else [sites[ii].coords for ii in permutation] _face_centers = [] number_of_faces = 0 for face in self._faces: @@ -1233,36 +1228,30 @@ def is_a_valid_coordination_geometry( if mp_symbol is not None: try: cg = self.get_geometry_from_mp_symbol(mp_symbol) - if IUPAC_symbol is not None: - if IUPAC_symbol != cg.IUPAC_symbol: - return False - if IUCr_symbol is not None: - if IUCr_symbol != cg.IUCr_symbol: - return False - if cn is not None: - if int(cn) != int(cg.coordination_number): - return False + if IUPAC_symbol is not None and IUPAC_symbol != cg.IUPAC_symbol: + return False + if IUCr_symbol is not None and IUCr_symbol != cg.IUCr_symbol: + return False + if cn is not None and int(cn) != int(cg.coordination_number): + return False return True except LookupError: return False elif IUPAC_symbol is not None: try: cg = self.get_geometry_from_IUPAC_symbol(IUPAC_symbol) - if IUCr_symbol is not None: - if IUCr_symbol != cg.IUCr_symbol: - return False - if cn is not None: - if cn != cg.coordination_number: - return False + if IUCr_symbol is not None and IUCr_symbol != cg.IUCr_symbol: + return False + if cn is not None and cn != cg.coordination_number: + return False return True except LookupError: return False elif IUCr_symbol is not None: try: cg = self.get_geometry_from_IUCr_symbol(IUCr_symbol) - if cn is not None: - if cn != cg.coordination_number: - return False + if cn is not None and cn != cg.coordination_number: + return False return True except LookupError: return True @@ -1318,10 +1307,7 @@ def pretty_print(self, type="implemented_geometries", maxcn=8, additional_info=N for cg in self.get_implemented_geometries(coordination=cn): if additional_info is not None: if "nb_hints" in additional_info: - if cg.neighbors_sets_hints is not None: - addinfo = " *" - else: - addinfo = "" + addinfo = " *" if cg.neighbors_sets_hints is not None else "" else: addinfo = "" else: diff --git a/pymatgen/analysis/chemenv/coordination_environments/coordination_geometry_finder.py b/pymatgen/analysis/chemenv/coordination_environments/coordination_geometry_finder.py index 1340e884e98..0d1ef3fd9c0 100644 --- a/pymatgen/analysis/chemenv/coordination_environments/coordination_geometry_finder.py +++ b/pymatgen/analysis/chemenv/coordination_environments/coordination_geometry_finder.py @@ -880,10 +880,7 @@ def update_nb_set_environments(self, se, isite, cn, inb_set, nb_set, recompute=F if ce is not None and not recompute: return ce ce = ChemicalEnvironments() - if optimization == 2: - neighb_coords = nb_set.neighb_coordsOpt - else: - neighb_coords = nb_set.neighb_coords + neighb_coords = nb_set.neighb_coordsOpt if optimization == 2 else nb_set.neighb_coords self.setup_local_geometry(isite, coords=neighb_coords, optimization=optimization) if optimization > 0: logging.debug("Getting StructureEnvironments with optimized algorithm") @@ -982,10 +979,7 @@ def setup_test_perfect_environment( else: raise ValueError("Wrong mp_symbol to setup coordination geometry") neighb_coords = [] - if points is not None: - mypoints = points - else: - mypoints = cg.points + mypoints = points if points is not None else cg.points if randomness: rv = np.random.random_sample(3) while norm(rv) > 1.0: @@ -1190,10 +1184,7 @@ def get_coordination_symmetry_measures(self, only_minimum=True, all_csms=True, o if only_minimum: if len(result) > 0: imin = np.argmin([rr["symmetry_measure"] for rr in result]) - if geometry.algorithms is not None: - algo = algos[imin] - else: - algo = algos + algo = algos[imin] if geometry.algorithms is not None else algos result_dict[geometry.mp_symbol] = { "csm": result[imin]["symmetry_measure"], "indices": permutations[imin], @@ -1305,25 +1296,21 @@ def get_coordination_symmetry_measures_optim( optimization=optimization, ) result, permutations, algos, local2perfect_maps, perfect2local_maps = cgsm - if only_minimum: - if len(result) > 0: - imin = np.argmin([rr["symmetry_measure"] for rr in result]) - if geometry.algorithms is not None: - algo = algos[imin] - else: - algo = algos - result_dict[geometry.mp_symbol] = { - "csm": result[imin]["symmetry_measure"], - "indices": permutations[imin], - "algo": algo, - "local2perfect_map": local2perfect_maps[imin], - "perfect2local_map": perfect2local_maps[imin], - "scaling_factor": 1.0 / result[imin]["scaling_factor"], - "rotation_matrix": np.linalg.inv(result[imin]["rotation_matrix"]), - "translation_vector": result[imin]["translation_vector"], - } - if all_csms: - self._update_results_all_csms(result_dict, permutations, imin, geometry) + if only_minimum and len(result) > 0: + imin = np.argmin([rr["symmetry_measure"] for rr in result]) + algo = algos[imin] if geometry.algorithms is not None else algos + result_dict[geometry.mp_symbol] = { + "csm": result[imin]["symmetry_measure"], + "indices": permutations[imin], + "algo": algo, + "local2perfect_map": local2perfect_maps[imin], + "perfect2local_map": perfect2local_maps[imin], + "scaling_factor": 1.0 / result[imin]["scaling_factor"], + "rotation_matrix": np.linalg.inv(result[imin]["rotation_matrix"]), + "translation_vector": result[imin]["translation_vector"], + } + if all_csms: + self._update_results_all_csms(result_dict, permutations, imin, geometry) return result_dict def coordination_geometry_symmetry_measures( diff --git a/pymatgen/analysis/chemenv/coordination_environments/structure_environments.py b/pymatgen/analysis/chemenv/coordination_environments/structure_environments.py index b1f9cd27530..55b0556399a 100644 --- a/pymatgen/analysis/chemenv/coordination_environments/structure_environments.py +++ b/pymatgen/analysis/chemenv/coordination_environments/structure_environments.py @@ -212,10 +212,7 @@ def distance_plateau(self): rtol=0.0, atol=self.detailed_voronoi.normalized_distance_tolerance, ): - if idist == 0: - plateau = np.inf - else: - plateau = all_nbs_normalized_distances_sorted[idist - 1] - maxdist + plateau = np.inf if idist == 0 else all_nbs_normalized_distances_sorted[idist - 1] - maxdist break if plateau is None: raise ValueError("Plateau not found ...") @@ -237,10 +234,7 @@ def angle_plateau(self): rtol=0.0, atol=self.detailed_voronoi.normalized_angle_tolerance, ): - if iang == 0: - plateau = minang - else: - plateau = minang - all_nbs_normalized_angles_sorted[iang - 1] + plateau = minang if iang == 0 else minang - all_nbs_normalized_angles_sorted[iang - 1] break if plateau is None: raise ValueError("Plateau not found ...") @@ -722,10 +716,7 @@ def get_csm_and_maps(self, isite, max_csm=8.0, figsize=None, symmetry_measure_ty if symmetry_measure_type is None: symmetry_measure_type = "csm_wcs_ctwcc" # Initializes the figure - if figsize is None: - fig = plt.figure() - else: - fig = plt.figure(figsize=figsize) + fig = plt.figure() if figsize is None else plt.figure(figsize=figsize) gs = GridSpec(2, 1, hspace=0.0, wspace=0.0) subplot = fig.add_subplot(gs[:]) subplot_distang = subplot.twinx() @@ -797,10 +788,7 @@ def ydist(wd): for ix, was in enumerate(all_was): subplot_distang.plot(0.2 + ix * np.ones_like(was), yang(was), " max_csm: - return None + if max_csm is not None and csmlist[imin] > max_csm: + return None return cglist[imin], csmlist[imin] def minimum_geometries(self, n=None, symmetry_measure_type=None, max_csm=None): diff --git a/pymatgen/analysis/chemenv/coordination_environments/tests/test_coordination_geometry_finder.py b/pymatgen/analysis/chemenv/coordination_environments/tests/test_coordination_geometry_finder.py index 107dc3e1ee3..c039b6c3a6f 100644 --- a/pymatgen/analysis/chemenv/coordination_environments/tests/test_coordination_geometry_finder.py +++ b/pymatgen/analysis/chemenv/coordination_environments/tests/test_coordination_geometry_finder.py @@ -122,13 +122,7 @@ def test_abstract_geometry(self): res = self.lgf.coordination_geometry_symmetry_measures_fallback_random( coordination_geometry=cg_tet, NRANDOM=5, points_perfect=points_perfect_tet ) - ( - permutations_symmetry_measures, - permutations, - algos, - local2perfect_maps, - perfect2local_maps, - ) = res + permutations_symmetry_measures, permutations, algos, local2perfect_maps, perfect2local_maps = res for perm_csm_dict in permutations_symmetry_measures: assert perm_csm_dict["symmetry_measure"] == pytest.approx(0.140355832317) diff --git a/pymatgen/analysis/chemenv/coordination_environments/tests/test_read_write.py b/pymatgen/analysis/chemenv/coordination_environments/tests/test_read_write.py index ed88b501e0b..c7e300a5f5f 100644 --- a/pymatgen/analysis/chemenv/coordination_environments/tests/test_read_write.py +++ b/pymatgen/analysis/chemenv/coordination_environments/tests/test_read_write.py @@ -162,14 +162,14 @@ def test_structure_environments_neighbors_sets(self): str(nb_set) == "Neighbors Set for site #6 :\n - Coordination number : 4\n - Voronoi indices : 1, 4, 5, 6\n" ) - assert not nb_set != nb_set + assert nb_set == nb_set assert hash(nb_set) == 4 def test_strategies(self): simplest_strategy_1 = SimplestChemenvStrategy() simplest_strategy_2 = SimplestChemenvStrategy(distance_cutoff=1.5, angle_cutoff=0.5) - assert not simplest_strategy_1 == simplest_strategy_2 + assert simplest_strategy_1 != simplest_strategy_2 simplest_strategy_1_from_dict = SimplestChemenvStrategy.from_dict(simplest_strategy_1.as_dict()) assert simplest_strategy_1, simplest_strategy_1_from_dict @@ -246,10 +246,10 @@ def test_strategies(self): multi_weights_strategy_1_from_dict = MultiWeightsChemenvStrategy.from_dict(multi_weights_strategy_1.as_dict()) assert multi_weights_strategy_1 == multi_weights_strategy_1_from_dict - assert not simplest_strategy_1 == multi_weights_strategy_1 - assert not multi_weights_strategy_1 == multi_weights_strategy_2 - assert not multi_weights_strategy_1 == multi_weights_strategy_3 - assert not multi_weights_strategy_2 == multi_weights_strategy_3 + assert simplest_strategy_1 != multi_weights_strategy_1 + assert multi_weights_strategy_1 != multi_weights_strategy_2 + assert multi_weights_strategy_1 != multi_weights_strategy_3 + assert multi_weights_strategy_2 != multi_weights_strategy_3 def test_read_write_voronoi(self): with open(f"{json_files_dir}/test_T--4_FePO4_icsd_4266.json") as f: diff --git a/pymatgen/analysis/chemenv/coordination_environments/tests/test_structure_environments.py b/pymatgen/analysis/chemenv/coordination_environments/tests/test_structure_environments.py index ff3cdae0924..73d1e746e3c 100644 --- a/pymatgen/analysis/chemenv/coordination_environments/tests/test_structure_environments.py +++ b/pymatgen/analysis/chemenv/coordination_environments/tests/test_structure_environments.py @@ -106,7 +106,7 @@ def test_structure_environments(self): assert len(se.differences_wrt(se)) == 0 - assert not se != se + assert se == se ce = se.ce_list[isite][4][0] @@ -148,7 +148,7 @@ def test_structure_environments(self): assert ce.is_close_to(ce2, rtol=0.01, atol=1e-4) assert not ce.is_close_to(ce2, rtol=0.0, atol=1e-8) - assert not ce == ce2 + assert ce != ce2 assert ce != ce2 def test_light_structure_environments(self): @@ -185,7 +185,7 @@ def test_light_structure_environments(self): assert len(nb_set) == 4 assert hash(nb_set) == 4 - assert not nb_set != nb_set + assert nb_set == nb_set assert ( str(nb_set) == "Neighbors Set for site #6 :\n" @@ -235,7 +235,7 @@ def test_light_structure_environments(self): assert lse.structure_contains_atom_environment(atom_symbol="Si", ce_symbol="T:4") assert not lse.structure_contains_atom_environment(atom_symbol="O", ce_symbol="T:4") assert lse.uniquely_determines_coordination_environments - assert not lse != lse + assert lse == lse envs = lse.strategy.get_site_coordination_environments(lse.structure[6]) assert len(envs) == 1 diff --git a/pymatgen/analysis/chemenv/coordination_environments/voronoi.py b/pymatgen/analysis/chemenv/coordination_environments/voronoi.py index e39a0cf408c..a91ccd6c16a 100644 --- a/pymatgen/analysis/chemenv/coordination_environments/voronoi.py +++ b/pymatgen/analysis/chemenv/coordination_environments/voronoi.py @@ -114,10 +114,7 @@ def __init__( self.valences = valences self.maximum_distance_factor = maximum_distance_factor self.minimum_angle_factor = minimum_angle_factor - if isites is None: - indices = list(range(len(structure))) - else: - indices = isites + indices = list(range(len(structure))) if isites is None else isites self.structure = structure logging.debug("Setting Voronoi list") if voronoi_list2 is not None: @@ -235,13 +232,12 @@ def setup_neighbors_distances_and_angles(self, indices): dnb_indices = {int(isorted_distances[0])} for idist in iter(isorted_distances): wd = normalized_distances[idist] - if self.maximum_distance_factor is not None: - if wd > self.maximum_distance_factor: - self.neighbors_normalized_distances[isite][icurrent]["nb_indices"] = list(nb_indices) - self.neighbors_distances[isite][icurrent]["nb_indices"] = list(nb_indices) - self.neighbors_normalized_distances[isite][icurrent]["dnb_indices"] = list(dnb_indices) - self.neighbors_distances[isite][icurrent]["dnb_indices"] = list(dnb_indices) - break + if self.maximum_distance_factor is not None and wd > self.maximum_distance_factor: + self.neighbors_normalized_distances[isite][icurrent]["nb_indices"] = list(nb_indices) + self.neighbors_distances[isite][icurrent]["nb_indices"] = list(nb_indices) + self.neighbors_normalized_distances[isite][icurrent]["dnb_indices"] = list(dnb_indices) + self.neighbors_distances[isite][icurrent]["dnb_indices"] = list(dnb_indices) + break if np.isclose( wd, self.neighbors_normalized_distances[isite][icurrent]["max"], @@ -306,13 +302,12 @@ def setup_neighbors_distances_and_angles(self, indices): dnb_indices = {int(isorted_angles[0])} for iang in iter(isorted_angles): wa = normalized_angles[iang] - if self.minimum_angle_factor is not None: - if wa < self.minimum_angle_factor: - self.neighbors_normalized_angles[isite][icurrent]["nb_indices"] = list(nb_indices) - self.neighbors_angles[isite][icurrent]["nb_indices"] = list(nb_indices) - self.neighbors_normalized_angles[isite][icurrent]["dnb_indices"] = list(dnb_indices) - self.neighbors_angles[isite][icurrent]["dnb_indices"] = list(dnb_indices) - break + if self.minimum_angle_factor is not None and wa < self.minimum_angle_factor: + self.neighbors_normalized_angles[isite][icurrent]["nb_indices"] = list(nb_indices) + self.neighbors_angles[isite][icurrent]["nb_indices"] = list(nb_indices) + self.neighbors_normalized_angles[isite][icurrent]["dnb_indices"] = list(dnb_indices) + self.neighbors_angles[isite][icurrent]["dnb_indices"] = list(dnb_indices) + break if np.isclose( wa, self.neighbors_normalized_angles[isite][icurrent]["min"], @@ -344,10 +339,7 @@ def setup_neighbors_distances_and_angles(self, indices): nang_dict = self.neighbors_normalized_angles[isite][iang] nang_dict_next = self.neighbors_normalized_angles[isite][iang + 1] nang_dict["next"] = nang_dict_next["max"] - if self.minimum_angle_factor is not None: - afact = self.minimum_angle_factor - else: - afact = 0.0 + afact = self.minimum_angle_factor if self.minimum_angle_factor is not None else 0.0 self.neighbors_normalized_angles[isite][-1]["next"] = afact self.neighbors_angles[isite][-1]["next"] = afact * self.neighbors_angles[isite][0]["max"] @@ -499,14 +491,8 @@ def neighbors_surfaces_bounded(self, isite, surface_calculation_options=None): dp2 = distance_bounds[idp + 1] if dp2 < mindist or dp1 > maxdist: continue - if dp1 < mindist: - d1 = mindist - else: - d1 = dp1 - if dp2 > maxdist: - d2 = maxdist - else: - d2 = dp2 + d1 = mindist if dp1 < mindist else dp1 + d2 = maxdist if dp2 > maxdist else dp2 for iap in range(len(angle_bounds) - 1): ap1 = angle_bounds[iap] ap2 = angle_bounds[iap + 1] @@ -822,15 +808,9 @@ def dp_func(dp): step_function = {"type": "normal_cdf", "scale": 0.0001} # Initializes the figure - if figsize is None: - fig = plt.figure() - else: - fig = plt.figure(figsize=figsize) + fig = plt.figure() if figsize is None else plt.figure(figsize=figsize) subplot = fig.add_subplot(111) - if normalized: - dists = self.neighbors_normalized_distances[isite] - else: - dists = self.neighbors_distances[isite] + dists = self.neighbors_normalized_distances[isite] if normalized else self.neighbors_distances[isite] if step_function["type"] == "step_function": isorted = np.argsort([dd["min"] for dd in dists]) @@ -862,6 +842,7 @@ def dp_func(dp): def get_sadf_figure(self, isite, normalized=True, figsize=None, step_function=None): """ Get the Solid Angle Distribution Figure for a given site. + Args: isite: Index of the site. normalized: Whether to normalize angles. @@ -881,15 +862,9 @@ def ap_func(ap): step_function = {"type": "step_function", "scale": 0.0001} # Initializes the figure - if figsize is None: - fig = plt.figure() - else: - fig = plt.figure(figsize=figsize) + fig = plt.figure() if figsize is None else plt.figure(figsize=figsize) subplot = fig.add_subplot(111) - if normalized: - angs = self.neighbors_normalized_angles[isite] - else: - angs = self.neighbors_angles[isite] + angs = self.neighbors_normalized_angles[isite] if normalized else self.neighbors_angles[isite] if step_function["type"] == "step_function": isorted = np.argsort([ap_func(aa["min"]) for aa in angs]) diff --git a/pymatgen/analysis/chemenv/utils/coordination_geometry_utils.py b/pymatgen/analysis/chemenv/utils/coordination_geometry_utils.py index b4c71130a9c..d1a3be7296a 100644 --- a/pymatgen/analysis/chemenv/utils/coordination_geometry_utils.py +++ b/pymatgen/analysis/chemenv/utils/coordination_geometry_utils.py @@ -339,14 +339,8 @@ def rectangle_surface_intersection( raise NotImplementedError("Bounds should be given right now ...") if x2 < bounds_lower[0] or x1 > bounds_lower[1]: return 0.0, 0.0 - if x1 < bounds_lower[0]: - xmin = bounds_lower[0] - else: - xmin = x1 - if x2 > bounds_lower[1]: - xmax = bounds_lower[1] - else: - xmax = x2 + xmin = bounds_lower[0] if x1 < bounds_lower[0] else x1 + xmax = bounds_lower[1] if x2 > bounds_lower[1] else x2 def diff(x): flwx = f_lower(x) @@ -706,10 +700,7 @@ def is_in_list(self, plane_list) -> bool: :param plane_list: List of Planes to be compared to :return: True if the plane is in the list, False otherwise """ - for plane in plane_list: - if self.is_same_plane_as(plane): - return True - return False + return any(self.is_same_plane_as(plane) for plane in plane_list) def indices_separate(self, points, dist_tolerance): """ diff --git a/pymatgen/analysis/chemenv/utils/graph_utils.py b/pymatgen/analysis/chemenv/utils/graph_utils.py index 3789fbf50b5..74b5874c74e 100644 --- a/pymatgen/analysis/chemenv/utils/graph_utils.py +++ b/pymatgen/analysis/chemenv/utils/graph_utils.py @@ -83,7 +83,6 @@ def _c2index_isreverse(c1, c2): cycle c2 : if it is *just after* the c2_0_index, reverse is False, if it is *just before* the c2_0_index, reverse is True, otherwise the function returns None). """ - c1_0 = c1.nodes[0] c1_1 = c1.nodes[1] if c1_0 not in c2.nodes: diff --git a/pymatgen/analysis/chemenv/utils/math_utils.py b/pymatgen/analysis/chemenv/utils/math_utils.py index 1737b9091dc..3d6b2e1627b 100644 --- a/pymatgen/analysis/chemenv/utils/math_utils.py +++ b/pymatgen/analysis/chemenv/utils/math_utils.py @@ -33,7 +33,7 @@ def _append_es2sequences(sequences, es): result.append([e]) else: for e in es: - result += [seq + [e] for seq in sequences] + result += [[*seq, e] for seq in sequences] return result @@ -307,10 +307,7 @@ def power2_tangent_decreasing(xx, edges=None, prefactor=None): :return: """ if edges is None: - if prefactor is None: - aa = 1.0 / np.power(-1.0, 2) - else: - aa = prefactor + aa = 1.0 / np.power(-1.0, 2) if prefactor is None else prefactor return -aa * np.power(xx - 1.0, 2) * np.tan((xx - 1.0) * np.pi / 2.0) # pylint: disable=E1130 xx_scaled_and_clamped = scale_and_clamp(xx, edges[0], edges[1], 0.0, 1.0) @@ -325,10 +322,7 @@ def power2_inverse_decreasing(xx, edges=None, prefactor=None): :return: """ if edges is None: - if prefactor is None: - aa = 1.0 / np.power(-1.0, 2) - else: - aa = prefactor + aa = 1.0 / np.power(-1.0, 2) if prefactor is None else prefactor return np.where(np.isclose(xx, 0.0), aa * float("inf"), aa * np.power(xx - 1.0, 2) / xx) # return aa * np.power(xx-1.0, 2) / xx if xx != 0 else aa * float("inf") xx_scaled_and_clamped = scale_and_clamp(xx, edges[0], edges[1], 0.0, 1.0) @@ -343,10 +337,7 @@ def power2_inverse_power2_decreasing(xx, edges=None, prefactor=None): :return: """ if edges is None: - if prefactor is None: - aa = 1.0 / np.power(-1.0, 2) - else: - aa = prefactor + aa = 1.0 / np.power(-1.0, 2) if prefactor is None else prefactor return np.where( np.isclose(xx, 0.0), aa * float("inf"), @@ -365,10 +356,7 @@ def power2_inverse_powern_decreasing(xx, edges=None, prefactor=None, powern=2.0) :return: """ if edges is None: - if prefactor is None: - aa = 1.0 / np.power(-1.0, 2) - else: - aa = prefactor + aa = 1.0 / np.power(-1.0, 2) if prefactor is None else prefactor return aa * np.power(xx - 1.0, 2) / xx**powern xx_scaled_and_clamped = scale_and_clamp(xx, edges[0], edges[1], 0.0, 1.0) diff --git a/pymatgen/analysis/chemenv/utils/scripts_utils.py b/pymatgen/analysis/chemenv/utils/scripts_utils.py index 0241c517af7..afbf1fac751 100644 --- a/pymatgen/analysis/chemenv/utils/scripts_utils.py +++ b/pymatgen/analysis/chemenv/utils/scripts_utils.py @@ -101,30 +101,26 @@ def draw_cg( if len(neighbors) < 3: if show_distorted: vis.add_bonds(neighbors, site, color=[0.0, 1.0, 0.0], opacity=0.4, radius=0.175) - if show_perfect: - if len(neighbors) == 2: - perfect_geometry = AbstractGeometry.from_cg(cg) - trans = csm_info["other_symmetry_measures"][f"translation_vector_{csm_suffix}"] - rot = csm_info["other_symmetry_measures"][f"rotation_matrix_{csm_suffix}"] - scale = csm_info["other_symmetry_measures"][f"scaling_factor_{csm_suffix}"] - points = perfect_geometry.points_wcs_ctwcc() - rotated_points = rotateCoords(points, rot) - points = [scale * pp + trans for pp in rotated_points] - if "wcs" in csm_suffix: - ef_points = points[1:] - else: - ef_points = points - edges = cg.edges(ef_points, input="coords") - vis.add_edges(edges, color=[1.0, 0.0, 0.0]) - for point in points: - vis.add_partial_sphere( - coords=point, - radius=perf_radius, - color=[0.0, 0.0, 0.0], - start=0, - end=360, - opacity=1, - ) + if show_perfect and len(neighbors) == 2: + perfect_geometry = AbstractGeometry.from_cg(cg) + trans = csm_info["other_symmetry_measures"][f"translation_vector_{csm_suffix}"] + rot = csm_info["other_symmetry_measures"][f"rotation_matrix_{csm_suffix}"] + scale = csm_info["other_symmetry_measures"][f"scaling_factor_{csm_suffix}"] + points = perfect_geometry.points_wcs_ctwcc() + rotated_points = rotateCoords(points, rot) + points = [scale * pp + trans for pp in rotated_points] + ef_points = points[1:] if "wcs" in csm_suffix else points + edges = cg.edges(ef_points, input="coords") + vis.add_edges(edges, color=[1.0, 0.0, 0.0]) + for point in points: + vis.add_partial_sphere( + coords=point, + radius=perf_radius, + color=[0.0, 0.0, 0.0], + start=0, + end=360, + opacity=1, + ) else: if show_distorted: if perm is not None: @@ -151,10 +147,7 @@ def draw_cg( points = perfect_geometry.points_wcs_ctwcc() rotated_points = rotateCoords(points, rot) points = [scale * pp + trans for pp in rotated_points] - if "wcs" in csm_suffix: - ef_points = points[1:] - else: - ef_points = points + ef_points = points[1:] if "wcs" in csm_suffix else points edges = cg.edges(ef_points, input="coords") vis.add_edges(edges, color=[1.0, 0.0, 0.0]) for point in points: diff --git a/pymatgen/analysis/chemenv/utils/tests/test_coordination_geometry_utils.py b/pymatgen/analysis/chemenv/utils/tests/test_coordination_geometry_utils.py index 4db6148c57f..9038bc369b1 100644 --- a/pymatgen/analysis/chemenv/utils/tests/test_coordination_geometry_utils.py +++ b/pymatgen/analysis/chemenv/utils/tests/test_coordination_geometry_utils.py @@ -23,7 +23,7 @@ def setUp(self): def test_factors_abcd_normal_vector(self): factors = self.plane.coefficients / self.expected_coefficients - self.assertArrayAlmostEqual([factors[0]] * 4, [ff for ff in factors]) + self.assertArrayAlmostEqual([factors[0]] * 4, list(factors)) assert np.allclose([2.0 / 3.0, 1.0 / 3.0, -2.0 / 3.0], self.plane.normal_vector) def test_from_npoints_plane(self): diff --git a/pymatgen/analysis/chemenv/utils/tests/test_graph_utils.py b/pymatgen/analysis/chemenv/utils/tests/test_graph_utils.py index 923a6a4d9e1..f43139ace30 100644 --- a/pymatgen/analysis/chemenv/utils/tests/test_graph_utils.py +++ b/pymatgen/analysis/chemenv/utils/tests/test_graph_utils.py @@ -307,7 +307,7 @@ def test_simple_graph_cycle(self): assert not sgc.ordered sgc.order() assert sgc.ordered - assert sgc.nodes == tuple([FakeNodeWithEqLtMethods(85)]) + assert sgc.nodes == (FakeNodeWithEqLtMethods(85),) sgc = SimpleGraphCycle( [ @@ -326,17 +326,15 @@ def test_simple_graph_cycle(self): assert not sgc.ordered sgc.order() assert sgc.ordered - assert sgc.nodes == tuple( - [ - FakeNodeWithEqLtMethods(1), - FakeNodeWithEqLtMethods(4), - FakeNodeWithEqLtMethods(3), - FakeNodeWithEqLtMethods(6), - FakeNodeWithEqLtMethods(2), - FakeNodeWithEqLtMethods(8), - FakeNodeWithEqLtMethods(32), - FakeNodeWithEqLtMethods(64), - ] + assert sgc.nodes == ( + FakeNodeWithEqLtMethods(1), + FakeNodeWithEqLtMethods(4), + FakeNodeWithEqLtMethods(3), + FakeNodeWithEqLtMethods(6), + FakeNodeWithEqLtMethods(2), + FakeNodeWithEqLtMethods(8), + FakeNodeWithEqLtMethods(32), + FakeNodeWithEqLtMethods(64), ) # Test str method @@ -375,7 +373,7 @@ def test_multigraph_cycle(self): # Test different cycle lengths inequality mg_cycle2 = MultiGraphCycle([2, 3, 4, 5, 6], [1, 0, 2, 0, 0]) - assert not mg_cycle1 == mg_cycle2 + assert mg_cycle1 != mg_cycle2 # Test equality mg_cycle2 = MultiGraphCycle([2, 4, 3, 5], [1, 0, 2, 0]) @@ -397,26 +395,26 @@ def test_multigraph_cycle(self): assert mg_cycle1 == mg_cycle2 # Test inequality mg_cycle2 = MultiGraphCycle([2, 5, 3, 4], [0, 1, 0, 1]) - assert not mg_cycle1 == mg_cycle2 + assert mg_cycle1 != mg_cycle2 mg_cycle2 = MultiGraphCycle([2, 5, 3, 4], [1, 0, 2, 0]) - assert not mg_cycle1 == mg_cycle2 + assert mg_cycle1 != mg_cycle2 mg_cycle2 = MultiGraphCycle([3, 5, 2, 4], [1, 0, 2, 0]) - assert not mg_cycle1 == mg_cycle2 + assert mg_cycle1 != mg_cycle2 # Test Self-loop case assert MultiGraphCycle([2], [1]) == MultiGraphCycle([2], [1]) - assert not MultiGraphCycle([1], [1]) == MultiGraphCycle([2], [1]) - assert not MultiGraphCycle([2], [1]) == MultiGraphCycle([2], [0]) - assert not MultiGraphCycle([2], [1]) == MultiGraphCycle([1], [1]) - assert not MultiGraphCycle([2], [0]) == MultiGraphCycle([2], [1]) + assert MultiGraphCycle([1], [1]) != MultiGraphCycle([2], [1]) + assert MultiGraphCycle([2], [1]) != MultiGraphCycle([2], [0]) + assert MultiGraphCycle([2], [1]) != MultiGraphCycle([1], [1]) + assert MultiGraphCycle([2], [0]) != MultiGraphCycle([2], [1]) # Test special case with two nodes assert MultiGraphCycle([2, 4], [1, 3]) == MultiGraphCycle([2, 4], [1, 3]) assert MultiGraphCycle([2, 4], [1, 3]) == MultiGraphCycle([2, 4], [3, 1]) assert MultiGraphCycle([2, 4], [1, 3]) == MultiGraphCycle([4, 2], [3, 1]) assert MultiGraphCycle([2, 4], [1, 3]) == MultiGraphCycle([4, 2], [1, 3]) - assert not MultiGraphCycle([2, 4], [1, 3]) == MultiGraphCycle([4, 2], [1, 2]) - assert not MultiGraphCycle([2, 4], [1, 3]) == MultiGraphCycle([4, 0], [1, 3]) + assert MultiGraphCycle([2, 4], [1, 3]) != MultiGraphCycle([4, 2], [1, 2]) + assert MultiGraphCycle([2, 4], [1, 3]) != MultiGraphCycle([4, 0], [1, 3]) # Test hashing function assert hash(mg_cycle1) == 4 @@ -604,8 +602,8 @@ def test_multigraph_cycle(self): assert not mgc.ordered mgc.order() assert mgc.ordered - assert mgc.nodes == tuple([FakeNodeWithEqLtMethods(85)]) - assert mgc.edge_indices == tuple([7]) + assert mgc.nodes == (FakeNodeWithEqLtMethods(85),) + assert mgc.edge_indices == (7,) mgc = MultiGraphCycle( [ @@ -625,19 +623,17 @@ def test_multigraph_cycle(self): assert not mgc.ordered mgc.order() assert mgc.ordered - assert mgc.nodes == tuple( - [ - FakeNodeWithEqLtMethods(1), - FakeNodeWithEqLtMethods(4), - FakeNodeWithEqLtMethods(3), - FakeNodeWithEqLtMethods(6), - FakeNodeWithEqLtMethods(2), - FakeNodeWithEqLtMethods(8), - FakeNodeWithEqLtMethods(32), - FakeNodeWithEqLtMethods(64), - ] + assert mgc.nodes == ( + FakeNodeWithEqLtMethods(1), + FakeNodeWithEqLtMethods(4), + FakeNodeWithEqLtMethods(3), + FakeNodeWithEqLtMethods(6), + FakeNodeWithEqLtMethods(2), + FakeNodeWithEqLtMethods(8), + FakeNodeWithEqLtMethods(32), + FakeNodeWithEqLtMethods(64), ) - assert mgc.edge_indices == tuple([0, 1, 4, 0, 2, 2, 5, 3]) + assert mgc.edge_indices == (0, 1, 4, 0, 2, 2, 5, 3) # Testing all cases for a length-4 cycle nodes_ref = tuple(FakeNodeWithEqLtMethods(inode) for inode in range(4)) @@ -654,7 +650,7 @@ def test_multigraph_cycle(self): ]: mgc = MultiGraphCycle( [FakeNodeWithEqLtMethods(inode) for inode in inodes], - edge_indices=[iedge for iedge in iedges], + edge_indices=list(iedges), ) strnodes = ", ".join(str(i) for i in inodes) assert mgc.nodes == nodes_ref, f"Nodes not equal for inodes = ({', '.join([str(i) for i in inodes])})" diff --git a/pymatgen/analysis/chempot_diagram.py b/pymatgen/analysis/chempot_diagram.py index 3cbb010cfe2..2cf672b7dd1 100644 --- a/pymatgen/analysis/chempot_diagram.py +++ b/pymatgen/analysis/chempot_diagram.py @@ -85,10 +85,10 @@ def __init__( default_min_limit (float): Default minimum chemical potential limit (i.e., lower bound) for unspecified elements within the "limits" argument. """ - self.entries = list(sorted(entries, key=lambda e: e.composition.reduced_composition)) + self.entries = sorted(entries, key=lambda e: e.composition.reduced_composition) self.limits = limits self.default_min_limit = default_min_limit - self.elements = list(sorted({els for e in self.entries for els in e.composition.elements})) + self.elements = sorted({els for e in self.entries for els in e.composition.elements}) self.dim = len(self.elements) self._min_entries, self._el_refs = self._get_min_entries_and_el_refs(self.entries) self._entry_dict = {e.composition.reduced_formula: e for e in self._min_entries} @@ -293,7 +293,6 @@ def _get_3d_plot( element_padding: float | None, ) -> Figure: """Returns a Plotly figure for a 3-dimensional chemical potential diagram.""" - if not formulas_to_draw: formulas_to_draw = [] @@ -319,15 +318,14 @@ def _get_3d_plot( contains_target_elems = set(entry.composition.elements).issubset(elements) - if formulas_to_draw: - if entry.composition.reduced_composition in draw_comps: - domain_simplexes[formula] = None - draw_domains[formula] = pts_3d + if formulas_to_draw and entry.composition.reduced_composition in draw_comps: + domain_simplexes[formula] = None + draw_domains[formula] = pts_3d - if contains_target_elems: - domains[formula] = pts_3d - else: - continue + if contains_target_elems: + domains[formula] = pts_3d + else: + continue if not contains_target_elems: domain_simplexes[formula] = None @@ -350,11 +348,11 @@ def _get_3d_plot( if label_stable: layout["scene"].update({"annotations": annotations}) - layout["scene_camera"] = dict( - eye=dict(x=5, y=5, z=5), # zoomed out - projection=dict(type="orthographic"), - center=dict(x=0, y=0, z=0), - ) + layout["scene_camera"] = { + "eye": {"x": 5, "y": 5, "z": 5}, # zoomed out + "projection": {"type": "orthographic"}, + "center": {"x": 0, "y": 0, "z": 0}, + } data = self._get_3d_domain_lines(domain_simplexes) @@ -402,15 +400,15 @@ def _get_2d_domain_lines(draw_domains) -> list[Scatter]: x, y = [], [] for pts in draw_domains.values(): - x.extend(pts[:, 0].tolist() + [None]) - y.extend(pts[:, 1].tolist() + [None]) + x.extend([*pts[:, 0].tolist(), None]) + y.extend([*pts[:, 1].tolist(), None]) lines = [ Scatter( x=x, y=y, mode="lines+markers", - line=dict(color="black", width=3), + line={"color": "black", "width": 3}, showlegend=False, ) ] @@ -426,9 +424,9 @@ def _get_3d_domain_lines(domains: dict[str, list[Simplex] | None]) -> list[Scatt for simplexes in domains.values(): if simplexes: for s in simplexes: - x.extend(s.coords[:, 0].tolist() + [None]) - y.extend(s.coords[:, 1].tolist() + [None]) - z.extend(s.coords[:, 2].tolist() + [None]) + x.extend([*s.coords[:, 0].tolist(), None]) + y.extend([*s.coords[:, 1].tolist(), None]) + z.extend([*s.coords[:, 2].tolist(), None]) lines = [ Scatter3d( @@ -436,7 +434,7 @@ def _get_3d_domain_lines(domains: dict[str, list[Simplex] | None]) -> list[Scatt y=y, z=z, mode="lines", - line=dict(color="black", width=4.5), + line={"color": "black", "width": 4.5}, showlegend=False, ) ] @@ -482,7 +480,7 @@ def _get_3d_formula_meshes( z=points_3d[:, 2], alphahull=0, showlegend=True, - lighting=dict(fresnel=1.0), + lighting={"fresnel": 1.0}, color=formula_colors[idx], name=f"{formula} (mesh)", opacity=0.13, @@ -507,9 +505,9 @@ def _get_3d_formula_lines( x, y, z = [], [], [] for s in simplexes: - x.extend(s.coords[:, 0].tolist() + [None]) - y.extend(s.coords[:, 1].tolist() + [None]) - z.extend(s.coords[:, 2].tolist() + [None]) + x.extend([*s.coords[:, 0].tolist(), None]) + y.extend([*s.coords[:, 1].tolist(), None]) + z.extend([*s.coords[:, 2].tolist(), None]) line = Scatter3d( x=x, @@ -702,10 +700,7 @@ def get_2d_orthonormal_vector(line_pts: np.ndarray) -> np.ndarray: x_diff = abs(x[1] - x[0]) y_diff = abs(y[1] - y[0]) - if np.isclose(x_diff, 0): - theta = np.pi / 2 - else: - theta = np.arctan(y_diff / x_diff) + theta = np.pi / 2 if np.isclose(x_diff, 0) else np.arctan(y_diff / x_diff) vec = np.array([np.sin(theta), np.cos(theta)]) diff --git a/pymatgen/analysis/diffraction/tem.py b/pymatgen/analysis/diffraction/tem.py index 8b9cbd92e4a..fad6e98c81b 100644 --- a/pymatgen/analysis/diffraction/tem.py +++ b/pymatgen/analysis/diffraction/tem.py @@ -83,6 +83,7 @@ def wavelength_rel(self) -> float: """ Calculates the wavelength of the electron beam with relativistic kinematic effects taken into account. + Args: none Returns: @@ -101,9 +102,11 @@ def wavelength_rel(self) -> float: def generate_points(coord_left: int = -10, coord_right: int = 10) -> np.ndarray: """ Generates a bunch of 3D points that span a cube. + Args: coord_left (int): The minimum coordinate value. coord_right (int): The maximum coordinate value. + Returns: Numpy 2d array """ @@ -119,9 +122,11 @@ def zone_axis_filter( ) -> list[tuple[int, int, int]]: """ Filters out all points that exist within the specified Laue zone according to the zone axis rule. + Args: points (np.ndarray): The list of points to be filtered. laue_zone (int): The desired Laue zone. + Returns: list of 3-tuples """ @@ -141,13 +146,14 @@ def get_interplanar_spacings( Args: structure (Structure): the input structure. points (tuple): the desired hkl indices. + Returns: Dict of hkl to its interplanar spacing, in angstroms (float). """ points_filtered = self.zone_axis_filter(points) if (0, 0, 0) in points_filtered: points_filtered.remove((0, 0, 0)) - interplanar_spacings_val = np.array(list(map(lambda x: structure.lattice.d_hkl(x), points_filtered))) + interplanar_spacings_val = np.array([structure.lattice.d_hkl(x) for x in points_filtered]) interplanar_spacings = dict(zip(points_filtered, interplanar_spacings_val)) return interplanar_spacings @@ -156,6 +162,7 @@ def bragg_angles( ) -> dict[tuple[int, int, int], float]: """ Gets the Bragg angles for every hkl point passed in (where n = 1). + Args: interplanar_spacings (dict): dictionary of hkl to interplanar spacing Returns: @@ -170,8 +177,10 @@ def bragg_angles( def get_s2(self, bragg_angles: dict[tuple[int, int, int], float]) -> dict[tuple[int, int, int], float]: """ Calculates the s squared parameter (= square of sin theta over lambda) for each hkl plane. + Args: bragg_angles (dict): The bragg angles for each hkl plane. + Returns: Dict of hkl plane to s2 parameter, calculates the s squared parameter (= square of sin theta over lambda). @@ -188,9 +197,11 @@ def x_ray_factors( """ Calculates x-ray factors, which are required to calculate atomic scattering factors. Method partially inspired by the equivalent process in the xrd module. + Args: structure (Structure): The input structure. bragg_angles (dict): Dictionary of hkl plane to Bragg angle. + Returns: dict of atomic symbol to another dict of hkl plane to x-ray factor (in angstroms). """ @@ -214,9 +225,11 @@ def electron_scattering_factors( ) -> dict[str, dict[tuple[int, int, int], float]]: """ Calculates atomic scattering factors for electrons using the Mott-Bethe formula (1st order Born approximation). + Args: structure (Structure): The input structure. bragg_angles (dict of 3-tuple to float): The Bragg angles for each hkl plane. + Returns: dict from atomic symbol to another dict of hkl plane to factor (in angstroms) """ @@ -239,9 +252,11 @@ def cell_scattering_factors( ) -> dict[tuple[int, int, int], int]: """ Calculates the scattering factor for the whole cell. + Args: structure (Structure): The input structure. bragg_angles (dict of 3-tuple to float): The Bragg angles for each hkl plane. + Returns: dict of hkl plane (3-tuple) to scattering factor (in angstroms). """ @@ -264,9 +279,11 @@ def cell_intensity( ) -> dict[tuple[int, int, int], float]: """ Calculates cell intensity for each hkl plane. For simplicity's sake, take I = |F|**2. + Args: structure (Structure): The input structure. bragg_angles (dict of 3-tuple to float): The Bragg angles for each hkl plane. + Returns: dict of hkl plane to cell intensity """ @@ -284,6 +301,7 @@ def get_pattern( ) -> pd.DataFrame: """ Returns all relevant TEM DP info in a pandas dataframe. + Args: structure (Structure): The input structure. scaled (bool): Required value for inheritance, does nothing in TEM pattern @@ -321,9 +339,11 @@ def normalized_cell_intensity( ) -> dict[tuple[int, int, int], float]: """ Normalizes the cell_intensity dict to 1, for use in plotting. + Args: structure (Structure): The input structure. bragg_angles (dict of 3-tuple to float): The Bragg angles for each hkl plane. + Returns: dict of hkl plane to normalized cell intensity """ @@ -343,10 +363,12 @@ def is_parallel( ) -> bool: """ Checks if two hkl planes are parallel in reciprocal space. + Args: structure (Structure): The input structure. plane (3-tuple): The first plane to be compared. other_plane (3-tuple): The other plane to be compared. + Returns: boolean """ @@ -356,9 +378,11 @@ def is_parallel( def get_first_point(self, structure: Structure, points: list) -> dict[tuple[int, int, int], float]: """ Gets the first point to be plotted in the 2D DP, corresponding to maximum d/minimum R. + Args: structure (Structure): The input structure. points (list): All points to be checked. + Returns: dict of a hkl plane to max interplanar distance. """ @@ -377,6 +401,7 @@ def get_interplanar_angle(structure: Structure, p1: tuple[int, int, int], p2: tu """ Returns the interplanar angle (in degrees) between the normal of two crystal planes. Formulas from International Tables for Crystallography Volume C pp. 2-9. + Args: structure (Structure): The input structure. p1 (3-tuple): plane 1 @@ -433,10 +458,12 @@ def get_plot_coeffs( """ Calculates coefficients of the vector addition required to generate positions for each DP point by the Moore-Penrose inverse method. + Args: p1 (3-tuple): The first point. Fixed. p2 (3-tuple): The second point. Fixed. p3 (3-tuple): The point whose coefficients are to be calculted. + Returns: Numpy array """ @@ -450,9 +477,11 @@ def get_positions(self, structure: Structure, points: list) -> dict[tuple[int, i """ Calculates all the positions of each hkl point in the 2D diffraction pattern by vector addition. Distance in centimeters. + Args: structure (Structure): The input structure. points (list): All points to be checked. + Returns: dict of hkl plane to xy-coordinates. """ @@ -500,9 +529,11 @@ def get_positions(self, structure: Structure, points: list) -> dict[tuple[int, i def tem_dots(self, structure: Structure, points) -> list: """ Generates all TEM_dot as named tuples that will appear on the 2D diffraction pattern. + Args: structure (Structure): The input structure. points (list): All points to be checked. + Returns: list of TEM_dots """ @@ -523,8 +554,10 @@ def tem_dots(self, structure: Structure, points) -> list: def get_plot_2d(self, structure: Structure) -> go.Figure: """ Generates the 2D diffraction pattern of the input structure. + Args: structure (Structure): The input structure. + Returns: Figure """ @@ -550,13 +583,13 @@ def get_plot_2d(self, structure: Structure) -> go.Figure: text=hkls, hoverinfo="text", mode="markers", - marker=dict( - size=8, - cmax=1, - cmin=0, - color=intensities, - colorscale=[[0, "black"], [1.0, "white"]], - ), + marker={ + "size": 8, + "cmax": 1, + "cmin": 0, + "color": intensities, + "colorscale": [[0, "black"], [1.0, "white"]], + }, showlegend=False, ), go.Scatter( @@ -565,30 +598,30 @@ def get_plot_2d(self, structure: Structure) -> go.Figure: text="(0, 0, 0): Direct beam", hoverinfo="text", mode="markers", - marker=dict(size=14, cmax=1, cmin=0, color="white"), + marker={"size": 14, "cmax": 1, "cmin": 0, "color": "white"}, showlegend=False, ), ] layout = go.Layout( title="2D Diffraction Pattern
Beam Direction: " + "".join(str(e) for e in self.beam_direction), - font=dict(size=14, color="#7f7f7f"), + font={"size": 14, "color": "#7f7f7f"}, hovermode="closest", - xaxis=dict( - range=[-4, 4], - showgrid=False, - zeroline=False, - showline=False, - ticks="", - showticklabels=False, - ), - yaxis=dict( - range=[-4, 4], - showgrid=False, - zeroline=False, - showline=False, - ticks="", - showticklabels=False, - ), + xaxis={ + "range": [-4, 4], + "showgrid": False, + "zeroline": False, + "showline": False, + "ticks": "", + "showticklabels": False, + }, + yaxis={ + "range": [-4, 4], + "showgrid": False, + "zeroline": False, + "showline": False, + "ticks": "", + "showticklabels": False, + }, width=550, height=550, paper_bgcolor="rgba(100,110,110,0.5)", @@ -601,8 +634,10 @@ def get_plot_2d_concise(self, structure: Structure) -> go.Figure: """ Generates the concise 2D diffraction pattern of the input structure of a smaller size and without layout. Does not display. + Args: structure (Structure): The input structure. + Returns: Figure """ @@ -628,33 +663,33 @@ def get_plot_2d_concise(self, structure: Structure) -> go.Figure: text=hkls, mode="markers", hoverinfo="skip", - marker=dict( - size=4, - cmax=1, - cmin=0, - color=intensities, - colorscale=[[0, "black"], [1.0, "white"]], - ), + marker={ + "size": 4, + "cmax": 1, + "cmin": 0, + "color": intensities, + "colorscale": [[0, "black"], [1.0, "white"]], + }, showlegend=False, ) ] layout = go.Layout( - xaxis=dict( - range=[-4, 4], - showgrid=False, - zeroline=False, - showline=False, - ticks="", - showticklabels=False, - ), - yaxis=dict( - range=[-4, 4], - showgrid=False, - zeroline=False, - showline=False, - ticks="", - showticklabels=False, - ), + xaxis={ + "range": [-4, 4], + "showgrid": False, + "zeroline": False, + "showline": False, + "ticks": "", + "showticklabels": False, + }, + yaxis={ + "range": [-4, 4], + "showgrid": False, + "zeroline": False, + "showline": False, + "ticks": "", + "showticklabels": False, + }, plot_bgcolor="black", margin={"l": 0, "r": 0, "t": 0, "b": 0}, width=121, diff --git a/pymatgen/analysis/diffraction/tests/test_tem.py b/pymatgen/analysis/diffraction/tests/test_tem.py index 319847e70be..b1e1c081d2a 100644 --- a/pymatgen/analysis/diffraction/tests/test_tem.py +++ b/pymatgen/analysis/diffraction/tests/test_tem.py @@ -265,8 +265,9 @@ def test_get_plot_2d_concise(self): structure = self.get_structure("Si") fig = c.get_plot_2d_concise(structure) width = fig.layout.width + assert width == 121 height = fig.layout.height - assert width == 121 and height == 121 + assert height == 121 if __name__ == "__main__": diff --git a/pymatgen/analysis/diffraction/xrd.py b/pymatgen/analysis/diffraction/xrd.py index 686a4e5baf3..997045f2d43 100644 --- a/pymatgen/analysis/diffraction/xrd.py +++ b/pymatgen/analysis/diffraction/xrd.py @@ -104,7 +104,7 @@ class XRDCalculator(AbstractDiffractionPatternCalculator): .. math:: - P( \theta) = \frac{1 + \cos^2(2 \theta)} + P( \theta) = \frac{1 + \cos^2(2 \theta)} { \sin^2( \theta) \cos( \theta)} """ diff --git a/pymatgen/analysis/dimensionality.py b/pymatgen/analysis/dimensionality.py index ad5231addf5..ce3b792c68b 100644 --- a/pymatgen/analysis/dimensionality.py +++ b/pymatgen/analysis/dimensionality.py @@ -350,10 +350,7 @@ def get_dimensionality_cheon( connected_list3 = find_connected_atoms(structure, tolerance=tolerance, ldict=ldict) max3, min3, clusters3 = find_clusters(structure, connected_list3) if min3 == min1: - if max3 == max1: - dim = "0D" - else: - dim = "intercalated molecule" + dim = "0D" if max3 == max1 else "intercalated molecule" else: dim = np.log2(float(max3) / max1) / np.log2(3) if dim == int(dim): @@ -367,10 +364,7 @@ def get_dimensionality_cheon( if min2 == 1: dim = "intercalated ion" elif min2 == min1: - if max2 == max1: - dim = "0D" - else: - dim = "intercalated molecule" + dim = "0D" if max2 == max1 else "intercalated molecule" else: dim = np.log2(float(max2) / max1) if dim == int(dim): @@ -381,10 +375,7 @@ def get_dimensionality_cheon( connected_list3 = find_connected_atoms(structure, tolerance=tolerance, ldict=ldict) max3, min3, clusters3 = find_clusters(structure, connected_list3) if min3 == min2: - if max3 == max2: - dim = "0D" - else: - dim = "intercalated molecule" + dim = "0D" if max3 == max2 else "intercalated molecule" else: dim = np.log2(float(max3) / max1) / np.log2(3) if dim == int(dim): diff --git a/pymatgen/analysis/elasticity/elastic.py b/pymatgen/analysis/elasticity/elastic.py index 375aa9cf758..d5c34dcaa30 100644 --- a/pymatgen/analysis/elasticity/elastic.py +++ b/pymatgen/analysis/elasticity/elastic.py @@ -975,10 +975,7 @@ def get_strain_state_dict(strains, stresses, eq_stress=None, tol: float = 1e-10, independent = {tuple(np.nonzero(vstrain)[0].tolist()) for vstrain in vstrains} strain_state_dict = {} if add_eq: - if eq_stress is not None: - veq_stress = Stress(eq_stress).voigt - else: - veq_stress = find_eq_stress(strains, stresses).voigt + veq_stress = Stress(eq_stress).voigt if eq_stress is not None else find_eq_stress(strains, stresses).voigt for ind in independent: # match strains with templates diff --git a/pymatgen/analysis/elasticity/tests/test_elastic.py b/pymatgen/analysis/elasticity/tests/test_elastic.py index 7fb6fb82f69..e9b084498b4 100644 --- a/pymatgen/analysis/elasticity/tests/test_elastic.py +++ b/pymatgen/analysis/elasticity/tests/test_elastic.py @@ -157,7 +157,7 @@ def test_structure_based_methods(self): test_et[0][0][0][0] = -100000 prop_dict = test_et.property_dict for attr_name in sprop_dict: - if attr_name not in (list(prop_dict) + ["structure"]): + if attr_name not in ([*prop_dict, "structure"]): with pytest.raises(ValueError): getattr(test_et, attr_name)(self.structure) with pytest.raises(ValueError): @@ -181,7 +181,7 @@ def test_new(self): def test_from_pseudoinverse(self): strain_list = [Strain.from_deformation(def_matrix) for def_matrix in self.def_stress_dict["deformations"]] - stress_list = [stress for stress in self.def_stress_dict["stresses"]] + stress_list = list(self.def_stress_dict["stresses"]) with warnings.catch_warnings(record=True): et_fl = -0.1 * ElasticTensor.from_pseudoinverse(strain_list, stress_list).voigt self.assertArrayAlmostEqual( diff --git a/pymatgen/analysis/eos.py b/pymatgen/analysis/eos.py index b180b2f0278..dfd25662e04 100644 --- a/pymatgen/analysis/eos.py +++ b/pymatgen/analysis/eos.py @@ -170,7 +170,7 @@ def results(self): Returns: dict """ - return dict(e0=self.e0, b0=self.b0, b1=self.b1, v0=self.v0) + return {"e0": self.e0, "b0": self.b0, "b1": self.b1, "v0": self.v0} def plot(self, width=8, height=None, plt=None, dpi=None, **kwargs): """ diff --git a/pymatgen/analysis/ewald.py b/pymatgen/analysis/ewald.py index 1db3550c91c..1c8341a1ed6 100644 --- a/pymatgen/analysis/ewald.py +++ b/pymatgen/analysis/ewald.py @@ -297,7 +297,8 @@ def get_site_energy(self, site_index): Args: site_index (int): Index of site ReturnS: - (float) - Energy of that site""" + (float) - Energy of that site + """ if not self._initialized: self._calc_ewald_terms() self._initialized = True @@ -669,10 +670,7 @@ def get_next_index(cls, matrix, manipulation, indices_left): f = manipulation[0] indices = list(indices_left.intersection(manipulation[2])) sums = np.sum(matrix[indices], axis=1) - if f < 1: - next_index = indices[sums.argmax(axis=0)] - else: - next_index = indices[sums.argmin(axis=0)] + next_index = indices[sums.argmax(axis=0)] if f < 1 else indices[sums.argmin(axis=0)] return next_index @@ -710,9 +708,8 @@ def _recurse(self, matrix, m_list, indices, output_m_list=None): if m_list[-1][1] > len(indices.intersection(m_list[-1][2])): return - if len(m_list) == 1 or m_list[-1][1] > 1: - if self.best_case(matrix, m_list, indices) > self._current_minimum: - return + if (len(m_list) == 1 or m_list[-1][1] > 1) and self.best_case(matrix, m_list, indices) > self._current_minimum: + return index = self.get_next_index(matrix, m_list[-1], indices) diff --git a/pymatgen/analysis/ferroelectricity/polarization.py b/pymatgen/analysis/ferroelectricity/polarization.py index 416122d0105..a5334fed269 100644 --- a/pymatgen/analysis/ferroelectricity/polarization.py +++ b/pymatgen/analysis/ferroelectricity/polarization.py @@ -98,7 +98,6 @@ def get_total_ionic_dipole(structure, zval_dict): center (np.array with shape [3,1]) : dipole center used by VASP tiny (float) : tolerance for determining boundary of calculation. """ - tot_ionic = [] for site in structure: zval = zval_dict[str(site.specie)] @@ -114,6 +113,7 @@ class PolarizationLattice(Structure): def get_nearest_site(self, coords, site, r=None): """ Given coords and a site, find closet site to coords. + Args: coords (3x1 array): Cartesian coords of center of sphere site: site to find closest to coords diff --git a/pymatgen/analysis/gb/grain.py b/pymatgen/analysis/gb/grain.py index dac510c8099..78b4e7fd57b 100644 --- a/pymatgen/analysis/gb/grain.py +++ b/pymatgen/analysis/gb/grain.py @@ -162,6 +162,7 @@ def get_sorted_structure(self, key=None, reverse=False): meaning as in list.sort. By default, sites are sorted by the electronegativity of the species. Note that Slab has to override this because of the different __init__ args. + Args: key: Specifies a function of one argument that is used to extract a comparison key from each list element: key=str.lower. The @@ -533,28 +534,21 @@ def gb_from_parameters( if reduce(gcd, rotation_axis) != 1: rotation_axis = [int(round(x / reduce(gcd, rotation_axis))) for x in rotation_axis] # transform four index notation to three index notation for plane - if plane is not None: - if len(plane) == 4: - u1 = plane[0] - v1 = plane[1] - w1 = plane[3] - plane = [u1, v1, w1] + if plane is not None and len(plane) == 4: + u1 = plane[0] + v1 = plane[1] + w1 = plane[3] + plane = [u1, v1, w1] # set the plane for grain boundary when plane is None. if plane is None: if lat_type.lower() == "c": plane = rotation_axis else: if lat_type.lower() == "h": - if ratio is None: - c2_a2_ratio = 1.0 - else: - c2_a2_ratio = ratio[0] / ratio[1] + c2_a2_ratio = 1.0 if ratio is None else ratio[0] / ratio[1] metric = np.array([[1, -0.5, 0], [-0.5, 1, 0], [0, 0, c2_a2_ratio]]) elif lat_type.lower() == "r": - if ratio is None: - cos_alpha = 0.5 - else: - cos_alpha = 1.0 / (ratio[0] / ratio[1] - 2) + cos_alpha = 0.5 if ratio is None else 1.0 / (ratio[0] / ratio[1] - 2) metric = np.array( [ [1, cos_alpha, cos_alpha], @@ -563,10 +557,7 @@ def gb_from_parameters( ] ) elif lat_type.lower() == "t": - if ratio is None: - c2_a2_ratio = 1.0 - else: - c2_a2_ratio = ratio[0] / ratio[1] + c2_a2_ratio = 1.0 if ratio is None else ratio[0] / ratio[1] metric = np.array([[1, 0, 0], [0, 1, 0], [0, 0, c2_a2_ratio]]) elif lat_type.lower() == "o": for i in range(3): @@ -811,11 +802,13 @@ def gb_from_parameters( def get_ratio(self, max_denominator=5, index_none=None): """ find the axial ratio needed for GB generator input. + Args: max_denominator (int): the maximum denominator for the computed ratio, default to be 5. index_none (int): specify the irrational axis. 0-a, 1-b, 2-c. Only may be needed for orthorhombic system. + Returns: axial ratio needed for GB generator (list of integers). """ @@ -930,6 +923,7 @@ def get_trans_mat( to the plane. quick_gen (bool): whether to quickly generate a supercell, if set to true, no need to find the smallest cell. + Returns: t1 (3 by 3 integer array): The transformation array for one grain. @@ -958,28 +952,21 @@ def get_trans_mat( if reduce(gcd, r_axis) != 1: r_axis = [int(round(x / reduce(gcd, r_axis))) for x in r_axis] - if surface is not None: - if len(surface) == 4: - u1 = surface[0] - v1 = surface[1] - w1 = surface[3] - surface = [u1, v1, w1] + if surface is not None and len(surface) == 4: + u1 = surface[0] + v1 = surface[1] + w1 = surface[3] + surface = [u1, v1, w1] # set the surface for grain boundary. if surface is None: if lat_type.lower() == "c": surface = r_axis else: if lat_type.lower() == "h": - if ratio is None: - c2_a2_ratio = 1.0 - else: - c2_a2_ratio = ratio[0] / ratio[1] + c2_a2_ratio = 1.0 if ratio is None else ratio[0] / ratio[1] metric = np.array([[1, -0.5, 0], [-0.5, 1, 0], [0, 0, c2_a2_ratio]]) elif lat_type.lower() == "r": - if ratio is None: - cos_alpha = 0.5 - else: - cos_alpha = 1.0 / (ratio[0] / ratio[1] - 2) + cos_alpha = 0.5 if ratio is None else 1.0 / (ratio[0] / ratio[1] - 2) metric = np.array( [ [1, cos_alpha, cos_alpha], @@ -988,10 +975,7 @@ def get_trans_mat( ] ) elif lat_type.lower() == "t": - if ratio is None: - c2_a2_ratio = 1.0 - else: - c2_a2_ratio = ratio[0] / ratio[1] + c2_a2_ratio = 1.0 if ratio is None else ratio[0] / ratio[1] metric = np.array([[1, 0, 0], [0, 1, 0], [0, 0, c2_a2_ratio]]) elif lat_type.lower() == "o": for i in range(3): @@ -1023,9 +1007,8 @@ def get_trans_mat( # make sure mu, mv are coprime integers. if ratio is None: mu, mv = [1, 1] - if w != 0: - if u != 0 or (v != 0): - raise RuntimeError("For irrational c2/a2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") + if w != 0 and (u != 0 or (v != 0)): + raise RuntimeError("For irrational c2/a2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") else: mu, mv = ratio if gcd(mu, mv) != 1: @@ -1080,11 +1063,10 @@ def get_trans_mat( # make sure mu, mv are coprime integers. if ratio is None: mu, mv = [1, 1] - if u + v + w != 0: - if u != v or u != w: - raise RuntimeError( - "For irrational ratio_alpha, CSL only exist for [1,1,1] or [u, v, -(u+v)] and m =0" - ) + if u + v + w != 0 and (u != v or u != w): + raise RuntimeError( + "For irrational ratio_alpha, CSL only exist for [1,1,1] or [u, v, -(u+v)] and m =0" + ) else: mu, mv = ratio if gcd(mu, mv) != 1: @@ -1157,9 +1139,8 @@ def get_trans_mat( elif lat_type.lower() == "t": if ratio is None: mu, mv = [1, 1] - if w != 0: - if u != 0 or (v != 0): - raise RuntimeError("For irrational c2/a2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") + if w != 0 and (u != 0 or (v != 0)): + raise RuntimeError("For irrational c2/a2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") else: mu, mv = ratio lam = mv @@ -1174,23 +1155,20 @@ def get_trans_mat( lam = non1 mv = non2 mu = 1 - if w != 0: - if u != 0 or (v != 0): - raise RuntimeError("For irrational c2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") + if w != 0 and (u != 0 or (v != 0)): + raise RuntimeError("For irrational c2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") elif lam is None: mu = non1 mv = non2 lam = 1 - if v != 0: - if u != 0 or (w != 0): - raise RuntimeError("For irrational b2, CSL only exist for [0,1,0] or [u,0,w] and m = 0") + if v != 0 and (u != 0 or (w != 0)): + raise RuntimeError("For irrational b2, CSL only exist for [0,1,0] or [u,0,w] and m = 0") elif mv is None: mu = non1 lam = non2 mv = 1 - if u != 0: - if w != 0 or (v != 0): - raise RuntimeError("For irrational a2, CSL only exist for [1,0,0] or [0,v,w] and m = 0") + if u != 0 and (w != 0 or (v != 0)): + raise RuntimeError("For irrational a2, CSL only exist for [1,0,0] or [0,v,w] and m = 0") else: mu, lam, mv = ratio if u == 0 and v == 0: @@ -1302,10 +1280,7 @@ def get_trans_mat( if lat_type.lower() == "h": trans_cry = np.array([[1, 0, 0], [-0.5, np.sqrt(3.0) / 2.0, 0], [0, 0, np.sqrt(mu / mv)]]) elif lat_type.lower() == "r": - if ratio is None: - c2_a2_ratio = 1.0 - else: - c2_a2_ratio = 3.0 / (2 - 6 * mv / mu) + c2_a2_ratio = 1.0 if ratio is None else 3.0 / (2 - 6 * mv / mu) trans_cry = np.array( [ [0.5, np.sqrt(3.0) / 6.0, 1.0 / 3 * np.sqrt(c2_a2_ratio)], @@ -1327,10 +1302,12 @@ def enum_sigma_cubic(cutoff, r_axis): Find all possible sigma values and corresponding rotation angles within a sigma value cutoff with known rotation axis in cubic system. The algorithm for this code is from reference, Acta Cryst, A40,108(1984) + Args: cutoff (int): the cutoff of sigma values. r_axis (list of three integers, e.g. u, v, w): the rotation axis of the grain boundary, with the format of [u,v,w]. + Returns: sigmas (dict): dictionary with keys as the possible integer sigma values @@ -1365,10 +1342,7 @@ def enum_sigma_cubic(cutoff, r_axis): m_max = int(np.sqrt(cutoff * a_max - n**2 * sum(np.array(r_axis) ** 2))) for m in range(0, m_max + 1): if gcd(m, n) == 1 or m == 0: - if m == 0: - n = 1 - else: - n = n_loop + n = 1 if m == 0 else n_loop # construct the quadruple [m, U,V,W], count the number of odds in # quadruple to determine the parameter a, refer to the reference quadruple = [m] + [x * n for x in r_axis] @@ -1442,9 +1416,8 @@ def enum_sigma_hex(cutoff, r_axis, c2_a2_ratio): # make sure mu, mv are coprime integers. if c2_a2_ratio is None: mu, mv = [1, 1] - if w != 0: - if u != 0 or (v != 0): - raise RuntimeError("For irrational c2/a2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") + if w != 0 and (u != 0 or (v != 0)): + raise RuntimeError("For irrational c2/a2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") else: mu, mv = c2_a2_ratio if gcd(mu, mv) != 1: @@ -1500,16 +1473,10 @@ def enum_sigma_hex(cutoff, r_axis, c2_a2_ratio): sigma = int(round((3 * mu * m**2 + d * n**2) / com_fac)) if 1 < sigma <= cutoff: if sigma not in list(sigmas): - if m == 0: - angle = 180.0 - else: - angle = 2 * np.arctan(n / m * np.sqrt(d / 3.0 / mu)) / np.pi * 180 + angle = 180.0 if m == 0 else 2 * np.arctan(n / m * np.sqrt(d / 3.0 / mu)) / np.pi * 180 sigmas[sigma] = [angle] else: - if m == 0: - angle = 180.0 - else: - angle = 2 * np.arctan(n / m * np.sqrt(d / 3.0 / mu)) / np.pi * 180 + angle = 180.0 if m == 0 else 2 * np.arctan(n / m * np.sqrt(d / 3.0 / mu)) / np.pi * 180 if angle not in sigmas[sigma]: sigmas[sigma].append(angle) if m_max == 0: @@ -1525,13 +1492,14 @@ def enum_sigma_rho(cutoff, r_axis, ratio_alpha): Args: cutoff (int): the cutoff of sigma values. - r_axis (list of three integers, e.g. u, v, w + r_axis (list[int]): of three integers, e.g. u, v, w or four integers, e.g. u, v, t, w): the rotation axis of the grain boundary, with the format of [u,v,w] or Weber indices [u, v, t, w]. ratio_alpha (list of two integers, e.g. mu, mv): mu/mv is the ratio of (1+2*cos(alpha))/cos(alpha) with rational number. If irrational, set ratio_alpha = None. + Returns: sigmas (dict): dictionary with keys as the possible integer sigma values @@ -1562,11 +1530,8 @@ def enum_sigma_rho(cutoff, r_axis, ratio_alpha): # make sure mu, mv are coprime integers. if ratio_alpha is None: mu, mv = [1, 1] - if u + v + w != 0: - if u != v or u != w: - raise RuntimeError( - "For irrational ratio_alpha, CSL only exist for [1,1,1] or [u, v, -(u+v)] and m =0" - ) + if u + v + w != 0 and (u != v or u != w): + raise RuntimeError("For irrational ratio_alpha, CSL only exist for [1,1,1] or [u, v, -(u+v)] and m =0") else: mu, mv = ratio_alpha if gcd(mu, mv) != 1: @@ -1639,16 +1604,10 @@ def enum_sigma_rho(cutoff, r_axis, ratio_alpha): sigma = int(round(abs(F / com_fac))) if 1 < sigma <= cutoff: if sigma not in list(sigmas): - if m == 0: - angle = 180.0 - else: - angle = 2 * np.arctan(n / m * np.sqrt(d / mu)) / np.pi * 180 + angle = 180.0 if m == 0 else 2 * np.arctan(n / m * np.sqrt(d / mu)) / np.pi * 180 sigmas[sigma] = [angle] else: - if m == 0: - angle = 180 - else: - angle = 2 * np.arctan(n / m * np.sqrt(d / mu)) / np.pi * 180.0 + angle = 180 if m == 0 else 2 * np.arctan(n / m * np.sqrt(d / mu)) / np.pi * 180.0 if angle not in sigmas[sigma]: sigmas[sigma].append(angle) if m_max == 0: @@ -1692,9 +1651,8 @@ def enum_sigma_tet(cutoff, r_axis, c2_a2_ratio): # make sure mu, mv are coprime integers. if c2_a2_ratio is None: mu, mv = [1, 1] - if w != 0: - if u != 0 or (v != 0): - raise RuntimeError("For irrational c2/a2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") + if w != 0 and (u != 0 or (v != 0)): + raise RuntimeError("For irrational c2/a2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") else: mu, mv = c2_a2_ratio if gcd(mu, mv) != 1: @@ -1710,10 +1668,7 @@ def enum_sigma_tet(cutoff, r_axis, c2_a2_ratio): # Enumerate all possible n, m to give possible sigmas within the cutoff. for n in range(1, n_max + 1): - if c2_a2_ratio is None and w == 0: - m_max = 0 - else: - m_max = int(np.sqrt((cutoff * 4 * mu * mv - n**2 * d) / mu)) + m_max = 0 if c2_a2_ratio is None and w == 0 else int(np.sqrt((cutoff * 4 * mu * mv - n**2 * d) / mu)) for m in range(0, m_max + 1): if gcd(m, n) == 1 or m == 0: # construct the rotation matrix, refer to the reference @@ -1750,16 +1705,10 @@ def enum_sigma_tet(cutoff, r_axis, c2_a2_ratio): sigma = int(round((mu * m**2 + d * n**2) / com_fac)) if 1 < sigma <= cutoff: if sigma not in list(sigmas): - if m == 0: - angle = 180.0 - else: - angle = 2 * np.arctan(n / m * np.sqrt(d / mu)) / np.pi * 180 + angle = 180.0 if m == 0 else 2 * np.arctan(n / m * np.sqrt(d / mu)) / np.pi * 180 sigmas[sigma] = [angle] else: - if m == 0: - angle = 180.0 - else: - angle = 2 * np.arctan(n / m * np.sqrt(d / mu)) / np.pi * 180 + angle = 180.0 if m == 0 else 2 * np.arctan(n / m * np.sqrt(d / mu)) / np.pi * 180 if angle not in sigmas[sigma]: sigmas[sigma].append(angle) if m_max == 0: @@ -1782,6 +1731,7 @@ def enum_sigma_ort(cutoff, r_axis, c2_b2_a2_ratio): mu:lam:mv is the square of the orthorhombic axial ratio with rational numbers. If irrational for one axis, set it to None. e.g. mu:lam:mv = c2,None,a2, means b2 is irrational. + Returns: sigmas (dict): dictionary with keys as the possible integer sigma values @@ -1816,23 +1766,20 @@ def enum_sigma_ort(cutoff, r_axis, c2_b2_a2_ratio): lam = non1 mv = non2 mu = 1 - if w != 0: - if u != 0 or (v != 0): - raise RuntimeError("For irrational c2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") + if w != 0 and (u != 0 or (v != 0)): + raise RuntimeError("For irrational c2, CSL only exist for [0,0,1] or [u,v,0] and m = 0") elif lam is None: mu = non1 mv = non2 lam = 1 - if v != 0: - if u != 0 or (w != 0): - raise RuntimeError("For irrational b2, CSL only exist for [0,1,0] or [u,0,w] and m = 0") + if v != 0 and (u != 0 or (w != 0)): + raise RuntimeError("For irrational b2, CSL only exist for [0,1,0] or [u,0,w] and m = 0") elif mv is None: mu = non1 lam = non2 mv = 1 - if u != 0: - if w != 0 or (v != 0): - raise RuntimeError("For irrational a2, CSL only exist for [1,0,0] or [0,v,w] and m = 0") + if u != 0 and (w != 0 or (v != 0)): + raise RuntimeError("For irrational a2, CSL only exist for [1,0,0] or [0,v,w] and m = 0") else: mu, lam, mv = c2_b2_a2_ratio if reduce(gcd, c2_b2_a2_ratio) != 1: @@ -1894,16 +1841,10 @@ def enum_sigma_ort(cutoff, r_axis, c2_b2_a2_ratio): sigma = int(round((mu * lam * m**2 + d * n**2) / com_fac)) if 1 < sigma <= cutoff: if sigma not in list(sigmas): - if m == 0: - angle = 180.0 - else: - angle = 2 * np.arctan(n / m * np.sqrt(d / mu / lam)) / np.pi * 180 + angle = 180.0 if m == 0 else 2 * np.arctan(n / m * np.sqrt(d / mu / lam)) / np.pi * 180 sigmas[sigma] = [angle] else: - if m == 0: - angle = 180.0 - else: - angle = 2 * np.arctan(n / m * np.sqrt(d / mu / lam)) / np.pi * 180 + angle = 180.0 if m == 0 else 2 * np.arctan(n / m * np.sqrt(d / mu / lam)) / np.pi * 180 if angle not in sigmas[sigma]: sigmas[sigma].append(angle) if m_max == 0: @@ -1923,6 +1864,7 @@ def enum_possible_plane_cubic(plane_cutoff, r_axis, r_angle): r_axis (list of three integers, e.g. u, v, w): the rotation axis of the grain boundary, with the format of [u,v,w]. r_angle (float): rotation angle of the GBs. + Returns: all_combinations (dict): dictionary with keys as GB type, e.g. 'Twist','Symmetric tilt',etc. @@ -2146,10 +2088,7 @@ def slab_from_csl(csl, surface, normal, trans_cry, max_search=20, quick_gen=Fals mil2 = int(round(miller_nonzero[j] / com_gcd)) lcm_miller.append(max(abs(mil1), abs(mil2))) lcm_sorted = sorted(lcm_miller) - if index_len == 2: - max_j = lcm_sorted[0] - else: - max_j = lcm_sorted[1] + max_j = lcm_sorted[0] if index_len == 2 else lcm_sorted[1] else: if not normal: t_matrix[0] = ab_vector[0] @@ -2368,13 +2307,9 @@ def fix_pbc(structure, matrix=None): Return: new structure with fixed frac_coords and lattice matrix """ - spec = [] coords = [] - if matrix is None: - latte = Lattice(structure.lattice.matrix) - else: - latte = Lattice(matrix) + latte = Lattice(structure.lattice.matrix) if matrix is None else Lattice(matrix) for site in structure: spec.append(site.specie) diff --git a/pymatgen/analysis/graphs.py b/pymatgen/analysis/graphs.py index 658883c296b..b8adac638c1 100644 --- a/pymatgen/analysis/graphs.py +++ b/pymatgen/analysis/graphs.py @@ -1075,7 +1075,7 @@ def types_of_coordination_environments(self, anonymous=False): motif = f"{centre_sp}-{','.join(labels)}" motifs.add(motif) - return sorted(list(motifs)) + return sorted(motifs) def as_dict(self): """ @@ -1737,10 +1737,7 @@ def with_local_env_strategy(molecule, strategy): structure = None for n in range(len(molecule)): - if structure is None: - neighbors = strategy.get_nn_info(molecule, n) - else: - neighbors = strategy.get_nn_info(structure, n) + neighbors = strategy.get_nn_info(molecule, n) if structure is None else strategy.get_nn_info(structure, n) for neighbor in neighbors: # all bonds in molecules should not cross # (artificial) periodic boundaries @@ -2030,7 +2027,7 @@ def get_disconnected_fragments(self): subgraphs = [original.graph.subgraph(c) for c in nx.weakly_connected_components(original.graph)] for subg in subgraphs: - nodes = sorted(list(subg.nodes)) + nodes = sorted(subg.nodes) # Molecule indices are essentially list-based, so node indices # must be remapped, incrementing from 0 @@ -2040,10 +2037,7 @@ def get_disconnected_fragments(self): # just give charge to whatever subgraph has node with index 0 # TODO: actually figure out how to distribute charge - if 0 in nodes: - charge = self.molecule.charge - else: - charge = 0 + charge = self.molecule.charge if 0 in nodes else 0 # relabel nodes in graph to match mapping new_graph = nx.relabel_nodes(subg, mapping) @@ -2612,10 +2606,7 @@ def draw_graph_to_file( # add display options for edges for u, v, k, d in g.edges(keys=True, data=True): # retrieve from/to images, set as origin if not defined - if "to_image" in d: - to_image = d["to_jimage"] - else: - to_image = (0, 0, 0) + to_image = d["to_jimage"] if "to_image" in d else (0, 0, 0) # set edge style d["style"] = "solid" diff --git a/pymatgen/analysis/interface_reactions.py b/pymatgen/analysis/interface_reactions.py index 75c3a7b782a..26a7ad044ee 100644 --- a/pymatgen/analysis/interface_reactions.py +++ b/pymatgen/analysis/interface_reactions.py @@ -348,7 +348,7 @@ def _get_plotly_figure(self) -> Figure: y=energy, mode="lines", name="Lines", - line=dict(color="navy", dash="solid", width=5.0), + line={"color": "navy", "dash": "solid", "width": 5.0}, hoverinfo="none", ) @@ -372,13 +372,13 @@ def _get_plotly_figure(self) -> Figure: name="Reactions", hoverinfo="text", hovertext=labels, - marker=dict( - color="black", - size=12, - opacity=0.8, - line=dict(color="black", width=3), - ), - hoverlabel=dict(bgcolor="navy"), + marker={ + "color": "black", + "size": 12, + "opacity": 0.8, + "line": {"color": "black", "width": 3}, + }, + hoverlabel={"bgcolor": "navy"}, ) min_label = f"{htmlify(str(rxn_min))}
\u0394Erxn = {round(e_min, 3)} eV/atom" # type: ignore @@ -389,7 +389,7 @@ def _get_plotly_figure(self) -> Figure: mode="markers", hoverinfo="text", hovertext=[min_label], - marker=dict(color="darkred", size=24, symbol="star"), + marker={"color": "darkred", "size": 24, "symbol": "star"}, name="Suggested reaction", ) @@ -424,7 +424,7 @@ def _get_matplotlib_figure(self) -> plt.Figure: textcoords="offset points", ha="right", va="bottom", - arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=0"), + arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=0"}, ) if self.norm: @@ -461,7 +461,7 @@ def _get_plotly_annotations(x: list[float], y: list[float], reactions: list[Reac products = ", ".join( [htmlify(p.reduced_formula) for p in rxn.products if not np.isclose(rxn.get_coeff(p), 0)] ) - annotation = dict(x=x_coord, y=y_coord, text=products, font=dict(size=18), ax=-25, ay=55) + annotation = {"x": x_coord, "y": y_coord, "text": products, "font": {"size": 18}, "ax": -25, "ay": 55} annotations.append(annotation) return annotations diff --git a/pymatgen/analysis/interfaces/substrate_analyzer.py b/pymatgen/analysis/interfaces/substrate_analyzer.py index 6571b7fb6f6..f0207c32135 100644 --- a/pymatgen/analysis/interfaces/substrate_analyzer.py +++ b/pymatgen/analysis/interfaces/substrate_analyzer.py @@ -44,7 +44,6 @@ def from_zsl( ground_state_energy=0, ): """Generate a substrate match from a ZSL match plus metadata""" - # Get the appropriate surface structure struct = SlabGenerator(film, film_miller, 20, 15, primitive=False).get_slab().oriented_unit_cell diff --git a/pymatgen/analysis/interfaces/zsl.py b/pymatgen/analysis/interfaces/zsl.py index fd6b77137a7..75f119105de 100644 --- a/pymatgen/analysis/interfaces/zsl.py +++ b/pymatgen/analysis/interfaces/zsl.py @@ -175,6 +175,7 @@ def get_equiv_transformations(self, transformation_sets, film_vectors, substrate Applies the transformation_sets to the film and substrate vectors to generate super-lattices and checks if they matches. Returns all matching vectors sets. + Args: transformation_sets(array): an array of transformation sets: each transformation set is an array with the (i,j) @@ -299,7 +300,6 @@ def reduce_vectors(a, b): Generate independent and unique basis vectors based on the methodology of Zur and McGill """ - if np.dot(a, b) < 0: return reduce_vectors(a, -b) diff --git a/pymatgen/analysis/local_env.py b/pymatgen/analysis/local_env.py index d79a85b49cc..fdc2865be83 100644 --- a/pymatgen/analysis/local_env.py +++ b/pymatgen/analysis/local_env.py @@ -142,10 +142,7 @@ def nearest_key(sorted_vals, skey): oxi_state = nearest_key(tab_oxi_states, oxi_state) radius = _ion_radii[el][str(oxi_state)][str(coord_no)] except KeyError: - if vnn.get_cn(self._structure, i) - coord_no > 0: - new_coord_no = coord_no + 1 - else: - new_coord_no = coord_no - 1 + new_coord_no = coord_no + 1 if vnn.get_cn(self._structure, i) - coord_no > 0 else coord_no - 1 try: radius = _ion_radii[el][str(oxi_state)][str(new_coord_no)] coord_no = new_coord_no @@ -215,7 +212,6 @@ def _get_valences(self): def _handle_disorder(structure: Structure, on_disorder: on_disorder_options): """What to do in bonding and coordination number analysis if a site is disordered.""" - if all(site.is_ordered for site in structure): return structure @@ -315,6 +311,7 @@ def get_cn( on each site. For {{Fe: 0.4, O: 0.4, C: 0.2}}, 'error' and 'take_majority_strict' will raise ValueError, while 'take_majority_drop' ignores this site altogether and 'take_max_species' will use Fe as the site specie. + Returns: cn (int or float): coordination number. """ @@ -362,6 +359,7 @@ def get_nn(self, structure: Structure, n: int): structure (Structure): input structure. n (int): index of site in structure for which to determine neighbors. + Returns: sites (list of Site objects): near neighbors. """ @@ -375,6 +373,7 @@ def get_weights_of_nn_sites(self, structure: Structure, n: int): Args: structure (Structure): input structure. n (int): index of site for which to determine the weights. + Returns: weights (list of floats): near-neighbor weights. """ @@ -389,6 +388,7 @@ def get_nn_images(self, structure: Structure, n: int): structure (Structure): input structure. n (int): index of site for which to determine the image location of near neighbors. + Returns: images (list of 3D integer array): image locations of near neighbors. @@ -447,6 +447,7 @@ def get_nn_shell_info(self, structure: Structure, site_idx, shell): site_idx (int): index of site for which to determine neighbor information. shell (int): Which neighbor shell to retrieve (1 == 1st NN shell) + Returns: list of dictionaries. Each entry in the list is information about a certain neighbor in the structure, in the same format as @@ -578,7 +579,8 @@ def _get_image(structure, site): @staticmethod def _get_original_site(structure, site): """Private convenience method for get_nn_info, - gives original site index from ProvidedPeriodicSite.""" + gives original site index from ProvidedPeriodicSite. + """ if isinstance(structure, (IStructure, Structure)): for i, s in enumerate(structure): if site.is_periodic_image(s): @@ -663,7 +665,7 @@ def get_local_order_parameters(self, structure: Structure, n: int): tmp = cn_opt_params[cn][name][1] if len(cn_opt_params[cn][name]) > 1 else None params.append(tmp) lostops = LocalStructOrderParams(types, parameters=params) - sites = [structure[n]] + self.get_nn(structure, n) + sites = [structure[n], *self.get_nn(structure, n)] lostop_vals = lostops.get_order_parameters(sites, 0, indices_neighs=list(range(1, cn + 1))) # type: ignore d = {} for i, lostop in enumerate(lostop_vals): @@ -754,10 +756,7 @@ def get_voronoi_polyhedra(self, structure: Structure, n: int): """ # Assemble the list of neighbors used in the tessellation # Gets all atoms within a certain radius - if self.targets is None: - targets = structure.composition.elements - else: - targets = self.targets + targets = structure.composition.elements if self.targets is None else self.targets center = structure[n] cutoff = self.cutoff @@ -815,10 +814,7 @@ def get_all_voronoi_polyhedra(self, structure): return [self.get_voronoi_polyhedra(structure, 0)] # Assemble the list of neighbors used in the tessellation - if self.targets is None: - targets = structure.composition.elements - else: - targets = self.targets + targets = structure.composition.elements if self.targets is None else self.targets # Initialize the list of sites with the atoms in the origin unit cell # The `get_all_neighbors` function returns neighbors for each site's image in @@ -1028,10 +1024,7 @@ def _extract_nn_info(self, structure: Structure, nns): (list of tuples (Site, array, float)): See nn_info """ # Get the target information - if self.targets is None: - targets = structure.composition.elements - else: - targets = self.targets + targets = structure.composition.elements if self.targets is None else self.targets # Extract the NN info siw = [] @@ -1139,10 +1132,7 @@ def _filter_nns(self, structure: Structure, n: int, nns: dict[str, Any]) -> list See get_nn_info for the format of the returned data. """ # Get the target information - if self.targets is None: - targets = structure.composition.elements - else: - targets = self.targets + targets = structure.composition.elements if self.targets is None else self.targets site = structure[n] @@ -1183,10 +1173,7 @@ def _is_in_targets(site, targets): (boolean) Whether this site contains a certain list of elements """ elems = _get_elements(site) - for elem in elems: - if elem not in targets: - return False - return True + return all(elem in targets for elem in elems) def _get_elements(site): @@ -1560,6 +1547,7 @@ def get_nn_shell_info(self, structure: Structure, site_idx, shell): site_idx (int): index of site for which to determine neighbor information. shell (int): Which neighbor shell to retrieve (1 == 1st NN shell) + Returns: list of dictionaries. Each entry in the list is information about a certain neighbor in the structure, in the same format as @@ -1651,10 +1639,7 @@ def get_nn_info(self, structure: Structure, n: int): if capture_bond: index = structure.index(site) - if self.order: - weight = bond.get_bond_order() - else: - weight = bond.length + weight = bond.get_bond_order() if self.order else bond.length siw.append({"site": site, "image": (0, 0, 0), "weight": weight, "site_index": index}) @@ -1709,6 +1694,7 @@ def get_nn_shell_info(self, structure: Structure, site_idx, shell): site_idx (int): index of site for which to determine neighbor information. shell (int): Which neighbor shell to retrieve (1 == 1st NN shell) + Returns: list of dictionaries. Each entry in the list is information about a certain neighbor in the structure, in the same format as @@ -1944,7 +1930,6 @@ def solid_angle(center, coords): Returns: The solid angle. """ - # Compute the displacement from the center r = [np.subtract(c, center) for c in coords] @@ -1963,10 +1948,7 @@ def solid_angle(center, coords): + r_norm[i] * np.dot(r[0], r[j]) + r_norm[0] * np.dot(r[i], r[j]) ) - if de == 0: - my_angle = 0.5 * pi if tp > 0 else -0.5 * pi - else: - my_angle = np.arctan(tp / de) + my_angle = (0.5 * pi if tp > 0 else -0.5 * pi) if de == 0 else np.arctan(tp / de) angle += (my_angle if my_angle > 0 else my_angle + np.pi) * 2 return angle @@ -1976,11 +1958,13 @@ def vol_tetra(vt1, vt2, vt3, vt4): """ Calculate the volume of a tetrahedron, given the four vertices of vt1, vt2, vt3 and vt4. + Args: vt1 (array-like): coordinates of vertex 1. vt2 (array-like): coordinates of vertex 2. vt3 (array-like): coordinates of vertex 3. vt4 (array-like): coordinates of vertex 4. + Returns: (float): volume of the tetrahedron. """ @@ -1998,11 +1982,11 @@ def get_okeeffe_params(el_symbol): Args: el_symbol (str): element symbol. + Returns: (dict): atom-size ('r') and electronegativity-related ('c') parameter. """ - el = Element(el_symbol) if el not in list(BV_PARAMS): raise RuntimeError( @@ -2097,7 +2081,6 @@ def site_is_of_motif_type(struct, n, approach="min_dist", delta=0.1, cutoff=10.0 Returns: motif type (str). """ - if thresh is None: thresh = { "qtet": 0.5, @@ -2160,7 +2143,6 @@ def gramschmidt(vin, uin): uin (numpy array): second input vector """ - vin_uin = np.inner(vin, uin) uin_uin = np.inner(uin, uin) if uin_uin <= 0.0: @@ -2808,6 +2790,7 @@ def get_type(self, index): Args: index (int): index of order parameter for which type is to be returned. + Returns: str: OP type. """ @@ -2828,6 +2811,7 @@ def get_parameters(self, index): index (int): index of order parameter for which associated parameters are to be returned. + Returns: [float]: parameters of a given OP. """ @@ -3098,11 +3082,10 @@ def get_order_parameters( tmp = self._params[i]["IGW_SPP"] * (thetak * ipi - 1.0) qsptheta[i][j][kc] += self._params[i]["w_SPP"] * exp(-0.5 * tmp * tmp) norms[i][j][kc] += self._params[i]["w_SPP"] - elif t == "sq_face_cap_trig_pris": - if thetak < self._params[i]["TA3"]: - tmp = self._params[i]["IGW_TA1"] * (thetak * ipi - self._params[i]["TA1"]) - qsptheta[i][j][kc] += exp(-0.5 * tmp * tmp) - norms[i][j][kc] += 1 + elif t == "sq_face_cap_trig_pris" and thetak < self._params[i]["TA3"]: + tmp = self._params[i]["IGW_TA1"] * (thetak * ipi - self._params[i]["TA1"]) + qsptheta[i][j][kc] += exp(-0.5 * tmp * tmp) + norms[i][j][kc] += 1 for m in range(nneigh): if (m != j) and (m != k) and (not flag_xaxis): @@ -3122,20 +3105,23 @@ def get_order_parameters( np.dot(xtwoaxis, xaxis), ) # South pole contributions of m. - if t in [ - "tri_bipyr", - "sq_bipyr", - "pent_bipyr", - "hex_bipyr", - "oct_max", - "sq_plan_max", - "hex_plan_max", - "see_saw_rect", - ]: - if thetam >= self._params[i]["min_SPP"]: - tmp = self._params[i]["IGW_SPP"] * (thetam * ipi - 1.0) - qsptheta[i][j][kc] += exp(-0.5 * tmp * tmp) - norms[i][j][kc] += 1 + if ( + t + in [ + "tri_bipyr", + "sq_bipyr", + "pent_bipyr", + "hex_bipyr", + "oct_max", + "sq_plan_max", + "hex_plan_max", + "see_saw_rect", + ] + and thetam >= self._params[i]["min_SPP"] + ): + tmp = self._params[i]["IGW_SPP"] * (thetam * ipi - 1.0) + qsptheta[i][j][kc] += exp(-0.5 * tmp * tmp) + norms[i][j][kc] += 1 # Contributions of j-i-m angle and # angles between plane j-i-k and i-m vector. @@ -3209,10 +3195,7 @@ def get_order_parameters( norms[i][j][kc] += 1 elif t == "bcc" and j < k: if thetak < self._params[i]["min_SPP"]: - if thetak > piover2: - fac = 1.0 - else: - fac = -1.0 + fac = 1.0 if thetak > piover2 else -1.0 tmp = (thetam - piover2) / asin(1 / 3) qsptheta[i][j][kc] += ( fac * cos(3 * phi) * fac_bcc * tmp * exp(-0.5 * tmp * tmp) @@ -3857,6 +3840,7 @@ def molecules_allowed(self): def get_nn_info(self, structure: Structure, n: int) -> list[dict]: """ Get all near-neighbor information. + Args: structure: (Structure) pymatgen Structure n: (int) index of target site @@ -4072,6 +4056,7 @@ def _semicircle_integral(dist_bins, idx): """ An internal method to get an integral between two bounds of a unit semicircle. Used in algorithm to determine bond probabilities. + Args: dist_bins: (float) list of all possible bond weights idx: (float) index of starting bond weight @@ -4133,6 +4118,7 @@ def _get_radius(site): """ An internal method to get the expected radius for a site with oxidation state. + Args: site: (Site) @@ -4435,12 +4421,11 @@ def metal_edge_extender( for sites in metal_sites.values(): for idx, indices in sites.items(): for ii, site in enumerate(mol_graph.molecule): - if ii != idx and ii not in indices: - if str(site.specie) in coordinators: - if site.distance(mol_graph.molecule[idx]) < cutoff: - mol_graph.add_edge(idx, ii) - num_new_edges += 1 - indices.append(ii) + if ii != idx and ii not in indices and str(site.specie) in coordinators: + if site.distance(mol_graph.molecule[idx]) < cutoff: + mol_graph.add_edge(idx, ii) + num_new_edges += 1 + indices.append(ii) # If no metal edges are found, increase cutoff by 1 Ang and repeat analysis total_metal_edges = 0 for sites in metal_sites.values(): @@ -4450,11 +4435,10 @@ def metal_edge_extender( for sites in metal_sites.values(): for idx, indices in sites.items(): for ii, site in enumerate(mol_graph.molecule): - if ii != idx and ii not in indices: - if str(site.specie) in coordinators: - if site.distance(mol_graph.molecule[idx]) < (cutoff + 1): - mol_graph.add_edge(idx, ii) - num_new_edges += 1 - indices.append(ii) + if ii != idx and ii not in indices and str(site.specie) in coordinators: + if site.distance(mol_graph.molecule[idx]) < (cutoff + 1): + mol_graph.add_edge(idx, ii) + num_new_edges += 1 + indices.append(ii) return mol_graph diff --git a/pymatgen/analysis/magnetism/analyzer.py b/pymatgen/analysis/magnetism/analyzer.py index 002691e7fa9..a46c869a35d 100644 --- a/pymatgen/analysis/magnetism/analyzer.py +++ b/pymatgen/analysis/magnetism/analyzer.py @@ -281,9 +281,8 @@ def __init__( # overwrite_magmom_mode = "normalize" set magmoms magnitude to 1 - elif overwrite_magmom_mode == "normalize": - if magmoms[idx] != 0: - magmoms[idx] = int(magmoms[idx] / abs(magmoms[idx])) + elif overwrite_magmom_mode == "normalize" and magmoms[idx] != 0: + magmoms[idx] = int(magmoms[idx] / abs(magmoms[idx])) # round magmoms, used to smooth out computational data magmoms = self._round_magmoms(magmoms, round_magmoms) if round_magmoms else magmoms # type: ignore @@ -413,7 +412,6 @@ def is_magnetic(self) -> bool: @property def magmoms(self) -> np.ndarray: """Convenience property, returns magmoms as a numpy array.""" - return np.array(self.structure.site_properties["magmom"]) @property @@ -428,7 +426,7 @@ def types_of_magnetic_species( if self.number_of_magnetic_sites > 0: structure = self.get_structure_with_only_magnetic_atoms() return tuple(sorted(structure.types_of_species)) - return tuple() + return () @property def types_of_magnetic_specie( @@ -459,7 +457,7 @@ def magnetic_species_and_magmoms(self) -> dict[str, Any]: if len(magmoms) == 1: magtypes[sp] = magmoms.pop() else: - magtypes[sp] = sorted(list(magmoms)) + magtypes[sp] = sorted(magmoms) return magtypes @@ -593,10 +591,7 @@ def __str__(self): outs = ["Structure Summary", repr(s.lattice)] outs.append("Magmoms Sites") for site in s: - if site.properties["magmom"] != 0: - prefix = f"{site.properties['magmom']:+.2f} " - else: - prefix = " " + prefix = f"{site.properties['magmom']:+.2f} " if site.properties["magmom"] != 0 else " " outs.append(prefix + repr(site)) return "\n".join(outs) @@ -1027,7 +1022,7 @@ def _add_structures(ordered_structures, ordered_structures_origins, structures_t num_sym_ops = [len(SpaceGroup.from_int_number(n).symmetry_ops) for n in symmetry_int_numbers] # find the largest values... - max_symmetries = sorted(list(set(num_sym_ops)), reverse=True) + max_symmetries = sorted(set(num_sym_ops), reverse=True) # ...and decide which ones to keep if len(max_symmetries) > self.truncate_by_symmetry: @@ -1087,7 +1082,6 @@ def magnetic_deformation(structure_A: Structure, structure_B: Structure) -> Magn Returns: Magnetic deformation """ - # retrieve orderings of both input structures ordering_a = CollinearMagneticStructureAnalyzer(structure_A, overwrite_magmom_mode="none").ordering ordering_b = CollinearMagneticStructureAnalyzer(structure_B, overwrite_magmom_mode="none").ordering diff --git a/pymatgen/analysis/magnetism/heisenberg.py b/pymatgen/analysis/magnetism/heisenberg.py index 8baf904e6bb..0af494edd46 100644 --- a/pymatgen/analysis/magnetism/heisenberg.py +++ b/pymatgen/analysis/magnetism/heisenberg.py @@ -193,13 +193,13 @@ def _get_nn_dict(self): i_key = unique_site_ids[k] connected_sites = sgraph.get_connected_sites(i) dists = [round(cs[-1], 2) for cs in connected_sites] # i<->j distances - dists = sorted(list(set(dists))) # NN, NNN, NNNN, etc. + dists = sorted(set(dists)) # NN, NNN, NNNN, etc. dists = dists[:3] # keep up to NNNN all_dists += dists # Keep only up to NNNN and call dists equal if they are within tol - all_dists = sorted(list(set(all_dists))) + all_dists = sorted(set(all_dists)) rm_list = [] for idx, d in enumerate(all_dists[:-1]): if abs(d - all_dists[idx + 1]) < tol: @@ -249,7 +249,7 @@ def _get_exchange_df(self): Returns: None: (sets self.ex_mat instance variable) - TODO: + Todo: * Deal with large variance in |S| across configs """ sgraphs = self.sgraphs @@ -449,11 +449,10 @@ def get_low_energy_orderings(self): afm_e = e mag_min = abs(sum(magmoms)) afm_e_min = e - elif abs(sum(magmoms)) == 0 and mag_min == 0: - if e < afm_e_min: - afm_struct = s - afm_e = e - afm_e_min = e + elif abs(sum(magmoms)) == 0 and mag_min == 0 and e < afm_e_min: + afm_struct = s + afm_e = e + afm_e_min = e # Convert to magnetic structures with 'magmom' site property fm_struct = CollinearMagneticStructureAnalyzer( @@ -557,6 +556,7 @@ def get_interaction_graph(self, filename=None): Args: filename (str): if not None, save interaction graph to filename. + Returns: igraph (StructureGraph): Exchange interaction graph. """ @@ -816,7 +816,7 @@ def _do_screen(structures, energies): # Prioritize structures with fewer magmoms < 1 uB df_high_energy = df_high_energy.sort_values(by="n_below_1ub") - index = [0, 1] + list(df_high_energy.index) + index = [0, 1, *df_high_energy.index] # sort df = df.reindex(index) @@ -915,7 +915,6 @@ def as_dict(self): @classmethod def from_dict(cls, d): """Create a HeisenbergModel from a dict.""" - # Reconstitute the site ids usids = {} wids = {} @@ -931,7 +930,7 @@ def from_dict(cls, d): for k, v in d["unique_site_ids"].items(): key = literal_eval(k) if isinstance(key, int): - usids[tuple([key])] = v + usids[(key,)] = v elif isinstance(key, tuple): usids[key] = v diff --git a/pymatgen/analysis/molecule_matcher.py b/pymatgen/analysis/molecule_matcher.py index 53eb00edde2..5bfad1f1b19 100644 --- a/pymatgen/analysis/molecule_matcher.py +++ b/pymatgen/analysis/molecule_matcher.py @@ -1298,7 +1298,7 @@ def permutations(self, p: Molecule): if p_atoms[jdx] != f_atom: continue - inds = indices + [jdx] + inds = [*indices, jdx] P = p_coords[inds] # Both sets of coordinates must be translated first, so that diff --git a/pymatgen/analysis/nmr.py b/pymatgen/analysis/nmr.py index 0e2a8676280..84a90a632d6 100644 --- a/pymatgen/analysis/nmr.py +++ b/pymatgen/analysis/nmr.py @@ -219,7 +219,6 @@ def coupling_constant(self, specie): or Site object Return: - the coupling constant as a FloatWithUnit in MHz """ planks_constant = FloatWithUnit(6.62607004e-34, "m^2 kg s^-1") diff --git a/pymatgen/analysis/path_finder.py b/pymatgen/analysis/path_finder.py index cb575154036..57b1a0f42f4 100644 --- a/pymatgen/analysis/path_finder.py +++ b/pymatgen/analysis/path_finder.py @@ -195,10 +195,7 @@ def string_relax( # logger.debug("Getting path from {} to {} (coords wrt V grid)".format(start, end)) # Set parameters - if not dr: - dr = np.array([1.0 / V.shape[0], 1.0 / V.shape[1], 1.0 / V.shape[2]]) - else: - dr = np.array(dr, dtype=float) + dr = np.array([1.0 / V.shape[0], 1.0 / V.shape[1], 1.0 / V.shape[2]]) if not dr else np.array(dr, dtype=float) keff = k * dr * n_images h0 = h diff --git a/pymatgen/analysis/phase_diagram.py b/pymatgen/analysis/phase_diagram.py index 04b0455c17d..cbd2f47b093 100644 --- a/pymatgen/analysis/phase_diagram.py +++ b/pymatgen/analysis/phase_diagram.py @@ -474,16 +474,16 @@ def _compute(self) -> dict[str, Any]: simplexes = [Simplex(qhull_data[f, :-1]) for f in facets] self.elements = elements - return dict( - facets=facets, - simplexes=simplexes, - all_entries=all_entries, - qhull_data=qhull_data, - dim=dim, + return { + "facets": facets, + "simplexes": simplexes, + "all_entries": all_entries, + "qhull_data": qhull_data, + "dim": dim, # Dictionary with Element keys is not JSON-serializable - el_refs=list(el_refs.items()), - qhull_entries=qhull_entries, - ) + "el_refs": list(el_refs.items()), + "qhull_entries": qhull_entries, + } def pd_coords(self, comp: Composition) -> np.ndarray: """ @@ -1110,7 +1110,7 @@ def get_element_profile(self, element, comp, comp_tol=1e-5): for cc in self.get_critical_compositions(el_comp, gc_comp)[1:]: decomp_entries = list(self.get_decomposition(cc)) decomp = [k.composition for k in decomp_entries] - rxn = Reaction([comp], decomp + [el_comp]) + rxn = Reaction([comp], [*decomp, el_comp]) rxn.normalize_to(comp) c = self.get_composition_chempots(cc + el_comp * 1e-5)[element] amt = -rxn.coeffs[rxn.all_comp.index(el_comp)] @@ -1410,9 +1410,9 @@ def __init__(self, entries, terminal_compositions, normalize_terminal_compositio self.original_entries = entries self.terminal_compositions = terminal_compositions self.normalize_terminals = normalize_terminal_compositions - (pentries, species_mapping) = self.transform_entries(entries, terminal_compositions) + p_entries, species_mapping = self.transform_entries(entries, terminal_compositions) self.species_mapping = species_mapping - super().__init__(pentries, elements=species_mapping.values()) + super().__init__(p_entries, elements=species_mapping.values()) def transform_entries(self, entries, terminal_compositions): """ @@ -1860,7 +1860,7 @@ def __init__(self, entry1, entry2, all_entries, tol: float = 1e-4, float_fmt="%. logger.debug(f"{len(all_entries)} total entries.") - pd = PhaseDiagram(all_entries + [entry1, entry2]) + pd = PhaseDiagram([*all_entries, entry1, entry2]) terminal_formulas = [ entry1.composition.reduced_formula, entry2.composition.reduced_formula, @@ -1968,7 +1968,7 @@ def get_compound_pd(self): entry2 = PDEntry(self.entry2.composition, 0) cpd = CompoundPhaseDiagram( - self.rxn_entries + [entry1, entry2], + [*self.rxn_entries, entry1, entry2], [ Composition(entry1.composition.reduced_formula), Composition(entry2.composition.reduced_formula), @@ -2297,10 +2297,7 @@ def plot_element_profile(self, element, comp, show_label_index=None, xlim=5): x1 = v y1 = d["evolution"] / num_atoms - if i != len(evolution) - 1: - x2 = -(evolution[i + 1]["chempot"] - element_energy) - else: - x2 = 5.0 + x2 = -(evolution[i + 1]["chempot"] - element_energy) if i != len(evolution) - 1 else 5.0 if show_label_index is not None and i in show_label_index: products = [ re.sub(r"(\d+)", r"$_{\1}$", p.reduced_formula) @@ -2354,10 +2351,10 @@ def _get_2d_plot( from matplotlib.font_manager import FontProperties if ordering is None: - (lines, labels, unstable) = self.pd_plot_data + lines, labels, unstable = self.pd_plot_data else: - (_lines, _labels, _unstable) = self.pd_plot_data - (lines, labels, unstable) = order_phase_diagram(_lines, _labels, _unstable, ordering) + _lines, _labels, _unstable = self.pd_plot_data + lines, labels, unstable = order_phase_diagram(_lines, _labels, _unstable, ordering) if energy_colormap is None: if process_attributes: for x, y in lines: @@ -2662,7 +2659,7 @@ def get_chempot_range_map_plot(self, elements, referenced=True): # Shade the forbidden chemical potential regions. excluded_region.append([xlim[1], ylim[1]]) excluded_region = sorted(excluded_region, key=lambda c: c[0]) - (x, y) = np.transpose(excluded_region) + x, y = np.transpose(excluded_region) plt.fill(x, y, "0.80") # The hull does not generate the missing horizontal and vertical lines. @@ -2751,7 +2748,7 @@ def get_contour_pd_plot(self): def _create_plotly_lines(self): """ - Creates Plotly scatter (line) plots for all phase diagram facets. + Create Plotly scatter (line) plots for all phase diagram facets. Returns: go.Scatter (or go.Scatter3d) plot @@ -2760,31 +2757,29 @@ def _create_plotly_lines(self): x, y, z, energies = [], [], [], [] for line in self.pd_plot_data[0]: - x.extend(list(line[0]) + [None]) - y.extend(list(line[1]) + [None]) + x.extend([*line[0], None]) + y.extend([*line[1], None]) if self._dim == 3: - z.extend( - [self._pd.get_form_energy_per_atom(self.pd_plot_data[1][coord]) for coord in zip(line[0], line[1])] - + [None] - ) + form_enes = [ + self._pd.get_form_energy_per_atom(self.pd_plot_data[1][coord]) for coord in zip(line[0], line[1]) + ] + z.extend([*form_enes, None]) elif self._dim == 4: - energies.extend( - [ - self._pd.get_form_energy_per_atom(self.pd_plot_data[1][coord]) - for coord in zip(line[0], line[1], line[2]) - ] - + [None] - ) - z.extend(list(line[2]) + [None]) - - plot_args = dict( - mode="lines", - hoverinfo="none", - line={"color": "rgba(0,0,0,1.0)", "width": 7.0}, - showlegend=False, - ) + form_enes = [ + self._pd.get_form_energy_per_atom(self.pd_plot_data[1][coord]) + for coord in zip(line[0], line[1], line[2]) + ] + energies.extend([*form_enes, None]) + z.extend([*line[2], None]) + + plot_args = { + "mode": "lines", + "hoverinfo": "none", + "line": {"color": "rgba(0,0,0,1.0)", "width": 7.0}, + "showlegend": False, + } if self._dim == 2: line_plot = go.Scatter(x=x, y=y, **plot_args) @@ -2863,16 +2858,16 @@ def _create_plotly_stable_labels(self, label_stable=True): if not label_stable or self._dim == 4: visible = "legendonly" - plot_args = dict( - text=text, - textposition=textpositions, - mode="text", - name="Labels (stable)", - hoverinfo="skip", - opacity=1.0, - visible=visible, - showlegend=True, - ) + plot_args = { + "text": text, + "textposition": textpositions, + "mode": "text", + "name": "Labels (stable)", + "hoverinfo": "skip", + "opacity": 1.0, + "visible": visible, + "showlegend": True, + } if self._dim == 2: stable_labels_plot = go.Scatter(x=x, y=y, **plot_args) @@ -2938,7 +2933,7 @@ def _create_plotly_element_annotations(self): # extra point ensures equilateral triangular scaling is displayed if self._dim == 3: - annotations_list.append(dict(x=1, y=1, z=0, opacity=0, text="")) + annotations_list.append({"x": 1, "y": 1, "z": 0, "opacity": 0, "text": ""}) return annotations_list @@ -3046,117 +3041,117 @@ def get_marker_props(coords, entries, stable=True): if self._dim == 2: stable_markers = plotly_layouts["default_binary_marker_settings"].copy() stable_markers.update( - dict( - x=list(stable_props["x"]), - y=list(stable_props["y"]), - name="Stable", - marker=dict(color="darkgreen", size=11, line=dict(color="black", width=2)), - opacity=0.9, - hovertext=stable_props["texts"], - error_y=dict( - array=list(stable_props["uncertainties"]), - type="data", - color="gray", - thickness=2.5, - width=5, - ), - ) + { + "x": list(stable_props["x"]), + "y": list(stable_props["y"]), + "name": "Stable", + "marker": {"color": "darkgreen", "size": 11, "line": {"color": "black", "width": 2}}, + "opacity": 0.9, + "hovertext": stable_props["texts"], + "error_y": { + "array": list(stable_props["uncertainties"]), + "type": "data", + "color": "gray", + "thickness": 2.5, + "width": 5, + }, + } ) unstable_markers = plotly_layouts["default_binary_marker_settings"].copy() unstable_markers.update( - dict( - x=list(unstable_props["x"]), - y=list(unstable_props["y"]), - name="Above Hull", - marker=dict( - color=unstable_props["energies"], - colorscale=plotly_layouts["unstable_colorscale"], - size=6, - symbol="diamond", - ), - hovertext=unstable_props["texts"], - ) + { + "x": list(unstable_props["x"]), + "y": list(unstable_props["y"]), + "name": "Above Hull", + "marker": { + "color": unstable_props["energies"], + "colorscale": plotly_layouts["unstable_colorscale"], + "size": 6, + "symbol": "diamond", + }, + "hovertext": unstable_props["texts"], + } ) elif self._dim == 3: stable_markers = plotly_layouts["default_ternary_marker_settings"].copy() stable_markers.update( - dict( - x=list(stable_props["y"]), - y=list(stable_props["x"]), - z=list(stable_props["z"]), - name="Stable", - marker=dict( - color="black", - size=12, - opacity=0.8, - line=dict(color="black", width=3), - ), - hovertext=stable_props["texts"], - error_z=dict( - array=list(stable_props["uncertainties"]), - type="data", - color="darkgray", - width=10, - thickness=5, - ), - ) + { + "x": list(stable_props["y"]), + "y": list(stable_props["x"]), + "z": list(stable_props["z"]), + "name": "Stable", + "marker": { + "color": "black", + "size": 12, + "opacity": 0.8, + "line": {"color": "black", "width": 3}, + }, + "hovertext": stable_props["texts"], + "error_z": { + "array": list(stable_props["uncertainties"]), + "type": "data", + "color": "darkgray", + "width": 10, + "thickness": 5, + }, + } ) unstable_markers = plotly_layouts["default_ternary_marker_settings"].copy() unstable_markers.update( - dict( - x=unstable_props["y"], - y=unstable_props["x"], - z=unstable_props["z"], - name="Above Hull", - marker=dict( - color=unstable_props["energies"], - colorscale=plotly_layouts["unstable_colorscale"], - size=6, - symbol="diamond", - colorbar=dict(title="Energy Above Hull
(eV/atom)", x=0.05, len=0.75), - ), - hovertext=unstable_props["texts"], - ) + { + "x": unstable_props["y"], + "y": unstable_props["x"], + "z": unstable_props["z"], + "name": "Above Hull", + "marker": { + "color": unstable_props["energies"], + "colorscale": plotly_layouts["unstable_colorscale"], + "size": 6, + "symbol": "diamond", + "colorbar": {"title": "Energy Above Hull
(eV/atom)", "x": 0.05, "len": 0.75}, + }, + "hovertext": unstable_props["texts"], + } ) elif self._dim == 4: stable_markers = plotly_layouts["default_quaternary_marker_settings"].copy() stable_markers.update( - dict( - x=stable_props["x"], - y=stable_props["y"], - z=stable_props["z"], - name="Stable", - marker=dict( - color=stable_props["energies"], - colorscale=plotly_layouts["stable_markers_colorscale"], - size=8, - opacity=0.9, - ), - hovertext=stable_props["texts"], - ) + { + "x": stable_props["x"], + "y": stable_props["y"], + "z": stable_props["z"], + "name": "Stable", + "marker": { + "color": stable_props["energies"], + "colorscale": plotly_layouts["stable_markers_colorscale"], + "size": 8, + "opacity": 0.9, + }, + "hovertext": stable_props["texts"], + } ) unstable_markers = plotly_layouts["default_quaternary_marker_settings"].copy() unstable_markers.update( - dict( - x=unstable_props["x"], - y=unstable_props["y"], - z=unstable_props["z"], - name="Above Hull", - marker=dict( - color=unstable_props["energies"], - colorscale=plotly_layouts["unstable_colorscale"], - size=5, - symbol="diamond", - colorbar=dict(title="Energy Above Hull
(eV/atom)", x=0.05, len=0.75), - ), - hovertext=unstable_props["texts"], - visible="legendonly", - ) + { + "x": unstable_props["x"], + "y": unstable_props["y"], + "z": unstable_props["z"], + "name": "Above Hull", + "marker": { + "color": unstable_props["energies"], + "colorscale": plotly_layouts["unstable_colorscale"], + "size": 5, + "symbol": "diamond", + "colorbar": {"title": "Energy Above Hull
(eV/atom)", "x": 0.05, "len": 0.75}, + }, + "hovertext": unstable_props["texts"], + "visible": "legendonly", + } ) stable_marker_plot = go.Scatter(**stable_markers) if self._dim == 2 else go.Scatter3d(**stable_markers) @@ -3209,7 +3204,7 @@ def _create_plotly_uncertainty_shading(self, stable_marker_plot): name="Uncertainty (window)", fill="toself", mode="lines", - line=dict(width=0), + line={"width": 0}, fillcolor="lightblue", hoverinfo="skip", opacity=0.4, @@ -3248,7 +3243,7 @@ def _create_plotly_ternary_support_lines(self): z=list(z), mode="lines", hoverinfo="none", - line=dict(color="rgba (0, 0, 0, 0.4)", dash="solid", width=1.0), + line={"color": "rgba (0, 0, 0, 0.4)", "dash": "solid", "width": 1.0}, showlegend=False, ) @@ -3273,9 +3268,9 @@ def _create_plotly_ternary_hull(self): opacity=0.8, intensity=list(energies), colorscale=plotly_layouts["stable_colorscale"], - colorbar=dict(title="Formation energy
(eV/atom)", x=0.9, len=0.75), + colorbar={"title": "Formation energy
(eV/atom)", "x": 0.9, "len": 0.75}, hoverinfo="none", - lighting=dict(diffuse=0.0, ambient=1.0), + lighting={"diffuse": 0.0, "ambient": 1.0}, name="Convex Hull (shading)", flatshading=True, showlegend=True, diff --git a/pymatgen/analysis/piezo_sensitivity.py b/pymatgen/analysis/piezo_sensitivity.py index 9d7254c0125..e038f868a03 100644 --- a/pymatgen/analysis/piezo_sensitivity.py +++ b/pymatgen/analysis/piezo_sensitivity.py @@ -681,7 +681,6 @@ def get_piezo(BEC, IST, FCM, rcond=0.0001): Return: 3x3x3 calculated Piezo tensor """ - numsites = len(BEC) temp_fcm = np.reshape(np.swapaxes(FCM, 1, 2), (numsites * 3, numsites * 3)) diff --git a/pymatgen/analysis/pourbaix_diagram.py b/pymatgen/analysis/pourbaix_diagram.py index b87ee705e12..7e48dfb6118 100644 --- a/pymatgen/analysis/pourbaix_diagram.py +++ b/pymatgen/analysis/pourbaix_diagram.py @@ -228,10 +228,7 @@ def from_dict(cls, d): Invokes a PourbaixEntry from a dictionary """ entry_type = d["entry_type"] - if entry_type == "Ion": - entry = IonEntry.from_dict(d["entry"]) - else: - entry = MontyDecoder().process_decoded(d["entry"]) + entry = IonEntry.from_dict(d["entry"]) if entry_type == "Ion" else MontyDecoder().process_decoded(d["entry"]) entry_id = d["entry_id"] concentration = d["concentration"] return cls(entry, entry_id, concentration) @@ -314,10 +311,7 @@ def __getattr__(self, item): "uncorrected_energy", ]: # TODO: Composition could be changed for compat with sum - if item == "composition": - start = Composition({}) - else: - start = 0 + start = Composition({}) if item == "composition" else 0 return sum( (getattr(e, item) * w for e, w in zip(self.entry_list, self.weights)), start, @@ -611,7 +605,7 @@ def _get_hull_in_nph_nphi_space(self, entries): hull = ConvexHull(points, qhull_options="QJ i") # Create facets and remove top - facets = [facet for facet in hull.simplices if not len(points) - 1 in facet] + facets = [facet for facet in hull.simplices if len(points) - 1 not in facet] if self.dim > 1: logger.debug("Filtering facets by Pourbaix composition") @@ -750,7 +744,7 @@ def process_multientry(entry_list, prod_comp, coeff_threshold=1e-4): entry_comps = [e.composition for e in entry_list] rxn = Reaction(entry_comps + dummy_oh, [prod_comp]) react_coeffs = [-rxn.get_coeff(comp) for comp in entry_comps] - all_coeffs = react_coeffs + [rxn.get_coeff(prod_comp)] + all_coeffs = [*react_coeffs, rxn.get_coeff(prod_comp)] # Check if reaction coeff threshold met for Pourbaix compounds # All reactant/product coefficients must be positive nonzero @@ -810,7 +804,7 @@ def get_pourbaix_domains(pourbaix_entries, limits=None): [0, 0, -1, 2 * g_max], ] hs_hyperplanes = np.vstack([hyperplanes, border_hyperplanes]) - interior_point = np.average(limits, axis=1).tolist() + [g_max] + interior_point = [*np.average(limits, axis=1).tolist(), g_max] hs_int = HalfspaceIntersection(hs_hyperplanes, np.array(interior_point)) # organize the boundary points by entry diff --git a/pymatgen/analysis/quasiharmonic.py b/pymatgen/analysis/quasiharmonic.py index c33e6b8f54a..b15135c1e36 100644 --- a/pymatgen/analysis/quasiharmonic.py +++ b/pymatgen/analysis/quasiharmonic.py @@ -261,7 +261,7 @@ def gruneisen_parameter(self, temperature, volume): Eq(31) in doi.org/10.1016/j.comphy.2003.12.001 Eq(7) in Blanco et. al. Joumal of Molecular Structure (Theochem) 368 (1996) 245-255 - Also se J.P. Poirier, Introduction to the Physics of the Earth’s + Also se J.P. Poirier, Introduction to the Physics of the Earth's Interior, 2nd ed. (Cambridge University Press, Cambridge, 2000) Eq(3.53) diff --git a/pymatgen/analysis/reaction_calculator.py b/pymatgen/analysis/reaction_calculator.py index a134073677d..8c77bbdf7c9 100644 --- a/pymatgen/analysis/reaction_calculator.py +++ b/pymatgen/analysis/reaction_calculator.py @@ -440,9 +440,9 @@ def __init__(self, reactant_entries, product_entries): self._reactant_entries = reactant_entries self._product_entries = product_entries self._all_entries = reactant_entries + product_entries - reactant_comp = [e.composition.get_reduced_composition_and_factor()[0] for e in reactant_entries] + reactant_comp = [e.composition.reduced_composition for e in reactant_entries] - product_comp = [e.composition.get_reduced_composition_and_factor()[0] for e in product_entries] + product_comp = [e.composition.reduced_composition for e in product_entries] super().__init__(list(reactant_comp), list(product_comp)) @@ -469,7 +469,7 @@ def calculated_reaction_energy(self): calc_energies = {} for entry in self._reactant_entries + self._product_entries: - (comp, factor) = entry.composition.get_reduced_composition_and_factor() + comp, factor = entry.composition.get_reduced_composition_and_factor() calc_energies[comp] = min(calc_energies.get(comp, float("inf")), entry.energy / factor) return self.calculate_energy(calc_energies) @@ -483,7 +483,7 @@ def calculated_reaction_energy_uncertainty(self): calc_energies = {} for entry in self._reactant_entries + self._product_entries: - (comp, factor) = entry.composition.get_reduced_composition_and_factor() + comp, factor = entry.composition.get_reduced_composition_and_factor() energy_ufloat = ufloat(entry.energy, entry.correction_uncertainty) calc_energies[comp] = min(calc_energies.get(comp, float("inf")), energy_ufloat / factor) diff --git a/pymatgen/analysis/solar/slme.py b/pymatgen/analysis/solar/slme.py index ba68e3e59d7..a5947806d16 100644 --- a/pymatgen/analysis/solar/slme.py +++ b/pymatgen/analysis/solar/slme.py @@ -29,7 +29,6 @@ def get_dir_indir_gap(run=""): """ Get direct and indirect bandgaps for a vasprun.xml """ - v = Vasprun(run) bandstructure = v.get_band_structure() dir_gap = bandstructure.get_direct_band_gap() @@ -40,8 +39,10 @@ def get_dir_indir_gap(run=""): def matrix_eigvals(matrix): """ Calculate the eigenvalues of a matrix. + Args: matrix (np.array): The matrix to diagonalise. + Returns: (np.array): Array of the matrix eigenvalues. """ @@ -53,6 +54,7 @@ def to_matrix(xx, yy, zz, xy, yz, xz): """ Convert a list of matrix components to a symmetric 3x3 matrix. Inputs should be in the order xx, yy, zz, xy, yz, xz. + Args: xx (float): xx component of the matrix. yy (float): yy component of the matrix. @@ -60,6 +62,7 @@ def to_matrix(xx, yy, zz, xy, yz, xz): xy (float): xy component of the matrix. yz (float): yz component of the matrix. xz (float): xz component of the matrix. + Returns: (np.array): The matrix, as a 3x3 numpy array. """ @@ -71,10 +74,12 @@ def parse_dielectric_data(data): """ Convert a set of 2D vasprun formatted dielectric data to the eigenvalues of each corresponding 3x3 symmetric numpy matrices. + Args: data (list): length N list of dielectric data. Each entry should be a list of ``[xx, yy, zz, xy, xz, yz ]`` dielectric tensor elements. + Returns: (np.array): a Nx3 numpy array. Each row contains the eigenvalues for the corresponding row in `data`. @@ -86,6 +91,7 @@ def absorption_coefficient(dielectric): """ Calculate the optical absorption coefficient from an input set of pymatgen vasprun dielectric constant data. + Args: dielectric (list): A list containing the dielectric response function in the pymatgen vasprun format. @@ -159,7 +165,6 @@ def slme( The calculated maximum efficiency. """ - # Defining constants for tidy equations c = constants.c # speed of light, m/s h = constants.h # Planck's constant J*s (W) diff --git a/pymatgen/analysis/structure_analyzer.py b/pymatgen/analysis/structure_analyzer.py index 8c12e3939f8..3a40f8aa7cb 100644 --- a/pymatgen/analysis/structure_analyzer.py +++ b/pymatgen/analysis/structure_analyzer.py @@ -483,10 +483,9 @@ def parse_oxide(self) -> tuple[str, int]: elif np.any(dist_matrix < relative_cutoff * 1.49): is_peroxide = True bond_atoms = np.where(dist_matrix < relative_cutoff * 1.49)[0] - if is_superoxide: - if len(bond_atoms) > len(set(bond_atoms)): - is_superoxide = False - is_ozonide = True + if is_superoxide and len(bond_atoms) > len(set(bond_atoms)): + is_superoxide = False + is_ozonide = True try: n_bonds = len(set(bond_atoms)) except UnboundLocalError: diff --git a/pymatgen/analysis/structure_matcher.py b/pymatgen/analysis/structure_matcher.py index 40ec92f091a..d57dfe3c917 100644 --- a/pymatgen/analysis/structure_matcher.py +++ b/pymatgen/analysis/structure_matcher.py @@ -773,10 +773,7 @@ def _strict_match( inv_lll_abc = np.array(avg_l.get_lll_reduced_lattice().reciprocal_lattice.abc) lll_frac_tol = inv_lll_abc * self.stol / (np.pi * normalization) dist, t_adj, mapping = self._cart_dists(s1fc, t_s2fc, avg_l, mask, normalization, lll_frac_tol) - if use_rms: - val = np.linalg.norm(dist) / len(dist) ** 0.5 - else: - val = max(dist) + val = np.linalg.norm(dist) / len(dist) ** 0.5 if use_rms else max(dist) # pylint: disable=E1136 if best_match is None or val < best_match[0]: total_t = t + t_adj @@ -901,6 +898,7 @@ def _anonymous_match( ): """ Tries all permutations of matching struct1 to struct2. + Args: struct1 (Structure): First structure struct2 (Structure): Second structure diff --git a/pymatgen/analysis/structure_prediction/dopant_predictor.py b/pymatgen/analysis/structure_prediction/dopant_predictor.py index 4b7e36bdfe0..8ae2081609b 100644 --- a/pymatgen/analysis/structure_prediction/dopant_predictor.py +++ b/pymatgen/analysis/structure_prediction/dopant_predictor.py @@ -202,7 +202,7 @@ def _int_to_roman(number): result = [] for arabic, roman in roman_conv: - (factor, number) = divmod(number, arabic) + factor, number = divmod(number, arabic) result.append(roman * factor) if number == 0: break diff --git a/pymatgen/analysis/structure_prediction/substitution_probability.py b/pymatgen/analysis/structure_prediction/substitution_probability.py index cd67ee699a3..404737bd3e7 100644 --- a/pymatgen/analysis/structure_prediction/substitution_probability.py +++ b/pymatgen/analysis/structure_prediction/substitution_probability.py @@ -205,6 +205,7 @@ def list_prediction(self, species, to_this_composition=True): will be found. If false, substitutions with this as a starting composition will be found (these are slightly different) + Returns: List of predictions in the form of dictionaries. If to_this_composition is true, the values of the dictionary @@ -239,11 +240,8 @@ def _recurse(output_prob, output_species): return for sp in self.p.species: i = len(output_prob) - if to_this_composition: - prob = self.p.cond_prob(sp, species[i]) - else: - prob = self.p.cond_prob(species[i], sp) - _recurse(output_prob + [prob], output_species + [sp]) + prob = self.p.cond_prob(sp, species[i]) if to_this_composition else self.p.cond_prob(species[i], sp) + _recurse([*output_prob, prob], [*output_species, sp]) _recurse([], []) logging.info(f"{len(output)} substitutions found") @@ -272,10 +270,7 @@ def composition_prediction(self, composition, to_this_composition=True): preds = self.list_prediction(list(composition), to_this_composition) output = [] for p in preds: - if to_this_composition: - subs = {v: k for k, v in p["substitutions"].items()} - else: - subs = p["substitutions"] + subs = {v: k for k, v in p["substitutions"].items()} if to_this_composition else p["substitutions"] charge = 0 for k, v in composition.items(): charge += subs[k].oxi_state * v diff --git a/pymatgen/analysis/structure_prediction/substitutor.py b/pymatgen/analysis/structure_prediction/substitutor.py index 6d4a19155ec..bda2a61bdf0 100644 --- a/pymatgen/analysis/structure_prediction/substitutor.py +++ b/pymatgen/analysis/structure_prediction/substitutor.py @@ -223,7 +223,7 @@ def _recurse(output_prob, output_species): for sp in self._sp.species: i = len(output_prob) prob = self._sp.cond_prob(sp, species_list[i]) - _recurse(output_prob + [prob], output_species + [sp]) + _recurse([*output_prob, prob], [*output_species, sp]) _recurse([], []) logging.info(f"{len(output)} substitutions found") diff --git a/pymatgen/analysis/structure_prediction/volume_predictor.py b/pymatgen/analysis/structure_prediction/volume_predictor.py index 2eff7b7f7c1..24af45a919a 100644 --- a/pymatgen/analysis/structure_prediction/volume_predictor.py +++ b/pymatgen/analysis/structure_prediction/volume_predictor.py @@ -62,6 +62,7 @@ def predict(self, structure: Structure, ref_structure): structure (Structure): structure w/unknown volume ref_structure (Structure): A reference structure with a similar structure but different species. + Returns: a float value of the predicted volume """ @@ -126,10 +127,12 @@ def get_predicted_structure(self, structure: Structure, ref_structure): """ Given a structure, returns back the structure scaled to predicted volume. + Args: structure (Structure): structure w/unknown volume ref_structure (Structure): A reference structure with a similar structure but different species. + Returns: a Structure object with predicted volume """ @@ -239,6 +242,7 @@ def get_predicted_structure(self, structure: Structure, icsd_vol=False): """ Given a structure, returns back the structure scaled to predicted volume. + Args: structure (Structure): structure w/unknown volume diff --git a/pymatgen/analysis/surface_analysis.py b/pymatgen/analysis/surface_analysis.py index 86941e33ea1..e65004aa782 100644 --- a/pymatgen/analysis/surface_analysis.py +++ b/pymatgen/analysis/surface_analysis.py @@ -23,16 +23,16 @@ Computational Materials, 3(1), 14. https://doi.org/10.1038/s41524-017-0017-z -TODO: - -Still assumes individual elements have their own chempots - in a molecular adsorbate instead of considering a single - chempot for a single molecular adsorbate. E.g. for an OH - adsorbate, the surface energy is a function of delu_O and - delu_H instead of delu_OH - -Need a method to automatically get chempot range when - dealing with non-stoichiometric slabs - -Simplify the input for SurfaceEnergyPlotter such that the - user does not need to generate a dict +Todo: +- Still assumes individual elements have their own chempots + in a molecular adsorbate instead of considering a single + chempot for a single molecular adsorbate. E.g. for an OH + adsorbate, the surface energy is a function of delu_O and + delu_H instead of delu_OH +- Need a method to automatically get chempot range when + dealing with non-stoichiometric slabs +- Simplify the input for SurfaceEnergyPlotter such that the + user does not need to generate a dict """ from __future__ import annotations @@ -131,7 +131,7 @@ def __init__( """ self.miller_index = miller_index self.label = label - self.adsorbates = [] if not adsorbates else adsorbates + self.adsorbates = adsorbates if adsorbates else [] self.clean_entry = clean_entry self.ads_entries_dict = {str(list(ads.composition.as_dict())[0]): ads for ads in self.adsorbates} self.mark = marker @@ -178,6 +178,7 @@ def gibbs_binding_energy(self, eads=False): def surface_energy(self, ucell_entry, ref_entries=None): """ Calculates the surface energy of this SlabEntry. + Args: ucell_entry (entry): An entry object for the bulk ref_entries (list: [entry]): A list of entries for each type @@ -190,7 +191,7 @@ def surface_energy(self, ucell_entry, ref_entries=None): Returns (Add (Sympy class)): Surface energy """ # Set up - ref_entries = [] if not ref_entries else ref_entries + ref_entries = ref_entries if ref_entries else [] # Check if appropriate ref_entries are present if the slab is non-stoichiometric # TODO: There should be a way to identify which specific species are @@ -421,6 +422,7 @@ def __init__(self, all_slab_entries, ucell_entry, ref_entries=None): """ Object for plotting surface energy in different ways for clean and adsorbed surfaces. + Args: all_slab_entries (dict or list): Dictionary or list containing all entries for slab calculations. See attributes. @@ -662,6 +664,7 @@ def get_surface_equilibrium(self, slab_entries, delu_dict=None): building surface phase diagrams. Note that to solve for x equations (x slab_entries), there must be x free variables (chemical potentials). Adjust delu_dict as need be to get the correct number of free variables. + Args: slab_entries (array): The coefficients of the first equation delu_dict (dict): Dictionary of the chemical potentials to be set as @@ -985,7 +988,7 @@ def chempot_vs_gamma( delu_dict = {} chempot_range = sorted(chempot_range) - plt = pretty_plot(width=8, height=7) if not plt else plt + plt = plt if plt else pretty_plot(width=8, height=7) axes = plt.gca() for hkl in self.all_slab_entries: @@ -1136,6 +1139,7 @@ def BE_vs_clean_SE( """ For each facet, plot the clean surface energy against the most stable binding energy. + Args: delu_dict (dict): Dictionary of the chemical potentials to be set as constant. Note the key should be a sympy Symbol object of the @@ -1198,6 +1202,7 @@ def surface_chempot_range_map( and determines the chempot rangeo fht e second element for each SlabEntry. Future implementation will determine the chempot range map first by solving systems of equations up to 3 instead of 2. + Args: elements (list): Sequence of elements to be considered as independent variables. E.g., if you want to show the stability ranges of @@ -1219,7 +1224,7 @@ def surface_chempot_range_map( """ # Set up delu_dict = delu_dict or {} - plt = pretty_plot(12, 8) if not plt else plt + plt = plt if plt else pretty_plot(12, 8) el1, el2 = str(elements[0]), str(elements[1]) delu1 = Symbol(f"delu_{str(elements[0])}") delu2 = Symbol(f"delu_{str(elements[1])}") @@ -1373,17 +1378,13 @@ def entry_dict_from_list(all_slab_entries): key to a dictionary with a clean SlabEntry as the key to a list of adsorbed SlabEntry. """ - entry_dict = {} for entry in all_slab_entries: hkl = tuple(entry.miller_index) if hkl not in entry_dict: entry_dict[hkl] = {} - if entry.clean_entry: - clean = entry.clean_entry - else: - clean = entry + clean = entry.clean_entry if entry.clean_entry else entry if clean not in entry_dict[hkl]: entry_dict[hkl][clean] = [] if entry.adsorbates: @@ -1518,7 +1519,7 @@ def get_locpot_along_slab_plot(self, label_energies=True, plt=None, label_fontsi Returns plt of the locpot vs c axis """ - plt = pretty_plot(width=6, height=4) if not plt else plt + plt = plt if plt else pretty_plot(width=6, height=4) # plot the raw locpot signal along c plt.plot(self.along_c, self.locpot_along_c, "b--") @@ -1641,6 +1642,7 @@ def is_converged(self, min_points_frac=0.015, tol: float = 0.0025): potential within some distance (min_point) about where the peak electrostatic potential is found along the c direction of the slab. This is dependent on the size of the slab. + Args: min_point (fractional coordinates): The number of data points +/- the point of where the electrostatic potential is at @@ -1986,6 +1988,7 @@ def sub_chempots(gamma_dict, chempots): Uses dot product of numpy array to sub chemical potentials into the surface grand potential. This is much faster than using the subs function in sympy. + Args: gamma_dict (dict): Surface grand potential equation as a coefficient dictionary @@ -1994,7 +1997,6 @@ def sub_chempots(gamma_dict, chempots): Returns: Surface energy as a float """ - coeffs = [gamma_dict[k] for k in gamma_dict] chempot_vals = [] for k in gamma_dict: diff --git a/pymatgen/analysis/tests/test_interface_reactions.py b/pymatgen/analysis/tests/test_interface_reactions.py index 300ae335086..16dfa9163f6 100644 --- a/pymatgen/analysis/tests/test_interface_reactions.py +++ b/pymatgen/analysis/tests/test_interface_reactions.py @@ -160,7 +160,7 @@ def setUp(self): norm=False, include_no_mixing_energy=True, ) - assert "Please provide non-grand phase diagram to compute no_mixing_energy!" == str(context2.exception) + assert str(context2.exception) == "Please provide non-grand phase diagram to compute no_mixing_energy!" self.ir = [ir_0, ir_1, ir_2, ir_3, ir_4, ir_5, ir_6, ir_7, ir_8, ir_9, ir_10, ir_11, ir_12] diff --git a/pymatgen/analysis/tests/test_local_env.py b/pymatgen/analysis/tests/test_local_env.py index 9448db0eff3..176554dd369 100644 --- a/pymatgen/analysis/tests/test_local_env.py +++ b/pymatgen/analysis/tests/test_local_env.py @@ -126,11 +126,11 @@ def test_nn_shell(self): # Get the 1NN shell self.nn.targets = None nns = self.nn.get_nn_shell_info(s, 0, 1) - assert 6 == len(nns) + assert len(nns) == 6 # Test the 2nd NN shell nns = self.nn.get_nn_shell_info(s, 0, 2) - assert 18 == len(nns) + assert len(nns) == 18 self.assertArrayAlmostEqual([1] * 6, [x["weight"] for x in nns if max(np.abs(x["image"])) == 2]) self.assertArrayAlmostEqual([2] * 12, [x["weight"] for x in nns if max(np.abs(x["image"])) == 1]) @@ -152,15 +152,15 @@ def test_nn_shell(self): ) self.nn.weight = "area" nns = self.nn.get_nn_shell_info(cscl, 0, 1) - assert 14 == len(nns) - assert 6 == np.isclose([x["weight"] for x in nns], 0.125 / 0.32476).sum() # Square faces - assert 8 == np.isclose([x["weight"] for x in nns], 1).sum() + assert len(nns) == 14 + assert np.isclose([x["weight"] for x in nns], 0.125 / 0.32476).sum() == 6 # Square faces + assert np.isclose([x["weight"] for x in nns], 1).sum() == 8 nns = self.nn.get_nn_shell_info(cscl, 0, 2) # Weight of getting back on to own site # Square-square hop: 6*5 options times (0.125/0.32476)^2 weight each # Hex-hex hop: 8*7 options times 1 weight each - assert 60.4444 == approx(np.sum([x["weight"] for x in nns if x["site_index"] == 0]), abs=1e-3) + assert approx(np.sum([x["weight"] for x in nns if x["site_index"] == 0]), abs=1e-3) == 60.4444 def test_adj_neighbors(self): # Make a simple cubic structure @@ -172,10 +172,10 @@ def test_adj_neighbors(self): # Each neighbor has 4 adjacent neighbors, all orthogonal for nn_info in neighbors.values(): - assert 4 == len(nn_info["adj_neighbors"]) + assert len(nn_info["adj_neighbors"]) == 4 for adj_key in nn_info["adj_neighbors"]: - assert 0 == np.dot(nn_info["normal"], neighbors[adj_key]["normal"]) + assert np.dot(nn_info["normal"], neighbors[adj_key]["normal"]) == 0 def test_all_at_once(self): # Get all of the sites for LiFePO4 @@ -219,7 +219,7 @@ def test_Cs2O(self): # Compute the voronoi tessellation result = VoronoiNN().get_all_voronoi_polyhedra(strc) - assert 3 == len(result) + assert len(result) == 3 def test_filtered(self): nn = VoronoiNN(weight="area") @@ -240,12 +240,12 @@ def test_filtered(self): # Run one test where you get the small neighbors nn.tol = little_weight * 0.99 nns = nn.get_nn_info(bcc, 0) - assert 14 == len(nns) + assert len(nns) == 14 # Run a second test where we screen out little faces nn.tol = little_weight * 1.01 nns = nn.get_nn_info(bcc, 0) - assert 8 == len(nns) + assert len(nns) == 8 # Make sure it works for the `get_all` operation all_nns = nn.get_all_nn_info(bcc * [2, 2, 2]) @@ -1199,12 +1199,12 @@ def test_get_order_parameters(self): assert int(op_vals[22] * 1000 + 0.5) == approx(1000) # Cuboctahedral motif. - op_vals = ops_101.get_order_parameters(self.cuboctahedron, 0, indices_neighs=[i for i in range(1, 13)]) + op_vals = ops_101.get_order_parameters(self.cuboctahedron, 0, indices_neighs=list(range(1, 13))) assert int(op_vals[24] * 1000 + 0.5) == approx(1000) assert int(op_vals[32] * 1000 + 0.5) == approx(1000) # See-saw motif. - op_vals = ops_101.get_order_parameters(self.see_saw_rect, 0, indices_neighs=[i for i in range(1, 5)]) + op_vals = ops_101.get_order_parameters(self.see_saw_rect, 0, indices_neighs=list(range(1, 5))) assert int(op_vals[25] * 1000 + 0.5) == approx(1000) # Hexagonal planar motif. @@ -1212,9 +1212,7 @@ def test_get_order_parameters(self): assert int(op_vals[26] * 1000 + 0.5) == approx(1000) # Square face capped trigonal prism. - op_vals = ops_101.get_order_parameters( - self.sq_face_capped_trig_pris, 0, indices_neighs=[i for i in range(1, 8)] - ) + op_vals = ops_101.get_order_parameters(self.sq_face_capped_trig_pris, 0, indices_neighs=list(range(1, 8))) assert int(op_vals[34] * 1000 + 0.5) == approx(1000) # Test providing explicit neighbor lists. diff --git a/pymatgen/analysis/tests/test_phase_diagram.py b/pymatgen/analysis/tests/test_phase_diagram.py index 6f9b1d02f35..4b1af227458 100644 --- a/pymatgen/analysis/tests/test_phase_diagram.py +++ b/pymatgen/analysis/tests/test_phase_diagram.py @@ -542,7 +542,7 @@ def test_get_critical_compositions(self): # For the moment, should also fail even if compositions are in the gppd # because it isn't handled properly - gppd = GrandPotentialPhaseDiagram(self.pd.all_entries, {"Xe": 1}, self.pd.elements + [Element("Xe")]) + gppd = GrandPotentialPhaseDiagram(self.pd.all_entries, {"Xe": 1}, [*self.pd.elements, Element("Xe")]) with pytest.raises(ValueError): gppd.get_critical_compositions( Composition("Fe2O3"), @@ -626,7 +626,7 @@ def test_el_refs(self): def test_val_err_on_no_entries(self): # check that PhaseDiagram raises ValueError when building phase diagram with no entries - for entries in [None, [], set(), tuple()]: + for entries in [None, [], set(), ()]: with pytest.raises(ValueError, match="Unable to build phase diagram without entries."): PhaseDiagram(entries=entries) @@ -871,8 +871,8 @@ def setUp(self): self.plotter_ternary_mpl = PDPlotter(self.pd_ternary, backend="matplotlib") self.plotter_ternary_plotly = PDPlotter(self.pd_ternary, backend="plotly") - entrieslio = [e for e in entries if "Fe" not in e.composition] - self.pd_binary = PhaseDiagram(entrieslio) + entries_LiO = [e for e in entries if "Fe" not in e.composition] + self.pd_binary = PhaseDiagram(entries_LiO) self.plotter_binary_mpl = PDPlotter(self.pd_binary, backend="matplotlib") self.plotter_binary_plotly = PDPlotter(self.pd_binary, backend="plotly") @@ -882,17 +882,17 @@ def setUp(self): self.plotter_quaternary_plotly = PDPlotter(self.pd_quaternary, backend="plotly") def test_pd_plot_data(self): - (lines, labels, unstable_entries) = self.plotter_ternary_mpl.pd_plot_data + lines, labels, unstable_entries = self.plotter_ternary_mpl.pd_plot_data assert len(lines) == 22 assert len(labels) == len(self.pd_ternary.stable_entries), "Incorrect number of lines generated!" assert len(unstable_entries) == len(self.pd_ternary.all_entries) - len( self.pd_ternary.stable_entries ), "Incorrect number of lines generated!" - (lines, labels, unstable_entries) = self.plotter_quaternary_mpl.pd_plot_data + lines, labels, unstable_entries = self.plotter_quaternary_mpl.pd_plot_data assert len(lines) == 33 assert len(labels) == len(self.pd_quaternary.stable_entries) assert len(unstable_entries) == len(self.pd_quaternary.all_entries) - len(self.pd_quaternary.stable_entries) - (lines, labels, unstable_entries) = self.plotter_binary_mpl.pd_plot_data + lines, labels, unstable_entries = self.plotter_binary_mpl.pd_plot_data assert len(lines) == 3 assert len(labels) == len(self.pd_binary.stable_entries) diff --git a/pymatgen/analysis/tests/test_pourbaix_diagram.py b/pymatgen/analysis/tests/test_pourbaix_diagram.py index 666d3750176..68a622b5311 100644 --- a/pymatgen/analysis/tests/test_pourbaix_diagram.py +++ b/pymatgen/analysis/tests/test_pourbaix_diagram.py @@ -222,7 +222,7 @@ def test_get_decomposition(self): ion = IonEntry(Ion.from_formula("NaO28H80Sn12C24+"), -161.676) custom_ion_entry = PourbaixEntry(ion, entry_id="my_ion") pbx = PourbaixDiagram( - entries + [custom_ion_entry], + [*entries, custom_ion_entry], filter_solids=True, comp_dict={"Na": 1, "Sn": 12, "C": 24}, ) diff --git a/pymatgen/analysis/tests/test_surface_analysis.py b/pymatgen/analysis/tests/test_surface_analysis.py index 0a1dc5717a2..6d2fa9e9c36 100644 --- a/pymatgen/analysis/tests/test_surface_analysis.py +++ b/pymatgen/analysis/tests/test_surface_analysis.py @@ -367,7 +367,7 @@ def test_scaled_wulff(self): w2 = self.nanoscale_stability.scaled_wulff(fcc_wulff, 10) assert w1.effective_radius == approx(w2.effective_radius) assert w1.effective_radius == approx(10) - assert 10 == approx(w2.effective_radius) + assert approx(w2.effective_radius) == 10 def get_entry_dict(filename): diff --git a/pymatgen/analysis/tests/test_wulff.py b/pymatgen/analysis/tests/test_wulff.py index af353ee9bc0..f56c0923842 100644 --- a/pymatgen/analysis/tests/test_wulff.py +++ b/pymatgen/analysis/tests/test_wulff.py @@ -117,7 +117,7 @@ def consistency_tests(self): # is the most dominant facet on the Wulff shape fractional_areas = self.wulff_Ir.area_fraction_dict - miller_list = [hkl for hkl in fractional_areas] + miller_list = list(fractional_areas) area_list = [fractional_areas[hkl] for hkl in fractional_areas] assert miller_list[area_list.index(max(area_list))] == (1, 1, 1) diff --git a/pymatgen/analysis/topological/spillage.py b/pymatgen/analysis/topological/spillage.py index 268cfd6d6c4..d16b2ebf482 100644 --- a/pymatgen/analysis/topological/spillage.py +++ b/pymatgen/analysis/topological/spillage.py @@ -22,6 +22,7 @@ class SOCSpillage: def __init__(self, wf_noso="", wf_so=""): """ Requires path to WAVECAR files with and without LSORBIT = .TRUE. + Args: wf_noso : WAVECAR without spin-orbit coupling wf_so : WAVECAR with spin-orbit coupling diff --git a/pymatgen/analysis/wulff.py b/pymatgen/analysis/wulff.py index 915f435b8c0..87b50f7ebb9 100644 --- a/pymatgen/analysis/wulff.py +++ b/pymatgen/analysis/wulff.py @@ -4,7 +4,7 @@ """ This module define a WulffShape class to generate the Wulff shape from a lattice, a list of indices and their corresponding surface energies, -and the total area and volume of the wulff shape,the weighted surface energy, +and the total area and volume of the Wulff shape, the weighted surface energy, the anisotropy and shape_factor can also be calculated. In support of plotting from a given view in terms of miller index. @@ -202,8 +202,8 @@ def __init__(self, lattice: Lattice, miller_list, e_surf_list, symprec=1e-5): # simplices (ndarray of ints, shape (nfacet, ndim)) # list of [i, j, k] , ndim = 3 # i, j, k: ind for normal_e_m - # recalculate the dual of dual, get the wulff shape. - # conner <-> surface + # recalculate the dual of dual, get the Wulff shape. + # corner <-> surface # get cross point from the simplices of the dual convex hull wulff_pt_list = [self._get_cross_pt_dual_simp(dual_simp) for dual_simp in dual_cv_simp] @@ -226,15 +226,12 @@ def __init__(self, lattice: Lattice, miller_list, e_surf_list, symprec=1e-5): def _get_all_miller_e(self): """ - from self: - get miller_list(unique_miller), e_surf_list and symmetry - operations(symmops) according to lattice - apply symmops to get all the miller index, then get normal, - get all the facets functions for wulff shape calculation: - |normal| = 1, e_surf is plane's distance to (0, 0, 0), - normal[0]x + normal[1]y + normal[2]z = e_surf + From self: get miller_list(unique_miller), e_surf_list and symmetry operations(symmops) + according to lattice apply symmops to get all the miller index, then get normal, get + all the facets functions for Wulff shape calculation: |normal| = 1, e_surf is plane's + distance to (0, 0, 0), normal[0]x + normal[1]y + normal[2]z = e_surf - return: + Returns: [WulffFacet] """ all_hkl = [] @@ -305,11 +302,11 @@ def _get_simpx_plane(self): def _get_colors(self, color_set, alpha, off_color, custom_colors=None): """ - assign colors according to the surface energies of on_wulff facets. + Assign colors according to the surface energies of on_wulff facets. - return: - (color_list, color_proxy, color_proxy_on_wulff, miller_on_wulff, - e_surf_on_wulff_list) + Returns: + tuple: color_list, color_proxy, color_proxy_on_wulff, miller_on_wulff, + e_surf_on_wulff_list """ import matplotlib as mpl import matplotlib.pyplot as plt @@ -430,13 +427,9 @@ def get_plot( import matplotlib.pyplot as plt import mpl_toolkits.mplot3d as mpl3 - ( - color_list, - color_proxy, - color_proxy_on_wulff, - miller_on_wulff, - e_surf_on_wulff, - ) = self._get_colors(color_set, alpha, off_color, custom_colors=custom_colors or {}) + color_list, color_proxy, color_proxy_on_wulff, miller_on_wulff, e_surf_on_wulff = self._get_colors( + color_set, alpha, off_color, custom_colors=custom_colors or {} + ) if not direction: # If direction is not specified, use the miller indices of @@ -513,7 +506,7 @@ def get_plot( ax1, cmap=cmap, norm=norm, - boundaries=[0] + bounds + [10], + boundaries=[0, *bounds] + [10], extend="both", ticks=bounds[:-1], spacing="proportional", @@ -602,7 +595,7 @@ def get_plotly( i=tri_indices[0], j=tri_indices[1], k=tri_indices[2], - hovertemplate=f"
%{{text}}
γ={plane.e_surf:.3f} {units}
", + hovertemplate=f"
%{{text}}
y={plane.e_surf:.3f} {units}
", color=color, text=[f"Miller index: {hkl}"] * len(x_pts), hoverinfo="name", @@ -633,7 +626,7 @@ def get_plotly( ticktext=ticktext, tickvals=tickvals, ), - colorscale=[[0, "rgb(255,255,255, 255)"]] + color_scale, # fix the scale + colorscale=[[0, "rgb(255,255,255, 255)"], *color_scale], # fix the scale intensity=[0, 0.33, 0.66, 1], i=[0], j=[0], @@ -644,22 +637,20 @@ def get_plotly( planes_data.append(colorbar) # Format aesthetics: background, axis, etc. - axis_dict = dict( - title="", - autorange=True, - showgrid=False, - zeroline=False, - ticks="", - showline=False, - showticklabels=False, - showbackground=False, - ) + axis_dict = { + "title": "", + "autorange": True, + "showgrid": False, + "zeroline": False, + "ticks": "", + "showline": False, + "showticklabels": False, + "showbackground": False, + } fig = go.Figure(data=planes_data) - fig.update_layout( - dict( - showlegend=True, - scene=dict(xaxis=axis_dict, yaxis=axis_dict, zaxis=axis_dict), - ) + fig.layout.update( + showlegend=True, + scene={"xaxis": axis_dict, "yaxis": axis_dict, "zaxis": axis_dict}, ) return fig @@ -800,7 +791,7 @@ def tot_edges(self): for idx, _ in enumerate(pt): if idx == len(pt) / 2: break - lines.append(tuple(sorted(tuple([tuple(pt[idx * 2]), tuple(pt[idx * 2 + 1])])))) + lines.append(tuple(sorted((tuple(pt[idx * 2]), tuple(pt[idx * 2 + 1]))))) for p in lines: if p not in all_edges: diff --git a/pymatgen/analysis/xas/tests/test_spectrum.py b/pymatgen/analysis/xas/tests/test_spectrum.py index 651ebc4553f..c75426e3e88 100644 --- a/pymatgen/analysis/xas/tests/test_spectrum.py +++ b/pymatgen/analysis/xas/tests/test_spectrum.py @@ -43,7 +43,7 @@ def setUp(self): self.site2_xanes = XAS.from_dict(site2_xanes_dict) def test_e0(self): - assert 7728.565 == approx(self.k_xanes.e0) + assert approx(self.k_xanes.e0) == 7728.565 def test_k(self): assert len(self.k_xanes.x) == len(self.k_xanes.k) @@ -51,14 +51,14 @@ def test_k(self): def test_normalization(self): self.k_xanes.normalize(mode="sum") - assert 1.0 == approx(np.sum(self.k_xanes.y)) + assert approx(np.sum(self.k_xanes.y)) == 1.0 def test_add_mul(self): scaled_spect = self.k_xanes + self.k_xanes scaled_spect2 = self.k_xanes * 3 assert np.allclose(scaled_spect.y, 2 * self.k_xanes.y) assert np.allclose(scaled_spect2.y, 3 * self.k_xanes.y) - assert 0.274302 == approx(self.k_xanes.get_interpolated_value(7720.422), abs=1e-3) + assert approx(self.k_xanes.get_interpolated_value(7720.422), abs=1e-3) == 0.274302 def test_to_from_dict(self): s = XAS.from_dict(self.k_xanes.as_dict()) @@ -86,7 +86,7 @@ def test_stitch_xafs(self): XAS.stitch(self.k_xanes, self.k_exafs, mode="invalid") xafs = XAS.stitch(self.k_xanes, self.k_exafs, mode="XAFS") assert isinstance(xafs, XAS) - assert "XAFS" == xafs.spectrum_type + assert xafs.spectrum_type == "XAFS" assert len(xafs.x) == 500 assert min(xafs.x) == approx(min(self.k_xanes.x), abs=1e-2) assert max(xafs.y) == approx(max(self.k_xanes.y), abs=1e-2) @@ -111,7 +111,7 @@ def test_stitch_l23(self): self.l2_xanes = XAS.from_dict(l2_xanes_dict) l23 = XAS.stitch(self.l2_xanes, self.l3_xanes, 100, mode="L23") assert isinstance(l23, XAS) - assert "L23" == l23.edge + assert l23.edge == "L23" assert min(l23.x) == approx(min(self.l3_xanes.x), abs=1e-3) assert max(l23.x) == approx(max(self.l3_xanes.x), abs=1e-3) assert np.greater_equal(l23.y, self.l2_xanes.y).all() diff --git a/pymatgen/apps/battery/analyzer.py b/pymatgen/apps/battery/analyzer.py index 681a5a36c40..bf4a1b3c63b 100644 --- a/pymatgen/apps/battery/analyzer.py +++ b/pymatgen/apps/battery/analyzer.py @@ -252,7 +252,6 @@ def is_redox_active_intercalation(element) -> bool: Args: element: Element object """ - ns = [ "Ti", "V", diff --git a/pymatgen/apps/battery/battery_abc.py b/pymatgen/apps/battery/battery_abc.py index 064242fa912..3dd3fa759ac 100644 --- a/pymatgen/apps/battery/battery_abc.py +++ b/pymatgen/apps/battery/battery_abc.py @@ -249,6 +249,7 @@ def get_sub_electrodes(self, adjacent_only=True): If this electrode contains multiple voltage steps, then it is possible to use only a subset of the voltage steps to define other electrodes. Must be implemented for each electrode object. + Args: adjacent_only: Only return electrodes from compounds that are adjacent on the convex hull, i.e. no electrodes returned diff --git a/pymatgen/apps/battery/insertion_battery.py b/pymatgen/apps/battery/insertion_battery.py index 7716d0088d9..928ec2b8a01 100644 --- a/pymatgen/apps/battery/insertion_battery.py +++ b/pymatgen/apps/battery/insertion_battery.py @@ -298,8 +298,8 @@ def get_sub_electrodes(self, adjacent_only=True, include_myself=True): entry_discharge = pair.entry_discharge if adjacent_only else pair[1].entry_discharge def in_range(entry): - chg_frac = entry_charge.composition.get_atomic_fraction(ion) # noqa: B023 - dischg_frac = entry_discharge.composition.get_atomic_fraction(ion) # noqa: B023 + chg_frac = entry_charge.composition.get_atomic_fraction(ion) + dischg_frac = entry_discharge.composition.get_atomic_fraction(ion) frac = entry.composition.get_atomic_fraction(ion) return chg_frac <= frac <= dischg_frac diff --git a/pymatgen/apps/battery/plotter.py b/pymatgen/apps/battery/plotter.py index 32f84d3ca1a..3fb8f808e3c 100644 --- a/pymatgen/apps/battery/plotter.py +++ b/pymatgen/apps/battery/plotter.py @@ -139,7 +139,7 @@ def get_plotly_figure( Returns: """ - font_dict = dict(family="Arial", size=24, color="#000000") if font_dict is None else font_dict + font_dict = {"family": "Arial", "size": 24, "color": "#000000"} if font_dict is None else font_dict hover_temp = "Voltage : %{y:.2f} V" data = [] @@ -166,8 +166,8 @@ def get_plotly_figure( width=width, height=height, font=font_dict, - xaxis=dict(title=self._choose_best_x_lable(formula=formula, wion_symbol=wion_symbol)), - yaxis=dict(title="Voltage (V)"), + xaxis={"title": self._choose_best_x_lable(formula=formula, wion_symbol=wion_symbol)}, + yaxis={"title": "Voltage (V)"}, **kwargs, ), ) @@ -181,15 +181,9 @@ def _choose_best_x_lable(self, formula, wion_symbol): if self.xaxis == "capacity_vol": return "Capacity (Ah/l)" - if len(formula) == 1: - formula = formula.pop() - else: - formula = None + formula = formula.pop() if len(formula) == 1 else None - if len(wion_symbol) == 1: - wion_symbol = wion_symbol.pop() - else: - wion_symbol = None + wion_symbol = wion_symbol.pop() if len(wion_symbol) == 1 else None if self.xaxis == "x_form": if formula and wion_symbol: diff --git a/pymatgen/cli/pmg_analyze.py b/pymatgen/cli/pmg_analyze.py index 22069aa1e87..a54c856d98f 100644 --- a/pymatgen/cli/pmg_analyze.py +++ b/pymatgen/cli/pmg_analyze.py @@ -34,6 +34,7 @@ def get_energies(rootdir, reanalyze, verbose, quick, sort, fmt): """ Get energies of all vaspruns in directory (nested). + Args: rootdir (str): Root directory. reanalyze (bool): Whether to ignore saved results and reanalyze diff --git a/pymatgen/cli/pmg_query.py b/pymatgen/cli/pmg_query.py index 9c6b9bcde00..e4a609a5c9e 100644 --- a/pymatgen/cli/pmg_query.py +++ b/pymatgen/cli/pmg_query.py @@ -49,13 +49,7 @@ def do_query(args): props += args.data entries = m.get_entries(criteria, property_data=props) t = [] - headers = [ - "mp-id", - "Formula", - "Spacegroup", - "E/atom (eV)", - "E above hull (eV)", - ] + args.data + headers = ["mp-id", "Formula", "Spacegroup", "E/atom (eV)", "E above hull (eV)", *args.data] for e in entries: row = [ e.entry_id, diff --git a/pymatgen/command_line/bader_caller.py b/pymatgen/command_line/bader_caller.py index deb25b43232..97f391bb110 100644 --- a/pymatgen/command_line/bader_caller.py +++ b/pymatgen/command_line/bader_caller.py @@ -161,14 +161,12 @@ def __init__( with ScratchDir("."): tmpfile = "CHGCAR" if chgcar_filename else "CUBE" - with zopen(fpath, "rt") as f_in: - with open(tmpfile, "w") as f_out: - shutil.copyfileobj(f_in, f_out) + with zopen(fpath, "rt") as f_in, open(tmpfile, "w") as f_out: + shutil.copyfileobj(f_in, f_out) args = [BADEREXE, tmpfile] if chgref_filename: - with zopen(chgrefpath, "rt") as f_in: - with open("CHGCAR_ref", "w") as f_out: - shutil.copyfileobj(f_in, f_out) + with zopen(chgrefpath, "rt") as f_in, open("CHGCAR_ref", "w") as f_out: + shutil.copyfileobj(f_in, f_out) args += ["-ref", "CHGCAR_ref"] if parse_atomic_densities: args += ["-p", "all_atom"] @@ -530,7 +528,6 @@ def bader_analysis_from_objects(chgcar, potcar=None, aeccar0=None, aeccar2=None) :param aeccar2: (optional) Chgcar object from aeccar2 file :return: summary dict """ - with ScratchDir(".") as temp_dir: if aeccar0 and aeccar2: # construct reference file diff --git a/pymatgen/command_line/chargemol_caller.py b/pymatgen/command_line/chargemol_caller.py index 11e78c7c6fd..f6231b6d100 100644 --- a/pymatgen/command_line/chargemol_caller.py +++ b/pymatgen/command_line/chargemol_caller.py @@ -183,18 +183,14 @@ def _execute_chargemol(self, **jobcontrol_kwargs): jobcontrol_kwargs: Keyword arguments for _write_jobscript_for_chargemol. """ with ScratchDir("."): - with zopen(self._chgcarpath, "rt") as f_in: - with open("CHGCAR", "w") as f_out: - shutil.copyfileobj(f_in, f_out) - with zopen(self._potcarpath, "rt") as f_in: - with open("POTCAR", "w") as f_out: - shutil.copyfileobj(f_in, f_out) - with zopen(self._aeccar0path, "rt") as f_in: - with open("AECCAR0", "w") as f_out: - shutil.copyfileobj(f_in, f_out) - with zopen(self._aeccar2path, "rt") as f_in: - with open("AECCAR2", "w") as f_out: - shutil.copyfileobj(f_in, f_out) + with zopen(self._chgcarpath, "rt") as f_in, open("CHGCAR", "w") as f_out: + shutil.copyfileobj(f_in, f_out) + with zopen(self._potcarpath, "rt") as f_in, open("POTCAR", "w") as f_out: + shutil.copyfileobj(f_in, f_out) + with zopen(self._aeccar0path, "rt") as f_in, open("AECCAR0", "w") as f_out: + shutil.copyfileobj(f_in, f_out) + with zopen(self._aeccar2path, "rt") as f_in, open("AECCAR2", "w") as f_out: + shutil.copyfileobj(f_in, f_out) # write job_script file: self._write_jobscript_for_chargemol(**jobcontrol_kwargs) @@ -354,10 +350,7 @@ def get_bond_order(self, index_from, index_to): """ bonded_set = self.bond_order_dict[index_from]["bonded_to"] bond_orders = [v["bond_order"] for v in bonded_set if v["index"] == index_to] - if bond_orders == []: - sum_bo = 0.0 - else: - sum_bo = np.sum(bond_orders) + sum_bo = 0.0 if bond_orders == [] else np.sum(bond_orders) return sum_bo def _write_jobscript_for_chargemol( @@ -501,7 +494,7 @@ def get_property_decorated_structure(self): Takes CHGCAR's structure object and updates it with properties from the Chargemol analysis. - Returns + Returns: Pymatgen structure with site properties added """ struct = self.structure.copy() @@ -554,10 +547,7 @@ def summary(self): if self.bond_order_dict: ddec_summary["bond_order_dict"] = self.bond_order_dict - if self.cm5_charges: - cm5_summary = {"partial_charges": self.cm5_charges} - else: - cm5_summary = None + cm5_summary = {"partial_charges": self.cm5_charges} if self.cm5_charges else None summary["ddec"] = ddec_summary summary["cm5"] = cm5_summary diff --git a/pymatgen/command_line/critic2_caller.py b/pymatgen/command_line/critic2_caller.py index e7de50358b1..855b3d8d219 100644 --- a/pymatgen/command_line/critic2_caller.py +++ b/pymatgen/command_line/critic2_caller.py @@ -102,16 +102,10 @@ def __init__(self, input_script): self._stdout = stdout self._stderr = stderr - if os.path.exists("cpreport.json"): - cpreport = loadfn("cpreport.json") - else: - cpreport = None + cpreport = loadfn("cpreport.json") if os.path.exists("cpreport.json") else None self._cpreport = cpreport - if os.path.exists("yt.json"): - yt = loadfn("yt.json") - else: - yt = None + yt = loadfn("yt.json") if os.path.exists("yt.json") else None self._yt = yt @classmethod @@ -487,6 +481,7 @@ def structure_graph(self, include_critical_points=("bond", "ring", "cage")): """ A StructureGraph object describing bonding information in the crystal. + Args: include_critical_points: add DummySpecies for the critical points themselves, a list of @@ -534,21 +529,20 @@ def structure_graph(self, include_critical_points=("bond", "ring", "cage")): for idx, edge in edges.items(): unique_idx = self.nodes[idx]["unique_idx"] # only check edges representing bonds, not rings - if self.critical_points[unique_idx].type == CriticalPointType.bond: - if idx not in idx_to_delete: - for idx2, edge2 in edges.items(): - if idx != idx2 and edge == edge2: - idx_to_delete.append(idx2) - warnings.warn( - "Duplicate edge detected, try re-running " - "critic2 with custom parameters to fix this. " - "Mostly harmless unless user is also " - "interested in rings/cages." - ) - logger.debug( - f"Duplicate edge between points {idx} (unique point {self.nodes[idx]['unique_idx']})" - f"and {idx2} ({self.nodes[idx2]['unique_idx']})." - ) + if self.critical_points[unique_idx].type == CriticalPointType.bond and idx not in idx_to_delete: + for idx2, edge2 in edges.items(): + if idx != idx2 and edge == edge2: + idx_to_delete.append(idx2) + warnings.warn( + "Duplicate edge detected, try re-running " + "critic2 with custom parameters to fix this. " + "Mostly harmless unless user is also " + "interested in rings/cages." + ) + logger.debug( + f"Duplicate edge between points {idx} (unique point {self.nodes[idx]['unique_idx']})" + f"and {idx2} ({self.nodes[idx2]['unique_idx']})." + ) # and remove any duplicate bonds present for idx in idx_to_delete: del edges[idx] diff --git a/pymatgen/command_line/enumlib_caller.py b/pymatgen/command_line/enumlib_caller.py index 182680b1571..f46113da32a 100644 --- a/pymatgen/command_line/enumlib_caller.py +++ b/pymatgen/command_line/enumlib_caller.py @@ -327,7 +327,7 @@ def _get_structures(self, num_structs): options = ["struct_enum.out", str(0), str(num_structs - 1)] with subprocess.Popen( - [makestr_cmd] + options, + [makestr_cmd, *options], stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True, diff --git a/pymatgen/command_line/gulp_caller.py b/pymatgen/command_line/gulp_caller.py index 68c578f61f3..5517d277bf4 100644 --- a/pymatgen/command_line/gulp_caller.py +++ b/pymatgen/command_line/gulp_caller.py @@ -629,7 +629,7 @@ def get_relaxed_structure(gout): fields = line.split() if fields[2] == "c": sp.append(fields[1]) - coords.append(list(float(x) for x in fields[3:6])) + coords.append([float(x) for x in fields[3:6]]) else: raise OSError("No structure found") diff --git a/pymatgen/command_line/mcsqs_caller.py b/pymatgen/command_line/mcsqs_caller.py index 4583039b4a6..0aee567ec5f 100644 --- a/pymatgen/command_line/mcsqs_caller.py +++ b/pymatgen/command_line/mcsqs_caller.py @@ -73,7 +73,6 @@ def run_mcsqs( Tuple of Pymatgen structure SQS of the input structure, the mcsqs objective function, list of all SQS structures, and the directory where calculations are run """ - num_atoms = len(structure) if structure.is_ordered: @@ -181,7 +180,6 @@ def _parse_sqs_path(path) -> Sqs: Tuple of Pymatgen structure SQS of the input structure, the mcsqs objective function, list of all SQS structures, and the directory where calculations are run """ - path = Path(path) # detected instances will be 0 if mcsqs was run in series, or number of instances @@ -201,10 +199,7 @@ def _parse_sqs_path(path) -> Sqs: objective_function_str = lines[-1].split("=")[-1].strip() objective_function: float | str - if objective_function_str != "Perfect_match": - objective_function = float(objective_function_str) - else: - objective_function = "Perfect_match" + objective_function = float(objective_function_str) if objective_function_str != "Perfect_match" else "Perfect_match" # Get all SQS structures and objective functions allsqs = [] @@ -223,10 +218,7 @@ def _parse_sqs_path(path) -> Sqs: objective_function_str = lines[-1].split("=")[-1].strip() obj: float | str - if objective_function_str != "Perfect_match": - obj = float(objective_function_str) - else: - obj = "Perfect_match" + obj = float(objective_function_str) if objective_function_str != "Perfect_match" else "Perfect_match" allsqs.append({"structure": sqs, "objective_function": obj}) clusters = _parse_clusters(path / "clusters.out") @@ -249,7 +241,6 @@ def _parse_clusters(filename): Returns: List of dicts """ - with open(filename) as f: lines = f.readlines() diff --git a/pymatgen/command_line/tests/test_gulp_caller.py b/pymatgen/command_line/tests/test_gulp_caller.py index fbcd5142736..66dceaabe14 100644 --- a/pymatgen/command_line/tests/test_gulp_caller.py +++ b/pymatgen/command_line/tests/test_gulp_caller.py @@ -256,9 +256,9 @@ def test_get_relaxed_structure(self): out_str = fp.read() struct = self.gio.get_relaxed_structure(out_str) assert isinstance(struct, Structure) - assert 8 == len(struct.sites) - assert 4.212 == struct.lattice.a - assert 90 == struct.lattice.alpha + assert len(struct.sites) == 8 + assert struct.lattice.a == 4.212 + assert struct.lattice.alpha == 90 @unittest.skip("Test later") def test_tersoff_inpt(self): @@ -334,12 +334,12 @@ def test_element_different_valence(self): assert "Sc_4+" not in self.bpl.species_dict def test_values(self): - assert "" != self.bpl.species_dict["Sc_2+"] - assert "" != self.bpl.pot_dict["Sc_2+"] + assert self.bpl.species_dict["Sc_2+"] != "" + assert self.bpl.pot_dict["Sc_2+"] != "" def test_spring(self): assert "Li" not in self.bpl.spring_dict - assert "" != self.bpl.spring_dict["O"] + assert self.bpl.spring_dict["O"] != "" @unittest.skipIf(not gulp_present, "gulp not present.") @@ -360,11 +360,11 @@ def test_non_exisitng_element(self): assert "Mn" not in self.bpb.species_dict def test_element_different_valence(self): - assert 2 != self.bpb.species_dict["Li"]["oxi"] + assert self.bpb.species_dict["Li"]["oxi"] != 2 def test_spring(self): - assert "" == self.bpb.spring_dict["Li"] - assert "" != self.bpb.spring_dict["O"] + assert self.bpb.spring_dict["Li"] == "" + assert self.bpb.spring_dict["O"] != "" if __name__ == "__main__": diff --git a/pymatgen/command_line/tests/test_vampire_caller.py b/pymatgen/command_line/tests/test_vampire_caller.py index b38ea5eb031..e120429c2ff 100644 --- a/pymatgen/command_line/tests/test_vampire_caller.py +++ b/pymatgen/command_line/tests/test_vampire_caller.py @@ -59,7 +59,7 @@ def test_vampire(self): voutput = vc.output critical_temp = voutput.critical_temp - assert 400 == approx(critical_temp) + assert approx(critical_temp) == 400 if os.path.exists("Mn3Al.mat"): os.remove("Mn3Al.mat") diff --git a/pymatgen/command_line/vampire_caller.py b/pymatgen/command_line/vampire_caller.py index f485f49969d..a7a2090410b 100644 --- a/pymatgen/command_line/vampire_caller.py +++ b/pymatgen/command_line/vampire_caller.py @@ -91,7 +91,7 @@ def __init__( mat_id_dict (dict): Maps sites to material id # for vampire indexing. - TODO: + Todo: * Create input files in a temp folder that gets cleaned up after run terminates """ self.mc_box_size = mc_box_size @@ -280,20 +280,11 @@ def _create_input(self): ] # Set temperature range and step size of simulation - if "start_t" in self.user_input_settings: - start_t = self.user_input_settings["start_t"] - else: - start_t = 0 + start_t = self.user_input_settings["start_t"] if "start_t" in self.user_input_settings else 0 - if "end_t" in self.user_input_settings: - end_t = self.user_input_settings["end_t"] - else: - end_t = 1500 + end_t = self.user_input_settings["end_t"] if "end_t" in self.user_input_settings else 1500 - if "temp_increment" in self.user_input_settings: - temp_increment = self.user_input_settings["temp_increment"] - else: - temp_increment = 25 + temp_increment = self.user_input_settings.get("temp_increment", 25) input_script += [ f"sim:minimum-temperature = {start_t}", @@ -399,7 +390,7 @@ def parse_stdout(vamp_stdout, nmats): # Parsing vampire MC output df = pd.read_csv(vamp_stdout, sep="\t", skiprows=9, header=None, names=names) - df.drop("nan", axis=1, inplace=True) + df = df.drop("nan", axis=1) parsed_out = df.to_json() diff --git a/pymatgen/core/bonds.py b/pymatgen/core/bonds.py index c753e0e058c..669e79fb84c 100644 --- a/pymatgen/core/bonds.py +++ b/pymatgen/core/bonds.py @@ -67,6 +67,7 @@ def get_bond_order(self, tol: float = 0.2, default_bl: float | None = None) -> f default_bl: If a particular type of bond does not exist, use this bond length as a default value (bond order = 1). If None, a ValueError will be thrown. + Returns: Float value of bond order. For example, for C-C bond in benzene, return 1.7. @@ -80,6 +81,7 @@ def get_bond_order(self, tol: float = 0.2, default_bl: float | None = None) -> f def is_bonded(site1, site2, tol: float = 0.2, bond_order: float | None = None, default_bl: float | None = None): """ Test if two sites are bonded, up to a certain limit. + Args: site1 (Site): First site site2 (Site): Second site @@ -91,6 +93,7 @@ def is_bonded(site1, site2, tol: float = 0.2, bond_order: float | None = None, d against all possible bond data. Defaults to None. default_bl: If a particular type of bond does not exist, use this bond length. If None, a ValueError will be thrown. + Returns: Boolean indicating whether two sites are bonded. """ @@ -102,10 +105,7 @@ def is_bonded(site1, site2, tol: float = 0.2, bond_order: float | None = None, d all_lengths = bond_lengths[syms] if bond_order: return dist < (1 + tol) * all_lengths[bond_order] - for v in all_lengths.values(): - if dist < (1 + tol) * v: - return True - return False + return any(dist < (1 + tol) * v for v in all_lengths.values()) if default_bl: return dist < (1 + tol) * default_bl raise ValueError(f"No bond data for elements {syms[0]} - {syms[1]}") diff --git a/pymatgen/core/composition.py b/pymatgen/core/composition.py index 8324e1483fc..5ed58eb8038 100644 --- a/pymatgen/core/composition.py +++ b/pymatgen/core/composition.py @@ -415,7 +415,7 @@ def get_integer_formula_and_factor( g = gcd_float(list(el_amt.values()), 1 / max_denominator) d = {k: round(v / g) for k, v in el_amt.items()} - (formula, factor) = reduce_formula(d, iupac_ordering=iupac_ordering) + formula, factor = reduce_formula(d, iupac_ordering=iupac_ordering) if formula in Composition.special_formulas: formula = Composition.special_formulas[formula] factor /= 2 @@ -666,7 +666,7 @@ def get_el_amt_dict(self) -> dict[str, float]: """ Returns: dict[str, float]: element symbol and (unreduced) amount. E.g. - {"Fe": 4.0, "O":6.0} or {"Fe3+": 4.0, "O2-":6.0} + {"Fe": 4.0, "O":6.0} or {"Fe3+": 4.0, "O2-":6.0} """ dic: dict[str, float] = collections.defaultdict(float) for el, amt in self.items(): @@ -675,30 +675,30 @@ def get_el_amt_dict(self) -> dict[str, float]: def as_dict(self) -> dict[str, float]: """ + Note: Subtly different from get_el_amt_dict in that they keys here are str(Element) instead of Element.symbol. + Returns: - dict with species symbol and (unreduced) amount e.g., - {"Fe": 4.0, "O":6.0} or {"Fe3+": 4.0, "O2-":6.0} + dict[str, float]: element symbol and (unreduced) amount. E.g. + {"Fe": 4.0, "O":6.0} or {"Fe3+": 4.0, "O2-":6.0} """ - d: dict[str, float] = collections.defaultdict(float) - for e, a in self.items(): - d[str(e)] += a - return d + dic: dict[str, float] = collections.defaultdict(float) + for el, amt in self.items(): + dic[str(el)] += amt + return dic @property - def to_reduced_dict(self) -> dict: + def to_reduced_dict(self) -> dict[str, float]: """ Returns: - Dict with element symbol and reduced amount e.g., - {"Fe": 2.0, "O":3.0} + dict[str, float]: element symbols mapped to reduced amount e.g. {"Fe": 2.0, "O":3.0}. """ - return self.get_reduced_composition_and_factor()[0].as_dict() + return self.reduced_composition.as_dict() @property - def to_weight_dict(self) -> dict: + def to_weight_dict(self) -> dict[str, float]: """ Returns: - Dict with weight fraction of each component - {"Ti": 0.90, "V": 0.06, "Al": 0.04} + dict[str, float] with weight fraction of each component {"Ti": 0.90, "V": 0.06, "Al": 0.04} """ return {str(el): self.get_wt_fraction(el) for el in self.elements} @@ -711,11 +711,11 @@ def to_data_dict(self) -> dict: reduced_cell_formula, elements and nelements. """ return { - "reduced_cell_composition": self.get_reduced_composition_and_factor()[0], + "reduced_cell_composition": self.reduced_composition, "unit_cell_composition": self.as_dict(), "reduced_cell_formula": self.reduced_formula, - "elements": list(self.as_dict()), - "nelements": len(self.as_dict()), + "elements": list(map(str, self)), + "nelements": len(self), } def oxi_state_guesses( diff --git a/pymatgen/core/interface.py b/pymatgen/core/interface.py index e12e1005e92..6764dc10610 100644 --- a/pymatgen/core/interface.py +++ b/pymatgen/core/interface.py @@ -292,7 +292,7 @@ def __update_c(self, new_c: float) -> None: if new_c <= 0: raise ValueError("New c-length must be greater than 0") - new_latt_matrix = self.lattice.matrix[:2].tolist() + [[0, 0, new_c]] + new_latt_matrix = [*self.lattice.matrix[:2].tolist(), [0, 0, new_c]] new_latice = Lattice(new_latt_matrix) self._lattice = new_latice @@ -321,12 +321,12 @@ def from_dict(cls, d): sites = [PeriodicSite.from_dict(sd, lattice) for sd in d["sites"]] s = Structure.from_sites(sites) - optional = dict( - in_plane_offset=d.get("in_plane_offset"), - gap=d.get("gap"), - vacuum_over_film=d.get("vacuum_over_film"), - interface_properties=d.get("interface_properties"), - ) + optional = { + "in_plane_offset": d.get("in_plane_offset"), + "gap": d.get("gap"), + "vacuum_over_film": d.get("vacuum_over_film"), + "interface_properties": d.get("interface_properties"), + } return Interface( lattice=lattice, species=s.species_and_occu, diff --git a/pymatgen/core/lattice.py b/pymatgen/core/lattice.py index c7acab91210..c1f795f33b7 100644 --- a/pymatgen/core/lattice.py +++ b/pymatgen/core/lattice.py @@ -1061,10 +1061,7 @@ def get_angles(v1, v2, l1, l2): aligned_m = np.array((c_a[i], c_b[j], c_c[k])) - if skip_rotation_matrix: - rotation_m = None - else: - rotation_m = np.linalg.solve(aligned_m, other_lattice.matrix) + rotation_m = None if skip_rotation_matrix else np.linalg.solve(aligned_m, other_lattice.matrix) yield Lattice(aligned_m), rotation_m, scale_m @@ -1244,11 +1241,11 @@ def get_niggli_reduced_lattice(self, tol: float = 1e-5) -> Lattice: 2 * G[0, 1], ) - if A > B + e or (abs(A - B) < e and abs(E) > abs(N) + e): + if B + e < A or (abs(A - B) < e and abs(E) > abs(N) + e): # A1 M = [[0, -1, 0], [-1, 0, 0], [0, 0, -1]] G = dot(transpose(M), dot(G, M)) - if (B > C + e) or (abs(B - C) < e and abs(N) > abs(Y) + e): + if (C + e < B) or (abs(B - C) < e and abs(N) > abs(Y) + e): # A2 M = [[-1, 0, 0], [0, 0, -1], [0, -1, 0]] G = dot(transpose(M), dot(G, M)) @@ -1290,19 +1287,19 @@ def get_niggli_reduced_lattice(self, tol: float = 1e-5) -> Lattice: ) # A5 - if abs(E) > B + e or (abs(E - B) < e and 2 * N < Y - e) or (abs(E + B) < e and Y < -e): + if abs(E) > B + e or (abs(E - B) < e and 2 * N < Y - e) or (abs(E + B) < e and -e > Y): M = [[1, 0, 0], [0, 1, -E / abs(E)], [0, 0, 1]] G = dot(transpose(M), dot(G, M)) continue # A6 - if abs(N) > A + e or (abs(A - N) < e and 2 * E < Y - e) or (abs(A + N) < e and Y < -e): + if abs(N) > A + e or (abs(A - N) < e and 2 * E < Y - e) or (abs(A + N) < e and -e > Y): M = [[1, 0, -N / abs(N)], [0, 1, 0], [0, 0, 1]] G = dot(transpose(M), dot(G, M)) continue # A7 - if abs(Y) > A + e or (abs(A - Y) < e and 2 * E < N - e) or (abs(A + Y) < e and N < -e): + if abs(Y) > A + e or (abs(A - Y) < e and 2 * E < N - e) or (abs(A + Y) < e and -e > N): M = [[1, -Y / abs(Y), 0], [0, 1, 0], [0, 0, 1]] G = dot(transpose(M), dot(G, M)) continue @@ -2071,10 +2068,7 @@ def find_neighbors(label: np.ndarray, nx: int, ny: int, nz: int) -> list[np.ndar """ array = [[-1, 0, 1]] * 3 neighbor_vectors = np.array(list(itertools.product(*array)), dtype=int) - if np.shape(label)[1] == 1: - label3d = _one_to_three(label, ny, nz) - else: - label3d = label + label3d = _one_to_three(label, ny, nz) if np.shape(label)[1] == 1 else label all_labels = label3d[:, None, :] - neighbor_vectors[None, :, :] filtered_labels = [] # filter out out-of-bound labels i.e., label < 0 diff --git a/pymatgen/core/molecular_orbitals.py b/pymatgen/core/molecular_orbitals.py index f9cd7604cd9..c6b2d568757 100644 --- a/pymatgen/core/molecular_orbitals.py +++ b/pymatgen/core/molecular_orbitals.py @@ -93,6 +93,7 @@ def aos_as_list(self): def obtain_band_edges(self): """ Fill up the atomic orbitals with available electrons. + Returns: HOMO, LUMO, and whether it's a metal. """ diff --git a/pymatgen/core/operations.py b/pymatgen/core/operations.py index 62a0d7e294e..d27af09ac03 100644 --- a/pymatgen/core/operations.py +++ b/pymatgen/core/operations.py @@ -549,7 +549,7 @@ def __repr__(self): def __hash__(self): # useful for obtaining a set of unique MagSymmOps - hashable_value = tuple(self.affine_matrix.flatten()) + (self.time_reversal,) + hashable_value = (*tuple(self.affine_matrix.flatten()), self.time_reversal) return hash(hashable_value) def operate_magmom(self, magmom): diff --git a/pymatgen/core/periodic_table.py b/pymatgen/core/periodic_table.py index 61d063f5395..81aa20031df 100644 --- a/pymatgen/core/periodic_table.py +++ b/pymatgen/core/periodic_table.py @@ -419,7 +419,8 @@ def common_oxidation_states(self) -> tuple[int, ...]: @property def icsd_oxidation_states(self) -> tuple[int, ...]: """Tuple of all oxidation states with at least 10 instances in - ICSD database AND at least 1% of entries for that element""" + ICSD database AND at least 1% of entries for that element + """ return tuple(self._data.get("ICSD oxidation states", [])) @property @@ -606,6 +607,7 @@ def from_name(name: str) -> Element: Args: name: Long name of the element, e.g. 'Hydrogen' or 'Iron'. Not case-sensitive. + Returns: Element with the name 'name' """ diff --git a/pymatgen/core/structure.py b/pymatgen/core/structure.py index 6e6c0616641..78d85050d3d 100644 --- a/pymatgen/core/structure.py +++ b/pymatgen/core/structure.py @@ -865,10 +865,7 @@ def from_spacegroup( except ValueError: spg = SpaceGroup(sg) - if isinstance(lattice, Lattice): - latt = lattice - else: - latt = Lattice(lattice) + latt = lattice if isinstance(lattice, Lattice) else Lattice(lattice) if not spg.is_compatible(latt): raise ValueError( @@ -963,10 +960,7 @@ def from_magnetic_spacegroup( if not isinstance(msg, MagneticSpaceGroup): msg = MagneticSpaceGroup(msg) - if isinstance(lattice, Lattice): - latt = lattice - else: - latt = Lattice(lattice) + latt = lattice if isinstance(lattice, Lattice) else Lattice(lattice) if not msg.is_compatible(latt): raise ValueError( @@ -1114,10 +1108,7 @@ def __eq__(self, other: object) -> bool: return False if self.lattice != other.lattice: return False - for site in self: - if site not in other: - return False - return True + return all(site in other for site in self) def __hash__(self) -> int: # For now, just use the composition hash code. @@ -1353,13 +1344,11 @@ def _get_neighbor_list_py( points_indices.append(n.index) offsets.append(n.image) distances.append(n.nn_distance) - return tuple( - ( - np.array(center_indices), - np.array(points_indices), - np.array(offsets), - np.array(distances), - ) + return ( + np.array(center_indices), + np.array(points_indices), + np.array(offsets), + np.array(distances), ) def get_neighbor_list( @@ -1418,13 +1407,11 @@ def get_neighbor_list( if exclude_self: self_pair = (center_indices == points_indices) & (distances <= numerical_tol) cond = ~self_pair - return tuple( - ( - center_indices[cond], - points_indices[cond], - images[cond], - distances[cond], - ) + return ( + center_indices[cond], + points_indices[cond], + images[cond], + distances[cond], ) def get_symmetric_neighbor_list( @@ -1505,7 +1492,7 @@ def get_symmetric_neighbor_list( for it2, (i2, j2, R2, d2) in enumerate(zip(*bonds)): bool1 = i == j2 bool2 = j == i2 - bool3 = (R == -R2).all() + bool3 = (-R2 == R).all() bool4 = np.isclose(d, d2, atol=numerical_tol) if bool1 and bool2 and bool3 and bool4: redundant.append(it2) @@ -1656,7 +1643,7 @@ def get_all_neighbors( # returns True immediately once one of the conditions are satisfied. psite.species != csite.species or (not np.allclose(psite.coords, csite.coords, atol=atol)) - or (not psite.properties == csite.properties) + or (psite.properties != csite.properties) ): neighbor_dict[cindex].append( PeriodicNeighbor( @@ -2335,7 +2322,7 @@ def to_s(x): outs.append( tabulate( data, - headers=["#", "SP", "a", "b", "c"] + keys, + headers=["#", "SP", "a", "b", "c", *keys], ) ) return "\n".join(outs) @@ -2432,23 +2419,24 @@ def as_dict(self, verbosity=1, fmt=None, **kwargs): def as_dataframe(self): """ - Returns a Pandas dataframe of the sites. Structure level attributes are stored in DataFrame.attrs. Example: + Create a Pandas dataframe of the sites. Structure-level attributes are stored in DataFrame.attrs. - Species a b c x y z magmom - 0 (Si) 0.0 0.0 0.000000e+00 0.0 0.000000e+00 0.000000e+00 5 - 1 (Si) 0.0 0.0 1.000000e-07 0.0 -2.217138e-07 3.135509e-07 -5 + Example: + Species a b c x y z magmom + 0 (Si) 0.0 0.0 0.000000e+00 0.0 0.000000e+00 0.000000e+00 5 + 1 (Si) 0.0 0.0 1.000000e-07 0.0 -2.217138e-07 3.135509e-07 -5 """ data = [] site_properties = self.site_properties prop_keys = list(site_properties) for site in self: - row = [site.species] + list(site.frac_coords) + list(site.coords) + row = [site.species, *site.frac_coords, *site.coords] for k in prop_keys: row.append(site.properties.get(k)) data.append(row) import pandas as pd - df = pd.DataFrame(data, columns=["Species", "a", "b", "c", "x", "y", "z"] + prop_keys) + df = pd.DataFrame(data, columns=["Species", "a", "b", "c", "x", "y", "z", *prop_keys]) df.attrs["Reduced Formula"] = self.composition.reduced_formula df.attrs["Lattice"] = self.lattice return df @@ -2756,9 +2744,9 @@ def __init__( validate_proximity: bool = False, site_properties: dict | None = None, charge_spin_check: bool = True, - ): + ) -> None: """ - Creates a Molecule. + Create a Molecule. Args: species: list of atomic species. Possible kinds of input include a @@ -2783,11 +2771,7 @@ def __init__( """ if len(species) != len(coords): raise StructureError( - ( - "The list of atomic species must be of the", - " same length as the list of fractional ", - "coordinates.", - ) + "The list of atomic species must be of the same length as the list of fractional coordinates." ) self._charge_spin_check = charge_spin_check @@ -2814,8 +2798,8 @@ def __init__( if spin_multiplicity: if charge_spin_check and (nelectrons + spin_multiplicity) % 2 != 1: raise ValueError( - f"Charge of {self._charge} and spin multiplicity of {spin_multiplicity} is not possible for " - "this molecule!" + f"Charge of {self._charge} and spin multiplicity of {spin_multiplicity} " + "is not possible for this molecule!" ) self._spin_multiplicity = spin_multiplicity else: @@ -2921,10 +2905,7 @@ def break_bond(self, ind1: int, ind2: int, tol: float = 0.2) -> tuple[IMolecule sites = [site for i, site in enumerate(self._sites) if i not in (ind1, ind2)] def belongs_to_cluster(site, cluster): - for test_site in cluster: - if CovalentBond.is_bonded(site, test_site, tol=tol): - return True - return False + return any(CovalentBond.is_bonded(site, test_site, tol=tol) for test_site in cluster) while len(sites) > 0: unmatched = [] @@ -2973,10 +2954,7 @@ def __eq__(self, other: object) -> bool: return False if self.spin_multiplicity != other.spin_multiplicity: return False - for site in self: - if site not in other: - return False - return True + return all(site in other for site in self) def __hash__(self): # For now, just use the composition hash code. diff --git a/pymatgen/core/surface.py b/pymatgen/core/surface.py index 1c8f83c5502..5442115dc2e 100644 --- a/pymatgen/core/surface.py +++ b/pymatgen/core/surface.py @@ -277,7 +277,7 @@ def equi_index(site): break else: # Move unselected atom to the opposite surface. - fcoords.append(s.frac_coords + [0, 0, shift]) + fcoords.append(s.frac_coords + [0, 0, shift]) # noqa: RUF005 # sort by species to put all similar species together. sp_fcoord = sorted(zip(species, fcoords), key=lambda x: x[0]) @@ -543,16 +543,16 @@ def get_surface_sites(self, tag=False): site as well. This will work for elemental systems only for now. Useful for analysis involving broken bonds and for finding adsorption sites. - Args: + Args: tag (bool): Option to adds site attribute "is_surfsite" (bool) to all sites of slab. Defaults to False - Returns: + Returns: A dictionary grouping sites on top and bottom of the slab together. {"top": [sites with indices], "bottom": [sites with indices} - TODO: + Todo: Is there a way to determine site equivalence between sites in a slab and bulk system? This would allow us get the coordination number of a specific site for multi-elemental systems or systems with more @@ -871,12 +871,12 @@ def __init__( slab_scale_factor.append(eye[c_index]) else: index_range = sorted( - reversed(range(-max_normal_search, max_normal_search + 1)), - key=lambda x: abs(x), + range(-max_normal_search, max_normal_search + 1), + key=lambda x: -abs(x), ) candidates = [] for uvw in itertools.product(index_range, index_range, index_range): - if (not any(uvw)) or abs(np.linalg.det(slab_scale_factor + [uvw])) < 1e-8: + if (not any(uvw)) or abs(np.linalg.det([*slab_scale_factor, uvw])) < 1e-8: continue vec = latt.get_cartesian_coords(uvw) l = np.linalg.norm(vec) @@ -1360,7 +1360,7 @@ class ReconstructionGenerator: The index of the termination of the slab - TODO: + Todo: - Right now there is no way to specify what atom is being added. In the future, use basis sets? """ @@ -1566,7 +1566,7 @@ def get_d(slab): """ sorted_sites = sorted(slab, key=lambda site: site.frac_coords[2]) for i, site in enumerate(sorted_sites): - if not f"{site.frac_coords[2]:.6f}" == f"{sorted_sites[i + 1].frac_coords[2]:.6f}": + if f"{site.frac_coords[2]:.6f}" != f"{sorted_sites[i + 1].frac_coords[2]:.6f}": d = abs(site.frac_coords[2] - sorted_sites[i + 1].frac_coords[2]) break return slab.lattice.get_cartesian_coords([0, 0, d])[2] @@ -1585,10 +1585,7 @@ def is_already_analyzed(miller_index: tuple, miller_list: list, symm_ops: list) symm_ops (list): Symmetry operations of a lattice, used to define family of indices """ - for op in symm_ops: - if in_coord_list(miller_list, op.operate(miller_index)): - return True - return False + return any(in_coord_list(miller_list, op.operate(miller_index)) for op in symm_ops) def get_symmetrically_equivalent_miller_indices(structure, miller_index, return_hkil=True): @@ -1602,7 +1599,6 @@ def get_symmetrically_equivalent_miller_indices(structure, miller_index, return_ return_hkil (bool): If true, return hkil form of Miller index for hexagonal systems, otherwise return hkl """ - # Change to hkl if hkil because in_coord_list only handles tuples of 3 miller_index = (miller_index[0], miller_index[1], miller_index[3]) if len(miller_index) == 4 else miller_index mmi = max(np.abs(miller_index)) @@ -1640,6 +1636,7 @@ def get_symmetrically_distinct_miller_indices(structure, max_index, return_hkil= Returns all symmetrically distinct indices below a certain max-index for a given structure. Analysis is based on the symmetry of the reciprocal lattice of the structure. + Args: structure (Structure): input structure. max_index (int): The maximum index. For example, a max_index of 1 @@ -1648,7 +1645,6 @@ def get_symmetrically_distinct_miller_indices(structure, max_index, return_hkil= return_hkil (bool): If true, return hkil form of Miller index for hexagonal systems, otherwise return hkl """ - r = list(range(-max_index, max_index + 1)) r.reverse() @@ -1848,7 +1844,6 @@ def get_slab_regions(slab, blength=3.5): want this value to be larger than the actual bondlengths in order to find atoms that are part of the slab """ - fcoords, indices, all_indices = [], [], [] for site in slab: # find sites with c < 0 (noncontiguous) @@ -1957,7 +1952,6 @@ def center_slab(slab): Returns: Returns a centered slab structure """ - # get a reasonable r cutoff to sample neighbors bdists = sorted(nn[1] for nn in slab.get_neighbors(slab[0], 10) if nn[1] > 0) r = bdists[0] * 3 diff --git a/pymatgen/core/tensors.py b/pymatgen/core/tensors.py index 0f430472115..e7e289660f9 100644 --- a/pymatgen/core/tensors.py +++ b/pymatgen/core/tensors.py @@ -154,7 +154,7 @@ def einsum_sequence(self, other_arrays, einsum_string=None): einsum_string += "," + lc[idx : idx + length] idx += length - einsum_args = [self] + list(other_arrays) + einsum_args = [self, *other_arrays] return np.einsum(einsum_string, *einsum_args) def project(self, n): @@ -211,10 +211,7 @@ def get_grouped_indices(self, voigt=False, **kwargs): list of index groups where tensor values are equivalent to within tolerances """ - if voigt: - array = self.voigt - else: - array = self + array = self.voigt if voigt else self indices = list(itertools.product(*(range(n) for n in array.shape))) remaining = indices.copy() @@ -254,15 +251,9 @@ def get_symbol_dict(self, voigt=True, zero_index=False, **kwargs): within tolerances """ d = {} - if voigt: - array = self.voigt - else: - array = self + array = self.voigt if voigt else self grouped = self.get_grouped_indices(voigt=voigt, **kwargs) - if zero_index: - p = 0 - else: - p = 1 + p = 0 if zero_index else 1 for indices in grouped: sym_string = self.symbol + "_" sym_string += "".join(str(i + p) for i in indices[0]) @@ -582,10 +573,7 @@ def from_values_indices( base = np.zeros(shape.astype(int)) for v, idx in zip(values, indices): base[tuple(idx)] = v - if 6 in shape: - obj = cls.from_voigt(base) - else: - obj = cls(base) + obj = cls.from_voigt(base) if 6 in shape else cls(base) if populate: assert structure, "Populate option must include structure input" obj = obj.populate(structure, vsym=vsym, verbose=verbose) @@ -1092,7 +1080,7 @@ def items(self): return zip(self._tensor_list, self._value_list) def __contains__(self, item): - return not self._get_item_index(item) is None + return self._get_item_index(item) is not None def _get_item_index(self, item): if len(self._tensor_list) == 0: diff --git a/pymatgen/core/tests/test_composition.py b/pymatgen/core/tests/test_composition.py index efe1a95f45b..0a283075b01 100644 --- a/pymatgen/core/tests/test_composition.py +++ b/pymatgen/core/tests/test_composition.py @@ -74,13 +74,13 @@ def test_init_(self): with pytest.raises(ValueError): Composition({"H": -0.1}) f = {"Fe": 4, "Li": 4, "O": 16, "P": 4} - assert "Li4 Fe4 P4 O16" == Composition(f).formula + assert Composition(f).formula == "Li4 Fe4 P4 O16" f = {None: 4, "Li": 4, "O": 16, "P": 4} with pytest.raises(TypeError): Composition(f) f = {1: 2, 8: 1} - assert "H2 O1" == Composition(f).formula - assert "Na2 O1" == Composition(Na=2, O=1).formula + assert Composition(f).formula == "H2 O1" + assert Composition(Na=2, O=1).formula == "Na2 O1" c = Composition({"S": Composition.amount_tolerance / 2}) assert len(c.elements) == 0 @@ -197,7 +197,7 @@ def test_reduced_composition(self): "ZnHO", ] for idx, comp in enumerate(self.comp): - assert comp.get_reduced_composition_and_factor()[0] == Composition(correct_reduced_formulas[idx]) + assert comp.reduced_composition == Composition(correct_reduced_formulas[idx]) def test_reduced_formula(self): correct_reduced_formulas = [ @@ -220,7 +220,7 @@ def test_reduced_formula(self): # test rounding c = Composition({"Na": 2 - Composition.amount_tolerance / 2, "Cl": 2}) - assert "NaCl" == c.reduced_formula + assert c.reduced_formula == "NaCl" def test_integer_formula(self): correct_reduced_formulas = [ @@ -391,7 +391,7 @@ def test_equals(self): c1, c2 = self.comp[:2] assert c1 == c1 - assert not c1 == c2 + assert c1 != c2 def test_hash_robustness(self): c1 = Composition(f"O{0.2}Fe{0.8}Na{Composition.amount_tolerance*0.99}") @@ -400,7 +400,7 @@ def test_hash_robustness(self): assert c1 == c3, "__eq__ not robust" assert (c1 == c3) == (hash(c1) == hash(c3)), "Hash doesn't match eq when true" - assert not hash(c1) == hash(c2), "Hash equal for different chemical systems" + assert hash(c1) != hash(c2), "Hash equal for different chemical systems" def test_comparisons(self): c1 = Composition({"S": 1}) @@ -417,7 +417,7 @@ def test_comparisons(self): assert sorted([c1, c1_1, c2, c4, c3]) == [c3, c1, c1_1, c4, c2] Fe = Element("Fe") - assert not c1 == Fe, NotImplemented + assert c1 != Fe, NotImplemented assert c1 != Fe with pytest.raises(TypeError): c1 < Fe # noqa: B015 @@ -434,8 +434,8 @@ def test_almost_equals(self): def test_equality(self): assert self.comp[0] == self.comp[0] - assert not self.comp[0] == self.comp[1] - assert not self.comp[0] != self.comp[0] + assert self.comp[0] != self.comp[1] + assert self.comp[0] == self.comp[0] assert self.comp[0] != self.comp[1] def test_fractional_composition(self): @@ -556,19 +556,19 @@ def test_oxi_state_decoration(self): # Basic test: Get compositions where each element is in a single charge state decorated = Composition("H2O").add_charges_from_oxi_state_guesses() assert Species("H", 1) in decorated - assert 2 == decorated.get(Species("H", 1)) + assert decorated.get(Species("H", 1)) == 2 # Test: More than one charge state per element decorated = Composition("Fe3O4").add_charges_from_oxi_state_guesses() - assert 1 == decorated.get(Species("Fe", 2)) - assert 2 == decorated.get(Species("Fe", 3)) - assert 4 == decorated.get(Species("O", -2)) + assert decorated.get(Species("Fe", 2)) == 1 + assert decorated.get(Species("Fe", 3)) == 2 + assert decorated.get(Species("O", -2)) == 4 # Test: No possible charge states # It should return an uncharged composition decorated = Composition("NiAl").add_charges_from_oxi_state_guesses() - assert 1 == decorated.get(Species("Ni", 0)) - assert 1 == decorated.get(Species("Al", 0)) + assert decorated.get(Species("Ni", 0)) == 1 + assert decorated.get(Species("Al", 0)) == 1 def test_Metallofullerene(self): # Test: Parse Metallofullerene formula (e.g. Y3N@C80) diff --git a/pymatgen/core/tests/test_ion.py b/pymatgen/core/tests/test_ion.py index 0db68edb3d9..5817dbdf91e 100644 --- a/pymatgen/core/tests/test_ion.py +++ b/pymatgen/core/tests/test_ion.py @@ -30,11 +30,11 @@ def setUp(self): def test_init_(self): c = Composition({"Fe": 4, "O": 16, "P": 4}) charge = 4 - assert "Fe4 P4 O16 +4" == Ion(c, charge).formula + assert Ion(c, charge).formula == "Fe4 P4 O16 +4" f = {1: 1, 8: 1} charge = -1 - assert "H1 O1 -1" == Ion(Composition(f), charge).formula - assert "S2 O3 -2" == Ion(Composition(S=2, O=3), -2).formula + assert Ion(Composition(f), charge).formula == "H1 O1 -1" + assert Ion(Composition(S=2, O=3), -2).formula == "S2 O3 -2" def test_charge_from_formula(self): assert Ion.from_formula("Li+").charge == 1 @@ -173,8 +173,8 @@ def test_equals(self): def test_equality(self): assert self.comp[0] == (self.comp[0]) - assert not self.comp[0] == (self.comp[1]) - assert not self.comp[0] != (self.comp[0]) + assert self.comp[0] != self.comp[1] + assert self.comp[0] == self.comp[0] assert self.comp[0] != (self.comp[1]) def test_mul(self): diff --git a/pymatgen/core/tests/test_lattice.py b/pymatgen/core/tests/test_lattice.py index b6ca8b08ed4..0f52ff5f976 100644 --- a/pymatgen/core/tests/test_lattice.py +++ b/pymatgen/core/tests/test_lattice.py @@ -50,15 +50,16 @@ def test_equal(self): assert not any(self.cubic_partial_pbc == x for x in self.families.values()) def test_format(self): - assert "[[10.000, 0.000, 0.000], [0.000, 10.000, 0.000], [0.000, 0.000, 10.000]]" == format( - self.lattice, ".3fl" + assert ( + format(self.lattice, ".3fl") == "[[10.000, 0.000, 0.000], [0.000, 10.000, 0.000], [0.000, 0.000, 10.000]]" ) - assert """10.000 0.000 0.000 + assert ( + format(self.lattice, ".3f") + == """10.000 0.000 0.000 0.000 10.000 0.000 -0.000 0.000 10.000""" == format( - self.lattice, ".3f" +0.000 0.000 10.000""" ) - assert "{10.0, 10.0, 10.0, 90.0, 90.0, 90.0}" == format(self.lattice, ".1fp") + assert format(self.lattice, ".1fp") == "{10.0, 10.0, 10.0, 90.0, 90.0, 90.0}" def test_init(self): a = 9.026 @@ -364,7 +365,7 @@ def test_scale(self): def test_get_wigner_seitz_cell(self): ws_cell = Lattice([[10, 0, 0], [0, 5, 0], [0, 0, 1]]).get_wigner_seitz_cell() - assert 6 == len(ws_cell) + assert len(ws_cell) == 6 for l in ws_cell[3]: assert [abs(i) for i in l] == [5.0, 2.5, 0.5] diff --git a/pymatgen/core/tests/test_libxcfunc.py b/pymatgen/core/tests/test_libxcfunc.py index b2ac37169d6..cd947254a65 100644 --- a/pymatgen/core/tests/test_libxcfunc.py +++ b/pymatgen/core/tests/test_libxcfunc.py @@ -10,7 +10,6 @@ class LibxcFuncTest(PymatgenTest): def test_libxcfunc_api(self): """Testing libxcfunc_api.""" - # LDA correlation: Hedin & Lundqvist xc = LibxcFunc.LDA_C_HL print(xc) diff --git a/pymatgen/core/tests/test_periodic_table.py b/pymatgen/core/tests/test_periodic_table.py index 9decdc945de..3970a58bbb4 100644 --- a/pymatgen/core/tests/test_periodic_table.py +++ b/pymatgen/core/tests/test_periodic_table.py @@ -26,7 +26,7 @@ class ElementTestCase(PymatgenTest): def test_init(self): - assert "Fe" == Element("Fe").symbol, "Fe test failed" + assert Element("Fe").symbol == "Fe", "Fe test failed" fictional_symbols = ["D", "T", "Zebra"] @@ -382,8 +382,8 @@ def test_eq(self): assert self.specie1 == self.specie3, "Static and actual constructor gives unequal result!" assert self.specie1 != self.specie2, "Fe2+ should not be equal to Fe3+" assert self.specie4 != self.specie3 - assert not self.specie1 == Element("Fe") - assert not Element("Fe") == self.specie1 + assert self.specie1 != Element("Fe") + assert Element("Fe") != self.specie1 def test_cmp(self): assert self.specie1 < self.specie2, "Fe2+ should be < Fe3+" @@ -509,8 +509,8 @@ def test_init(self): assert self.specie2.spin == 3 def test_eq(self): - assert not DummySpecies("Xg") == DummySpecies("Xh") - assert not DummySpecies("Xg") == DummySpecies("Xg", 3) + assert DummySpecies("Xg") != DummySpecies("Xh") + assert DummySpecies("Xg") != DummySpecies("Xg", 3) assert DummySpecies("Xg", 3) == DummySpecies("Xg", 3) def test_from_string(self): diff --git a/pymatgen/core/tests/test_spectrum.py b/pymatgen/core/tests/test_spectrum.py index 1517b0b8112..04eedc087f5 100644 --- a/pymatgen/core/tests/test_spectrum.py +++ b/pymatgen/core/tests/test_spectrum.py @@ -6,7 +6,7 @@ import unittest import numpy as np -import scipy.stats as stats +from scipy import stats from pymatgen.core.spectrum import Spectrum from pymatgen.util.testing import PymatgenTest diff --git a/pymatgen/core/tests/test_structure.py b/pymatgen/core/tests/test_structure.py index b659a8f29c7..8f055c99834 100644 --- a/pymatgen/core/tests/test_structure.py +++ b/pymatgen/core/tests/test_structure.py @@ -110,11 +110,11 @@ def test_equal(self): struct = self.struct assert struct == struct assert struct == struct.copy() - assert not struct == 2 * struct + assert struct != 2 * struct - assert not struct == "a" * len(struct) # GH-2584 + assert struct != "a" * len(struct) # GH-2584 assert struct is not None - assert not struct == 42 # GH-2587 + assert struct != 42 # GH-2587 assert struct == Structure.from_dict(struct.as_dict()) @@ -500,7 +500,7 @@ def test_get_all_neighbors_and_get_neighbors(self): r = random.uniform(3, 6) all_nn = s.get_all_neighbors(r, True, True) for idx, site in enumerate(s): - assert 4 == len(all_nn[idx][0]) + assert len(all_nn[idx][0]) == 4 assert len(all_nn[idx]) == len(s.get_neighbors(site, r)) for site, nns in zip(s, all_nn): @@ -779,7 +779,7 @@ def test_mutable_sequence_methods(self): def test_non_hash(self): with pytest.raises(TypeError): - dict([(self.structure, 1)]) + {self.structure: 1} def test_sort(self): s = self.structure @@ -1647,7 +1647,7 @@ def test_insert_remove_append(self): mol.append("N", [1, 1, 1]) assert mol.formula == "H3 C1 N1 O1" with pytest.raises(TypeError): - dict([(mol, 1)]) + {mol: 1} mol.remove_sites([0, 1]) assert mol.formula == "H3 N1" @@ -1761,7 +1761,7 @@ def test_to_from_file_string(self): def test_extract_cluster(self): species = self.mol.species * 2 - coords = list(self.mol.cart_coords) + list(self.mol.cart_coords + [10, 0, 0]) + coords = [*self.mol.cart_coords, *(self.mol.cart_coords + [10, 0, 0])] # noqa: RUF005 mol = Molecule(species, coords) cluster = Molecule.from_sites(mol.extract_cluster([mol[0]])) assert mol.formula == "H8 C2" diff --git a/pymatgen/core/tests/test_surface.py b/pymatgen/core/tests/test_surface.py index f9e08f37b5e..9149b9a5c41 100644 --- a/pymatgen/core/tests/test_surface.py +++ b/pymatgen/core/tests/test_surface.py @@ -873,7 +873,6 @@ def test_generate_all_slabs(self): def test_miller_index_from_sites(self): """Test surface miller index convenience function""" - # test on a cubic system m = Lattice.cubic(1) s1 = np.array([0.5, -1.5, 3]) diff --git a/pymatgen/core/tests/test_tensors.py b/pymatgen/core/tests/test_tensors.py index 7f678ba8543..f7b2ef3ac2a 100644 --- a/pymatgen/core/tests/test_tensors.py +++ b/pymatgen/core/tests/test_tensors.py @@ -182,7 +182,7 @@ def test_einsum_sequence(self): x = [1, 0, 0] test = Tensor(np.arange(0, 3**4).reshape((3, 3, 3, 3))) self.assertArrayAlmostEqual([0, 27, 54], test.einsum_sequence([x] * 3)) - assert 360 == test.einsum_sequence([np.eye(3)] * 2) + assert test.einsum_sequence([np.eye(3)] * 2) == 360 with pytest.raises(ValueError): test.einsum_sequence(Tensor(np.zeros(3))) @@ -289,7 +289,7 @@ def test_symmetry_reduce(self): for k, v in reduced.items(): reconstructed.extend([k.voigt] + [k.transform(op).voigt for op in v]) reconstructed = sorted(reconstructed, key=lambda x: np.argmax(x)) - self.assertArrayAlmostEqual([tb for tb in reconstructed], np.eye(6) * 0.01) + self.assertArrayAlmostEqual(list(reconstructed), np.eye(6) * 0.01) def test_tensor_mapping(self): # Test get @@ -392,9 +392,9 @@ def test_round(self): class TensorCollectionTest(PymatgenTest): def setUp(self): - self.seq_tc = [t for t in np.arange(4 * 3**3).reshape((4, 3, 3, 3))] + self.seq_tc = list(np.arange(4 * 3**3).reshape((4, 3, 3, 3))) self.seq_tc = TensorCollection(self.seq_tc) - self.rand_tc = TensorCollection([t for t in np.random.random((4, 3, 3))]) + self.rand_tc = TensorCollection(list(np.random.random((4, 3, 3)))) self.diff_rank = TensorCollection([np.ones([3] * i) for i in range(2, 5)]) self.struct = self.get_structure("Si") ieee_file_path = os.path.join(PymatgenTest.TEST_FILES_DIR, "ieee_conversion_data.json") @@ -467,7 +467,7 @@ def test_list_based_functions(self): self.list_based_function_check("convert_to_ieee", tc, struct) # from_voigt - tc_input = [t for t in np.random.random((3, 6, 6))] + tc_input = list(np.random.random((3, 6, 6))) tc = TensorCollection.from_voigt(tc_input) for t_input, t in zip(tc_input, tc): self.assertArrayAlmostEqual(Tensor.from_voigt(t_input), t) diff --git a/pymatgen/core/tests/test_units.py b/pymatgen/core/tests/test_units.py index f47d7362ef2..d632d34d5d7 100644 --- a/pymatgen/core/tests/test_units.py +++ b/pymatgen/core/tests/test_units.py @@ -267,7 +267,6 @@ def test_as_base_units(self): class DataPersistenceTest(PymatgenTest): def test_pickle(self): """Test whether FloatWithUnit and ArrayWithUnit support pickle""" - for cls in [FloatWithUnit, ArrayWithUnit]: a = cls(1, "eV") b = cls(10, "N bohr") diff --git a/pymatgen/core/trajectory.py b/pymatgen/core/trajectory.py index 04dbe8561ea..b93fa292733 100644 --- a/pymatgen/core/trajectory.py +++ b/pymatgen/core/trajectory.py @@ -368,10 +368,7 @@ def write_Xdatcar( if si == 0 or not self.constant_lattice: lines.extend([system, "1.0"]) - if self.constant_lattice: - _lattice = self.lattice - else: - _lattice = self.lattice[si] + _lattice = self.lattice if self.constant_lattice else self.lattice[si] for latt_vec in _lattice: lines.append(f'{" ".join(map(str, latt_vec))}') diff --git a/pymatgen/core/xcfunc.py b/pymatgen/core/xcfunc.py index b89229f46ee..33b3086a5bd 100644 --- a/pymatgen/core/xcfunc.py +++ b/pymatgen/core/xcfunc.py @@ -103,14 +103,14 @@ class XcFunc(MSONable): # and 42_libpaw/m_pawpsp.F90 for the implementation. # Fortunately, all the other cases are handled with libxc. abinitixc_to_libxc = { - 1: dict(xc=xcf.LDA_XC_TETER93), - 2: dict(x=xcf.LDA_X, c=xcf.LDA_C_PZ), # PZ 001009 - 4: dict(x=xcf.LDA_X, c=xcf.LDA_C_WIGNER), # W - 5: dict(x=xcf.LDA_X, c=xcf.LDA_C_HL), # HL - 7: dict(x=xcf.LDA_X, c=xcf.LDA_C_PW), # PW 001012 - 11: dict(x=xcf.GGA_X_PBE, c=xcf.GGA_C_PBE), # PBE - 14: dict(x=xcf.GGA_X_PBE_R, c=xcf.GGA_C_PBE), # revPBE - 15: dict(x=xcf.GGA_X_RPBE, c=xcf.GGA_C_PBE), # RPBE + 1: {"xc": xcf.LDA_XC_TETER93}, + 2: {"x": xcf.LDA_X, "c": xcf.LDA_C_PZ}, # PZ 001009 + 4: {"x": xcf.LDA_X, "c": xcf.LDA_C_WIGNER}, # W + 5: {"x": xcf.LDA_X, "c": xcf.LDA_C_HL}, # HL + 7: {"x": xcf.LDA_X, "c": xcf.LDA_C_PW}, # PW 001012 + 11: {"x": xcf.GGA_X_PBE, "c": xcf.GGA_C_PBE}, # PBE + 14: {"x": xcf.GGA_X_PBE_R, "c": xcf.GGA_C_PBE}, # revPBE + 15: {"x": xcf.GGA_X_RPBE, "c": xcf.GGA_C_PBE}, # RPBE } del xcf diff --git a/pymatgen/electronic_structure/bandstructure.py b/pymatgen/electronic_structure/bandstructure.py index 1be1e7131cd..ae222c1ddec 100644 --- a/pymatgen/electronic_structure/bandstructure.py +++ b/pymatgen/electronic_structure/bandstructure.py @@ -325,9 +325,8 @@ def get_projections_on_elements_and_orbitals(self, el_orb_spec): sp = structure[k].specie for orb_i in range(len(v[i][j])): o = Orbital(orb_i).name[0] - if sp in el_orb_spec: - if o in el_orb_spec[sp]: - result[spin][i][j][str(sp)][o] += v[i][j][orb_i][k] + if sp in el_orb_spec and o in el_orb_spec[sp]: + result[spin][i][j][str(sp)][o] += v[i][j][orb_i][k] return result def is_metal(self, efermi_tol=1e-4) -> bool: @@ -493,7 +492,7 @@ def get_band_gap(self): return {"energy": 0.0, "direct": False, "transition": None} cbm = self.get_cbm() vbm = self.get_vbm() - result = dict(direct=False, energy=0.0, transition=None) + result = {"direct": False, "energy": 0.0, "transition": None} result["energy"] = cbm["energy"] - vbm["energy"] @@ -698,6 +697,7 @@ def from_old_dict(cls, d): Args: d (dict): A dict with all data for a band structure symm line object. + Returns: A BandStructureSymmLine object """ @@ -805,11 +805,10 @@ def __init__( self.distance.append(np.linalg.norm(kpt.cart_coords - previous_kpoint.cart_coords) + previous_distance) previous_kpoint = kpt previous_distance = self.distance[i] - if label: - if previous_label: - if len(one_group) != 0: - branches_tmp.append(one_group) - one_group = [] + if label and previous_label: + if len(one_group) != 0: + branches_tmp.append(one_group) + one_group = [] previous_label = label one_group.append(i) @@ -908,10 +907,9 @@ def apply_scissor(self, new_band_gap): below = True if self.bands[Spin.up][i][j] > self.efermi: above = True - if above and below: - if i > max_index: - max_index = i - # spin_index = Spin.up + if above and below and i > max_index: + max_index = i + # spin_index = Spin.up if self.is_spin_polarized: below = False above = False @@ -920,10 +918,9 @@ def apply_scissor(self, new_band_gap): below = True if self.bands[Spin.down][i][j] > self.efermi: above = True - if above and below: - if i > max_index: - max_index = i - # spin_index = Spin.down + if above and below and i > max_index: + max_index = i + # spin_index = Spin.down old_dict = self.as_dict() shift = new_band_gap for spin in old_dict["bands"]: @@ -1045,6 +1042,7 @@ def from_old_dict(cls, d): Args: d (dict): A dict with all data for a band structure symm line object. + Returns: A BandStructureSymmLine object """ @@ -1126,9 +1124,8 @@ def get_projections_on_elements_and_orbitals(self, el_orb_spec): for key, item in v[i][j].items(): for key2, item2 in item.items(): specie = str(Element(re.split(r"[0-9]+", key)[0])) - if get_el_sp(str(specie)) in el_orb_spec: - if key2 in el_orb_spec[get_el_sp(str(specie))]: - result[spin][i][j][specie][key2] += item2 + if get_el_sp(str(specie)) in el_orb_spec and key2 in el_orb_spec[get_el_sp(str(specie))]: + result[spin][i][j][specie][key2] += item2 return result diff --git a/pymatgen/electronic_structure/boltztrap.py b/pymatgen/electronic_structure/boltztrap.py index 5728ae40718..432f4da46a5 100644 --- a/pymatgen/electronic_structure/boltztrap.py +++ b/pymatgen/electronic_structure/boltztrap.py @@ -280,10 +280,7 @@ def write_energy(self, output_file): else: for i, kpt in enumerate(self._bs.kpoints): eigs = [] - if self.run_type == "DOS": - spin_lst = [self.spin] - else: - spin_lst = self._bs.bands + spin_lst = [self.spin] if self.run_type == "DOS" else self._bs.bands for spin in spin_lst: # use 90% of bottom bands since highest eigenvalues @@ -542,6 +539,7 @@ def run( """ Write inputs (optional), run BoltzTraP, and ensure convergence (optional) + Args: path_dir (str): directory in which to run BoltzTraP convergence (bool): whether to check convergence and make @@ -1357,6 +1355,7 @@ def get_seebeck_eff_mass(self, output="average", temp=300, doping_levels=False, temp: temperature of calculated seebeck. Lambda: fitting parameter used to model the scattering (0.5 means constant relaxation time). + Returns: a list of values for the seebeck effective mass w.r.t the chemical potential, if doping_levels is set at False; @@ -1413,6 +1412,7 @@ def get_complexity_factor(self, output="average", temp=300, doping_levels=False, temp: temperature of calculated seebeck and conductivity. Lambda: fitting parameter used to model the scattering (0.5 means constant relaxation time). + Returns: a list of values for the complexity factor w.r.t the chemical potential, if doping_levels is set at False; @@ -1542,10 +1542,7 @@ def is_isotropic(x, isotropy_tolerance) -> bool: isotropic = is_isotropic(evs, isotropy_tolerance) if absval: evs = [abs(x) for x in evs] - if use_average: - val = float(sum(evs)) / len(evs) - else: - val = max(evs) + val = float(sum(evs)) / len(evs) if use_average else max(evs) if x_val is None or (val > x_val and maximize) or (val < x_val and not maximize): x_val = val x_temp = t @@ -1680,7 +1677,7 @@ def get_carrier_concentration(self): """ gives the carrier concentration (in cm^-3) - Returns + Returns: a dictionary {temp:[]} with an array of carrier concentration (in cm^-3) at each temperature The array relates to each step of electron chemical potential @@ -1693,7 +1690,7 @@ def get_hall_carrier_concentration(self): the Hall tensor (see Boltztrap source code) Hall carrier concentration are not always exactly the same than carrier concentration. - Returns + Returns: a dictionary {temp:[]} with an array of Hall carrier concentration (in cm^-3) at each temperature The array relates to each step of electron chemical potential @@ -1841,6 +1838,7 @@ def parse_intrans(path_dir): def parse_struct(path_dir): """ Parses boltztrap.struct file (only the volume) + Args: path_dir: (str) dir containing the boltztrap.struct file @@ -2286,7 +2284,6 @@ def compare_sym_bands(bands_obj, bands_ref_obj, nb=None): sym line, for all bands and locally (for each branches) the difference squared (%) if nb is specified. """ - if bands_ref_obj.is_spin_polarized: nbands = min(bands_obj.nb_bands, 2 * bands_ref_obj.nb_bands) else: diff --git a/pymatgen/electronic_structure/boltztrap2.py b/pymatgen/electronic_structure/boltztrap2.py index a318510ed3f..95c85028b29 100644 --- a/pymatgen/electronic_structure/boltztrap2.py +++ b/pymatgen/electronic_structure/boltztrap2.py @@ -200,6 +200,7 @@ def __init__(self, bs_obj, structure=None, nelect=None, mommat=None, magmom=None nelect: Number of electrons in the calculation. momat: Matrix of derivatives of energy eigenvalues. Not implemented yet. magmom: Matrix of magnetic moments in non collinear calculations. Not implemented yet. + Example: vrun = Vasprun('vasprun.xml') bs = vrun.get_band_structure() @@ -456,6 +457,7 @@ def __init__( save_bands: Default False. If True interpolated bands are also stored. It can be slower than interpolate them. Not recommended. fname: File path where to store/load from the coefficients and equivalences. + Example: data = VasprunLoader().from_file('vasprun.xml') bztInterp = BztInterpolator(data) @@ -500,7 +502,8 @@ def load(self, fname="bztInterp.json.gz"): def save(self, fname="bztInterp.json.gz", bands=False): """Save the coefficient, equivalences to fname. - If bands is True, also interpolated bands are stored.""" + If bands is True, also interpolated bands are stored. + """ if bands: dumpfn( [ @@ -1250,6 +1253,7 @@ def plot_dos(self, T=None, npoints=10000): def merge_up_down_doses(dos_up, dos_dn): """ Merge the up and down DOSs. + Args: dos_up: Up DOS. dos_dn: Down DOS diff --git a/pymatgen/electronic_structure/cohp.py b/pymatgen/electronic_structure/cohp.py index 7b65d921885..1d9507f436c 100644 --- a/pymatgen/electronic_structure/cohp.py +++ b/pymatgen/electronic_structure/cohp.py @@ -117,10 +117,7 @@ def get_cohp(self, spin=None, integrated=False): None and both spins are present, both spins will be returned as a dictionary. """ - if not integrated: - populations = self.cohp - else: - populations = self.icohp + populations = self.cohp if not integrated else self.icohp if populations is None: return None @@ -195,14 +192,8 @@ def from_dict(cls, d): """ Returns a COHP object from a dict representation of the COHP. """ - if "ICOHP" in d: - icohp = {Spin(int(key)): np.array(val) for key, val in d["ICOHP"].items()} - else: - icohp = None - if "are_cobis" not in d: - are_cobis = False - else: - are_cobis = d["are_cobis"] + icohp = {Spin(int(key)): np.array(val) for key, val in d["ICOHP"].items()} if "ICOHP" in d else None + are_cobis = False if "are_cobis" not in d else d["are_cobis"] return Cohp( d["efermi"], d["energies"], @@ -536,11 +527,11 @@ def get_orbital_resolved_cohp(self, label, orbitals, summed_spin_channels=False) orbs = [] for orbital in orbitals: if isinstance(orbital[1], int): - orbs.append(tuple((orbital[0], Orbital(orbital[1])))) + orbs.append((orbital[0], Orbital(orbital[1]))) elif isinstance(orbital[1], Orbital): - orbs.append(tuple((orbital[0], orbital[1]))) + orbs.append((orbital[0], orbital[1])) elif isinstance(orbital[1], str): - orbs.append(tuple((orbital[0], Orbital[orbital[1]]))) + orbs.append((orbital[0], Orbital[orbital[1]])) else: raise TypeError("Orbital must be str, int, or Orbital.") orb_index = cohp_orbs.index(orbs) @@ -622,7 +613,7 @@ def from_dict(cls, d): } except KeyError: icohp = None - orbitals = [tuple((int(o[0]), Orbital[o[1]])) for o in d["orb_res_cohp"][label][orb]["orbitals"]] + orbitals = [(int(o[0]), Orbital[o[1]]) for o in d["orb_res_cohp"][label][orb]["orbitals"]] orb_cohp[label][orb] = { "COHP": cohp, "ICOHP": icohp, @@ -674,10 +665,7 @@ def from_dict(cls, d): icohp = None avg_cohp = Cohp(efermi, energies, cohp, icohp=icohp) - if "are_cobis" not in d: - are_cobis = False - else: - are_cobis = d["are_cobis"] + are_cobis = False if "are_cobis" not in d else d["are_cobis"] return CompleteCohp( structure, @@ -735,14 +723,13 @@ def from_file(cls, fmt, filename=None, structure_file=None, are_coops=False, are raise ValueError("You cannot have info about COOPs and COBIs in the same file.") if structure_file is None: structure_file = "POSCAR" - if filename is None: - if filename is None: - if are_coops: - filename = "COOPCAR.lobster" - elif are_cobis: - filename = "COBICAR.lobster" - else: - filename = "COHPCAR.lobster" + if filename is None and filename is None: + if are_coops: + filename = "COOPCAR.lobster" + elif are_cobis: + filename = "COBICAR.lobster" + else: + filename = "COHPCAR.lobster" cohp_file = Cohpcar(filename=filename, are_coops=are_coops, are_cobis=are_cobis) orb_res_cohp = cohp_file.orb_res_cohp else: @@ -1038,10 +1025,7 @@ def summed_icohp(self): Returns: icohp value in eV """ - if self._is_spin_polarized: - sum_icohp = self._icohp[Spin.down] + self._icohp[Spin.up] - else: - sum_icohp = self._icohp[Spin.up] + sum_icohp = self._icohp[Spin.down] + self._icohp[Spin.up] if self._is_spin_polarized else self._icohp[Spin.up] return sum_icohp @@ -1195,6 +1179,7 @@ def get_icohp_dict_of_site( ): """ get a dict of IcohpValue for a certain site (indicated by integer) + Args: site: integer describing the site of interest, order as in Icohplist.lobster/Icooplist.lobster, starts at 0 minsummedicohp: float, minimal icohp/icoop of the bonds that are considered. It is the summed ICOHP value @@ -1204,6 +1189,7 @@ def get_icohp_dict_of_site( minbondlength: float, defines the minimum of the bond lengths of the bonds maxbondlength: float, defines the maximum of the bond lengths of the bonds only_bonds_to: list of strings describing the bonding partners that are allowed, e.g. ['O'] + Returns: dict of IcohpValues, the keys correspond to the values from the initial list_labels """ @@ -1218,10 +1204,7 @@ def get_icohp_dict_of_site( value._atom1 = value._atom2 value._atom2 = save - if only_bonds_to is None: - second_test = True - else: - second_test = re.split(r"(\d+)", value._atom2)[0] in only_bonds_to + second_test = True if only_bonds_to is None else re.split("(\\d+)", value._atom2)[0] in only_bonds_to if value._length >= minbondlength and value._length <= maxbondlength and second_test: if minsummedicohp is not None: if value.summed_icohp >= minsummedicohp: @@ -1249,11 +1232,7 @@ def extremum_icohpvalue(self, summed_spin_channels=True, spin=Spin.up): Returns: lowest ICOHP/largest ICOOP value (i.e. ICOHP/ICOOP value of strongest bond) """ - if self._are_coops or self._are_cobis: - extremum = -sys.float_info.max - - else: - extremum = sys.float_info.max + extremum = -sys.float_info.max if self._are_coops or self._are_cobis else sys.float_info.max if not self._is_spin_polarized: if spin == Spin.down: @@ -1377,10 +1356,7 @@ def get_integrated_cohp_in_energy_range( return spl_spinup(cohp.efermi) - spl_spinup(energy_range) return {Spin.up: spl_spinup(cohp.efermi) - spl_spinup(energy_range)} - if relative_E_Fermi: - energies_corrected = cohp.energies - cohp.efermi - else: - energies_corrected = cohp.energies + energies_corrected = cohp.energies - cohp.efermi if relative_E_Fermi else cohp.energies spl_spinup = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.up], ext=0) diff --git a/pymatgen/electronic_structure/core.py b/pymatgen/electronic_structure/core.py index 344522cc7e7..b2bdccd80d0 100644 --- a/pymatgen/electronic_structure/core.py +++ b/pymatgen/electronic_structure/core.py @@ -331,10 +331,7 @@ def get_consistent_set_and_saxis(magmoms, saxis=None): :return: (list of Magmoms, global spin axis) tuple """ magmoms = [Magmom(magmom) for magmom in magmoms] - if saxis is None: - saxis = Magmom.get_suggested_saxis(magmoms) - else: - saxis = saxis / np.linalg.norm(saxis) + saxis = Magmom.get_suggested_saxis(magmoms) if saxis is None else saxis / np.linalg.norm(saxis) magmoms = [magmom.get_moment(saxis=saxis) for magmom in magmoms] return magmoms, saxis diff --git a/pymatgen/electronic_structure/dos.py b/pymatgen/electronic_structure/dos.py index 4be5d7d1a33..fdb0785551e 100644 --- a/pymatgen/electronic_structure/dos.py +++ b/pymatgen/electronic_structure/dos.py @@ -464,10 +464,7 @@ def __init__( self.A_to_cm = 1e-8 if bandgap: - if evbm < self.efermi < ecbm: - eref = self.efermi - else: - eref = (evbm + ecbm) / 2.0 + eref = self.efermi if evbm < self.efermi < ecbm else (evbm + ecbm) / 2.0 idx_fermi = int(np.argmin(abs(self.energies - eref))) @@ -852,7 +849,7 @@ def get_band_filling( spin: Spin | None = None, ) -> float: """ - Computes the orbital-projected band filling, defined as the zeroth moment + Compute the orbital-projected band filling, defined as the zeroth moment up to the Fermi level Args: @@ -868,21 +865,20 @@ def get_band_filling( if elements and sites: raise ValueError("Both element and site cannot be specified.") + densities: dict[Spin, ArrayLike] = {} if elements: - for i, el in enumerate(elements): + for idx, el in enumerate(elements): spd_dos = self.get_element_spd_dos(el)[band] - if i == 0: - densities = spd_dos.densities - else: - densities = add_densities(densities, spd_dos.densities) + densities = ( + spd_dos.densities if idx == 0 else add_densities(densities, spd_dos.densities) # type: ignore + ) dos = Dos(self.efermi, self.energies, densities) elif sites: - for i, site in enumerate(sites): + for idx, site in enumerate(sites): spd_dos = self.get_site_spd_dos(site)[band] - if i == 0: - densities = spd_dos.densities - else: - densities = add_densities(densities, spd_dos.densities) + densities = ( + spd_dos.densities if idx == 0 else add_densities(densities, spd_dos.densities) # type: ignore + ) dos = Dos(self.efermi, self.energies, densities) else: dos = self.get_spd_dos()[band] @@ -907,7 +903,7 @@ def get_band_center( erange: list[float] | None = None, ) -> float: """ - Computes the orbital-projected band center, defined as the first moment + Compute the orbital-projected band center, defined as the first moment relative to the Fermi level int_{-inf}^{+inf} rho(E)*E dE/int_{-inf}^{+inf} rho(E) dE based on the work of Hammer and Norskov, Surf. Sci., 343 (1995) where the @@ -1065,21 +1061,16 @@ def get_n_moment( if elements and sites: raise ValueError("Both element and site cannot be specified.") + densities: Mapping[Spin, ArrayLike] = {} if elements: for i, el in enumerate(elements): spd_dos = self.get_element_spd_dos(el)[band] - if i == 0: - densities = spd_dos.densities - else: - densities = add_densities(densities, spd_dos.densities) + densities = spd_dos.densities if i == 0 else add_densities(densities, spd_dos.densities) dos = Dos(self.efermi, self.energies, densities) elif sites: for i, site in enumerate(sites): spd_dos = self.get_site_spd_dos(site)[band] - if i == 0: - densities = spd_dos.densities - else: - densities = add_densities(densities, spd_dos.densities) + densities = spd_dos.densities if i == 0 else add_densities(densities, spd_dos.densities) dos = Dos(self.efermi, self.energies, densities) else: dos = self.get_spd_dos()[band] @@ -1110,8 +1101,7 @@ def get_hilbert_transform( elements: list[SpeciesLike] | None = None, sites: list[PeriodicSite] | None = None, ) -> Dos: - """ - Returns the Hilbert transform of the orbital-projected density of states, + """Return the Hilbert transform of the orbital-projected density of states, often plotted for a Newns-Anderson analysis. Args: @@ -1126,22 +1116,16 @@ def get_hilbert_transform( if elements and sites: raise ValueError("Both element and site cannot be specified.") + densities: Mapping[Spin, ArrayLike] = {} if elements: - densities: Mapping[Spin, ArrayLike] for i, el in enumerate(elements): spd_dos = self.get_element_spd_dos(el)[band] - if i == 0: - densities = spd_dos.densities - else: - densities = add_densities(densities, spd_dos.densities) + densities = spd_dos.densities if i == 0 else add_densities(densities, spd_dos.densities) dos = Dos(self.efermi, self.energies, densities) elif sites: for i, site in enumerate(sites): spd_dos = self.get_site_spd_dos(site)[band] - if i == 0: - densities = spd_dos.densities - else: - densities = add_densities(densities, spd_dos.densities) + densities = spd_dos.densities if i == 0 else add_densities(densities, spd_dos.densities) dos = Dos(self.efermi, self.energies, densities) else: dos = self.get_spd_dos()[band] @@ -1318,15 +1302,9 @@ def get_dos_fp_similarity( Returns: Similarity index (float): The value of dot product """ - if not isinstance(fp1, dict): - fp1_dict = CompleteDos.fp_to_dict(fp1) - else: - fp1_dict = fp1 + fp1_dict = CompleteDos.fp_to_dict(fp1) if not isinstance(fp1, dict) else fp1 - if not isinstance(fp2, dict): - fp2_dict = CompleteDos.fp_to_dict(fp2) - else: - fp2_dict = fp2 + fp2_dict = CompleteDos.fp_to_dict(fp2) if not isinstance(fp2, dict) else fp2 if pt == "All": vec1 = np.array([pt[col] for pt in fp1_dict.values()]).flatten() @@ -1442,6 +1420,7 @@ def get_site_orbital_dos(self, site: PeriodicSite, orbital: str) -> Dos: # type def get_site_t2g_eg_resolved_dos(self, site: PeriodicSite) -> dict[str, Dos]: """ Get the t2g, eg projected DOS for a particular site. + Args: site: Site in Structure associated with CompleteDos. @@ -1482,7 +1461,7 @@ def get_spd_dos(self) -> dict[str, Dos]: # type: ignore else: spd_dos[orbital_type] = add_densities(spd_dos[orbital_type], pdos) - return {orb: Dos(self.efermi, self.energies, densities) for orb, densities in spd_dos.items()} + return {orb: Dos(self.efermi, self.energies, densities) for orb, densities in spd_dos.items()} # type: ignore def get_element_spd_dos(self, el: SpeciesLike) -> dict[str, Dos]: # type: ignore """ @@ -1506,13 +1485,11 @@ def get_element_spd_dos(self, el: SpeciesLike) -> dict[str, Dos]: # type: ignor else: el_dos[orbital_type] = add_densities(el_dos[orbital_type], pdos) - return {orb: Dos(self.efermi, self.energies, densities) for orb, densities in el_dos.items()} + return {orb: Dos(self.efermi, self.energies, densities) for orb, densities in el_dos.items()} # type: ignore @classmethod def from_dict(cls, d) -> LobsterCompleteDos: - """ - Returns: CompleteDos object from dict representation. - """ + """Hydrate CompleteDos object from dict representation.""" tdos = Dos.from_dict(d) struct = Structure.from_dict(d["structure"]) pdoss = {} @@ -1527,8 +1504,7 @@ def from_dict(cls, d) -> LobsterCompleteDos: def add_densities(density1: Mapping[Spin, ArrayLike], density2: Mapping[Spin, ArrayLike]) -> dict[Spin, np.ndarray]: - """ - Method to sum two densities. + """Sum two densities. Args: density1: First density. @@ -1540,7 +1516,7 @@ def add_densities(density1: Mapping[Spin, ArrayLike], density2: Mapping[Spin, Ar return {spin: np.array(density1[spin]) + np.array(density2[spin]) for spin in density1} -def _get_orb_type(orb): +def _get_orb_type(orb) -> OrbitalType: try: return orb.orbital_type except AttributeError: @@ -1548,7 +1524,7 @@ def _get_orb_type(orb): def f0(E, fermi, T) -> float: - """Returns the equilibrium fermi-dirac. + """Return the equilibrium fermi-dirac. Args: E (float): energy in eV @@ -1561,31 +1537,16 @@ def f0(E, fermi, T) -> float: return 1.0 / (1.0 + np.exp((E - fermi) / (_cd("Boltzmann constant in eV/K") * T))) -def _get_orb_type_lobster(orb): +def _get_orb_type_lobster(orb) -> OrbitalType | None: """ Args: orb: string representation of orbital + Returns: OrbitalType """ - orb_labs = [ - "s", - "p_y", - "p_z", - "p_x", - "d_xy", - "d_yz", - "d_z^2", - "d_xz", - "d_x^2-y^2", - "f_y(3x^2-y^2)", - "f_xyz", - "f_yz^2", - "f_z^3", - "f_xz^2", - "f_z(x^2-y^2)", - "f_x(x^2-3y^2)", - ] + orb_labs = ["s", "p_y", "p_z", "p_x", "d_xy", "d_yz", "d_z^2", "d_xz", "d_x^2-y^2"] + orb_labs += ["f_y(3x^2-y^2)", "f_xyz", "f_yz^2", "f_z^3", "f_xz^2", "f_z(x^2-y^2)", "f_x(x^2-3y^2)"] try: orbital = Orbital(orb_labs.index(orb[1:])) diff --git a/pymatgen/electronic_structure/plotter.py b/pymatgen/electronic_structure/plotter.py index eca8bc0557e..4e81831b963 100644 --- a/pymatgen/electronic_structure/plotter.py +++ b/pymatgen/electronic_structure/plotter.py @@ -108,10 +108,7 @@ def add_dos_dict(self, dos_dict, key_sort_func=None): dos_dict: dict of {label: Dos} key_sort_func: function used to sort the dos_dict keys. """ - if key_sort_func: - keys = sorted(dos_dict, key=key_sort_func) - else: - keys = list(dos_dict) + keys = sorted(dos_dict, key=key_sort_func) if key_sort_func else list(dos_dict) for label in keys: self.add_dos(label, dos_dict[label]) @@ -448,18 +445,14 @@ def bs_plot_data(self, zero_to_efermi=True, bs=None, bs_ref=None, split_branches zero_energy = 0.0 if zero_to_efermi: - if bs_is_metal: - zero_energy = bs.efermi - else: - zero_energy = vbm["energy"] + zero_energy = bs.efermi if bs_is_metal else vbm["energy"] # rescale distances when a bs_ref is given as reference, # and when bs and bs_ref have different points in branches. # Usually bs_ref is the first one in self._bs list is bs_ref distances = bs.distance - if bs_ref is not None: - if bs_ref.branches != bs.branches: - distances = self._rescale_distances(bs_ref, bs) + if bs_ref is not None and bs_ref.branches != bs.branches: + distances = self._rescale_distances(bs_ref, bs) if split_branches: steps = [br["end_index"] + 1 for br in bs.branches][:-1] @@ -1553,7 +1546,8 @@ def get_projected_plots_dots_patom_pmorb( {Element: [Site numbers]}, for instance: {'Cu':[1,5],'O':[3,4]} will give projections for Cu on site-1 and on site-5, O on site-3 and on site-4 in the cell. - Attention: + + Attention: The correct site numbers of atoms are consistent with themselves in the structure computed. Normally, the structure should be totally similar with POSCAR file, @@ -1663,7 +1657,7 @@ class to get the final_structure and as a br += 1 for i in range(self._nb_bands): plt.plot( - list(map(lambda x: x - shift[br], data["distances"][b])), + [x - shift[br] for x in data["distances"][b]], [data["energy"][str(Spin.up)][b][i][j] for j in range(len(data["distances"][b]))], "b-", linewidth=band_linewidth, @@ -1671,12 +1665,7 @@ class to get the final_structure and as a if self._bs.is_spin_polarized: plt.plot( - list( - map( - lambda x: x - shift[br], - data["distances"][b], - ) - ), + [x - shift[br] for x in data["distances"][b]], [data["energy"][str(Spin.down)][b][i][j] for j in range(len(data["distances"][b]))], "r--", linewidth=band_linewidth, @@ -1764,9 +1753,8 @@ def _Orbitals_SumOrbitals(cls, dictio, sum_morbs): ) if orb not in all_orbitals: raise ValueError(f"The invalid name of orbital is given in 'dictio[{elt}]'.") - if orb in individual_orbs: - if len(set(dictio[elt]).intersection(individual_orbs[orb])) != 0: - raise ValueError(f"The 'dictio[{elt}]' contains orbitals repeated.") + if orb in individual_orbs and len(set(dictio[elt]).intersection(individual_orbs[orb])) != 0: + raise ValueError(f"The 'dictio[{elt}]' contains orbitals repeated.") nelems = Counter(dictio[elt]).values() if sum(nelems) > len(nelems): raise ValueError(f"You put in at least two similar orbitals in dictio[{elt}].") @@ -2291,6 +2279,7 @@ def __init__( def get_plot(self, bs: BandStructureSymmLine, dos: Dos | CompleteDos | None = None): """ Get a matplotlib plot object. + Args: bs (BandStructureSymmLine): the bandstructure to plot. Projection data must exist for projected plots. @@ -3763,10 +3752,7 @@ def add_cohp_dict(self, cohp_dict, key_sort_func=None): key_sort_func: function used to sort the cohp_dict keys. """ - if key_sort_func: - keys = sorted(cohp_dict, key=key_sort_func) - else: - keys = list(cohp_dict) + keys = sorted(cohp_dict, key=key_sort_func) if key_sort_func else list(cohp_dict) for label in keys: self.add_cohp(label, cohp_dict[label]) @@ -3829,10 +3815,7 @@ def get_plot( if plot_negative: cohp_label = "-" + cohp_label - if self.zero_at_efermi: - energy_label = "$E - E_f$ (eV)" - else: - energy_label = "$E$ (eV)" + energy_label = "$E - E_f$ (eV)" if self.zero_at_efermi else "$E$ (eV)" ncolors = max(3, len(self._cohps)) ncolors = min(9, ncolors) @@ -3848,10 +3831,7 @@ def get_plot( keys = list(self._cohps) for i, key in enumerate(keys): energies = self._cohps[key]["energies"] - if not integrated: - populations = self._cohps[key]["COHP"] - else: - populations = self._cohps[key]["ICOHP"] + populations = self._cohps[key]["COHP"] if not integrated else self._cohps[key]["ICOHP"] for spin in [Spin.up, Spin.down]: if spin in populations: if invert_axes: @@ -4308,10 +4288,7 @@ def fold_point(p, lattice, coords_are_cartesian=False): Returns: The Cartesian coordinates folded inside the first Brillouin zone """ - if coords_are_cartesian: - p = lattice.get_fractional_coords(p) - else: - p = np.array(p) + p = lattice.get_fractional_coords(p) if coords_are_cartesian else np.array(p) p = np.mod(p + 0.5 - 1e-10, 1) - 0.5 + 1e-10 p = lattice.get_cartesian_coords(p) diff --git a/pymatgen/electronic_structure/tests/test_core.py b/pymatgen/electronic_structure/tests/test_core.py index 69e328f8290..55a9e0f52d6 100644 --- a/pymatgen/electronic_structure/tests/test_core.py +++ b/pymatgen/electronic_structure/tests/test_core.py @@ -131,7 +131,7 @@ def test_relative_to_crystal_axes(self): def test_equality(self): assert Magmom([1, 1, 1]) == Magmom([1, 1, 1]) - assert not Magmom([1, 1, 2]) == Magmom([1, 1, 1]) + assert Magmom([1, 1, 2]) != Magmom([1, 1, 1]) assert Magmom([0, 0, 10]) == 10 def test_negative(self): diff --git a/pymatgen/entries/compatibility.py b/pymatgen/entries/compatibility.py index 37c55a02f76..e7a2876304e 100644 --- a/pymatgen/entries/compatibility.py +++ b/pymatgen/entries/compatibility.py @@ -367,7 +367,7 @@ def get_correction(self, entry) -> ufloat: err = self.cpd_errors[rform] * comp.num_atoms correction += ufloat(corr, err) - if not rform == "H2O": + if rform != "H2O": # if the composition contains water molecules (e.g. FeO.nH2O), # correct the gibbs free energy such that the waters are assigned energy=MU_H2O # in other words, we assume that the DFT energy of such a compound is really @@ -677,10 +677,7 @@ def get_adjustments(self, entry: AnyComputedEntry) -> list[EnergyAdjustment]: corrections, uncertainties = self.get_corrections_dict(entry) for k, v in corrections.items(): - if v != 0 and uncertainties[k] == 0: - uncertainty = np.nan - else: - uncertainty = uncertainties[k] + uncertainty = np.nan if v != 0 and uncertainties[k] == 0 else uncertainties[k] adjustment_list.append(ConstantEnergyAdjustment(v, uncertainty=uncertainty, name=k, cls=self.as_dict())) return adjustment_list @@ -1377,7 +1374,7 @@ def get_adjustments(self, entry: ComputedEntry) -> list[EnergyAdjustment]: # where E is DFT energy, dE is an energy correction, and g is Gibbs free energy # This means we have to 1) remove energy corrections associated with H and O in water # and then 2) remove the free energy of the water molecules - if not rform == "H2O": + if rform != "H2O": # count the number of whole water molecules in the composition nH2O = int(min(comp["H"] / 2.0, comp["O"])) if nH2O > 0: diff --git a/pymatgen/entries/computed_entries.py b/pymatgen/entries/computed_entries.py index c6d97518722..714fe2b7c5a 100644 --- a/pymatgen/entries/computed_entries.py +++ b/pymatgen/entries/computed_entries.py @@ -501,9 +501,8 @@ def __eq__(self, other: object) -> bool: # However, if entry_id is same, they may have different corrections (e.g., due # to mixing scheme used) and thus should be compared on corrected energy. - if getattr(self, "entry_id", None) and getattr(other, "entry_id", None): - if self.entry_id != other.entry_id: - return False + if getattr(self, "entry_id", None) and getattr(other, "entry_id", None) and self.entry_id != other.entry_id: + return False if not math.isclose(self.energy, other.energy): return False diff --git a/pymatgen/entries/correction_calculator.py b/pymatgen/entries/correction_calculator.py index 0460c4058d9..bddf470537e 100644 --- a/pymatgen/entries/correction_calculator.py +++ b/pymatgen/entries/correction_calculator.py @@ -392,7 +392,7 @@ def make_yaml(self, name: str = "MP2020", dir: str | None = None) -> None: o_error: dict[str, float] = {} f_error: dict[str, float] = {} - for specie in list(self.species) + ["ozonide"]: + for specie in [*self.species, "ozonide"]: if specie in ggau_correction_species: o[specie] = self.corrections_dict[specie][0] f[specie] = self.corrections_dict[specie][0] @@ -418,10 +418,7 @@ def make_yaml(self, name: str = "MP2020", dir: str | None = None) -> None: CompositionCorrections: """ fn = name + "Compatibility.yaml" - if dir: - path = os.path.join(dir, fn) - else: - path = fn + path = os.path.join(dir, fn) if dir else fn yml = yaml.YAML() yml.default_flow_style = False diff --git a/pymatgen/entries/entry_tools.py b/pymatgen/entries/entry_tools.py index 65cd9c0179b..e14d7ed6987 100644 --- a/pymatgen/entries/entry_tools.py +++ b/pymatgen/entries/entry_tools.py @@ -309,7 +309,7 @@ def to_csv(self, filename: str, latexify_names: bool = False) -> None: els: set[Element] = set() for entry in self.entries: els.update(entry.composition.elements) - elements = sorted(list(els), key=lambda a: a.X) + elements = sorted(els, key=lambda a: a.X) with open(filename, "w") as f: writer = csv.writer( f, diff --git a/pymatgen/entries/mixing_scheme.py b/pymatgen/entries/mixing_scheme.py index ba37b4f26ec..d877fec9e5b 100644 --- a/pymatgen/entries/mixing_scheme.py +++ b/pymatgen/entries/mixing_scheme.py @@ -289,12 +289,11 @@ def get_adjustments(self, entry, mixing_state_data: pd.DataFrame = None): "WARNING! `mixing_state_data` DataFrame is None. No energy adjustments will be applied." ) - if not all(mixing_state_data["hull_energy_1"].notna()): - if any(mixing_state_data["entry_id_1"].notna()): - raise CompatibilityError( - f"WARNING! {self.run_type_1} entries do not form a complete PhaseDiagram." - " No energy adjustments will be applied." - ) + if not all(mixing_state_data["hull_energy_1"].notna()) and any(mixing_state_data["entry_id_1"].notna()): + raise CompatibilityError( + f"WARNING! {self.run_type_1} entries do not form a complete PhaseDiagram." + " No energy adjustments will be applied." + ) if run_type not in self.valid_rtypes_1 + self.valid_rtypes_2: raise CompatibilityError( @@ -578,8 +577,8 @@ def _get_sg(struct) -> int: row_list.append(self._populate_df_row(grp, comp, sg, n, pd_type_1, pd_type_2, all_entries)) mixing_state_data = pd.DataFrame(row_list, columns=columns) - mixing_state_data.sort_values( - ["formula", "energy_1", "spacegroup", "num_sites"], inplace=True, ignore_index=True + mixing_state_data = mixing_state_data.sort_values( + ["formula", "energy_1", "spacegroup", "num_sites"], ignore_index=True ) return mixing_state_data diff --git a/pymatgen/entries/tests/test_compatibility.py b/pymatgen/entries/tests/test_compatibility.py index 80a52dc7f46..e8ff4e9473c 100644 --- a/pymatgen/entries/tests/test_compatibility.py +++ b/pymatgen/entries/tests/test_compatibility.py @@ -560,7 +560,7 @@ def test_get_explanation_dict(self): }, ) d = compat.get_explanation_dict(entry) - assert "MPRelaxSet Potcar Correction" == d["corrections"][0]["name"] + assert d["corrections"][0]["name"] == "MPRelaxSet Potcar Correction" def test_get_corrections_dict(self): compat = MaterialsProjectCompatibility(check_potcar_hash=False) @@ -1126,7 +1126,7 @@ def test_get_explanation_dict(self): }, ) d = compat.get_explanation_dict(entry) - assert "MPRelaxSet Potcar Correction" == d["corrections"][0]["name"] + assert d["corrections"][0]["name"] == "MPRelaxSet Potcar Correction" def test_energy_adjustments(self): compat = MaterialsProject2020Compatibility(check_potcar_hash=False) @@ -1554,7 +1554,7 @@ def test_get_explanation_dict(self): }, ) d = compat.get_explanation_dict(entry) - assert "MITRelaxSet Potcar Correction" == d["corrections"][0]["name"] + assert d["corrections"][0]["name"] == "MITRelaxSet Potcar Correction" def test_msonable(self): compat_dict = self.compat.as_dict() diff --git a/pymatgen/entries/tests/test_mixing_scheme.py b/pymatgen/entries/tests/test_mixing_scheme.py index bd63cfe74f3..0e94840f815 100644 --- a/pymatgen/entries/tests/test_mixing_scheme.py +++ b/pymatgen/entries/tests/test_mixing_scheme.py @@ -220,7 +220,6 @@ def ms_complete(): """ Mixing state where we have R2SCAN for all GGA """ - gga_entries = [ ComputedStructureEntry( Structure(lattice1, ["Sn"], [[0, 0, 0]]), 0, parameters={"run_type": "GGA"}, entry_id="gga-1" @@ -1101,7 +1100,7 @@ def test_no_foreign_entries(self, mixing_scheme_no_compat, ms_complete): # process_entries should discard all GGA entries and return all R2SCAN entries = mixing_scheme_no_compat.process_entries( - ms_complete.all_entries + [foreign_entry], mixing_state_data=ms_complete.state_data + [*ms_complete.all_entries, foreign_entry], mixing_state_data=ms_complete.state_data ) assert len(entries) == 7 for e in entries: diff --git a/pymatgen/ext/matproj.py b/pymatgen/ext/matproj.py index 58cef99d65e..7d6ff739edb 100644 --- a/pymatgen/ext/matproj.py +++ b/pymatgen/ext/matproj.py @@ -278,10 +278,7 @@ def _make_request(self, sub_url, payload=None, method="GET", mp_decode=True): else: response = self.session.get(url, params=payload, verify=True) if response.status_code in [200, 400]: - if mp_decode: - data = json.loads(response.text, cls=MontyDecoder) - else: - data = json.loads(response.text) + data = json.loads(response.text, cls=MontyDecoder) if mp_decode else json.loads(response.text) if data["valid_response"]: if data.get("warning"): warnings.warn(data["warning"]) @@ -542,7 +539,7 @@ def get_entries( "potcar_symbols", "oxide_type", ] - props = ["energy", "unit_cell_formula", "task_id"] + params + props = ["energy", "unit_cell_formula", "task_id", *params] if sort_by_e_above_hull: if property_data and "e_above_hull" not in property_data: property_data.append("e_above_hull") diff --git a/pymatgen/ext/optimade.py b/pymatgen/ext/optimade.py index 8c071497418..68529ce18c4 100644 --- a/pymatgen/ext/optimade.py +++ b/pymatgen/ext/optimade.py @@ -46,30 +46,34 @@ class OptimadeRester: aliases: dict[str, str] = { "aflow": "http://aflow.org/API/optimade/", "cod": "https://www.crystallography.net/cod/optimade", - "mcloud.2dstructures": "https://aiida.materialscloud.org/2dstructures/optimade", + "mcloud.mc3d": "https://aiida.materialscloud.org/mc3d/optimade", + "mcloud.mc2d": "https://aiida.materialscloud.org/mc2d/optimade", "mcloud.2dtopo": "https://aiida.materialscloud.org/2dtopo/optimade", - "mcloud.curated-cofs": "https://aiida.materialscloud.org/curated-cofs/optimade", - "mcloud.li-ion-conductors": "https://aiida.materialscloud.org/li-ion-conductors/optimade", - "mcloud.optimade-sample": "https://aiida.materialscloud.org/optimade-sample/optimade", + "mcloud.tc-applicability": "https://aiida.materialscloud.org/tc-applicability/optimade", "mcloud.pyrene-mofs": "https://aiida.materialscloud.org/pyrene-mofs/optimade", - "mcloud.scdm": "https://aiida.materialscloud.org/autowannier/optimade", - "mcloud.sssp": "https://aiida.materialscloud.org/sssplibrary/optimade", + "mcloud.curated-cofs": "https://aiida.materialscloud.org/curated-cofs/optimade", "mcloud.stoceriaitf": "https://aiida.materialscloud.org/stoceriaitf/optimade", - "mcloud.tc-applicability": "https://aiida.materialscloud.org/tc-applicability/optimade", - "mcloud.threedd": "https://aiida.materialscloud.org/3dd/optimade", + "mcloud.scdm": "https://aiida.materialscloud.org/autowannier/optimade", + "mcloud.tin-antimony-sulfoiodide": "https://aiida.materialscloud.org/tin-antimony-sulfoiodide/optimade", + "mcloud.optimade-sample": "https://aiida.materialscloud.org/optimade-sample/optimade", "mp": "https://optimade.materialsproject.org", "mpds": "https://api.mpds.io", "nmd": "https://nomad-lab.eu/prod/rae/optimade/", "odbx": "https://optimade.odbx.science", + "odbx.odbx_misc": "https://optimade-misc.odbx.science", "omdb.omdb_production": "http://optimade.openmaterialsdb.se", "oqmd": "http://oqmd.org/optimade/", + "jarvis": "https://jarvis.nist.gov/optimade/jarvisdft", "tcod": "https://www.crystallography.net/tcod/optimade", + "twodmatpedia": "http://optimade.2dmatpedia.org", } # The set of OPTIMADE fields that are required to define a `pymatgen.core.Structure` mandatory_response_fields: set[str] = {"lattice_vectors", "cartesian_site_positions", "species", "species_at_sites"} - def __init__(self, aliases_or_resource_urls: str | list[str] | None = None, timeout: int = 5): + def __init__( + self, aliases_or_resource_urls: str | list[str] | None = None, refresh_aliases: bool = False, timeout: int = 5 + ): """ OPTIMADE is an effort to provide a standardized interface to retrieve information from many different materials science databases. @@ -83,20 +87,19 @@ def __init__(self, aliases_or_resource_urls: str | list[str] | None = None, time consider calling the APIs directly. For convenience, known OPTIMADE endpoints have been given aliases in pymatgen to save - typing the full URL. The current list of aliases is: + typing the full URL. - aflow, cod, mcloud.sssp, mcloud.2dstructures, mcloud.2dtopo, mcloud.tc-applicability, - mcloud.threedd, mcloud.scdm, mcloud.curated-cofs, mcloud.optimade-sample, mcloud.stoceriaitf, - mcloud.pyrene-mofs, mcloud.li-ion-conductors, mp, odbx, omdb.omdb_production, oqmd, tcod - - To refresh this list of aliases, generated from the current list of OPTIMADE providers - at optimade.org, call the refresh_aliases() method. + To get an up-to-date list aliases, generated from the current list of OPTIMADE providers + at optimade.org, call the refresh_aliases() method or pass refresh_aliases=True when + creating instances of this class. Args: aliases_or_resource_urls: the alias or structure resource URL or a list of aliases or resource URLs, if providing the resource URL directly it should not be an index, this interface can only currently access the "v1/structures" information from the specified resource URL + refresh_aliases: if True, use an up-to-date list of providers/aliases from the live + list of OPTIMADE providers hosted at https://providers.optimade.org. timeout: number of seconds before an attempted request is abandoned, a good timeout is useful when querying many providers, some of which may be offline """ @@ -105,6 +108,12 @@ def __init__(self, aliases_or_resource_urls: str | list[str] | None = None, time self.session = requests.Session() self._timeout = timeout # seconds + # Optionally refresh the aliases before interpreting those provided by the user + # or using potentially outdated set provided in the code + if refresh_aliases: + _logger.warning("Refreshing OPTIMADE provider aliases from https://providers.optimade.org") + self.refresh_aliases() + if isinstance(aliases_or_resource_urls, str): aliases_or_resource_urls = [aliases_or_resource_urls] @@ -172,7 +181,7 @@ def _build_filter( if elements: if isinstance(elements, str): elements = [elements] - elements_str = ", ".join(f"{el!r}" for el in elements) + elements_str = ", ".join(f'"{el}"' for el in elements) filters.append(f"(elements HAS ALL {elements_str})") if nsites: @@ -188,10 +197,10 @@ def _build_filter( filters.append(f"({nelements=})") if chemical_formula_anonymous: - filters.append(f"(chemical_formula_anonymous={chemical_formula_anonymous!r})") + filters.append(f'(chemical_formula_anonymous="{chemical_formula_anonymous}")') if chemical_formula_hill: - filters.append(f"(chemical_formula_hill={chemical_formula_anonymous!r})") + filters.append(f'(chemical_formula_hill="{chemical_formula_hill}")') return " AND ".join(filters) @@ -304,7 +313,7 @@ def get_snls_with_filter( response_fields = self._handle_response_fields(additional_response_fields) for identifier, resource in self.resources.items(): - url = join(resource, f"v1/structures?filter={optimade_filter}&{response_fields=}") + url = join(resource, f"v1/structures?filter={optimade_filter}&response_fields={response_fields}") try: json = self._get_json(url) diff --git a/pymatgen/ext/tests/test_matproj.py b/pymatgen/ext/tests/test_matproj.py index 70f505a2585..f2b60988f4c 100644 --- a/pymatgen/ext/tests/test_matproj.py +++ b/pymatgen/ext/tests/test_matproj.py @@ -71,7 +71,7 @@ def test_get_all_materials_ids_doc(self): def test_get_xas_data(self): # Test getting XAS data data = self.rester.get_xas_data("mp-19017", "Li") - assert "mp-19017,Li" == data["mid_and_el"] + assert data["mid_and_el"] == "mp-19017,Li" assert data["spectrum"]["x"][0] == pytest.approx(55.178) assert data["spectrum"]["y"][0] == pytest.approx(0.0164634) diff --git a/pymatgen/ext/tests/test_optimade.py b/pymatgen/ext/tests/test_optimade.py index 58df7fe5abd..b106f9aaccb 100644 --- a/pymatgen/ext/tests/test_optimade.py +++ b/pymatgen/ext/tests/test_optimade.py @@ -17,11 +17,11 @@ website_down = True -@unittest.skipIf( - not SETTINGS.get("PMG_MAPI_KEY") or website_down, - "PMG_MAPI_KEY environment variable not set or MP is down.", -) class OptimadeTest(PymatgenTest): + @unittest.skipIf( + not SETTINGS.get("PMG_MAPI_KEY") or website_down, + "PMG_MAPI_KEY environment variable not set or MP is down.", + ) def test_get_structures_mp(self): with OptimadeRester("mp") as optimade: structs = optimade.get_structures(elements=["Ga", "N"], nelements=2) @@ -39,6 +39,10 @@ def test_get_structures_mp(self): raw_filter_structs["mp"] ), f"Raw filter {_filter} did not return the same number of results as the query builder." + @unittest.skipIf( + not SETTINGS.get("PMG_MAPI_KEY") or website_down, + "PMG_MAPI_KEY environment variable not set or MP is down.", + ) def test_get_snls_mp(self): with OptimadeRester("mp") as optimade: structs = optimade.get_snls(elements=["Ga", "N"], nelements=2) @@ -78,3 +82,33 @@ def test_get_snls_mp(self): # optimade.refresh_aliases() # # self.assertIn("mp", optimade.aliases) + + def test_build_filter(self): + with OptimadeRester("mp") as optimade: + assert optimade._build_filter( + elements=["Ga", "N"], + nelements=2, + nsites=(1, 100), + chemical_formula_anonymous="A2B", + chemical_formula_hill="GaN", + ) == ( + '(elements HAS ALL "Ga", "N")' + " AND (nsites>=1 AND nsites<=100)" + " AND (nelements=2)" + ' AND (chemical_formula_anonymous="A2B")' + ' AND (chemical_formula_hill="GaN")' + ) + + assert optimade._build_filter( + elements=["C", "H", "O"], + nelements=(3, 4), + nsites=(1, 100), + chemical_formula_anonymous="A4B3C", + chemical_formula_hill="C4H3O", + ) == ( + '(elements HAS ALL "C", "H", "O")' + " AND (nsites>=1 AND nsites<=100)" + " AND (nelements>=3 AND nelements<=4)" + ' AND (chemical_formula_anonymous="A4B3C")' + ' AND (chemical_formula_hill="C4H3O")' + ) diff --git a/pymatgen/io/abinit/abiobjects.py b/pymatgen/io/abinit/abiobjects.py index e6aa4f52cc2..0acafc0b0d9 100644 --- a/pymatgen/io/abinit/abiobjects.py +++ b/pymatgen/io/abinit/abiobjects.py @@ -32,7 +32,6 @@ def lattice_from_abivars(cls=None, *args, **kwargs): cls: Lattice class to be instantiated. pymatgen.core.lattice.Lattice if `cls` is None Example: - lattice_from_abivars(acell=3*[10], rprim=np.eye(3)) """ cls = Lattice if cls is None else cls @@ -107,8 +106,7 @@ def structure_from_abivars(cls=None, *args, **kwargs): Args: cls: Structure class to be instantiated. pymatgen.core.structure.Structure if cls is None - example: - + Example: al_structure = structure_from_abivars( acell=3*[7.5], rprim=[0.0, 0.5, 0.5, @@ -176,7 +174,6 @@ def species_by_znucl(structure: Structure) -> list[Species]: Return list of unique specie found in structure **ordered according to sites**. Example: - Site0: 0.5 0 0 O Site1: 0 0 0 Si @@ -255,13 +252,13 @@ def structure_to_abivars(structure, enforce_znucl=None, enforce_typat=None, **kw xred = np.where(np.abs(xred) > 1e-8, xred, 0.0) # Info on atoms. - d = dict( - natom=natom, - ntypat=ntypat, - typat=typat, - znucl=znucl_type, - xred=xred, - ) + d = { + "natom": natom, + "ntypat": ntypat, + "typat": typat, + "znucl": znucl_type, + "xred": xred, + } # Add info on the lattice. # Should we use (rprim, acell) or (angdeg, acell) to specify the lattice? @@ -520,18 +517,18 @@ class ElectronsAlgorithm(dict, AbivarAble, MSONable): """Variables controlling the SCF/NSCF algorithm.""" # None indicates that we use abinit defaults. - _DEFAULT = dict( - iprcell=None, - iscf=None, - diemac=None, - diemix=None, - diemixmag=None, - dielam=None, - diegap=None, - dielng=None, - diecut=None, - nstep=50, - ) + _DEFAULT = { + "iprcell": None, + "iscf": None, + "diemac": None, + "diemix": None, + "diemixmag": None, + "dielam": None, + "diegap": None, + "dielng": None, + "diecut": None, + "nstep": 50, + } def __init__(self, *args, **kwargs): """Initialize object.""" @@ -1055,7 +1052,6 @@ class RelaxationMethod(AbivarAble, MSONable): def __init__(self, *args, **kwargs): """Initialize object.""" - # Initialize abivars with the default values. self.abivars = self._default_vars @@ -1532,20 +1528,20 @@ def symsigma(self): def to_abivars(self): """Returns a dictionary with the abinit variables.""" - abivars = dict( - gwcalctyp=self.gwcalctyp, - ecuteps=self.ecuteps, - ecutsigx=self.ecutsigx, - symsigma=self.symsigma, - gw_qprange=self.gw_qprange, - gwpara=self.gwpara, - optdriver=self.optdriver, - nband=self.nband + abivars = { + "gwcalctyp": self.gwcalctyp, + "ecuteps": self.ecuteps, + "ecutsigx": self.ecutsigx, + "symsigma": self.symsigma, + "gw_qprange": self.gw_qprange, + "gwpara": self.gwpara, + "optdriver": self.optdriver, + "nband": self.nband # "ecutwfn" : self.ecutwfn, # "kptgw" : self.kptgw, # "nkptgw" : self.nkptgw, # "bdgw" : self.bdgw, - ) + } # FIXME: problem with the spin # assert len(self.bdgw) == self.nkptgw @@ -1670,22 +1666,22 @@ def use_direct_diago(self): def to_abivars(self): """Returns a dictionary with the abinit variables.""" - abivars = dict( - bs_calctype=1, - bs_loband=self.bs_loband, + abivars = { + "bs_calctype": 1, + "bs_loband": self.bs_loband, # nband=self.nband, - mbpt_sciss=self.mbpt_sciss, - ecuteps=self.ecuteps, - bs_algorithm=self._ALGO2VAR[self.algo], - bs_coulomb_term=21, - mdf_epsinf=self.mdf_epsinf, - bs_exchange_term=1 if self.with_lf else 0, - inclvkb=self.inclvkb, - zcut=self.zcut, - bs_freq_mesh=self.bs_freq_mesh, - bs_coupling=self._EXC_TYPES[self.exc_type], - optdriver=self.optdriver, - ) + "mbpt_sciss": self.mbpt_sciss, + "ecuteps": self.ecuteps, + "bs_algorithm": self._ALGO2VAR[self.algo], + "bs_coulomb_term": 21, + "mdf_epsinf": self.mdf_epsinf, + "bs_exchange_term": 1 if self.with_lf else 0, + "inclvkb": self.inclvkb, + "zcut": self.zcut, + "bs_freq_mesh": self.bs_freq_mesh, + "bs_coupling": self._EXC_TYPES[self.exc_type], + "optdriver": self.optdriver, + } if self.use_haydock: # FIXME diff --git a/pymatgen/io/abinit/abitimer.py b/pymatgen/io/abinit/abitimer.py index 4b0d9e755cb..2d5ae84749c 100644 --- a/pymatgen/io/abinit/abitimer.py +++ b/pymatgen/io/abinit/abitimer.py @@ -557,7 +557,7 @@ def totable(self, stop=None, reverse=True): osects = osects[:stop] n = len(self.filenames) - table = [["AbinitTimerSection"] + alternate(self.filenames, n * ["%"])] + table = [["AbinitTimerSection", *alternate(self.filenames, n * ["%"])]] for sect_name in osects: peff = self[sect_name]["wall_time"] fract = self[sect_name]["wall_fract"] diff --git a/pymatgen/io/abinit/inputs.py b/pymatgen/io/abinit/inputs.py index 1050f453625..2d5e8f6dded 100644 --- a/pymatgen/io/abinit/inputs.py +++ b/pymatgen/io/abinit/inputs.py @@ -109,9 +109,9 @@ # Default values used if user does not specify them -_DEFAULTS = dict( - kppa=1000, -) +_DEFAULTS = { + "kppa": 1000, +} def as_structure(obj): @@ -560,10 +560,9 @@ def calc_shiftk(structure, symprec: float = 0.01, angle_tolerance=5): shiftk = [0.0, 0.0, 0.0] shiftk[hex_ax] = 0.5 - elif lattice_type == "tetragonal": - if "I" in spg_symbol: - # BCT - shiftk = [0.25, 0.25, 0.25, -0.25, -0.25, -0.25] + elif lattice_type == "tetragonal" and "I" in spg_symbol: + # BCT + shiftk = [0.25, 0.25, 0.25, -0.25, -0.25, -0.25] if shiftk is None: # Use default value. @@ -637,7 +636,6 @@ def set_vars(self, *args, **kwargs): Return dict with the variables added to the input. Example: - input.set_vars(ecut=10, ionmov=3) """ kwargs.update(dict(*args)) @@ -651,7 +649,6 @@ def set_vars_ifnotin(self, *args, **kwargs): Return dict with the variables added to the input. Example: - input.set_vars(ecut=10, ionmov=3) """ kwargs.update(dict(*args)) diff --git a/pymatgen/io/abinit/pseudos.py b/pymatgen/io/abinit/pseudos.py index e787207ec00..79d50101629 100644 --- a/pymatgen/io/abinit/pseudos.py +++ b/pymatgen/io/abinit/pseudos.py @@ -1744,10 +1744,7 @@ def is_complete(self, zmax=118) -> bool: """ True if table is complete i.e. all elements with Z < zmax have at least on pseudopotential """ - for z in range(1, zmax): - if not self[z]: - return False - return True + return all(self[z] for z in range(1, zmax)) def all_combinations_for_elements(self, element_symbols): """ diff --git a/pymatgen/io/abinit/tests/test_abiobjects.py b/pymatgen/io/abinit/tests/test_abiobjects.py index 60c347ea5ab..a950afca975 100644 --- a/pymatgen/io/abinit/tests/test_abiobjects.py +++ b/pymatgen/io/abinit/tests/test_abiobjects.py @@ -81,7 +81,6 @@ def test_rprim_acell(self): def test_znucl_typat(self): """Test the order of typat and znucl in the Abinit input and enforce_typat, enforce_znucl.""" - # Ga Ga1 1 0.33333333333333 0.666666666666667 0.500880 1.0 # Ga Ga2 1 0.66666666666667 0.333333333333333 0.000880 1.0 # N N3 1 0.333333333333333 0.666666666666667 0.124120 1.0 diff --git a/pymatgen/io/abinit/tests/test_inputs.py b/pymatgen/io/abinit/tests/test_inputs.py index 3cb6f13d936..4950ec2f2bf 100644 --- a/pymatgen/io/abinit/tests/test_inputs.py +++ b/pymatgen/io/abinit/tests/test_inputs.py @@ -127,15 +127,15 @@ def test_input_errors(self): with pytest.raises(BasicAbinitInput.Error): BasicAbinitInput(si_structure, pseudos=abiref_file("H-wdr.oncvpsp")) - si1_negative_volume = dict( - ntypat=1, - natom=1, - typat=[1], - znucl=14, - acell=3 * [7.60], - rprim=[[0.0, 0.5, 0.5], [-0.5, -0.0, -0.5], [0.5, 0.5, 0.0]], - xred=[[0.0, 0.0, 0.0]], - ) + si1_negative_volume = { + "ntypat": 1, + "natom": 1, + "typat": [1], + "znucl": 14, + "acell": 3 * [7.60], + "rprim": [[0.0, 0.5, 0.5], [-0.5, -0.0, -0.5], [0.5, 0.5, 0.0]], + "xred": [[0.0, 0.0, 0.0]], + } # Negative triple product. with pytest.raises(BasicAbinitInput.Error): @@ -216,7 +216,7 @@ def test_api(self): inp.write(filepath=filepath) multi.write(filepath=filepath) - new_multi = BasicMultiDataset.from_inputs([inp for inp in multi]) + new_multi = BasicMultiDataset.from_inputs(list(multi)) assert new_multi.ndtset == multi.ndtset assert new_multi.structure == multi.structure diff --git a/pymatgen/io/abinit/variable.py b/pymatgen/io/abinit/variable.py index 3a072fed8e8..30d49f18993 100644 --- a/pymatgen/io/abinit/variable.py +++ b/pymatgen/io/abinit/variable.py @@ -57,7 +57,7 @@ def __init__(self, name, value, units="", valperline=3): def get_value(self): """Return the value.""" if self.units: - return list(self.value) + [self.units] + return [*self.value, self.units] return self.value @property diff --git a/pymatgen/io/adf.py b/pymatgen/io/adf.py index e8a6e12610e..1c18d322ad0 100644 --- a/pymatgen/io/adf.py +++ b/pymatgen/io/adf.py @@ -26,7 +26,7 @@ def is_numeric(s) -> bool: s : str A string. - Returns + Returns: ------- res : bool If True, ``s`` is a numeric string and can be converted to an int or a @@ -120,7 +120,7 @@ def __init__(self, name, options=None, subkeys=None): subkeys : Sized The subkeys for this key. - Raises + Raises: ------ ValueError If elements in ``subkeys`` are not ``AdfKey`` objects. @@ -170,7 +170,7 @@ def __str__(self): """ Return the string representation of this ``AdfKey``. - Notes + Notes: ----- If this key is 'Atoms' and the coordinates are in Cartesian form, a different string format will be used. @@ -211,7 +211,7 @@ def has_subkey(self, subkey): subkey : str or AdfKey A key name or an AdfKey object. - Returns + Returns: ------- has : bool True if this key contains the given key. Otherwise False. @@ -222,9 +222,8 @@ def has_subkey(self, subkey): key = subkey.key else: raise ValueError("The subkey should be an AdfKey or a string!") - if len(self.subkeys) > 0: - if key in map(lambda k: k.key, self.subkeys): - return True + if len(self.subkeys) > 0 and key in (k.key for k in self.subkeys): + return True return False def add_subkey(self, subkey): @@ -236,7 +235,7 @@ def add_subkey(self, subkey): subkey : AdfKey A new subkey. - Notes + Notes: ----- Duplicate check will not be performed if this is an 'Atoms' block. """ @@ -269,7 +268,7 @@ def add_option(self, option): A new option to add. This must have the same format with existing options. - Raises + Raises: ------ TypeError If the format of the given ``option`` is different. @@ -291,7 +290,7 @@ def remove_option(self, option): option : str or int The name (str) or index (int) of the option to remove. - Raises + Raises: ------ TypeError If the option has a wrong type. @@ -318,17 +317,14 @@ def has_option(self, option): option : str The option. - Returns + Returns: ------- has : bool True if the option can be found. Otherwise False will be returned. """ if len(self.options) == 0: return False - for op in self.options: - if (self._sized_op and op[0] == option) or (op == option): - return True - return False + return any(self._sized_op and op[0] == option or op == option for op in self.options) def as_dict(self): """ @@ -357,7 +353,7 @@ def from_dict(cls, d): d : dict A dict of saved attributes. - Returns + Returns: ------- adfkey : AdfKey An AdfKey object recovered from the JSON dict ``d``. @@ -365,10 +361,7 @@ def from_dict(cls, d): key = d.get("name") options = d.get("options", None) subkey_list = d.get("subkeys", []) - if len(subkey_list) > 0: - subkeys = list(map(lambda k: AdfKey.from_dict(k), subkey_list)) - else: - subkeys = None + subkeys = [AdfKey.from_dict(k) for k in subkey_list] if len(subkey_list) > 0 else None return cls(key, options, subkeys) @staticmethod @@ -381,18 +374,18 @@ def from_string(string): string : str A string. - Returns + Returns: ------- adfkey : AdfKey An AdfKey object recovered from the string. - Raises + Raises: ------ ValueError Currently nested subkeys are not supported. If ``subend`` was found a ValueError would be raised. - Notes + Notes: ----- Only the first block key will be returned. """ @@ -403,10 +396,7 @@ def is_float(s) -> bool: if string.find("\n") == -1: el = string.split() if len(el) > 1: - if string.find("=") != -1: - options = list(map(lambda s: s.split("="), el[1:])) - else: - options = el[1:] + options = [s.split("=") for s in el[1:]] if string.find("=") != -1 else el[1:] for i, op in enumerate(options): if isinstance(op, list) and is_numeric(op[1]): op[1] = float(op[1]) if is_float(op[1]) else int(op[1]) @@ -443,7 +433,7 @@ class AdfTask(MSONable): """ Basic task for ADF. All settings in this class are independent of molecules. - Notes + Notes: ----- Unlike other quantum chemistry packages (NWChem, Gaussian, ...), ADF does not support calculating force/gradient. @@ -546,7 +536,7 @@ def _setup_task(self, geo_subkeys): geo_subkeys : Sized User-defined subkeys for the block 'Geometry'. - Notes + Notes: ----- Most of the run types of ADF are specified in the Geometry block except the 'AnalyticFreq'. @@ -608,7 +598,7 @@ def from_dict(cls, d): d : dict A dict of saved attributes. - Returns + Returns: ------- task : AdfTask An AdfTask object recovered from the JSON dict ``d``. @@ -682,7 +672,7 @@ class AdfOutput: """ A basic ADF output file parser. - Attributes + Attributes: ---------- is_failed : bool True is the ADF job is terminated without success. Otherwise False. @@ -761,7 +751,7 @@ def _sites_to_mol(sites): sites : list A list of sites. - Returns + Returns: ------- mol : Molecule A ``Molecule`` object. diff --git a/pymatgen/io/ase.py b/pymatgen/io/ase.py index e718cc580e0..cc7ad1b4742 100644 --- a/pymatgen/io/ase.py +++ b/pymatgen/io/ase.py @@ -85,14 +85,8 @@ def get_atoms(structure, **kwargs): initial_charges = structure.site_properties["charge"] atoms.set_initial_charges(initial_charges) - if "final_magmom" in structure.site_properties: - magmoms = structure.site_properties["final_magmom"] - else: - magmoms = None - if "final_charge" in structure.site_properties: - charges = structure.site_properties["final_charge"] - else: - charges = None + magmoms = structure.site_properties["final_magmom"] if "final_magmom" in structure.site_properties else None + charges = structure.site_properties["final_charge"] if "final_charge" in structure.site_properties else None if magmoms or charges: if magmoms and charges: calc = SinglePointDFTCalculator(atoms, **{"magmoms": magmoms, "charges": charges}) @@ -163,18 +157,9 @@ def get_structure(atoms, cls=None, **cls_kwargs): magmoms = None charges = None - if atoms.has("initial_magmoms"): - initial_magmoms = atoms.get_initial_magnetic_moments() - else: - initial_magmoms = None - if atoms.has("initial_charges"): - initial_charges = atoms.get_initial_charges() - else: - initial_charges = None - if atoms.has("oxi_states"): - oxi_states = atoms.get_array("oxi_states") - else: - oxi_states = None + initial_magmoms = atoms.get_initial_magnetic_moments() if atoms.has("initial_magmoms") else None + initial_charges = atoms.get_initial_charges() if atoms.has("initial_charges") else None + oxi_states = atoms.get_array("oxi_states") if atoms.has("oxi_states") else None # If the ASE Atoms object has constraints, make sure that they are of the # kind FixAtoms, which are the only ones that can be supported in Pymatgen. @@ -255,14 +240,8 @@ def get_molecule(atoms, cls=None, **cls_kwargs): """ cls = Molecule if cls is None else cls molecule = AseAtomsAdaptor.get_structure(atoms, cls=cls, **cls_kwargs) - if atoms.has("initial_charges"): - charge = round(np.sum(atoms.get_initial_charges())) - else: - charge = 0 - if atoms.has("initial_magmoms"): - mult = round(np.sum(atoms.get_initial_magnetic_moments())) + 1 - else: - mult = 1 + charge = round(np.sum(atoms.get_initial_charges())) if atoms.has("initial_charges") else 0 + mult = round(np.sum(atoms.get_initial_magnetic_moments())) + 1 if atoms.has("initial_magmoms") else 1 molecule.set_charge_and_spin(charge, spin_multiplicity=mult) return molecule diff --git a/pymatgen/io/cif.py b/pymatgen/io/cif.py index 60a3c8a1e27..7e2359759e3 100644 --- a/pymatgen/io/cif.py +++ b/pymatgen/io/cif.py @@ -139,10 +139,7 @@ def _format_field(self, v): if v == "": return '""' if (" " in v or v[0] == "_") and not (v[0] == "'" and v[-1] == "'") and not (v[0] == '"' and v[-1] == '"'): - if "'" in v: - q = '"' - else: - q = "'" + q = '"' if "'" in v else "'" v = q + v + q return v @@ -1001,7 +998,7 @@ def get_matching_coord(coord): coord_to_magmoms[match] = None sum_occu = [ - sum(c.values()) for c in coord_to_species.values() if not set(c.elements) == {Element("O"), Element("H")} + sum(c.values()) for c in coord_to_species.values() if set(c.elements) != {Element("O"), Element("H")} ] if any(o > 1 for o in sum_occu): msg = ( @@ -1032,7 +1029,7 @@ def get_matching_coord(coord): if coord_to_species.items(): for idx, (comp, group) in enumerate( groupby( - sorted(list(coord_to_species.items()), key=lambda x: x[1]), + sorted(coord_to_species.items(), key=lambda x: x[1]), key=lambda x: x[1], ) ): @@ -1208,9 +1205,8 @@ def get_bibtex_string(self): # convert to bibtex author format ('and' delimited) if "author" in bibtex_entry: # separate out semicolon authors - if isinstance(bibtex_entry["author"], str): - if ";" in bibtex_entry["author"]: - bibtex_entry["author"] = bibtex_entry["author"].split(";") + if isinstance(bibtex_entry["author"], str) and ";" in bibtex_entry["author"]: + bibtex_entry["author"] = bibtex_entry["author"].split(";") if isinstance(bibtex_entry["author"], list): bibtex_entry["author"] = " and ".join(bibtex_entry["author"]) diff --git a/pymatgen/io/common.py b/pymatgen/io/common.py index d101b373f4c..67749d669a9 100644 --- a/pymatgen/io/common.py +++ b/pymatgen/io/common.py @@ -74,7 +74,7 @@ def __init__(self, structure: Structure, data, distance_matrix=None, data_aug=No self.ngridpts = self.dim[0] * self.dim[1] * self.dim[2] # lazy init the spin data since this is not always needed. self._spin_data: dict[Spin, float] = {} - self._distance_matrix = {} if not distance_matrix else distance_matrix + self._distance_matrix = distance_matrix if distance_matrix else {} self.xpoints = np.linspace(0.0, 1.0, num=self.dim[0]) self.ypoints = np.linspace(0.0, 1.0, num=self.dim[1]) self.zpoints = np.linspace(0.0, 1.0, num=self.dim[2]) diff --git a/pymatgen/io/core.py b/pymatgen/io/core.py index 33c3875e39c..707a206e722 100644 --- a/pymatgen/io/core.py +++ b/pymatgen/io/core.py @@ -179,9 +179,8 @@ def write_input( for fname, contents in self.inputs.items(): file = path / fname - if not path.exists(): - if make_dir: - path.mkdir(parents=True, exist_ok=True) + if not path.exists() and make_dir: + path.mkdir(parents=True, exist_ok=True) if file.exists() and not overwrite: raise FileExistsError(f"File {str(fname)} already exists!") diff --git a/pymatgen/io/cp2k/inputs.py b/pymatgen/io/cp2k/inputs.py index 512f030bc10..eaa58a62a8f 100644 --- a/pymatgen/io/cp2k/inputs.py +++ b/pymatgen/io/cp2k/inputs.py @@ -109,9 +109,8 @@ def __eq__(self, other: object) -> bool: if self.name.upper() == other.name.upper(): v1 = [_.upper() if isinstance(_, str) else _ for _ in self.values] v2 = [_.upper() if isinstance(_, str) else _ for _ in other.values] - if v1 == v2: - if self.units == self.units: - return True + if v1 == v2 and self.units == self.units: + return True return False def __add__(self, other): @@ -1897,10 +1896,7 @@ def f3(x): n_beta = [] unpaired_orbital = None while tmp: - if tmp > 0: - tmp2 = -min((esv[0][2], tmp)) - else: - tmp2 = min((f2(esv[0][1]) - esv[0][2], -tmp)) + tmp2 = -min((esv[0][2], tmp)) if tmp > 0 else min((f2(esv[0][1]) - esv[0][2], -tmp)) l_alpha.append(esv[0][1]) l_beta.append(esv[0][1]) nel_alpha.append(tmp2) @@ -2317,10 +2313,7 @@ def softmatch(self, other): return False d1 = self.as_dict() d2 = other.as_dict() - for k, v in d1.items(): - if v is not None and v != d2[k]: - return False - return True + return all(not (v is not None and v != d2[k]) for k, v in d1.items()) @classmethod def from_string(cls, string: str) -> BasisInfo: @@ -2419,10 +2412,7 @@ def softmatch(self, other): other_names = [other.name] if other.alias_names: other_names.extend(other.alias_names) - for nm in this_names: - if nm is not None and nm not in other_names: - return False - return True + return all(not (nm is not None and nm not in other_names) for nm in this_names) def get_hash(self) -> str: """Get a hash of this object""" @@ -2541,7 +2531,7 @@ def from_string(cls, string: str) -> GaussianTypeOrbitalBasisSet: _info[k] = v info = BasisInfo.from_dict(_info) potential: Literal["All Electron", "Pseudopotential"] - if any("ALL" in x for x in [name] + aliases): + if any("ALL" in x for x in [name, *aliases]): info.electrons = element.Z potential = "All Electron" else: @@ -2624,10 +2614,7 @@ def softmatch(self, other): return False d1 = self.as_dict() d2 = other.as_dict() - for k, v in d1.items(): - if v is not None and v != d2[k]: - return False - return True + return all(not (v is not None and v != d2[k]) for k, v in d1.items()) @classmethod def from_string(cls, string): @@ -2787,7 +2774,7 @@ def from_string(cls, string): info[k] = v info = PotentialInfo.from_dict(info) potential: Literal["All Electron", "Pseudopotential"] - if any("ALL" in x for x in [name] + aliases): + if any("ALL" in x for x in [name, *aliases]): potential = "All Electron" info.electrons = Element(element).Z else: @@ -2820,7 +2807,7 @@ def from_string(cls, string): L = 1 i += 1 - while L < nprj_ppnl[l]: + while nprj_ppnl[l] > L: line2 = list(map(float, lines[i].split())) hprj_ppnl[l][L] = {j: float(ln) for j, ln in enumerate(line2)} i += 1 diff --git a/pymatgen/io/cp2k/outputs.py b/pymatgen/io/cp2k/outputs.py index 830405e8fd2..d3ec2d0d065 100644 --- a/pymatgen/io/cp2k/outputs.py +++ b/pymatgen/io/cp2k/outputs.py @@ -243,9 +243,8 @@ def is_metal(self) -> bool: def is_hubbard(self) -> bool: """Returns True if hubbard +U correction was used""" for v in self.data.get("atomic_kind_info", {}).values(): - if "DFT_PLUS_U" in v: - if v.get("DFT_PLUS_U").get("U_MINUS_J") > 0: - return True + if "DFT_PLUS_U" in v and v.get("DFT_PLUS_U").get("U_MINUS_J") > 0: + return True return False def parse_files(self): @@ -1532,10 +1531,7 @@ def parse_chi_tensor(self, chi_filename=None): elif "LOCAL" in first: dat = "chi_local" elif "Total" in first: - if "ppm" in first: - dat = "chi_total_ppm_cgs" - else: - dat = "chi_total" + dat = "chi_total_ppm_cgs" if "ppm" in first else "chi_total" elif first.startswith("PV1"): splt = [postprocessor(s) for s in line.split()] splt = [s for s in splt if isinstance(s, float)] @@ -1701,10 +1697,7 @@ def read_table_pattern( processed_line = [postprocess(v) for v in ml.groups()] table_contents.append(processed_line) tables.append(table_contents) - if last_one_only: - retained_data = tables[-1] - else: - retained_data = tables + retained_data = tables[-1] if last_one_only else tables if attribute_name is not None: self.data[attribute_name] = retained_data return retained_data @@ -1808,10 +1801,7 @@ def parse_pdos(dos_file=None, spin_channel=None, total=False): DOS object is not created here """ - if spin_channel: - spin = Spin(spin_channel) - else: - spin = Spin.down if "BETA" in os.path.split(dos_file)[-1] else Spin.up + spin = Spin(spin_channel) if spin_channel else Spin.down if "BETA" in os.path.split(dos_file)[-1] else Spin.up with zopen(dos_file, "rt") as f: lines = f.readlines() diff --git a/pymatgen/io/cp2k/sets.py b/pymatgen/io/cp2k/sets.py index 421e23f2544..cda04acb174 100644 --- a/pymatgen/io/cp2k/sets.py +++ b/pymatgen/io/cp2k/sets.py @@ -510,12 +510,11 @@ def match_elecs(x): else: raise ValueError("No explicit basis found and matching has failed.") - if aux_basis is None: - if basis_and_potential.get(el, {}).get("aux_basis"): - warnings.warn( - f"Unable to validate auxiliary basis for {el}. Exact name provided will be put in input file." - ) - aux_basis = basis_and_potential[el].get("aux_basis") + if aux_basis is None and basis_and_potential.get(el, {}).get("aux_basis"): + warnings.warn( + f"Unable to validate auxiliary basis for {el}. Exact name provided will be put in input file." + ) + aux_basis = basis_and_potential[el].get("aux_basis") if potential is None: if basis_and_potential.get(el, {}).get("potential"): @@ -1352,10 +1351,9 @@ def validate(self): raise Cp2kValidationError("Does not support hartree fock with kpoints") for _, v in self["force_eval"]["subsys"].subsections.items(): - if v.name.upper() == "KIND": - if v["POTENTIAL"].values[0].upper() == "ALL": - if self["force_eval"]["dft"]["qs"]["method"].values[0].upper() != "GAPW": - raise Cp2kValidationError("All electron basis sets require GAPW method") + if v.name.upper() == "KIND" and v["POTENTIAL"].values[0].upper() == "ALL": + if self["force_eval"]["dft"]["qs"]["method"].values[0].upper() != "GAPW": + raise Cp2kValidationError("All electron basis sets require GAPW method") class StaticSet(DftSet): diff --git a/pymatgen/io/exciting/inputs.py b/pymatgen/io/exciting/inputs.py index ff84bfdc2bb..209bdea416b 100644 --- a/pymatgen/io/exciting/inputs.py +++ b/pymatgen/io/exciting/inputs.py @@ -130,16 +130,10 @@ def from_string(data): cartesian = False # get the scale attribute scale_in = root.find("structure").find("crystal").get("scale") - if scale_in: - scale = float(scale_in) * ExcitingInput.bohr2ang - else: - scale = ExcitingInput.bohr2ang + scale = float(scale_in) * ExcitingInput.bohr2ang if scale_in else ExcitingInput.bohr2ang # get the stretch attribute stretch_in = root.find("structure").find("crystal").get("stretch") - if stretch_in: - stretch = np.array([float(a) for a in stretch_in]) - else: - stretch = np.array([1.0, 1.0, 1.0]) + stretch = np.array([float(a) for a in stretch_in]) if stretch_in else np.array([1.0, 1.0, 1.0]) # get basis vectors and scale them accordingly basisnode = root.find("structure").find("crystal").iter("basevect") for vect in basisnode: diff --git a/pymatgen/io/feff/inputs.py b/pymatgen/io/feff/inputs.py index 92ab779fb81..63772f5c864 100644 --- a/pymatgen/io/feff/inputs.py +++ b/pymatgen/io/feff/inputs.py @@ -723,12 +723,10 @@ def from_file(filename="feff.inp"): ieels_max = ieels + 5 else: params[key] = val - if ieels >= 0: - if ieels <= i <= ieels_max: - if i == ieels + 1: - if int(line.split()[1]) == 1: - ieels_max -= 1 - eels_params.append(line) + if ieels >= 0 and ieels <= i <= ieels_max: + if i == ieels + 1 and int(line.split()[1]) == 1: + ieels_max -= 1 + eels_params.append(line) if eels_params: if len(eels_params) == 6: @@ -823,9 +821,8 @@ def diff(self, other): else: similar_param[k1] = v1 for k2, v2 in other.items(): - if k2 not in similar_param and k2 not in different_param: - if k2 not in self: - different_param[k2] = {"FEFF_TAGS1": "Default", "FEFF_TAGS2": v2} + if k2 not in similar_param and k2 not in different_param and k2 not in self: + different_param[k2] = {"FEFF_TAGS1": "Default", "FEFF_TAGS2": v2} return {"Same": similar_param, "Different": different_param} def __add__(self, other): @@ -1063,9 +1060,8 @@ def get_atom_map(structure, absorbing_atom=None): # if there is only a single absorbing atom in the structure, # it should be excluded from this list - if absorbing_atom: - if len(structure.indices_from_symbol(absorbing_atom)) == 1: - unique_pot_atoms.remove(absorbing_atom) + if absorbing_atom and len(structure.indices_from_symbol(absorbing_atom)) == 1: + unique_pot_atoms.remove(absorbing_atom) atom_map = {} for i, atom in enumerate(unique_pot_atoms): diff --git a/pymatgen/io/feff/outputs.py b/pymatgen/io/feff/outputs.py index e1dd1e1443b..4ed7b932a19 100644 --- a/pymatgen/io/feff/outputs.py +++ b/pymatgen/io/feff/outputs.py @@ -262,13 +262,13 @@ class Xmu(MSONable): Parser for data in 'xmu.dat' file. The file 'xmu.dat' contains XANES, EXAFS or NRIXS data depending on the situation; \\mu, \\mu_0, and \\chi = \\chi * \\mu_0/ \\mu_0/(edge+50eV) as - functions of absolute energy E, relative energy E − E_f and wave number k. + functions of absolute energy E, relative energy E - E_f and wave number k. Default attributes: xmu: Photon absorption cross section of absorbing atom in material Energies: Energies of data point relative_energies: E - E_fermi - wavenumber: k=\\sqrt(E −E_fermi) + wavenumber: k=\\sqrt(E -E_fermi) mu: The total absorption cross-section. mu0: The embedded atomic background absorption. chi: fine structure. diff --git a/pymatgen/io/fiesta.py b/pymatgen/io/fiesta.py index bb54dcf941f..421d20d8b60 100644 --- a/pymatgen/io/fiesta.py +++ b/pymatgen/io/fiesta.py @@ -591,6 +591,7 @@ def from_string(cls, string_input): Args: string_input: string_input to parse. + Returns: FiestaInput object """ diff --git a/pymatgen/io/gaussian.py b/pymatgen/io/gaussian.py index 166b946a071..10eea0db6ca 100644 --- a/pymatgen/io/gaussian.py +++ b/pymatgen/io/gaussian.py @@ -41,7 +41,7 @@ def read_route_line(route): Args: route (str) : the route line - return + Return: functional (str) : the method (HF, PBE ...) basis_set (str) : the basis set route (dict) : dictionary of parameters @@ -66,10 +66,7 @@ def read_route_line(route): route_params[m.group(1)] = m.group(2) elif tok.upper() in ["#", "#N", "#P", "#T"]: # does not store # in route to avoid error in input - if tok == "#": - dieze_tag = "#N" - else: - dieze_tag = tok + dieze_tag = "#N" if tok == "#" else tok continue else: m = re.match(multi_params_patt, tok.strip("#")) @@ -543,7 +540,6 @@ class GaussianOutput: Still in early beta. Attributes: - .. attribute:: structures All structures from the calculation in the standard orientation. If the @@ -714,7 +710,6 @@ class GaussianOutput: that are printed using `pop=NBOREAD` and `$nbo bndidx $end`. Methods: - .. method:: to_input() Return a GaussianInput object using the last geometry and the same @@ -1023,7 +1018,7 @@ def _parse(self, filename): frequencies[ifreq]["symmetry"] = sym line = f.readline() - #  read normal modes + # read normal modes line = f.readline() while normal_mode_patt.search(line): values = list(map(float, float_patt.findall(line))) @@ -1036,8 +1031,8 @@ def _parse(self, filename): frequencies = [] elif parse_hessian: - #  read Hessian matrix under "Force constants in Cartesian coordinates" - #  Hessian matrix is in the input orientation framework + # read Hessian matrix under "Force constants in Cartesian coordinates" + # Hessian matrix is in the input orientation framework # WARNING : need #P in the route line parse_hessian = False ndf = 3 * len(input_structures[0]) @@ -1141,7 +1136,7 @@ def _parse(self, filename): while not resume_end_patt.search(line): resume.append(line) line = f.readline() - #  security if \\@ not in one line ! + # security if \\@ not in one line ! if line == "\n": break resume.append(line) @@ -1210,7 +1205,7 @@ def as_dict(self): d["errors"] = self.errors d["Mulliken_charges"] = self.Mulliken_charges - unique_symbols = sorted(list(d["unit_cell_formula"])) + unique_symbols = sorted(d["unit_cell_formula"]) d["elements"] = unique_symbols d["nelements"] = len(unique_symbols) d["charge"] = self.charge @@ -1248,7 +1243,6 @@ def read_scan(self): Read a potential energy surface from a gaussian scan calculation. Returns: - A dict: {"energies": [ values ], "coords": {"d1": [ values ], "A2", [ values ], ... }} @@ -1354,7 +1348,6 @@ def read_excitation_energies(self): Read a excitation energies after a TD-DFT calculation. Returns: - A list: A list of tuple for each transition such as [(energie (eV), lambda (nm), oscillatory strength), ... ] """ @@ -1368,10 +1361,9 @@ def read_excitation_energies(self): if re.search(r"^\sExcitation energies and oscillator strengths:", line): td = True - if td: - if re.search(r"^\sExcited State\s*\d", line): - val = [float(v) for v in float_patt.findall(line)] - transitions.append(tuple(val[0:3])) + if td and re.search(r"^\sExcited State\s*\d", line): + val = [float(v) for v in float_patt.findall(line)] + transitions.append(tuple(val[0:3])) line = f.readline() return transitions @@ -1464,7 +1456,7 @@ def to_input( the output file and with the same calculation parameters. Arguments are the same as GaussianInput class. - Returns + Returns: gaunip (GaussianInput) : the gaussian input object """ if not mol: diff --git a/pymatgen/io/lammps/data.py b/pymatgen/io/lammps/data.py index 6240a2af1d2..9da44158107 100644 --- a/pymatgen/io/lammps/data.py +++ b/pymatgen/io/lammps/data.py @@ -308,7 +308,7 @@ def structure(self): masses = self.masses atoms = self.atoms.copy() if "nx" in atoms.columns: - atoms.drop(["nx", "ny", "nz"], axis=1, inplace=True) + atoms = atoms.drop(["nx", "ny", "nz"], axis=1) atoms["molecule-ID"] = 1 ld_copy = self.__class__(self.box, masses, atoms) topologies = ld_copy.disassemble()[-1] @@ -389,7 +389,7 @@ def get_string(self, distance=6, velocity=8, charge=4, hybrid=True): right_indent = len(str(max(all_stats))) count_lines = [f"{v:>{right_indent}} {k}" for k, v in counts.items()] type_lines = [f"{v:>{right_indent}} {k+ ' types'}" for k, v in types.items()] - stats = "\n".join(count_lines + [""] + type_lines) + stats = "\n".join([*count_lines, "", *type_lines]) def map_coords(q): return f"{q:.{distance}f}" @@ -630,7 +630,7 @@ def label_topo(t): for kw in SECTION_KEYWORDS["topology"]: if data.get(kw): topologies[kw] = (np.array(data[kw]) - shift).tolist() - topologies = None if not topologies else topologies + topologies = topologies if topologies else None topo_list.append( Topology( sites=m, @@ -717,9 +717,9 @@ def parse_section(sec_lines): df.columns = names if sort_id: sort_by = "id" if kw != "PairIJ Coeffs" else ["id1", "id2"] - df.sort_values(sort_by, inplace=True) + df = df.sort_values(sort_by) if "id" in df.columns: - df.set_index("id", drop=True, inplace=True) + df = df.set_index("id", drop=True) df.index.name = None return kw, df @@ -774,7 +774,7 @@ def from_ff_and_topologies(cls, box, ff, topologies, atom_style="full"): atom_types = set.union(*(t.species for t in topologies)) assert atom_types.issubset(ff.maps["Atoms"]), "Unknown atom type found in topologies" - items = dict(box=box, atom_style=atom_style, masses=ff.masses, force_field=ff.force_field) + items = {"box": box, "atom_style": atom_style, "masses": ff.masses, "force_field": ff.force_field} mol_ids, charges, coords, labels = [], [], [], [] v_collector = [] if topologies[0].velocities else None @@ -812,8 +812,8 @@ def from_ff_and_topologies(cls, box, ff, topologies, atom_style="full"): df["type"] = list(map(ff.maps[k].get, topo_labels[k])) if any(pd.isnull(df["type"])): # Throw away undefined topologies warnings.warn(f"Undefined {k.lower()} detected and removed") - df.dropna(subset=["type"], inplace=True) - df.reset_index(drop=True, inplace=True) + df = df.dropna(subset=["type"]) + df = df.reset_index(drop=True) df.index += 1 topology[k] = df[SECTION_HEADERS[k]] topology = {k: v for k, v in topology.items() if not v.empty} @@ -836,10 +836,7 @@ def from_structure(cls, structure: Structure, ff_elements=None, atom_style="char "charge" (charged). Default to "charge". is_sort (bool): whether to sort sites """ - if is_sort: - s = structure.get_sorted_structure() - else: - s = structure.copy() + s = structure.get_sorted_structure() if is_sort else structure.copy() box, symm_op = lattice_2_lmpbox(s.lattice) coords = symm_op.operate_multi(s.cart_coords) site_properties = s.site_properties @@ -903,10 +900,7 @@ def __init__(self, sites, ff_label=None, charges=None, velocities=None, topologi if not isinstance(sites, (Molecule, Structure)): sites = Molecule.from_sites(sites) - if ff_label: - type_by_sites = sites.site_properties.get(ff_label) - else: - type_by_sites = [site.specie.symbol for site in sites] + type_by_sites = sites.site_properties.get(ff_label) if ff_label else [site.specie.symbol for site in sites] # search for site property if not override if charges is None: charges = sites.site_properties.get("charge") diff --git a/pymatgen/io/lammps/tests/test_data.py b/pymatgen/io/lammps/tests/test_data.py index c100bc5d304..d54314c3df7 100644 --- a/pymatgen/io/lammps/tests/test_data.py +++ b/pymatgen/io/lammps/tests/test_data.py @@ -161,7 +161,7 @@ def test_get_string(self): ] kw_inds = {l: i for i, l in enumerate(pep_lines) if l in pep_kws} # section sequence - assert [k for k in sorted(kw_inds, key=kw_inds.get)] == pep_kws + assert sorted(kw_inds, key=kw_inds.get) == pep_kws # header pep_header = "\n".join(pep_lines[: kw_inds["Masses"]]) pep_header_7 = """Generated by pymatgen.io.lammps.data.LammpsData @@ -221,7 +221,7 @@ def test_get_string(self): ] kw_inds = {l: i for i, l in enumerate(c2h6_lines) if l in c2h6_kws} # section sequence - assert [k for k in sorted(kw_inds, key=kw_inds.get)] == c2h6_kws + assert sorted(kw_inds, key=kw_inds.get) == c2h6_kws # header c2h6_header = "\n".join(c2h6_lines[: kw_inds["Masses"]]) c2h6_header_5 = """Generated by pymatgen.io.lammps.data.LammpsData @@ -348,7 +348,7 @@ def test_disassemble(self): # test no guessing element and pairij as nonbond coeffs v = self.virus _, v_ff, _ = v.disassemble(guess_element=False) - assert v_ff.maps["Atoms"] == dict(Qa1=1, Qb1=2, Qc1=3, Qa2=4) + assert v_ff.maps["Atoms"] == {"Qa1": 1, "Qb1": 2, "Qc1": 3, "Qa2": 4} pairij_coeffs = v.force_field["PairIJ Coeffs"].drop(["id1", "id2"], axis=1) np.testing.assert_array_equal(v_ff.nonbond_coeffs, pairij_coeffs.values) # test class2 ff @@ -1058,7 +1058,7 @@ def test_disassemble(self): # test no guessing element v = self.li_2 _, v_ff, _ = v.disassemble(guess_element=False)[0] - assert v_ff.maps["Atoms"] == dict(Qa1=1) + assert v_ff.maps["Atoms"] == {"Qa1": 1} def test_json_dict(self): encoded = json.dumps(self.li_ec.as_dict(), cls=MontyEncoder) diff --git a/pymatgen/io/lammps/utils.py b/pymatgen/io/lammps/utils.py index 71e3ea9704e..a95a0132b72 100644 --- a/pymatgen/io/lammps/utils.py +++ b/pymatgen/io/lammps/utils.py @@ -464,7 +464,7 @@ def run(self): """ Write the input/data files and run LAMMPS. """ - lammps_cmd = self.lammps_bin + ["-in", self.input_filename] + lammps_cmd = [*self.lammps_bin, "-in", self.input_filename] print(f"Running: {' '.join(lammps_cmd)}") with Popen(lammps_cmd, stdout=PIPE, stderr=PIPE) as p: (stdout, stderr) = p.communicate() diff --git a/pymatgen/io/lmto.py b/pymatgen/io/lmto.py index 6a1b181e2ca..3fd995f4d83 100644 --- a/pymatgen/io/lmto.py +++ b/pymatgen/io/lmto.py @@ -79,19 +79,13 @@ def get_string(self, sigfigs=8): lines.append("STRUC".ljust(10) + "ALAT=" + str(round(ctrl_dict["ALAT"], sigfigs))) for l, latt in enumerate(ctrl_dict["PLAT"]): - if l == 0: - line = "PLAT=".rjust(15) - else: - line = " ".ljust(15) + line = "PLAT=".rjust(15) if l == 0 else " ".ljust(15) line += " ".join(str(round(v, sigfigs)) for v in latt) lines.append(line) for cat in ["CLASS", "SITE"]: for a, atoms in enumerate(ctrl_dict[cat]): - if a == 0: - line = [cat.ljust(9)] - else: - line = [" ".ljust(9)] + line = [cat.ljust(9)] if a == 0 else [" ".ljust(9)] for token, val in sorted(atoms.items()): if token == "POS": line.append("POS=" + " ".join(str(round(p, sigfigs)) for p in val)) diff --git a/pymatgen/io/lobster/inputs.py b/pymatgen/io/lobster/inputs.py index 5506fcad1ac..26db39e663b 100644 --- a/pymatgen/io/lobster/inputs.py +++ b/pymatgen/io/lobster/inputs.py @@ -202,10 +202,7 @@ def diff(self, other): for k2, v2 in other.items(): if k2.upper() not in similar_param and k2.upper() not in different_param: for key_here in self: - if k2.lower() == key_here.lower(): - new_key = key_here - else: - new_key = k2 + new_key = key_here if k2.lower() == key_here.lower() else k2 if new_key not in self: different_param[k2.upper()] = {"lobsterin1": None, "lobsterin2": v2} return {"Same": similar_param, "Different": different_param} @@ -871,7 +868,7 @@ def get_all_possible_basis_combinations(min_basis: list, max_basis: list) -> lis if not isinstance(elbasis, list): new_start_basis.append([elbasis, elbasis2]) else: - new_start_basis.append(elbasis.copy() + [elbasis2]) + new_start_basis.append([*elbasis.copy(), elbasis2]) start_basis = new_start_basis return start_basis return [[basis] for basis in start_basis] diff --git a/pymatgen/io/lobster/lobsterenv.py b/pymatgen/io/lobster/lobsterenv.py index 0610f490edb..da796d6d738 100644 --- a/pymatgen/io/lobster/lobsterenv.py +++ b/pymatgen/io/lobster/lobsterenv.py @@ -508,10 +508,7 @@ def get_info_cohps_to_neighbors( if only_bonds_to is None: # sort by anion type - if per_bond: - divisor = len(labels) - else: - divisor = 1 + divisor = len(labels) if per_bond else 1 plotlabel = self._get_plot_label(atoms, per_bond) summed_cohp = self.completecohp.get_summed_cohp_by_label_list( @@ -549,10 +546,7 @@ def get_info_cohps_to_neighbors( new_atoms.append(atompair) # print(new_labels) if len(new_labels) > 0: - if per_bond: - divisor = len(new_labels) - else: - divisor = 1 + divisor = len(new_labels) if per_bond else 1 plotlabel = self._get_plot_label(new_atoms, per_bond) summed_cohp = self.completecohp.get_summed_cohp_by_label_list( diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index 438d01d1a60..81c0a61989c 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -237,7 +237,7 @@ def _get_bond_data(line: str) -> dict: if "[" in sites[0]: orbs = [re.findall(r"\[(.*)\]", site)[0] for site in sites] - orbitals = [tuple((int(orb[0]), Orbital(orb_labs.index(orb[1:])))) for orb in orbs] + orbitals = [(int(orb[0]), Orbital(orb_labs.index(orb[1:]))) for orb in orbs] orb_label = f"{orbitals[0][0]}{orbitals[0][1].name}-{orbitals[1][0]}{orbitals[1][1].name}" # type: ignore else: @@ -542,10 +542,7 @@ def _parse_doscar(self): _, ncol = data.shape orbnumber = 0 for j in range(1, ncol): - if j % 2 == 0: - spin = Spin.down - else: - spin = Spin.up + spin = Spin.down if j % 2 == 0 else Spin.up orb = orbitals[atom + 1][orbnumber] pdos[orb][spin] = data[:, j] if j % 2 == 0: @@ -877,27 +874,24 @@ def get_doc(self): def _get_lobster_version(data): for row in data: splitrow = row.split() - if len(splitrow) > 1: - if splitrow[0] == "LOBSTER": - return splitrow[1] + if len(splitrow) > 1 and splitrow[0] == "LOBSTER": + return splitrow[1] raise RuntimeError("Version not found.") @staticmethod def _has_fatband(data): for row in data: splitrow = row.split() - if len(splitrow) > 1: - if splitrow[1] == "FatBand": - return True + if len(splitrow) > 1 and splitrow[1] == "FatBand": + return True return False @staticmethod def _get_dft_program(data): for row in data: splitrow = row.split() - if len(splitrow) > 4: - if splitrow[3] == "program...": - return splitrow[4] + if len(splitrow) > 4 and splitrow[3] == "program...": + return splitrow[4] return None @staticmethod @@ -910,9 +904,8 @@ def _get_number_of_spins(data): def _get_threads(data): for row in data: splitrow = row.split() - if len(splitrow) > 11: - if (splitrow[11]) == "threads" or (splitrow[11] == "thread"): - return splitrow[10] + if len(splitrow) > 11 and ((splitrow[11]) == "threads" or (splitrow[11] == "thread")): + return splitrow[10] raise ValueError("Threads not found.") @staticmethod @@ -921,12 +914,11 @@ def _get_spillings(data, number_of_spins): total_spilling = [] for row in data: splitrow = row.split() - if len(splitrow) > 2: - if splitrow[2] == "spilling:": - if splitrow[1] == "charge": - charge_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - if splitrow[1] == "total": - total_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) + if len(splitrow) > 2 and splitrow[2] == "spilling:": + if splitrow[1] == "charge": + charge_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) + if splitrow[1] == "total": + total_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) if len(charge_spilling) == number_of_spins and len(total_spilling) == number_of_spins: break @@ -982,24 +974,9 @@ def _get_timing(data): if "sys" in splitrow: sys_time = splitrow[0:8] - wall_time_dict = { - "h": wall_time[0], - "min": wall_time[2], - "s": wall_time[4], - "ms": wall_time[6], - } - user_time_dict = { - "h": user_time[0], - "min": user_time[2], - "s": user_time[4], - "ms": user_time[6], - } - sys_time_dict = { - "h": sys_time[0], - "min": sys_time[2], - "s": sys_time[4], - "ms": sys_time[6], - } + wall_time_dict = {"h": wall_time[0], "min": wall_time[2], "s": wall_time[4], "ms": wall_time[6]} + user_time_dict = {"h": user_time[0], "min": user_time[2], "s": user_time[4], "ms": user_time[6]} + sys_time_dict = {"h": sys_time[0], "min": sys_time[2], "s": sys_time[4], "ms": sys_time[6]} return wall_time_dict, user_time_dict, sys_time_dict @@ -1017,9 +994,8 @@ def _get_all_warning_lines(data): ws = [] for row in data: splitrow = row.split() - if len(splitrow) > 0: - if splitrow[0] == "WARNING:": - ws.append(" ".join(splitrow[1:])) + if len(splitrow) > 0 and splitrow[0] == "WARNING:": + ws.append(" ".join(splitrow[1:])) return ws @staticmethod @@ -1027,9 +1003,8 @@ def _get_all_info_lines(data): infos = [] for row in data: splitrow = row.split() - if len(splitrow) > 0: - if splitrow[0] == "INFO:": - infos.append(" ".join(splitrow[1:])) + if len(splitrow) > 0 and splitrow[0] == "INFO:": + infos.append(" ".join(splitrow[1:])) return infos @@ -1321,7 +1296,7 @@ def _read(self, contents: list, spin_numbers: list): elif "maxDeviation" in line: if spin not in self.bandoverlapsdict: self.bandoverlapsdict[spin] = {} - if not " ".join(kpoint_array) in self.bandoverlapsdict[spin]: + if " ".join(kpoint_array) not in self.bandoverlapsdict[spin]: self.bandoverlapsdict[spin][" ".join(kpoint_array)] = {} maxdev = line.split(" ")[2] self.bandoverlapsdict[spin][" ".join(kpoint_array)]["maxDeviation"] = float(maxdev) @@ -1343,10 +1318,7 @@ def has_good_quality_maxDeviation(self, limit_maxDeviation: float = 0.1) -> bool Returns: Boolean that will give you information about the quality of the projection """ - for deviation in self.max_deviation: - if deviation > limit_maxDeviation: - return False - return True + return all(deviation <= limit_maxDeviation for deviation in self.max_deviation) def has_good_quality_check_occupied_bands( self, @@ -1418,7 +1390,7 @@ def __init__(self, filename: str = "GROSSPOP.lobster"): self.list_dict_grosspop = [] # transfers content of file to list of dict for line in contents[3:]: - cleanline = [i for i in line.split(" ") if not i == ""] + cleanline = [i for i in line.split(" ") if i != ""] if len(cleanline) == 5: smalldict = {} smalldict["element"] = cleanline[1] @@ -1551,26 +1523,24 @@ def set_volumetric_data(self, grid, structure): y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] - if x != Nx: - if y != Ny: - if z != Nz: - if not np.isclose(self.points[runner][0], x_here, 1e-3): - if not np.isclose(self.points[runner][1], y_here, 1e-3): - if not np.isclose(self.points[runner][2], z_here, 1e-3): - raise ValueError( - "The provided wavefunction from Lobster does not contain all relevant" - " points. " - "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " - "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " - ) - - new_x.append(x_here) - new_y.append(y_here) - new_z.append(z_here) - - new_real.append(self.real[runner]) - new_imaginary.append(self.imaginary[runner]) - new_density.append(self.real[runner] ** 2 + self.imaginary[runner] ** 2) + if x != Nx and y != Ny and z != Nz: + if not np.isclose(self.points[runner][0], x_here, 1e-3): + if not np.isclose(self.points[runner][1], y_here, 1e-3): + if not np.isclose(self.points[runner][2], z_here, 1e-3): + raise ValueError( + "The provided wavefunction from Lobster does not contain all relevant" + " points. " + "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " + "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " + ) + + new_x.append(x_here) + new_y.append(y_here) + new_z.append(z_here) + + new_real.append(self.real[runner]) + new_imaginary.append(self.imaginary[runner]) + new_density.append(self.real[runner] ** 2 + self.imaginary[runner] ** 2) runner += 1 diff --git a/pymatgen/io/lobster/tests/test_lobster.py b/pymatgen/io/lobster/tests/test_lobster.py index 804665be10d..2edfffe90cb 100644 --- a/pymatgen/io/lobster/tests/test_lobster.py +++ b/pymatgen/io/lobster/tests/test_lobster.py @@ -200,7 +200,7 @@ def test_cohp_data(self): assert len(val["ICOHP"][Spin.up]) == 6 def test_orbital_resolved_cohp(self): - orbitals = [tuple((Orbital(i), Orbital(j))) for j in range(4) for i in range(4)] + orbitals = [(Orbital(i), Orbital(j)) for j in range(4) for i in range(4)] assert self.cohp_bise.orb_res_cohp is None assert self.coop_bise.orb_res_cohp is None assert self.cohp_fe.orb_res_cohp is None @@ -211,7 +211,7 @@ def test_orbital_resolved_cohp(self): orb_set = self.orb.orb_res_cohp["1"][orbs]["orbitals"] assert orb_set[0][0] == 4 assert orb_set[1][0] == 4 - assert tuple((orb_set[0][1], orb_set[1][1])) in orbitals + assert (orb_set[0][1], orb_set[1][1]) in orbitals # test d and f orbitals comparelist = [ @@ -644,14 +644,14 @@ def test_values(self): assert icooplist_fe == self.icoop_fe.icohplist assert -0.29919 == self.icoop_fe.icohpcollection.extremum_icohpvalue() assert icooplist_bise == self.icoop_bise.icohplist - assert 0.24714 == self.icoop_bise.icohpcollection.extremum_icohpvalue() + assert self.icoop_bise.icohpcollection.extremum_icohpvalue() == 0.24714 assert self.icobi.icohplist["1"]["icohp"][Spin.up] == approx(0.58649) assert self.icobi_orbitalwise.icohplist["2"]["icohp"][Spin.up] == approx(0.58649) assert self.icobi_orbitalwise.icohplist["1"]["icohp"][Spin.up] == approx(0.58649) assert self.icobi_orbitalwise_spinpolarized.icohplist["1"]["icohp"][Spin.up] == approx(0.58649 / 2, abs=1e-3) assert self.icobi_orbitalwise_spinpolarized.icohplist["1"]["icohp"][Spin.down] == approx(0.58649 / 2, abs=1e-3) assert self.icobi_orbitalwise_spinpolarized.icohplist["2"]["icohp"][Spin.down] == approx(0.58649 / 2, abs=1e-3) - assert 0.58649 == self.icobi.icohpcollection.extremum_icohpvalue() + assert self.icobi.icohpcollection.extremum_icohpvalue() == 0.58649 class DoscarTest(unittest.TestCase): @@ -2073,13 +2073,9 @@ def is_kpoint_in_list(self, kpoint, kpointlist, weight, weightlist) -> bool: np.isclose(-kpoint[0], kpoint2[0]) and np.isclose(-kpoint[1], kpoint2[1]) and np.isclose(-kpoint[2], kpoint2[2]) - ): - if weight == weightlist[ikpoint2]: - found += 1 - if found == 1: - return True - else: - return False + ) and weight == weightlist[ikpoint2]: + found += 1 + return found == 1 def test_MSONable_implementation(self): # tests as dict and from dict methods diff --git a/pymatgen/io/packmol.py b/pymatgen/io/packmol.py index c36818c2872..0bd19050f74 100644 --- a/pymatgen/io/packmol.py +++ b/pymatgen/io/packmol.py @@ -44,9 +44,11 @@ class PackmolSet(InputSet): def run(self, path: str | Path, timeout=30): """Run packmol and write out the packed structure. + Args: path: The path in which packmol input files are located. timeout: Timeout in seconds. + Raises: ValueError if packmol does not succeed in packing the box. TimeoutExpiredError if packmold does not finish within the timeout. @@ -148,7 +150,8 @@ def get_input_set( # type: ignore 2. "number" - the number of that molecule to pack into the box 3. "coords" - Coordinates in the form of either a Molecule object or a path to a file. - Example: + + Example: {"name": "water", "number": 500, "coords": "/path/to/input/file.xyz"} @@ -172,7 +175,7 @@ def get_input_set( # type: ignore if " " in str(self.outputfile): # NOTE - double quotes are deliberately used inside the f-string here, do not change # fmt: off - file_contents += f'output {self.outputfile!r}\n\n' + file_contents += f'output "{self.outputfile}"\n\n' # fmt: on else: file_contents += f"output {self.outputfile}\n\n" @@ -183,10 +186,7 @@ def get_input_set( # type: ignore # estimate the total volume of all molecules in cubic Å net_volume = 0.0 for d in molecules: - if not isinstance(d["coords"], Molecule): - mol = Molecule.from_file(d["coords"]) - else: - mol = d["coords"] + mol = Molecule.from_file(d["coords"]) if not isinstance(d["coords"], Molecule) else d["coords"] # pad the calculated length by an amount related to the tolerance parameter # the amount to add was determined arbitrarily length = ( @@ -210,7 +210,7 @@ def get_input_set( # type: ignore if " " in str(fname): # NOTE - double quotes are deliberately used inside the f-string here, do not change # fmt: off - file_contents += f'structure {fname!r}\n' + file_contents += f"structure {fname!r}\n" # fmt: on else: file_contents += f"structure {fname}\n" diff --git a/pymatgen/io/phonopy.py b/pymatgen/io/phonopy.py index 0a2605b5749..2c87a531fa9 100644 --- a/pymatgen/io/phonopy.py +++ b/pymatgen/io/phonopy.py @@ -81,7 +81,6 @@ def get_structure_from_dict(d): Adds "phonopy_masses" in the site_properties of the structures. Compatible with older phonopy versions. """ - species = [] frac_coords = [] masses = [] @@ -117,7 +116,6 @@ def eigvec_to_eigdispl(v, q, frac_coords, mass): frac_coords: the fractional coordinates of the atom mass: the mass of the atom """ - c = np.exp(2j * np.pi * np.dot(frac_coords, q)) / np.sqrt(mass) return c * v @@ -142,7 +140,6 @@ def get_ph_bs_symm_line_from_dict(bands_dict, has_nac=False, labels_dict=None): labels_dict: dict that links a qpoint in frac coords to a label. Its value will replace the data contained in the band.yaml. """ - structure = get_structure_from_dict(bands_dict) qpts = [] @@ -272,7 +269,6 @@ def get_displaced_structures(pmg_structure, atom_disp=0.01, supercell_matrix=Non A list of symmetrically inequivalent structures with displacements, in which the first element is the perfect supercell structure. """ - is_plusminus = kwargs.get("is_plusminus", "auto") is_diagonal = kwargs.get("is_diagonal", True) is_trigonal = kwargs.get("is_trigonal", False) @@ -451,7 +447,6 @@ def get_gruneisenparameter(gruneisen_path, structure=None, structure_path=None) Returns: GruneisenParameter object """ - gruneisen_dict = loadfn(gruneisen_path) if structure_path and structure is None: @@ -468,10 +463,7 @@ def get_gruneisenparameter(gruneisen_path, structure=None, structure_path=None) for p in gruneisen_dict["phonon"]: q = p["q-position"] qpts.append(q) - if "multiplicity" in p: - m = p["multiplicity"] - else: - m = 1 + m = p["multiplicity"] if "multiplicity" in p else 1 multiplicities.append(m) bands, gruneisenband = [], [] for b in p["band"]: @@ -526,7 +518,6 @@ def get_gs_ph_bs_symm_line_from_dict( These derivations occur because of very small frequencies (and therefore numerical inaccuracies) close to gamma. """ - if structure_path and structure is None: structure = Structure.from_file(structure_path) else: diff --git a/pymatgen/io/pwscf.py b/pymatgen/io/pwscf.py index 45148553671..a7f2b2e69c0 100644 --- a/pymatgen/io/pwscf.py +++ b/pymatgen/io/pwscf.py @@ -141,10 +141,7 @@ def to_str(v): out.append("ATOMIC_SPECIES") for k, v in sorted(site_descriptions.items(), key=lambda i: i[0]): e = re.match(r"[A-Z][a-z]?", k).group(0) - if self.pseudo is not None: - p = v - else: - p = v["pseudo"] + p = v if self.pseudo is not None else v["pseudo"] out.append(f" {k} {Element(e).atomic_mass:.4f} {p}") out.append("ATOMIC_POSITIONS crystal") diff --git a/pymatgen/io/qchem/inputs.py b/pymatgen/io/qchem/inputs.py index 0fbeff6eb0c..20f0899273b 100644 --- a/pymatgen/io/qchem/inputs.py +++ b/pymatgen/io/qchem/inputs.py @@ -32,7 +32,7 @@ class QCInput(InputFile): """ An object representing a QChem input file. QCInput attributes represent different sections of a QChem input file. - To add a new section one needs to modify __init__, __str__, from_sting and add staticmethods + To add a new section one needs to modify __init__, __str__, from_sting and add static methods to read and write the new section i.e. section_template and read_section. By design, there is very little (or no) checking that input parameters conform to the appropriate QChem format, this responsible lands on the user or a separate error handling software. @@ -107,9 +107,9 @@ def __init__( A list of lists of dictionaries, where each dictionary represents a charge constraint in the cdft section of the QChem input file. - Each entry in the main list represents one state (allowing for multiconfiguration calculations - using constrainted density functional theory - configuration interaction (CDFT-CI). - Each state is relresented by a list, which itself contains some number of constraints + Each entry in the main list represents one state (allowing for multi-configuration calculations + using constrained density functional theory - configuration interaction (CDFT-CI). + Each state is represented by a list, which itself contains some number of constraints (dictionaries). Ex: @@ -124,7 +124,7 @@ def __init__( Note that a type of None will default to a charge constraint (which can also be accessed by requesting a type of "c" or "charge". - 2. For a multireference calculation: + 2. For a multi-reference calculation: cdft=[ [ {"value": 1.0, "coefficients": [1.0], "first_atoms": [1], "last_atoms": [27], @@ -191,9 +191,8 @@ def __init__( if "basis" not in self.rem: raise ValueError("The rem dictionary must contain a 'basis' entry") - if "method" not in self.rem: - if "exchange" not in self.rem: - raise ValueError("The rem dictionary must contain either a 'method' entry or an 'exchange' entry") + if "method" not in self.rem and "exchange" not in self.rem: + raise ValueError("The rem dictionary must contain either a 'method' entry or an 'exchange' entry") if "job_type" not in self.rem: raise ValueError("The rem dictionary must contain a 'job_type' entry") if self.rem.get("job_type").lower() not in valid_job_types: @@ -379,6 +378,7 @@ def write_multi_job_file(job_list: list[QCInput], filename: str): def from_file(filename: str | Path) -> QCInput: """ Create QcInput from file. + Args: filename (str): Filename @@ -392,6 +392,7 @@ def from_file(filename: str | Path) -> QCInput: def from_multi_jobs_file(cls, filename: str) -> list[QCInput]: """ Create list of QcInput from a file. + Args: filename (str): Filename @@ -420,9 +421,8 @@ def molecule_template(molecule: Molecule | list[Molecule] | Literal["read"]) -> mol_list.append("$molecule") # Edge case; can't express molecule as fragments with only one fragment - if isinstance(molecule, list): - if len(molecule) == 1: - molecule = molecule[0] + if isinstance(molecule, list) and len(molecule) == 1: + molecule = molecule[0] if isinstance(molecule, str): if molecule == "read": @@ -680,15 +680,14 @@ def cdft_template(cdft: list[list[dict]]) -> str: Returns: (str) """ - - cdft_list = list() + cdft_list = [] cdft_list.append("$cdft") for ii, state in enumerate(cdft): for constraint in state: types = constraint["types"] cdft_list.append(f" {constraint['value']}") - type_strings = list() + type_strings = [] for t in types: if t is None: type_strings.append("") @@ -725,8 +724,7 @@ def almo_template(almo_coupling: list[list[tuple[int, int]]]) -> str: Returns: (str) """ - - almo_list = list() + almo_list = [] almo_list.append("$almo_coupling") # ALMO coupling calculations always involve 2 states @@ -829,10 +827,7 @@ def read_molecule(string: str) -> Molecule | list[Molecule] | Literal["read"]: charge = float(matches["charge"][0][0]) if "spin_mult" in matches: spin_mult = int(matches["spin_mult"][0][0]) - if "fragment" in matches: - multi_mol = True - else: - multi_mol = False + multi_mol = "fragment" in matches if not multi_mol: header = r"^\s*\$molecule\n\s*(?:\-)*\d+\s+(?:\-)*\d+" @@ -851,7 +846,7 @@ def read_molecule(string: str) -> Molecule | list[Molecule] | Literal["read"]: row = r"\s*([A-Za-z]+)\s+([\d\-\.]+)\s+([\d\-\.]+)\s+([\d\-\.]+)" footer = r"(:?(:?\-\-)|(:?\$end))" - molecules = list() + molecules = [] patterns = {"charge_spin": r"\s*\-\-\s*([\-0-9]+)\s+([\-0-9]+)"} matches = read_pattern(string, patterns) @@ -985,10 +980,7 @@ def read_vdw(string: str) -> tuple[str, dict]: print("No valid vdW inputs found. Note that there should be no '=' characters in vdW input lines.") return "", {} - if vdw_table[0][0][0] == 2: - mode = "sequential" - else: - mode = "atomic" + mode = "sequential" if vdw_table[0][0][0] == 2 else "atomic" return mode, dict(vdw_table[0][1:]) @@ -1156,7 +1148,6 @@ def read_cdft(string: str) -> list[list[dict]]: Returns: (list of lists of dicts) cdft parameters """ - pattern_sec = { "full_section": r"\$cdft((:?(:?\s*[0-9\.\-]+\s+[0-9]+\s+[0-9]+(:?\s+[A-Za-z]+)?\s*\n)+|" r"(:?\s*[0-9\.\-]+\s*\n)|(:?\s*\-+\s*\n))+)\$end" @@ -1169,23 +1160,23 @@ def read_cdft(string: str) -> list[list[dict]]: section = read_pattern(string, pattern_sec)["full_section"] if len(section) == 0: print("No valid cdft inputs found.") - return list() + return [] - cdft = list() + cdft = [] section = section[0][0] states = re.split(r"\-{2,25}", section) for state in states: - state_list = list() + state_list = [] const_out = list(read_pattern(state, pattern_const).get("constraint")) if len(const_out) == 0: continue for const in const_out: const_dict = { "value": float(const[0]), - "coefficients": list(), - "first_atoms": list(), - "last_atoms": list(), - "types": list(), + "coefficients": [], + "first_atoms": [], + "last_atoms": [], + "types": [], } # type: ignore subconsts = const[1].strip().split("\n") for subconst in subconsts: @@ -1215,7 +1206,6 @@ def read_almo(string: str) -> list[list[tuple[int, int]]]: Returns: (list of lists of int 2-tuples) almo_coupling parameters """ - pattern = { "key": r"\$almo_coupling\s*\n((?:\s*[\-0-9]+\s+[\-0-9]+\s*\n)+)\s*\-\-" r"((?:\s*[\-0-9]+\s+[\-0-9]+\s*\n)+)\s*\$end" @@ -1225,7 +1215,7 @@ def read_almo(string: str) -> list[list[tuple[int, int]]]: if len(section) == 0: print("No valid almo inputs found.") - return list() + return [] section = section[0] diff --git a/pymatgen/io/qchem/outputs.py b/pymatgen/io/qchem/outputs.py index f7d94da6609..b51eb134bec 100644 --- a/pymatgen/io/qchem/outputs.py +++ b/pymatgen/io/qchem/outputs.py @@ -70,15 +70,14 @@ def __init__(self, filename: str): self.data["multiple_outputs"] = read_pattern( self.text, {"key": r"Job\s+\d+\s+of\s+(\d+)\s+"}, terminate_on_match=True ).get("key") - if self.data.get("multiple_outputs") is not None: - if self.data.get("multiple_outputs") != [["1"]]: - raise ValueError( - "ERROR: multiple calculation outputs found in file " - + filename - + ". Please instead call QCOutput.mulitple_outputs_from_file(QCOutput,'" - + filename - + "')" - ) + if self.data.get("multiple_outputs") is not None and self.data.get("multiple_outputs") != [["1"]]: + raise ValueError( + "ERROR: multiple calculation outputs found in file " + + filename + + ". Please instead call QCOutput.mulitple_outputs_from_file(QCOutput,'" + + filename + + "')" + ) # Parse the Q-Chem major version if read_pattern( @@ -657,8 +656,8 @@ def multiple_outputs_from_file(cls, filename, keep_sub_files=True): def _read_eigenvalues(self): """Parse the orbital energies from the output file. An array of the - dimensions of the number of orbitals used in the calculation is stored.""" - + dimensions of the number of orbitals used in the calculation is stored. + """ # Find the pattern corresponding to the "Final Alpha MO Eigenvalues" section header_pattern = r"Final Alpha MO Eigenvalues" # The elements of the matrix are always floats, they are surrounded by @@ -695,8 +694,8 @@ def _read_eigenvalues(self): def _read_fock_matrix(self): """Parses the Fock matrix. The matrix is read in whole - from the output file and then transformed into the right dimensions.""" - + from the output file and then transformed into the right dimensions. + """ # The header is the same for both spin-restricted and spin-unrestricted calculations. header_pattern = r"Final Alpha Fock Matrix" # The elements of the matrix are always floats, they are surrounded by @@ -734,7 +733,8 @@ def _read_fock_matrix(self): def _read_coefficient_matrix(self): """Parses the coefficient matrix from the output file. Done is much - the same was as the Fock matrix.""" + the same was as the Fock matrix. + """ # The header is the same for both spin-restricted and spin-unrestricted calculations. header_pattern = r"Final Alpha MO Coefficients" # The elements of the matrix are always floats, they are surrounded by @@ -967,7 +967,6 @@ def _read_charges_and_dipoles(self): Parses associated dipoles. Also parses spins given an unrestricted SCF. """ - self.data["dipoles"] = {} temp_dipole_total = read_pattern( self.text, {"key": r"X\s*[\d\-\.]+\s*Y\s*[\d\-\.]+\s*Z\s*[\d\-\.]+\s*Tot\s*([\d\-\.]+)"} @@ -1042,8 +1041,9 @@ def _read_charges_and_dipoles(self): ).get("key") temp_RESP_dipole = read_pattern( self.text, - { # pylint: disable=line-too-long - "key": r"Related Dipole Moment =\s*[\d\-\.]+\s*\(X\s*([\d\-\.]+)\s*Y\s*([\d\-\.]+)\s*Z\s*([\d\-\.]+)\)" + { + "key": r"Related Dipole Moment =\s*[\d\-\.]+\s*\(X\s*([\d\-\.]+)\s*Y\s*([\d\-\.]+)" + r"\s*Z\s*([\d\-\.]+)\)" }, ).get("key") if temp_RESP_dipole is not None: @@ -1222,12 +1222,18 @@ def _read_geometries(self): ) # Parses optimized XYZ coordinates. If not present, parses optimized Z-matrix. - if self.data.get("new_optimizer") is None: # pylint: disable-next=line-too-long - header_pattern = r"\*+\s+(OPTIMIZATION|TRANSITION STATE)\s+CONVERGED\s+\*+\s+\*+\s+Coordinates \(Angstroms\)\s+ATOM\s+X\s+Y\s+Z" + if self.data.get("new_optimizer") is None: + header_pattern = ( + r"\*+\s+(OPTIMIZATION|TRANSITION STATE)\s+CONVERGED\s+\*+\s+\*+\s+Coordinates " + r"\(Angstroms\)\s+ATOM\s+X\s+Y\s+Z" + ) table_pattern = r"\s+\d+\s+\w+\s+([\d\-\.]+)\s+([\d\-\.]+)\s+([\d\-\.]+)" footer_pattern = r"\s+Z-matrix Print:" - else: # pylint: disable-next=line-too-long - header_pattern = r"(OPTIMIZATION|TRANSITION STATE)\sCONVERGED\s+\*+\s+\*+\s+-+\s+Standard Nuclear Orientation \(Angstroms\)\s+I\s+Atom\s+X\s+Y\s+Z\s+-+" + else: + header_pattern = ( + r"(OPTIMIZATION|TRANSITION STATE)\sCONVERGED\s+\*+\s+\*+\s+-+\s+Standard " + r"Nuclear Orientation \(Angstroms\)\s+I\s+Atom\s+X\s+Y\s+Z\s+-+" + ) table_pattern = r"\s*\d+\s+[a-zA-Z]+\s*([\d\-\.]+)\s*([\d\-\.]+)\s*([\d\-\.]+)\s*" footer_pattern = r"\s*-+" parsed_optimized_geometries = read_table_pattern(self.text, header_pattern, table_pattern, footer_pattern) @@ -1709,11 +1715,10 @@ def _read_scan_data(self): self.data["scan_constraint_sets"]["bend"].append( {"atoms": atoms, "current": current, "target": target} ) - elif entry[0] == "Dihedral": - if len(atoms) == 4: - self.data["scan_constraint_sets"]["tors"].append( - {"atoms": atoms, "current": current, "target": target} - ) + elif entry[0] == "Dihedral" and len(atoms) == 4: + self.data["scan_constraint_sets"]["tors"].append( + {"atoms": atoms, "current": current, "target": target} + ) def _read_pcm_information(self): """ @@ -1795,8 +1800,10 @@ def _read_isosvp_information(self): "final_soln_phase_e": r"\s*The Final Solution-Phase Energy\s+=\s+([\d\-\.]+)\s*", "solute_internal_e": r"\s*The Solute Internal Energy\s+=\s+([\d\-\.]+)\s*", "total_solvation_free_e": r"\s*The Total Solvation Free Energy\s+=\s+([\d\-\.]+)\s*", - "change_solute_internal_e": r"\s*The Change in Solute Internal Energy\s+=\s+(\s+[\d\-\.]+)\s+\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", # pylint: disable=line-too-long - "reaction_field_free_e": r"\s*The Reaction Field Free Energy\s+=\s+(\s+[\d\-\.]+)\s+\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", # pylint: disable=line-too-long + "change_solute_internal_e": r"\s*The Change in Solute Internal Energy\s+=\s+(\s+[\d\-\.]+)" + r"\s+\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", + "reaction_field_free_e": r"\s*The Reaction Field Free Energy\s+=\s+(\s+[\d\-\.]+)\s+" + r"\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", "isosvp_dielectric": r"\s*DIELST=\s+(\s+[\d\-\.]+)\s*", }, ) @@ -1841,8 +1848,10 @@ def _read_cmirs_information(self): { "dispersion_e": r"\s*The Dispersion Energy\s+=\s+(\s+[\d\-\.]+)\s+\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", "exchange_e": r"\s*The Exchange Energy\s+=\s+(\s+[\d\-\.]+)\s+\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", - "min_neg_field_e": r"\s*Min. Negative Field Energy\s+=\s+(\s+[\d\-\.]+)\s+\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", # pylint: disable=line-too-long - "max_pos_field_e": r"\s*Max. Positive Field Energy\s+=\s+(\s+[\d\-\.]+)\s+\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", # pylint: disable=line-too-long + "min_neg_field_e": r"\s*Min. Negative Field Energy\s+=\s+(\s+[\d\-\.]+)\s+" + r"\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", + "max_pos_field_e": r"\s*Max. Positive Field Energy\s+=\s+(\s+[\d\-\.]+)\s+" + r"\(\s+([\d\-\.]+)\s+KCAL/MOL\)\s*", }, ) @@ -1871,13 +1880,12 @@ def _read_cdft(self): """ Parses output from charge- or spin-constrained DFT (CDFT) calculations. """ - # Parse constraint and optimization parameters temp_dict = read_pattern( self.text, {"constraint": r"Constraint\s+(\d+)\s+:\s+([\-\.0-9]+)", "multiplier": r"\s*Lam\s+([\.\-0-9]+)"} ) - self.data["cdft_constraints_multipliers"] = list() + self.data["cdft_constraints_multipliers"] = [] for const, multip in zip(temp_dict.get("constraint", []), temp_dict.get("multiplier", [])): entry = {"index": int(const[0]), "constraint": float(const[1]), "multiplier": float(multip[0])} self.data["cdft_constraints_multipliers"].append(entry) @@ -1895,14 +1903,14 @@ def _read_cdft(self): self.data["cdft_becke_population"] = None self.data["cdft_becke_net_spin"] = None else: - self.data["cdft_becke_excess_electrons"] = list() - self.data["cdft_becke_population"] = list() - self.data["cdft_becke_net_spin"] = list() + self.data["cdft_becke_excess_electrons"] = [] + self.data["cdft_becke_population"] = [] + self.data["cdft_becke_net_spin"] = [] for table in becke_table: - excess = list() - population = list() - spin = list() + excess = [] + population = [] + spin = [] for row in table: excess.append(float(row[0])) @@ -1919,7 +1927,6 @@ def _read_almo_msdft(self): """ Parse output of ALMO(MSDFT) calculations for coupling between diabatic states """ - temp_dict = read_pattern( self.text, { @@ -2124,9 +2131,8 @@ def _check_completion_errors(self): {"key": r"\d+\s+failed line searches\.\s+Resetting"}, terminate_on_match=False, ).get("key") - if tmp_failed_line_searches is not None: - if len(tmp_failed_line_searches) > 10: - self.data["errors"] += ["SCF_failed_to_converge"] + if tmp_failed_line_searches is not None and len(tmp_failed_line_searches) > 10: + self.data["errors"] += ["SCF_failed_to_converge"] if self.data.get("errors") == []: self.data["errors"] += ["unknown_error"] @@ -2159,6 +2165,7 @@ def check_for_structure_changes(mol1: Molecule, mol2: Molecule) -> str: Args: mol1: Pymatgen Molecule object to be compared. mol2: Pymatgen Molecule object to be compared. + Returns: One of ["unconnected_fragments", "fewer_bonds", "more_bonds", "bond_change", "no_change"] @@ -2220,7 +2227,6 @@ def jump_to_header(lines: list[str], header: str) -> list[str]: Raises: RuntimeError """ - # Search for the header for i, line in enumerate(lines): if header in line.strip(): @@ -2244,7 +2250,6 @@ def get_percentage(line: str, orbital: str) -> str: Raises: n/a """ - # Locate orbital in line index = line.find(orbital) line = line[index:] @@ -2290,7 +2295,6 @@ def parse_natural_populations(lines: list[str]) -> list[pd.DataFrame]: Raises: RuntimeError """ - no_failures = True pop_dfs = [] @@ -2351,7 +2355,6 @@ def parse_hyperbonds(lines: list[str]) -> list[pd.DataFrame]: Raises: RuntimeError """ - no_failures = True hyperbond_dfs = [] @@ -2433,7 +2436,6 @@ def parse_hybridization_character(lines: list[str]) -> list[pd.DataFrame]: Raises: RuntimeError """ - # Orbitals orbitals = ["s", "p", "d", "f"] @@ -2669,7 +2671,6 @@ def parse_perturbation_energy(lines: list[str]) -> list[pd.DataFrame]: Raises: RuntimeError """ - no_failures = True e2_dfs = [] @@ -2821,7 +2822,6 @@ def nbo_parser(filename: str) -> dict[str, list[pd.DataFrame]]: Raises: RuntimeError """ - # Open the lines with zopen(filename, mode="rt", encoding="ISO-8859-1") as f: lines = f.readlines() diff --git a/pymatgen/io/qchem/sets.py b/pymatgen/io/qchem/sets.py index 1585a5c4da1..94be02618fe 100644 --- a/pymatgen/io/qchem/sets.py +++ b/pymatgen/io/qchem/sets.py @@ -251,9 +251,9 @@ def __init__( A list of lists of dictionaries, where each dictionary represents a charge constraint in the cdft section of the QChem input file. - Each entry in the main list represents one state (allowing for multiconfiguration - calculations using constrainted density functional theory - configuration interaction - (CDFT-CI). Each state is relresented by a list, which itself contains some number of + Each entry in the main list represents one state (allowing for multi-configuration + calculations using constrained density functional theory - configuration interaction + (CDFT-CI). Each state is represented by a list, which itself contains some number of constraints (dictionaries). Ex: @@ -391,24 +391,18 @@ def __init__( plots_defaults = {"grid_spacing": "0.05", "total_density": "0"} - if self.opt_variables is None: - myopt = {} - else: - myopt = self.opt_variables + myopt = {} if self.opt_variables is None else self.opt_variables - if self.scan_variables is None: - myscan = {} - else: - myscan = self.scan_variables - - mypcm: dict = dict() - mysolvent: dict = dict() - mysmx: dict = dict() - myvdw: dict = dict() - myplots: dict = dict() - myrem: dict = dict() - mysvp: dict = dict() - mypcm_nonels: dict = dict() + myscan = {} if self.scan_variables is None else self.scan_variables + + mypcm: dict = {} + mysolvent: dict = {} + mysmx: dict = {} + myvdw: dict = {} + myplots: dict = {} + myrem: dict = {} + mysvp: dict = {} + mypcm_nonels: dict = {} myrem["job_type"] = job_type myrem["basis"] = self.basis_set myrem["max_scf_cycles"] = str(self.max_scf_cycles) @@ -581,16 +575,15 @@ def __init__( if sec == "svp": temp_svp = lower_and_check_unique(sec_dict) for k, v in temp_svp.items(): - if k == "rhoiso": - if self.cmirs_solvent is not None: - # must update both svp and pcm_nonels sections - if v not in ["0.001", "0.0005"]: - raise RuntimeError( - "CMIRS is only parameterized for RHOISO values of 0.001 or 0.0005! Exiting..." - ) - for k2, _v2 in mypcm_nonels.items(): - if CMIRS_SETTINGS[self.cmirs_solvent][v].get(k2): # type: ignore - mypcm_nonels[k2] = CMIRS_SETTINGS[self.cmirs_solvent][v].get(k2) # type: ignore + if k == "rhoiso" and self.cmirs_solvent is not None: + # must update both svp and pcm_nonels sections + if v not in ["0.001", "0.0005"]: + raise RuntimeError( + "CMIRS is only parameterized for RHOISO values of 0.001 or 0.0005! Exiting..." + ) + for k2, _v2 in mypcm_nonels.items(): + if CMIRS_SETTINGS[self.cmirs_solvent][v].get(k2): # type: ignore + mypcm_nonels[k2] = CMIRS_SETTINGS[self.cmirs_solvent][v].get(k2) # type: ignore if k == "idefesr": if self.cmirs_solvent is not None and v == "0": warnings.warn( @@ -746,9 +739,9 @@ def __init__( A list of lists of dictionaries, where each dictionary represents a charge constraint in the cdft section of the QChem input file. - Each entry in the main list represents one state (allowing for multiconfiguration - calculations using constrainted density functional theory - configuration interaction - (CDFT-CI). Each state is relresented by a list, which itself contains some number of + Each entry in the main list represents one state (allowing for multi-configuration + calculations using constrained density functional theory - configuration interaction + (CDFT-CI). Each state is represented by a list, which itself contains some number of constraints (dictionaries). Ex: @@ -981,9 +974,9 @@ def __init__( A list of lists of dictionaries, where each dictionary represents a charge constraint in the cdft section of the QChem input file. - Each entry in the main list represents one state (allowing for multiconfiguration - calculations using constrainted density functional theory - configuration interaction - (CDFT-CI). Each state is relresented by a list, which itself contains some number of + Each entry in the main list represents one state (allowing for multi-configuration + calculations using constrained density functional theory - configuration interaction + (CDFT-CI). Each state is represented by a list, which itself contains some number of constraints (dictionaries). Ex: @@ -1317,9 +1310,9 @@ def __init__( A list of lists of dictionaries, where each dictionary represents a charge constraint in the cdft section of the QChem input file. - Each entry in the main list represents one state (allowing for multiconfiguration - calculations using constrainted density functional theory - configuration interaction - (CDFT-CI). Each state is relresented by a list, which itself contains some number of + Each entry in the main list represents one state (allowing for multi-configuration + calculations using constrained density functional theory - configuration interaction + (CDFT-CI). Each state is represented by a list, which itself contains some number of constraints (dictionaries). Ex: @@ -1509,9 +1502,9 @@ def __init__( A list of lists of dictionaries, where each dictionary represents a charge constraint in the cdft section of the QChem input file. - Each entry in the main list represents one state (allowing for multiconfiguration - calculations using constrainted density functional theory - configuration interaction - (CDFT-CI). Each state is relresented by a list, which itself contains some number of + Each entry in the main list represents one state (allowing for multi-configuration + calculations using constrained density functional theory - configuration interaction + (CDFT-CI). Each state is represented by a list, which itself contains some number of constraints (dictionaries). Ex: diff --git a/pymatgen/io/qchem/tests/test_outputs.py b/pymatgen/io/qchem/tests/test_outputs.py index 656a4bd43f3..ec25d1eaa05 100644 --- a/pymatgen/io/qchem/tests/test_outputs.py +++ b/pymatgen/io/qchem/tests/test_outputs.py @@ -400,30 +400,30 @@ def test_cdft_parsing(self): data = QCOutput( os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "cdft_simple.qout") ).data - self.assertEqual(data["cdft_becke_excess_electrons"][0][0], 0.432641) - self.assertEqual(len(data["cdft_becke_population"][0]), 12) - self.assertEqual(data["cdft_becke_net_spin"][0][6], -0.000316) + assert data["cdft_becke_excess_electrons"][0][0] == 0.432641 + assert len(data["cdft_becke_population"][0]) == 12 + assert data["cdft_becke_net_spin"][0][6] == -0.000316 def test_cdft_dc_parsing(self): data = QCOutput.multiple_outputs_from_file( os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "cdft_dc.qout"), keep_sub_files=False, )[-1].data - self.assertEqual(data["direct_coupling_eV"], 0.0103038246) + assert data["direct_coupling_eV"] == 0.0103038246 def test_almo_msdft2_parsing(self): data = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "almo.out")).data self.assertListEqual(data["almo_coupling_states"], [[[1, 2], [0, 1]], [[0, 1], [1, 2]]]) - self.assertEqual(data["almo_hamiltonian"][0][0], -156.62929) + assert data["almo_hamiltonian"][0][0] == -156.62929 self.assertAlmostEqual(data["almo_coupling_eV"], 0.26895) def test_pod_parsing(self): data = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "pod2_gs.out")).data - self.assertEqual(data["pod_coupling_eV"], 0.247818) + assert data["pod_coupling_eV"] == 0.247818 def test_fodft_parsing(self): data = QCOutput(os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "new_qchem_files", "fodft.out")).data - self.assertEqual(data["fodft_coupling_eV"], 0.268383) + assert data["fodft_coupling_eV"] == 0.268383 def test_isosvp_water(self): data = QCOutput( diff --git a/pymatgen/io/qchem/tests/test_sets.py b/pymatgen/io/qchem/tests/test_sets.py index 25b974e753a..968294e9344 100644 --- a/pymatgen/io/qchem/tests/test_sets.py +++ b/pymatgen/io/qchem/tests/test_sets.py @@ -994,9 +994,9 @@ def test_init(self): "symmetry": "false", } assert test_pes_scan.rem == ref_dict - assert test_pes_scan.pcm == dict() - assert test_pes_scan.solvent == dict() - assert test_pes_scan.smx == dict() + assert test_pes_scan.pcm == {} + assert test_pes_scan.solvent == {} + assert test_pes_scan.smx == {} assert test_pes_scan.scan == {"stre": ["3 6 1.5 1.9 0.01"]} assert test_pes_scan.molecule == test_molecule diff --git a/pymatgen/io/qchem/utils.py b/pymatgen/io/qchem/utils.py index 2cf9ce92ac5..839875fe7bd 100644 --- a/pymatgen/io/qchem/utils.py +++ b/pymatgen/io/qchem/utils.py @@ -119,10 +119,7 @@ def read_table_pattern( processed_line = [postprocess(v) for v in ml.groups()] table_contents.append(processed_line) tables.append(table_contents) - if last_one_only: - retained_data = tables[-1] - else: - retained_data = tables + retained_data = tables[-1] if last_one_only else tables if attribute_name is not None: data[attribute_name] = retained_data return data diff --git a/pymatgen/io/tests/test_adf.py b/pymatgen/io/tests/test_adf.py index 7953a180b01..2dff7da4f3a 100644 --- a/pymatgen/io/tests/test_adf.py +++ b/pymatgen/io/tests/test_adf.py @@ -79,7 +79,7 @@ def readfile(file_object): file_object : file or str The file to read. This can be either a File object or a file path. - Returns + Returns: ------- content : str The content of the file. diff --git a/pymatgen/io/tests/test_gaussian.py b/pymatgen/io/tests/test_gaussian.py index bed93656d68..3b8537546af 100644 --- a/pymatgen/io/tests/test_gaussian.py +++ b/pymatgen/io/tests/test_gaussian.py @@ -130,7 +130,7 @@ def test_from_string(self): HALF3=11.861807""" gau = GaussianInput.from_string(gau_str) - assert "X3SiH4" == gau.molecule.composition.reduced_formula + assert gau.molecule.composition.reduced_formula == "X3SiH4" def test_gen_basis(self): gau_str = """#N B3LYP/Gen Pseudo=Read @@ -198,7 +198,6 @@ def test_multiple_paramaters(self): def test_no_molecule(self): """Test that we can write input files without a geometry""" - # Makes a file without geometry input_file = GaussianInput(None, charge=0, spin_multiplicity=2) input_str = input_file.to_string().strip() @@ -257,10 +256,10 @@ def test_props(self): for mol in gau.structures: assert mol.formula == "H4 C1" assert "opt" in gau.route_parameters - assert "Minimum" == gau.stationary_type - assert "hf" == gau.functional - assert "3-21G" == gau.basis_set - assert 17 == gau.num_basis_func + assert gau.stationary_type == "Minimum" + assert gau.functional == "hf" + assert gau.basis_set == "3-21G" + assert gau.num_basis_func == 17 d = gau.as_dict() assert d["input"]["functional"] == "hf" assert d["output"]["final_energy"] == approx(-39.9768775602) @@ -375,15 +374,15 @@ def test_scan(self): assert len(d["energies"]) == len(gau.energies) assert len(d["energies"]) == 21 gau = GaussianOutput(os.path.join(test_dir, "so2_scan_opt.log")) - assert 21 == len(gau.opt_structures) + assert len(gau.opt_structures) == 21 d = gau.read_scan() assert -548.02336 == approx(d["energies"][-1]) assert len(d["coords"]) == 2 assert len(d["energies"]) == 21 - assert 1.60000 == approx(d["coords"]["DSO"][6]) - assert 124.01095 == approx(d["coords"]["ASO"][2]) + assert approx(d["coords"]["DSO"][6]) == 1.60000 + assert approx(d["coords"]["ASO"][2]) == 124.01095 gau = GaussianOutput(os.path.join(test_dir, "H2O_scan_G16.out")) - assert 21 == len(gau.opt_structures) + assert len(gau.opt_structures) == 21 coords = [ [0.000000, 0.000000, 0.094168], [0.000000, 0.815522, -0.376673], @@ -394,8 +393,8 @@ def test_scan(self): assert -0.00523 == approx(d["energies"][-1]) assert len(d["coords"]) == 3 assert len(d["energies"]) == 21 - assert 0.94710 == approx(d["coords"]["R1"][6]) - assert 0.94277 == approx(d["coords"]["R2"][17]) + assert approx(d["coords"]["R1"][6]) == 0.94710 + assert approx(d["coords"]["R2"][17]) == 0.94277 def test_geo_opt(self): """ diff --git a/pymatgen/io/tests/test_nwchem.py b/pymatgen/io/tests/test_nwchem.py index d93df9419f1..4d9d1cd2242 100644 --- a/pymatgen/io/tests/test_nwchem.py +++ b/pymatgen/io/tests/test_nwchem.py @@ -406,7 +406,7 @@ def test_read(self): nwo = NwOutput(os.path.join(test_dir, "CH4.nwout")) nwo_cosmo = NwOutput(os.path.join(test_dir, "N2O4.nwout")) - assert 0 == nwo[0]["charge"] + assert nwo[0]["charge"] == 0 assert -1 == nwo[-1]["charge"] assert len(nwo) == 5 assert -1102.6224491715582 == approx(nwo[0]["energies"][-1], abs=1e-2) @@ -438,7 +438,7 @@ def test_read(self): ie = nwo[4]["energies"][-1] - nwo[2]["energies"][-1] ea = nwo[2]["energies"][-1] - nwo[3]["energies"][-1] - assert 0.7575358648355177 == approx(ie) + assert approx(ie) == 0.7575358648355177 assert -14.997877958701338 == approx(ea, abs=1e-3) assert nwo[4]["basis_set"]["C"]["description"] == "6-311++G**" diff --git a/pymatgen/io/tests/test_template_input.py b/pymatgen/io/tests/test_template_input.py index 04e5170230d..c4eb7998b26 100644 --- a/pymatgen/io/tests/test_template_input.py +++ b/pymatgen/io/tests/test_template_input.py @@ -39,7 +39,7 @@ def test_write_inputs(self): # test len, iter, getitem assert len(tis.inputs) == 1 - assert len([i for i in tis.inputs]) == 1 + assert len(list(tis.inputs)) == 1 assert isinstance(tis.inputs["hello_world.in"], str) with pytest.raises(FileExistsError): @@ -52,4 +52,4 @@ def test_write_inputs(self): tis.write_input(scratch_dir, zip_inputs=True) - assert "InputSet.zip" in [f for f in os.listdir(scratch_dir)] + assert "InputSet.zip" in list(os.listdir(scratch_dir)) diff --git a/pymatgen/io/tests/test_wannier90.py b/pymatgen/io/tests/test_wannier90.py index 52e1b8bb846..c0017f90f2f 100644 --- a/pymatgen/io/tests/test_wannier90.py +++ b/pymatgen/io/tests/test_wannier90.py @@ -112,28 +112,28 @@ def test_repr(self): def test_eq(self): # not implemented - assert not self.unk_std == "poop" + assert self.unk_std != "poop" # ng tmp_unk = Unk(1, np.random.rand(10, 5, 5, 4)) - assert not self.unk_std == tmp_unk + assert self.unk_std != tmp_unk # ik tmp_unk = Unk(2, self.data_std) - assert not self.unk_std == tmp_unk + assert self.unk_std != tmp_unk # noncol - assert not self.unk_std == self.unk_ncl + assert self.unk_std != self.unk_ncl # nbnd tmp_unk = Unk(1, np.random.rand(9, 5, 5, 5)) - assert not self.unk_std == tmp_unk + assert self.unk_std != tmp_unk # data tmp_unk = Unk(1, np.random.rand(10, 5, 5, 5)) - assert not self.unk_std == tmp_unk + assert self.unk_std != tmp_unk tmp_unk = Unk(1, np.random.rand(10, 2, 5, 5, 5)) - assert not self.unk_ncl == tmp_unk + assert self.unk_ncl != tmp_unk # same assert self.unk_std == self.unk_std diff --git a/pymatgen/io/tests/test_zeopp.py b/pymatgen/io/tests/test_zeopp.py index 2c9da89ebf8..df66d7fd620 100644 --- a/pymatgen/io/tests/test_zeopp.py +++ b/pymatgen/io/tests/test_zeopp.py @@ -256,11 +256,9 @@ def setUp(self): print((el, self.rad_dict[el].real)) def test_get_voronoi_nodes(self): - ( - vor_node_struct, - vor_edge_center_struct, - vor_face_center_struct, - ) = get_voronoi_nodes(self.structure, self.rad_dict) + vor_node_struct, vor_edge_center_struct, vor_face_center_struct = get_voronoi_nodes( + self.structure, self.rad_dict + ) assert isinstance(vor_node_struct, Structure) assert isinstance(vor_edge_center_struct, Structure) assert isinstance(vor_face_center_struct, Structure) diff --git a/pymatgen/io/vasp/inputs.py b/pymatgen/io/vasp/inputs.py index 983e81ad577..1252c71c87e 100644 --- a/pymatgen/io/vasp/inputs.py +++ b/pymatgen/io/vasp/inputs.py @@ -192,13 +192,12 @@ def natoms(self): return [len(tuple(a[1])) for a in itertools.groupby(syms)] def __setattr__(self, name, value): - if name in ("selective_dynamics", "velocities"): - if value is not None and len(value) > 0: - value = np.array(value) - dim = value.shape - if dim[1] != 3 or dim[0] != len(self.structure): - raise ValueError(name + " array must be same length as the structure.") - value = value.tolist() + if name in ("selective_dynamics", "velocities") and value is not None and len(value) > 0: + value = np.array(value) + dim = value.shape + if dim[1] != 3 or dim[0] != len(self.structure): + raise ValueError(name + " array must be same length as the structure.") + value = value.tolist() super().__setattr__(name, value) @staticmethod @@ -924,9 +923,8 @@ def diff(self, other: Incar) -> dict[str, dict[str, Any]]: else: similar_param[k1] = v1 for k2, v2 in other.items(): - if k2 not in similar_param and k2 not in different_param: - if k2 not in self: - different_param[k2] = {"INCAR1": None, "INCAR2": v2} + if k2 not in similar_param and k2 not in different_param and k2 not in self: + different_param[k2] = {"INCAR1": None, "INCAR2": v2} return {"Same": similar_param, "Different": different_param} def __add__(self, other): @@ -976,13 +974,12 @@ def check_params(self): # if we have a list of possible parameters, check # if the user given parameter is in this list - elif type(incar_params[k]).__name__ == "list": - if v not in incar_params[k]: - warnings.warn( - f"{k}: Cannot find {v} in the list of parameters", - BadIncarWarning, - stacklevel=2, - ) + elif type(incar_params[k]).__name__ == "list" and v not in incar_params[k]: + warnings.warn( + f"{k}: Cannot find {v} in the list of parameters", + BadIncarWarning, + stacklevel=2, + ) class Kpoints_supported_modes(Enum): @@ -1876,10 +1873,7 @@ def from_file(filename: str) -> PotcarSingle: :return: PotcarSingle. """ match = re.search(r"(?<=POTCAR\.)(.*)(?=.gz)", str(filename)) - if match: - symbol = match.group(0) - else: - symbol = "" + symbol = match.group(0) if match else "" try: with zopen(filename, "rt") as f: @@ -1995,7 +1989,7 @@ def verify_potcar(self) -> tuple[bool, bool]: If no SHA256 hash is found in the file, the file hash (md5 hash of the whole file) is checked against all POTCAR file hashes known to pymatgen. - Returns + Returns: ------- (bool, bool) has_sh256 and passed_hash_check are returned. @@ -2003,20 +1997,14 @@ def verify_potcar(self) -> tuple[bool, bool]: """ if hasattr(self, "SHA256"): has_sha256 = True - if self.hash_sha256_from_file == self.hash_sha256_computed: - passed_hash_check = True - else: - passed_hash_check = False + passed_hash_check = self.hash_sha256_from_file == self.hash_sha256_computed else: has_sha256 = False # if no sha256 hash is found in the POTCAR file, compare the whole # file with known potcar file hashes. md5_file_hash = self.file_hash hash_db = loadfn(os.path.join(cwd, "vasp_potcar_file_hashes.json")) - if md5_file_hash in hash_db: - passed_hash_check = True - else: - passed_hash_check = False + passed_hash_check = md5_file_hash in hash_db return (has_sha256, passed_hash_check) def identify_potcar(self, mode: Literal["data", "file"] = "data"): @@ -2489,6 +2477,5 @@ def run_vasp( vasp_cmd = [os.path.expanduser(os.path.expandvars(t)) for t in vasp_cmd] if not vasp_cmd: raise RuntimeError("You need to supply vasp_cmd or set the PMG_VASP_EXE in .pmgrc.yaml to run VASP.") - with cd(run_dir): - with open(output_file, "w") as f_std, open(err_file, "w", buffering=1) as f_err: - subprocess.check_call(vasp_cmd, stdout=f_std, stderr=f_err) + with cd(run_dir), open(output_file, "w") as f_std, open(err_file, "w", buffering=1) as f_err: + subprocess.check_call(vasp_cmd, stdout=f_std, stderr=f_err) diff --git a/pymatgen/io/vasp/optics.py b/pymatgen/io/vasp/optics.py index 99f7b54d6a5..042258826ba 100644 --- a/pymatgen/io/vasp/optics.py +++ b/pymatgen/io/vasp/optics.py @@ -212,10 +212,7 @@ def plot_weighted_transition_data( index to include in the calculation min_val: Minimum value below this value the matrix element will not be shown. """ - if mask is not None: - cderm = self.cder * mask - else: - cderm = self.cder + cderm = self.cder * mask if mask is not None else self.cder norm_kweights = np.array(self.kweights) / np.sum(self.kweights) eigs_shifted = self.eigs - self.efermi @@ -388,10 +385,7 @@ def epsilon_imag( # for the transition between two bands at one kpoint the contributions is: # (fermi[band_i] - fermi[band_j]) * rspin * normalized_kpoint_weight - if mask is not None: - cderm = cder * mask - else: - cderm = cder + cderm = cder * mask if mask is not None else cder # min_band0, max_band0 = np.min(np.where(cderm)[0]), np.max(np.where(cderm)[0]) # min_band1, max_band1 = np.min(np.where(cderm)[1]), np.max(np.where(cderm)[1]) diff --git a/pymatgen/io/vasp/outputs.py b/pymatgen/io/vasp/outputs.py index 756056cf4c6..c51ca364837 100644 --- a/pymatgen/io/vasp/outputs.py +++ b/pymatgen/io/vasp/outputs.py @@ -120,9 +120,7 @@ def _parse_varray(elem): def _parse_from_incar(filename, key): - """ - Helper function to parse a parameter from the INCAR. - """ + """Helper function to parse a parameter from the INCAR.""" dirname = os.path.dirname(filename) for f in os.listdir(dirname): if re.search(r"INCAR", f): @@ -432,11 +430,7 @@ def _parse(self, stream, parse_dos, parse_eigen, parse_projected_eigen): self.incar = self._parse_params(elem) elif tag == "kpoints": if not hasattr(self, "kpoints"): - ( - self.kpoints, - self.actual_kpoints, - self.actual_kpoints_weights, - ) = self._parse_kpoints(elem) + self.kpoints, self.actual_kpoints, self.actual_kpoints_weights = self._parse_kpoints(elem) elif tag == "parameters": self.parameters = self._parse_params(elem) elif tag == "structure" and elem.attrib.get("name") == "initialpos": @@ -460,10 +454,7 @@ def _parse(self, stream, parse_dos, parse_eigen, parse_projected_eigen): elif parse_eigen and tag == "eigenvalues": self.eigenvalues = self._parse_eigen(elem) elif parse_projected_eigen and tag == "projected": - ( - self.projected_eigenvalues, - self.projected_magnetisation, - ) = self._parse_projected_eigen(elem) + self.projected_eigenvalues, self.projected_magnetisation = self._parse_projected_eigen(elem) elif tag == "dielectricfunction": if ( "comment" not in elem.attrib @@ -624,11 +615,8 @@ def converged_electronic(self): True if electronic step convergence has been reached in the final ionic step """ - if self.incar not in ["CHI"]: - final_esteps = self.ionic_steps[-1]["electronic_steps"] - else: - final_esteps = 0 - # In a response function run there is no ionic steps, there is no scf step + final_esteps = self.ionic_steps[-1]["electronic_steps"] if self.incar not in ["CHI"] else 0 + # In a response function run there is no ionic steps, there is no scf step if "LEPSILON" in self.incar and self.incar["LEPSILON"]: i = 1 to_check = {"e_wo_entrp", "e_fr_energy", "e_0_energy"} @@ -870,8 +858,7 @@ def get_band_structure( line_mode: bool = False, force_hybrid_mode: bool = False, ): - """ - Returns the band structure as a BandStructure object + """Get the band structure as a BandStructure object. Args: kpoints_filename: Full path of the KPOINTS file from which @@ -907,9 +894,8 @@ def get_band_structure( """ if not kpoints_filename: kpoints_filename = zpath(os.path.join(os.path.dirname(self.filename), "KPOINTS")) - if kpoints_filename: - if not os.path.exists(kpoints_filename) and line_mode is True: - raise VaspParserError("KPOINTS needed to obtain band structure along symmetry lines.") + if kpoints_filename and not os.path.exists(kpoints_filename) and line_mode is True: + raise VaspParserError("KPOINTS needed to obtain band structure along symmetry lines.") if efermi == "smart": e_fermi = self.calculate_efermi() @@ -918,7 +904,7 @@ def get_band_structure( else: e_fermi = efermi - kpoint_file = None + kpoint_file: Kpoints = None # type: ignore if kpoints_filename and os.path.exists(kpoints_filename): kpoint_file = Kpoints.from_file(kpoints_filename) lattice_new = Lattice(self.final_structure.lattice.reciprocal_lattice.matrix) @@ -954,9 +940,8 @@ def get_band_structure( if self.parameters.get("LHFCALC", False) or 0.0 in self.actual_kpoints_weights: hybrid_band = True - if kpoint_file is not None: - if kpoint_file.style == Kpoints.supported_modes.Line_mode: - line_mode = True + if kpoint_file is not None and kpoint_file.style == Kpoints.supported_modes.Line_mode: + line_mode = True if line_mode: labels_dict = {} @@ -1208,7 +1193,7 @@ def as_dict(self): d["is_hubbard"] = self.is_hubbard d["hubbards"] = self.hubbards - unique_symbols = sorted(list(set(self.atomic_symbols))) + unique_symbols = sorted(set(self.atomic_symbols)) d["elements"] = unique_symbols d["nelements"] = len(unique_symbols) @@ -1258,7 +1243,7 @@ def as_dict(self): eigen = {str(spin): v.tolist() for spin, v in self.eigenvalues.items()} vout["eigenvalues"] = eigen (gap, cbm, vbm, is_direct) = self.eigenvalue_band_properties - vout.update(dict(bandgap=gap, cbm=cbm, vbm=vbm, is_gap_direct=is_direct)) + vout.update({"bandgap": gap, "cbm": cbm, "vbm": vbm, "is_gap_direct": is_direct}) if self.projected_eigenvalues: vout["projected_eigenvalues"] = { @@ -1470,10 +1455,7 @@ def _parse_dos(elem): data = np.array(_parse_varray(ss)) nrow, ncol = data.shape for j in range(1, ncol): - if lm: - orb = Orbital(j - 1) - else: - orb = OrbitalType(j - 1) + orb = Orbital(j - 1) if lm else OrbitalType(j - 1) pdos[orb][spin] = data[:, j] pdoss.append(pdos) elem.clear() @@ -1636,7 +1618,7 @@ def as_dict(self): d["is_hubbard"] = self.is_hubbard d["hubbards"] = self.hubbards - unique_symbols = sorted(list(set(self.atomic_symbols))) + unique_symbols = sorted(set(self.atomic_symbols)) d["elements"] = unique_symbols d["nelements"] = len(unique_symbols) @@ -1671,7 +1653,7 @@ def as_dict(self): eigen[i][str(spin)] = v vout["eigenvalues"] = eigen (gap, cbm, vbm, is_direct) = self.eigenvalue_band_properties - vout.update(dict(bandgap=gap, cbm=cbm, vbm=vbm, is_gap_direct=is_direct)) + vout.update({"bandgap": gap, "cbm": cbm, "vbm": vbm, "is_gap_direct": is_direct}) if self.projected_eigenvalues: peigen = [{} for _ in eigen] @@ -2241,10 +2223,7 @@ def read_table_pattern( tables.append(table_contents) if first_one_only: break - if last_one_only or first_one_only: - retained_data = tables[-1] - else: - retained_data = tables + retained_data = tables[-1] if last_one_only or first_one_only else tables if attribute_name is not None: self.data[attribute_name] = retained_data return retained_data @@ -4197,7 +4176,7 @@ def __init__(self, filename, ionicstep_start=1, ionicstep_end=None, comment=None title = l elif title == l: preamble_done = False - p = Poscar.from_string("\n".join(preamble + ["Direct"] + coords_str)) + p = Poscar.from_string("\n".join([*preamble, "Direct", *coords_str])) if ionicstep_end is None: if ionicstep_cnt >= ionicstep_start: structures.append(p.structure) @@ -4222,7 +4201,7 @@ def __init__(self, filename, ionicstep_start=1, ionicstep_end=None, comment=None else: preamble.append(l) elif l == "" or "Direct configuration=" in l: - p = Poscar.from_string("\n".join(preamble + ["Direct"] + coords_str)) + p = Poscar.from_string("\n".join([*preamble, "Direct", *coords_str])) if ionicstep_end is None: if ionicstep_cnt >= ionicstep_start: structures.append(p.structure) @@ -4235,7 +4214,7 @@ def __init__(self, filename, ionicstep_start=1, ionicstep_end=None, comment=None coords_str = [] else: coords_str.append(l) - p = Poscar.from_string("\n".join(preamble + ["Direct"] + coords_str)) + p = Poscar.from_string("\n".join([*preamble, "Direct", *coords_str])) if ionicstep_end is None: if ionicstep_cnt >= ionicstep_start: structures.append(p.structure) @@ -4304,7 +4283,7 @@ def concatenate(self, filename, ionicstep_start=1, ionicstep_end=None): else: preamble.append(l) elif l == "" or "Direct configuration=" in l: - p = Poscar.from_string("\n".join(preamble + ["Direct"] + coords_str)) + p = Poscar.from_string("\n".join([*preamble, "Direct", *coords_str])) if ionicstep_end is None: if ionicstep_cnt >= ionicstep_start: structures.append(p.structure) @@ -4315,7 +4294,7 @@ def concatenate(self, filename, ionicstep_start=1, ionicstep_end=None): coords_str = [] else: coords_str.append(l) - p = Poscar.from_string("\n".join(preamble + ["Direct"] + coords_str)) + p = Poscar.from_string("\n".join([*preamble, "Direct", *coords_str])) if ionicstep_end is None: if ionicstep_cnt >= ionicstep_start: structures.append(p.structure) @@ -4828,10 +4807,7 @@ def _generate_G_points(self, kpoint: np.ndarray, gamma: bool = False) -> tuple[l Returns: a list containing valid G-points """ - if gamma: - kmax = self._nbmax[0] + 1 - else: - kmax = 2 * self._nbmax[0] + 1 + kmax = self._nbmax[0] + 1 if gamma else 2 * self._nbmax[0] + 1 gpoints = [] extra_gpoints = [] @@ -4849,7 +4825,7 @@ def _generate_G_points(self, kpoint: np.ndarray, gamma: bool = False) -> tuple[l v = kpoint + G g = np.linalg.norm(np.dot(v, self.b)) E = g**2 / self._C - if E < self.encut: + if self.encut > E: gpoints.append(G) if gamma and (k1, j2, i3) != (0, 0, 0): extra_gpoints.append(-G) diff --git a/pymatgen/io/vasp/sets.py b/pymatgen/io/vasp/sets.py index ec4b45eb195..9ec8ea38416 100644 --- a/pymatgen/io/vasp/sets.py +++ b/pymatgen/io/vasp/sets.py @@ -575,9 +575,8 @@ def incar(self) -> Incar: # correct overly large KSPACING values (small number of kpoints) # if necessary. # if "KSPACING" not in self.user_incar_settings: - if self.kpoints is not None: - if np.product(self.kpoints.kpts) < 4 and incar.get("ISMEAR", 0) == -5: - incar["ISMEAR"] = 0 + if self.kpoints is not None and np.product(self.kpoints.kpts) < 4 and incar.get("ISMEAR", 0) == -5: + incar["ISMEAR"] = 0 if self.user_incar_settings.get("KSPACING", 0) > 0.5 and incar.get("ISMEAR", 0) == -5: warnings.warn( @@ -587,14 +586,13 @@ def incar(self) -> Incar: BadInputSetWarning, ) - if all(k.is_metal for k in structure.composition): - if incar.get("NSW", 0) > 0 and incar.get("ISMEAR", 1) < 1: - warnings.warn( - "Relaxation of likely metal with ISMEAR < 1 " - "detected. Please see VASP recommendations on " - "ISMEAR for metals.", - BadInputSetWarning, - ) + if all(k.is_metal for k in structure.composition) and incar.get("NSW", 0) > 0 and incar.get("ISMEAR", 1) < 1: + warnings.warn( + "Relaxation of likely metal with ISMEAR < 1 " + "detected. Please see VASP recommendations on " + "ISMEAR for metals.", + BadInputSetWarning, + ) return incar @@ -976,18 +974,15 @@ def __init__(self, structure: Structure, bandgap=0, **kwargs): if self.user_incar_settings.get("SIGMA"): del updates["SIGMA"] - if self.vdw: - if self.vdw != "rvv10": - warnings.warn( - "Use of van der waals functionals other than rVV10 with SCAN is not supported at this time. " - ) - # delete any vdw parameters that may have been added to the INCAR - vdw_par = loadfn(str(MODULE_DIR / "vdW_parameters.yaml")) - for k in vdw_par[self.vdw]: - try: - del self._config_dict["INCAR"][k] - except KeyError: - pass + if self.vdw and self.vdw != "rvv10": + warnings.warn("Use of van der waals functionals other than rVV10 with SCAN is not supported at this time. ") + # delete any vdw parameters that may have been added to the INCAR + vdw_par = loadfn(str(MODULE_DIR / "vdW_parameters.yaml")) + for k in vdw_par[self.vdw]: + try: + del self._config_dict["INCAR"][k] + except KeyError: + pass self._config_dict["INCAR"].update(updates) @@ -1117,7 +1112,7 @@ def incar(self): if self.lcalcpol: incar["LCALCPOL"] = True - for k in ["MAGMOM", "NUPDOWN"] + list(self.user_incar_settings): + for k in ["MAGMOM", "NUPDOWN", *self.user_incar_settings]: # For these parameters as well as user specified settings, override # the incar settings. if parent_incar.get(k, None) is not None: @@ -1152,13 +1147,12 @@ def kpoints(self) -> Kpoints | None: # Prefer to use k-point scheme from previous run # except for when lepsilon = True is specified - if kpoints is not None: - if self.prev_kpoints and self.prev_kpoints.style != kpoints.style: - if (self.prev_kpoints.style == Kpoints.supported_modes.Monkhorst) and (not self.lepsilon): - k_div = [kp + 1 if kp % 2 == 1 else kp for kp in kpoints.kpts[0]] # type: ignore - kpoints = Kpoints.monkhorst_automatic(k_div) # type: ignore - else: - kpoints = Kpoints.gamma_automatic(kpoints.kpts[0]) # type: ignore + if kpoints is not None and self.prev_kpoints and self.prev_kpoints.style != kpoints.style: + if (self.prev_kpoints.style == Kpoints.supported_modes.Monkhorst) and (not self.lepsilon): + k_div = [kp + 1 if kp % 2 == 1 else kp for kp in kpoints.kpts[0]] # type: ignore + kpoints = Kpoints.monkhorst_automatic(k_div) # type: ignore + else: + kpoints = Kpoints.gamma_automatic(kpoints.kpts[0]) # type: ignore return kpoints def override_from_prev_calc(self, prev_calc_dir="."): @@ -3067,10 +3061,7 @@ def get_valid_magmom_struct(structure, inplace=True, spin_mode="auto"): else: mode = spin_mode[0].lower() - if not inplace: - new_struct = structure.copy() - else: - new_struct = structure + new_struct = structure.copy() if not inplace else structure for isite in new_struct.sites: if mode == "n": if "magmom" in isite.properties: diff --git a/pymatgen/io/vasp/tests/test_outputs.py b/pymatgen/io/vasp/tests/test_outputs.py index 494d19b0fc3..538b33c9687 100644 --- a/pymatgen/io/vasp/tests/test_outputs.py +++ b/pymatgen/io/vasp/tests/test_outputs.py @@ -217,7 +217,7 @@ def test_standard(self): pdos0_norm = vasprun.complete_dos_normalized.pdos[vasprun.final_structure[0]] self.assertAlmostEqual(pdos0_norm[Orbital.s][Spin.up][16], 0.0026) # the site data should not change - self.assertEqual(pdos0_norm[Orbital.s][Spin.up].shape, (301,)) + assert pdos0_norm[Orbital.s][Spin.up].shape == (301,) cdos_norm, cdos = vasprun.complete_dos_normalized, vasprun.complete_dos ratio = np.nanmax(cdos.densities[Spin.up] / cdos_norm.densities[Spin.up]) @@ -226,7 +226,7 @@ def test_standard(self): filepath2 = self.TEST_FILES_DIR / "lifepo4.xml" vasprun_ggau = Vasprun(filepath2, parse_projected_eigen=True, parse_potcar_file=False) totalscsteps = sum(len(i["electronic_steps"]) for i in vasprun.ionic_steps) - assert 29 == len(vasprun.ionic_steps) + assert len(vasprun.ionic_steps) == 29 assert len(vasprun.structures) == len(vasprun.ionic_steps) trajectory = vasprun.get_trajectory() @@ -240,7 +240,7 @@ def test_standard(self): vasprun.structures[i] == vasprun.ionic_steps[i]["structure"] for i in range(len(vasprun.ionic_steps)) ) - assert 308 == totalscsteps, "Incorrect number of energies read from vasprun.xml" + assert totalscsteps == 308, "Incorrect number of energies read from vasprun.xml" assert ["Li"] + 4 * ["Fe"] + 4 * ["P"] + 16 * ["O"] == vasprun.atomic_symbols assert vasprun.final_structure.composition.reduced_formula == "LiFe4(PO4)4" @@ -357,15 +357,15 @@ def test_no_projected(self): def test_dielectric(self): vasprun_diel = Vasprun(self.TEST_FILES_DIR / "vasprun.xml.dielectric", parse_potcar_file=False) - assert 0.4294 == approx(vasprun_diel.dielectric[0][10]) - assert 19.941 == approx(vasprun_diel.dielectric[1][51][0]) - assert 19.941 == approx(vasprun_diel.dielectric[1][51][1]) - assert 19.941 == approx(vasprun_diel.dielectric[1][51][2]) - assert 0.0 == approx(vasprun_diel.dielectric[1][51][3]) - assert 34.186 == approx(vasprun_diel.dielectric[2][85][0]) - assert 34.186 == approx(vasprun_diel.dielectric[2][85][1]) - assert 34.186 == approx(vasprun_diel.dielectric[2][85][2]) - assert 0.0 == approx(vasprun_diel.dielectric[2][85][3]) + assert approx(vasprun_diel.dielectric[0][10]) == 0.4294 + assert approx(vasprun_diel.dielectric[1][51][0]) == 19.941 + assert approx(vasprun_diel.dielectric[1][51][1]) == 19.941 + assert approx(vasprun_diel.dielectric[1][51][2]) == 19.941 + assert approx(vasprun_diel.dielectric[1][51][3]) == 0.0 + assert approx(vasprun_diel.dielectric[2][85][0]) == 34.186 + assert approx(vasprun_diel.dielectric[2][85][1]) == 34.186 + assert approx(vasprun_diel.dielectric[2][85][2]) == 34.186 + assert approx(vasprun_diel.dielectric[2][85][3]) == 0.0 def test_dielectric_vasp608(self): # test reading dielectric constant in vasp 6.0.8 @@ -373,12 +373,12 @@ def test_dielectric_vasp608(self): self.TEST_FILES_DIR / "vasprun.xml.dielectric_6.0.8", parse_potcar_file=False, ) - assert 0.4338 == approx(vasprun_diel.dielectric[0][10]) - assert 5.267 == approx(vasprun_diel.dielectric[1][51][0]) - assert 0.4338 == approx(vasprun_diel.dielectric_data["density"][0][10]) - assert 5.267 == approx(vasprun_diel.dielectric_data["density"][1][51][0]) - assert 0.4338 == approx(vasprun_diel.dielectric_data["velocity"][0][10]) - assert 1.0741 == approx(vasprun_diel.dielectric_data["velocity"][1][51][0]) + assert approx(vasprun_diel.dielectric[0][10]) == 0.4338 + assert approx(vasprun_diel.dielectric[1][51][0]) == 5.267 + assert approx(vasprun_diel.dielectric_data["density"][0][10]) == 0.4338 + assert approx(vasprun_diel.dielectric_data["density"][1][51][0]) == 5.267 + assert approx(vasprun_diel.dielectric_data["velocity"][0][10]) == 0.4338 + assert approx(vasprun_diel.dielectric_data["velocity"][1][51][0]) == 1.0741 assert len(vasprun_diel.other_dielectric) == 0 def test_indirect_vasprun(self): @@ -391,18 +391,18 @@ def test_optical_vasprun(self): self.TEST_FILES_DIR / "vasprun.xml.opticaltransitions", parse_potcar_file=False, ) - assert 3.084 == approx(vasprun_optical.optical_transition[0][0]) - assert 3.087 == approx(vasprun_optical.optical_transition[3][0]) - assert 0.001 == approx(vasprun_optical.optical_transition[0][1]) - assert 0.001 == approx(vasprun_optical.optical_transition[1][1]) - assert 0.001 == approx(vasprun_optical.optical_transition[7][1]) - assert 0.001 == approx(vasprun_optical.optical_transition[19][1]) - assert 3.3799999999 == approx(vasprun_optical.optical_transition[54][0]) - assert 3.381 == approx(vasprun_optical.optical_transition[55][0]) - assert 3.381 == approx(vasprun_optical.optical_transition[56][0]) - assert 10554.9860 == approx(vasprun_optical.optical_transition[54][1]) - assert 0.0 == approx(vasprun_optical.optical_transition[55][1]) - assert 0.001 == approx(vasprun_optical.optical_transition[56][1]) + assert approx(vasprun_optical.optical_transition[0][0]) == 3.084 + assert approx(vasprun_optical.optical_transition[3][0]) == 3.087 + assert approx(vasprun_optical.optical_transition[0][1]) == 0.001 + assert approx(vasprun_optical.optical_transition[1][1]) == 0.001 + assert approx(vasprun_optical.optical_transition[7][1]) == 0.001 + assert approx(vasprun_optical.optical_transition[19][1]) == 0.001 + assert approx(vasprun_optical.optical_transition[54][0]) == 3.3799999999 + assert approx(vasprun_optical.optical_transition[55][0]) == 3.381 + assert approx(vasprun_optical.optical_transition[56][0]) == 3.381 + assert approx(vasprun_optical.optical_transition[54][1]) == 10554.9860 + assert approx(vasprun_optical.optical_transition[55][1]) == 0.0 + assert approx(vasprun_optical.optical_transition[56][1]) == 0.001 def test_force_constants(self): vasprun_fc = Vasprun(self.TEST_FILES_DIR / "vasprun.xml.dfpt.phonon", parse_potcar_file=False) @@ -1540,9 +1540,9 @@ def test_write(self): with open("CHGCAR_pmg") as f: for i, line in enumerate(f): if i == 22130: - assert "augmentation occupancies 1 15\n" == line + assert line == "augmentation occupancies 1 15\n" if i == 44255: - assert "augmentation occupancies 1 15\n" == line + assert line == "augmentation occupancies 1 15\n" os.remove("CHGCAR_pmg") def test_soc_chgcar(self): @@ -1626,8 +1626,8 @@ def test_as_dict_and_from_dict(self): class ElfcarTest(PymatgenTest): def test_init(self): elfcar = Elfcar.from_file(self.TEST_FILES_DIR / "ELFCAR.gz") - assert 0.19076207645194002 == approx(np.mean(elfcar.data["total"])) - assert 0.19076046677910055 == approx(np.mean(elfcar.data["diff"])) + assert approx(np.mean(elfcar.data["total"])) == 0.19076207645194002 + assert approx(np.mean(elfcar.data["diff"])) == 0.19076046677910055 reconstituted = Elfcar.from_dict(elfcar.as_dict()) assert elfcar.data == reconstituted.data assert elfcar.poscar.structure == reconstituted.poscar.structure @@ -1635,12 +1635,12 @@ def test_init(self): def test_alpha(self): elfcar = Elfcar.from_file(self.TEST_FILES_DIR / "ELFCAR.gz") alpha = elfcar.get_alpha() - assert 2.936678808979031 == approx(np.median(alpha.data["total"])) + assert approx(np.median(alpha.data["total"])) == 2.936678808979031 def test_interpolation(self): elfcar = Elfcar.from_file(self.TEST_FILES_DIR / "ELFCAR.gz") - assert 0.0918471 == approx(elfcar.value_at(0.4, 0.5, 0.6)) - assert 100 == len(elfcar.linear_slice([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])) + assert approx(elfcar.value_at(0.4, 0.5, 0.6)) == 0.0918471 + assert len(elfcar.linear_slice([0.0, 0.0, 0.0], [1.0, 1.0, 1.0])) == 100 class ProcarTest(PymatgenTest): diff --git a/pymatgen/io/vasp/tests/test_sets.py b/pymatgen/io/vasp/tests/test_sets.py index 9bf7d580b23..119bde4b31f 100644 --- a/pymatgen/io/vasp/tests/test_sets.py +++ b/pymatgen/io/vasp/tests/test_sets.py @@ -371,7 +371,7 @@ def test_get_incar(self): # test that NELECT does not get set when use_structure_charge = False mpr = MPRelaxSet(struct, use_structure_charge=False) - assert not ("NELECT" in mpr.incar), "NELECT should not be set when use_structure_charge is False" + assert "NELECT" not in mpr.incar, "NELECT should not be set when use_structure_charge is False" struct = Structure(latt, ["Co", "O"], coords) mpr = MPRelaxSet(struct) diff --git a/pymatgen/io/xcrysden.py b/pymatgen/io/xcrysden.py index 8e10b773a68..be672278779 100644 --- a/pymatgen/io/xcrysden.py +++ b/pymatgen/io/xcrysden.py @@ -50,10 +50,7 @@ def to_string(self, atom_symbol=True): app(f" {len(cart_coords)} 1") for site, coord in zip(self.structure, cart_coords): - if atom_symbol: - sp = site.specie.symbol - else: - sp = f"{site.specie.Z}" + sp = site.specie.symbol if atom_symbol else f"{site.specie.Z}" x, y, z = coord app(f"{sp} {x:20.14f} {y:20.14f} {z:20.14f}") @@ -101,10 +98,7 @@ def from_string(cls, input_string, cls_=None): for j in range(i + 2, i + 2 + num_sites): tokens = lines[j].split() - if tokens[0].isalpha(): - Z = Element(tokens[0]).Z - else: - Z = int(tokens[0]) + Z = Element(tokens[0]).Z if tokens[0].isalpha() else int(tokens[0]) species.append(Z) coords.append([float(j) for j in tokens[1:4]]) break diff --git a/pymatgen/io/xtb/outputs.py b/pymatgen/io/xtb/outputs.py index be3f8a30ecb..4f4277d253f 100644 --- a/pymatgen/io/xtb/outputs.py +++ b/pymatgen/io/xtb/outputs.py @@ -32,6 +32,7 @@ class CRESTOutput(MSONable): def __init__(self, output_filename, path="."): """ Assumes runtype is iMTD-GC [default] + Args: output_filename (str): Filename to parse path (str): Path to directory including output_filename and all @@ -78,27 +79,19 @@ def _parse_crest_output(self): # Get CREST input flags for i, entry in enumerate(split_cmd): value = None - if entry: - if "-" in entry: - option = entry[1:] - if i + 1 < len(split_cmd): - if "-" not in split_cmd[i + 1]: - value = split_cmd[i + 1] - self.cmd_options[option] = value + if entry and "-" in entry: + option = entry[1:] + if i + 1 < len(split_cmd) and "-" not in split_cmd[i + 1]: + value = split_cmd[i + 1] + self.cmd_options[option] = value # Get input charge for decorating parsed molecules chg = 0 if "chrg" in self.cmd_options: str_chg = self.cmd_options["chrg"] - if "-" in str_chg: - chg = int(str_chg) - else: - chg = int(str_chg[-1]) + chg = int(str_chg) if "-" in str_chg else int(str_chg[-1]) elif "c" in self.cmd_options: str_chg = self.cmd_options["c"] - if "-" in str_chg: - chg = int(str_chg) - else: - chg = int(str_chg[-1]) + chg = int(str_chg) if "-" in str_chg else int(str_chg[-1]) # Check for proper termination with open(output_filepath, "rb+") as xtbout_file: diff --git a/pymatgen/io/xtb/tests/test_inputs.py b/pymatgen/io/xtb/tests/test_inputs.py index ac32c94d9a2..6b31d5041bd 100644 --- a/pymatgen/io/xtb/tests/test_inputs.py +++ b/pymatgen/io/xtb/tests/test_inputs.py @@ -34,7 +34,7 @@ def test_coordinates_file(self): cin = CRESTInput(molecule=mol, coords_filename="crest_in.xyz") assert mol.as_dict() == cin.molecule.as_dict() - assert "crest_in.xyz" == cin.coords_filename + assert cin.coords_filename == "crest_in.xyz" def test_constraints_file(self): constraints = {"atoms": [8, 1, 2], "force_constant": 0.5} diff --git a/pymatgen/io/zeopp.py b/pymatgen/io/zeopp.py index 3a8bf7231d3..e5f9d316525 100644 --- a/pymatgen/io/zeopp.py +++ b/pymatgen/io/zeopp.py @@ -243,7 +243,6 @@ def get_voronoi_nodes(structure, rad_dict=None, probe_rad=0.1): voronoi face centers as pymatgen.core.structure.Structure within the unit cell defined by the lattice of input structure """ - with ScratchDir("."): name = "temp_zeo1" zeo_inp_filename = name + ".cssr" @@ -335,7 +334,6 @@ def get_high_accuracy_voronoi_nodes(structure, rad_dict, probe_rad=0.1): voronoi face centers as pymatgen.core.structure.Structure within the unit cell defined by the lattice of input structure """ - with ScratchDir("."): name = "temp_zeo1" zeo_inp_filename = name + ".cssr" @@ -401,7 +399,6 @@ def get_free_sphere_params(structure, rad_dict=None, probe_rad=0.1): voronoi face centers as pymatgen.core.structure.Structure within the unit cell defined by the lattice of input structure """ - with ScratchDir("."): name = "temp_zeo1" zeo_inp_filename = name + ".cssr" diff --git a/pymatgen/phonon/bandstructure.py b/pymatgen/phonon/bandstructure.py index d6809d4804f..dc9b9808c1f 100644 --- a/pymatgen/phonon/bandstructure.py +++ b/pymatgen/phonon/bandstructure.py @@ -262,12 +262,12 @@ def as_dict(self): d["labels_dict"][kpoint_letter] = kpoint_object.as_dict()["fcoords"] # split the eigendisplacements to real and imaginary part for serialization - d["eigendisplacements"] = dict( - real=np.real(self.eigendisplacements).tolist(), - imag=np.imag(self.eigendisplacements).tolist(), - ) + d["eigendisplacements"] = { + "real": np.real(self.eigendisplacements).tolist(), + "imag": np.imag(self.eigendisplacements).tolist(), + } d["nac_eigendisplacements"] = [ - (direction, dict(real=np.real(e).tolist(), imag=np.imag(e).tolist())) + (direction, {"real": np.real(e).tolist(), "imag": np.imag(e).tolist()}) for direction, e in self.nac_eigendisplacements ] d["nac_frequencies"] = [(direction, f.tolist()) for direction, f in self.nac_frequencies] @@ -377,11 +377,10 @@ def _reuse_init(self, eigendisplacements, frequencies, has_nac, qpoints): ) previous_qpoint = self.qpoints[i] previous_distance = self.distance[i] - if label: - if previous_label: - if len(one_group) != 0: - branches_tmp.append(one_group) - one_group = [] + if label and previous_label: + if len(one_group) != 0: + branches_tmp.append(one_group) + one_group = [] previous_label = label one_group.append(i) if len(one_group) != 0: diff --git a/pymatgen/phonon/gruneisen.py b/pymatgen/phonon/gruneisen.py index ffbb46ce7be..9716eba9d93 100644 --- a/pymatgen/phonon/gruneisen.py +++ b/pymatgen/phonon/gruneisen.py @@ -209,6 +209,7 @@ def debye_temp_limit(self): def debye_temp_phonopy(self, freq_max_fit=None): """ Get Debye temperature in K as implemented in phonopy. + Args: freq_max_fit: Maximum frequency to include for fitting. Defaults to include first quartile of frequencies. @@ -312,9 +313,10 @@ def as_dict(self): for kpoint_letter, kpoint_object in self.labels_dict.items(): d["labels_dict"][kpoint_letter] = kpoint_object.as_dict()["fcoords"] # split the eigendisplacements to real and imaginary part for serialization - d["eigendisplacements"] = dict( - real=np.real(self.eigendisplacements).tolist(), imag=np.imag(self.eigendisplacements).tolist() - ) + d["eigendisplacements"] = { + "real": np.real(self.eigendisplacements).tolist(), + "imag": np.imag(self.eigendisplacements).tolist(), + } d["gruneisen"] = self.gruneisen.tolist() if self.structure: d["structure"] = self.structure.as_dict() diff --git a/pymatgen/phonon/plotter.py b/pymatgen/phonon/plotter.py index 4a3d2b567ef..4b3025b53a6 100644 --- a/pymatgen/phonon/plotter.py +++ b/pymatgen/phonon/plotter.py @@ -35,7 +35,6 @@ def freq_units(units): Returns conversion factor from THz to the required units and the label in the form of a namedtuple """ - d = { "thz": FreqUnits(1, "THz"), "ev": FreqUnits(const.value("hertz-electron volt relationship") * const.tera, "eV"), @@ -111,10 +110,7 @@ def add_dos_dict(self, dos_dict, key_sort_func=None): dos_dict: dict of {label: Dos} key_sort_func: function used to sort the dos_dict keys. """ - if key_sort_func: - keys = sorted(dos_dict, key=key_sort_func) - else: - keys = list(dos_dict) + keys = sorted(dos_dict, key=key_sort_func) if key_sort_func else list(dos_dict) for label in keys: self.add_dos(label, dos_dict[label]) @@ -433,7 +429,7 @@ def get_proj_plot( Args: site_comb: a list of list, for example, [[0],[1],[2,3,4]]; the numbers in each sublist represents the indices of atoms; - the atoms in a same sublist will be plotted in a same color; + the atoms in a same sublist will be plotted in a same color; if not specified, unique elements are automatically grouped. ylim: Specify the y-axis (frequency) limits; by default None let the code choose. @@ -685,6 +681,7 @@ def _plot_thermo(self, func, temperatures, factor=1, ax=None, ylabel=None, label label: label of the plot ylim: tuple specifying the y-axis limits. kwargs: kwargs passed to the matplotlib function 'plot'. + Returns: matplotlib figure """ @@ -722,15 +719,13 @@ def plot_cv(self, tmin, tmax, ntemp, ylim=None, **kwargs): ntemp: number of steps ylim: tuple specifying the y-axis limits. kwargs: kwargs passed to the matplotlib function 'plot'. + Returns: matplotlib figure """ temperatures = np.linspace(tmin, tmax, ntemp) - if self.structure: - ylabel = r"$C_v$ (J/K/mol)" - else: - ylabel = r"$C_v$ (J/K/mol-c)" + ylabel = "$C_v$ (J/K/mol)" if self.structure else "$C_v$ (J/K/mol-c)" fig = self._plot_thermo(self.dos.cv, temperatures, ylabel=ylabel, ylim=ylim, **kwargs) @@ -747,15 +742,13 @@ def plot_entropy(self, tmin, tmax, ntemp, ylim=None, **kwargs): ntemp: number of steps ylim: tuple specifying the y-axis limits. kwargs: kwargs passed to the matplotlib function 'plot'. + Returns: matplotlib figure """ temperatures = np.linspace(tmin, tmax, ntemp) - if self.structure: - ylabel = r"$S$ (J/K/mol)" - else: - ylabel = r"$S$ (J/K/mol-c)" + ylabel = "$S$ (J/K/mol)" if self.structure else "$S$ (J/K/mol-c)" fig = self._plot_thermo(self.dos.entropy, temperatures, ylabel=ylabel, ylim=ylim, **kwargs) @@ -772,15 +765,13 @@ def plot_internal_energy(self, tmin, tmax, ntemp, ylim=None, **kwargs): ntemp: number of steps ylim: tuple specifying the y-axis limits. kwargs: kwargs passed to the matplotlib function 'plot'. + Returns: matplotlib figure """ temperatures = np.linspace(tmin, tmax, ntemp) - if self.structure: - ylabel = r"$\Delta E$ (kJ/mol)" - else: - ylabel = r"$\Delta E$ (kJ/mol-c)" + ylabel = "$\\Delta E$ (kJ/mol)" if self.structure else "$\\Delta E$ (kJ/mol-c)" fig = self._plot_thermo(self.dos.internal_energy, temperatures, ylabel=ylabel, ylim=ylim, factor=1e-3, **kwargs) @@ -797,15 +788,13 @@ def plot_helmholtz_free_energy(self, tmin, tmax, ntemp, ylim=None, **kwargs): ntemp: number of steps ylim: tuple specifying the y-axis limits. kwargs: kwargs passed to the matplotlib function 'plot'. + Returns: matplotlib figure """ temperatures = np.linspace(tmin, tmax, ntemp) - if self.structure: - ylabel = r"$\Delta F$ (kJ/mol)" - else: - ylabel = r"$\Delta F$ (kJ/mol-c)" + ylabel = "$\\Delta F$ (kJ/mol)" if self.structure else "$\\Delta F$ (kJ/mol-c)" fig = self._plot_thermo( self.dos.helmholtz_free_energy, temperatures, ylabel=ylabel, ylim=ylim, factor=1e-3, **kwargs @@ -824,6 +813,7 @@ def plot_thermodynamic_properties(self, tmin, tmax, ntemp, ylim=None, **kwargs): ntemp: number of steps ylim: tuple specifying the y-axis limits. kwargs: kwargs passed to the matplotlib function 'plot'. + Returns: matplotlib figure """ diff --git a/pymatgen/phonon/thermal_displacements.py b/pymatgen/phonon/thermal_displacements.py index 04dc43d593f..5dad35248d0 100644 --- a/pymatgen/phonon/thermal_displacements.py +++ b/pymatgen/phonon/thermal_displacements.py @@ -43,7 +43,7 @@ class ThermalDisplacementMatrices(MSONable): An earlier implementation based on Matlab can be found here: https://github.com/JaGeo/MolecularToolbox - ( J. George, A. Wang, V. L. Deringer, R. Wang, R. Dronskowski, U. Englert, CrystEngComm, 2015, 17, 7414–7422.) + ( J. George, A. Wang, V. L. Deringer, R. Wang, R. Dronskowski, U. Englert, CrystEngComm, 2015, 17, 7414-7422.) """ def __init__(self, thermal_displacement_matrix_cart, structure, temperature, thermal_displacement_matrix_cif=None): @@ -128,7 +128,7 @@ def get_reduced_matrix(thermal_displacement): @property def Ustar(self): """ - Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480. + Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477-480. Returns: Ustar as a numpy array, first dimension are the atoms in the structure """ A = self.structure.lattice.matrix.T @@ -142,11 +142,11 @@ def Ustar(self): @property def Ucif(self): """ - Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480. + Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477-480. Returns: Ucif as a numpy array, first dimension are the atoms in the structure """ if self.thermal_displacement_matrix_cif is None: - # computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480. + # computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477-480. # will compute Ucif based on Ustar for each atom in the structure A = self.structure.lattice.matrix.T # check this again? @@ -164,7 +164,7 @@ def Ucif(self): @property def B(self): """ - Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480. + Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477-480. Returns: B as a numpy array, first dimension are the atoms in the structure """ B = [] @@ -176,7 +176,7 @@ def B(self): @property def beta(self): """ - Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480. + Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477-480. Returns: beta as a numpy array, first dimension are the atoms in the structure """ # will compute beta based on Ustar @@ -189,7 +189,7 @@ def beta(self): @property def U1U2U3(self): """ - Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477–480. + Computation as described in R. W. Grosse-Kunstleve, P. D. Adams, J Appl Cryst 2002, 35, 477-480. Returns: numpy array of eigenvalues of Ucart, first dimension are the atoms in the structure """ U1U2U3 = [] @@ -240,6 +240,7 @@ def compute_directionality_quality_criterion(self, other): """ Will compute directionality of prolate displacement ellipsoids as described in https://doi.org/10.1039/C9CE00794F with the earlier implementation: https://github.com/damMroz/Angle/ + Args: other: ThermalDisplacementMatrix please make sure that the order of the atoms in both objects that are compared diff --git a/pymatgen/symmetry/analyzer.py b/pymatgen/symmetry/analyzer.py index 9ecbb681a27..53b5d14383c 100644 --- a/pymatgen/symmetry/analyzer.py +++ b/pymatgen/symmetry/analyzer.py @@ -1033,7 +1033,7 @@ def _proc_no_rot_sym(self): else: for v in self.principal_axes: mirror_type = self._find_mirror(v) - if not mirror_type == "": + if mirror_type != "": self.sch_symbol = "Cs" break @@ -1046,9 +1046,8 @@ def _proc_cyclic(self): self.sch_symbol += "h" elif mirror_type == "v": self.sch_symbol += "v" - elif mirror_type == "": - if self.is_valid_op(SymmOp.rotoreflection(main_axis, angle=180 / rot)): - self.sch_symbol = f"S{2 * rot}" + elif mirror_type == "" and self.is_valid_op(SymmOp.rotoreflection(main_axis, angle=180 / rot)): + self.sch_symbol = f"S{2 * rot}" def _proc_dihedral(self): """Handles dihedral group molecules, i.e those with intersecting R2 axes and a @@ -1059,7 +1058,7 @@ def _proc_dihedral(self): mirror_type = self._find_mirror(main_axis) if mirror_type == "h": self.sch_symbol += "h" - elif not mirror_type == "": + elif mirror_type != "": self.sch_symbol += "d" def _check_R2_axes_asym(self): @@ -1098,10 +1097,9 @@ def _find_mirror(self, axis): if len(self.rot_sym) > 1: mirror_type = "d" for v, _ in self.rot_sym: - if np.linalg.norm(v - axis) >= self.tol: - if np.dot(v, normal) < self.tol: - mirror_type = "v" - break + if np.linalg.norm(v - axis) >= self.tol and np.dot(v, normal) < self.tol: + mirror_type = "v" + break else: mirror_type = "v" break @@ -1529,10 +1527,7 @@ def cluster_sites(mol, tol, give_only_index=False): origin_site = None for idx, site in enumerate(mol): if avg_dist[f[idx]] < tol: - if give_only_index: - origin_site = idx - else: - origin_site = site + origin_site = idx if give_only_index else site else: if give_only_index: clustered_sites[(avg_dist[f[idx]], site.species)].append(idx) @@ -1618,10 +1613,7 @@ def are_symmetrically_equivalent(self, sites1, sites2, symm_prec=1e-3) -> bool: """ def in_sites(site): - for test_site in sites1: - if test_site.is_periodic_image(site, symm_prec, False): - return True - return False + return any(test_site.is_periodic_image(site, symm_prec, False) for test_site in sites1) for op in self: newsites2 = [PeriodicSite(site.species, op.operate(site.frac_coords), site.lattice) for site in sites2] diff --git a/pymatgen/symmetry/bandstructure.py b/pymatgen/symmetry/bandstructure.py index a8659ff6137..edafec44bcf 100644 --- a/pymatgen/symmetry/bandstructure.py +++ b/pymatgen/symmetry/bandstructure.py @@ -344,10 +344,7 @@ def get_continuous_path(bandstructure): elif edge_euler[::-1] == edge_reg: distances_map.append((plot_axis.index(edge_reg), True)) - if bandstructure.is_spin_polarized: - spins = [Spin.up, Spin.down] - else: - spins = [Spin.up] + spins = [Spin.up, Spin.down] if bandstructure.is_spin_polarized else [Spin.up] new_kpoints = [] new_bands = {spin: [np.array([]) for _ in range(bandstructure.nb_bands)] for spin in spins} diff --git a/pymatgen/symmetry/groups.py b/pymatgen/symmetry/groups.py index 36ee4706e69..f3ec414e128 100644 --- a/pymatgen/symmetry/groups.py +++ b/pymatgen/symmetry/groups.py @@ -57,10 +57,7 @@ def __contains__(self, item: object) -> bool: if not isinstance(item, SymmOp): return NotImplemented - for i in self.symmetry_ops: - if np.allclose(i.affine_matrix, item.affine_matrix): - return True - return False + return any(np.allclose(i.affine_matrix, item.affine_matrix) for i in self.symmetry_ops) def __hash__(self) -> int: return len(self) diff --git a/pymatgen/symmetry/kpath.py b/pymatgen/symmetry/kpath.py index 02bbdae0586..b3824256a36 100644 --- a/pymatgen/symmetry/kpath.py +++ b/pymatgen/symmetry/kpath.py @@ -9,7 +9,6 @@ import abc import itertools -import operator from math import ceil, cos, e, pi, sin, tan from typing import Any from warnings import warn @@ -1299,23 +1298,19 @@ def _get_ksymm_kpath(self, has_magmoms, magmom_axis, axis_specified, symprec, an for orbit in key_points_inds_orbits[:-1]: orbit_cosines.append( sorted( - sorted( + ( ( - ( - j, - np.round( - np.dot(key_points[k], self.LabelPoints(j)) - / (np.linalg.norm(key_points[k]) * np.linalg.norm(self.LabelPoints(j))), - decimals=3, - ), - ) - for k in orbit - for j in range(26) - ), - key=operator.itemgetter(0), + j, + np.round( + np.dot(key_points[k], self.LabelPoints(j)) + / (np.linalg.norm(key_points[k]) * np.linalg.norm(self.LabelPoints(j))), + decimals=3, + ), + ) + for k in orbit + for j in range(26) ), - key=operator.itemgetter(1), - reverse=True, + key=lambda x: (-x[1], x[0]), ) ) diff --git a/pymatgen/symmetry/maggroups.py b/pymatgen/symmetry/maggroups.py index d2ee7355644..2477662b321 100644 --- a/pymatgen/symmetry/maggroups.py +++ b/pymatgen/symmetry/maggroups.py @@ -124,9 +124,8 @@ def __init__(self, id, setting_transformation="a,b,c;0,0,0"): if isinstance(setting_transformation, str): if setting_transformation != "a,b,c;0,0,0": self.jf = JonesFaithfulTransformation.from_transformation_string(setting_transformation) - elif isinstance(setting_transformation, JonesFaithfulTransformation): - if setting_transformation != self.jf: - self.jf = setting_transformation + elif isinstance(setting_transformation, JonesFaithfulTransformation) and setting_transformation != self.jf: + self.jf = setting_transformation self._data["magtype"] = raw_data[0] # int from 1 to 4 self._data["bns_number"] = [raw_data[1], raw_data[2]] diff --git a/pymatgen/symmetry/structure.py b/pymatgen/symmetry/structure.py index fe311fd3ce1..72c151c0735 100644 --- a/pymatgen/symmetry/structure.py +++ b/pymatgen/symmetry/structure.py @@ -128,7 +128,7 @@ def to_s(x): outs.append( tabulate( data, - headers=["#", "SP", "a", "b", "c", "Wyckoff"] + keys, + headers=["#", "SP", "a", "b", "c", "Wyckoff", *keys], ) ) return "\n".join(outs) diff --git a/pymatgen/symmetry/tests/test_analyzer.py b/pymatgen/symmetry/tests/test_analyzer.py index 3d2efdcd18d..634b7d65244 100644 --- a/pymatgen/symmetry/tests/test_analyzer.py +++ b/pymatgen/symmetry/tests/test_analyzer.py @@ -141,8 +141,8 @@ def test_get_symmetry(self): def test_get_crystal_system(self): crystal_system = self.sg.get_crystal_system() - assert "orthorhombic" == crystal_system - assert "tetragonal" == self.disordered_sg.get_crystal_system() + assert crystal_system == "orthorhombic" + assert self.disordered_sg.get_crystal_system() == "tetragonal" orig_spg = self.sg._space_group_data["number"] self.sg._space_group_data["number"] = 0 diff --git a/pymatgen/transformations/advanced_transformations.py b/pymatgen/transformations/advanced_transformations.py index dda72ed8708..fd7680f305f 100644 --- a/pymatgen/transformations/advanced_transformations.py +++ b/pymatgen/transformations/advanced_transformations.py @@ -234,10 +234,7 @@ def apply_transformation(self, structure: Structure, return_ranked_list=False): ) outputs = [] for charge, el_list in self.substitution_dict.items(): - if charge > 0: - sign = "+" - else: - sign = "-" + sign = "+" if charge > 0 else "-" dummy_sp = f"X{charge}{sign}" mapping = { self.sp_to_replace: { @@ -255,10 +252,7 @@ def apply_transformation(self, structure: Structure, return_ranked_list=False): dummy_structure = trans.apply_transformation(dummy_structure) for el in el_list: - if charge > 0: - sign = "+" - else: - sign = "-" + sign = "+" if charge > 0 else "-" st = SubstitutionTransformation({f"X{charge}+": f"{el}{charge}{sign}"}) new_structure = st.apply_transformation(dummy_structure) outputs.append({"structure": new_structure}) @@ -1345,9 +1339,9 @@ def _partition(collection): for smaller in _partition(collection[1:]): # insert `first` in each of the subpartition's subsets for n, subset in enumerate(smaller): - yield smaller[:n] + [[first] + subset] + smaller[n + 1 :] + yield smaller[:n] + [[first, *subset]] + smaller[n + 1 :] # put `first` in its own subset - yield [[first]] + smaller + yield [[first], *smaller] def _sort_partitions(partitions_to_sort): """ diff --git a/pymatgen/util/convergence.py b/pymatgen/util/convergence.py index d1c8764068b..d3dd3e19b20 100644 --- a/pymatgen/util/convergence.py +++ b/pymatgen/util/convergence.py @@ -571,13 +571,7 @@ def determine_convergence(xs, ys, name, tol: float = 0.0001, extra="", verbose=F if verbose: print(n, ys[n]) print(ys) - if tol < 0: - if popt[0] is not None: - test = abs(popt[0] - ys[n]) - else: - test = float("inf") - else: - test = abs(ds[n]) + test = (abs(popt[0] - ys[n]) if popt[0] is not None else float("inf")) if tol < 0 else abs(ds[n]) if verbose: print(test) if test < abs(tol): diff --git a/pymatgen/util/coord.py b/pymatgen/util/coord.py index e82a9f11f36..e0dce83bfa2 100644 --- a/pymatgen/util/coord.py +++ b/pymatgen/util/coord.py @@ -95,9 +95,8 @@ def coord_list_mapping(subset, superset, atol=1e-8): c2 = np.array(superset) inds = np.where(np.all(np.isclose(c1[:, None, :], c2[None, :, :], atol=atol), axis=2))[1] result = c2[inds] - if not np.allclose(c1, result, atol=atol): - if not is_coord_subset(subset, superset): - raise ValueError("subset is not a subset of superset") + if not np.allclose(c1, result, atol=atol) and not is_coord_subset(subset, superset): + raise ValueError("subset is not a subset of superset") if not result.shape == c1.shape: raise ValueError("Something wrong with the inputs, likely duplicates in superset") return inds @@ -274,10 +273,7 @@ def is_coord_subset_pbc(subset, superset, atol=1e-8, mask=None, pbc=(True, True, # pylint: disable=I1101 c1 = np.array(subset, dtype=np.float64) c2 = np.array(superset, dtype=np.float64) - if mask is not None: - m = np.array(mask, dtype=int) - else: - m = np.zeros((len(subset), len(superset)), dtype=int) + m = np.array(mask, dtype=int) if mask is not None else np.zeros((len(subset), len(superset)), dtype=int) atol = np.zeros(3, dtype=np.float64) + atol return cuc.is_coord_subset_pbc(c1, c2, atol, m, pbc) @@ -483,10 +479,7 @@ def line_intersection(self, point1, point2, tolerance=1e-8): def __eq__(self, other: object) -> bool: if not isinstance(other, Simplex): return NotImplemented - for p in itertools.permutations(self._coords): - if np.allclose(p, other.coords): - return True - return False + return any(np.allclose(p, other.coords) for p in itertools.permutations(self._coords)) def __hash__(self): return len(self._coords) diff --git a/pymatgen/util/io_utils.py b/pymatgen/util/io_utils.py index a93f6c4f958..52865039bcc 100644 --- a/pymatgen/util/io_utils.py +++ b/pymatgen/util/io_utils.py @@ -53,7 +53,6 @@ def clean_lines(string_list, remove_empty_lines=True): Returns: List of clean strings with no whitespaces. """ - for s in string_list: clean_s = s if "#" in s: diff --git a/pymatgen/util/string.py b/pymatgen/util/string.py index 35d6869dd12..18b5e964e38 100644 --- a/pymatgen/util/string.py +++ b/pymatgen/util/string.py @@ -210,7 +210,6 @@ def unicodeify(formula): :param formula: :return: """ - if "." in formula: raise ValueError("No unicode character exists for subscript period.") @@ -252,7 +251,6 @@ def unicodeify_spacegroup(spacegroup_symbol): Returns: A unicode spacegroup with proper subscripts and overlines. """ - if not spacegroup_symbol: return "" @@ -287,7 +285,6 @@ def unicodeify_species(specie_string): Returns: Species string, e.g. O²⁻ """ - if not specie_string: return "" @@ -310,9 +307,10 @@ def stream_has_colours(stream): import curses curses.setupterm() - return curses.tigetnum("colors") > 2 except Exception: return False # guess false in case of error + else: + return curses.tigetnum("colors") > 2 def transformation_to_string(matrix, translation_vec=(0, 0, 0), components=("x", "y", "z"), c="", delim=","): @@ -367,7 +365,6 @@ def disordered_formula(disordered_struct, symbols=("x", "y", "z"), fmt="plain"): Returns (str): a disordered formula string """ - # this is in string utils and not in # Composition because we need to have access # to site occupancies to calculate this, so diff --git a/pymatgen/vis/plotters.py b/pymatgen/vis/plotters.py index 6f05ae1cd49..36bc6c03f7e 100644 --- a/pymatgen/vis/plotters.py +++ b/pymatgen/vis/plotters.py @@ -78,10 +78,7 @@ def add_spectra(self, spectra_dict, key_sort_func=None): dos_dict: dict of {label: Dos} key_sort_func: function used to sort the dos_dict keys. """ - if key_sort_func: - keys = sorted(spectra_dict, key=key_sort_func) - else: - keys = list(spectra_dict) + keys = sorted(spectra_dict, key=key_sort_func) if key_sort_func else list(spectra_dict) for label in keys: self.add_spectra(label, spectra_dict[label]) diff --git a/pymatgen/vis/structure_chemview.py b/pymatgen/vis/structure_chemview.py index bf8ff21686a..7896e997d96 100644 --- a/pymatgen/vis/structure_chemview.py +++ b/pymatgen/vis/structure_chemview.py @@ -41,10 +41,10 @@ def quick_view( show_box: (bool) unit cell is shown. Defaults to True. bond_tol: (float) used if bonds=True. Sets the extra distance tolerance when finding bonds. stick_radius: (float) radius of bonds. + Returns: A chemview.MolecularViewer object """ - s = structure.copy() if conventional: s = SpacegroupAnalyzer(s).get_conventional_standard_structure() diff --git a/pymatgen/vis/structure_vtk.py b/pymatgen/vis/structure_vtk.py index b373ca65f4f..a5ffed3aef3 100644 --- a/pymatgen/vis/structure_vtk.py +++ b/pymatgen/vis/structure_vtk.py @@ -258,10 +258,7 @@ def set_structure(self, structure: Structure, reset_camera=True, to_unit_cell=Tr anion = elements[-1] def contains_anion(site): - for sp in site.species: - if sp.symbol == anion.symbol: - return True - return False + return any(sp.symbol == anion.symbol for sp in site.species) anion_radius = anion.average_ionic_radius for site in s: @@ -1052,9 +1049,8 @@ def apply_tags(self): tags = {} for tag in self.tags: istruct = tag.get("istruct", "all") - if istruct != "all": - if istruct != self.istruct: - continue + if istruct != "all" and istruct != self.istruct: + continue site_index = tag["site_index"] color = tag.get("color", [0.5, 0.5, 0.5]) opacity = tag.get("opacity", 0.5) diff --git a/pyproject.toml b/pyproject.toml index 4fd4464509c..a9cff93694a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,3 +9,45 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 120 + +[tool.ruff] +target-version = "py38" +line-length = 120 +select = [ + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "D", # pydocstyle + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "PLE", # pylint error + "PLW", # pylint warning + "Q", # flake8-quotes + "RUF", # Ruff-specific rules + "SIM", # flake8-simplify + "TID", # tidy imports + "UP", # pyupgrade + "W", # pycodestyle + "YTT", # flake8-2020 +] +ignore = [ + "B019", # functools.lru_cache on methods can lead to memory leaks + "B023", # Function definition does not bind loop variable + "B904", # Within an except clause, raise exceptions with ... + "D100", # Missing docstring in public module + "D104", # Missing docstring in public package + "D105", # Missing docstring in magic method + "D107", # Missing docstring in __init__ + "D200", # One-line docstring should fit on one line with quotes + "D205", # 1 blank line required between summary line and description + "D212", # Multi-line docstring summary should start at the first line + "D415", # First line should end with a period, question mark, or exclamation point + "E741", # tmp: we should fix all ambiguous variable names + "PLR2004", # Magic number + "PLW0120", # awaiting bug fix https://github.com/charliermarsh/ruff/issues/3019 +] +pydocstyle.convention = "google" +isort.required-imports = ["from __future__ import annotations"] + +[tool.ruff.per-file-ignores] +"__init__.py" = ["F401"] diff --git a/setup.cfg b/setup.cfg index bd09b6c1cd5..fb66f157adb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,25 +1,6 @@ -[isort] -profile = black -add_imports = from __future__ import annotations - [tool:pytest] addopts = -x --durations=30 --quiet -rxXs --color=yes -p no:warnings -[pycodestyle] -count = true -ignore = E121,E123,E126,E133,E226,E241,E242,E704,W503,W504,W505,E741,W605,W293,W291,W292,E203,E231 -max-line-length = 120 -statistics = true -exclude = docs_rst/*.py - -[flake8] -exclude = .git,__pycache__,docs_rst/conf.py,tests -extend-ignore = E741,W291,W293,E501,E231,E203,B019,B023,B028 -max-line-length = 120 -per-file-ignores = - # F401: imported but unused - __init__.py: F401 - [pydocstyle] convention = google # D100: Missing docstring in public module @@ -62,12 +43,5 @@ ignore_missing_imports = true [mypy-requests.*] ignore_missing_imports = true -[autoflake] -in-place = true -remove-unused-variables = true -remove-all-unused-imports = true -expand-star-imports = true -ignore-init-module-imports = true - [codespell] ignore-words-list = titel,alls,ans,nd,mater,nwo,te,hart,ontop,ist,ot,fo diff --git a/setup.py b/setup.py index f645c107c33..4c3a1b06ec1 100644 --- a/setup.py +++ b/setup.py @@ -46,16 +46,12 @@ "relaxation": ["m3gnet"], "dev": [ "black", - "coverage", - "coveralls", - "flake8", "mypy", "pre-commit", - "pydocstyle", - "pylint", - "pytest", "pytest-cov", "pytest-split", + "pytest", + "ruff", ], "docs": [ "sphinx", diff --git a/tasks.py b/tasks.py index 4cdaa96a5e4..e1aea093877 100644 --- a/tasks.py +++ b/tasks.py @@ -106,7 +106,7 @@ def make_dash(ctx): if l.strip() == "": xml.append("dashIndexFilePath") xml.append("index.html") - with open(plist, "wt") as f: + with open(plist, "w") as f: f.write("\n".join(xml)) ctx.run('tar --exclude=".DS_Store" -cvzf pymatgen.tgz pymatgen.docset') # xml = [] @@ -131,7 +131,7 @@ def contribute_dash(ctx, version): with open("docset.json") as f: data = json.load(f) data["version"] = version - with open("docset.json", "wt") as f: + with open("docset.json", "w") as f: json.dump(data, f, indent=4) ctx.run(f'git commit --no-verify -a -m "Update to v{version}"') ctx.run("git push") @@ -185,14 +185,14 @@ def set_ver(ctx, version): contents = f.read() contents = re.sub(r"__version__ = .*\n", f"__version__ = {version!r}\n", contents) - with open("pymatgen/core/__init__.py", "wt") as f: + with open("pymatgen/core/__init__.py", "w") as f: f.write(contents) with open("setup.py") as f: contents = f.read() contents = re.sub(r"version=([^,]+),", f"version={version!r},", contents) - with open("setup.py", "wt") as f: + with open("setup.py", "w") as f: f.write(contents) diff --git a/test_files/Zr_Vasprun/test_vasprun.py b/test_files/Zr_Vasprun/test_vasprun.py index 029e8f5ec7e..da6c858ae2a 100644 --- a/test_files/Zr_Vasprun/test_vasprun.py +++ b/test_files/Zr_Vasprun/test_vasprun.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from os.path import abspath, dirname from pymatgen.io.vasp.outputs import Vasprun