{ "cells": [ { "cell_type": "markdown", "source": [ "# Polarizability using automatic differentiation\n", "\n", "Simple example for computing properties using (forward-mode)\n", "automatic differentation.\n", "For a more classical approach and more details about computing polarizabilities,\n", "see Polarizability by linear response." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "using DFTK\n", "using LinearAlgebra\n", "using ForwardDiff\n", "\n", "# Construct PlaneWaveBasis given a particular electric field strength\n", "# Again we take the example of a Helium atom.\n", "function make_basis(ε::T; a=10., Ecut=30) where T\n", " lattice=T(a) * I(3) # lattice is a 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", " model = model_DFT(lattice, atoms, [:lda_x, :lda_c_vwn];\n", " extra_terms=[ExternalFromReal(r -> -ε * (r[1] - a/2))],\n", " symmetries=false)\n", " PlaneWaveBasis(model; Ecut, kgrid=[1, 1, 1]) # No k-point sampling on isolated system\n", "end\n", "\n", "# dipole moment of a given density (assuming the current geometry)\n", "function dipole(basis, ρ)\n", " @assert isdiag(basis.model.lattice)\n", " a = basis.model.lattice[1, 1]\n", " rr = [a * (r[1] - 1/2) for r in r_vectors(basis)]\n", " sum(rr .* ρ) * basis.dvol\n", "end\n", "\n", "# Function to compute the dipole for a given field strength\n", "function compute_dipole(ε; tol=1e-8, kwargs...)\n", " scfres = self_consistent_field(make_basis(ε; kwargs...), tol=tol)\n", " dipole(scfres.basis, scfres.ρ)\n", "end;" ], "metadata": {}, "execution_count": 1 }, { "cell_type": "markdown", "source": [ "With this in place we can compute the polarizability from finite differences\n", "(just like in the previous example):" ], "metadata": {} }, { "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "n Energy Eₙ-Eₙ₋₁ ρout-ρin α Diag\n", "--- --------------- --------- -------- ---- ----\n", " 1 -2.770678846504 NaN 2.97e-01 0.80 8.0\n", " 2 -2.772137366142 -1.46e-03 4.99e-02 0.80 1.0\n", " 3 -2.772170583186 -3.32e-05 2.41e-03 0.80 2.0\n", " 4 -2.772170722157 -1.39e-07 6.85e-05 0.80 2.0\n", " 5 -2.772170722987 -8.30e-10 1.70e-05 0.80 3.0\n", "n Energy Eₙ-Eₙ₋₁ ρout-ρin α Diag\n", "--- --------------- --------- -------- ---- ----\n", " 1 -2.770778324926 NaN 2.98e-01 0.80 8.0\n", " 2 -2.772055607034 -1.28e-03 4.87e-02 0.80 1.0\n", " 3 -2.772083022243 -2.74e-05 2.78e-03 0.80 1.0\n", " 4 -2.772083399965 -3.78e-07 3.20e-04 0.80 2.0\n", " 5 -2.772083415187 -1.52e-08 1.46e-04 0.80 2.0\n", " 6 -2.772083417777 -2.59e-09 1.32e-05 0.80 2.0\n" ] }, { "output_type": "execute_result", "data": { "text/plain": "1.7738451891157154" }, "metadata": {}, "execution_count": 2 } ], "cell_type": "code", "source": [ "polarizability_fd = let\n", " ε = 0.01\n", " (compute_dipole(ε) - compute_dipole(0.0)) / ε\n", "end" ], "metadata": {}, "execution_count": 2 }, { "cell_type": "markdown", "source": [ "## Forward-mode implicit differentiation\n", "\n", "Right now DFTK has no out-of-the-box support for implicit differentiation through the SCF.\n", "However one can easily work around this as follows. We keep both a non-dual basis\n", "and a basis including duals for easy bookkeeping (but redundant computation ...)." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function self_consistent_field_dual(basis::PlaneWaveBasis, basis_dual::PlaneWaveBasis{T};\n", " kwargs...) where T <: ForwardDiff.Dual\n", " scfres = self_consistent_field(basis; kwargs...)\n", " ψ = DFTK.select_occupied_orbitals(basis, scfres.ψ)\n", " filled_occ = DFTK.filled_occupation(basis.model)\n", " n_spin = basis.model.n_spin_components\n", " n_bands = div(basis.model.n_electrons, n_spin * filled_occ)\n", " occupation = [filled_occ * ones(n_bands) for _ in basis.kpoints]\n", "\n", " # promote everything eagerly to Dual numbers\n", " occupation_dual = [T.(occupation[1])]\n", " ψ_dual = [Complex.(T.(real(ψ[1])), T.(imag(ψ[1])))]\n", " ρ_dual = compute_density(basis_dual, ψ_dual, occupation_dual)\n", "\n", " _, δH = energy_hamiltonian(basis_dual, ψ_dual, occupation_dual; ρ=ρ_dual)\n", " δHψ = δH * ψ_dual\n", " δHψ = [ForwardDiff.partials.(δHψ[1], 1)]\n", " δψ = DFTK.solve_ΩplusK(basis, ψ, -δHψ, occupation)\n", " δρ = DFTK.compute_δρ(basis, ψ, δψ, occupation)\n", " ρ = ForwardDiff.value.(ρ_dual)\n", " ψ, ρ, δψ, δρ\n", "end;" ], "metadata": {}, "execution_count": 3 }, { "cell_type": "markdown", "source": [ "This function is now used in the following to provide a dual version\n", "for the compute_dipole function:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function compute_dipole(ε::ForwardDiff.Dual; tol=1e-8, kwargs...)\n", " T = ForwardDiff.tagtype(ε)\n", " basis = make_basis(ForwardDiff.value(ε); kwargs...)\n", " basis_dual = make_basis(ε; kwargs...)\n", " ψ, ρ, δψ, δρ = self_consistent_field_dual(basis, basis_dual; tol)\n", " ρ_dual = ForwardDiff.Dual{T}.(ρ, δρ)\n", " dipole(basis_dual, ρ_dual)\n", "end;" ], "metadata": {}, "execution_count": 4 }, { "cell_type": "markdown", "source": [ "This setup allows to compute the polarizability via automatic differentiation:" ], "metadata": {} }, { "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "n Energy Eₙ-Eₙ₋₁ ρout-ρin α Diag\n", "--- --------------- --------- -------- ---- ----\n", " 1 -2.770779143543 NaN 2.98e-01 0.80 8.0\n", " 2 -2.772051054186 -1.27e-03 4.85e-02 0.80 1.0\n", " 3 -2.772083267433 -3.22e-05 2.97e-03 0.80 2.0\n", " 4 -2.772083416404 -1.49e-07 9.22e-05 0.80 2.0\n", " 5 -2.772083417729 -1.33e-09 2.89e-05 0.80 2.0\n", "\n", "Polarizability via ForwardDiff: 1.772564556461622\n", "Polarizability via finite difference: 1.7738451891157154\n" ] } ], "cell_type": "code", "source": [ "polarizability = ForwardDiff.derivative(compute_dipole, 0.0)\n", "println()\n", "println(\"Polarizability via ForwardDiff: $polarizability\")\n", "println(\"Polarizability via finite difference: $polarizability_fd\")" ], "metadata": {}, "execution_count": 5 } ], "nbformat_minor": 3, "metadata": { "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.7.0" }, "kernelspec": { "name": "julia-1.7", "display_name": "Julia 1.7.0", "language": "julia" } }, "nbformat": 4 }