Skip to content

Commit 9a728db

Browse files
authored
Sc back (#171)
* working * working * working * working * working * pyrho * pyrho * data * pyrho * up_sample * up_sample * up_sample * up_sample * up_sample * update * update
1 parent f329d9d commit 9a728db

File tree

15 files changed

+3906
-71
lines changed

15 files changed

+3906
-71
lines changed

.github/workflows/testing.yml

+1-20
Original file line numberDiff line numberDiff line change
@@ -66,26 +66,6 @@ jobs:
6666
token: ${{ secrets.CODECOV_TOKEN }}
6767
file: ./coverage.xml
6868

69-
# figure out if it is possible to upload to testpypi
70-
# deploy-test:
71-
# runs-on: ubuntu-latest
72-
# steps:
73-
# - uses: actions/checkout@v3
74-
# - name: Set up Python
75-
# uses: actions/setup-python@v4
76-
# with:
77-
# python-version: '3.x'
78-
# - name: Install dependencies
79-
# run: |
80-
# python -m pip install --upgrade pip
81-
# pip install build
82-
# - name: Build package
83-
# run: python -m build
84-
# - name: Publish package to TestPyPI
85-
# uses: pypa/gh-action-pypi-publish@release/v1
86-
# with:
87-
# password: ${{ secrets.TEST_PYPI_API_TOKEN }}
88-
# repository-url: https://test.pypi.org/legacy/
8969

9070
docs:
9171
runs-on: ubuntu-latest
@@ -107,6 +87,7 @@ jobs:
10787

10888
- name: Install dependencies
10989
run: |
90+
python -m pip install --upgrade pip
11091
pip install -e .[strict]
11192
pip install -e .[docs]
11293
- name: Build

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ exclude: ^(tests)
33
repos:
44
- repo: https://github.com/astral-sh/ruff-pre-commit
55
# Ruff version.
6-
rev: v0.1.8
6+
rev: v0.2.2
77
hooks:
88
# Run the linter.
99
- id: ruff
1010
args: [ --fix ]
1111
# Run the formatter.
1212
- id: ruff-format
1313
- repo: https://github.com/pre-commit/mirrors-mypy
14-
rev: v1.5.1
14+
rev: v1.8.0
1515
hooks:
1616
- id: mypy
1717
files: ^pymatgen/

docs/source/content/defining-defects.ipynb

+8-5
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@
222222
"metadata": {},
223223
"outputs": [],
224224
"source": [
225-
"#sc_struct_smaller = mg_ga_defect0.get_supercell_structure(max_atoms=100)\n",
226-
"#sc_struct_smaller.num_sites"
225+
"# sc_struct_smaller = mg_ga_defect0.get_supercell_structure(max_atoms=100)\n",
226+
"# sc_struct_smaller.num_sites"
227227
]
228228
},
229229
{
@@ -234,9 +234,9 @@
234234
"\n",
235235
"Intersitial defects are usually hard to define due to a lack of reference points for the site.\n",
236236
"Extensive symmetry can be done to identifiy highly symmetric sites in the structure for interstitial insertion.\n",
237-
"However, the recommended method to create interstitial defects is to use the `ChargeInterstitialGenerator` which analyzes the charge density to identify interstitial sites. The code snippet to generate the interstitial sites are given below.\n",
237+
"However, the recommended method to create interstitial defects is to use the `ChargeInterstitialGenerator` which analyzes the charge density to identify interstitial sites. The code snippet to generate the interstitial sites is given below.\n",
238238
"\n",
239-
"For more details, check out this [paper](https://www.nature.com/articles/s41524-020-00422-3)\n"
239+
"For more details on how the intersitial site identification works, check out this [paper](https://www.nature.com/articles/s41524-020-00422-3)."
240240
]
241241
},
242242
{
@@ -247,8 +247,11 @@
247247
},
248248
"outputs": [],
249249
"source": [
250+
"from pymatgen.analysis.defects.generators import (\n",
251+
" ChargeInterstitialGenerator,\n",
252+
" generate_all_native_defects,\n",
253+
")\n",
250254
"from pymatgen.io.vasp import Chgcar\n",
251-
"from pymatgen.analysis.defects.generators import ChargeInterstitialGenerator, generate_all_native_defects\n",
252255
"\n",
253256
"chgcar = Chgcar.from_file(TEST_FILES / \"CHGCAR.Fe3O4.vasp\")\n",
254257
"cig = ChargeInterstitialGenerator()\n",

pymatgen/analysis/defects/ccd.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import logging
55
from dataclasses import dataclass
66
from itertools import groupby
7-
from typing import TYPE_CHECKING, Optional, Sequence, Tuple
7+
from typing import TYPE_CHECKING
88

99
import numpy as np
1010
from monty.json import MSONable
@@ -20,6 +20,7 @@
2020
if TYPE_CHECKING:
2121
from ctypes import Structure
2222
from pathlib import Path
23+
from typing import Optional, Sequence, Tuple
2324

2425
import numpy.typing as npt
2526
from matplotlib.axes import Axes
@@ -65,9 +66,9 @@ class HarmonicDefect(MSONable):
6566
charge_state: int
6667
ispin: int
6768
vrun: Optional[Vasprun] = None
68-
distortions: Optional[list[float]] = None
69-
structures: Optional[list[Structure]] = None
70-
energies: Optional[list[float]] = None
69+
distortions: Optional[Sequence[float]] = None
70+
structures: Optional[Sequence[Structure]] = None
71+
energies: Optional[Sequence[float]] = None
7172
defect_band: Optional[Sequence[tuple]] = None
7273
relaxed_index: Optional[int] = None
7374
relaxed_bandstructure: Optional[BandStructure] = None
@@ -234,7 +235,7 @@ def _parse_vasprun(vasprun: Vasprun):
234235
omega=omega,
235236
charge_state=charge_state,
236237
ispin=ispin,
237-
structures=structures,
238+
structures=list(structures),
238239
distortions=distortions,
239240
energies=energies,
240241
defect_band=defect_band,
@@ -313,7 +314,7 @@ def occupation(self, t: npt.ArrayLike | float) -> npt.ArrayLike:
313314
return 1.0 / (1 - np.exp(-self.omega_eV / KB * t))
314315

315316
def read_wswqs(
316-
self, directory: Path, distortions: list[float] | None = None
317+
self, directory: Path, distortions: Sequence[float] | None = None
317318
) -> None:
318319
"""Read the WSWQ files from a directory.
319320

pymatgen/analysis/defects/corrections/freysoldt.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,8 @@ def plot_plnr_avg(plot_data, title=None, saved=False, ax=None):
412412
)
413413

414414
ax.set_xlim(round(x[0]), round(x[-1]))
415-
ymin = min(min(v_R), min(dft_diff), min(short_range))
416-
ymax = max(max(v_R), max(dft_diff), max(short_range))
415+
ymin = min(v_R + dft_diff + short_range)
416+
ymax = max(v_R + dft_diff + short_range)
417417
ax.set_ylim(-0.2 + ymin, 0.2 + ymax)
418418
ax.set_xlabel(r"distance along axis ($\AA$)", fontsize=15)
419419
ax.set_ylabel("Potential (V)", fontsize=15)

pymatgen/analysis/defects/generators.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
2222

2323
if TYPE_CHECKING:
24+
from typing import Sequence
25+
2426
from pymatgen.analysis.defects.core import Defect
2527
from pymatgen.io.vasp import VolumetricData
2628

@@ -86,7 +88,10 @@ def __init__(
8688
self.angle_tolerance = angle_tolerance
8789

8890
def generate(
89-
self, structure: Structure, rm_species: list[str | Species] = None, **kwargs
91+
self,
92+
structure: Structure,
93+
rm_species: list[str | Species] | None = None,
94+
**kwargs,
9095
) -> Generator[Vacancy, None, None]:
9196
"""Generate a vacancy defects.
9297
@@ -254,9 +259,10 @@ def __init__(self, min_dist: float = 0.5) -> None:
254259
def generate(
255260
self,
256261
structure: Structure,
257-
insertions: dict[str, list[list[float]]],
258-
multiplicities: dict[str, list[int]] | None = None,
259-
equivalent_positions: dict[str, list[list[list[float]]]] | None = None,
262+
insertions: dict[str, Sequence[Sequence[float]]],
263+
multiplicities: dict[str, Sequence[int]] | None = None,
264+
equivalent_positions: dict[str, Sequence[Sequence[Sequence[float]]]]
265+
| None = None,
260266
**kwargs,
261267
) -> Generator[Interstitial, None, None]:
262268
"""Generate interstitials.
@@ -306,8 +312,8 @@ def generate(
306312
)
307313

308314
def _filter_colliding(
309-
self, fcoords: list[list[float]], structure: Structure
310-
) -> Generator[tuple[int, list[float]], None, None]:
315+
self, fcoords: Sequence[Sequence[float]], structure: Structure
316+
) -> Generator[tuple[int, Sequence[float]], None, None]:
311317
"""Check the sites for collisions.
312318
313319
Args:

pymatgen/analysis/defects/plotting/optics.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ def plot_optical_transitions(
2727
kpt_index: int = 0,
2828
band_window: int = 5,
2929
user_defect_band: tuple = tuple(),
30-
other_defect_bands: list[int] = None,
31-
ijdirs: list[tuple] = None,
32-
shift_eig: dict[tuple, float] = None,
30+
other_defect_bands: list[int] | None = None,
31+
ijdirs: list[tuple] | None = None,
32+
shift_eig: dict[tuple, float] | None = None,
3333
x0: float = 0,
3434
x_width: float = 2,
3535
ax=None,
@@ -118,9 +118,9 @@ def get_bs_eigenvalues(
118118
defect: HarmonicDefect,
119119
kpt_index: int = 0,
120120
band_window: int = 5,
121-
user_defect_band: tuple = None,
122-
other_defect_bands: list[int] = None,
123-
shift_eig: dict[tuple, float] = None,
121+
user_defect_band: tuple | None = None,
122+
other_defect_bands: list[int] | None = None,
123+
shift_eig: dict[tuple, float] | None = None,
124124
) -> dict[tuple, float]:
125125
"""Read the eigenvalues from `HarmonicDefect.relaxed_bandstructure`.
126126

pymatgen/analysis/defects/plotting/phases.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ def plot_chempot_2d(
9090
)
9191
ax.add_patch(patch)
9292

93-
ax.set_xlabel(f"$\Delta\mu_{{{x_element}}}$ (eV)")
94-
ax.set_ylabel(f"$\Delta\mu_{{{y_element}}}$ (eV)")
93+
ax.set_xlabel(rf"$\Delta\mu_{{{x_element}}}$ (eV)")
94+
ax.set_ylabel(rf"$\Delta\mu_{{{y_element}}}$ (eV)")
9595
ax.set_xlim(x_min - PLOT_PADDING, 0 + PLOT_PADDING)
9696
ax.set_ylim(y_min - PLOT_PADDING, 0 + PLOT_PADDING)
9797
if label_lines:
@@ -102,7 +102,7 @@ def _convex_hull_2d(
102102
points: list[dict],
103103
x_element: Element,
104104
y_element: Element,
105-
competing_phases: list = None,
105+
competing_phases: list | None = None,
106106
) -> list[dict]:
107107
"""Compute the convex hull of a set of points in 2D.
108108

pymatgen/analysis/defects/supercells.py

+105-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@
66
import math
77
from typing import TYPE_CHECKING
88

9+
import numpy as np
10+
from monty.dev import deprecated
911
from pymatgen.analysis.structure_matcher import ElementComparator, StructureMatcher
12+
from pymatgen.core import Lattice
13+
from pymatgen.util.coord_cython import is_coord_subset_pbc, pbc_shortest_vectors
14+
from pyrho.charge_density import ChargeDensity
1015

1116
# from ase.build import find_optimal_cell_shape, get_deviation_from_optimal_cell_shape
1217
# from pymatgen.io.ase import AseAtomsAdaptor
1318

1419
if TYPE_CHECKING:
20+
import numpy.typing as npt
1521
from numpy.typing import ArrayLike, NDArray
1622
from pymatgen.core import Structure
23+
from pymatgen.io.vasp.outputs import VolumetricData
1724

1825
__author__ = "Jimmy-Xuan Shen"
1926
__copyright__ = "Copyright 2022, The Materials Project"
@@ -58,12 +65,13 @@ def get_sc_fromstruct(
5865
return sc_mat
5966

6067

61-
def get_matched_structure_mapping(
68+
def get_matched_structure_mapping_old(
6269
uc_struct: Structure, sc_struct: Structure, sm: StructureMatcher | None = None
6370
):
6471
"""Get the mapping of the supercell to the unit cell.
6572
66-
Get the mapping from the supercell defect structure onto the base structure,
73+
Get the mapping from the supercell structure onto the base structure,
74+
Note: this only works for structures that are exactly matched.
6775
6876
Args:
6977
uc_struct: host structure, smaller cell
@@ -86,6 +94,38 @@ def get_matched_structure_mapping(
8694
return sc_m, total_t
8795

8896

97+
@deprecated(message="This function was reworked in Feb 2024")
98+
def get_matched_structure_mapping(
99+
uc_struct: Structure, sc_struct: Structure, sm: StructureMatcher | None = None
100+
):
101+
"""Get the mapping of the supercell to the unit cell.
102+
103+
Get the mapping from the supercell structure onto the base structure,
104+
Note: this only works for structures that are exactly matched.
105+
106+
Args:
107+
uc_struct: host structure, smaller cell
108+
sc_struct: bigger cell
109+
sm: StructureMatcher instance
110+
Returns:
111+
sc_m : supercell matrix to apply to s1 to get s2
112+
total_t : translation to apply on s1 * sc_m to get s2
113+
"""
114+
if sm is None:
115+
sm = StructureMatcher(
116+
primitive_cell=False, comparator=ElementComparator(), attempt_supercell=True
117+
)
118+
s1, s2 = sm._process_species([sc_struct.copy(), uc_struct.copy()])
119+
trans = sm.get_transformation(s1, s2)
120+
if trans is None:
121+
return None
122+
sc, t, mapping = trans
123+
temp = s2.copy().make_supercell(sc)
124+
ii, jj = 0, mapping[0]
125+
vec = np.round(sc_struct[ii].frac_coords - temp[jj].frac_coords)
126+
return sc, t + vec
127+
128+
89129
def _cubic_cell(
90130
base_struct: Structure,
91131
min_atoms: int = 80,
@@ -159,3 +199,66 @@ def _ase_cubic(base_structure, min_atoms: int = 80, max_atoms: int = 240):
159199
if min_dev[1] is None:
160200
raise RuntimeError("Could not find a cubic supercell")
161201
return min_dev[1]
202+
203+
204+
def _avg_lat(l1, l2):
205+
"""Get the average lattice from two lattices."""
206+
params = (np.array(l1.parameters) + np.array(l2.parameters)) / 2
207+
return Lattice.from_parameters(*params)
208+
209+
210+
def _lowest_dist(struct, ref_struct):
211+
"""For each site, return the lowest distance to any site in the reference structure."""
212+
avg_lat = _avg_lat(struct.lattice, ref_struct.lattice)
213+
_, d_2 = pbc_shortest_vectors(
214+
avg_lat, struct.frac_coords, ref_struct.frac_coords, return_d2=True
215+
)
216+
return np.min(d_2, axis=1)
217+
218+
219+
def get_closest_sc_mat(
220+
uc_struct: Structure,
221+
sc_struct: Structure,
222+
sm: StructureMatcher | None = None,
223+
debug: bool = False,
224+
):
225+
"""Get the best guess for the supercell matrix that created this defect cell.
226+
227+
Args:
228+
uc_struct: unit cell structure, should be the host structure
229+
sc_struct: supercell structure, should be the defect structure
230+
sm: StructureMatcher instance, if None, one will be created with default settings
231+
debug: bool, if True, return the full list of (distances, lattice, sc_mat) will
232+
be returned
233+
234+
Returns:
235+
sc_mat: supercell matrix to apply to s1 to get s2
236+
dist: mean distance between the two structures
237+
"""
238+
if sm is None:
239+
sm = StructureMatcher(primitive_cell=False, comparator=ElementComparator())
240+
241+
fu = int(np.round(sc_struct.lattice.volume / uc_struct.lattice.volume))
242+
candidate_lattices = tuple(
243+
sm._get_lattices(sc_struct.lattice, uc_struct, supercell_size=fu)
244+
)
245+
246+
def _get_mean_dist(lattice, sc_mat):
247+
if (
248+
np.dot(np.cross(lattice.matrix[0], lattice.matrix[1]), lattice.matrix[2])
249+
< 0
250+
):
251+
return float("inf")
252+
sc2 = uc_struct * sc_mat
253+
return np.mean(_lowest_dist(sc2, sc_struct))
254+
255+
_, best_sc_mat = min(candidate_lattices, key=lambda x: _get_mean_dist(x[0], x[1]))
256+
if debug:
257+
return sorted(
258+
[
259+
(_get_mean_dist(lat_, smat_), lat_, smat_)
260+
for lat_, smat_ in candidate_lattices
261+
],
262+
key=lambda x: x[0],
263+
)
264+
return best_sc_mat

0 commit comments

Comments
 (0)