Crystal#
For the full technical reference see Crystal
Crystal is a child of the the Lattice
and utilize
Atom
for the storage of atoms. We recommend you to read about
Lattice and Atom first.
Crystal behaves like a list of atoms. See List-like behavior for the details.
Import#
>>> # Exact import
>>> from wulfric.crystal import Crystal
>>> # Recommended import
>>> from wulfric import Crystal
For the examples in this page we need additional import and some predefined variables:
>>> from wulfric import Lattice, Atom
Creation of the Crystal#
To create a crystal you would typically need to define the lattice and a set of atoms:
lattice
- instance of theLattice
class.atoms
- list of theAtom
instances, 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 no atoms.
>>> from wulfric 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 that 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.
Moving atoms in the cell#
Two routines are predefined:
Crystal.shift_atoms()
- shift atoms in a way that the point in the middle of the atom's coordinates extremums will be at the givengravity_point
.>>> crystal = Crystal(cell=[[2, 0, 0], [0, 2, 0], [0, 0, 2]]) >>> crystal.add_atom('Cr1', position=(0.0, 0.0, 0.0)) >>> crystal.add_atom('Cr2', position=(0.5, 0.5, 1.0)) >>> crystal.shift_atoms(gravity_point=(0.5, 0.5, 0.5)) >>> for atom in crystal.atoms: ... print(atom.name, atom.position) ... Cr1 [0.25 0.25 0. ] Cr2 [0.75 0.75 1. ] >>> crystal.shift_atoms(gravity_point=(1,1,1), relative=False) >>> for atom in crystal.atoms: ... print(atom.name, atom.position) ... Cr1 [0.25 0.25 0. ] Cr2 [0.75 0.75 1. ]
Crystal.cure_negative()
- enforce all atoms to have positive relative coordinates.>>> crystal = Crystal(cell=[[2, 0, 0], [0, 2, 0], [0, 0, 2]]) >>> crystal.add_atom('Cr1', position=(-0.5, 0.5, 0.0)) >>> crystal.add_atom('Cr2', position=(0.1, 0.5, 0.0)) >>> crystal.cure_negative() >>> for atom in crystal.atoms: ... print(atom.name, atom.position) ... Cr1 [0. 0.5 0. ] Cr2 [0.6 0.5 0. ]
Atom getters#
There is a number of properties involving atom of the crystal you have access to:
Crystal.atoms
- list of theAtom
instances in the crystal.Crystal.get_atom
- get atom by name or by fullname.
>>> 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
List-like behavior#
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
in
syntax returnsTrue
if 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 either none or more than one 'Cr' atoms.
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.