NQCDistributions.jl
Storing and sampling distributions
In order to perform ensembles of trajectories, it is useful to have a convenient way to generate distributions of velocities and positions which can be sampled to initialise trajectories. The NQCDistributions.jl package contains the types and functions that seek to address this requirement as painlessly as possible.
For quantum classical nonadiabatic dynamics simulations, the initial distributions contain both nuclear and electronic degrees of freedom.
Currently, we allow for product distributions only, where the nuclear and electronic distributions are separable. In the future it would be great to remove this restriction, if you are interested, please open an issue on GitHub.
This page describes the types that can be used to represent nuclear and electronic distributions and demonstrates how they can be combined into a product distribution.
Nuclear Distributions using DynamicalDistribution
When handling distributions for the nuclear degrees of freedom, the DynamicalDistribution
type can be used to store initial velocities and positions:
d = DynamicalDistribution(10, 5, (3, 2))
Here, we have created a delta distribution with fixed velocities and positions, the final argument specifies the size of each sample. The (3, 2)
case shown here would be appropriate when using 2 atoms each with 3 degrees of freedom.
julia> rand(d)
ComponentVector{Int64}(v = [10 10; 10 10; 10 10], r = [5 5; 5 5; 5 5])
DynamicalDistribution
is flexible and each of the first two arguments can be Real
, Vector
or Sampleable
.
Real
s are used whenever the same value is desired for every sample, as above.Vector
s can be provided when sampling a provided vector of configurations.Sampleable
s are provided byDistributions.jl
and can be used when specifying an analytic distribution such as the Maxwell-Boltzmann distribution for velocities.
Each of these options can be composed in any combination, let's see the case where we have an analytic distribution of positions and a preset collection of velocities:
using Distributions
velocity = [[1.0 1.0;1.0 1.0], [2.0 2.0; 2.0 2.0], [3.0 3.0; 3.0 3.0]]
position = Normal()
d = DynamicalDistribution(velocity, position, (2, 2))
rand(d)
ComponentVector{Float64}(v = [1.0 1.0; 1.0 1.0], r = [-0.07058313895389791 -0.806852326006714; 0.5314767537831963 2.456991333983293])
This has generated normally distributed positions along with one of the three velocities we provided.
Sampling the nuclear distribution
To learn how to generate configurations to use with the DynamicalDistribution
, read on to the next sections about the included sampling methods.
VelocityBoltzmann
When performing equilibrium simulations it is often desirable to initialise trajectories with thermal velocities, e.g. in combination with positions obtained from Monte Carlo sampling. These can be obtained for each atom from a gaussian distribution of the appropriate width, or alternatively, using the VelocityBoltzmann
distribution which simplifies the process. This takes the temperature, masses and size of the system and ensures the samples you obtain are of the correct shape:
using NQCDynamics
using Unitful
velocity = VelocityBoltzmann(300u"K", rand(10), (3, 10))
rand(velocity)
3×10 Matrix{Float64}:
0.0177757 0.00852031 -0.01817 0.00255313 … 0.0107218 0.0137637
0.0121971 0.0223211 -0.033602 0.0119113 0.0374042 -0.00928718
0.0141005 -0.0456442 -0.025302 -0.00625611 -0.0342534 0.052824
This can be handed directly to the DynamicalDistribution
when Boltzmann velocities are required.
distribution = DynamicalDistribution(velocity, 1, (3, 10))
rand(distribution)
ComponentVector{Float64}(v = [0.003131592503204323 -0.028111899383812214 … 0.04359873308587451 -0.0884528861004582; 0.05671282180749602 -0.012196942275571901 … 0.008840183797715034 -0.024438617026233622; 0.21636524331190934 0.06534435425906177 … 0.041305731755436455 0.002715920560214123], r = [1.0 1.0 … 1.0 1.0; 1.0 1.0 … 1.0 1.0; 1.0 1.0 … 1.0 1.0])
Wigner distributions
For harmonic oscillator systems, we have implemented the analytic Wigner distribution. These are just mormal distributions of the appropriate width but can be accessed easily as in the following:
julia> using NQCDistributions
julia> omega = 1.0;
julia> beta = 1e-3;
julia> mass = 10;
julia> dist = PositionHarmonicWigner(omega, beta, mass, centre=0.0)
Distributions.Normal{Float64}(μ=0.0, σ=10.000000416666651)
julia> rand(dist)
2.5383319811382297
julia> dist = VelocityHarmonicWigner(omega, beta, mass)
Distributions.Normal{Float64}(μ=0.0, σ=10.000000416666651)
julia> rand(dist)
-6.031638329701433
These can also be given to the DynamicalDistribution
since they are just univariate normal distributions.
Nuclear distributions for Ring polymer simulations
The components making up a DynamicalDistribution
can all be adapted for use in Ring Polymer simulations.
For samplable components based on nuclear configurations, simply use three-dimensional arrays instead of two-dimensional ones.
Pre-defined distribution functions such as VelocityBoltzmann
can be turned into a three-dimensional version using the RingPolymerWrapper
:
velocity = VelocityBoltzmann(300u"K", rand(10), (3, 10))
velocity_ring_polymer = RingPolymerWrapper(velocity, n_beads, Int[]) # RingPolymerWrapper(Distribution, number of ring-polymer beads, indices of atoms to treat classically)
Electronic distributions
For nonadiabatic dynamics, the initial electronic variables must also be sampled. For this, we can use an ElectronicDistribution
which will tell our simulation how we want to sample the initial variables. Currently, two of these are provided, the PureState
and the MixedState
. The PureState
is used for nonequilibrium simulations when the population is confined to a single state, whereas MixedState
allows for a mixed state distribution.
julia> using NQCDistributions
julia> PureState(1, Diabatic())
PureState{Diabatic}(1, Diabatic())
julia> PureState(2, Adiabatic())
PureState{Adiabatic}(2, Adiabatic())
julia> MixedState([1, 2], Diabatic())
MixedState{Int64, Diabatic}([1, 2], Diabatic())
These structs contain only the minimal information about the distributions, whereas the sampling of the distribution is handled separately by each of the different methods.
Product distributions - Combining Nuclear and electronic distributions
The initial nuclear and electronic states of a system can be combined in a product distribution, which can be used in place of a purely nuclear distribution for methods considering electronic dynamics.
nuclear_dist = DynamicalDistribution(velocity, position, (2, 2))
electronic_dist = PureState(2, Adiabatic())
total_dist = nuclear_dist * electronic_dist
rand(total_dist.nuclear) # Returns a random nuclear configuration
total_dist.electronic.state # Returns the chosen electronic state.