@@ -1499,6 +1499,130 @@ def get_spglib_cell(self):
14991499 cell = (lattice , positions , numbers )
15001500 return cell
15011501
1502+ def get_primitive_cell (self , symprec = 1e-5 , angle_tolerance = - 1.0 , no_idealize = False ,
1503+ return_itau = False ):
1504+ """
1505+ GET PRIMITIVE CELL
1506+ ==================
1507+
1508+ This method uses spglib to find the primitive cell from a supercell structure.
1509+ It exploits the crystal symmetries to identify the smallest repeating unit.
1510+
1511+ If the input structure is already a primitive cell, a copy is returned.
1512+
1513+ Parameters
1514+ ----------
1515+ symprec : float, optional
1516+ Symmetry precision tolerance in Cartesian coordinates.
1517+ Default is 1e-5.
1518+ angle_tolerance : float, optional
1519+ Tolerance of angle between basis vectors in degrees.
1520+ If negative (default), the angle tolerance is disabled.
1521+ no_idealize : bool, optional
1522+ If True, disable idealization of basis vectors and atomic positions.
1523+ This preserves the original Cartesian coordinates. Default is False.
1524+ return_itau : bool, optional
1525+ If True, also return the itau array mapping from input atoms to primitive atoms.
1526+ Similar to the itau in generate_supercell, this indicates which primitive atom
1527+ each input atom corresponds to. Default is False.
1528+
1529+ Returns
1530+ -------
1531+ Structure or tuple
1532+ The primitive cell structure. If return_itau is True, returns a tuple
1533+ (primitive_structure, itau) where itau is an ndarray indicating
1534+ which primitive atom each input atom corresponds to.
1535+
1536+ Raises
1537+ ------
1538+ ImportError
1539+ If spglib is not available.
1540+ ValueError
1541+ If the structure has no unit cell or if primitive search fails.
1542+
1543+ Notes
1544+ -----
1545+ This method requires spglib to be installed. The optional dependency is
1546+ handled with the __SPGLIB__ flag from the symmetries module.
1547+
1548+ The primitive cell search may change the orientation of the lattice vectors
1549+ to match spglib's standard conventions, unless no_idealize=True.
1550+
1551+ The masses dictionary is copied from the original structure to the primitive
1552+ structure, preserving all atomic masses.
1553+ """
1554+ # Check if spglib is available
1555+ if not SYM .__SPGLIB__ :
1556+ raise ImportError ("Error, get_primitive_cell requires spglib to be installed." )
1557+
1558+ # Check if structure has unit cell
1559+ if not self .has_unit_cell :
1560+ raise ValueError ("Error, the structure must have a valid unit cell to find the primitive cell." )
1561+
1562+ # Get the spglib cell representation
1563+ cell = self .get_spglib_cell ()
1564+
1565+ # Use spglib to find the primitive cell
1566+ # standardize_cell with to_primitive=True is the modern recommended approach
1567+ primitive_cell = SYM .spglib .standardize_cell (
1568+ cell ,
1569+ to_primitive = True ,
1570+ no_idealize = no_idealize ,
1571+ symprec = symprec ,
1572+ angle_tolerance = angle_tolerance
1573+ )
1574+
1575+ if primitive_cell is None :
1576+ raise ValueError ("Error, spglib failed to find the primitive cell. "
1577+ "The structure may be too distorted or symprec too strict." )
1578+
1579+ # Unpack the primitive cell data
1580+ primitive_lattice , primitive_positions , primitive_numbers = primitive_cell
1581+
1582+ # Create a new Structure for the primitive cell
1583+ primitive_struct = Structure ()
1584+ primitive_struct .has_unit_cell = True
1585+ primitive_struct .unit_cell = np .array (primitive_lattice , dtype = np .float64 )
1586+
1587+ # Convert fractional positions to Cartesian coordinates
1588+ # primitive_positions are in fractional coordinates of the primitive lattice
1589+ primitive_struct .coords = np .zeros ((len (primitive_positions ), 3 ), dtype = np .float64 )
1590+ for i , pos in enumerate (primitive_positions ):
1591+ # Convert fractional to Cartesian: r_cart = r_frac * lattice_vectors
1592+ primitive_struct .coords [i , :] = np .dot (pos , primitive_lattice )
1593+
1594+ primitive_struct .N_atoms = len (primitive_numbers )
1595+
1596+ # Map atomic numbers back to symbols
1597+ # We need to reverse the mapping from get_spglib_cell()
1598+ # Build the forward mapping first (same logic as get_spglib_cell)
1599+ forward_mapping = {}
1600+ for s in self .atoms :
1601+ forward_mapping .setdefault (s , len (forward_mapping ) + 1 )
1602+
1603+ # Create reverse mapping: number -> symbol
1604+ reverse_mapping = {v : k for k , v in forward_mapping .items ()}
1605+
1606+ # Assign atomic symbols
1607+ primitive_struct .atoms = [reverse_mapping [num ] for num in primitive_numbers ]
1608+
1609+ # Copy the masses dictionary from the original structure
1610+ primitive_struct .masses = self .masses .copy ()
1611+
1612+ # Handle return_itau
1613+ if return_itau :
1614+ # Get the symmetry dataset which contains the mapping information
1615+ dataset = SYM .spglib .get_symmetry_dataset (cell , symprec = symprec , angle_tolerance = angle_tolerance )
1616+ if dataset is None :
1617+ raise ValueError ("Error, spglib failed to get symmetry dataset for itau mapping." )
1618+
1619+ # mapping_to_primitive gives the index of the primitive atom for each input atom
1620+ # This is analogous to itau in generate_supercell
1621+ itau = np .array (dataset .mapping_to_primitive , dtype = np .intc )
1622+ return primitive_struct , itau
1623+
1624+ return primitive_struct
1625+
15021626 def get_phonopy_calculation (self , supercell = [1 ,1 ,1 ]):
15031627 """
15041628 Convert the CellConstructor structure to a phonopy object
0 commit comments