{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "a1e6dd82", "metadata": { "tags": [ "remove-input" ] }, "outputs": [], "source": [ "%config InlineBackend.figure_formats = ['svg']\n", "\n", "import numpy as np\n", "from numpy.linalg import norm\n", "import matplotlib.pyplot as plt\n", "plt.rcParams['animation.frame_format'] = \"svg\"" ] }, { "cell_type": "markdown", "id": "66c9f338", "metadata": {}, "source": [ "(fields)=\n", "\n", "# Magnetic fields\n", "\n", "In flatspin, external fields and temperature are modeled as a combination of magnetic fields.\n", "The total magnetic field affecting each spin is the sum of three types of fields:\n", "\n", "1. Dipolar fields from neighboring magnets (`h_dip`)\n", "2. An external field (`h_ext`)\n", "3. A stochastic thermal field (`h_therm`)\n", "\n", "Magnetic field vectors can be viewed from two frames of reference:\n", "1. The **global reference frame** `[h_x, h_y]` is the most intuitive, where `h_x` is the horizontal component (from left to right) and `h_y` is the the vertical component (going upwards).\n", "2. In the **local reference frame**, the field is projected along the magnetization direction of each spin, resulting in a vector `[h_par, h_perp]` where `h_par` is the field component which is parallel to the magnetization, and `h_perp` is the perpendicular component. In other words, the local reference frame represents the magnetic field \"felt\" by each spin. When `h_par` is positive, the field is pushing in the same direction as the magnetization, and when `h_par` is negative, the field is pushing in the opposite direction of the magnetization.\n", "\n", "In general, field attributes (such as `h_ext`) are always stored in the global reference frame, while the fields returned by field methods (such as {func}`external_fields() `) are in the local reference frame." ] }, { "cell_type": "markdown", "id": "885ebda6", "metadata": {}, "source": [ "(fields-dipolar)\n", "## Dipolar fields\n", "\n", "Spins are coupled through [dipole-dipole interactions](https://en.wikipedia.org/wiki/Magnetic_dipole%E2%80%93dipole_interaction), and each spin is subject to a magnetic field from all neighboring spins.\n", "These dipolar fields can be calculated using {func}`dipolar_fields() `, which returns an array of vectors `[h_par, h_perp]` where `h_par` is the parallel component of the dipolar field and `h_perp` is the perpendicular component (local reference frame).\n", "\n", "Below the spins are colorized according their `h_par` values." ] }, { "cell_type": "code", "execution_count": null, "id": "6eb8a111", "metadata": {}, "outputs": [], "source": [ "from flatspin.model import SquareSpinIceClosed\n", "\n", "model = SquareSpinIceClosed()\n", "model.spin[[0, 4, 39]] = -1 # flip spins 0, 4 and 39\n", "\n", "h_dip = model.dipolar_fields()\n", "quiv = model.plot(C=h_dip[:,0], cmap='coolwarm_r')\n", "plt.colorbar(quiv);" ] }, { "cell_type": "markdown", "id": "1d86cb71", "metadata": {}, "source": [ "The strength of the dipolar fields scale with the parameter `alpha` (the coupling strength): a higher `alpha` results in stronger dipolar fields.\n", "\n", "Because the dipole-dipole interaction quickly fades over long distances, flatspin only considers a finite neighborhood when computing dipolar fields. The radius of this neighborhood can be configured with the `neighbor_distance` model parameter. Setting `neighbor_distance=np.inf` results in a global neighborhood, but is computationally costly for large systems.\n", "\n", "Below we illustrate how the neighborhood (light red) of the center spin (dark red) changes for different values of `neighbor_distance`.\n", "See also [](examples/neighbor_distance) for a discussion on the effects of neighbor distance." ] }, { "cell_type": "code", "execution_count": null, "id": "39a65201", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "plt.figure(figsize=(8,4))\n", "for nd in [1, 2, 3]:\n", " plt.subplot(1,3,nd)\n", " plt.title(f'neighbor_distance={nd}')\n", " m = SquareSpinIceClosed(size=(6,6), neighbor_distance=nd)\n", " i = m.spin_count//2 - 1\n", " neighs = np.zeros(m.spin_count)\n", " neighs[i] = 1\n", " neighs[m.neighbors(i)] = 0.8\n", " m.plot(C=neighs, cmap='coolwarm', clim=(0,1))" ] }, { "cell_type": "markdown", "id": "6c8801b3", "metadata": {}, "source": [ "(fields-external)=\n", "## External fields\n", "\n", "The external magnetic field is the primary mechanism for altering the state of an ASI in a controlled manner.\n", "To set the external field, use {func}`set_h_ext() `.\n", "The external field can be set either globally for the entire system, or locally on a per spin basis.\n", "The `h_ext` attribute stores the external fields for all the spins (global reference frame).\n", "\n", "As with the dipolar fields, the parallel and perpendicular components of the external field acting on each spin can be calculated using {func}`external_fields() `.\n", "\n", "Passing a single vector to {func}`set_h_ext() ` sets a global field:" ] }, { "cell_type": "code", "execution_count": null, "id": "7a8cc591", "metadata": {}, "outputs": [], "source": [ "from flatspin.plotting import plot_vectors\n", "\n", "# Global external field\n", "model.set_h_ext([0.1, 0.1])\n", "\n", "plt.figure()\n", "plt.title(\"h_ext\")\n", "plot_vectors(model.pos, model.h_ext, normalize=True)\n", "\n", "plt.figure()\n", "plt.title(\"h_ext_par\")\n", "\n", "h_ext = model.external_fields()\n", "\n", "# Colorize spins by the parallel component of the external field\n", "quiv = model.plot(C=h_ext[:,0], cmap='coolwarm_r')\n", "plt.colorbar(quiv);" ] }, { "cell_type": "markdown", "id": "0f981695", "metadata": {}, "source": [ "Passing an array of vectors to {func}`set_h_ext() ` sets a local external field, i.e., an individual external field for each spin.\n", "The length of the array must match the number of spins in the system." ] }, { "cell_type": "code", "execution_count": null, "id": "8ec749eb", "metadata": {}, "outputs": [], "source": [ "# A local field which increases in strength with spin index\n", "H = np.linspace(0, 0.1, model.spin_count)\n", "phi = np.deg2rad(10)\n", "h_ext = np.column_stack([H * np.cos(phi), H * np.sin(phi)])\n", "print(f'h_ext.shape: {h_ext.shape}')\n", "model.set_h_ext(h_ext)\n", "\n", "plt.figure()\n", "plt.title(\"h_ext\")\n", "plot_vectors(model.pos, model.h_ext, normalize=True)\n", "\n", "plt.figure()\n", "plt.title(\"h_ext_par\")\n", "\n", "h_ext = model.external_fields()\n", "\n", "# Colorize spins by the parallel component of the external field\n", "quiv = model.plot(C=h_ext[:,0], cmap='coolwarm_r')\n", "plt.colorbar(quiv);" ] }, { "cell_type": "markdown", "id": "ed4483bf", "metadata": {}, "source": [ "(fields:spatial)=\n", "It is also possible to map a spatial vector field defined on a grid, onto the spins with {func}`set_h_ext_grid() `:" ] }, { "cell_type": "code", "execution_count": null, "id": "9efb26a6", "metadata": {}, "outputs": [], "source": [ "# A spatial vector field defined on a grid\n", "x = np.linspace(0, 2*np.pi, 9, endpoint=True)\n", "y = np.linspace(0, 2*np.pi, 9, endpoint=True)\n", "xx, yy = np.meshgrid(x, y)\n", "H = np.cos(xx) + np.cos(yy)\n", "h_ext = np.stack([0.01*H, 0.1*H], axis=-1)\n", "print(f'h_ext.shape: {h_ext.shape}')\n", "\n", "model.set_h_ext_grid(h_ext)\n", "\n", "plt.figure()\n", "plt.title(\"H\")\n", "plt.imshow(H, cmap='coolwarm_r')\n", "plt.colorbar()\n", "\n", "plt.figure()\n", "plt.title(\"h_ext\")\n", "plot_vectors(model.pos, model.h_ext, normalize=True);" ] }, { "cell_type": "markdown", "id": "094da877", "metadata": {}, "source": [ "(fields-thermal)=\n", "## Thermal fields\n", "\n", "The thermal field is a stochastic field which represents thermal fluctuations acting independently on each spin. In most cases, the thermal fields will be directed antiparallel to the spin magnetization, i.e., towards the easiest switching direction (see also [](switching)).\n", "\n", "After the temperature is set with {func}`set_temperature() `, the thermal field must be re-sampled using {func}`update_thermal_noise() `:" ] }, { "cell_type": "code", "execution_count": null, "id": "07e93d2f", "metadata": {}, "outputs": [], "source": [ "# Set temperature to 300 K, and re-sample h_therm\n", "model.set_temperature(300)\n", "model.update_thermal_noise()\n", "\n", "plt.title(\"h_therm @ 300 K\")\n", "h_therm_magnitude = norm(model.h_therm, axis=-1)\n", "# Colorize vectors by their magnitude\n", "quiv = plot_vectors(model.pos, model.h_therm,\n", " C=h_therm_magnitude, cmap='coolwarm', normalize=True)\n", "plt.colorbar(quiv);" ] }, { "cell_type": "markdown", "id": "8ccdf3fa", "metadata": {}, "source": [ "Increasing the temperature increases the magnitude of `h_therm` (but does not change the direction):" ] }, { "cell_type": "code", "execution_count": null, "id": "a3cb9bbc", "metadata": {}, "outputs": [], "source": [ "model.set_temperature(600)\n", "model.update_thermal_noise()\n", "\n", "plt.title(\"h_therm @ 600 K\")\n", "h_therm_magnitude = norm(model.h_therm, axis=-1)\n", "# Colorize vectors by their magnitude\n", "quiv = plot_vectors(model.pos, model.h_therm,\n", " C=h_therm_magnitude, cmap='coolwarm', normalize=True)\n", "plt.colorbar(quiv);" ] }, { "cell_type": "markdown", "id": "2932d30d", "metadata": {}, "source": [ "## Total field\n", "\n", "Finally, the total magnetic fields are the sum of the dipolar, external and thermal fields. The total field for each spin can be computed directly using {func}`total_fields() `:" ] }, { "cell_type": "code", "execution_count": null, "id": "493ee7a6", "metadata": {}, "outputs": [], "source": [ "plt.figure()\n", "plt.title(\"h_tot_par\")\n", "\n", "h_tot = model.total_fields()\n", "\n", "# Colorize spins by the parallel component of the total field\n", "quiv = model.plot(C=h_tot[:,0], cmap='coolwarm_r')\n", "plt.colorbar(quiv);" ] }, { "cell_type": "markdown", "id": "9a3b03e8", "metadata": {}, "source": [ "## GPU acceleration\n", "\n", "flatspin provides GPU acceleration to speed up calculations of the magnetic fields. Enabling GPU acceleration with `use_opencl=True` can greatly speed up calculations, especially for large systems:" ] }, { "cell_type": "code", "execution_count": null, "id": "b5b0e85b", "metadata": {}, "outputs": [], "source": [ "model_cpu = SquareSpinIceClosed(size=(100,100))\n", "%timeit model_cpu.dipolar_fields()" ] }, { "cell_type": "code", "execution_count": null, "id": "b82ac248", "metadata": {}, "outputs": [], "source": [ "model_gpu = SquareSpinIceClosed(size=(100,100), use_opencl=True)\n", "%timeit model_gpu.dipolar_fields()" ] } ], "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, 24, 43, 53, 62, 71, 84, 98, 116, 121, 141, 146, 165, 174, 185, 189, 199, 205, 214, 220, 225 ] }, "nbformat": 4, "nbformat_minor": 5 }