Source code for muesr.core.magmodel

# http://magcryst.org/resources/magnetic-coordinates/

import numpy as np
from muesr.core.cells import get_cell_parameters
from muesr.core.isstr import isstr

have_sympy = True
try:
    import sympy as sy
except:
    have_sympy = False


[docs]class MM(object): """ Magnetic model class. The magnetic structures are defined in terms of a single propagation vector, complex Fourier components and phases. :param int cell_size: number of Fourier components=number of atoms :param latt_vects: lattice vectors as numpy 3x3 ndarray. If None, the Fourier componentscan only be specified in cartesian coordinates. :raises: TypeError """ __isfrozen = False def __setattr__(self, key, value): if self.__isfrozen and not hasattr(self, key): raise TypeError( "Cannot set attributes in this class" ) object.__setattr__(self, key, value) def _freeze(self): self.__isfrozen = True def __init__(self, cell_size, latt_vects=None): self._size = int(cell_size) try: assert(self._size>0) except: ValueError("Cannot parse size of mag model." + "Must be (strictly) positive int.") self._description = "No title" self._validFormats = [0] if isinstance(latt_vects, np.ndarray): if latt_vects.shape == (3,3): self._latt = latt_vects self._rlatt = np.dot(np.diag(np.divide([1.,1.,1.], get_cell_parameters(self._latt))),self._latt) self._validFormats += [1,2] else: raise TypeError("Cannot parse lattice vectors.") elif latt_vects is None: self._latt = None self._rlatt = None else: raise TypeError("Lattice vectors must be numpy array.") self._fc = np.zeros([cell_size,3],dtype=np.complex) self._k = np.array([0,0,0]) self._phi = np.zeros(cell_size,dtype=np.float) self._freeze() # no new attributes after this point. @property def size(self): """ Number of Fourier components. """ return self._size @property def lattice_params(self): """ Lattice parameters used to convert the Fourier components to the various coordinates systems. """ return self._latt.copy() @property def k(self): """ The propagation vector in reciprocal lattice units. :getter: Returns a numpy array of shape (3,) with the propagation vector. :setter: Sets the propagation vector. :type: numpy ndarray of shape (3,) """ return self._k.copy() @k.setter def k(self, value): if isinstance(value, list): try: value = np.asarray(value,np.float) if value.shape == (3,): self._k=value else: raise ValueError('Array must be a single 3D vector.') except: raise ValueError('Array must be a single 3D vector.') elif isinstance(value, np.ndarray): if value.shape == (3,): self._k=np.asarray(value,np.float) else: raise ValueError('Array must be a single 3D vector.') else: raise TypeError('k value must be a 3D numpy array or list.') @property def fc(self): """ Fourier components in Cartesian coordinates. Same as :py:attr:`~fcCart` """ return self.fc_get() @fc.setter def fc(self, value): return self.fc_set(value) @property def fcCart(self): """ Get Fourier components in Cartesian coordinates. :getter: Returns a numpy array of size (:py:attr:`~size`,3) with the fourier components. :setter: Sets the fourier compinents :type: numpy ndarray of size (:py:attr:`~size`,3) """ return self.fc_get() @fcCart.setter def fcCart(self,value): return self.fc_set(value) @property def fcLattBMA(self): """ Get Fourier components in Bohr Magneton/Angstrom units, with x||a, y||b and z||c :getter: Returns a numpy array of size (:py:attr:`~size`,3) with the fourier components. :setter: Sets the fourier compinents :type: numpy ndarray of size (:py:attr:`~size`,3) """ return self.fc_get(1) @fcLattBMA.setter def fcLattBMA(self,value): return self.fc_set(value, 1) @property def fcLattBM(self): """ Fourier components in Bohr Magneton units, with x||a, y||b and z||c :getter: Returns a numpy array of size (:py:attr:`~size`,3) with the fourier components. :setter: Sets the fourier compinents :type: numpy ndarray of size (:py:attr:`~size`,3) """ return self.fc_get(2) @fcLattBM.setter def fcLattBM(self, value): return self.fc_set(value,2)
[docs] def fc_get(self, coord_system=0): """ Retrives Fourier components. :params int coord_system: requested coordinate system. * 0 means Cartesian coordinates * 1 means Lattice coordinates (values in units of Bohr Magneton/Angstrom) * 2 means Bohr magnetons in each lattice direction (values in units Bohr Magneton) See http://magcryst.org/resources/magnetic-coordinates/ :raises: ValueError """ # check if the lattice is defined, otherwise no conversion :/ coord_system = int(coord_system) if not (coord_system in self._validFormats): raise ValueError("Invalid/unsupported input type. Have you provided lattice cell at instantiation?") if coord_system == 0: return np.copy(self._fc) elif coord_system == 1: return np.copy(np.dot(self._fc,np.linalg.inv(self._latt))) elif coord_system == 2: return np.copy(np.dot(self._fc, np.linalg.inv(self._rlatt)))
[docs] def fc_set(self, value, coord_system=0): """ Sets Fourier components. :param value: numpy array containing the fourier components for all the atoms. :param int coord_system: requested coordinate system. * 0 means Cartesian coordinates * 1 means Lattice coordinates (values in units of Bohr Magneton/Angstrom) * 2 means Bohr magnetons in each lattice direction (values in units Bohr Magneton) See http://magcryst.org/resources/magnetic-coordinates/ :raises: ValueError, TypeError """ validFCS = False validType = False # validate FCs if isinstance(value, np.ndarray): if value.dtype != np.complex: raise ValueError("Fourier components must be a complex array!") #check that number of FCs is the same as atoms if not (value.shape == self._fc.shape): raise ValueError("Invalid shape: {}".format(value.shape) + " instead of {}".format(self._fc.shape)) else: raise TypeError("Value must be numpy array.") #validate coord_system if type(coord_system) != int: raise TypeError("coord_system must be int.") # check if the lattice is defined, otherwise no conversion :/ if not (coord_system in self._validFormats): raise ValueError("Invalid/unsupported input type. Have you provided lattice cell at instantiation?") if coord_system==0: # no conversion needed self._fc = value elif coord_system==1: self._fc = np.dot(value, self._latt) elif coord_system==2: # we get cartesian coordinates as for atoms. self._fc = np.dot(value, self._rlatt)
@property def phi(self): """ The phase for each fourier component in units of 2 PI. :getter: Returns a numpy array of size :py:attr:`~size` with the phases. :setter: Sets the phases. Type can be list of numpy array. :type: numpy ndarray """ return self._phi.copy() @phi.setter def phi(self, value): if isinstance(value, list): value = np.array(value,dtype=np.float) if isinstance(value, np.ndarray): if value.shape == self._phi.shape: self._phi=np.asarray(value,np.float) else: raise ValueError("Incorrect size of array/list. Must " + "be a 1D list of " + str(self._size) + "phases.") else: raise TypeError("Must be numpy array or list.") @property def desc(self): """ Description of the magnetic structure :getter: Returns the description :setter: Sets the description :type: str """ return self._description @desc.setter def desc(self, value): if isstr(value): try: value = str(value) self._description = value except: raise TypeError("Description type must be type string, got {} instead.".format(type(value))) else: raise TypeError("Description type must be type string, got {} instead.".format(type(value))) @property def isSymbolic(self): """ True if Fourier components are defined as sympy symbols, False otherwise. ALWAYS False for MM objects. """ return False
#def set_k(mm, kval): # ''' # Sets propagation vector of a MagneticModel object. # ''' # if (type(kval) is np.ndarray): # mm.k = kval # return True # # elif (type(kval) is list): # mm.k = np.array(kval) # return True # # else: # return False # if have_sympy: class SMM(MM): """ This class defines a symbolic magnetic order in which Fuorier components are symbols and not numbers. :param int cell_size: number of Fourier components=number of atoms :param str sparams: comma separated symbols used in sympy.symbols. Example: "x,y,z" :param latt_vects: lattice vectors as numpy 3x3 ndarray. If None, the Fourier componentscan only be specified in cartesian coordinates. """ def __init__(self, cell_size, sparams, latt_vects=None): """ Initialize symbolic magnetic structure. """ self._symbols = sy.symbols(sparams) self._symFCexpr = None self._symFClambda = None self._inputType = -1 MM.__init__(self, cell_size, latt_vects) def set_symFC(self, value, coord_system=0): """ This function is used to set the symbolic value of the Fourier components for each atom in the system (non magnetic atoms should have 0 value) :param str value: a string passed to sympy.sympify. Must be a 2D list. Example "[[0,0,0],[x,y,0]]" or "[[0,0,z],[I,I,0]]". See sympy documentation for more details :param int coord_system: coordinate definition for Fourier components. * 0 means Cartesian coordinates * 1 means Lattice coordinates (values in units of Bohr Magneton/Angstrom) * 2 means Bohr magnetons in each lattice direction (values in units Bohr Magneton) See http://magcryst.org/resources/magnetic-coordinates/ :return: None :rtype: None :raises: TypeError, ValueError """ #TODO: check that only declared symbols are present if type(coord_system) != int: raise TypeError("Cannot parse inputType for fourier componets definition.") else: self._inputType = coord_system if not isstr(value): raise TypeError("value type must be str") self._symFCexpr = sy.sympify(value) # "[[0,0,0],[x,y,0]]" if self._symFCexpr is None: raise ValueError("Cannot sympyfy expression! See sympy manual.") if len(self._symFCexpr) != self.size: size_given = len(self._symFCexpr) self._symFCexpr = None self._symFClambda = None raise ValueError("Invalid shape size: " + str(size_given) + " instead of " + str(self.size)) self._symFClambda = sy.lambdify(self._symbols, self._symFCexpr, modules='numpy') def set_params(self, values): """ Coverts the symbolic values into numerical values. The order of the parameters must be the same as the one given in the class instantiaion. :param list values: a list containing the values for the sympy symbols. :return: None :rtype: None :raises: RuntimeError """ if self._symFClambda != None: self.fc_set( np.array(self._symFClambda(*values), \ dtype=np.complex), \ self._inputType) else: raise RuntimeError("Symbolic FC not defined!") @property def isSymbolic(self): """ True if Fourier components are defined as sympy symbols, False otherwise. ALWAYS True for SMM objects. """ return True