{ "cells": [ { "cell_type": "markdown", "source": [ "# Polarizability by linear response\n", "\n", "We compute the polarizability of a Helium atom. The polarizability\n", "is defined as the change in dipole moment\n", "$$\n", "\\mu = \\int r ρ(r) dr\n", "$$\n", "with respect to a small uniform electric field ``E = -x``.\n", "\n", "We compute this in two ways: first by finite differences (applying a\n", "finite electric field), then by linear response. Note that DFTK is\n", "not really adapted to isolated atoms because it uses periodic\n", "boundary conditions. Nevertheless we can simply embed the Helium\n", "atom in a large enough box (although this is computationally wasteful).\n", "\n", "As in other tests, this is not fully converged, convergence\n", "parameters were simply selected for fast execution on CI," ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "using DFTK\n", "using LinearAlgebra\n", "\n", "a = 10.\n", "lattice = a * I(3) # cube of ``a`` bohrs\n", "He = ElementPsp(:He, psp=load_psp(\"hgh/lda/He-q2\"))\n", "atoms = [He => [[1/2; 1/2; 1/2]]] # Helium at the center of the box\n", "\n", "kgrid = [1, 1, 1] # no kpoint sampling for an isolated system\n", "Ecut = 30\n", "tol = 1e-8\n", "\n", "# dipole moment of a given density (assuming the current geometry)\n", "function dipole(ρ)\n", " basis = ρ.basis\n", " rr = [a * (r[1] - 1/2) for r in r_vectors(basis)]\n", " d = sum(rr .* ρ.real) * basis.model.unit_cell_volume / prod(basis.fft_size)\n", "end;" ], "metadata": {}, "execution_count": 1 }, { "cell_type": "markdown", "source": [ "## Polarizability by finite differences\n", "We first compute the polarizability by finite differences.\n", "First compute the dipole moment at rest:" ], "metadata": {} }, { "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "n Energy Eₙ-Eₙ₋₁ ρout-ρin Diag\n", "--- --------------- --------- -------- ----\n", " 1 -2.769860305239 NaN 2.95e-01 8.0 \n", " 2 -2.771179776648 -1.32e-03 5.19e-02 1.0 \n", " 3 -2.771211774852 -3.20e-05 1.26e-03 1.0 \n", " 4 -2.771212958636 -1.18e-06 3.89e-04 2.0 \n", " 5 -2.771212976101 -1.75e-08 2.10e-04 2.0 \n", " 6 -2.771212981686 -5.58e-09 4.40e-06 1.0 \n" ] }, { "output_type": "execute_result", "data": { "text/plain": "-0.00013442502028186899" }, "metadata": {}, "execution_count": 2 } ], "cell_type": "code", "source": [ "model = model_LDA(lattice, atoms; symmetries=false)\n", "basis = PlaneWaveBasis(model, Ecut; kgrid=kgrid)\n", "res = self_consistent_field(basis, tol=tol)\n", "μref = dipole(res.ρ)" ], "metadata": {}, "execution_count": 2 }, { "cell_type": "markdown", "source": [ "Then in a small uniform field:" ], "metadata": {} }, { "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "n Energy Eₙ-Eₙ₋₁ ρout-ρin Diag\n", "--- --------------- --------- -------- ----\n", " 1 -2.769882858754 NaN 2.97e-01 8.0 \n", " 2 -2.771266481197 -1.38e-03 4.94e-02 1.0 \n", " 3 -2.771299562859 -3.31e-05 2.47e-03 1.0 \n", " 4 -2.771300359060 -7.96e-07 1.22e-04 2.0 \n", " 5 -2.771300362292 -3.23e-09 2.74e-05 2.0 \n" ] }, { "output_type": "execute_result", "data": { "text/plain": "0.017613524615732953" }, "metadata": {}, "execution_count": 3 } ], "cell_type": "code", "source": [ "ε = .01\n", "model_ε = model_LDA(lattice, atoms; extra_terms=[ExternalFromReal(r -> -ε * (r[1] - a/2))],\n", " symmetries=false)\n", "basis_ε = PlaneWaveBasis(model_ε, Ecut; kgrid=kgrid)\n", "res_ε = self_consistent_field(basis_ε, tol=tol)\n", "με = dipole(res_ε.ρ)" ], "metadata": {}, "execution_count": 3 }, { "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Reference dipole: -0.00013442502028186899\n", "Displaced dipole: 0.017613524615732953\n", "Polarizability : 1.7747949636014824\n" ] } ], "cell_type": "code", "source": [ "polarizability = (με - μref) / ε\n", "\n", "println(\"Reference dipole: $μref\")\n", "println(\"Displaced dipole: $με\")\n", "println(\"Polarizability : $polarizability\")" ], "metadata": {}, "execution_count": 4 }, { "cell_type": "markdown", "source": [ "The result on more converged grids is very close to published results.\n", "For example [DOI 10.1039/C8CP03569E](https://doi.org/10.1039/C8CP03569E)\n", "quotes **1.65** with LSDA and **1.38** with CCSD(T)." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Polarizability by linear response\n", "Now we use linear response to compute this analytically; we refer to standard\n", "textbooks for the formalism. In the following, ``\\chi_0`` is the\n", "independent-particle polarizability, and ``K`` the\n", "Hartree-exchange-correlation kernel. We denote with ``dV_{\\rm ext}`` an external\n", "perturbing potential (like in this case the uniform electric field). Then:\n", "$$\n", "d\\rho = \\chi_0 dV = \\chi_0 (dV_{\\rm ext} + K d\\rho),\n", "$$\n", "which implies\n", "$$\n", "d\\rho = (1-\\chi_0 K)^-1 \\chi_0 dV_{\\rm ext}.\n", "$$\n", "From this we identify the polarizability operator to be ``\\chi = (1-\\chi_0 K)^-1 \\chi_0``.\n", "Numerically, we apply ``\\chi`` to ``dV = -x`` by solving a linear equation\n", "(the Dyson equation) iteratively." ], "metadata": {} }, { "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WARNING: using KrylovKit.basis in module ##274 conflicts with an existing identifier.\n", "┌ Info: GMRES linsolve in iter 1; step 1: normres = 2.490924271746e-01\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:55\n", "┌ Info: GMRES linsolve in iter 1; step 2: normres = 3.769820423005e-03\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 1; step 3: normres = 3.105974308775e-04\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 1; step 4: normres = 4.722013722182e-06\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 1; step 5: normres = 1.300954567158e-07\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 1; step 6: normres = 1.848606581918e-09\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 1; step 7: normres = 1.096075226458e-11\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 1; step 8: normres = 2.127358457221e-13\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 1; finised at step 8: normres = 2.127358457221e-13\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:96\n", "┌ Info: GMRES linsolve in iter 2; step 1: normres = 1.235406912479e-06\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:55\n", "┌ Info: GMRES linsolve in iter 2; step 2: normres = 1.173663805660e-07\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 2; step 3: normres = 2.897721890735e-09\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 2; step 4: normres = 2.742469307574e-11\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 2; step 5: normres = 2.185095163252e-13\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 2; finised at step 5: normres = 2.185095163252e-13\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:96\n", "┌ Info: GMRES linsolve in iter 3; step 1: normres = 5.607169896923e-12\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:55\n", "┌ Info: GMRES linsolve in iter 3; step 2: normres = 3.120788690871e-13\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:89\n", "┌ Info: GMRES linsolve in iter 3; finised at step 2: normres = 3.120788690871e-13\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:96\n", "┌ Info: GMRES linsolve converged at iteration 3, step 2:\n", "│ * norm of residual = 3.107289549007794e-13\n", "│ * number of operations = 17\n", "└ @ KrylovKit /home/runner/.julia/packages/KrylovKit/OLgKs/src/linsolve/gmres.jl:127\n", "Non-interacting polarizability: 1.9255644004942734\n", "Interacting polarizability: 1.7734475083660768\n" ] } ], "cell_type": "code", "source": [ "using KrylovKit\n", "\n", "# KrylovKit cannot deal with the density as a 3D array, so we need `vec` to vectorize\n", "# and `devec` to \"unvectorize\"\n", "devec(arr) = from_real(basis, reshape(arr, size(res.ρ.real)))\n", "\n", "# Apply (1- χ0 K) to a vectorized dρ\n", "function dielectric_operator(dρ)\n", " dρ = devec(dρ)\n", " dv = apply_kernel(basis, dρ; ρ=res.ρ)\n", " χ0dv = apply_χ0(res.ham, res.ψ, res.εF, res.eigenvalues, dv...)[1]\n", " vec((dρ - χ0dv).real)\n", "end\n", "\n", "# dVext is the potential from a uniform field interacting with the dielectric dipole\n", "# of the density.\n", "dVext = from_real(basis, [-a * (r[1] - 1/2) for r in r_vectors(basis)])\n", "\n", "# Apply χ0 once to get non-interacting dipole\n", "dρ_nointeract = apply_χ0(res.ham, res.ψ, res.εF, res.eigenvalues, dVext)[1]\n", "\n", "# Solve Dyson equation to get interacting dipole\n", "dρ = devec(linsolve(dielectric_operator, vec(dρ_nointeract.real), verbosity=3)[1])\n", "\n", "println(\"Non-interacting polarizability: $(dipole(dρ_nointeract))\")\n", "println(\"Interacting polarizability: $(dipole(dρ))\")" ], "metadata": {}, "execution_count": 5 }, { "cell_type": "markdown", "source": [ "As expected, the interacting polarizability matches the finite difference\n", "result. The non-interacting polarizability is higher." ], "metadata": {} } ], "nbformat_minor": 3, "metadata": { "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.5.3" }, "kernelspec": { "name": "julia-1.5", "display_name": "Julia 1.5.3", "language": "julia" } }, "nbformat": 4 }