Evaluating a cluster expansion#
The ClusterExpansion
class is used to calculate:
the value of a cluster expansion
the change in the value of a cluster expansion given changes in degree of freedom (DoF) values
Evaluating a cluster expansion requires:
Cluster expansion basis functions, as a CASM
Clexulator
source code fileCluster expansion coefficients, as a
SparseCoefficients
objectConfiguration degree of freedom (DoF) values, as a
ConfigDoFValues
objectA neighbor list for the supercell of the configuration, which is used to find the correct degree of freedom (DoF) values for each basis function, as a
SuperNeighborList
.
Construct a ClusterExpansion#
The make_cluster_expansion()
method can be used to construct a ClusterExpansion
calculator along with consistent ConfigDoFValues
and SuperNeighborList
objects, given a Prim
and the integer transformation matrix for the supercell containing the configuration:
Note
Before running, set the environment variable CASM_PREFIX
to the location where CASM is installed in order to enable proper compilation and linking of the CASM clexulator using the CASM libraries.
export CASM_PREFIX=$(python -m libcasm.casmglobal --prefix)
In some cases, finer control of compilation and linking options may be necessary, which can be done as described in the make_clexulator
documentation. For example, compiling and linking with gcc may require:
export CASM_SOFLAGS="-shared -Wl,--no-as-needed"
import numpy as np
import libcasm.xtal as xtal
from libcasm.clexulator import (
make_cluster_expansion,
SparseCoefficients,
)
# construct the Prim
xtal_prim = # xtal.Prim(...)
# specify the clexulator source file
clexulator_source = # str( ... path to clexulator source code file ... )
# specify the cluster expansion coefficients
coefficients = # SparseCoefficients(...)
# specify the supercell:
l_unitcells = # int(... specify a l * l * l unit cells sized supercell ...)
transformation_matrix_to_super = np.eye(3) * l_unitcells
# construct a ClusterExpansion calculator
cluster_expansion, info = make_cluster_expansion(
xtal_prim=xtal_prim,
clexulator_source=clexulator_source,
coefficients=coefficients,
transformation_matrix_to_super=transformation_matrix_to_super,
)
# get the ConfigDoFValues that cluster_expansion will evaluate
config_dof_values = info.config_dof_values
In this example, the make_cluster_expansion()
method returns two objects:
cluster_expansion: The constructed
ClusterExpansion
calculatorinfo: A
ClusterExpansionInfo
instance which provides the supercell neighbor list and a default initializedConfigDoFValues
instance for the specified supercell
Evaluate the cluster expansion#
To evaluate the cluster expansion, the ClusterExpansion
calculator must be set to point at a ConfigDoFValues
instance. This is done automatically for the objects returned by make_cluster_expansion()
.
Then, the cluster expansion value can be evaluated with:
# ... calculate cluster expansion value for the current state
# of the ConfigDoFValues that cluster_expansion has been set with ...
# evaluate and return per_supercell value:
value_per_supercell = cluster_expansion.per_supercell()
# or evaluate and return the per_unitcell value:
value_per_unitcell = cluster_expansion.per_unitcell()
Change DoF values and re-calculate#
To change DoF values and re-calculate the cluster expansion, just modify the values of the ConfigDoFValues
instance and re-evaluate:
# Correct usage -->
# The following all assign new DoF values to
# `config_dof_values`'s data using a copy:
# ... set ConfigDoFValues occupant values, using a copy...
occupation = np.array(...)
config_dof_values.set_occupation(occupation)
# ... or set a single ConfigDoFValues occupant value, using a copy...
linear_site_index = int(...)
new_occ = int(...)
config_dof_values.set_occ(linear_site_index, new_occ)
# ... or set all ConfigDoFValues values from other
# ConfigDoFValues instance, using copies ...
other = ConfigDoFValues(...)
config_dof_values.set(other)
# ... calculate cluster expansion value for the current state
# of the ConfigDoFValues that cluster_expansion has been set with ...
# evaluate and return the per_unitcell value:
value_per_unitcell = cluster_expansion.per_unitcell()
Warning
Wrong usage, trying to change DoF values, but actually changing what a Python variable points to:
# !Wrong usage! -->
# In Python, the following will change what `config_dof_values`
# points at, not assign values to `config_dof_values`'s data
# using a copy.
# Therefore, cluster_expansion, which still has a pointer to the
# original data structure, will not use the DoF values from `other`.
other = ConfigDoFValues(...)
config_dof_values = other
# ... this will try to calculate cluster expansion value
# but still points at the original `config_dof_values` data ...
# ... so it will evaluate and return the wrong per_unitcell value ...
wrong_value_per_unitcell = cluster_expansion.per_unitcell()
Warning
Wrong usage, trying to change DoF values using an external data structure which was copied:
# !Wrong usage! -->
# ConfigDoFValues.set_occuption copies values of an occupation array
# into the `config_dof_values`'s data, so subsequently changing the
# values in the array has no effect on `config_dof_values`
occupation = np.array(...)
# ... this copies `occupation` values into `config_dof_values`'s data ...
config_dof_values.set_occupation(occupation)
# ... so this has no effect on `config_dof_values`'s data ...
occupation[linear_site_index] = new_occ
# ... and it will evaluate and return the wrong per_unitcell value ...
wrong_value_per_unitcell = cluster_expansion.per_unitcell()
Set a ClusterExpansion to use a different ConfigDoFValues instance#
The function set()
may be used to point ClusterExpansion
at the data of a different ConfigDoFValues
instance which must have the same supercell:
# Correct usage -->
# Reset cluster_expansion to point at the data in `other`,
# which must have the same supercell.
other = ConfigDoFValues(...)
cluster_expansion.set(other)
# ... calculate cluster expansion value using `other`'s data ...
value_per_unitcell = cluster_expansion.per_unitcell()
Warning
To calculate the cluster expansion in a different supercell, a new ClusterExpansion()
instance must be constructed.
Calculate the effect of changes in DoF values#
ClusterExpansion
includes methods to efficiently calculate the change in the value of the per_supercell cluster expansion value due to changes in particular DoF values:
occ_delta_value()
: For the change in the per_supercell cluster expansion value due to the change in a single occupant valuemulti_occ_delta_value()
: For the change in the per_supercell cluster expansion value due to changes in multiple occupant valueslocal_delta_value()
: For the change in the per_supercell cluster expansion value due to the change in a single local continuous DoF valueglobal_delta_value()
: For the change in the per_supercell cluster expansion value due to the change in a single global continuous DoF value
As an example, the following is pseudo-code that uses occ_delta_value()
in the inner loop of a semi-grand canonical Monte Carlo simulation:
# ... propose the change of a single occupation value ...
linear_site_index = ... random site index ...
new_occ = ... random occupant index, excluding current value ...
# ... calculate the change in formation energy relative the current
# state of the ConfigDoFValues that cluster_expansion has been set with ...
delta_formation_energy = cluster_expansion.occ_delta_value(
linear_site_index, new_occ)
delta_generalized_enthalpy = ... calculate change in potential ...
# ... accept or reject ...
if is_accepted(delta_generalized_enthalpy):
# ... if accepted, update config_dof_values ...
config_dof_values.set_occ(linear_site_index, new_occ)