{ "cells": [ { "cell_type": "markdown", "source": [ "# Custom solvers\n", "In this example, we show how to define custom solvers. Our system\n", "will again be silicon, because we are not very imaginative" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "using DFTK, LinearAlgebra\n", "\n", "a = 10.26\n", "lattice = a / 2 * [[0 1 1.];\n", " [1 0 1.];\n", " [1 1 0.]]\n", "Si = ElementPsp(:Si, psp=load_psp(\"hgh/lda/Si-q4\"))\n", "atoms = [Si => [ones(3)/8, -ones(3)/8]]\n", "\n", "# We take very (very) crude parameters\n", "model = model_LDA(lattice, atoms)\n", "kgrid = [1, 1, 1]\n", "Ecut = 5\n", "basis = PlaneWaveBasis(model, Ecut; kgrid=kgrid);" ], "metadata": {}, "execution_count": 1 }, { "cell_type": "markdown", "source": [ "We define our custom fix-point solver: simply a damped fixed-point" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function my_fp_solver(f, x0, max_iter; tol)\n", " mixing_factor = .7\n", " x = x0\n", " fx = f(x)\n", " for n = 1:max_iter\n", " inc = fx - x\n", " if norm(inc) < tol\n", " break\n", " end\n", " x = x + mixing_factor * inc\n", " fx = f(x)\n", " end\n", " (fixpoint=x, converged=norm(fx-x) < tol)\n", "end;" ], "metadata": {}, "execution_count": 2 }, { "cell_type": "markdown", "source": [ "Our eigenvalue solver just forms the dense matrix and diagonalizes\n", "it explicitly (this only works for very small systems)" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "function my_eig_solver(A, X0; maxiter, tol, kwargs...)\n", " n = size(X0, 2)\n", " A = Array(A)\n", " E = eigen(A)\n", " λ = E.values[1:n]\n", " X = E.vectors[:, 1:n]\n", " (λ=λ, X=X, residual_norms=[], iterations=0, converged=true, n_matvec=0)\n", "end;" ], "metadata": {}, "execution_count": 3 }, { "cell_type": "markdown", "source": [ "Finally we also define our custom mixing scheme. It will be a mixture\n", "of simple mixing (for the first 2 steps) and than default to Kerker mixing.\n", "In the mixing interface `δF` is ``(ρ_\\text{out} - ρ_\\text{in})``, i.e.\n", "the difference in density between two subsequent SCF steps and the `mix`\n", "function returns ``δρ``, which is added to ``ρ_\\text{in}`` to yield ``ρ_\\text{next}``,\n", "the density for the next SCF step." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "struct MyMixing\n", " α # Damping parameter\n", "end\n", "MyMixing() = MyMixing(0.7)\n", "\n", "function DFTK.mix(mixing::MyMixing, basis, δF::RealFourierArray, δF_spin=nothing; n_iter, kwargs...)\n", " if n_iter <= 2\n", " # Just do simple mixing on total density and spin density (if it exists)\n", " (mixing.α * δF, isnothing(δF_spin) ? nothing : mixing.α * δF_spin)\n", " else\n", " # Use the KerkerMixing from DFTK\n", " DFTK.mix(KerkerMixing(α=mixing.α), basis, δF, δF_spin; kwargs...)\n", " end\n", "end" ], "metadata": {}, "execution_count": 4 }, { "cell_type": "markdown", "source": [ "That's it! Now we just run the SCF with these solvers" ], "metadata": {} }, { "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "n Energy Eₙ-Eₙ₋₁ ρout-ρin Diag\n", "--- --------------- --------- -------- ----\n", " 1 -7.200034051584 NaN 3.43e-01 0.0 \n", " 2 -7.242360972977 -4.23e-02 1.66e-01 0.0 \n", " 3 -7.248323239800 -5.96e-03 7.24e-02 0.0 \n", " 4 -7.248939855828 -6.17e-04 3.97e-02 0.0 \n", " 5 -7.249130570912 -1.91e-04 2.20e-02 0.0 \n", " 6 -7.249189473889 -5.89e-05 1.23e-02 0.0 \n", " 7 -7.249207938905 -1.85e-05 7.01e-03 0.0 \n", " 8 -7.249213872539 -5.93e-06 4.04e-03 0.0 \n", " 9 -7.249215839064 -1.97e-06 2.37e-03 0.0 \n", " 10 -7.249216513283 -6.74e-07 1.41e-03 0.0 \n", " 11 -7.249216752480 -2.39e-07 8.47e-04 0.0 \n", " 12 -7.249216840125 -8.76e-08 5.17e-04 0.0 \n", " 13 -7.249216873178 -3.31e-08 3.19e-04 0.0 \n", " 14 -7.249216885954 -1.28e-08 1.98e-04 0.0 \n", " 15 -7.249216890992 -5.04e-09 1.24e-04 0.0 \n" ] } ], "cell_type": "code", "source": [ "scfres = self_consistent_field(basis;\n", " tol=1e-8,\n", " solver=my_fp_solver,\n", " eigensolver=my_eig_solver,\n", " mixing=MyMixing());" ], "metadata": {}, "execution_count": 5 }, { "cell_type": "markdown", "source": [ "Note that the default convergence criterion is on the difference of\n", "energy from one step to the other; when this gets below `tol`, the\n", "\"driver\" `self_consistent_field` artificially makes the fixpoint\n", "solver think it's converged by forcing `f(x) = x`. You can customize\n", "this with the `is_converged` keyword argument to\n", "`self_consistent_field`." ], "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 }