from __future__ import annotations
from warnings import warn
try:
import ase
import numpy as np
from pymatgen.core import Composition, Element, Structure
from pymatgen.io.ase import MSONAtoms
except ImportError:
raise ImportError("Try installing dependencies with `pip install moyopy[interface]`")
import moyopy
[docs]
class MoyoAdapter:
@staticmethod
[docs]
def get_structure(
cell: moyopy.Cell, *, unique_species_mapping: dict[int, Composition] | None = None
) -> Structure:
"""Convert a Moyo Cell to a pymatgen Structure.
If the Cell was created from a disordered Structure, the unique_species_mapping should be
provided to reconstruct the original species.
Parameters
----------
cell : moyopy.Cell
The Moyo Cell to convert.
unique_species_mapping : dict[int, Composition] | None
A mapping from integer indices used in the Moyo Cell to the original pymatgen
SpeciesLike objects. If None, assumes the Cell was created from an ordered Structure.
Returns
-------
structure : Structure
The converted pymatgen Structure object.
"""
species = _species_from_numbers(cell.numbers, unique_species_mapping)
return Structure(lattice=cell.basis, species=species, coords=cell.positions)
@staticmethod
[docs]
def get_atoms(cell: moyopy.Cell) -> ase.Atoms:
atoms = ase.Atoms(
cell=cell.basis,
scaled_positions=cell.positions,
numbers=cell.numbers,
pbc=True,
)
return atoms
@staticmethod
[docs]
def from_structure(structure: Structure) -> moyopy.Cell:
"""Convert a pymatgen Structure to a Moyo Cell."""
if not structure.is_ordered:
raise ValueError("Structure must be ordered. Use from_disordered_structure instead.")
basis = structure.lattice.matrix.tolist()
positions = structure.frac_coords.tolist()
numbers = [site.specie.Z for site in structure]
return moyopy.Cell(
basis=basis,
positions=positions,
numbers=numbers,
)
@staticmethod
[docs]
def from_disordered_structure(
structure: Structure,
) -> tuple[moyopy.Cell, dict[int, Composition]]:
"""Convert a disordered pymatgen Structure to a Moyo Cell.
Parameters
----------
structure : Structure
A pymatgen Structure object, which may contain disordered sites.
Returns
-------
cell : moyopy.Cell
The converted Moyo Cell object.
unique_species_mapping : dict[int, Composition]
A mapping from integer indices used in the Moyo Cell to the original pymatgen
SpeciesLike objects.
"""
if structure.is_ordered:
warn("Structure is ordered. Consider using from_structure.")
unique_species, unique_species_mapping = _get_unique_species_and_mapping(structure)
basis = structure.lattice.matrix.tolist()
positions = structure.frac_coords.tolist()
numbers = [unique_species[site.species] for site in structure]
cell = moyopy.Cell(
basis=basis,
positions=positions,
numbers=numbers,
)
return cell, unique_species_mapping
@staticmethod
[docs]
def from_atoms(atoms: ase.Atoms) -> moyopy.Cell:
basis = list(atoms.cell)
positions = atoms.get_scaled_positions()
numbers = atoms.get_atomic_numbers()
return moyopy.Cell(
basis=basis,
positions=positions,
numbers=numbers,
)
@staticmethod
[docs]
def from_py_obj(struct: Structure | ase.Atoms | MSONAtoms) -> moyopy.Cell:
"""Convert a Python atomic structure object to a Moyo Cell.
Args:
struct: Currently supports pymatgen Structure, ASE Atoms, and MSONAtoms
Returns:
moyopy.Cell: The converted Moyo cell
"""
if isinstance(struct, (ase.Atoms, MSONAtoms)):
return MoyoAdapter.from_atoms(struct)
elif isinstance(struct, Structure):
return MoyoAdapter.from_structure(struct)
else:
cls_name = type(struct).__name__
raise TypeError(f"Expected Structure, Atoms, or MSONAtoms, got {cls_name}")
[docs]
class MoyoNonCollinearMagneticAdapter:
@staticmethod
[docs]
def get_structure(
magnetic_cell: moyopy.NonCollinearMagneticCell,
*,
unique_species_mapping: dict[int, Composition] | None = None,
) -> Structure:
"""Convert a Moyo NonCollinearMagneticCell to a pymatgen Structure.
If the NonCollinearMagneticCell was created from a disordered Structure, the
unique_species_mapping should be provided to reconstruct the original species.
Parameters
----------
magnetic_cell : moyopy.NonCollinearMagneticCell
The Moyo NonCollinearMagneticCell to convert.
unique_species_mapping : dict[int, Composition] | None
A mapping from integer indices used in the Moyo NonCollinearMagneticCell to the
original pymatgen SpeciesLike objects. If None, assumes the NonCollinearMagneticCell
was created from an ordered Structure.
Returns
-------
structure : Structure
The converted pymatgen Structure object with magnetic moments in
site_properties['magmom'].
"""
species = _species_from_numbers(magnetic_cell.numbers, unique_species_mapping)
return Structure(
lattice=magnetic_cell.basis,
species=species,
coords=magnetic_cell.positions,
site_properties={"magmom": magnetic_cell.magnetic_moments},
)
@staticmethod
[docs]
def from_structure(structure: Structure) -> moyopy.NonCollinearMagneticCell:
"""Convert a pymatgen Structure with non-collinear magnetic moments to a Moyo
NonCollinearMagneticCell."""
if not structure.is_ordered:
raise ValueError("Structure must be ordered. Use from_disordered_structure instead.")
basis = structure.lattice.matrix.tolist()
positions = structure.frac_coords.tolist()
numbers = [site.specie.Z for site in structure]
if "magmom" not in structure.site_properties:
raise ValueError(
"Structure must have non-collinear magnetic moments in site_properties['magmom']"
)
magnetic_moments_arr = np.array(structure.site_properties["magmom"])
if magnetic_moments_arr.shape != (len(structure), 3):
raise ValueError(
f"Structure must have non-collinear magnetic moments. "
f"Expected: ({len(structure)}, 3), actual: {magnetic_moments_arr.shape}."
)
return moyopy.NonCollinearMagneticCell(
basis=basis,
positions=positions,
numbers=numbers,
magnetic_moments=magnetic_moments_arr.tolist(),
)
@staticmethod
[docs]
def from_disordered_structure(
structure: Structure,
) -> tuple[moyopy.NonCollinearMagneticCell, dict[int, Composition]]:
"""Convert a disordered pymatgen Structure with non-collinear magnetic moments to a Moyo
NonCollinearMagneticCell.
Parameters
----------
structure : Structure
A pymatgen Structure object, which may contain disordered sites. Must have
non-collinear magnetic moments in site_properties['magmom'].
Returns
-------
magnetic_cell : moyopy.NonCollinearMagneticCell
The converted Moyo NonCollinearMagneticCell object.
unique_species_mapping : dict[int, Composition]
A mapping from integer indices used in the Moyo NonCollinearMagneticCell to the
original pymatgen SpeciesLike objects.
"""
if structure.is_ordered:
warn("Structure is ordered. Consider using from_structure.")
unique_species, unique_species_mapping = _get_unique_species_and_mapping(structure)
basis = structure.lattice.matrix.tolist()
positions = structure.frac_coords.tolist()
numbers = [unique_species[site.species] for site in structure]
if "magmom" not in structure.site_properties:
raise ValueError(
"Structure must have non-collinear magnetic moments in site_properties['magmom']"
)
magnetic_moments_arr = np.array(structure.site_properties["magmom"])
if magnetic_moments_arr.shape != (len(structure), 3):
raise ValueError(
"Structure must have non-collinear magnetic moments in site_properties['magmom']. "
f"Expected shape: ({len(structure)}, 3), got: {magnetic_moments_arr.shape}."
)
magnetic_cell = moyopy.NonCollinearMagneticCell(
basis=basis,
positions=positions,
numbers=numbers,
magnetic_moments=magnetic_moments_arr.tolist(),
)
return magnetic_cell, unique_species_mapping
[docs]
def _species_from_numbers(
numbers: list[int], unique_species_mapping: dict[int, Composition] | None = None
) -> list[Composition]:
if unique_species_mapping:
species = [unique_species_mapping[number] for number in numbers]
else:
species = [Element.from_Z(number) for number in numbers]
return species
[docs]
def _get_unique_species_and_mapping(
structure: Structure,
) -> tuple[dict[Composition, int], dict[int, Composition]]:
unique_species = {}
for site in structure:
key = site.species
if key not in unique_species:
unique_species[key] = len(unique_species)
unique_species_mapping = {i: species for i, species in enumerate(unique_species)}
return unique_species, unique_species_mapping