{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Bulk Structures and Relaxations\n\nHere, we create bulk structures and optimize them to their ideal bulk properties\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\nimport numpy as np\n\nfrom ase.build import bulk\nfrom ase.calculators.emt import EMT\nfrom ase.eos import EquationOfState\nfrom ase.filters import FrechetCellFilter\nfrom ase.optimize import BFGS\nfrom ase.units import kJ\nfrom ase.visualize.plot import plot_atoms" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setting up bulk structures\n\nASE provides three frameworks for setting up bulk structures:\n\n * :func:`ase.build.bulk`. Knows lattice types\n and lattice constants for elemental bulk structures\n and a few compounds, but\n with limited customization.\n\n * :func:`ase.spacegroup.crystal`. Creates atoms\n from typical crystallographic information such as spacegroup,\n lattice parameters, and basis.\n\n * :mod:`ase.lattice`. Creates atoms explicitly from lattice and basis.\n\nLet's run a simple bulk calculation. We use :func:`ase.build.bulk`\nto get a primitive cell of silver, and then visualize it. Silver is known\nto form an FCC structure, so presumably the function returned a primitive\nFCC cell. You can, e.g., use the ASE GUI to repeat the structure and\nrecognize the A-B-C stacking.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "atoms = bulk('Ag')\n\nfig, ax = plt.subplots()\nplot_atoms(atoms * (3, 3, 3), ax=ax)\nax.set_xlabel(r'$x (\\AA)$')\nax.set_ylabel(r'$y (\\AA)$')\nfig.tight_layout()\n\n# For interactive use of the ASE GUI:\n# view(atoms * (3,3,3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ASE should also be able to verify that it really is a primitive FCC cell\nand tell us what lattice constant was chosen:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(f'Bravais lattice: {atoms.cell.get_bravais_lattice()}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Periodicity\n\nPeriodic structures in ASE are represented using ``atoms.cell``\nand ``atoms.pbc``.\n* The cell is a :class:`~ase.cell.Cell` object which represents\nthe crystal lattice with three vectors.\n* ``pbc`` is an array of three booleans indicating whether the system\nis periodic in each direction.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print('Cell:\\n', atoms.cell.round(3))\nprint('Periodicity: ', atoms.pbc)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Equation of state\n\nWe can find the optimal lattice parameter and calculate the bulk modulus\nby doing an equation-of-state calculation. This means sampling the energy\nand lattice constant over a range of values to get the minimum as well\nas the curvature, which gives us the bulk modulus.\n\nThe online ASE docs already provide a tutorial on how to perform\nequation-of-state calculations: (`eos_example`)\n\nFirst, we calculate the volume and potential energy, whilst scaling the\natoms' cell. Here, we use ase's empirical calculator ``EMT``:\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "calc = EMT()\ncell = atoms.get_cell()\n\nvolumes = []\nenergies = []\nfor x in np.linspace(0.95, 1.05, 5):\n atoms_copy = atoms.copy()\n atoms_copy.calc = calc\n atoms_copy.set_cell(cell * x, scale_atoms=True)\n atoms_copy.get_potential_energy()\n volumes.append(atoms_copy.get_volume())\n energies.append(atoms_copy.get_potential_energy())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, via :func:`ase.eos.EquationOfState`, we can calculate and plot\nthe bulk modulus:\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "eos = EquationOfState(volumes, energies)\nv0, e0, B = eos.fit()\n\nax = eos.plot()\nax.axhline(e0, linestyle='--', alpha=0.5, color='black')\nax.axvline(v0, linestyle='--', alpha=0.5, color='red')\n\nprint(f'Minimum Volume = {v0:.3f}AA^3')\nprint(f'Minimum Energy = {e0:.3f}eV')\nprint(f'Bulk modulus = {B / kJ * 1.0e24:.3f} GPa')\nplt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bulk Optimization\n\nWe can also find the optimal, relaxed cell via variable-cell relaxation.\nThis requires setting a filter, in this case the\n:func:`ase.filters.FrechetCellFilter` filter, which allows minimizing both the\natomic forces and the unit cell stress.\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "original_lattice = atoms.cell.get_bravais_lattice()\ncalc = EMT()\natoms.calc = calc\n\nopt = BFGS(FrechetCellFilter(atoms), trajectory='opt.Ag.traj')\nopt.run(fmax=0.05)\nprint('\\n')\nprint(f'Original_Lattice: {original_lattice}')\nprint(f'Final Lattice: {atoms.cell.get_bravais_lattice()}')\nprint(f'Final Cell Volume: {atoms.get_volume():.3f}AA^3')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.13.7" } }, "nbformat": 4, "nbformat_minor": 0 }