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 via FixedPointAccelerationJLSolver.
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
$c_1(\vec{r}; [\rho])$ from $F_\mathrm{exc}[\rho]$
Recall that $c_1(\vec{r}; [\rho]) = - \delta \beta F_\mathrm{exc}[\rho] / \delta \rho(\vec{r})$. Let's check for the RosenfeldFunctional if the analytically implemented one-body direct correlation functional satisfies this relation. We thereby 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.
For convenience, the Enzyme extension specializes c1!_func. Calling c1!_func(Reverse, functional) returns a function for evaluating $c_1(\vec{r}; [\rho])$ in-place using automatic differentiation of $F_\mathrm{exc}[\rho]$ as defined by the given functional.
Using Enzyme in solve
Once $F_\mathrm{exc}[\rho]$ is implemented, automatic differentiation yields access to $c_1(\vec{r}; [\rho])$ without any further analytic derivations or implementations. To utilize this feature in the self-consistent solution of the Euler-Lagrange equations, one can specify in solve how to evaluate $c_1(\vec{r}; [\rho])$ via the keyword argument c1!. The following code demonstrates how to select reverse-mode automatic differentiation of $F_\mathrm{exc}[\rho]$ as the method to be utilized:
using Enzyme
using Plots
using ClassicalDFT
L, dz = 10, 0.01
T, μ = 1.0, 3.0
geom = CartesianGeometry{1}(L, dz; boundary=:walls)
functional = PercusFunctional(geom)
ρ = solve(functional; T, μ, c1! = c1!_func(Reverse, functional)).ρ
plot(geom, ρ, label="ρ")[ Info: starting iteration
[ Info: converged after 659 iterations (‖ΔρEL‖ = 9.966426794960626e-9 < 1.0e-8)Automatic differentiation with Enzyme generally incurs upfront compilation, such that the self-consistent iteration in solve might not start immediately. However, after this initial compilation time, the evaluation of automatically differentiated functions should be very performant and the iteration should progress as usual.
Higher-order correlation functions
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, but note that 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)")