Occupation event basics#

The class OccEvent is used to represent changes in occupation. For example, this could be:

  • the change in occupation due to a diffusive hop

  • the change in occupation due to a molecular re-orientation

An occupation event is represented by specifying the trajectories of each of the occupants involved in the event.

Construct an atomic hop OccEvent#

As an example, consider an event representing atom-vacancy exchange. In an OccEvent, the exchange of an atom and a vacancy is specified by:

  • The trajectory of the atom, which consists of:

    • the initial position of the atom

    • the final position of the atom

  • The trajectory of the vacancy, which consists of:

    • the initial position of the vacancy

    • the final position of the vacancy

The type and position of the occupants can be specified with:

  • the site the occupant is on, represented by IntegralSiteCoordinate

  • an occupation index, which is the index of the occupant in the allowed occupant list ( occ_dof()) for the sublattice of the occupied site

The occupant position are represented by OccPosition, and a trajectory is represented by a two-element list[OccPosition].

The following example shows how to construct an OccEvent representing the exchange of an “A” atom and a vacancy on the 1NN sites in an FCC prim with allowed occupants “A”, “B”, and “Va”:

import libcasm.xtal as xtal
from libcasm.xtal.prims import FCC as FCC_prim
import libcasm.occ_events as occ_events
from libcasm.occ_events import OccPosition, OccEvent

xtal_prim = FCC_prim(r=1.0, occ_dof=["A", "B", "Va"])

site1 = xtal.IntegralSiteCoordinate(sublattice=0, unitcell=[0, 0, 0])
site2 = xtal.IntegralSiteCoordinate(sublattice=0, unitcell=[1, 0, 0])

A_occ_index = 0
Va_occ_index = 2

A_initial_pos = OccPosition.occupant(site1, A_occ_index)
A_final_pos = OccPosition.occupant(site2, A_occ_index)
Va_initial_pos = OccPosition.occupant(site2, Va_occ_index)
Va_final_pos = OccPosition.occupant(site1, Va_occ_index)

occ_event = OccEvent([
    [A_initial_pos, A_final_pos],
    [Va_initial_pos, Va_final_pos],
])

Transforming OccEvent#

OccEvent can be translated by multiples of the lattice vectors:

translation = np.array([a, b, c], dtype=int)

# copy and translate:
translated_occ_event = occ_event + translation
assert translated_occ_event is not occ_event

translated_occ_event = occ_event - translation
assert translated_occ_event is not occ_event

# mutate by translatation:
occ_event += translation
occ_event -= translation

Symmetry operations can be applied to OccEvent using the OccEventRep representation:

# construct the prim factor group
factor_group = xtal.make_factor_group(xtal_prim)

# construct a representation of the prim factor group (list[OccEventRep])
# for transforming OccEvent
occevent_symgroup_rep = occ_events.make_occevent_symgroup_rep(
    factor_group,
    xtal_prim)

for rep in occevent_symgroup_rep:
    # copy and transform
    transformed_occ_event = rep * occ_event
    assert transformed_occ_event is not occ_event

A copy of an OccEvent can be constructed using OccEvent.copy or copy.deepcopy:

import copy

# in Python, assignment is not a copy
assigned_occ_event = occ_event
assert assigned_occ_event is occ_event

# to create a copy, use OccEvent.copy
copied_occ_event = occ_event.copy()
assert copied_occ_event is not occ_event

# or, use copy.deepcopy
copied_occ_event = copy.deepcopy(occ_event)
assert copied_occ_event is not occ_event

Comparing OccEvent#

To compare OccEvent, they can be put in a standardized form which sorts the occupant trajectories, considering both the forward and reverse directions:

# mutate, into standardized form
occ_event_a.standardize()

# mutate, into standardized form
occ_event_b.standardize()

# check for equivalence:
print("OccEvent are equal?:", occ_event_a == occ_event_b)
print("OccEvent are not equal?:", occ_event_a != occ_event_b)

# check ordering (lexicographical ordering of trajectories):
print("occ_event_a < occ_event_b?:", occ_event_a < occ_event_b)
print("occ_event_a <= occ_event_b?:", occ_event_a <= occ_event_b)
print("occ_event_a > occ_event_b?:", occ_event_a > occ_event_b)
print("occ_event_a >= occ_event_b?:", occ_event_a >= occ_event_b)
print("OccEvent are not equal?:", occ_event_a != occ_event_b)

Orbits of OccEvent#

The orbit of symmetrically equivalent OccEvent can be constructed using make_prim_periodic_orbit():

factor_group = xtal.make_factor_group(xtal_prim)
occevent_symgroup_rep = occ_events.make_occevent_symgroup_rep(
    factor_group,
    xtal_prim)
occevent_orbit = occ_events.make_prim_periodic_orbit(
    occ_event,
    occevent_symgroup_rep)

The output, occevent_orbit, is a list[OccEvent], giving the OccEvent associated with the origin unit cell from the orbit of all equivalent OccEvent under prim factor group symmetry.

Atomic hops with sublattice restrictions#

Note that in the previous example the occupation order is the same on every sublattice. In a more complicated Prim with multiple sublattices, the occupation index for the “A” atom or vacancy might change from one sublattice to the other.

In the following example, “A”, “B”, and vacancies are allowed on FCC corner sites, while “B”, “C”, and vacancies are allowed on face sites:

import libcasm.xtal as xtal
from libcasm.occ_events import OccPosition, OccEvent

lattice = xtal.Lattice(np.array([
    [1.0, 0.0, 0.0], # first lattice vector
    [1.0, 0.0, 0.0], # second
    [1.0, 0.0, 0.0], # third
]).transpose()) # <--- note transpose

# Basis sites positions, as columns of a matrix,
# in fractional coordinates with respect to the lattice vectors
coordinate_frac = np.array([
    [0., 0., 0.]   # coordinates of basis site, b=0
    ]).transpose() # <--- note transpose

# Occupation degrees of freedom (DoF)
occ_dof=[
  ["A", "B", "Va"], # occupants allowed on basis site, b=0
  ["B", "C", "Va"], # occupants allowed on basis site, b=1
  ["B", "C", "Va"], # occupants allowed on basis site, b=2
  ["B", "C", "Va"], # occupants allowed on basis site, b=3
])

xtal_prim = xtal.Prim(
    lattice=lattice,
    coordinate_frac=coordinate_frac,
    occ_dof=occ_dof,
    title="FCC, with sublattice restrictions")

Then an “B”-vacancy exchange event between corner and face sites is constructed using:

site1 = xtal.IntegralSiteCoordinate(sublattice=0, unitcell=[0, 0, 0])
site2 = xtal.IntegralSiteCoordinate(sublattice=1, unitcell=[0, 0, 0])

B_init_occ_index = 1
B_final_occ_index = 0
Va_occ_index = 2

B_initial_pos = OccPosition.occupant(site1, B_init_occ_index)
B_final_pos = OccPosition.occupant(site2, B_final_occ_index)
Va_initial_pos = OccPosition.occupant(site2, Va_occ_index)
Va_final_pos = OccPosition.occupant(site1, Va_occ_index)

occ_event = OccEvent([
    [A_initial_pos, A_final_pos],
    [Va_initial_pos, Va_final_pos],
])