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

Warning

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)")

Note

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)")

Note

c1 is again a closure, so wrap it in Duplicated. Vector mode (chunked) differentiation is not supported and hence disabled by the keyword argument chunk=Val(1).