Manipulations with crystal#
For the full technical reference see wulfric.crystal.
On this page we give examples of what can be done with the pair of cell and atoms
(introduced on the key concepts page), that
define a crystal and reference cell. All functions that deal with atoms or crystal are
available under wulfric.crystal submodule.
In the examples below we use crystal with six atoms and orthorhombic cell.
>>> import numpy as np
>>> import wulfric
>>> csb_cell = np.array([
... [0.000000, 4.744935, 0.000000],
... [3.553350, 0.000000, 0.000000],
... [0.000000, 0.000000, 8.760497],
... ])
>>> csb_atoms = {
... "names": ["Cr1", "Br1", "S1", "Cr2", "Br2", "S2"],
... "positions": np.array([
... [0.000000, -0.500000, 0.882382],
... [0.000000, 0.000000, 0.677322],
... [-0.500000, -0.500000, 0.935321],
... [0.500000, 0.000000, 0.117618],
... [0.500000, 0.500000, 0.322678],
... [0.000000, 0.000000, 0.064679],
... ]),
... }
Choice of the cell#
Please read Cell's choice first.
Conventional cell#
To choose conventional cell use wulfric.crystal.get_conventional()
# Default convention is HPKOT
>>> conv_cell, conv_atoms = wulfric.crystal.get_conventional(csb_cell, csb_atoms)
Note that the first two lattice vectors are changed.
>>> conv_cell
array([[3.55335 , 0. , 0. ],
[0. , 4.744935, 0. ],
[0. , 0. , 8.760497]])
Atom's positions are modified to account for the new cell. In that way the crystal is not modified and stays in the same spatial orientation.
>>> conv_atoms["positions"]
array([[0.5 , 0. , 0.882382],
[0. , 0. , 0.677322],
[0.5 , 0.5 , 0.935321],
[0. , 0.5 , 0.117618],
[0.5 , 0.5 , 0.322678],
[0. , 0. , 0.064679]])
Conventional atoms are either the same ones as in the original crystal ("Br1", "Cr2", "Br2", "S2") or translationally equivalent ones ("Cr1", "S1")
>>> csb_atoms["positions"] @ csb_cell
array([[-1.776675 , 0. , 7.73010486],
[ 0. , 0. , 5.93367735],
[-1.776675 , -2.3724675 , 8.19387681],
[ 0. , 2.3724675 , 1.03039214],
[ 1.776675 , 2.3724675 , 2.82681965],
[ 0. , 0. , 0.56662019]])
>>> # Cr1 (index 0) Shifted by cell[1]
>>> # Br1 (index 1) Same
>>> # S1 (index 2) Shifted by cell[0] + cell[1]
>>> # Cr2 (index 3) Same
>>> # Br2 (index 4) Same
>>> # S2 (index 5) Same
>>> conv_atoms["positions"] @ conv_cell
array([[1.776675 , 0. , 7.73010486],
[0. , 0. , 5.93367735],
[1.776675 , 2.3724675 , 8.19387681],
[0. , 2.3724675 , 1.03039214],
[1.776675 , 2.3724675 , 2.82681965],
[0. , 0. , 0.56662019]])
Primitive cell#
To choose primitive cell use wulfric.crystal.get_primitive().
>>> prim_cell, prim_atoms = wulfric.crystal.get_primitive(csb_cell, csb_atoms)
Atom's names#
Wulfric does not impose any rule on atom's names. Any non-empty string is a valid name.
To get a set of unique names for your atoms you can use
wulfric.crystal.get_unique_names(), that supports two strategies for modification
of names
(default) "all"
Adds an index of the atom to the end of each name. First atom gets an index
1."repeated-only"
Adds an indices only to the atoms that have the same name. The count is separate for each group of atoms.
>>> import wulfric
>>> atoms = {"names" : ["Cr1", "Cr2", "Br", "Br", "S", "S"]}
>>> # Default strategy is "all"
>>> wulfric.crystal.get_unique_names(atoms)
['Cr11', 'Cr22', 'Br3', 'Br4', 'S5', 'S6']
>>> wulfric.crystal.get_unique_names(atoms, strategy="repeated-only")
['Cr1', 'Cr2', 'Br1', 'Br2', 'S1', 'S2']
Atom's species#
Names of atoms are not restricted in any way and user is free to name atoms as they please (however, it is a common practice to include atom's species in the name).
On contrary, the "species" are one of the 118 pre-defined strings (see wulfric.constants.ATOM_SPECIES).
Wulfric implements two functions to automatically guess the atom's species from its name
>>> wulfric.crystal.get_atom_species("Cr1")
'Cr'
If it is unable to the atom species from its name, then it issues a RuntimeWarning
and returns "X" as a species.
>>> wulfric.crystal.get_atom_species("124")
...
'X'
>>> # You can raise an error instead of the warning
wulfric.crystal.get_atom_species("124", raise_on_fail=True)
...
wulfric._exceptions.FailedToDeduceAtomSpecies: Tried to deduce name from '124'. Failed.
To guess the names for all atoms at once use wulfric.crystal.get_atoms_species()
>>> wulfric.crystal.get_atoms_species(csb_atoms)
['Cr', 'Br', 'S', 'Cr', 'Br', 'S']
Atom's positions#
Wulfric implements a couple of routines to perform common operations on atom's positions,
that do not change orientation of the crystal. Those functions return None and modify
the same atoms, that was passed to them.
-
Ensures that relative coordinates of all atoms are within \([0,1]\)
>>> import wulfric >>> atoms = {"positions": [[0, 0.5, 0], [1.25, 0, -0.52], [0.25, -0.65, 2.375]]} >>> for p in atoms["positions"]: ... print(p) [0, 0.5, 0] [1.25, 0, -0.52] [0.25, -0.65, 2.375] >>> wulfric.crystal.ensure_000(atoms) >>> for p in atoms["positions"]: ... print(p) [0, 0.5, 0] [0.25, 0, 0.48] [0.25, 0.35, 0.375]
wulfric.crystal.cure_negative()Ensures that all relative coordinates of all atoms are positive
>>> import wulfric >>> atoms = { ... "names": ["Cr1", "Cr2"], ... "positions": [[-0.5, 0.5, 0.0], [0.1, 0.5, 0.0]], ... } >>> wulfric.crystal.cure_negative(atoms) >>> for i in range(len(atoms["names"])): ... print(atoms["names"][i], atoms["positions"][i]) Cr1 [0. 0.5 0. ] Cr2 [0.6 0.5 0. ]
-
Shifts al atoms at once in a special way.
>>> import wulfric >>> cell = [[2, 0, 0], [0, 2, 0], [0, 0, 2]] >>> atoms = { ... "names": ["Cr1", "Cr2"], ... "positions": [[0.0, 0.0, 0.0], [0.5, 0.5, 1.0]], ... } >>> wulfric.crystal.shift_atoms(atoms=atoms, gravity_point=(0.5, 0.5, 0.5)) >>> for i in range(len(atoms["names"])): ... print(atoms["names"][i], atoms["positions"][i]) Cr1 [0.25 0.25 0. ] Cr2 [0.75 0.75 1. ] >>> wulfric.crystal.shift_atoms( ... atoms, gravity_point=(1, 1, 1), cell=cell, gp_is_relative=False ... ) >>> for i in range(len(atoms["names"])): ... print(atoms["names"][i], atoms["positions"][i]) Cr1 [0.25 0.25 0. ] Cr2 [0.75 0.75 1. ]
Pair of atoms#
Often a distance or a vector between a pair of atoms is required. Wulfric has two functions for that. It assumes that first atom is located in the reference unit cell with indices \((0, 0, 0)\) and second atom is located in any unit cell of the crystal with indices \((i, j, k)\).
To get the vector from atom 1 to atom 2 or distance between them use
wulfric.crystal.get_vector() or wulfric.crystal.get_distance()
>>> wulfric.crystal.get_vector(csb_cell, csb_atoms, atom1=0, atom2=0, R=(0,1,0))
array([3.55335, 0. , 0. ])
>>> wulfric.crystal.get_distance(csb_cell, csb_atoms, atom1=0, atom2=0, R=(0,1,0))
3.55335