22
33from __future__ import annotations
44
5+ import logging
6+ import math
57from typing import TYPE_CHECKING , Any
68
79import numpy as np
8- import math
9- import logging
10-
1110from pymatgen .core .interface import GrainBoundaryGenerator
1211from pymatgen .symmetry .analyzer import SpacegroupAnalyzer
1312
@@ -33,22 +32,23 @@ class GBCalc(PropCalc):
3332
3433 :ivar relax_bulk: Whether to relax the bulk structure (cell and atoms) before GB generation.
3534 :type relax_bulk: bool
36-
35+
3736 :ivar relax_gb: Whether to relax the grain boundary structures (atoms only) after generation.
3837 :type relax_gb: bool
39-
38+
4039 :ivar fmax: Force convergence criterion (eV/Å) for relaxations.
4140 :type fmax: float
42-
41+
4342 :ivar optimizer: Optimizer for structure relaxations.
4443 :type optimizer: str | Optimizer
45-
44+
4645 :ivar max_steps: Maximum optimization steps for relaxations.
4746 :type max_steps: int
4847
4948 :ivar relax_calc_kwargs: Additional keyword arguments for relaxation calculations. Defaults to None.
50- :type relax_calc_kwargs: dict | None
49+ :type relax_calc_kwargs: dict | None
5150 """
51+
5252 def __init__ (
5353 self ,
5454 calculator : Calculator | str ,
@@ -69,16 +69,16 @@ def __init__(
6969
7070 :param relax_bulk: Whether to relax the bulk structure before GB generation. Default True.
7171 :type relax_bulk: bool, optional
72-
72+
7373 :param relax_gb: Whether to relax the GB structures after generation. Default True.
7474 :type relax_gb: bool, optional
75-
75+
7676 :param fmax: Force tolerance (eV/Å) for relaxations. Default 0.1.
7777 :type fmax: float, optional
78-
78+
7979 :param optimizer: ASE optimizer or name for relaxations. Default "FIRE".
8080 :type optimizer: str | Optimizer, optional
81-
81+
8282 :param max_steps: Max optimization steps. Default 500.
8383 :type max_steps: int, optional
8484
@@ -141,7 +141,7 @@ def calc_gb(
141141
142142 :param gb_generator_kwargs: Additional args for GrainBoundaryGenerator(). Default None.
143143 :type gb_generator_kwargs: dict | None, optional
144-
144+
145145 :param gb_from_parameters_kwargs: Additional args for gb_from_parameters(). Default None.
146146 :type gb_from_parameters_kwargs: dict | None, optional
147147
@@ -150,7 +150,6 @@ def calc_gb(
150150 'bulk_energy_per_atom' and 'final_bulk'.
151151 :rtype: dict[str, Any]
152152 """
153-
154153 # GB generation - start with a primitive structure.
155154 structure = to_pmg_structure (structure )
156155 bulk_primitive = structure .to_primitive ()
@@ -175,41 +174,46 @@ def calc_gb(
175174 initial_structure = bulk_primitive ,
176175 ** (gb_generator_kwargs or {}),
177176 )
178-
177+
179178 analyzer = SpacegroupAnalyzer (bulk_primitive )
180179 lattice_type = analyzer .get_crystal_system ()[0 ]
181180 c_a_ratio = gb_gen .get_ratio ()
182- angles = gb_gen .get_rotation_angle_from_sigma (sigma = sigma , r_axis = rotation_axis , lat_type = lattice_type , ratio = c_a_ratio )
181+ angles = gb_gen .get_rotation_angle_from_sigma (
182+ sigma = sigma , r_axis = rotation_axis , lat_type = lattice_type , ratio = c_a_ratio
183+ )
183184
184- # look through the list of computed angles and find the one
185+ # look through the list of computed angles and find the one
185186 # that is within the tolerance of the requested angle
186187 rotation_angle_exact = next (
187- (a for a in angles
188- if math .isclose (a , rotation_angle , abs_tol = rotation_angle_tolerance )),
188+ (a for a in angles if math .isclose (a , rotation_angle , abs_tol = rotation_angle_tolerance )),
189189 None ,
190190 )
191191 # …and if none are within the tolerance, error out
192192 if rotation_angle_exact is None :
193- raise ValueError (f"No matching rotation angle { rotation_angle } for sigma={ sigma } . \
194- Possible angles: { angles } " )
195-
193+ raise ValueError (
194+ f"No matching rotation angle { rotation_angle } for sigma={ sigma } . \
195+ Possible angles: { angles } "
196+ )
197+
196198 grain_boundary = gb_gen .gb_from_parameters (
197- rotation_axis = rotation_axis ,
198- rotation_angle = rotation_angle_exact ,
199- plane = gb_plane ,
200- expand_times = expand_times ,
201- vacuum_thickness = vacuum_thickness ,
202- ab_shift = ab_shift ,
203- normal = normal ,
204- rm_ratio = rm_ratio ,
205- ** (gb_from_parameters_kwargs or {}),
199+ rotation_axis = rotation_axis ,
200+ rotation_angle = rotation_angle_exact ,
201+ plane = gb_plane ,
202+ expand_times = expand_times ,
203+ vacuum_thickness = vacuum_thickness ,
204+ ab_shift = ab_shift ,
205+ normal = normal ,
206+ rm_ratio = rm_ratio ,
207+ ** (gb_from_parameters_kwargs or {}),
206208 )
207209
208- return self .calc ({
210+ return self .calc (
211+ {
209212 "grain_boundary" : grain_boundary ,
210213 "bulk_energy_per_atom" : bulk_energy_per_atom ,
211214 "final_bulk" : final_bulk ,
212- })
215+ }
216+ )
213217
214218 def calc (
215219 self ,
@@ -236,7 +240,7 @@ def calc(
236240 "For grain boundary calculations, structure must be a dict in one of the following formats: "
237241 "{'grain_boundary': gb_structure, 'bulk': bulk_structure} or {'grain_boundary': gb_structure, 'bulk_energy_per_atom': energy}."
238242 )
239-
243+
240244 result_dict = structure .copy ()
241245
242246 if "bulk_energy_per_atom" in structure :
@@ -247,7 +251,7 @@ def calc(
247251 calculator = self .calculator ,
248252 fmax = self .fmax ,
249253 max_steps = self .max_steps ,
250- relax_cell = self .relax_bulk ,
254+ relax_cell = self .relax_bulk ,
251255 relax_atoms = self .relax_bulk ,
252256 optimizer = self .optimizer ,
253257 ** (self .relax_calc_kwargs or {}),
@@ -280,11 +284,9 @@ def calc(
280284
281285 # Compute grain boundary energy
282286 gamma_gb = (gb_energy - n_atoms * bulk_energy_per_atom ) / (2 * area )
283-
287+
284288 return result_dict | {
285289 "final_grain_boundary" : final_gb ,
286290 "gb_relax_energy" : gb_energy ,
287291 "grain_boundary_energy" : gamma_gb ,
288292 }
289-
290-
0 commit comments