Extensions
ClassicalDFT.jl provides some extensions, which make it interoperable with other Julia packages and which enable additional functionality if these packages are installed and loaded.
Plots
An extension of Plots provides a specialization of the function plot, which simplifies plotting a one-body profile (e.g. the density) in a given geometry. See the Examples for usage.
Additionally, PlotCallback may be used in solve to display an automatically updating plot of the density profile during the minimization.
FixedPointAcceleration
The extension of FixedPointAcceleration makes the fixed-point iteration algorithms implemented by this package available for usage in solve.
ForwardDiff
The ForwardDiff extension enables the evaluation of FMT functionals with forward mode automatic differentiation, see Fundamental measure theory (FMT) functionals for further details.
Example
using ForwardDiff
using Plots
using ClassicalDFT
L, dz = 10, 0.01
T, μ = 1.0, 3.0
geom = PlanarGeometry(L, dz; boundary=:walls)
functional_analytic = RosenfeldFunctional(geom; dΦ_mode=:analytic) # is the default
ρ_analytic = solve(functional_analytic; T, μ).ρ
functional_ForwardDiff = RosenfeldFunctional(geom; dΦ_mode=:ForwardDiff)
ρ_ForwardDiff = solve(functional_ForwardDiff; T, μ).ρ
plot(geom, ρ_analytic, label="ρ (dΦ_mode=:analytic)")
plot!(geom, ρ_ForwardDiff, label="ρ (dΦ_mode=:ForwardDiff)")[ Info: starting iteration
[ Info: converged after 388 iterations (‖ΔρEL‖ = 9.988906479740933e-9 < 1.0e-8)
[ Info: starting iteration
[ Info: converged after 388 iterations (‖ΔρEL‖ = 9.98890636871863e-9 < 1.0e-8)Enzyme
Enzyme support is somewhat experimental at the moment. In particular, Enzyme can be very finicky if it encounters code which is type-unstable or otherwise hard to compile/differentiate. Implementations of the provided functionals try to work around some peculiarities of Enzyme as much as possible, but things might still break occasionally.
The Enzyme extension enables forward and backward mode automatic differentiation, which is especially useful to carry out functional calculus.
Examples
Recall that $c_1(\vec{r}; [\rho]) = - \delta \beta F_\mathrm{exc}[\rho] / \delta \rho(\vec{r})$. Let's check if the analytically implemented one-body direct correlation functional matches the functional derivative of the excess free energy functional, whereby we compute the functional derivative using reverse mode automatic differentiation:
using Enzyme
using Plots
using ClassicalDFT
L, dx = 10, 0.01
geom = PlanarGeometry(L, dx)
zs = coordinate_axes(geom)[1]
functional = RosenfeldFunctional(geom)
Fexc = Fexc_func(functional)
c1 = c1_func(functional)
ρ = zeros(coordinate_size(geom)) .+ 0.4 .+ 0.1 .* sin.(2π / L * zs) # some density profile
c1_analytic = c1(ρ)
c1_autodiff = -gradient(Reverse, Duplicated(Fexc, make_zero(Fexc)), ρ)[1] / dx
plot(geom, c1_analytic, label="c1 (analytic)")
plot!(geom, c1_autodiff, label="c1 (autodiff)")Due to the way Enzyme evaluates derivatives, closures (such as Fexc in the above code) must be wrapped in Duplicated in order to handle captured variables correctly.
In a similar fashion, differentiating $c_1(\vec{r}; [\rho])$ with respect to the density profile yields the two-body direct correlation functional $c_2(\vec{r}, \vec{r}'; [\rho]) = \delta c_1(\vec{r}; [\rho]) / \delta \rho(\vec{r}')$. One may use either reverse or forward mode for this, although forward mode is usually more performant when differentiating a function with vector input and output values of equal size.
using Enzyme
using Plots
using ClassicalDFT
L, dx = 3, 0.01
geom = PlanarGeometry(L, dx)
zs = coordinate_axes(geom)[1]
functional = RosenfeldFunctional(geom)
c1 = c1_func(functional)
ρ = zeros(coordinate_size(geom)) .+ 0.4 # constant bulk density
# c2_autodiff = jacobian(Reverse, Duplicated(c1, make_zero(c1)), ρ; chunk=Val(1))[1] / dx
c2_autodiff = jacobian(Forward, Duplicated(c1, make_zero(c1)), ρ; chunk=Val(1))[1] / dx
heatmap(zs, zs, c2_autodiff, aspect_ratio=1, xlabel="z", ylabel="z'", colorbar_title="c2 (autodiff)")