{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercise 2: Simulation Codes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Authors: Jan Janssen, Tilmann Hickel, Jörg Neugebauer ([Max-Planck-Institut für Eisenforschung](https://www.mpie.de))**\n", "\n", "The scope of this second exercise is to become familar with:\n", "* Interatomic potential calculation, \n", "* the pyiron job management and\n", "* the aggregation of multiple calculations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Update Notice \n", "Since the workshop in 2020, there have been some updates to pyiron commands, and here in the exercises new commands are used. \n", "\n", "The changes include:\n", "- Now pyiron has various modules, like pyiron_atomistics, pyiron_continuum, pyiron_experimental. So we can import `Project` from `pyiron_atomistics` directly.\n", "- using `pr.create` to create instances of various objects, such as structures and jobs. For example:\n", "```python\n", "pr.create.structure.ase.bulk() ## this replaces pr.create_ase_bulk()\n", "```\n", ", or\n", "```python\n", "pr.create.job.Lammps('job_name') ## this replaces pr.pr.create_job(job_type=pr.job_type.Lammps,'job_name')\n", "```\n", "\n", "Similary, one can create pyiron tables via `pr.create.table()`.\n", "\n", "By this approach, the user easily gets the available options via autocompeletion.\n", "\n", "**Please note that in the video tutorials, the old commands are still presented.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reminder\n", "In the first session we learned how to create a pyiron project object and then use this pyiron project object to create atomistic structure objects. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import the Project object\n", "from pyiron_atomistics import ____" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create a Project object instance for a project named atomistics\n", "pr = ____(\"simulations\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create a cubic aluminum fcc structure and repeat it 3 times in each direction\n", "al_fcc = pr.create.structure.ase.bulk(____, _____=True)\n", "al_fcc_repeated = al_fcc.repeat(____)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Confirm the final structure has 108 atoms by calculating the length of the structure object\n", "____(al_fcc_repeated) == 108" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## LAMMPS Calculation \n", "The Large-scale Atomic/Molecular Massively Parallel Simulator (LAMMPS) code is used inside pyiron for atomistic simualtion with interatomic potentials. These interatomic potentials approximate the interaction of atoms and can be either fitted to density functional theory (DFT) or experimental results. Still in contrast to density functional theory which scales cubically with the number of atoms interatomic potentials scale linearly with the number of atoms. Therefore we are going to use primarly interatomic potentials in this workshop but most calculations could be executed with a DFT codes as well. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Molecular dynamics calculation\n", "We start with a first molecular dynamic calculation at an ensemble with constant number of atoms, constant volume and contant fixed temperature." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create a LAMMPS job object with the job name lmp\n", "job_md = pr.create.job.Lammps(\n", " job_name=____\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Assign the fcc aluminium structure to the LAMMPS job object\n", "job_md.structure = _________" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Define an ensemble with constant number of atoms, \n", "# constant volume and a constant temperature of 500K\n", "# and simulate 10000 molecular dynamics steps\n", "job_md.calc_md(temperature=______, n_ionic_steps=______)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Execute the calculation - You get a warning that \n", "# not potential was set and the default potential was\n", "# used instead. The selection of interatomic potentials \n", "# is discussed below.\n", "job_md.run()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Animate the molecular dynamics trajectory\n", "job_md.animate_structure()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Reminder: Job Management in pyiron\n", "After the successful execution of the calculation it is listed in the project database and can be reloaded using either the job name or the job id, therefore theese have to be unique for a given project. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# list all calculations in the current project using the project job table\n", "______.job_table()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As discussed in the previous section by default pyiron calculations are reloaded from the database when a calculation with the same name already exists in a given project. Therefore to overwrite the calculation parameters we use `delete_existing_job` parameter in the `run()` function. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Change the temperature to 800K and calculate 20000 steps\n", "# You get a warning message that the job is already finished. \n", "job_md.calc_md(temperature=_____, n_ionic_steps=_______)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Execute the LAMMPS calculation by calling the run function \n", "# with the delete_existing parameter set to true: \n", "job_md.run(delete_existing_job=______)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Reminder: Plot calculation results using matplotlib \n", "In the same way we can again use matplotlib to analyse the results of our calculation. For example we can use the matplotlib library to plot the temperature over simulation steps." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import the matplotlib library for plotting. \n", "import matplotlib.pyplot as plt\n", "\n", "# for the LAMMPS job object plot the temperature over simualation steps\n", "plt.plot(________.output.steps, _____.output.temperature)\n", "plt.xlabel(\"timesteps\")\n", "plt.ylabel(\"Temperature\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the beginning the potential energy is close to the 0K equilibrium, therefore to accelerate the equilibration the kinetic energy is set to twice the expected kinetic energy. The additional kinetic energy is transfered to the potential energy resulting in an equal distribution of potential and kinetic energy. With this trick the equilibration is accelerated. The large temperature fluctuations are related to the small number of atoms in the simulation cell." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Advanced input options\n", "Besides the general functions `calc_static()`, `calc_minimize()` and `calc_md()`. pyiron also has the option to modify the input of the simulation code directly. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# display the LAMMPS input file \n", "job_md.input.control" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# change the number of simulation steps to 2000 \n", "# by manually modifying the run command.\n", "# You again get a warning message that the job \n", "# is already finished. \n", "job_md.input.control[\"run\"] = ____" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Advanced output options \n", "Besides the output properties it is also possible to access the output of a calculation directly from the data interface which is based om the hierachical file format (HDF5) pyiron is using to store the simulation data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# print content of the job object \n", "job_md" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# print content of the output of the job object\n", "# use strings to specify the path for the data interface \n", "job_md[_______]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# print content of the generic group \n", "# of the output of the job object \n", "job_md[______/______]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# plot the temperature over simulation steps directly from the HDF5 file \n", "plt.plot(job_md[___________], job_md[___________])\n", "plt.xlabel(\"timesteps\")\n", "plt.ylabel(\"Temperature\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Accessing the original output files of the LAMMPS code\n", "While pyiron parses most of the output of the simulation codes some users might have the need to access addtional output parameters. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Decompress the LAMMPS job\n", "_______.decompress()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Read the LAMMPS output file of the LAMMPS job\n", "_______[\"log.lammps\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Beyond a single LAMMPS calculation\n", "While for individual LAMMPS calculation an integrated solution like pyiron is not required, pyiron really shines when it comes to combining multiple calculations. So in the following we iterate over a database of existing interatomic potentials and determine the lattice structure by minimizing the simulation cell. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Filter Interatomic Potential Database\n", "We start by identifying suitable interatomic potentials. By default pyiron already filters the interatomic potentials to only list those which include the required elements. Still the user can further filter the list of available potentials for a given project. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# List interatomic potentials from the NIST repository: \n", "# https://www.ctcms.nist.gov/potentials/\n", "# which include interactions for aluminium by calling view_potentials() on the LAMMPS job object\n", "potential_df = ______.view_potentials()\n", "potential_df[potential_df.Model == \"NISTiprpy\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Choose an interatmoic potential\n", "Interatomic potentials can be fitted for specific applications, therefore before selecting a given interatomic potential it is recommended to test basic properties of the potential. In this example we calculate the 0K equilibrium lattice constant by optimizing the atmoic supercell. \n", "\n", "#### Job Template\n", "To develop a scalable simulation protocol, we first define a job template and then apply this template to the available interatomic potentials." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# To compare different potentials we start by creating a template job named lmp_template\n", "job_template = pr.create.job.Lammps(\n", " job_name=______\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# We assign a cubic fcc aluminium supercell \n", "job_template.structure = pr.create.structure.ase.bulk(_____, _____)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Enable volume minimization by specifying the pressure as zero\n", "job_template.calc_minimize(pressure=_____)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Iterate over interatomic potentials\n", "After the template is constructed we can iterate over the database of existing interatomic potentials. In this example we limit the total umber of potentials to three to accelerate the calculations. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Select the first three potentials from the NIST database\n", "potential_df[potential_df.Model == \"NISTiprpy\"].Name.values[_____]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# We then iterate over the first three potentials of the NIST database\n", "for p in potential_df[potential_df.Model == \"NISTiprpy\"].Name.values[_____]:\n", " # create a copy for each of the template job for each of the potentials\n", " # without creating a new database entry by setting the new_database_entry \n", " # parameter to False \n", " job_minimize = job_template.copy_to(\n", " new_job_name=\"lmp_\" + p.replace(\"-\", \"_\"), \n", " new_database_entry=_____\n", " )\n", " \n", " # We then assign the potential \n", " job_minimize.potential = p\n", " \n", " # Execute the calculation and set the delete_existing_job parameter to True\n", " job_minimize.run(delete_existing_job=____)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# list all calculations in the current project using the project job table\n", "pr.job_table()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Validate calculations\n", "We validate the simulation results by confirming the calculations have been executed successfully. In this example one calculation failed. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# We use the pyiron job table of the project object \n", "# to validate all jobs finished successfully with \n", "# the status \"finished\"\n", "df = ____.job_table()\n", "len(df[df.status == _______]) == 4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Analyse results\n", "Iterate over the successful calculation and compare the calculated lattice constants. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# load a job object by the job name in the column job \n", "# of the pyiron job table \n", "job = pr.load(______)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# print the final lattice constant \n", "job[\"output/generic/cells\"][______]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# We iterate over the jobs in a the current project \n", "for job in pr.iter_jobs():\n", " # Filter job using only those who have the job status \n", " # finished and \"lmp_\" in the job_name\n", " if job.status == _____ and ____ in job.job_name: \n", " # Print the job name and the lattice constant \n", " print(job.job_name, job[\"output/generic/cells\"][_____])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### pyiron table \n", "To automate the collection of data from individual calculations pyiron includes the pyiron table object. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create a pyiron table object\n", "table = pr.create.table()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Implement a filter function, which returns true \n", "# for finished jobs and jobs with \"lmp_\" in the job_name\n", "def filter_jobs(job):\n", " return job.status == ______ and ______ in job.job_name" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Implement an analysis functions, which takes a job object\n", "# as an input and returns the lattice constant. Based on the\n", "# previous cell above to print the lattice constant.\n", "def get_lattice_constant(job): \n", " return _________________" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Implement a second analysis functions, which takes a \n", "# job object as an input and returns the job_name.\n", "def get_job_name(job):\n", " return _________________" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Assigne the filter functions and the analysis functions \n", "# to the pyiron table object\n", "table.filter_function = filter_jobs\n", "table.add[\"job_name\"] = get_job_name\n", "table.add[\"lattice_constant\"] = get_lattice_constant" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Execute the pyiron table just like a pyiron job object\n", "table.run()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Return a pandas DataFrame with the collected results \n", "table.get_dataframe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary \n", "In this section you learned:\n", "* to execute LAMMPS calculation with generic and code-specifc input,\n", "* to iterate over multiple interatomic potentials\n", "* and aggregate the data in a pyiron table object. \n", "This technique was afterwards used to calculate the lattice constant of multiple interatomic potentials. \n", "\n", "Additional exercises: \n", "* Calculate the lattice constant at a finite temperature of 500K by averaging the lattice constant over 1000 time steps. \n", "* Calculate the thermal expansion for multiple potentials. \n", "* How does an interstitial element change the thermal expansion? How does a vacancy change the thermal expansion? " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.12" } }, "nbformat": 4, "nbformat_minor": 4 }