{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "f248affc", "metadata": { "tags": [ "remove-input" ] }, "outputs": [], "source": [ "%config InlineBackend.figure_formats = ['svg']\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "plt.rcParams['animation.frame_format'] = \"svg\"\n", "\n", "import warnings\n", "from matplotlib._api import MatplotlibDeprecationWarning\n", "warnings.filterwarnings(\"ignore\", category=MatplotlibDeprecationWarning)" ] }, { "cell_type": "markdown", "id": "79a219ac", "metadata": {}, "source": [ "(quickstart)=\n", "\n", "# Quickstart\n", "\n", "This page gives a whirlwind introduction to flatspin. Make sure you have [installed flatspin](installation) before you continue.\n", "\n", "The best way to get familiar with flatspin, is to play with the simulator in an interactive Python environment.\n", "This guide is written as a [Jupyter notebook](https://jupyter.org/), which you can download and run yourself.\n", "Just click the download link from the top of this page.\n", "\n", "Let's get started!" ] }, { "cell_type": "markdown", "id": "43a50928", "metadata": {}, "source": [ "## The flatspin model\n", "\n", "The main object in flatspin is the [model](model).\n", "Each model class defines a spin ice *geometry*, which specifies the positions and angles of the spins. \n", "The {class}`SquareSpinIceClosed ` class, for instance, creates a square spin ice geometry (with \"closed\" edges):" ] }, { "cell_type": "code", "execution_count": null, "id": "550c33d0", "metadata": {}, "outputs": [], "source": [ "from flatspin.model import SquareSpinIceClosed\n", "\n", "model = SquareSpinIceClosed()\n", "model.plot();" ] }, { "cell_type": "markdown", "id": "654d3086", "metadata": {}, "source": [ "A list of available model classes can be found in the [user guide](model).\n", "Custom geometries may be created by [extending flatspin](extending).\n", "\n", "## Model parameters\n", "\n", "Properties of the model can be changed through *parameters*, which are passed as keyword arguments to the model class. For instance, we may change the `size` parameter to create a larger spin ice:" ] }, { "cell_type": "code", "execution_count": null, "id": "0904e895", "metadata": {}, "outputs": [], "source": [ "model = SquareSpinIceClosed(size=(10,5))\n", "print(f\"10x5 square ASI has\", model.spin_count, \"spins\")\n", "model.plot();" ] }, { "cell_type": "markdown", "id": "220fe14a", "metadata": {}, "source": [ "Note that `size` is geometry-specific: for {class}`SquareSpinIceClosed `, the size specifies the number of columns (rows) of horizontal (vertical) magnets.\n", "For {class}`KagomeSpinIce `, the size denotes the number of hexagonal units:" ] }, { "cell_type": "code", "execution_count": null, "id": "fb8773e9", "metadata": {}, "outputs": [], "source": [ "from flatspin.model import KagomeSpinIce\n", "model = KagomeSpinIce(size=(10,5))\n", "print(f\"10x5 kagome ASI has\", model.spin_count, \"spins\")\n", "model.plot();" ] }, { "cell_type": "markdown", "id": "3cd3dbb3", "metadata": {}, "source": [ "Other important parameters include `alpha` (the coupling strength), `hc` (the coercive field), `disorder` (random variations in the coercive fields) and `temperature` (absolute temperature in Kelvin). For a list of available parameters, see {class}`SpinIce `.\n", "\n", "## Running the model\n", "\n", "The primary method for interacting with the model is the *external field*. We set the external field with {func}`set_h_ext() `." ] }, { "cell_type": "code", "execution_count": null, "id": "d889dc98", "metadata": {}, "outputs": [], "source": [ "model = SquareSpinIceClosed()\n", "model.set_h_ext([-0.058, -0.058])" ] }, { "cell_type": "markdown", "id": "5c363639", "metadata": {}, "source": [ "Next we run the model by calling {func}`relax() `, which will flip all spins until equilibrium is reached, i.e., until there are no more flippable spins. {func}`relax() ` returns the number of spins that were flipped." ] }, { "cell_type": "code", "execution_count": null, "id": "abeae942", "metadata": {}, "outputs": [], "source": [ "flips = model.relax()\n", "print(flips, \"spins flipped\")\n", "model.plot();" ] }, { "cell_type": "markdown", "id": "f81be64a", "metadata": {}, "source": [ "We may also flip only a single spin at a time by calling {func}`step() `. For each call to {func}`step() `, the spin with the highest \"switching energy\" will be flipped, and the method returns `True`. If there are no flippable spins, {func}`step() ` returns `False`. Below we use {func}`step() ` to create an animation of the relaxation process." ] }, { "cell_type": "code", "execution_count": null, "id": "a5061a3b", "metadata": {}, "outputs": [], "source": [ "from matplotlib.animation import FuncAnimation\n", "from IPython.display import HTML\n", "\n", "# Reset system back to the polarized state\n", "model.polarize()\n", "\n", "fig, ax = plt.subplots()\n", "\n", "def do_steps():\n", " yield model\n", " while model.step():\n", " yield model\n", "\n", "def animate(model):\n", " model.plot(ax=ax, replace=True)\n", " \n", "anim = FuncAnimation(fig, animate, frames=do_steps(), interval=200, blit=False)\n", "plt.close() # Only show the animation\n", "HTML(anim.to_jshtml())" ] }, { "cell_type": "markdown", "id": "2b7b96bf", "metadata": {}, "source": [ "## Hysteresis loops\n", "\n", "Often we are interested in some average quantity of the ensemble, such as the total magnetization.\n", "\n", "A hysteresis loop is a plot of the total magnetization, projected along the direction of the external field. Below we gradually increase the strength `H` of the external field, applied at some angle `phi`, and record the total magnetization for each field value `H`. We also enable GPU acceleration to speed up the simulation with `use_opencl=True`.\n", "If your system is not set up to use opencl, replace with `use_opencl=False` (this will slow down your simulations significantly)." ] }, { "cell_type": "code", "execution_count": null, "id": "a333afc2", "metadata": {}, "outputs": [], "source": [ "model = SquareSpinIceClosed(size=(10,10), use_opencl=True)\n", "\n", "# H decreases linearly from 0.1 to -0.1, then back to 0.1\n", "H = np.linspace(0.1, -0.1, 500)\n", "H = np.concatenate([H, -H])\n", "\n", "# Angle of the external field\n", "phi = np.deg2rad(45)\n", "h_dir = np.array([np.cos(phi), np.sin(phi)])\n", "\n", "M_H = []\n", "\n", "for h in H:\n", " model.set_h_ext(h * h_dir)\n", " model.relax()\n", " # Magnetization projected along field direction\n", " m = model.total_magnetization().dot(h_dir)\n", " M_H.append(m)\n", "\n", "plt.plot(H, M_H)\n", "plt.xlabel(\"H [T]\")\n", "plt.ylabel(r\"M_H [a.u.]\");" ] }, { "cell_type": "markdown", "id": "ec7fe4fc", "metadata": {}, "source": [ "And here is an animation of the hysteresis loop, where only changes to the state are shown:" ] }, { "cell_type": "code", "execution_count": null, "id": "b07f226e", "metadata": {}, "outputs": [], "source": [ "# Reset system back to the polarized state\n", "model.polarize()\n", "\n", "fig, ax = plt.subplots()\n", "\n", "def do_hysteresis():\n", " yield model\n", " for h in H:\n", " model.set_h_ext(h * h_dir)\n", " if model.relax():\n", " # Only show frames where any spins flipped\n", " yield model\n", "\n", "def animate(model):\n", " h = model.h_ext[0].dot(h_dir)\n", " ax.set_title(f'H = {h:g}')\n", " model.plot(ax=ax, replace=True)\n", " \n", "anim = FuncAnimation(fig, animate, frames=do_hysteresis(), interval=200, blit=False)\n", "plt.close() # Only show the animation\n", "HTML(anim.to_jshtml())" ] }, { "cell_type": "markdown", "id": "0d15becb", "metadata": {}, "source": [ "## Temperature\n", "\n", "If we increase the temperature, spins flip more easily due to additional thermal energy. Below we can see how temperature affects the hysteresis loop from earlier. Notice how the hysteresis loop becomes narrower at higher temperatures, as the additional thermal energy causes spins to flip for weaker fields." ] }, { "cell_type": "code", "execution_count": null, "id": "6c5b3d9f", "metadata": {}, "outputs": [], "source": [ "model = SquareSpinIceClosed(size=(10,10), m_therm=1e-17, use_opencl=True)\n", "\n", "# H, phi and h_dir as before\n", "\n", "temperatures = [0, 200, 400, 600]\n", "\n", "for T in temperatures:\n", " M_H = []\n", "\n", " model.polarize()\n", " model.set_temperature(T)\n", " for h in H:\n", " model.set_h_ext(h * h_dir)\n", " model.relax()\n", " m = model.total_magnetization().dot(h_dir)\n", " M_H.append(m)\n", "\n", " plt.plot(H, M_H, label=f\"{T} K\")\n", "\n", "plt.xlabel(\"H [T]\")\n", "plt.ylabel(r\"M_H [a.u.]\");\n", "plt.legend();" ] }, { "cell_type": "markdown", "id": "a5784be9", "metadata": {}, "source": [ "## Vertices\n", "\n", "Spin ice systems are frequently analyzed in terms of its *vertices*. A *vertex* is defined by the points in the geometry where spins point either in or out. In square spin ice, a vertex is surrounded by four spins. In the example below, the blue dots denote the vertices." ] }, { "cell_type": "code", "execution_count": null, "id": "1862f527", "metadata": {}, "outputs": [], "source": [ "model = SquareSpinIceClosed()\n", "model.plot()\n", "model.plot_vertices();" ] }, { "cell_type": "markdown", "id": "992cd33c", "metadata": {}, "source": [ "The *vertex type* is defined by the dipolar energy between the spins in the vertex. Below we randomize the spin states of the model, and count the number of different vertex types with {func}`vertex_count() `. In the plot, the colors of the dots indicate the vertex type: green for type 1 (lowest energy), blue for type 2, red for type 3 and gray for type 4 (highest energy)." ] }, { "cell_type": "code", "execution_count": null, "id": "9d4eeaa1", "metadata": {}, "outputs": [], "source": [ "model.randomize(seed=0x9876)\n", "print(\"Vertex counts:\", model.vertex_count())\n", "model.plot()\n", "model.plot_vertices();" ] }, { "cell_type": "markdown", "id": "3cd56445", "metadata": {}, "source": [ "Another useful visualization is the *vertex magnetization*, which is the net magnetic moment of the spins in each vertex.\n", "Type 1 vertices have zero net moment, and depicted as white (empty) regions in the plot below." ] }, { "cell_type": "code", "execution_count": null, "id": "9052cfc0", "metadata": {}, "outputs": [], "source": [ "model.plot_vertex_mag();" ] }, { "cell_type": "markdown", "id": "9e5e8215", "metadata": {}, "source": [ "## Large scale patterns\n", "\n", "Spin-spin interactions can give rise to large scale patterns in the ensemble.\n", "The emergent behavior is a function of the spin ice geometry.\n", "Square spin ice exhibits **antiferromagnetic domains** with zero net magnetization.\n", "Large scale patterns are easily visible if we plot the vertex magnetization.\n", "\n", "Below, we initialize a square spin ice to a random configuration, and \"anneal\" it to a low energy state.\n", "We cheat a bit and use a very high coupling strength `alpha` to anneal the system without any external field.\n", "For a more physically realistic field-based annealing protocol, see [](examples/anneal_field)." ] }, { "cell_type": "code", "execution_count": null, "id": "b566167a", "metadata": {}, "outputs": [], "source": [ "model = SquareSpinIceClosed(size=(25,25), alpha=1.0, init='random', use_opencl=True)\n", "model.relax()\n", "model.plot_vertex_mag();" ] }, { "cell_type": "markdown", "id": "94b24dcf", "metadata": {}, "source": [ "Pinwheel spin ice, on the other hand, favors **ferromagnetic domains** with a positive net magnetization." ] }, { "cell_type": "code", "execution_count": null, "id": "136ef20d", "metadata": {}, "outputs": [], "source": [ "from flatspin.model import PinwheelSpinIceDiamond\n", "model = PinwheelSpinIceDiamond(size=(25,25), alpha=1.0, init='random', use_opencl=True)\n", "model.relax()\n", "model.plot_vertex_mag();" ] }, { "cell_type": "markdown", "id": "50ed98e1", "metadata": {}, "source": [ "## Energy\n", "\n", "The energy of each spin is derived from the total fields acting on each spin, i.e., the dipolar fields, external fields and thermal fields.\n", "Below we obtain the energy of each spin by calling {func}`energy() `. Next we use the energy to color the arrows of each spin in the relaxed pinwheel system from above.\n", "Notice how the spins that are part of the domain walls (the boundaries of the ferromagnetic domains) have higher energy than the spins that are well inside the domains." ] }, { "cell_type": "code", "execution_count": null, "id": "e437b241", "metadata": {}, "outputs": [], "source": [ "E = model.energy()\n", "print(\"Total energy:\", np.sum(E))\n", "quiv = model.plot(C=E, cmap='plasma')\n", "plt.colorbar(quiv);" ] }, { "cell_type": "markdown", "id": "df58edcb", "metadata": {}, "source": [ "## Ready to learn more?\n", "\n", "This has been a quick whirlwind introduction to flatspin.\n", "If you haven't already done so, you are encouraged to download this notebook and play with the code examples yourself.\n", "Just click the download button from the top of this page!\n", "\n", "In this short time, we have covered only a small part of the flatspin simulator.\n", "To learn more, head over to the [](userguide) and explore the topics covered." ] } ], "metadata": { "jupytext": { "formats": "md:myst", "text_representation": { "extension": ".md", "format_name": "myst", "format_version": 0.13, "jupytext_version": "1.11.5" } }, "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.8.10" }, "source_map": [ 15, 27, 41, 49, 54, 63, 67, 72, 77, 85, 88, 92, 96, 100, 120, 129, 152, 156, 178, 184, 207, 213, 217, 221, 226, 231, 233, 246, 250, 254, 259, 267, 272 ] }, "nbformat": 4, "nbformat_minor": 5 }