From 12626817f397012971138e0937a532a0f731513c Mon Sep 17 00:00:00 2001 From: James Gardner Date: Wed, 22 Jun 2022 13:24:27 +0100 Subject: [PATCH 1/3] Add discretisation methods --- src/diabatic/DiabaticModels.jl | 8 + src/diabatic/wide_band_bath_discretisation.jl | 162 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/diabatic/wide_band_bath_discretisation.jl diff --git a/src/diabatic/DiabaticModels.jl b/src/diabatic/DiabaticModels.jl index c6ea421..17f66e6 100644 --- a/src/diabatic/DiabaticModels.jl +++ b/src/diabatic/DiabaticModels.jl @@ -161,4 +161,12 @@ export ErpenbeckThoss include("widebandbath.jl") export WideBandBath +include("wide_band_bath_discretisation.jl") +export ShenviGaussLegendre +export GaussLegendreReferenceImplementation +export GaussLegendre +export RiemannSum +export TrapezoidalRule +export FullGaussLegendre + end # module diff --git a/src/diabatic/wide_band_bath_discretisation.jl b/src/diabatic/wide_band_bath_discretisation.jl new file mode 100644 index 0000000..8a75b23 --- /dev/null +++ b/src/diabatic/wide_band_bath_discretisation.jl @@ -0,0 +1,162 @@ +using FastGaussQuadrature: gausslegendre + +abstract type WideBandBathDiscretisation end +NQCModels.nstates(bath::WideBandBathDiscretisation) = length(bath.bathstates) + +function fillbathstates!(out::Hermitian, bath::WideBandBathDiscretisation) + diagonal = view(out, diagind(out)[2:end]) + copy!(diagonal, bath.bathstates) +end + +function fillbathcoupling!(out::Hermitian, coupling::Real, bath::WideBandBathDiscretisation) + first_column = @view out.data[2:end, 1] + setcoupling!(first_column, bath.bathcoupling, coupling) + first_row = @view out.data[1, 2:end] + copy!(first_row, first_column) +end + +function setcoupling!(out::AbstractVector, bathcoupling::AbstractVector, coupling::Real) + out .= bathcoupling .* coupling +end + +function setcoupling!(out::AbstractVector, bathcoupling::Real, coupling::Real) + fill!(out, bathcoupling * coupling) +end + +struct TrapezoidalRule{B,T} <: WideBandBathDiscretisation + bathstates::B + bathcoupling::T +end + +function TrapezoidalRule(M, bandmin, bandmax) + ΔE = bandmax - bandmin + bathstates = range(bandmin, bandmax, length=M) + coupling = sqrt(ΔE / M) + return TrapezoidalRule(bathstates, coupling) +end + +""" + ShenviGaussLegendre{T} + +Defined exactly as described by Shenvi et al. in J. Chem. Phys. 130, 174107 (2009). +""" +struct ShenviGaussLegendre{T} <: WideBandBathDiscretisation + bathstates::Vector{T} + bathcoupling::Vector{T} +end + +function ShenviGaussLegendre(M, bandmin, bandmax) + M % 2 == 0 || throw(error("The number of states `M` must be even.")) + knots, weights = gausslegendre(div(M, 2)) + ΔE = bandmax - bandmin + + bathstates = zeros(M) + for i in eachindex(knots) + bathstates[i] = -ΔE/2 * (1/2 + knots[i]/2) + end + for i in eachindex(knots) + bathstates[i+length(knots)] = ΔE/2 * (1/2 + knots[i]/2) + end + + bathcoupling = zeros(M) + for (i, w) in zip(eachindex(bathcoupling), Iterators.cycle(weights)) + bathcoupling[i] = sqrt(ΔE * w) / 2 + end + + return ShenviGaussLegendre(bathstates, bathcoupling) +end + +""" + GaussLegendreReferenceImplementation{T} + +Implementation translated from Fortran code used for simulations of Shenvi et al. in J. Chem. Phys. 130, 174107 (2009). +Two differences from ShenviGaussLegendre: +- Position of minus sign in energy levels has been corrected. +- Division by sqrt(ΔE) in the coupling. +""" +struct GaussLegendreReferenceImplementation{T} <: WideBandBathDiscretisation + bathstates::Vector{T} + bathcoupling::Vector{T} +end + +function GaussLegendreReferenceImplementation(M, bandmin, bandmax) + M % 2 == 0 || throw(error("The number of states `M` must be even.")) + knots, weights = gausslegendre(div(M, 2)) + ΔE = bandmax - bandmin + + bathstates = zeros(M) + for i in eachindex(knots) + bathstates[i] = ΔE/2 * (-1/2 + knots[i]/2) + end + for i in eachindex(knots) + bathstates[i+length(knots)] = ΔE/2 * (1/2 + knots[i]/2) + end + + bathcoupling = zeros(M) + for (i, w) in zip(eachindex(bathcoupling), Iterators.cycle(weights)) + bathcoupling[i] = sqrt(w) / 2 + end + + return GaussLegendreReferenceImplementation(bathstates, bathcoupling) +end + +struct FullGaussLegendre{T} <: WideBandBathDiscretisation + bathstates::Vector{T} + bathcoupling::Vector{T} +end + +function FullGaussLegendre(M, bandmin, bandmax) + + knots, weights = gausslegendre(M) + ΔE = bandmax - bandmin + + bathstates = zeros(M) + for i in eachindex(knots, bathstates) + bathstates[i] = ΔE/2 * knots[i] + end + + bathcoupling = zeros(M) + for i in eachindex(bathcoupling, weights) + bathcoupling[i] = sqrt(weights[i] * ΔE/2) + end + + return FullGaussLegendre(bathstates, bathcoupling) +end + +struct GaussLegendre{T} <: WideBandBathDiscretisation + bathstates::Vector{T} + bathcoupling::Vector{T} +end + +function GaussLegendre(M, bandmin, bandmax) + M % 2 == 0 || throw(error("The number of states `M` must be even.")) + knots, weights = gausslegendre(div(M, 2)) + ΔE = bandmax - bandmin + + bathstates = zeros(M) + for i in eachindex(knots) + bathstates[i] = ΔE^2/16 * weights[i] * (knots[i] - 1) + end + for i in eachindex(knots) + bathstates[i+length(knots)] = ΔE^2/16 * weights[i] * (knots[i] + 1) + end + + bathcoupling = zeros(M) + for (i, w) in zip(eachindex(bathcoupling), Iterators.cycle(weights)) + bathcoupling[i] = ΔE * w / 4 + end + + return GaussLegendre(bathstates, bathcoupling) +end + +struct RiemannSum{B,T} <: WideBandBathDiscretisation + bathstates::B + bathcoupling::T +end + +function RiemannSum(M, bandmin, bandmax) + ΔE = bandmax - bandmin + bathstates = range(bandmin, bandmax, length=M) .* ΔE / M + coupling = ΔE / M + return RiemannSum(bathstates, coupling) +end From cffc640e00e2975e32638c95a9bb4a3ca01439b3 Mon Sep 17 00:00:00 2001 From: James Gardner Date: Wed, 22 Jun 2022 13:27:36 +0100 Subject: [PATCH 2/3] Add FastGaussQuadrature --- Project.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Project.toml b/Project.toml index 2a2ac8b..3e2c0f7 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["James Gardner "] version = "0.8.10" [deps] +FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" NQCBase = "78c76ebc-5665-4934-b512-82d81b5cbfb7" @@ -18,6 +19,7 @@ UnitfulAtomic = "a7773ee8-282e-5fa2-be4e-bd808c38a91a" UnitfulRecipes = "42071c24-d89e-48dd-8a24-8a12d9b8861f" [compat] +FastGaussQuadrature = "0.4" ForwardDiff = "0.10" NQCBase = "0.1, 0.2" Parameters = "0.12" From 13197aab58b31dc0c2e550132850151e577580a8 Mon Sep 17 00:00:00 2001 From: James Gardner Date: Wed, 29 Jun 2022 12:21:09 +0100 Subject: [PATCH 3/3] Add Anderson-Holstein model --- src/diabatic/DiabaticModels.jl | 9 +-- src/diabatic/anderson_holstein.jl | 43 +++++++++++++ src/diabatic/wide_band_bath_discretisation.jl | 64 ++++++------------- test/runtests.jl | 6 ++ test/wide_band_bath_discretisations.jl | 47 ++++++++++++++ 5 files changed, 121 insertions(+), 48 deletions(-) create mode 100644 src/diabatic/anderson_holstein.jl create mode 100644 test/wide_band_bath_discretisations.jl diff --git a/src/diabatic/DiabaticModels.jl b/src/diabatic/DiabaticModels.jl index 17f66e6..aa185c5 100644 --- a/src/diabatic/DiabaticModels.jl +++ b/src/diabatic/DiabaticModels.jl @@ -162,11 +162,12 @@ include("widebandbath.jl") export WideBandBath include("wide_band_bath_discretisation.jl") -export ShenviGaussLegendre -export GaussLegendreReferenceImplementation -export GaussLegendre -export RiemannSum export TrapezoidalRule +export ShenviGaussLegendre +export ReferenceGaussLegendre export FullGaussLegendre +include("anderson_holstein.jl") +export AndersonHolstein + end # module diff --git a/src/diabatic/anderson_holstein.jl b/src/diabatic/anderson_holstein.jl new file mode 100644 index 0000000..4b7b3b0 --- /dev/null +++ b/src/diabatic/anderson_holstein.jl @@ -0,0 +1,43 @@ + +struct AndersonHolstein{M<:DiabaticModel,B} <: LargeDiabaticModel + model::M + bath::B +end + +NQCModels.nstates(model::AndersonHolstein) = NQCModels.nstates(model.bath) + 1 +NQCModels.ndofs(model::AndersonHolstein) = NQCModels.ndofs(model.model) +NQCModels.nelectrons(model::AndersonHolstein) = fld(NQCModels.nstates(model.bath), 2) +NQCModels.fermilevel(::AndersonHolstein) = 0 + +function NQCModels.potential!(model::AndersonHolstein, V::Hermitian, r::AbstractMatrix) + Vsystem = NQCModels.potential(model.model, r) + V[1,1] = Vsystem[2,2] - Vsystem[1,1] + fillbathstates!(V, model.bath) + fillbathcoupling!(V, Vsystem[2,1], model.bath) + return V +end + +function NQCModels.derivative!(model::AndersonHolstein, D::AbstractMatrix{<:Hermitian}, r::AbstractMatrix) + + Dsystem = NQCModels.derivative(model.model, r) + + for I in eachindex(Dsystem, D) + D[I][1,1] = Dsystem[I][2,2] - Dsystem[I][1,1] + fillbathcoupling!(D[I], Dsystem[I][2,1], model.bath) + end + + return D +end + +function NQCModels.state_independent_potential(model::AndersonHolstein, r::AbstractMatrix) + Vsystem = NQCModels.potential(model.model, r) + return Vsystem[1,1] +end + +function NQCModels.state_independent_derivative!(model::AndersonHolstein, ∂V::AbstractMatrix, r::AbstractMatrix) + Dsystem = NQCModels.derivative(model.model, r) + for I in eachindex(∂V, Dsystem) + ∂V[I] = Dsystem[I][1,1] + end + return ∂V +end diff --git a/src/diabatic/wide_band_bath_discretisation.jl b/src/diabatic/wide_band_bath_discretisation.jl index 8a75b23..a606094 100644 --- a/src/diabatic/wide_band_bath_discretisation.jl +++ b/src/diabatic/wide_band_bath_discretisation.jl @@ -23,6 +23,12 @@ function setcoupling!(out::AbstractVector, bathcoupling::Real, coupling::Real) fill!(out, bathcoupling * coupling) end +""" + TrapezoidalRule{B,T} <: WideBandBathDiscretisation + +Discretise wide band continuum using trapezoidal rule. +Leads to evenly spaced states and constant coupling. +""" struct TrapezoidalRule{B,T} <: WideBandBathDiscretisation bathstates::B bathcoupling::T @@ -38,7 +44,8 @@ end """ ShenviGaussLegendre{T} -Defined exactly as described by Shenvi et al. in J. Chem. Phys. 130, 174107 (2009). +Defined as described by Shenvi et al. in J. Chem. Phys. 130, 174107 (2009). +The position of the negative sign for the state energy level has been moved to ensure the states are sorted from lowest to highest. """ struct ShenviGaussLegendre{T} <: WideBandBathDiscretisation bathstates::Vector{T} @@ -52,7 +59,7 @@ function ShenviGaussLegendre(M, bandmin, bandmax) bathstates = zeros(M) for i in eachindex(knots) - bathstates[i] = -ΔE/2 * (1/2 + knots[i]/2) + bathstates[i] = ΔE/2 * (-1/2 + knots[i]/2) end for i in eachindex(knots) bathstates[i+length(knots)] = ΔE/2 * (1/2 + knots[i]/2) @@ -67,20 +74,21 @@ function ShenviGaussLegendre(M, bandmin, bandmax) end """ - GaussLegendreReferenceImplementation{T} + ReferenceGaussLegendre{T} Implementation translated from Fortran code used for simulations of Shenvi et al. in J. Chem. Phys. 130, 174107 (2009). Two differences from ShenviGaussLegendre: - Position of minus sign in energy levels has been corrected. - Division by sqrt(ΔE) in the coupling. """ -struct GaussLegendreReferenceImplementation{T} <: WideBandBathDiscretisation +struct ReferenceGaussLegendre{T} <: WideBandBathDiscretisation bathstates::Vector{T} bathcoupling::Vector{T} end -function GaussLegendreReferenceImplementation(M, bandmin, bandmax) +function ReferenceGaussLegendre(M, bandmin, bandmax) M % 2 == 0 || throw(error("The number of states `M` must be even.")) + @warn "(ReferenceGaussLegendre) The division by sqrt(ΔE) in the coupling is incorrect. Use ShenviGaussLegendre instead." knots, weights = gausslegendre(div(M, 2)) ΔE = bandmax - bandmin @@ -97,9 +105,15 @@ function GaussLegendreReferenceImplementation(M, bandmin, bandmax) bathcoupling[i] = sqrt(w) / 2 end - return GaussLegendreReferenceImplementation(bathstates, bathcoupling) + return ReferenceGaussLegendre(bathstates, bathcoupling) end +""" + FullGaussLegendre{T} <: WideBandBathDiscretisation + +Use Gauss-Legendre quadrature to discretise the continuum across the entire band width. +This is similar to the ShenviGaussLegendre except that splits the continuum at the Fermi level into two halves. +""" struct FullGaussLegendre{T} <: WideBandBathDiscretisation bathstates::Vector{T} bathcoupling::Vector{T} @@ -122,41 +136,3 @@ function FullGaussLegendre(M, bandmin, bandmax) return FullGaussLegendre(bathstates, bathcoupling) end - -struct GaussLegendre{T} <: WideBandBathDiscretisation - bathstates::Vector{T} - bathcoupling::Vector{T} -end - -function GaussLegendre(M, bandmin, bandmax) - M % 2 == 0 || throw(error("The number of states `M` must be even.")) - knots, weights = gausslegendre(div(M, 2)) - ΔE = bandmax - bandmin - - bathstates = zeros(M) - for i in eachindex(knots) - bathstates[i] = ΔE^2/16 * weights[i] * (knots[i] - 1) - end - for i in eachindex(knots) - bathstates[i+length(knots)] = ΔE^2/16 * weights[i] * (knots[i] + 1) - end - - bathcoupling = zeros(M) - for (i, w) in zip(eachindex(bathcoupling), Iterators.cycle(weights)) - bathcoupling[i] = ΔE * w / 4 - end - - return GaussLegendre(bathstates, bathcoupling) -end - -struct RiemannSum{B,T} <: WideBandBathDiscretisation - bathstates::B - bathcoupling::T -end - -function RiemannSum(M, bandmin, bandmax) - ΔE = bandmax - bandmin - bathstates = range(bandmin, bandmax, length=M) .* ΔE / M - coupling = ΔE / M - return RiemannSum(bathstates, coupling) -end diff --git a/test/runtests.jl b/test/runtests.jl index dd47566..0bfab93 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,9 @@ using NQCBase using NQCModels using LinearAlgebra using FiniteDiff +using SafeTestsets + +@time @safetestset "Wide band bath discretisations" begin include("wide_band_bath_discretisations.jl") end function finite_difference_gradient(model::NQCModels.AdiabaticModels.AdiabaticModel, R) f(x) = potential(model, x) @@ -91,6 +94,9 @@ end @test test_model(ErpenbeckThoss(Γ=2.0), 1) @test test_model(WideBandBath(ErpenbeckThoss(Γ=2.0); step=0.1, bandmin=-1.0, bandmax=1.0), 1) @test test_model(WideBandBath(GatesHollowayElbow(); step=0.1, bandmin=-1.0, bandmax=1.0), 2) + @test test_model(AndersonHolstein(ErpenbeckThoss(Γ=2.0), TrapezoidalRule(10, -1, 1)), 1) + @test test_model(AndersonHolstein(ErpenbeckThoss(Γ=2.0), ShenviGaussLegendre(10, -1, 1)), 1) + @test test_model(AndersonHolstein(GatesHollowayElbow(), ShenviGaussLegendre(10, -1, 1)), 2) end @testset "FrictionModels" begin diff --git a/test/wide_band_bath_discretisations.jl b/test/wide_band_bath_discretisations.jl new file mode 100644 index 0000000..1b0388f --- /dev/null +++ b/test/wide_band_bath_discretisations.jl @@ -0,0 +1,47 @@ +using Test +using NQCModels + +using Unitful +using LinearAlgebra: Hermitian, diagind + +@testset "TrapezoidalRule" begin + bath = NQCModels.TrapezoidalRule(50, -10, 10) + n = NQCModels.nstates(bath) + out = Hermitian(zeros(n+1, n+1)) + + NQCModels.DiabaticModels.fillbathstates!(out, bath) + @test all(out[diagind(out)[2:end]] .== bath.bathstates) + NQCModels.DiabaticModels.fillbathcoupling!(out, 1.0, bath) + @test all(isapprox.(out[1,2:end], 1.0 / sqrt(50/20))) + @test all(isapprox.(out[2:end,1], 1.0 / sqrt(50/20))) +end + +@testset "ShenviGaussLegendre" begin + bath = NQCModels.ShenviGaussLegendre(20, -10, 10.0) + + n = NQCModels.nstates(bath) + out = Hermitian(zeros(n+1, n+1)) + + NQCModels.DiabaticModels.fillbathstates!(out, bath) + NQCModels.DiabaticModels.fillbathcoupling!(out, 1.0, bath) +end + +@testset "ReferenceGaussLegendre" begin + bath = NQCModels.ReferenceGaussLegendre(20, -10, 10.0) + + n = NQCModels.nstates(bath) + out = Hermitian(zeros(n+1, n+1)) + + NQCModels.DiabaticModels.fillbathstates!(out, bath) + NQCModels.DiabaticModels.fillbathcoupling!(out, 1.0, bath) +end + +@testset "FullGaussLegendre" begin + bath = NQCModels.FullGaussLegendre(20, -10, 10.0) + + n = NQCModels.nstates(bath) + out = Hermitian(zeros(n+1, n+1)) + + NQCModels.DiabaticModels.fillbathstates!(out, bath) + NQCModels.DiabaticModels.fillbathcoupling!(out, 1.0, bath) +end