Metadensity Functional Learning for Classical Fluids: Regularizing with Pair Correlations.
The 2 matches
- [1] § Metadensity Functional Learning › Regularized Metadensity Functional Learning ↔ Tutorial.ipynb, lines 184–249 · score 0.60 · Picard iteration, Euler Lagrange, machine learning, numerical, density functional, minimization
- [2] § Metadensity Functional Learning › Regularized Metadensity Functional Learning ↔ Tutorial.ipynb, lines 184–249 · score 0.51 · Euler Lagrange, bulk density, external potential, density profile
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 786 lines · 51 KB · MIT · 2 matches
- # %% [markdown]
- # # Neural functional theory for inhomogeneous fluids – Tutorial
- # %% [markdown]
- # This material is available at https://github.com/sfalmo/NeuralDFT-Tutorial and it supplements the following publication:
- #
- # **Why neural functionals suit statistical mechanics**
- # *Florian Sammüller, Sophie Hermann, and Matthias Schmidt, [J. Phys.: Condens. Matter **36**, 243002](https://doi.org/10.1088/1361-648X/ad326f) (2024); [arXiv:2312.04681](https://arxiv.org/abs/2312.04681).*
- #
- # We show how the physics of simple fluids can be described with **many-body simulations** and **classical density functional theory** (DFT) and how the two approaches can be combined with the help of machine learning to a **neural functional theory**.
- # The connection of the different methods is exemplified for the case of the one-dimensional hard rod fluid with hands-on code examples and exercises in the programming language Julia.
- # Besides the pedagogical overview given here, a more in-depth account of the presented methodology with applications to the hard sphere and Lennard-Jones fluids in three dimensions can be found in
- #
- # **Neural functional theory of inhomogeneous fluids: Fundamentals and applications**
- # *Florian Sammüller, Sophie Hermann, Daniel de las Heras, and Matthias Schmidt, [Proc. Natl. Acad. Sci. **120**, e2312484120](https://doi.org/10.1073/pnas.2312484120) (2023); [arXiv:2307.04539](https://arxiv.org/abs/2307.04539).*
- # %% [markdown]
- # ## Technical remarks
- # %% [markdown]
- # Running some of the tasks in this notebook requires a considerable amount of compute power, e.g. for the generation of simulation data and for the training of the neural network.
- # Keep this in mind if you are using an online service with limited resources.
- # For convenience, we also provide ready-to-use data sets and models, which can be downloaded and applied in the relevant parts.
- #
- # Particularly for the machine learning tasks, we recommend using a GPU, as this speeds up training and inference considerably.
- # Using a recent Nvidia GPU should work out of the box.
- # For other GPU vendors, you might need to install the corresponding Julia packages, see https://juliagpu.org/ for further details.
- # %% [markdown]
- # ### Google Colab setup
- # %% [markdown]
- # 1. Connect to a GPU runtime:
- # - Click the arrow (▼) next to *Connect* and select *Change runtime type*
- # - Under *Runtime type*, select "julia"
- # - Under *Hardware accelerator*, select some option with "GPU" in its name
- # - Click *Connect*
- # 2. Run the following cell to copy remaining files into your workspace and to install the required packages:
- # %%
- run(`git clone https://github.com/sfalmo/NeuralDFT-Tutorial.git`)
- run(`cp NeuralDFT-Tutorial/simulation.jl NeuralDFT-Tutorial/dft.jl NeuralDFT-Tutorial/neural.jl .`)
- using Pkg
- Pkg.add(["BSON", "CUDA", "DelimitedFiles", "FFTW", "Flux", "IJulia", "Plots", "cuDNN", "Dates", "Downloads", "Printf"])
- Pkg.instantiate()
- # %% [markdown]
- # ## Part 1: Many-body simulations
- # %% [markdown]
- # Many-body simulations have long become a standard tool for the investigation of classical fluids.
- # Conceptually, they offer a rather straightforward way to predict the behavior of a fluid from a microscopic description, i.e. by specifying the interactions of its constituent particles.
- # However, simulations often come with a significant demand of computational resources.
- #
- # In the following, we focus on Monte Carlo methods for the description of equilibrium systems.
- # Specifically, we choose the grand ensemble where the temperature $T$ and the chemical potential $\mu$ are kept fixed (we also specify the length $L$ of the one-dimensional domain).
- # The statistical mechanics of such a system is then determined by the equilibrium distribution function $\psi(x^{(N)}) \sim \exp(-\beta (U(x^{(N)}) - \mu N))$ where $\beta = 1 / (k_B T)$ with the Boltzmann constant $k_B$.
- # The potential energy $U(x^{(N)}) = u(x^{(N)}) + \sum_{i=1}^N V_\mathrm{ext}(x_i)$ of a given microstate $x^{(N)} = (x_1, x_2, \dots, x_N)$ of $N$ particles consists of a contribution due to an external potential $V_\mathrm{ext}(x)$ and of the internal energy $u(x^{(N)})$.
- # If the particles in the fluid possess pairwise interactions, $u(x^{(N)}) = \sum_{i=1}^N \sum_{j>i}^N \phi(|x_j - x_i|)$, where $\phi(r)$ is the interaction potential for a given distance $r$ of two particles.
- #
- # Monte Carlo is based on the generation of microstates according to their known equilibrium distribution.
- # This is done iteratively by mutating an initial state $A$ into a new state $B$ with a probability such that a given distribution $P$ of states is kept intact.
- # The mutation happens in two stages: a trial transition selects a new state $B$, and a criterion $\mathrm{acc}(A \rightarrow B)$ determines if the new state $B$ shall be accepted or if the system shall be reset to the previous state $A$.
- # Specifically, a valid choice for this acceptance probability is the Metropolis criterion $\mathrm{acc}(A \rightarrow B) = \min(1, P(B) / P(A))$.
- #
- # We now apply this scheme to the grand ensemble, which yields the standard grand canonical Monte Carlo (GCMC) method.
- # There are three possible trial transitions: i) a particle is moved to a new position, ii) a particle is inserted, iii) a particle is removed.
- # The goal distribution of states $P$ is the grand canonical equilibrium distribution $\psi$ (see above).
- # When considering particle displacements, the Metropolis rate becomes
- # $$
- # \mathrm{acc}(x^{(N)} \rightarrow \tilde{x}^{(N)}) = \min\left(1, \exp[-\beta(U(\tilde{x}^{(N)}) - U(x^{(N)}))]\right)
- # $$
- # for moving a particle and changing the initial microstate $x^{(N)}$ to the new configuration $\tilde{x}^{(N)}$.
- #
- # Let us now illustrate this trial move with code.
- # For simplification, we provide some utilities for common tasks in [simulation.jl](simulation.jl), e.g. `calc_energy` for calculating the potential energy of one particle, `pbc!` for applying periodic boundary conditions, and definitions of containers (structs) which hold the state of the `System` and the `Histograms`.
- # %%
- include("simulation.jl");
- function trial_move(system::System; Δxmax=0.1)
- if isempty(system.particles) # If no particles are in the system, do nothing
- return
- end
- i = rand(1:length(system.particles)) # Select random particle with index i
- xbefore = system.particles[i] # Save its initial position
- Ebefore = calc_energy(system, i) # Calculate the initial potential energy of particle i
- system.particles[i] += Δxmax * (2 * rand() - 1) # Move particle to a new position
- pbc!(system, i) # Apply periodic boundary conditions (this places the particle back in the box if it has moved outside of the valid range)
- Eafter = calc_energy(system, i) # Calculate the potential energy of particle i after it has been moved
- if rand() > exp(-system.β * (Eafter - Ebefore))
- system.particles[i] = xbefore # Trial move rejected. Reset particle to previous state
- end
- end
- # %% [markdown]
- # As the particle number can fluctuate in the grand ensemble, there are additional transitions which add and remove particles at random throughout the simulation.
- # The acceptance probabilities of these transitions can be derived by considering particle exchanges of the system with a virtual reservoir.
- # We spare this derivation here and only give the results
- # $$
- # \mathrm{acc}(x^{(N)} \rightarrow x^{(N+1)}) = \min\left(1, \frac{L}{N+1} \exp[\beta(\mu - U(x^{(N+1)}) + U(x^{(N)}))]\right),
- # $$
- # $$
- # \mathrm{acc}(x^{(N)} \rightarrow x^{(N-1)}) = \min\left(1, \frac{N}{L} \exp[-\beta(\mu + U(x^{(N-1)}) - U(x^{(N)}))]\right).
- # $$
- #
- # These trial transitions are already provided as `trial_insert` and `trial_delete` in [simulation.jl](simulation.jl), feel free to take a look for the implementation details.
- #
- # With all transitions being specified, we can write a simulation loop which consists of an equilibration stage and a stage in which measurements take place.
- # For each simulation step, we perform a sweep over a fixed number of trial transitions which are chosen at random without introducing bias in the insertions and deletions.
- # %%
- using Dates
- function sweep(system::System; transitions=10, insert_delete_probability=0.2)
- for _ in 1:transitions
- if rand() < insert_delete_probability # Randomly select trial transition: either move or insert/delete
- rand() < 0.5 ? trial_insert(system) : trial_delete(system) # Do trial insertions and removals with equal probability
- else
- trial_move(system)
- end
- end
- end
- function simulate(L::Number, μ::Number, T::Number, Vext::Function, ϕ::Function; equilibration_time=Dates.Second(1), production_time=Dates.Second(2), sweep_transitions=10)
- system = System(L, μ, T, Vext, ϕ) # The state of the system is encapsulated in this struct
- histograms = Histograms(system)
- equilibration_start = now()
- while now() - equilibration_start < equilibration_time # Equilibration stage, no sampling
- sweep(system; transitions=sweep_transitions)
- end
- production_start = now()
- while now() - production_start < production_time
- sweep(system; transitions=sweep_transitions)
- sample(system, histograms)
- end
- get_results(system, histograms) # Normalizes histograms and returns (xs, ρ)
- end
- # %% [markdown]
- # Equilibrium averages such as the one-body density profile $\rho(x) = \langle \sum_{i=1}^N \delta(x - x_i) \rangle$ are obtained by sampling.
- # For this, the particle configuration is recorded in a position-resolved histogram, which yields the desired average after normalization:
- # %%
- function sample(system::System, histograms::Histograms)
- for x in system.particles
- bin = ceil(Int, x / L * histograms.bins) # Calculate the bin index from the given particle position
- histograms.ρ[bin] += 1
- end
- histograms.count += 1 # Needed later for normalization
- end
- # %% [markdown]
- # Let us now do some simulations to illustrate the usage of the code.
- # We just pass the length $L$ of our system, the thermodynamic statepoint $\mu$ and $T$, the external potential $V_\mathrm{ext}(x)$ and the pair interaction potential $\phi(r)$ to the `simulate` function.
- # In this tutorial, we focus on the hard rod fluid, which is characterized by forbidding the overlap of particles as illustrated below.
- #
- # 
- #
- # This behavior is reflected by the pair interaction potential
- # $$
- # \phi(r) = \begin{cases}
- # \infty, &r < \sigma,\\
- # 0, &r \geq \sigma,
- # \end{cases}
- # $$
- # where $\sigma$ specifies the particle size (we set $\sigma = 1$ in the code).
- # As a simple test case, we choose confinement between hard walls by setting $V_\mathrm{ext}(x) = \infty$ at the boundaries of the domain.
- # Change the form of the external potential as well as the rest of the system parameters to explore the behavior of the hard rod fluid.
- # %%
- using Plots
- L = 10.0
- μ, T = 2.0, 1.0
- Vext(x) = x < 0.5 || x > L - 0.5 ? Inf : 0 # Hard walls at the boundaries
- ϕ(r) = r < 1.0 ? Inf : 0 # Hard core repulsion
- xs, ρ = simulate(L, μ, T, Vext, ϕ; equilibration_time=Dates.Second(1), production_time=Dates.Second(2), sweep_transitions=10)
- plot(xs, ρ, label="ρ", xlims=(0, L), xlabel="x/σ")
- # %% [markdown]
- # ## Part 2: Classical density functional theory
- # %% [markdown]
- # Classical DFT is founded on a minimization principle of the grand potential $\Omega[\rho]$, which can be expressed as a functional of the one-body density profile $\rho(x)$.
- # Its strength is therefore the reduction of a many-body problem (see Part 1) to a formally exact description on the level of one-body quantities.
- # By writing out entropic, external and internal contributions of the grand potential, carrying out the functional derivative $\delta \Omega[\rho] / \delta \rho(x)$ and demanding that it vanishes at the equilibrium density, one arrives at the Euler-Lagrange equation of DFT,
- # $$
- # c_1(x) = \ln\rho(x) + \beta (V_\mathrm{ext}(x) - \mu),
- # $$
- # where $c_1(x)$ is the one-body direct correlation function.
- # This rather abstract object attains a fundamental meaning in classical DFT as it captures the nontrivial effects of the internal interactions within the fluid on the one-body level.
- # Formally, it arises from a functional derivative,
- # $$
- # c_1(x; [\rho]) = - \frac{\delta \beta F_\mathrm{exc}[\rho]}{\delta \rho(x)},
- # $$
- # where $F_\mathrm{exc}[\rho]$ is the excess free energy, i.e. the part of $\Omega[\rho]$ which accounts for the beyond-ideal-gas behavior of the interacting fluid.
- # As an immediate consequence of $F_\mathrm{exc}[\rho]$ being a density functional, $c_1(x; [\rho])$ also attains a functional dependence on $\rho(x)$, which is made explicit by the bracket notation.
- # In principle, $F_\mathrm{exc}[\rho]$ and $c_1(x; [\rho])$ also depend parametrically on the temperature $T$, but this dependence becomes trivial for the hard rod fluid.
- #
- # By rearranging the above equation to
- # $$
- # \rho(x) = \exp(-\beta(V_\mathrm{ext}(x) - \mu) + c_1(x; [\rho])),
- # $$
- # we can reveal its use in actual DFT applications.
- # Given a suitable expression for $c_1(x; [\rho])$ (which one has to obtain somehow for a given type of model fluid), a self-consistent iteration scheme can be used to solve for $\rho(x)$.
- # One such method is the Picard iteration with mixing, in which the iteration is performed as follows:
- # $$
- # \rho(x) \leftarrow (1 - \alpha) \rho(x) + \alpha \rho_\mathrm{EL}(x).
- # $$
- # Here, $\alpha$ is a mixing parameter and $\rho_\mathrm{EL}(x) = \exp(-\beta(V_\mathrm{ext}(x) - \mu) + c_1(x; [\rho]))$ is the right hand side of the rearranged Euler-Lagrange equation.
- #
- # A simple DFT program proceeds as follows:
- # The system parameters $\mu$ and $T$, an external potential $V_\mathrm{ext}(x)$ and a functional form of $c_1(x; [\rho])$ are given, and the density profile is initialized (e.g. with a constant value) on a discrete numerical grid that spans the domain of length $L$.
- # The iteration is then started and Picard steps are performed to update $\rho(x)$ as described above.
- # If the change of $\rho(x)$ between iteration steps falls below a predefined numerical tolerance, the iteration is stopped and the converged self-consistent density profile is obtained as a result.
- # We give an example of such a program in the following:
- # %%
- function minimize(L::Number, μ::Number, T::Number, Vext::Function, get_c1::Function; α::Number=0.03, maxiter::Int=10000, dx::Number=0.01, floattype::Type=Float32, tol::Number=max(eps(floattype(1e3)), 1e-8))
- L, μ, T = floattype.((L, μ, T)) # Technical detail: we will use Float32 in the machine learning part as neural networks usually operate on single precision floats
- xs = collect(floattype, dx/2:dx:L) # Construct the numerical grid
- Vext = Vext.(xs) # Evaluate the external potential on the grid
- infiniteVext = isinf.(Vext) # Check where Vext is infinite to set ρ = 0 there
- ρ, ρEL = zero(xs), zero(xs) # Preallocate the density profile and an intermediate buffer for iteration
- fill!(ρ, 0.5) # Start with a bulk density of 0.5
- c1 = get_c1(xs) # Obtain the c1 functional for the given numerical grid
- i = 0
- while true
- ρEL .= exp.((μ .- Vext) ./ T .+ c1(ρ)) # Evaluate the RHS of the Euler-Lagrange equation
- ρ .= (1 - α) .* ρ .+ α .* ρEL # Do a Picard iteration step to update ρ
- ρ[infiniteVext] .= 0 # Set ρ to 0 where Vext = ∞
- clamp!(ρ, 0, Inf) # Make sure that ρ does not become negative
- Δρmax = maximum(abs.(ρ - ρEL)[.!infiniteVext]) # Calculate the remaining discrepancy to check convergence
- i += 1
- if Δρmax < tol
- println("Converged (step: $(i), ‖Δρ‖ = $(Δρmax) < $(tol) = tolerance)")
- break # The remaining discrepancy is below the tolerance: break out of the loop and return the result
- end
- if !isfinite(Δρmax) || i >= maxiter
- println("Did not converge (step: $(i) of $(maxiter), ‖Δρ‖: $(Δρmax), tolerance: $(tol))")
- return nothing # The iteration did not converge, there is no valid result
- end
- end
- xs, ρ
- end
- # %% [markdown]
- # The way in which $c_1(x; [\rho])$ appears both in the theory as well as in the code example above seems innocuous at first.
- # However, the crux of DFT is finding a suitable functional expression for $c_1(x; [\rho])$ (or equivalently for $F_\mathrm{exc}[\rho]$) for a given fluid model, and much of the ongoing research deals exactly with this problem.
- # In Part 3, we will show how to efficiently use neural networks to capture such a nontrivial functional mapping from simulation data.
- #
- # In the following, we proceed analytically as the focus lies on the hard rod system, which is at present one of very few model fluids where the exact excess free energy functional could be found.
- # This success can be traced back to the purely geometrical nature of this particular system.
- # For hard spheres in 3D, fundamental measure theory provides a similar geometric approach to the construction of excess functionals, although the results remain approximate in this more general geometry.
- #
- # For the exact hard rod density functional, which is attributed to Jerry Percus, the excess free energy can be expressed as
- # $$
- # \beta F_\mathrm{exc}[\rho] = \int \mathrm{d}x \Phi(n_0(x; [\rho]), n_1(x; [\rho]))
- # $$
- # where the free energy density has the specific form $\Phi(n_0, n_1) = - n_0 \ln(1 - n_1)$.
- # The functions $n_0(x; [\rho])$ and $n_1(x; [\rho])$ are *weighted* densities which arise from convolutions of the density profile with the weight functions $\omega_0(x) = (\delta(x-R) + \delta(x+R)) / 2$ and $\omega_1(x) = \Theta(R - |x|)$, i.e. $n_\alpha(x; [\rho]) = (\omega_\alpha \star \rho)(x)$.
- # From this excess free energy functional, one can easily obtain by functional differentiation the result
- # $$
- # c_1(x; [\rho]) = - \sum_{\alpha=0,1} \left(\omega_\alpha \star \frac{\partial \Phi}{\partial n_\alpha}\right)(x)
- # $$
- # for the one-body direct correlation function.
- #
- # We implement a method that constructs the Percus $c_1(x; [\rho])$ for a given numerical grid in the following.
- # As assistance, we provide `conv_fft` to evaluate convolutions efficiently in Fourier space and `get_weights_Percus` to obtain $\omega_\alpha(x)$ on the numerical grid in the file [dft.jl](dft.jl).
- # %%
- include("dft.jl")
- function get_c1_Percus(xs)
- ω0, ω1 = get_weights_Percus(xs)
- conv(f, g) = conv_fft(f, g; dx=xs[2]-xs[1])
- function (ρ)
- n0, n1 = conv(ω0, ρ), conv(ω1, ρ) # Calculate the weighted densities
- ∂ϕ_∂n0 = -log.(1 .- n1) # Evaluate the partial derivatives of the excess free energy density
- ∂ϕ_∂n1 = n0 ./ (1 .- n1)
- -(conv(ω0, ∂ϕ_∂n0) .+ conv(ω1, ∂ϕ_∂n1)) # Return c1
- end
- end
- # %% [markdown]
- # Now we can perform some DFT minimizations to see if the results match the simulations.
- # Note that in general, you might have to adjust the mixing parameter $\alpha$ to ensure convergence of the Picard iteration (see the keyword arguments of `minimize`).
- # %%
- L = 10.0
- μ, T = 2.0, 1.0
- Vext(x) = x < 0.5 || x > L - 0.5 ? Inf : 0
- xs, ρ = minimize(L, μ, T, Vext, get_c1_Percus)
- plot(xs, ρ, label="ρ", xlims=(0, L), xlabel="x/σ")
- # %% [markdown]
- # By comparing simulation and DFT results for the same choices of $L$, $\mu$, $T$ and $V_\mathrm{ext}(x)$, we can cross-check the validity of the density profiles of the hard rod fluid.
- # However, it is obvious that both methods have substantial (and quite contrary) restrictions:
- # - The simulation data is noisy, which can only be improved with longer simulation runs. This quickly becomes prohibitively expensive, in particular if one wishes to perform many individual simulations to explore the behavior for different system parameters. However, one is free to change the type of considered fluid by simply modifying the form of the internal interactions.
- # - The DFT calculation is fast and does not suffer from noisy results, thus enabling vast and efficient parameter studies. The results are exact for the case of hard rods, but as illustrated above, one had to find and implement a suitable density functional in order to capture the nontrivial intrinsic correlations. For more complex fluids, there is little hope in deriving an exact functional analytically, and the construction of good approximations often proves to be very difficult.
- #
- # In the final part of this tutorial, we will combine the advantages of both methods with the help of machine learning.
- # We will demonstrate how to acquire an accurate and flexible representation of $c_1(x; [\rho])$ by training a neural network with simulation data.
- # The resulting *neural functional* can be used in the minimization scheme as seen above, but it can also reveal more fundamental information about the statistical mechanics of the considered fluid.
- # %% [markdown]
- # ## Part 3: Neural functional theory
- # %% [markdown]
- # 
- #
- # The survey in Part 2 indicates that classical DFT is a powerful concept for the determination of fluid equilibra, but that it is limited by the difficulty of finding analytic expressions for $c_1(x; [\rho])$.
- # In the following, we will train a neural network with simulation data (see Part 1) to obtain a representation of $c_1(x; [\rho])$.
- # We will then show applications of this neural functional, which include the prediction of density profiles and the investigation of neural functional calculus.
- #
- # To generate a suitable data set, simulations of systems with randomized inhomogeneous external potentials $V_\mathrm{ext}(x)$ and random values of the chemical potential $\mu$ are performed.
- # For the construction of an appropriate form of $V_\mathrm{ext}(x)$, we combine Fourier modes, linear segments and hard walls, which are defined in the following.
- # We also write a method which generates a combination of these elementary functions with randomly chosen parameters.
- # %%
- Vext_sin(x; n::Int, A::Number, φ::Number, L::Number) = A * sin(2π * x * n / L + φ)
- Vext_lin(x; x1::Number, x2::Number, E1::Number, E2::Number) = x > x1 && x < x2 ? E1 + (x - x1) * (E2 - E1) / (x2 - x1) : 0
- Vext_wall(x; xw::Number, L::Number) = x < xw || x > L - xw ? Inf : 0
- function generate_Vext(L::Number; num_sin=4, num_lin=rand(1:5), wall=true)
- Avar = 1.0
- sin_parameters = []
- for n in 1:num_sin # Generate random parameters for periodic sine functions with increasing frequency
- push!(sin_parameters, (n = n, A = randn() * Avar, φ = rand() * 2π, L = L))
- end
- Evar = 1.0
- lin_parameters = []
- for _ in 1:num_lin # Generate random parameters for discontinuous linear segments
- push!(lin_parameters, (x1 = round(rand() * L, digits=2), x2 = round(rand() * L, digits=2), E1 = randn() * Evar, E2 = randn() * Evar))
- end
- xwmax = 1.0
- wall_params = (xw = round(rand() * xwmax, digits=2), L = L) # Set a random wall width
- function (x) # Return a method which evaluates a combination of all functions with the chosen parameters above
- result = 0.0
- for sin_params in sin_parameters
- result += Vext_sin(x; sin_params...)
- end
- for lin_params in lin_parameters
- result += Vext_lin(x; lin_params...)
- end
- if wall
- result += Vext_wall(x; wall_params...)
- end
- result
- end
- end
- # %% [markdown]
- # To get an idea of the randomized inhomogeneous environments, let us generate and plot some $V_\mathrm{ext}(x)$ profiles.
- # Run the following code cell a few times:
- # %%
- L = 10.0
- dx = 0.01
- xs = dx/2:dx:L
- Vext_generated = generate_Vext(L)
- plot(xs, Vext_generated.(xs), label="Vext", xlims=(0, L), xlabel="x/σ")
- # %% [markdown]
- # We can now proceed to generate reference data by running simulations with these randomized external potentials and random values of the chemical potential as input.
- # Of course, this requires spending some computational resources.
- # You can run the simulations yourself, but be aware that this does take a considerable amount of compute time (~hours) if you want to get good data.
- # For this, change `use_prepared_simulations` to `false` in the following code cell and set an appropriate number of simulations and the time to spend for the equilibration and production stage.
- # Alternatively, you could "fake" the simulations by using the exact Percus DFT as illustrated in Part 2.
- #
- # For readers without the required computational resources and/or patience, we have prepared a ready-to-use data set for the following machine learning tasks, which is downloaded by default as follows:
- # %%
- using DelimitedFiles, Downloads
- use_prepared_simulations = true # Choose if you want to use pregenerated simulations (true) or if you want to generate a simulation data set from scratch (false)
- if use_prepared_simulations
- println("Downloading pregenerated simulation data set")
- Downloads.download("https://www.staff.uni-bayreuth.de/~bt306964/neuraldft-tutorial/data.tar", "data.tar")
- run(`tar xf data.tar`) # You should now have a directory "data" with a bunch of simulation files
- datadir = "data"
- else
- num_sim = 512
- equilibration_time = Dates.Second(10)
- production_time = Dates.Second(200)
- nthreads = Threads.nthreads()
- println("Generating simulation reference data from scratch. This will take approximately $(canonicalize(num_sim * (equilibration_time + production_time) / nthreads)).")
- L = 10
- ϕ(r) = r < 1.0 ? Inf : 0 # Hard core repulsion
- μmin, μmax = -7.0, 5.0
- T = 1.0
- datadir = mkdir("data_$(now())")
- println("Saving results to $(datadir)")
- Threads.@threads for i in 1:num_sim
- μ = μmin + rand() * (μmax - μmin)
- Vext_generated = generate_Vext(L)
- println("Simulation $(i) running...")
- xs, ρ = simulate(L, μ, T, Vext_generated, ϕ; equilibration_time, production_time)
- βμloc = (μ .- Vext_generated.(xs)) / T
- writedlm("$(datadir)/$(i).dat", [xs βμloc ρ])
- println("Simulation $(i) done")
- end
- end
- println("There are $(length(readdir(datadir))) result files in the directory $(datadir)")
- # %% [markdown]
- # If you have generated your own training set, the results are now located in the directory `data_<timestamp>` (use the file explorer of JupyterLab on the left side).
- # Otherwise, proceed with the pregenerated simulations which are located in `data` after successful download and unpacking.
- #
- # Let us first plot some of the data files to check if they are reasonable:
- # %%
- datadir = "data" # ... or use your own simulation results from above
- sim = rand(readdir(datadir, join=true)) # Select a random simulation within the data directory
- xs, βμloc, ρ = eachcol(readdlm(sim)) # Read the result file and split columns
- display(plot(xs, βμloc, label="β μloc", xlims=(0, L), xlabel="x/σ"))
- display(plot(xs, ρ, label="ρ", xlims=(0, L), xlabel="x/σ"))
- # %% [markdown]
- # Note that besides $\rho(x)$, we have saved the local chemical potential $\mu_\mathrm{loc}(x) = \mu - V_\mathrm{ext}(x)$ as a further one-body field.
- # This additional information suffices to calculate the one-body direct correlation function $c_1(x) = \ln \rho(x) - \beta \mu_\mathrm{loc}(x)$ for each $x$ where $\rho(x) \neq 0$, which is the target of our following investigation.
- #
- # In Part 2, we have shown that the one-body direct correlation function cannot only be obtained pointwise by the above relation, but that it also constitutes a universal functional $c_1(x; [\rho])$ of the density profile, without having to invoke the local chemical potential $\mu_\mathrm{loc}(x)$ explicitly.
- # In the following, we attempt to machine-learn this functional relationship with a neural network.
- # For constructing the specific input-output mapping, we appeal to some physical background.
- # Formally, the functional dependence on $\rho(x)$ is given with respect to the profile of the whole system.
- # However, for short-ranged pair interactions, the influence of the surrounding density profile on the value of $c_1(x; [\rho])$ at a certain position $x$ also remains very short-ranged.
- # Therefore, we can adopt a *local* learning strategy: the density profile is taken only within a narrow window around a given position as input to the neural network, which is trained to yield the *scalar value* of $c_1(x; [\rho])$ at that position.
- # The whole one-body direct correlation profile can be recovered by evaluating the neural network at different positions across the system domain.
- # The following schematic illustrates this setup.
- #
- # 
- #
- # Due to this specific construction, the data has to be prepared accordingly prior to the training, i.e. the input-output pairs of $\rho$-windows and $c_1$-values have to be generated from the full profiles.
- # For this, some utility functions are given in the file [neural.jl](neural.jl).
- # Note that we choose a window width of $1 \sigma$ to each side.
- # For the hard rod fluid, we know that this choice suffices, as the exact analytic solution for $c_1(x; [\rho])$ also has a convolutional range of $1 \sigma$.
- # For other fluid models, one would have to determine the window width heuristically, for which the neural functional calculus (see below) could provide some guidance.
- # %%
- include("neural.jl")
- ρ_profiles, c1_profiles = read_sim_data(datadir)
- ρ_windows, c1_values = generate_inout(ρ_profiles, c1_profiles; window_width=1.0, dx=xs[2]-xs[1]) # window width = 1σ suffices for hard rods
- size(ρ_windows), size(c1_values)
- # %% [markdown]
- # The data is now ready to be given to a neural network for training.
- # We choose a simple architecture with fully-connected hidden layers and continuous activation functions (e.g. `softplus`).
- # For the construction of the neural network and the subsequent machine learning routines, the framework [Flux.jl](https://fluxml.ai/) is used.
- # We also specify a standard optimizer and give the generated input-output pairs to a data loader, which automates shuffling and batching during training.
- # As this is a common regression task, we select the mean squared error as the loss function and the mean absolute error as the metric.
- # The learning rate is decreased exponentially after the first few epochs, which improves the final training result.
- #
- # You can choose in the following code cell via `use_pretrained_model` whether you want to download a ready-to-use model or whether you want to do the training yourself.
- # Running the training on a GPU is recommended.
- # Otherwise, the code runs as-is on the CPU, but training might be slow.
- # %%
- using BSON, Dates, Downloads, Flux, Printf
- using CUDA # For Nvidia GPU support, defaults to CPU if no CUDA device is available. Switch out this package if you have a different GPU manufacturer.
- use_pretrained_model = true # Choose if you want to use the pretrained model (true) or if you want to do the training yourself (false)
- if use_pretrained_model
- println("Downloading pretrained model")
- Downloads.download("https://www.staff.uni-bayreuth.de/~bt306964/neuraldft-tutorial/model.bson", "model.bson")
- BSON.@load "model.bson" model
- else
- println("Training model from scratch")
- ρ_windows, c1_values = (ρ_windows, c1_values) |> gpu
- model = Chain(
- Dense(size(ρ_windows)[1] => 128, softplus),
- Dense(128 => 64, softplus),
- Dense(64 => 32, softplus),
- Dense(32 => 1)
- ) |> gpu
- display(model) # Show a summary of the model with the number of fittable parameters
- opt = Flux.setup(Adam(), model) # Set up a standard Adam optimizer
- loader = Flux.DataLoader((ρ_windows, c1_values), batchsize=256, shuffle=true) # Initialize the DataLoader to yield shuffled batches
- loss(m, x, y) = Flux.mse(m(x), y) # Use mean squared error as loss
- metric(m, x, y) = Flux.mae(m(x), y) # Use mean absolute error as metric
- get_learning_rate(epoch; initial=0.0001, rate=0.03, wait=5) = epoch < wait ? initial : initial * (1 - rate)^(epoch - wait)
- model_savefile = "model_$(now()).bson"
- println("Saving model to $(model_savefile)")
- for epoch in 1:250 # Do the training loop
- learning_rate = get_learning_rate(epoch)
- Flux.adjust!(opt, learning_rate)
- @printf "Epoch: %3i (learning_rate: %.2e)..." epoch learning_rate; flush(stdout)
- Flux.train!(loss, model, loader, opt)
- @printf " loss: %.5f, metric: %.5f\n" loss(model, ρ_windows, c1_values) metric(model, ρ_windows, c1_values); flush(stdout)
- model = model |> cpu
- BSON.@save model_savefile model
- model = model |> gpu
- end
- end
- model
- # %% [markdown]
- # The model is now ready to be used for inference.
- # For convenience, we write a method that lets us evaluate the whole $c_1$-profile via the trained model for a given density profile.
- # This is necessary due to the local nature of the mapping, which prevents $\rho(x)$ from being used directly as input.
- # Instead, as shown above, the density profile must be restructured into appropriate windows which are passed to the model.
- # %%
- function get_c1_neural(model, xs)
- window_bins = length(model.layers[1].weight[1,:]) # Get the number of input bins from the shape of the first layer
- model = model |> gpu
- function (ρ)
- ρ_windows = generate_windows(ρ; window_bins) |> gpu # The helper function generate_windows is defined in neural.jl
- model(ρ_windows) |> cpu |> vec # Evaluate the model, make sure the result gets back to the CPU, and transpose it to a vector
- end
- end
- # %% [markdown]
- # The central use of the neural functional is revealed by applying it for the self-consistent calculation of density profiles.
- # Just like we have used the analytic functional due to Percus in Part 2, we can now employ the neural functional to obtain results for $c_1(x; [\rho])$ within a DFT minimization.
- # Importantly, the specific minimization scheme to update and iterate $\rho(x)$ until convergence remains unchanged upon switching out the analytic one-body direct correlation functional with its neural counterpart.
- # Hence, this *neural* DFT is an effective means to circumvent the cumbersome search for analytic functionals while retaining the merits of the DFT minimization procedure.
- #
- # We show in the following that neural DFT indeed yields accurate results for the considered hard rod fluid.
- # Exact reference data for comparison can be obtained immediately by the Percus theory (one would have to resort to simulation results for other fluid models).
- # %%
- L = 10.0
- μ, T = 2.0, 1.0
- Vext(x) = x < 0.5 || x > L - 0.5 ? Inf : 0
- xs, ρ_neural = minimize(L, μ, T, Vext, xs -> get_c1_neural(model, xs))
- xs, ρ_Percus = minimize(L, μ, T, Vext, get_c1_Percus)
- plot(xs, [ρ_neural ρ_Percus], label=["ρ (neural)" "ρ (Percus)"], linestyle=[:solid :dash], xlims=(0, L), xlabel="x/σ")
- # %% [markdown]
- # Although the box size has been kept constant during training and in the above example, we can immediately apply the neural functional for different choices of $L$ due to its local nature.
- # This also gives us the possibility to run some rather demanding quality checks.
- # For narrow confinement, which has clearly not been included in our training data, the hard rod fluid yields very interesting density profiles.
- # The following test reveals that the neural functional is able to extrapolate to these unseen cases and that the results from neural DFT match the analytic theory with surprising accuracy.
- # %%
- L = 3.0 # Try other values between 2.0 and 4.0, the density profiles will be interesting
- μ, T = 2.0, 1.0
- Vext(x) = x < 0.5 || x > L - 0.5 ? Inf : 0
- xs, ρ_neural = minimize(L, μ, T, Vext, xs -> get_c1_neural(model, xs))
- xs, ρ_Percus = minimize(L, μ, T, Vext, get_c1_Percus)
- plot(xs, [ρ_neural ρ_Percus], label=["ρ (neural)" "ρ (Percus)"], linestyle=[:solid :dash], xlims=(0, L), xlabel="x/σ")
- # %% [markdown]
- # We can also increase $L$ significantly compared to the previous cases.
- # The ability of the neural functional to be used straightforwardly in this manner creates the opportunity of efficient multiscale investigations.
- # In the following, this is illustrated by considering the sedimentation of the hard rod fluid in a column with hard walls at the top and at the bottom.
- # The external potential is chosen to slowly increase linearly with height $x$ in order to model the effect of gravity.
- # Within the sedimentation column, the density closely follows the equation of state of the hard rod fluid.
- # %%
- L = 100.0 # You can try to increase L even further
- μ, T = 2.0, 1.0
- Vext(x) = x < 0.5 || x > L - 0.5 ? Inf : 0.05 * x # Hard walls at the boundaries and linearly increasing within the sedimentation column
- xs, ρ_neural = minimize(L, μ, T, Vext, xs -> get_c1_neural(model, xs))
- xs, ρ_Percus = minimize(L, μ, T, Vext, get_c1_Percus)
- plot(xs, [ρ_neural ρ_Percus], label=["ρ (neural)" "ρ (Percus)"], linestyle=[:solid :dash], xlims=(0, L), xlabel="x/σ")
- # %% [markdown]
- # Besides using the trained model directly in DFT minimizations, we show in the following that one can extract much more fundamental physical information from the neural functional.
- # For this, we aim at implementing functional calculus on the basis of the neural network.
- # An important prerequisite for this is to understand the concept of automatic differentiation.
- # In fact, we have used autodifferentiation already above to realize the adaptation of the neural network parameters during its training.
- # This has been hidden within the `Flux.train!` method, which evaluates for the given training data the output of the neural network as well as gradients of the loss function with respect to all trainable parameters.
- # These gradients are used to trace the error of the neural network predictions back to the weights in order to nudge them in the "right direction", i.e. to decrease the loss.
- #
- # Reverse mode automatic differentiation computes derivatives by recording individual subexpressions of a given composite function, e.g. of the neural network, during evaluation.
- # The chain rule is then applied programmatically to yield the derivative of the function in a second computational pass.
- # As this mechanism is central to machine learning, many frameworks come with ready-to-use implementations.
- # For instance, Flux.jl provides the `gradient` method, which can be used as follows for scalar-valued functions:
- # %%
- f(x, y) = x^2 + sin(x * y) # Some arbitrary test function
- df(x, y) = (2*x + y * cos(x * y), x * cos(x * y)) # Analytic derivative to test autodiff results
- x, y = π, 2.0 # Values for which the derivative will be computed
- println("analytic: ", df(x, y))
- println("autodiff: ", Flux.gradient(f, x, y))
- # %% [markdown]
- # Similarly, we can derive the output of the neural functional with respect to the input density via autodifferentiation.
- # But what do we get out of this?
- #
- # In Part 2, we have seen that the one-body direct correlation functional is obtained from the functional derivative of the excess free energy, $c_1(x; [\rho]) = -\delta \beta F_\mathrm{exc}[\rho] / \delta \rho(x)$, and that it is a central object in DFT calculations.
- # Applying an additional functional derivative yields another important quantity, the two-body direct correlation function
- # $$
- # c_2(x, x'; [\rho]) = \frac{\delta c_1(x; [\rho])}{\delta \rho(x')}.
- # $$
- # In fact, a complete hierarchy of direct correlation functions $c_n(x, x', ...; [\rho])$ is defined by iterating subsequent functional derivatives.
- #
- # From fundamental physical considerations, further relations can be obtained for these direct correlation functions.
- # In particular, by applying Noether's theorem to statistical mechanical systems, one arrives at so-called sum rules, which constrain the interrelation of different quantities of interest.
- # One such sum rule which connects $c_1(x)$, $c_2(x, x')$ and $\rho(x)$ reads as follows:
- # $$
- # \nabla c_1(x) = \int \mathrm{d}x' c_2(x, x') \nabla' \rho(x').
- # $$
- # As these identities emerge from fundamental invariances of equilibrium many-body systems, they can be used as further valuable tests to check the consistency of the neural functional.
- #
- # We implement in the following a method to calculate $c_2(x, x'; [\rho])$ via autodifferentiation of a given function for $c_1(x; [\rho])$.
- # Note that autodifferentiation can be used very generically, which gives us a way to obtain $c_2(x, x'; [\rho])$ also from the Percus $c_1(x; [\rho])$ without having to derive and implement the analytic expression by hand.
- # %%
- function get_c2_autodiff(c1_function, xs)
- dx = xs[2] - xs[1]
- function (ρ)
- Flux.jacobian(c1_function, ρ)[1] / dx
- end
- end
- # %% [markdown]
- # First, we run some quick tests to illustrate the usage of this method.
- # As test density input, we take profiles that follow from a randomized continuous external potential.
- # Run the cell a few times to see different results for $c_2(x, x'; [\rho])$.
- # Also try to switch out the neural correlation functional for the Percus expression, the use of autodifferentiation remains straightforward.
- # %%
- L = 10.0
- μ, T = 0.0, 1.0
- xs, ρ = minimize(L, μ, T, generate_Vext(L; num_sin=5, num_lin=0, wall=false), get_c1_Percus)
- c1_func = get_c1_neural(model, xs)
- # c1_func = get_c1_Percus(xs) # The Percus c1 can also be used in the following autodifferentiation, no need for a pen-and-paper derivation :)
- c2_func = get_c2_autodiff(c1_func, xs)
- c1 = c1_func(ρ)
- c2 = c2_func(ρ)
- display(plot(xs, ρ, label="ρ", xlims=(0, L), xlabel="x/σ"))
- display(plot(xs, c1, label="c1", xlims=(0, L), xlabel="x/σ"))
- heatmap(xs, xs, c2, colorbar_title="c2", aspect_ratio=1, xlims=(0, L), ylims=(0, L), xlabel="x/σ", ylabel="x'/σ")
- # %% [markdown]
- # Now we can check the above Noether sum rule.
- # The gradients are evaluated numerically on the spatial grid via finite differences, for which we have supplied the function `finite_diff` in [neural.jl](neural.jl).
- # The following plot shows the right and left hand side of the identity, which coincide when evaluating the quantities with a neural functional that has been trained sufficiently well.
- # Recall that we have neither specified $c_2(x, x'; [\rho])$ nor the Noether identity during training.
- # The successful reproduction of such fundamental results implies that the neural functional does not merely interpolate values of $c_1(x; [\rho])$, but that it instead also captures the essential physics of the underlying many-body problem.
- # %%
- dx = xs[2] - xs[1]
- grad_c1 = finite_diff(c1; dx)
- grad_ρ = finite_diff(ρ; dx)
- lhs = grad_c1 # Left hand side of the Noether sum rule
- rhs = c2 * grad_ρ * dx # Right hand side of the Noether sum rule
- plot(xs, [lhs rhs], label=["∇c1(x)" "∫dx' c2(x,x') ∇'ρ(x')"], linestyle=[:solid :dash], xlims=(0, L), xlabel="x/σ")
- # %% [markdown]
- # To complete the functional calculus, we now consider the *inverse* operation of functional differentiation.
- # This gives us direct access to the excess free energy $F_\mathrm{exc}[\rho]$, as can be deduced from the definition of $c_1(x; [\rho])$ via its functional derivative.
- # Formally, functional line integration serves as such an inverse operation, and it amounts to performing a line integral in function space.
- # By choosing a linear parameterization $\rho_a(x) = a \rho(x)$, one can derive the explicit result
- # $$
- # \beta F_\mathrm{exc}[\rho] = - \int \mathrm{d}x \rho(x) \int_0^1 \mathrm{d}a c_1(x; [\rho_a]).
- # $$
- #
- # On the level of the neural functional (or, in fact, any representation of the one-body direct correlation functional), this expression is easy to evaluate numerically by discretizing both integrals.
- # We write a method for this in the following.
- # %%
- function get_Fexc_funcintegral(c1_function, xs; num_a=100)
- dx = xs[2] - xs[1]
- da = 1 / num_a
- as = da/2:da:1
- function (ρ)
- aintegral = zero(ρ)
- for a in as
- aintegral .+= c1_function(a .* ρ)
- end
- -sum(ρ .* aintegral) * dx * da
- end
- end
- # %% [markdown]
- # In the following code cell, randomized inhomogeneous systems are considered as above to test the excess free energy calculation via functional line integration.
- # We additionally implement the exact analytic Percus expression for $F_\mathrm{exc}[\rho]$ (see Part 2) in order to judge the results.
- # The values of $F_\mathrm{exc}[\rho]$ as obtained by functional line integration of the neural correlation functional show almost no discrepancy to the exact analytic theory.
- # %%
- L = 10.0
- μ, T = 0.0, 1.0
- xs, ρ = minimize(L, μ, T, generate_Vext(L), get_c1_Percus)
- c1_func = get_c1_neural(model, xs)
- # c1_func = get_c1_Percus(xs)
- Fexc_func = get_Fexc_funcintegral(c1_func, xs)
- function get_Fexc_Percus(xs)
- dx = xs[2] - xs[1]
- ω0, ω1 = get_weights_Percus(xs)
- conv(f, g) = conv_fft(f, g; dx)
- function (ρ)
- n0, n1 = conv(ω0, ρ), conv(ω1, ρ)
- ϕ = n0 .* log.(1 .- n1)
- -sum(ϕ) * dx
- end
- end
- Fexc_Percus = get_Fexc_Percus(xs)
- println("Fexc from analytic expression (Percus): ", Fexc_Percus(ρ))
- println("Fexc from functional line integral (neural): ", Fexc_func(ρ))
- plot(xs, ρ, label="ρ", xlims=(0, L), xlabel="x/σ")
- # %% [markdown]
- # ## Conclusions
- # %% [markdown]
- # In this tutorial, we have investigated various methods for the description of classical statistical many-body systems.
- # In particular, we have focused on developing a machine learning framework based on classical DFT and many-body simulations, which combines the strengths of both methods and which facilitates to overcome their individual limitations.
- #
- # As preparation, the fundamentals of GCMC simulations were laid out in Part 1.
- # We have shown that many-body simulations are suitable to obtain equilibrium averages such as the density profile from the microscopic description of a system.
- # This was exemplified specifically for the hard rod fluid in inhomogeneous environments.
- # However, the computational demands of simulations often turn out to be too restrictive in concrete applications.
- #
- # In Part 2, classical DFT was presented as a coarse-grained method that operates directly on the level of the one-body density rather than on the full many-body picture, which greatly reduces the computational complexity of the problem.
- # For the case of the hard rod fluid, we have reproduced the exact analytic density functional due to Percus and have shown its application in a standard DFT minimization for the self-consistent calculation of density profiles.
- # While DFT is formally exact for arbitrary fluid models, we have emphasized the difficulty of finding analytic functionals and the exceptionality of the hard rod result.
- # These considerations led us to the application of machine learning with the goal of capturing functional maps from simulation data.
- #
- # Part 3 gives a detailed account of our machine learning procedure, in which a neural network serves as the central object for the representation of a functional relationship.
- # Training data has been acquired with GCMC simulations, and we have described in this regard how one can generate suitable external potentials randomly to cover diverse inhomogeneous environments.
- # The actual training procedure was based on the functional mapping from the density profile $\rho(x)$ to the one-body direct correlation functional $c_1(x; [\rho])$, which were hence taken respectively as input and target output of the neural network.
- # Crucially, however, we have adopted a *local* learning strategy.
- # The neural network was constructed to yield only the scalar value of $c_1(x; [\rho])$ at a certain location $x$ when provided with the surrounding density profile within a narrow window around $x$ as input.
- # This setup imposes no substantial restriction, as the whole profile of $c_1(x; [\rho])$ can be recovered by evaluating the neural network (in parallel) at multiple positions within the system domain.
- # However, there are numerous benefits to considering the functional relationship locally, such as an improvement of training statistics, the prescription of the short-ranged nature of $c_1(x; [\rho])$ and the applicability of the neural functional to multiscale problems.
- #
- # We have then investigated applications of the trained neural correlation functional and its use in DFT minimizations.
- # The results of this *neural* DFT were highly accurate even for unseen external environments, which could be demonstrated for the hard rod fluid by comparison to the exact Percus solution.
- # Further, we have shown the implementation of functional calculus on the basis of the neural network, which enabled us to obtain related quantities such as the two-body direct correlation functional $c_2(x, x'; [\rho])$ or the excess free energy $F_\mathrm{exc}[\rho]$.
- # In this regard, automatic differentiation was utilized for the evaluation of functional derivatives, and a functional line integral served as the explicit inverse operation.
- # The results of the neural functional calculus could be verified by the exact Percus theory.
- # A special case, which has not been covered in this tutorial, is the application of the neural functional calculus to systems with spatially constant density, where important bulk properties such as the equation of state and the bulk pair structure can be determined.
- # It is a valuable excercise to use the above tools for the investigation of these bulk results.
- # Further details are given in our research paper ([doi:10.1073/pnas.2312484120](https://doi.org/10.1073/pnas.2312484120), [arXiv:2307.04539](https://arxiv.org/abs/2307.04539)).
- #
- # As a further consistency check of the neural functional, we have shown the validity of a sum rule which arises from the application of Noether's theorem to statistical many-body systems, and which can hence be traced back to a fundamental invariance property of phase space.
- # It is therefore nontrivial that the neural network reproduces the identity given that no such information was provided during training.
- # There are many more sum rules which result from thermal Noether invariance and which are described in more detail in the accompanying manuscript ([arXiv:2312.04681](https://arxiv.org/abs/2312.04681)).
- # We encourage the reader to implement and verify them; the above code provides the required utilities and may serve as guidance.
- #
- # Even though we have focused on the hard rod fluid, which is owed to the pedagogical purpose of this tutorial and to the availability of exact theoretical results, the presented machine learning framework is generically applicable to other fluid models with short-ranged interactions.
- # You can try this yourself by modifying the above code examples.
- # Simulation data can be generated for a different type of fluid by simply changing the form of the pair potential $\phi(r)$ and by selecting appropriate parameters for the random generation of chemical and external potentials.
- # The training routine of the neural functional then proceeds identically, provided that the input range of the density is chosen sufficiently large for the specific interaction type (this could be checked e.g. by evaluating $c_2(x, x'; [\rho])$ via autodifferentiation).
- # The trained neural functional can be applied as before in DFT minimizations and in the functional calculus, which is particularly relevant for fluid models where no satisfactory analytic functionals exist.
- # One could even go beyond an isothermal setting and include variations of the temperature, which then serves as a further input parameter to the neural correlation functional.
- # In total, the neural functional theory leaves much room for further research and we have demonstrated here that it serves as a promising hybrid approach to the investigation of inhomogeneous fluids.
Tutorial.ipynb at commit 357f85d, under MIT · at the source
Overview
Abstract
The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.
Repository
Its files are read in the Code ↔ Paper reader above, with 2 matches between paragraphs and lines of code.
sfalmo/NeuralDFT-Tutorial
357f85d1b472ca38a79c3a3b1a28ecc7bb501d05, 20 May 2025Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
6 files
- Tutorial.ipynb, Jupyter, 786 lines, 2 matches
- dft.jl, Julia, 22 lines
- neural.jl, Julia, 52 lines
- simulation.jl, Julia, 76 lines
- LICENSE, License, 21 lines
- README.md, Text, 42 lines
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 4 scripts, each with its path and the digest of its content;
- 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability statement
The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- no repository, dataset or request procedure was recognized in it
Read it in the paper: doi.org/10.1021/acs.jpcb.6c01662.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 2, 28 September 2026
- Publisher: n/a → American Chemical Society
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 3 authors, 1 funder, 93 references.
Cite
This paper
Kampa, S. M., Sammüller, F., & Schmidt, M. (2026). Metadensity Functional Learning for Classical Fluids: Regularizing with Pair Correlations. The journal of physical chemistry. B, 130(24), 6231-6244. https://
BibTeX
@article{kampa2026metade
author = {Kampa, Stefanie M and Sammüller, Florian and Schmidt, Matthias},
title = {{Metadensity Functional Learning for Classical Fluids: Regularizing with Pair Correlations}},
journal = {The journal of physical chemistry. B},
year = {2026},
month = jun,
volume = {130},
number = {24},
pages = {6231--6244},
publisher = {American Chemical Society},
issn = {1520-6106},
doi = {10.1021/
url = {https://
pmid = {42240207},
pmcid = {PMC13288625}
}
RIS
TY - JOUR
AU - Kampa, Stefanie M
AU - Sammüller, Florian
AU - Schmidt, Matthias
TI - Metadensity Functional Learning for Classical Fluids: Regularizing with Pair Correlations
T2 - The journal of physical chemistry. B
J2 - J Phys Chem B
PY - 2026
DA - 2026/
VL - 130
IS - 24
SP - 6231
EP - 6244
SN - 1520-6106
PB - American Chemical Society
DO - 10.1021/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1021/
"type": "article-journal",
"title": "Metadensity Functional Learning for Classical Fluids: Regularizing with Pair Correlations",
"container-title": "The journal of physical chemistry. B",
"author": [
{
"family": "Kampa",
"given": "Stefanie M"
},
{
"family": "Sammüller",
"given": "Florian"
},
{
"family": "Schmidt",
"given": "Matthias"
}
],
"container-title-short":
"volume": "130",
"issue": "24",
"page": "6231-6244",
"DOI": "10.1021/
"PMID": "42240207",
"PMCID": "PMC13288625",
"ISSN": "1520-6106",
"publisher": "American Chemical Society",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
4
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1073/pnas.2610049123 [code]
- A unified machine-learning framework for ab initio multiscale modeling of liquids.Journal: Proceedings of the National Academy of Sciences of the United States of AmericaIn common: 15 references
- [2] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: Flux.jl, Plots.jl
- [3] doi:10.7554/elife.89629 [code]
- Active dendrites enable robust spiking computations despite timing jitter.Journal: eLifeIn common: Flux.jl, Plots.jl
- [4] doi:10.1038/s41540-026-00749-5 [code]
- A novel approach to quantify out-of-distribution uncertainty in Neural and Universal Differential Equations.Journal: NPJ systems biology and applicationsIn common: Flux.jl, Plots.jl
- [5] doi:10.1038/s41593-026-02342-9 [code]
- Interpretable abstractions of artificial neural networks predict behavior and neural activity during human information gathering.Journal: Nature neuroscienceIn common: Flux.jl
- [6] doi:10.1523/eneuro.0379-25.2026 [code]
- Next-Generation Neural Mass Models Reproduce Features of Speech Processing.Journal: eNeuroIn common: Plots.jl
- [7] doi:10.1371/journal.pcbi.1014222 [code]
- Neural population models for EEG: From Canonical models to alternative model structures.Journal: PLoS computational biologyIn common: Plots.jl
- [8] doi:10.1371/journal.pcbi.1014549 [code]
- Inward rectifier potassium channels interact with calcium channels to promote robust and physiological bistability.Journal: PLoS computational biologyIn common: Plots.jl
- [9] doi:10.1038/s41467-026-74556-1 [code]
- Hypernetworks induce stable hyperlocking.Journal: Nature communicationsIn common: Plots.jl
- [10] doi:10.1523/eneuro.0029-26.2026 [code]
- Electrical Coupling within Thalamocortical Networks Cumulatively Reduces Cortical Correlation to Sensory Inputs.Journal: eNeuroIn common: Plots.jl
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 4 scripts, and 2 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:2eeb319c1c62f3a8…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
