{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Crystal symmetry in ASE\n", "\n", "This notebook provides an introduction to some of the crystal symmetry features in ASE and spglib.\n", "\n", "It is intended as a self-guided tour, with small problems set in **bold text**. A few example systems are used, and readers are strongly encouraged to try out these tools with systems they are interested in, especially those with non-primitive unit cells and where data is available from geometry optimisation or structure prediction.\n", "\n", "> Notes written in quote blocks give tips that are related more to Python, IPython and Jupyter features.\n", "> Some familiarity with Python is assumed, but these tips should be helpful for completing the problems.\n", "\n", "The tutorial will first look at how we can apply known symmetry information when creating a system, then move on to analysing the symmetry of an existing structure." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bravais lattices\n", "The simplest crystal structures are defined entirely by their periodicity; the position of the atom within the unit cell is unimportant.\n", "\n", "Here we consider metallic silver: the list of available Bravais lattices in ASE is available [here](https://wiki.fysik.dtu.dk/ase/ase/lattice.html#available-crystal-lattices)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Made a lattice for Ag: \n" ] } ], "source": [ "import ase.lattice\n", "\n", "ag_lattice = ase.lattice.FCC(a=4.09) # Face-centered cubic\n", "print(\"Made a lattice for Ag: \", type(ag_lattice))\n", "\n", "assert isinstance(ag_lattice, ase.lattice.BravaisLattice)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is an instance of the FCC class which is derived from a parent class *BravaisLattice*. We don't work with BravaisLattice directly, but it provides features to its \"children\". Here we have set up an instance of FCC with lattice parameters matching metallic silver. To make an Atoms object, we convert to a unit cell and then to Atoms.\n", "> In Python, we can compare types with `type(x) == type(y)` but this will only succeed if they are exactly the same.\n", "> It is often more robust to use `isinstance()`, which will return True if the object type matches _or inherits from_ the compared type." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cell([[0.0, 2.045, 2.045], [2.045, 0.0, 2.045], [2.045, 2.045, 0.0]])\n" ] } ], "source": [ "from ase import Atoms\n", "ag_cell = ag_lattice.tocell()\n", "ag_atoms = Atoms('Ag', cell=ag_cell, pbc=True)\n", "print(ag_atoms.cell)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that _Atoms_ needs to know what element we are using, while the _Cell_ does not. Because there is one atom and it doesn't matter where it goes, we didn't have to specify any positions.\n", "\n", "This operation has constructed a *primitive* cell of Ag rather than the cubic cell with 4.09Å sides.\n", "We can view the structure with ASE's inbuilt viewer. By default the `ase.visualize.view` command will create a pop-up window with the structure in it (you may need to move your browser window to spot it!)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "from ase.visualize import view\n", "view(ag_atoms)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A more notebook-friendly option is to plot with matplotlib. (Interactive notebook viewers are also avilable; see the [ASE docs](https://wiki.fysik.dtu.dk/ase/ase/visualize/visualize.html#viewer-for-jupyter-notebooks).)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": false }, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support. ' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " if (mpl.ratio != 1) {\n", " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", " }\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " fig.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var backingStore = this.context.backingStorePixelRatio ||\n", "\tthis.context.webkitBackingStorePixelRatio ||\n", "\tthis.context.mozBackingStorePixelRatio ||\n", "\tthis.context.msBackingStorePixelRatio ||\n", "\tthis.context.oBackingStorePixelRatio ||\n", "\tthis.context.backingStorePixelRatio || 1;\n", "\n", " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width * mpl.ratio);\n", " canvas.attr('height', height * mpl.ratio);\n", " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
');\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('');\n", " button.click(method_name, toolbar_event);\n", " button.mouseover(tooltip, toolbar_mouse_event);\n", " nav_element.append(button);\n", " }\n", "\n", " // Add the status bar.\n", " var status_bar = $('');\n", " nav_element.append(status_bar);\n", " this.message = status_bar[0];\n", "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", "mpl.figure.prototype._root_extra_style = function(el){\n", " var fig = this\n", " el.on(\"remove\", function(){\n", "\tfig.close_ws(fig, {});\n", " });\n", "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", " el.attr('tabindex', 0)\n", " // reach out to IPython and tell the keyboard manager to turn it's self\n", " // off when our div gets focus\n", "\n", " // location in version 3\n", " if (IPython.notebook.keyboard_manager) {\n", " IPython.notebook.keyboard_manager.register_events(el);\n", " }\n", " else {\n", " // location in version 2\n", " IPython.keyboard_manager.register_events(el);\n", " }\n", "\n", "}\n", "\n", "mpl.figure.prototype._key_event_extra = function(event, name) {\n", " var manager = IPython.notebook.keyboard_manager;\n", " if (!manager)\n", " manager = IPython.keyboard_manager;\n", "\n", " // Check for shift+enter\n", " if (event.shiftKey && event.which == 13) {\n", " this.canvas_div.blur();\n", " event.shiftKey = false;\n", " // Send a \"J\" for go to next cell\n", " event.which = 74;\n", " event.keyCode = 74;\n", " manager.command_mode();\n", " manager.handle_keydown(event);\n", " }\n", "}\n", "\n", "mpl.figure.prototype.handle_save = function(fig, msg) {\n", " fig.ondownload(fig, null);\n", "}\n", "\n", "\n", "mpl.find_output_cell = function(html_output) {\n", " // Return the cell and output element which can be found *uniquely* in the notebook.\n", " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", " // IPython event is triggered only after the cells have been serialised, which for\n", " // our purposes (turning an active figure into a static one), is too late.\n", " var cells = IPython.notebook.get_cells();\n", " var ncells = cells.length;\n", " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", " data = data.data;\n", " }\n", " if (data['text/html'] == html_output) {\n", " return [cell, data, j];\n", " }\n", " }\n", " }\n", " }\n", "}\n", "\n", "// Register the function which deals with the matplotlib target/channel.\n", "// The kernel may be null if the page has been refreshed.\n", "if (IPython.notebook.kernel != null) {\n", " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", "}\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import ase.spacegroup\n", "quartz = ase.spacegroup.crystal(symbols=['O', 'Si'],\n", " basis=[[0.413, 0.2711, 0.2172],\n", " [0.4673, 0, 0.3333]],\n", " spacegroup=152,\n", " cellpar=[4.9019, 4.9019, 5.3988, 90, 90, 120])\n", "fig, ax = plt.subplots()\n", "plot_atoms(quartz)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Convince yourself that this structure is sensible**\n", "\n", "> Hopefully you can find out that this is a network of tetrahedral units, consisting of Si surrounded by O. It may be helpful to open the 3D viewer (using `view()`) and use the \"repeats\" option in the View menu to expand the viewing region. Alternatively, we can dump out a structure with e.g. `quartz.write('POSCAR', vasp5=True)` for viewing in your favourite atomistic structure viewer (e.g. VESTA)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also open up the original .cif file and see if the results agree.\n", "\n", "You don't *have* to make structures from .cif files this way. ASE has CIF support built-in, and we could have saved ourselves a lot of trouble with" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "import ase.io\n", "atoms = ase.io.read('quartz.cif')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "but it's not unusual to find structures specified this way in research papers or textbooks." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Examine a .cif file for a high-symmetry material of interest to your research. How many symmetry operations are there? What happens when you construct the cell in ASE?**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Getting the symmetry operations\n", "How did ASE do that?\n", "\n", "ASE has a data structure for spacegroups that includes information about the symmetry operations, loaded from a data table." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Is a-quartz centrosymmetric? no\n", "Is the a-quartz lattice primitive? yes\n", "What symbol is this spacegroup? P 31 2 1\n" ] } ], "source": [ "spg = ase.spacegroup.Spacegroup(152)\n", "print(\"Is a-quartz centrosymmetric? {}\".format(\"yes\" if spg.centrosymmetric else \"no\"))\n", "print(\"Is the a-quartz lattice primitive? {}\".format(\"yes\" if spg.lattice == 'P' else \"no\"))\n", "print(\"What symbol is this spacegroup? {}\".format(spg.symbol))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This Class provides access to the actual symmetry operations as a list or iterator." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Symmetry operation #1\n", "Rotation matrix: [1 0 0]\n", " [0 1 0]\n", " [0 0 1]\n", "Translation vector: [0. 0. 0.]\n", "\n", "Symmetry operation #2\n", "Rotation matrix: [ 0 -1 0]\n", " [ 1 -1 0]\n", " [0 0 1]\n", "Translation vector: [0. 0. 0.33333333]\n", "\n", "Symmetry operation #3\n", "Rotation matrix: [-1 1 0]\n", " [-1 0 0]\n", " [0 0 1]\n", "Translation vector: [0. 0. 0.66666667]\n", "\n", "Symmetry operation #4\n", "Rotation matrix: [0 1 0]\n", " [1 0 0]\n", " [ 0 0 -1]\n", "Translation vector: [0. 0. 0.]\n", "\n", "Symmetry operation #5\n", "Rotation matrix: [ 1 -1 0]\n", " [ 0 -1 0]\n", " [ 0 0 -1]\n", "Translation vector: [0. 0. 0.66666667]\n", "\n", "Symmetry operation #6\n", "Rotation matrix: [-1 0 0]\n", " [-1 1 0]\n", " [ 0 0 -1]\n", "Translation vector: [0. 0. 0.33333333]\n", "\n" ] } ], "source": [ "for i, (rot, trans) in enumerate(spg.get_symop()):\n", " print(\"Symmetry operation #{}\".format(i + 1))\n", " print(\"Rotation matrix: \", rot[0,:])\n", " print(\" \", rot[1,:])\n", " print(\" \", rot[2,:])\n", " print(\"Translation vector: \", trans)\n", " print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hopefully these operations look familiar from the CIF file!\n", "\n", "**Have a look at the symmetry operations from some other spacegroups. Can you identify rotation, reflection and glide operations?**\n", "\n", "To generate a structure with these operations, ASE uses the symops in a method `Spacegroup.equivalent_sites`. This takes positions in fractional coordinates and applies the symmetry operations to derive a set of images. If we provide just the Si atom from the .cif file, we get our original site back, with two other images - this is consistent with the cell of three formula units." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([[0.4673 , 0. , 0.3333 ],\n", " [0. , 0.4673 , 0.66663333],\n", " [0.5327 , 0.5327 , 0.99996667]]), [0, 0, 0])" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "spg.equivalent_sites([0.4673, 0, 0.3333])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**What does the [0, 0, 0] mean? This is not part of the equivalent_sites array. Have a look at the docstring...**\n", ">\n", "> To examine detailed help from a regular python terminal you could use `help(ase.spacegroup.Spacegroup)`.\n", "> To inspect the manually-written docstring part directly you could print `ase.spacegroup.Spacegroup.__doc__`.\n", "> From an iPython terminal or Jupyter notebook the most convenient option is the special syntax `ase.spacegroup.Spacegroup?`.\n", "> Most functions in ASE should have docstrings, so adding a `?` to a half-written line of code can be very helpful in the heat of the moment.\n", "\n", "**What happens if you include redundant sites in the input to *equivalent_sites()*?**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Analysing symmetry in existing structures\n", "\n", "Many atomistic codes are \"smart\" about how they use symmetry. They can identify symmetry and use this to:\n", "\n", "- reduce the amount of information to be calculated;\n", "- reduce the size of data files;\n", "- impose constraints on calculated structures and fields.\n", "\n", "However, there are several reasons reasearchers _should_ be aware (and possibly worried) about the symmetry of their system, including:\n", "\n", "- working with primitive/conventional unit cells as appropriate;\n", "- identifying unique sites for substitutions and defects;\n", "- identifying when symmetry was broken by numerical errors;\n", "- avoiding the imposition of excessive symmetry on top of the _magnetic_ spacegroup." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Identifying the spacegroup\n", "\n", "Going back to the quartz cell that was imported from a .cif file, we can have a look at some metadata stored in the `info` attribute of *Atoms*." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'spacegroup': Spacegroup(152, setting=1),\n", " 'unit_cell': 'conventional',\n", " 'occupancy': {0: {'O': 1}, 1: {'Si': 1}}}" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "atoms.info" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The spacegroup was actually set as a tag when it was imported. Let's break the symmetry slightly by making some random changes to the atom positions." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1.36052887e+00, 1.15072730e+00, 1.17326705e+00],\n", " [ 3.22672813e+00, 6.02155459e-01, 2.97198522e+00],\n", " [ 2.76919195e+00, 2.49268218e+00, 4.77134989e+00],\n", " [ 3.17205300e-01, 1.75279176e+00, 4.22571491e+00],\n", " [ 7.74497067e-01, 3.64086703e+00, 6.25255722e-01],\n", " [-1.09148013e+00, 3.09329153e+00, 2.42689489e+00],\n", " [ 2.28974985e+00, -1.41230370e-03, 1.80088569e+00],\n", " [-1.14555471e+00, 1.98383544e+00, 3.59759529e+00],\n", " [ 1.30507668e+00, 2.26151294e+00, 5.39746905e+00]])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "atoms.rattle()\n", "atoms.positions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This didn't change the tag, so to get the correct symmetry we need to analyse the structure." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Spacegroup(1, setting=1)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ase.spacegroup.get_spacegroup(atoms)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now have spacegroup 1, with zero symmetry. That's an accurate assessment of the system, but there are plenty of cases where we have a structure that isn't quite _perfect_ and we'd like to know what high-symmetry structure is nearby. To do this we can increase the tolerance used to determine if atoms are \"in the same position\"." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Spacegroup(152, setting=1)" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ase.spacegroup.get_spacegroup(atoms, symprec=1e-2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This useful function is actually provided by [spglib](https://atztogo.github.io/spglib), an external library written in C. The performance benefits of C are quite helpful here, as determining the spacegroup involves applying many symmetry operations and analysing the results. Spglib includes a Python API and the integration into ASE is not too complicated. We can also access the same function directly" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'P3_121 (152)'" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from spglib import get_spacegroup\n", "get_spacegroup((atoms.cell, atoms.get_scaled_positions(), atoms.numbers),\n", " symprec=1e-2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's a bit more cumbersome compared to calling directly from ASE because spglib doesn't know how to read an Atoms object, so we extract the necessary parts and put them in a `(tuple)`.\n", "**When might it be useful to call this function directly? Look at the docstring...**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cleaning up a structure with standardize_cell\n", "\n", "There are a few more functions in the *spglib interface* that are not directly provided in ASE.\n", "\n", "Suppose your collaborator has asked you to calculate some properties and has sent you the structure file *mystery.cell* (included in the directory with this notebook)." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Spacegroup(1, setting=1)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mystery_atoms = ase.io.read('mystery.cell')\n", "ase.spacegroup.get_spacegroup(mystery_atoms)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Well, you _could_ assume the structure is already perfect and we are simply dealing with a low-symmetry system.\n", "\n", "**Visualise the structure. Does this look low-symmetry to you?**\n", "\n", "It's not always easy to judge by eye. We don't know what spacegroup the material is _supposed_ to have. A useful procedure in this scenario is to look at how sensitive the spacegroup is to the distance threshold." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Threshold: 1e-05 Spacegroup: P 1\n", "Threshold: 0.0005 Spacegroup: P 1\n", "Threshold: 0.0001 Spacegroup: P 1\n", "Threshold: 0.001 Spacegroup: P 1\n", "Threshold: 0.005 Spacegroup: I -4\n", "Threshold: 0.01 Spacegroup: I -4\n", "Threshold: 0.1 Spacegroup: I -4\n" ] } ], "source": [ "for symprec in (1e-5, 5e-4, 1e-4, 1e-3, 5e-3, 1e-2, 1e-1):\n", " print(\"Threshold: {} Spacegroup: {}\".format(symprec,\n", " ase.spacegroup.get_spacegroup(mystery_atoms,\n", " symprec=symprec).symbol))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So there is an I-4 system within a small atomic movement. It's possible that the symmetry-breaking is real, of course, but it would be nice to do some calculations with the high-symmetry system and get those efficiency improvements." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "from spglib import standardize_cell" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`standardize_cell` replaces the older `find_primitive` and `refine_cell` functions, and is generally recommended for this purpose." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([[ 5.38326953, 0. , 0. ],\n", " [ 0. , 5.38326953, 0. ],\n", " [ 0. , 0. , 10.72708263]]),\n", " array([[0. , 0.5 , 0.75 ],\n", " [0. , 0. , 0.5 ],\n", " [0.25965518, 0.76835118, 0.38004812],\n", " [0.23164882, 0.25965518, 0.61995188],\n", " [0.76835118, 0.74034482, 0.61995188],\n", " [0.74034482, 0.23164882, 0.38004812],\n", " [0.5 , 0. , 0.75 ],\n", " [0.5 , 0.5 , 0.5 ],\n", " [0.5 , 0. , 0.25 ],\n", " [0.5 , 0.5 , 0. ],\n", " [0.75965518, 0.26835118, 0.88004812],\n", " [0.73164882, 0.75965518, 0.11995188],\n", " [0.26835118, 0.24034482, 0.11995188],\n", " [0.24034482, 0.73164882, 0.88004812],\n", " [0. , 0.5 , 0.25 ],\n", " [0. , 0. , 0. ]]),\n", " array([29, 29, 16, 16, 16, 16, 50, 30, 29, 29, 16, 16, 16, 16, 50, 30],\n", " dtype=int32))" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mystery_cell = (mystery_atoms.cell, mystery_atoms.get_scaled_positions(), mystery_atoms.numbers)\n", "new_cell = standardize_cell(mystery_cell, to_primitive=False, symprec=5e-3)\n", "new_cell" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that an spglib _cell_ is more analogous to _Atoms_ in ASE than to a _Cell_ in ASE. To convert this to an Atoms object we can feed the data into an Atoms constructor. This is made especially clear using Python's variable-unpacking syntax `x, y, z = a`." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support. ' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " if (mpl.ratio != 1) {\n", " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", " }\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " fig.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var backingStore = this.context.backingStorePixelRatio ||\n", "\tthis.context.webkitBackingStorePixelRatio ||\n", "\tthis.context.mozBackingStorePixelRatio ||\n", "\tthis.context.msBackingStorePixelRatio ||\n", "\tthis.context.oBackingStorePixelRatio ||\n", "\tthis.context.backingStorePixelRatio || 1;\n", "\n", " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width * mpl.ratio);\n", " canvas.attr('height', height * mpl.ratio);\n", " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
');\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('