Skip to content

sync #91

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged

sync #91

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 41 additions & 32 deletions src/pymatgen/core/lattice.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import warnings
from collections import defaultdict
from fractions import Fraction
from functools import reduce
from functools import cached_property, reduce
from typing import TYPE_CHECKING

import numpy as np
Expand All @@ -22,6 +22,7 @@

if TYPE_CHECKING:
from collections.abc import Iterator
from typing import Literal

from numpy.typing import ArrayLike, NDArray
from typing_extensions import Self
Expand Down Expand Up @@ -74,17 +75,6 @@ def __init__(

self.pbc = pbc

@property
def pbc(self) -> tuple[bool, bool, bool]:
"""Tuple defining the periodicity of the Lattice."""
return self._pbc # type:ignore[return-value]

@pbc.setter
def pbc(self, pbc: tuple[bool, bool, bool]) -> None:
if len(pbc) != 3 or any(item not in {True, False} for item in pbc):
raise ValueError(f"pbc must be a tuple of three True/False values, got {pbc}")
self._pbc = tuple(bool(item) for item in pbc)

def __repr__(self) -> str:
return "\n".join(
[
Expand Down Expand Up @@ -148,6 +138,22 @@ def __format__(self, fmt_spec: str = "") -> str:
return fmt.format(*(format(c, fmt_spec) for row in matrix for c in row))

@property
def pbc(self) -> tuple[bool, bool, bool]:
"""Tuple defining the periodicity of the Lattice."""
return self._pbc # type:ignore[return-value]

@pbc.setter
def pbc(self, pbc: tuple[bool, bool, bool]) -> None:
if len(pbc) != 3 or any(item not in {True, False} for item in pbc):
raise ValueError(f"pbc must be a tuple of three True/False values, got {pbc}")
self._pbc = tuple(bool(item) for item in pbc)

@property
def matrix(self) -> NDArray[np.float64]:
"""Copy of matrix representing the Lattice."""
return self._matrix

@cached_property
def lengths(self) -> tuple[float, float, float]:
"""Lattice lengths.

Expand All @@ -156,7 +162,7 @@ def lengths(self) -> tuple[float, float, float]:
"""
return tuple(np.sqrt(np.sum(self._matrix**2, axis=1)).tolist()) # type: ignore[return-value]

@property
@cached_property
def angles(self) -> tuple[float, float, float]:
"""Lattice angles.

Expand All @@ -172,16 +178,17 @@ def angles(self) -> tuple[float, float, float]:
angles = np.arccos(angles) * 180.0 / np.pi # type: ignore[assignment]
return tuple(angles.tolist()) # type: ignore[return-value]

@cached_property
def volume(self) -> float:
"""Volume of the unit cell in Angstrom^3."""
matrix = self._matrix
return float(abs(np.dot(np.cross(matrix[0], matrix[1]), matrix[2])))

@property
def is_orthogonal(self) -> bool:
"""Whether all angles are 90 degrees."""
return all(abs(a - 90) < 1e-5 for a in self.angles)

@property
def matrix(self) -> NDArray[np.float64]:
"""Copy of matrix representing the Lattice."""
return self._matrix

@property
def is_3d_periodic(self) -> bool:
"""True if the Lattice is periodic in all directions."""
Expand Down Expand Up @@ -507,12 +514,6 @@ def gamma(self) -> float:
"""Angle gamma of lattice in degrees."""
return self.angles[2]

@property
def volume(self) -> float:
"""Volume of the unit cell in Angstrom^3."""
matrix = self._matrix
return float(abs(np.dot(np.cross(matrix[0], matrix[1]), matrix[2])))

@property
def parameters(self) -> tuple[float, float, float, float, float, float]:
"""6-tuple of floats (a, b, c, alpha, beta, gamma)."""
Expand All @@ -521,7 +522,7 @@ def parameters(self) -> tuple[float, float, float, float, float, float]:
@property
def params_dict(self) -> dict[str, float]:
"""Dictionary of lattice parameters."""
return dict(zip("a b c alpha beta gamma".split(), self.parameters, strict=True))
return dict(zip(("a", "b", "c", "alpha", "beta", "gamma"), self.parameters, strict=True))

@property
def reciprocal_lattice(self) -> Self:
Expand Down Expand Up @@ -897,11 +898,11 @@ def selling_dist(self, other: Self) -> float:

return min(np.linalg.norm(reflection - selling2) for reflection in all_reflections) # type: ignore[return-value, type-var]

def as_dict(self, verbosity: int = 0) -> dict:
def as_dict(self, verbosity: Literal[0, 1] = 0) -> dict:
"""MSONable dict representation of the Lattice.

Args:
verbosity (int): Default of 0 only includes the matrix representation.
verbosity (0 | 1): Default of 0 only includes the matrix representation.
Set to 1 to include the lattice parameters.
"""
dct = {
Expand All @@ -910,7 +911,15 @@ def as_dict(self, verbosity: int = 0) -> dict:
"matrix": self._matrix.tolist(),
"pbc": self.pbc,
}
if verbosity > 0:

if verbosity not in {0, 1}:
warnings.warn(
f"`verbosity={verbosity}` is deprecated and will be disallowed in a future version. "
"Please use 0 (silent) or 1 (verbose) explicitly.",
DeprecationWarning,
stacklevel=2,
)
if verbosity > 0: # TODO: explicit check `verbosity == 1`
dct |= self.params_dict
dct["volume"] = self.volume

Expand Down Expand Up @@ -1282,11 +1291,11 @@ def scale(self, new_volume: float) -> Self:
Returns:
New lattice with desired volume.
"""
versors = self.matrix / self.abc
versors = self.matrix / self.lengths

geo_factor = abs(np.dot(np.cross(versors[0], versors[1]), versors[2]))

ratios = np.array(self.abc) / self.c
ratios = np.array(self.lengths) / self.c

new_c = (new_volume / (geo_factor * np.prod(ratios))) ** (1 / 3.0)

Expand Down Expand Up @@ -1541,7 +1550,7 @@ def get_points_in_sphere_old(
# TODO: refactor to use lll matrix (nmax will be smaller)
# Determine the maximum number of supercells in each direction
# required to contain a sphere of radius n
recp_len = np.array(self.reciprocal_lattice.abc) / (2 * np.pi)
recp_len = np.array(self.reciprocal_lattice.lengths) / (2 * np.pi)
nmax = float(r) * recp_len + 0.01

# Get the fractional coordinates of the center of the sphere
Expand Down Expand Up @@ -1849,7 +1858,7 @@ def get_points_in_spheres(
if np.any(_pbc):
if lattice is None:
raise ValueError("Lattice needs to be supplied when considering periodic boundary")
recp_len = np.array(lattice.reciprocal_lattice.abc)
recp_len = np.array(lattice.reciprocal_lattice.lengths)
maxr = np.ceil((r + 0.15) * recp_len / (2 * math.pi))
frac_coords = lattice.get_fractional_coords(center_coords)
nmin_temp = np.floor(np.min(frac_coords, axis=0)) - maxr
Expand Down
28 changes: 18 additions & 10 deletions src/pymatgen/core/sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import collections
import json
import warnings
from typing import TYPE_CHECKING, cast

import numpy as np
Expand All @@ -16,7 +17,7 @@
from pymatgen.util.misc import is_np_dict_equal

if TYPE_CHECKING:
from typing import Any
from typing import Any, Literal

from numpy.typing import ArrayLike, NDArray
from typing_extensions import Self
Expand Down Expand Up @@ -254,7 +255,7 @@ def as_dict(self) -> dict:
dct = {
"name": self.species_string,
"species": species,
"xyz": [float(c) for c in self.coords],
"xyz": self.coords.astype(float).tolist(),
"properties": self.properties,
"@module": type(self).__module__,
"@class": type(self).__name__,
Expand Down Expand Up @@ -531,7 +532,7 @@ def distance_and_image_from_frac_coords(
Args:
fcoords (3x1 array): fractional coordinates to get distance from.
jimage (3x1 array): Specific periodic image in terms of
lattice translations, e.g. [1,0,0] implies to take periodic
lattice translations, e.g. [1, 0, 0] implies to take periodic
image that is one a-lattice vector away. If jimage is None,
the image that is nearest to the site is found.

Expand All @@ -556,7 +557,7 @@ def distance_and_image(
Args:
other (PeriodicSite): Other site to get distance from.
jimage (3x1 array): Specific periodic image in terms of lattice
translations, e.g. [1,0,0] implies to take periodic image
translations, e.g. [1, 0, 0] implies to take periodic image
that is one a-lattice vector away. If jimage is None,
the image that is nearest to the site is found.

Expand All @@ -576,7 +577,7 @@ def distance(
Args:
other (PeriodicSite): Other site to get distance from.
jimage (3x1 array): Specific periodic image in terms of lattice
translations, e.g. [1,0,0] implies to take periodic image
translations, e.g. [1, 0, 0] implies to take periodic image
that is one a-lattice vector away. If jimage is None,
the image that is nearest to the site is found.

Expand All @@ -585,11 +586,11 @@ def distance(
"""
return self.distance_and_image(other, jimage)[0]

def as_dict(self, verbosity: int = 0) -> dict:
def as_dict(self, verbosity: Literal[0, 1] = 0) -> dict:
"""JSON-serializable dict representation of PeriodicSite.

Args:
verbosity (int): Verbosity level. Default of 0 only includes the matrix
verbosity (0 | 1): Verbosity level. Default of 0 only includes the matrix
representation. Set to 1 for more details such as Cartesian coordinates, etc.
"""
species = []
Expand All @@ -602,16 +603,23 @@ def as_dict(self, verbosity: int = 0) -> dict:

dct = {
"species": species,
"abc": [float(c) for c in self._frac_coords],
"abc": self._frac_coords.astype(float).tolist(),
"lattice": self._lattice.as_dict(verbosity=verbosity),
"@module": type(self).__module__,
"@class": type(self).__name__,
"properties": self.properties,
"label": self.label,
}

if verbosity > 0:
dct["xyz"] = [float(c) for c in self.coords]
if verbosity not in {0, 1}:
warnings.warn(
f"`verbosity={verbosity}` is deprecated and will be disallowed in a future version. "
"Please use 0 (silent) or 1 (verbose) explicitly.",
DeprecationWarning,
stacklevel=2,
)
if verbosity > 0: # TODO: explicitly check `verbosity == 1`
dct["xyz"] = self.coords.astype(float).tolist()

return dct

Expand Down
7 changes: 5 additions & 2 deletions src/pymatgen/core/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2813,14 +2813,14 @@ def get_orderings(

def as_dict(
self,
verbosity: int = 1,
verbosity: Literal[0, 1] = 1,
fmt: Literal["abivars"] | None = None,
**kwargs,
) -> dict[str, Any]:
"""Dict representation of Structure.

Args:
verbosity (int): Verbosity level. Default of 1 includes both
verbosity (0 | 1): Verbosity level. Default of 1 includes both
direct and Cartesian coordinates for all sites, lattice
parameters, etc. Useful for reading and for insertion into a
database. Set to 0 for an extremely lightweight version
Expand Down Expand Up @@ -2857,7 +2857,10 @@ def as_dict(
del site_dict["lattice"]
del site_dict["@module"]
del site_dict["@class"]
if verbosity == 0:
del site_dict["label"]
sites.append(site_dict)

dct["sites"] = sites
return dct

Expand Down
1 change: 0 additions & 1 deletion tests/core/test_lattice.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ def test_static_methods(self):
assert lattice.angles == approx(expected_angles)

def test_attributes(self):
"""Docstring for test_attributes."""
lattice = Lattice.cubic(10.0)
assert lattice.a == approx(10)
assert lattice.b == approx(10)
Expand Down
1 change: 1 addition & 0 deletions tests/core/test_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ def test_as_dict(self):
dct = struct.as_dict(0)
assert "volume" not in dct["lattice"]
assert "xyz" not in dct["sites"][0]
assert "label" not in dct["sites"][0]

def test_from_dict(self):
dct = self.propertied_structure.as_dict()
Expand Down