Note
Go to the end to download the full example code.
Invoking a Crystal Genome Test Driver Directly
Note
This Python file has comments written to be rendered using Sphinx-Gallery as part of the documentation for the kim-tools package. A rendered version of this file should be available as part of that documentation. The rendered documentation is contained entirely in the comments, so this file can be run just like any other Python file from your shell, e.g.
python CrystalGenomeASEExample__TD_000000654321_000/run.py
This file is intended to demonstrate how to directly run Test Drivers that derive from SingleCrystalTestDriver
for debugging. When developing your Test Driver, copy this file into its top-level directory and modify it (probably removing
all of these complicated comments as well).
We will use a model from OpenKIM.org to run our Driver. For this, kimpy must be installed (see Note below regarding using non-KIM ASE calculators). The KIM Model needs to first be installed using the following command (in your shell, not in Python):
kim-api-collections-management install user SW_ZhouWardMartin_2013_CdTeZnSeHgS__MO_503261197030_003
or (KIM Developer Platform only)
kimitems install SW_ZhouWardMartin_2013_CdTeZnSeHgS__MO_503261197030_003
more models can be found at https://openkim.org/browse/models/by-species, just replace the model name in the above commands and it will be automatically downloaded and installed.
First, import your Test Driver and instantiate it by passing it a KIM Model Name:
from test_driver.test_driver import TestDriver
kim_model_name = "SW_ZhouWardMartin_2013_CdTeZnSeHgS__MO_503261197030_003"
test_driver = TestDriver(kim_model_name)
Note
If the Test Driver uses only ASE for computations, you can use any ASE calculator,
not just KIM models. Simply instantiate the TestDriver class by passing it
the Calculator instance you wish to use instead
of the string identifying the KIM model.
Running Using an Atoms Object
You can run your Driver by directly passing it an ase.Atoms object. The base class will automatically
perform a symmetry analysis on the structure and store a symmetry-reduced description of it. Note that the Atoms
object you pass will not itself be passed to the _calculate() method, the crystal will be re-created from the
symmetry-reduced description. Let's build a bulk zincblende structure and run our Driver on it, setting the
_calculate() argument max_volume_scale to 0.1 and leaving the other argument as default. When testing a different
Test Driver, this is where you would instead pass the specific arguments your _calculate() method uses instead.
We are also demonstrating how to pass temperature and stress, even if our Test Driver doesn't use it.
from ase.build import bulk
atoms = bulk("ZnS", "zincblende", a=5.4093)
print("\nRUNNING TEST DRIVER ON ZINCBLENDE ATOMS OBJECT\n")
computed_property_instances = test_driver(
atoms,
max_volume_scale=0.1,
num_steps=1,
temperature_K=0, # Not used, for demonstration only
cell_cauchy_stress_eV_angstrom3=[0, 0, 0, 0, 0, 0], # Not used, for demonstration only
# pressure_eV_angstrom3=0, # Either stress or pressure can be specified, not both.
)
The results of the calculation is returned in the format defined by the Property Definitions that the Driver uses and the KIM Properties Framework. It can be accessed as a list of dictionaries, each corresponding to a Property Instance. Each caclulation may produce multiple Property Instances.
For example, this is how you access the value of the key a (corresponding to the lattice constant),
found in every Crystal Genome property, from the first Property Instance.
print("\n--------------------------------------")
print(
"Lattice constant a: %f %s"
% (
computed_property_instances[0]["a"]["source-value"],
computed_property_instances[0]["a"]["source-unit"],
)
)
print("--------------------------------------\n")
You probably received a warning that the configuration you provided had a
non-negligible force or stress. This is because the atoms object was not minimized
with the potential we are using. One option to minimize the configuration
is to use the EquilibriumCrystalStructure Test Driver, available in the
kimvv package, which can simply be installed
with
pip install kimvv
Once your Test Driver is published on openkim.org, it will be incorporated into
kimvv and available to users just as easily! kimvv automatically
handles dependencies, but since your Test Driver is not incorporated yet,
you will have to call EquilibriumCrystalStructure explicitly.
Instead an ase.Atoms object, your Test Driver can use a KIM Property
Instance containing a symmetry-reduced description of the crystal.
EquilibriumCrystalStructure returns
3 different properties for each crystal (structure, energy, and density),
but all of them contain the required fields to build a crystal, so we can
pass any of them to our Test Driver to use as a relaxed structure.
In order to avoid conflicts in the "output" directory, interspersing
calls to different instances of Test Drivers is not allowed, so we must
re-instantiate our Test Driver to avoid conflict with
EquilibriumCrystalStructure. Not doing so will raise an error.
from kimvv import EquilibriumCrystalStructure
ecs = EquilibriumCrystalStructure(kim_model_name)
print("\nMINIMIZING STRUCTURE\n")
relaxed_structure = ecs(atoms)[0]
test_driver = TestDriver(kim_model_name)
print("\nRUNNING TEST DRIVER ON EquilibriumCrystalStructure OUTPUT\n")
computed_property_instances = test_driver(
relaxed_structure,
max_volume_scale=0.1,
num_steps=1,
)
Testing Using a Prototype Label
In the KIM Processing Pipeline, Test Drivers automatically run on of thousands of different crystal structures under the Crystal Genome testing framework. These are precomputed relaxations of each structure with each compatible interatomic potential in OpenKIM expressed as KIM Property Instances.
You can replicate this functionality using
the utility method kim_tools.test_driver.core.query_crystal_structures()
to query for relaxed structures:
from kim_tools import query_crystal_structures
list_of_queried_structures = query_crystal_structures(
kim_model_name=kim_model_name,
stoichiometric_species=["Zn", "S"],
prototype_label="AB_hP4_186_b_b",
)
# do something with computed_property_instances if you want
AB_hP4_186_b_b is the AFLOW prototype label describing the symmetry of the
wurtzite structure. To fully specify the crystal structure, this label must
be combined with one or more free parameters (unit cell and internal atom
degrees of freedom). The equilibrium values of these free parameters will depend
on the potential being used, which is why the query function takes the KIM model
name as an argument.
Todo
While the option to query for finite temperature and pressure exists, no such structures are in the database yet, plus the query function needs to have tolerances implemented
Todo
query_crystal_structures should be moved out of kim-tools and become a query endpoint on query.openkim.org
A detailed description of AFLOW prototype labels can be found in Part IIB of https://arxiv.org/abs/2401.06875.
A single set of the arguments given above (model, species, and AFLOW prototype label)
may or may not correspond to multiple local minima (i.e. multiple sets of free parameters),
so kim_tools.test_driver.core.query_crystal_structures() returns a list of
dictionaries. You can then run your TestDriver by passing one of these
dictionaries instead of an ase.Atoms object.
for queried_structure in list_of_queried_structures:
print("\nRUNNING TEST DRIVER ON QUERIED STRUCTURE\n")
computed_property_instances = test_driver(
queried_structure, max_volume_scale=0.1, num_steps=1
)
# do something with computed_property_instances if you want
You can also omit the kim_model_name argument, in which case
instead of querying for potential-specific minima, reference data will be queried instead
(typically DFT-relaxed). This is useful if you are using a model that is not on OpenKIM.org.
In this case you should minimize the structure first. Just like any other Test Driver,
kimvv.EquilibriumCrystalStructure can take the dictionaries returned by
kim_tools.test_driver.core.query_crystal_structures().
Here we are also demonstrating the ability to query by the crystal's name instead of
or in addition to the AFLOW Prototype Label. This will be searched as a case-insensitive
regex, so partial matches will work. Please note that, like any human-curated set of names,
this is inevitably limited and incomplete. For example, "Face-Centered Cubic" is recognized,
but not "FCC" or "Face Centered Cubic".
Note that when querying for Reference Data, it is quite likely that the query will return duplicate structures
for this, the kim_tools.test_driver.core.detect_unique_crystal_structures() utility is provided.
Todo
Expand the database of short names
list_of_queried_structures = query_crystal_structures(
stoichiometric_species=["Zn", "S"], short_name="Wurtzite"
)
from kim_tools import detect_unique_crystal_structures
unique_structure_indices = detect_unique_crystal_structures(list_of_queried_structures)
print(
f"\n{len(unique_structure_indices)} of {len(list_of_queried_structures)} queried structures were found to be unique.\n"
)
for i in unique_structure_indices:
# Minimize the structure with EquilibriumCrystalStructure
print("\nMINIMIZING STRUCTURE\n")
ecs = EquilibriumCrystalStructure(kim_model_name)
relaxed_structure = ecs(list_of_queried_structures[i])[0]
print("\nRUNNING TEST DRIVER ON MINIMIZED QUERIED STRUCTURE\n")
test_driver = TestDriver(kim_model_name)
computed_property_instances = test_driver(
relaxed_structure,
max_volume_scale=0.1,
num_steps=1,
)
# do something with computed_property_instances if you want
In addition to returning the computed property instances on each run,
Test Drivers accumulate all the property instances computed over all calls,
which can be accessed from property_instances as
a list of Python dictionaries, or written to a file (default output/results.edn)
print(f"\nI've accumulated {len(test_driver.property_instances)} Property Instances\n")
test_driver.write_property_instances_to_file()