# -*- coding: utf-8 -*-
"""
===============================================================================
Crystal structure classes (:mod:`sknano.core.structures.xtal_structures`)
===============================================================================
.. currentmodule:: sknano.core.structures.xtal_structures
"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
__docformat__ = 'restructuredtext en'
import numpy as np
from sknano.core.atoms import BasisAtom, BasisAtoms
from sknano.core.crystallography import Crystal2DLattice, Crystal3DLattice
from sknano.core.math import Vector
from sknano.core.refdata import lattice_parameters
from .base import CrystalStructureBase
from .extras import pymatgen_structure # , supercell_lattice_points
__all__ = ['Crystal2DStructure', 'Crystal3DStructure',
'CaesiumChlorideStructure', 'CsClStructure',
'DiamondStructure',
'RocksaltStructure', 'RockSaltStructure', 'NaClStructure',
'SphaleriteStructure', 'ZincblendeStructure', 'ZincBlendeStructure',
'CubicStructure', 'BCCStructure', 'FCCStructure',
'Iron', 'Copper', 'Gold',
'CubicClosePackedStructure', 'CCPStructure',
'HexagonalClosePackedStructure', 'HCPStructure',
'HexagonalStructure', 'AlphaQuartz', 'BetaQuartz', 'MoS2']
cubic_lattice_parameters = lattice_parameters['cubic']
[docs]class Crystal2DStructure(CrystalStructureBase):
"""Base class for 2D crystal structures.
.. warning:: The implementation of this class is not complete.
Parameters
----------
lattice : :class:`~sknano.core.crystallography.LatticeBase` sub-class
basis : {:class:`~python:list`, :class:`~sknano.core.atoms.BasisAtoms`}
coords : {:class:`~python:list`}, optional
cartesian : {:class:`~python:bool`}, optional
scaling_matrix : {:class:`~python:int`, :class:`~python:list`}, optional
structure : `Crystal2DStructure`, optional
"""
def __init__(self, lattice=None, basis=None, coords=None, cartesian=False,
scaling_matrix=None, structure=None, **kwargs):
if structure is not None:
lattice = structure.lattice
basis = structure.basis
if not isinstance(lattice, Crystal2DLattice):
lattice = Crystal2DLattice(cell_matrix=lattice)
super().__init__(lattice=lattice, basis=basis, coords=coords,
cartesian=cartesian, scaling_matrix=scaling_matrix,
**kwargs)
@classmethod
[docs] def from_pymatgen_structure(cls, structure):
"""Classmethod to generate structure using pymatgen."""
atoms = BasisAtoms()
for site in structure.sites:
atoms.append(BasisAtom(element=site.specie.symbol,
x=site.x, y=site.y, z=site.z))
return cls(lattice=Crystal2DLattice(
cell_matrix=structure.lattice.matrix), basis=atoms)
@classmethod
[docs] def from_spacegroup(cls, sg, lattice=None, basis=None, coords=None,
**kwargs):
"""Return a `Crystal2DStructure` from a spacegroup number/symbol.
Parameters
----------
sg : :class:`~python:int` or :class:`~python:str`
lattice : :class:`Crystal2DLattice`
basis : :class:`~python:list` of :class:`~python:str`\ s
coords : :class:`~python:list` of :class:`~python:float`\ s
Returns
-------
:class:`Crystal2DStructure`
Notes
-----
Under the hood this method first creates a pymatgen
:class:`~pymatgen:Structure`
See Also
--------
pymatgen_structure
"""
if not isinstance(basis, list):
basis = [basis]
if len(basis) != len(coords):
if isinstance(coords, list) and len(coords) != 0 and \
isinstance(coords[0], (int, float)):
coords = [coords]
cls.from_spacegroup(sg, lattice, basis, coords)
structure = \
pymatgen_structure(sg, lattice.cell_matrix, basis, coords,
classmethod='from_spacegroup')
return cls.from_pymatgen_structure(structure)
[docs]class Crystal3DStructure(CrystalStructureBase):
"""Base class for 3D crystal structures.
Parameters
----------
lattice : :class:`~sknano.core.crystallography.LatticeBase` sub-class
basis : {:class:`~python:list`, :class:`~sknano.core.atoms.BasisAtoms`}
coords : {:class:`~python:list`}, optional
cartesian : {:class:`~python:bool`}, optional
scaling_matrix : {:class:`~python:int`, :class:`~python:list`}, optional
structure : `Crystal3DStructure`, optional
"""
def __init__(self, lattice=None, basis=None, coords=None, cartesian=False,
scaling_matrix=None, structure=None, **kwargs):
if structure is not None:
lattice = structure.lattice
basis = structure.basis
if not isinstance(lattice, Crystal3DLattice):
lattice = Crystal3DLattice(cell_matrix=lattice)
super().__init__(lattice=lattice, basis=basis, coords=coords,
cartesian=cartesian, scaling_matrix=scaling_matrix,
**kwargs)
@classmethod
[docs] def from_pymatgen_structure(cls, structure):
"""Return a `Crystal3DStructure` from a \
:class:`pymatgen:pymatgen.core.Structure`.
Parameters
----------
structure : :class:`pymatgen:pymatgen.core.Structure`
Returns
-------
:class:`Crystal3DStructure`
"""
atoms = BasisAtoms()
for site in structure.sites:
atoms.append(BasisAtom(site.specie.symbol,
x=site.x, y=site.y, z=site.z))
return cls(lattice=Crystal3DLattice(
cell_matrix=structure.lattice.matrix), basis=atoms)
@classmethod
[docs] def from_spacegroup(cls, sg, lattice=None, basis=None, coords=None,
structure=None, **kwargs):
"""Return a `Crystal3DStructure` from a spacegroup number/symbol.
Parameters
----------
sg : :class:`~python:int` or :class:`~python:str`
lattice : :class:`Crystal3DLattice`
basis : :class:`~python:list` of :class:`~python:str`\ s
coords : :class:`~python:list` of :class:`~python:float`\ s
Returns
-------
:class:`Crystal3DStructure`
Notes
-----
Under the hood, this method generates a :class:`Crystal3DStructure`
from a :class:`~pymatgen:pymatgen.core.Structure`.
See Also
--------
pymatgen_structure
"""
if structure is not None and coords is not None:
basis = structure.basis.copy()
lattice = basis.lattice
basis_elements = list(set(basis.elements))
if len(basis_elements) != len(coords):
raise ValueError('You must specify coordinates of structure '
'basis')
xtal_basis = BasisAtoms()
xtal_structure = cls.from_spacegroup(sg, lattice=lattice,
basis=basis_elements,
coords=coords)
basis.center_centroid()
for tvec in xtal_structure.basis.r:
basis_copy = basis.copy()
basis_copy.rotate(axis=Vector(np.random.rand(3)).unit_vector,
angle=2*np.pi*np.random.rand(1))
basis_copy.translate(tvec)
xtal_basis.extend(basis_copy)
# for atom in basis:
# for tvec in xtal_structure.basis.r:
# xs, ys, zs = \
# lattice.cartesian_to_fractional(atom.r + tvec)
# xtal_basis.append(BasisAtom(atom.element,
# lattice=lattice,
# xs=xs, ys=ys, zs=zs))
xtal_structure.basis = xtal_basis
return xtal_structure
if not isinstance(basis, list):
basis = [basis]
if len(basis) != len(coords):
if isinstance(coords, list) and len(coords) != 0 and \
isinstance(coords[0], (int, float)):
coords = [coords]
cls.from_spacegroup(sg, lattice=lattice, basis=basis,
coords=coords, **kwargs)
structure = \
pymatgen_structure(sg, lattice.cell_matrix, basis, coords,
classmethod='from_spacegroup')
return cls.from_pymatgen_structure(structure, **kwargs)
[docs]class CubicStructure(Crystal3DStructure):
"""Base class for a cubic `Crystal3DStructure`.
Parameters
----------
centering : :class:`~python:str`
lattice : :class:`Crystal3DLattice`, optional
a : float, optional
basis : :class:`~python:list`, optional
coords : :class:`~python:list`, optional
scaling_matrix : :class:`~python:int` or :class:`~python:list`, optional
structure : :class:`Crystal3DStructure`, optional
"""
def __init__(self, *args, centering=None, lattice=None, basis=None,
coords=None, scaling_matrix=None, structure=None,
**kwargs):
if len(args) == 1 and basis is None:
basis = args[0]
if lattice is None and structure is None:
lattice = CubicStructure.get_lattice(basis=basis,
centering=centering,
**kwargs)
super().__init__(lattice=lattice, basis=basis, coords=coords,
scaling_matrix=scaling_matrix, structure=structure,
**kwargs)
@classmethod
[docs] def get_lattice(cls, name=None, centering=None, basis=None, **kwargs):
"""Return cubic lattice."""
if centering is None or not isinstance(centering, str):
raise TypeError('Expected str for `centering` parameter')
elif centering not in cubic_lattice_parameters:
raise ValueError('Unrecognized `centering` value')
a = kwargs.pop('a',
cubic_lattice_parameters[centering].get(name, None))
if a is None:
if basis is None:
raise ValueError('`basis` cannot be None')
elif isinstance(basis, (tuple, list)):
if len(basis) != 1:
raise ValueError('Expected a single element basis')
else:
basis = basis[0]
if not isinstance(basis, str):
raise ValueError('Expected `str` object for basis')
if basis not in cubic_lattice_parameters[centering]:
raise ValueError('No lattice parameters for given basis '
'{}'.format(basis))
a = cubic_lattice_parameters[centering][basis]
lattice = Crystal3DLattice.cubic(a)
return lattice
@classmethod
[docs] def from_spacegroup(cls, *args, lattice=None, basis=None, coords=None,
structure=None, centering=None, **kwargs):
"""Return :class:`CubicStructure` from spacegroup."""
if len(args) == 2 and basis is None:
sg, basis = args
if len(args) == 1:
sg = args[0]
if lattice is None and structure is None:
lattice = CubicStructure.get_lattice(basis=basis,
centering=centering,
**kwargs)
if coords is None:
coords = [[0, 0, 0]]
return super().from_spacegroup(sg, lattice=lattice, basis=basis,
coords=coords, structure=structure)
[docs]class CaesiumChlorideStructure(CubicStructure):
"""Abstract representation of caesium chloride structure."""
def __init__(self, lattice=None, basis=['Cs', 'Cl'], **kwargs):
if lattice is None:
lattice = self.get_lattice(name='caesium_chloride',
centering='cP', **kwargs)
caesium_chloride = \
pymatgen_structure(221, lattice.cell_matrix, basis,
[[0, 0, 0], [0.5, 0.5, 0.5]],
classmethod='from_spacegroup')
caesium_chloride = \
Crystal3DStructure.from_pymatgen_structure(caesium_chloride)
super().__init__(structure=caesium_chloride, **kwargs)
CsClStructure = CaesiumChlorideStructure
[docs]class DiamondStructure(CubicStructure):
"""Abstract representation of diamond structure."""
def __init__(self, lattice=None, basis=['C'], **kwargs):
if lattice is None:
lattice = self.get_lattice(name='diamond', centering='FCC',
**kwargs)
diamond = \
pymatgen_structure(227, lattice.cell_matrix, basis, [[0, 0, 0]],
classmethod='from_spacegroup')
diamond = \
Crystal3DStructure.from_pymatgen_structure(diamond)
super().__init__(structure=diamond, **kwargs)
[docs]class RocksaltStructure(CubicStructure):
"""Abstract representation of rock salt structure."""
def __init__(self, lattice=None, basis=['Na', 'Cl'], **kwargs):
if lattice is None:
lattice = self.get_lattice(name='rocksalt', centering='FCC',
**kwargs)
rock_salt = \
pymatgen_structure(225, lattice.cell_matrix, basis,
[[0, 0, 0], [0.5, 0.5, 0.5]],
classmethod='from_spacegroup')
rock_salt = \
Crystal3DStructure.from_pymatgen_structure(rock_salt)
super().__init__(structure=rock_salt, **kwargs)
NaClStructure = RockSaltStructure = RocksaltStructure
[docs]class ZincblendeStructure(CubicStructure):
"""Abstract representation of zinc blende structure."""
def __init__(self, lattice=None, basis=['Zn', 'S'], **kwargs):
if lattice is None:
lattice = self.get_lattice(name='zincblende', centering='FCC',
**kwargs)
zincblende = \
pymatgen_structure(216, lattice.cell_matrix, basis,
[[0, 0, 0], [0.25, 0.25, 0.25]],
classmethod='from_spacegroup')
zincblende = \
Crystal3DStructure.from_pymatgen_structure(zincblende)
super().__init__(structure=zincblende, **kwargs)
SphaleriteStructure = ZincBlendeStructure = ZincblendeStructure
[docs]class BCCStructure(CubicStructure):
"""BCC structure class."""
def __init__(self, *args, **kwargs):
kwargs['centering'] = 'bcc'
kwargs['structure'] = \
CubicStructure.from_spacegroup(229, *args, **kwargs)
super().__init__(*args, **kwargs)
[docs]class FCCStructure(CubicStructure):
"""FCC structure class."""
def __init__(self, *args, **kwargs):
kwargs['centering'] = 'fcc'
kwargs['structure'] = \
CubicStructure.from_spacegroup(225, *args, **kwargs)
super().__init__(*args, **kwargs)
[docs]class Iron(BCCStructure):
"""Iron structure."""
def __init__(self, **kwargs):
kwargs['basis'] = 'Fe'
super().__init__(**kwargs)
[docs]class Gold(FCCStructure):
"""Gold structure."""
def __init__(self, **kwargs):
kwargs['basis'] = 'Au'
super().__init__(**kwargs)
[docs]class Copper(FCCStructure):
"""Copper structure."""
def __init__(self, **kwargs):
kwargs['basis'] = 'Cu'
super().__init__(**kwargs)
[docs]class HexagonalClosePackedStructure(Crystal3DStructure):
"""Abstract representation of hexagonal close-packed structure."""
pass
HCPStructure = HexagonalClosePackedStructure
[docs]class CubicClosePackedStructure(Crystal3DStructure):
"""Abstract representation of cubic close-packed structure."""
pass
CCPStructure = CubicClosePackedStructure
[docs]class HexagonalStructure(Crystal3DStructure):
"""Base :class:`Crystal3DStructure` for a hexagonal crystal system.
Parameters
----------
centering : :class:`~python:str`
lattice : :class:`Crystal3DLattice`, optional
a, c : :class:`~python:float`, optional
basis : :class:`~python:list`, optional
coords : :class:`~python:list`, optional
scaling_matrix : :class:`~python:int` or :class:`~python:list`, optional
structure : :class:`Crystal3DStructure`, optional
"""
def __init__(self, *args, lattice=None, basis=None, coords=None,
scaling_matrix=None, structure=None, **kwargs):
if len(args) == 1 and basis is None:
basis = args[0]
if lattice is None and structure is None:
lattice = self.get_lattice(basis=basis, **kwargs)
super().__init__(lattice=lattice, basis=basis, coords=coords,
scaling_matrix=scaling_matrix, structure=structure,
**kwargs)
@classmethod
[docs] def get_lattice(cls, name=None, basis=None, **kwargs):
"""Return hexagonal :class:`Crystal3DLattice`."""
a, c = [kwargs.pop(k, default) for k, default in
zip(('a', 'c'),
lattice_parameters['hexagonal'].get(name, (None, None)))]
if a is None or c is None:
if basis is None:
raise ValueError('`basis` cannot be None.')
elif isinstance(basis, (tuple, list)):
if len(basis) != 1:
raise ValueError('Expected a single element basis')
else:
basis = basis[0]
if not isinstance(basis, str):
raise ValueError('Expected `str` object for basis')
if basis not in lattice_parameters['hexagonal']:
raise ValueError('No lattice constants for '
'given basis {}'.format(basis))
a, c = lattice_parameters['hexagonal'][basis]
lattice = Crystal3DLattice.hexagonal(a, c)
return lattice
@classmethod
[docs] def from_spacegroup(cls, *args, lattice=None, basis=None, coords=None,
structure=None, **kwargs):
"""Return :class:`HexagonalStructure` from space group."""
if len(args) == 2 and basis is None:
sg, basis = args
if len(args) == 1:
sg = args[0]
if lattice is None and structure is None:
lattice = HexagonalStructure.get_lattice(basis=basis, **kwargs)
if coords is None:
coords = [[0, 0, 0]]
return super().from_spacegroup(sg, lattice=lattice, basis=basis,
coords=coords, structure=structure)
[docs]class MoS2(HexagonalStructure):
"""Molybdenum disulphide structure class."""
def __init__(self, lattice=None, **kwargs):
if lattice is None:
lattice = self.get_lattice(name='MoS2', **kwargs)
basis = ['Mo', 'S']
molybdenum_disulphide = \
pymatgen_structure(194, lattice.cell_matrix, basis,
[[1/3, 2/3, 1/4], [1/3, 2/3, 0.621]],
classmethod='from_spacegroup')
molybdenum_disulphide = \
Crystal3DStructure.from_pymatgen_structure(molybdenum_disulphide)
super().__init__(structure=molybdenum_disulphide, **kwargs)
[docs]class AlphaQuartz(HexagonalStructure):
"""Alpha quartz structure class."""
def __init__(self, lattice=None, **kwargs):
if lattice is None:
lattice = self.get_lattice(name='alpha_quartz', **kwargs)
# basis = BasisAtoms(3 * ["Si"] + 6 * ["O"])
basis = 3 * ['Si'] + 6 * ['O']
coords = [[0.4697, 0.0000, 0.0000],
[0.0000, 0.4697, 0.6667],
[0.5305, 0.5303, 0.3333],
[0.4133, 0.2672, 0.1188],
[0.2672, 0.4133, 0.5479],
[0.7328, 0.1461, 0.7855],
[0.5867, 0.8539, 0.2145],
[0.8539, 0.5867, 0.4521],
[0.1461, 0.7328, 0.8812]]
alpha_quartz = pymatgen_structure(lattice.cell_matrix, basis, coords)
alpha_quartz = \
Crystal3DStructure.from_pymatgen_structure(alpha_quartz)
# alpha_quartz = \
# HexagonalStructure.from_spacegroup(154, lattice, ["Si", "O"],
# [[0.4697, 0.0000, 0.0000],
# [0.4135, 0.2669, 0.1191]])
super().__init__(structure=alpha_quartz, **kwargs)
[docs]class BetaQuartz(HexagonalStructure):
"""Beta quartz structure class."""
def __init__(self, lattice=None, **kwargs):
if lattice is None:
lattice = self.get_lattice(name='beta_quartz', **kwargs)
basis = ["Si", "O"]
# basis = 3 * ['Si'] + 6 * ['O']
# basis = BasisAtoms(3 * ["Si"] + 6 * ["O"])
# coords = [[0.4697, 0.0000, 0.0000],
# [0.0000, 0.4697, 0.6667],
# [0.5305, 0.5303, 0.3333],
# [0.4133, 0.2672, 0.1188],
# [0.2672, 0.4133, 0.5479],
# [0.7328, 0.1461, 0.7855],
# [0.5867, 0.8539, 0.2145],
# [0.8539, 0.5867, 0.4521],
# [0.1461, 0.7328, 0.8812]]
# beta_quartz = pymatgen_structure(lattice.cell_matrix,
# basis.symbols, coords)
# alpha_quartz = \
# Crystal3DStructure.from_pymatgen_structure(alpha_quartz)
beta_quartz = pymatgen_structure(180, lattice.cell_matrix, basis,
[[0.5000, 0.0000, 0.0000],
[0.4147, 0.2078, 0.1667]],
classmethod='from_spacegroup')
beta_quartz = Crystal3DStructure.from_pymatgen_structure(beta_quartz)
super().__init__(structure=beta_quartz, **kwargs)