Crystal#
Added in version 0.7.
For the full reference see Crystal
Crystal is a child of the the Lattice and utilize
Atom for the storage of atoms. We recommend you to read
Lattice and Atom first.
Crystal behaves like a list of atoms. See List-like behaviour for the details.
Import#
>>> # Exact import
>>> from radtools.crystal.crystal import Crystal
>>> # Explicit import
>>> from radtools.crystal import Crystal
>>> # Recommended import
>>> from radtools import Crystal
For the examples in this page we need additional import and some predefined variables:
>>> from radtools import Lattice, Atom
Creation of the Crystal#
To create a crystal you would typically need to define the lattice and the set of atoms:
lattice- instance of theLatticeclass.atoms- list of theAtominstances, which defines the set of atoms in the unit cell.
>>> lattice = Lattice(3.0, 3.0, 3.0, 90.0, 90.0, 90.0)
>>> atoms = [Atom('Si', (0.0, 0.0, 0.0))]
>>> crystal = Crystal(lattice, atoms)
Parameters for lattice creation can be passed to the Crystal constructor as
keyword arguments:
>>> crystal = Crystal(a=3.0, b=3.0, c=3.0, alpha=90.0, beta=90.0, gamma=90.0)
By default the Crystal is created with the cubic lattice (\(a = 1\)) and and no atoms.
>>> from radtools import Crystal
>>> crystal = Crystal()
>>> crystal.cell
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
>>> crystal.type()
'CUB'
>>> crystal.atoms
[]
Adding atoms#
Adding atoms to the crystal is straightforward:
>>> crystal = Crystal()
>>> crystal.add_atom('Cr', position=(0.0, 0.0, 0.0))
>>> crystal.add_atom('Cr', position=(0.5, 0.5, 0.5))
>>> for atom in crystal.atoms:
... print(atom.fullname, atom.position)
...
Cr__1 [0. 0. 0.]
Cr__2 [0.5 0.5 0.5]
By default atom coordinates are interpreted as relative.
In order to add atom with absolute coordinates set
the relative keyword argument to False:
>>> crystal = Crystal(cell=[[3., 0., 0.], [0., 3., 0.], [0., 0., 3.]])
>>> crystal.add_atom(Atom('Cr', (0.0, 0.0, 0.0)), relative=False)
>>> crystal.add_atom(Atom('Cr', (1.5, 1.5, 1.5)), relative=False)
>>> for atom in crystal.atoms:
... print(atom.fullname, atom.position)
...
Cr__1 [0. 0. 0.]
Cr__2 [0.5 0.5 0.5]
Note
Crystal.get_distance() and Crystal.get_vector() methods
are the ones which returns absolute coordinates by default.
It is possible to add atoms by passing the Atom instance:
>>> crystal = Crystal()
>>> Cr = Atom('Cr', position=(0.0, 0.0, 0.0))
>>> crystal.add_atom(Cr)
Removing atoms#
Removing atoms is also straightforward:
>>> crystal = Crystal()
>>> crystal.add_atom("Cr", position=(0.0, 0.0, 0.0))
>>> atom = Atom("Cr", (0.5, 0.5, 0.5))
>>> crystal.add_atom(atom)
>>> crystal.remove_atom("Cr__1")
>>> for atom in crystal.atoms:
... print(atom.fullname, atom.position)
...
Cr__2 [0.5 0.5 0.5]
>>> crystal.remove_atom(atom)
>>> crystal.atoms
[]
>>> crystal.add_atom("Cr", position = (0.0, 0.0, 0.0))
>>> crystal.add_atom("Cr", position = (1.0, 0.0, 0.0))
>>> crystal.add_atom("Cr", position = (1.0, 1.0, 0.0))
>>> crystal.add_atom("Cr", position = (1.0, 1.0, 1.0))
>>> # Same as len(crystal.atoms)
>>> len(crystal)
4
>>> # You can remove all atoms with the same name at once
>>> crystal.remove_atom("Cr")
>>> len(crystal)
0
Note
The Crystal.remove_atom() method can take either the name (or fullname)
or instance of the atom as an argument.
Atom getters#
There is a number of properties involving atom of the crystal you have access to:
Crystal.atoms- list of theAtominstances in the crystal.Crystal.get_atom- get atom by name or by fullname.
..doctest:
>>> crystal = Crystal()
>>> crystal.add_atom('Cr', position=(0.0, 0.0, 0.0))
>>> atom1 = crystal.get_atom('Cr')
>>> atom1.name
'Cr'
>>> atom1.position
array([0., 0., 0.])
>>> atom1.index
1
>>> crystal.add_atom('Cr', position=(0.5, 0.5, 0.5))
>>> atom2 = crystal.get_atom('Cr')
Traceback (most recent call last):
...
ValueError: Multiple matches found for name = Cr, index = None
>>> atom2 = crystal.get_atom('Cr__2')
>>> atom2.index
2
Crystal.get_atom_coordinates- get coordinates of the atom by name or by fullname.
>>> crystal = Crystal(cell=[[2, 0, 0], [0, 2, 0],[0, 0, 2]])
>>> crystal.add_atom('Cr', position = (0.0, 0.5, 0.0))
>>> crystal.get_atom_coordinates('Cr')
array([0. , 0.5, 0. ])
>>> crystal.Cr.position
array([0. , 0.5, 0. ])
>>> crystal.get_atom_coordinates('Cr', relative=False)
array([0., 1., 0.])
Crystal.get_vector- get vector from the atom1 to atom2 by name or by fullname.
>>> crystal = Crystal(cell=[[2, 0, 0], [0, 2, 0], [0, 0, 2]])
>>> crystal.add_atom('Cr', position = (0.0, 0.5, 0.0))
>>> crystal.add_atom('Cr', position = (0.5, 0.0, 0.0))
>>> # Gives result in absolute coordinates
>>> crystal.get_vector('Cr__1', 'Cr__2')
array([ 1., -1., 0.])
>>> crystal.get_vector('Cr__1', 'Cr__2', relative=True)
array([ 0.5, -0.5, 0. ])
Crystal.get_distance- get distance between atom1 and atom2 by name or by fullname.
>>> crystal = Crystal(cell=[[2, 0, 0], [0, 2, 0], [0, 0, 2]])
>>> crystal.add_atom('Cr', position=(0.0, 0.5, 0.0))
>>> crystal.add_atom('Cr', position=(0.5, 0.0, 0.0))
>>> round(crystal.get_distance('Cr__1', 'Cr__2'), 4)
1.4142
Magnetic dipole-dipole interaction energy#
If magnetic moments of crystal`s atoms are defined, the magnetic dipole-dipole interaction energy can be calculated.
Two functions are used for this purpose:
Crystal.mag_dipdip_energy()- calculate the energy of the magnetic dipole-dipole interaction.It returns energy in meV if atom`s positions are in Angstroms and magnetic moments are in Bohr magnetons.
>>> crystal = Crystal(cell=[[2, 0, 0], [0, 2, 0], [0, 0, 2]])
>>> crystal.add_atom('Cr', position=(0.0, 0.25, 0.0))
>>> crystal.add_atom('Cr', position=(0.25, 0.0, 0.0))
>>> energy = crystal.mag_dipdip_energy(1,1,1, progress_bar=False)
Traceback (most recent call last):
...
ValueError: There are no magnetic atoms in the crystal.
>>> crystal.Cr__1.magmom = [0, 0, 1]
>>> crystal.Cr__2.magmom = [0, 0, 1]
>>> energy = crystal.mag_dipdip_energy(1,1,1, progress_bar=False)
>>> round(energy, 8)
0.07591712
>>> crystal.Cr__1.magmom = [0,1,0]
>>> energy = crystal.mag_dipdip_energy(1,1,1, progress_bar=False)
>>> round(energy, 8)
0.0
>>> crystal.Cr__2.magmom = [1,0,0]
>>> energy = crystal.mag_dipdip_energy(1,1,1, progress_bar=False)
>>> round(energy, 8)
0.11387568
Crystal.converge_mag_dipdip_energy()- converge the energy of the magnetic dipole-dipole interaction.
List-like behaviour#
Crystal class supports the logic of a list of atoms.
The following list-like methods are implemented:
len(crystal)- returns the number of atoms in the crystal
>>> crystal = Crystal()
>>> crystal.add_atom('Cr', position=(0.0, 0.0, 0.0))
>>> crystal.add_atom('Cr', position=(0.5, 0.5, 0.5))
>>> len(crystal)
2
Iteration over the atoms
>>> crystal = Crystal()
>>> crystal.add_atom('Cr', position=(0.0, 0.0, 0.0))
>>> crystal.add_atom('Cr', position=(0.5, 0.5, 0.5))
>>> for atom in crystal:
... print(atom.fullname)
...
Cr__1
Cr__2
insyntax returnsTrueif the atom is present in the Crystal
>>> crystal = Crystal()
>>> atom = Atom('Cr', (0.0, 0.0, 0.0))
>>> crystal.add_atom(atom)
>>> atom in crystal
True
>>> "Cr" in crystal
True
>>> 'Cr__1' in crystal
True
>>> 'Cr__3' in crystal
False
>>> "Br" in crystal
False
Attribute and item access#
For the access to the atoms objects two additional interfaces are implemented.
They both rely on the Crystal.get_atom() method.
Access via attribute
It is identical to passing the name to
the Crystal.get_atom() method with return_all=False.
>>> crystal = Crystal()
>>> crystal.add_atom('Cr', position=(0.0, 0.0, 0.0))
>>> crystal.add_atom('Cr', position=(0.5, 0.5, 0.5))
>>> atom = crystal.Cr__1
>>> atom.fullname
'Cr__1'
>>> atom = crystal.Cr__2
>>> atom.fullname
'Cr__2'
>>> crystal.Cr
Traceback (most recent call last):
...
AttributeError: 'Crystal' object has no attribute 'Cr'
Access via item
It is identical to passing the name to
the Crystal.get_atom() method with return_all=True.
>>> crystal = Crystal()
>>> crystal.add_atom('Cr', position=(0.0, 0.0, 0.0))
>>> crystal.add_atom('Cr', position=(0.5, 0.5, 0.5))
>>> atoms = crystal["Cr__1"]
>>> atoms[0].fullname
'Cr__1'
>>> atoms = crystal["Cr__2"]
>>> atoms[0].fullname
'Cr__2'
>>> atoms = crystal["Cr"]
>>> for atom in atoms:
... print(atom.fullname)
...
Cr__1
Cr__2
Note
Second method returns a list of atoms, while the first one returns a single atom.