@@ -1437,29 +1437,42 @@ def __init__(
1437
1437
min_atoms : int | None = None ,
1438
1438
max_atoms : int | None = None ,
1439
1439
min_length : float = 15.0 ,
1440
+ max_length : float | None = None ,
1440
1441
force_diagonal : bool = False ,
1441
1442
force_90_degrees : bool = False ,
1443
+ allow_orthorhombic : bool = False ,
1442
1444
angle_tolerance : float = 1e-3 ,
1445
+ step_size : float = 0.1 ,
1443
1446
):
1444
1447
"""
1445
1448
Args:
1446
1449
max_atoms: Maximum number of atoms allowed in the supercell.
1447
1450
min_atoms: Minimum number of atoms allowed in the supercell.
1448
1451
min_length: Minimum length of the smallest supercell lattice vector.
1452
+ max_length: Maximum length of the larger supercell lattice vector.
1449
1453
force_diagonal: If True, return a transformation with a diagonal
1450
1454
transformation matrix.
1451
1455
force_90_degrees: If True, return a transformation for a supercell
1452
1456
with 90 degree angles (if possible). To avoid long run times,
1453
- please use max_atoms
1457
+ please use max_atoms or max_length
1458
+ allow_orthorhombic: Instead of a cubic cell, also orthorhombic cells
1459
+ are allowed. max_length is required for this option.
1454
1460
angle_tolerance: tolerance to determine the 90 degree angles.
1461
+ step_size (float): step_size which is used to increase the supercell.
1462
+ If allow_orthorhombic and force_90_degrees is both set to True,
1463
+ the chosen step_size will be automatically multiplied by 5 to
1464
+ prevent a too long search for the possible supercell.
1455
1465
"""
1456
1466
self .min_atoms = min_atoms or - np .inf
1457
1467
self .max_atoms = max_atoms or np .inf
1458
1468
self .min_length = min_length
1469
+ self .max_length = max_length
1459
1470
self .force_diagonal = force_diagonal
1460
1471
self .force_90_degrees = force_90_degrees
1472
+ self .allow_orthorhombic = allow_orthorhombic
1461
1473
self .angle_tolerance = angle_tolerance
1462
1474
self .transformation_matrix = None
1475
+ self .step_size = step_size
1463
1476
1464
1477
def apply_transformation (self , structure : Structure ) -> Structure :
1465
1478
"""The algorithm solves for a transformation matrix that makes the
@@ -1478,74 +1491,129 @@ def apply_transformation(self, structure: Structure) -> Structure:
1478
1491
"""
1479
1492
lat_vecs = structure .lattice .matrix
1480
1493
1481
- # boolean for if a sufficiently large supercell has been created
1482
- sc_not_found = True
1494
+ if self . max_length is None and self . allow_orthorhombic :
1495
+ raise AttributeError ( "max_length is required for orthorhombic cells" )
1483
1496
1484
1497
if self .force_diagonal :
1485
1498
scale = self .min_length / np .array (structure .lattice .abc )
1486
1499
self .transformation_matrix = np .diag (np .ceil (scale ).astype (int )) # type: ignore[assignment]
1487
1500
st = SupercellTransformation (self .transformation_matrix )
1488
1501
return st .apply_transformation (structure )
1489
1502
1490
- # target_threshold is used as the desired cubic side lengths
1491
- target_sc_size = self .min_length
1492
- while sc_not_found :
1493
- target_sc_lat_vecs = np .eye (3 , 3 ) * target_sc_size
1494
- self .transformation_matrix = target_sc_lat_vecs @ np .linalg .inv (lat_vecs )
1503
+ if not self .allow_orthorhombic :
1504
+ # boolean for if a sufficiently large supercell has been created
1505
+ sc_not_found = True
1495
1506
1496
- # round the entries of T and force T to be non-singular
1497
- self .transformation_matrix = _round_and_make_arr_singular ( # type: ignore[assignment]
1498
- self .transformation_matrix # type: ignore[arg-type]
1507
+ # target_threshold is used as the desired cubic side lengths
1508
+ target_sc_size = self .min_length
1509
+ while sc_not_found :
1510
+ target_sc_lat_vecs = np .eye (3 , 3 ) * target_sc_size
1511
+ length_vecs , n_atoms , superstructure , self .transformation_matrix = self .get_possible_supercell (
1512
+ lat_vecs , structure , target_sc_lat_vecs
1513
+ )
1514
+ # Check if constraints are satisfied
1515
+ if self .check_constraints (length_vecs = length_vecs , n_atoms = n_atoms , superstructure = superstructure ):
1516
+ return superstructure
1517
+
1518
+ # Increase threshold until proposed supercell meets requirements
1519
+ target_sc_size += self .step_size
1520
+ self .check_exceptions (length_vecs , n_atoms )
1521
+
1522
+ raise AttributeError ("Unable to find cubic supercell" )
1523
+
1524
+ if self .force_90_degrees :
1525
+ # prevent a too long search for the supercell
1526
+ self .step_size *= 5
1527
+
1528
+ combined_list = [
1529
+ [size_a , size_b , size_c ]
1530
+ for size_a in np .arange (self .min_length , self .max_length , self .step_size )
1531
+ for size_b in np .arange (self .min_length , self .max_length , self .step_size )
1532
+ for size_c in np .arange (self .min_length , self .max_length , self .step_size )
1533
+ ]
1534
+ combined_list = sorted (combined_list , key = sum )
1535
+
1536
+ for size_a , size_b , size_c in combined_list :
1537
+ target_sc_lat_vecs = np .array ([[size_a , 0 , 0 ], [0 , size_b , 0 ], [0 , 0 , size_c ]])
1538
+ length_vecs , n_atoms , superstructure , self .transformation_matrix = self .get_possible_supercell (
1539
+ lat_vecs , structure , target_sc_lat_vecs
1499
1540
)
1541
+ # Check if constraints are satisfied
1542
+ if self .check_constraints (length_vecs = length_vecs , n_atoms = n_atoms , superstructure = superstructure ):
1543
+ return superstructure
1544
+
1545
+ self .check_exceptions (length_vecs , n_atoms )
1546
+ raise AttributeError ("Unable to find orthorhombic supercell" )
1500
1547
1501
- proposed_sc_lat_vecs = self .transformation_matrix @ lat_vecs
1502
-
1503
- # Find the shortest dimension length and direction
1504
- a = proposed_sc_lat_vecs [0 ]
1505
- b = proposed_sc_lat_vecs [1 ]
1506
- c = proposed_sc_lat_vecs [2 ]
1507
-
1508
- length1_vec = c - _proj (c , a ) # a-c plane
1509
- length2_vec = a - _proj (a , c )
1510
- length3_vec = b - _proj (b , a ) # b-a plane
1511
- length4_vec = a - _proj (a , b )
1512
- length5_vec = b - _proj (b , c ) # b-c plane
1513
- length6_vec = c - _proj (c , b )
1514
- length_vecs = np .array (
1515
- [
1516
- length1_vec ,
1517
- length2_vec ,
1518
- length3_vec ,
1519
- length4_vec ,
1520
- length5_vec ,
1521
- length6_vec ,
1522
- ]
1548
+ def check_exceptions (self , length_vecs , n_atoms ):
1549
+ """Check supercell exceptions."""
1550
+ if n_atoms > self .max_atoms :
1551
+ raise AttributeError (
1552
+ "While trying to solve for the supercell, the max "
1553
+ "number of atoms was exceeded. Try lowering the number"
1554
+ "of nearest neighbor distances."
1523
1555
)
1556
+ if self .max_length is not None and np .max (np .linalg .norm (length_vecs , axis = 1 )) >= self .max_length :
1557
+ raise AttributeError ("While trying to solve for the supercell, the max length was exceeded." )
1524
1558
1525
- # Get number of atoms
1526
- st = SupercellTransformation (self .transformation_matrix )
1527
- superstructure = st .apply_transformation (structure )
1528
- n_atoms = len (superstructure )
1559
+ def check_constraints (self , length_vecs , n_atoms , superstructure ):
1560
+ """
1561
+ Check if the supercell constraints are met.
1529
1562
1530
- # Check if constraints are satisfied
1531
- if (
1563
+ Returns:
1564
+ bool
1565
+
1566
+ """
1567
+ return bool (
1568
+ (
1532
1569
np .min (np .linalg .norm (length_vecs , axis = 1 )) >= self .min_length
1533
1570
and self .min_atoms <= n_atoms <= self .max_atoms
1534
- ) and (
1571
+ )
1572
+ and (
1535
1573
not self .force_90_degrees
1536
1574
or np .all (np .absolute (np .array (superstructure .lattice .angles ) - 90 ) < self .angle_tolerance )
1537
- ):
1538
- return superstructure
1575
+ )
1576
+ )
1539
1577
1540
- # Increase threshold until proposed supercell meets requirements
1541
- target_sc_size += 0.1
1542
- if n_atoms > self .max_atoms :
1543
- raise AttributeError (
1544
- "While trying to solve for the supercell, the max "
1545
- "number of atoms was exceeded. Try lowering the number"
1546
- "of nearest neighbor distances."
1547
- )
1548
- raise AttributeError ("Unable to find cubic supercell" )
1578
+ @staticmethod
1579
+ def get_possible_supercell (lat_vecs , structure , target_sc_lat_vecs ):
1580
+ """
1581
+ Get the supercell possible with the set conditions.
1582
+
1583
+ Returns:
1584
+ length_vecs, n_atoms, superstructure, transformation_matrix
1585
+ """
1586
+ transformation_matrix = target_sc_lat_vecs @ np .linalg .inv (lat_vecs )
1587
+ # round the entries of T and force T to be non-singular
1588
+ transformation_matrix = _round_and_make_arr_singular ( # type: ignore[assignment]
1589
+ transformation_matrix # type: ignore[arg-type]
1590
+ )
1591
+ proposed_sc_lat_vecs = transformation_matrix @ lat_vecs
1592
+ # Find the shortest dimension length and direction
1593
+ a = proposed_sc_lat_vecs [0 ]
1594
+ b = proposed_sc_lat_vecs [1 ]
1595
+ c = proposed_sc_lat_vecs [2 ]
1596
+ length1_vec = c - _proj (c , a ) # a-c plane
1597
+ length2_vec = a - _proj (a , c )
1598
+ length3_vec = b - _proj (b , a ) # b-a plane
1599
+ length4_vec = a - _proj (a , b )
1600
+ length5_vec = b - _proj (b , c ) # b-c plane
1601
+ length6_vec = c - _proj (c , b )
1602
+ length_vecs = np .array (
1603
+ [
1604
+ length1_vec ,
1605
+ length2_vec ,
1606
+ length3_vec ,
1607
+ length4_vec ,
1608
+ length5_vec ,
1609
+ length6_vec ,
1610
+ ]
1611
+ )
1612
+ # Get number of atoms
1613
+ st = SupercellTransformation (transformation_matrix )
1614
+ superstructure = st .apply_transformation (structure )
1615
+ n_atoms = len (superstructure )
1616
+ return length_vecs , n_atoms , superstructure , transformation_matrix
1549
1617
1550
1618
1551
1619
class AddAdsorbateTransformation (AbstractTransformation ):
0 commit comments