|
| 1 | ++++ |
| 2 | +cover = false |
| 3 | ++++ |
| 4 | + |
| 5 | +# Canonical Velocity Rescaling Thermostat |
| 6 | + |
| 7 | +This thermostat is the most widely used thermostat for molecular dynamics (MD) |
| 8 | +simulations. The [manuscript](https://pubs.aip.org/aip/jcp/article-abstract/126/1/014101/186581/Canonical-sampling-through-velocity-rescaling?redirectedFrom=fulltext) it was |
| 9 | +published in is one of the highest cited papers related to MD simulations. |
| 10 | +Due to the massive success of this thermostat there is a wide array of resources |
| 11 | +explaining the thermostat and the math behind it in simple terms. However, none |
| 12 | +of these resources show how to implement the thermostat in a programming language. |
| 13 | +Making the source code of MD packages the only option, but they are often very |
| 14 | +difficult to follow. Since I've implemented this thermostat in Julia for my |
| 15 | +MD package ([YASS](https://github.com/Cavenfish/YetAnotherSimulationSuite.jl)), |
| 16 | +I wanted to put out a short explanation of implementing it. |
| 17 | + |
| 18 | +## Brief Math Explanation |
| 19 | + |
| 20 | +The temperature of a system is related to the total kineteic energy of a system |
| 21 | +by, |
| 22 | + |
| 23 | +$ |
| 24 | +K = \frac{1}{2} k_B N_f T |
| 25 | +$ |
| 26 | + |
| 27 | +where $K$ is the kinetic energy, $k_B$ is Boltzmann constant, $N_f$ is the |
| 28 | +degrees of freedom, and $T$ is the temperature. A velocity rescaling thermostat |
| 29 | +takes advantage of this experssion by scaling the velocities to increase or |
| 30 | +decrease the temperature of the system. Typically through a scaling factor |
| 31 | +($\alpha$), |
| 32 | + |
| 33 | +$ |
| 34 | +\alpha = \sqrt{\frac{K_\text{target}}{K\text{current}}} |
| 35 | +$ |
| 36 | + |
| 37 | +where $K_\text{target}$ is the target kinetic energy, and $K\text{current}$ is |
| 38 | +the kinetic energy of the current timeframe of the simulation. The CVR thermostat |
| 39 | +enforces a canonical sampling for $K_\text{target}$ to ensure proper |
| 40 | +thermodynamic proporties in the simulation. The explicit derivation of this |
| 41 | +can be found in the original manuscript, or even other online resources. For |
| 42 | +the sake of just learning to implement the thermostat only these two concepts |
| 43 | +are necessary. |
| 44 | + |
| 45 | +## Coding it Up |
| 46 | + |
| 47 | +We first need a function that calculates the temperature of our system from |
| 48 | +the kinetic energy. We can calculate this by taking $\sum_i m_i v_i^2 = k_B N_f T$, |
| 49 | +where the $\frac{1}{2}$ is dropped from both sides. |
| 50 | + |
| 51 | +```julia |
| 52 | +function getTemp(v::AbstractVector, m::AbstractVector, kB::Float64) |
| 53 | + # Leave out the 1/2 to get 2Ekin for T calc |
| 54 | + N = length(m) |
| 55 | + Nf = 3N - 3 |
| 56 | + v2 = [i'i for i in v] |
| 57 | + Ekin = sum(m .* v2) |
| 58 | + |
| 59 | + Ekin / (kB * Nf) |
| 60 | +end |
| 61 | +``` |
| 62 | + |
| 63 | +The next thing we need is a function for sampling from a normal and gamma |
| 64 | +distributions. You could implement this from scratch but its better to use the |
| 65 | +implementations in [Distributions.jl](https://juliastats.org/Distributions.jl/stable/) |
| 66 | +since they are well tested and highly performant. These two functions are |
| 67 | +`Normal` and `Gamma` in the code blocks below. |
| 68 | + |
| 69 | +The CVR thermostat needs to have non-zero velocities to scale (or else you still |
| 70 | +have zero after scaling), so if your system has all zero velocities (or $T=0$) |
| 71 | +then you skip apply the thermostat and let the system gain some amount of kinetic |
| 72 | +energy due to the potential. This means adding a conditional return in your CVR |
| 73 | +function to avoid wasteful computations. In this example I use a short circuit |
| 74 | +expression (which I love but some hate), so I'll quickly explain them. These |
| 75 | +expressions take advantage of the and/or logic by using the `&&` and `||` |
| 76 | +operators to write compact if-then statements. |
| 77 | + |
| 78 | +```julia |
| 79 | +# This shows a typical if statment within a for loop |
| 80 | +for i = 1:100 |
| 81 | + if i % 10 == 0 |
| 82 | + println(i) |
| 83 | + end |
| 84 | +end |
| 85 | + |
| 86 | +# This does the same thing but using a short circuit expression |
| 87 | +for i = 1:100 |
| 88 | + i % 10 == 0 && println(i) |
| 89 | +end |
| 90 | + |
| 91 | +# Again, the same thing but using || |
| 92 | +for i = 1:100 |
| 93 | + i % 10 != 0 || println(i) |
| 94 | +end |
| 95 | +``` |
| 96 | + |
| 97 | +Getting back to the CVR thermostat, once we have a non-zero kinetic energy |
| 98 | +we can rescale the velocities to try to reach our desired temperature. |
| 99 | + |
| 100 | +```julia |
| 101 | +function CVR!( |
| 102 | + v::AbstractVector, m::AbstractVector, |
| 103 | + kB::Float64, tau::Float64, Ttar::Float64 |
| 104 | +) |
| 105 | + N = length(m) |
| 106 | + Tsim = getTemp(v, m, kB) |
| 107 | + |
| 108 | + Tsim == 0.0 && return |
| 109 | + |
| 110 | + Nf = 3N - 3 |
| 111 | + n = Normal(0.0,1.0) |
| 112 | + R1 = rand(n) |
| 113 | + |
| 114 | + R2 = if (Nf-1)%2 == 0 |
| 115 | + alpha = (Nf - 1)/2 |
| 116 | + G = Gamma(alpha, 1) |
| 117 | + |
| 118 | + 2 .* rand(G) |
| 119 | + else |
| 120 | + alpha = (Nf - 2)/2 |
| 121 | + G = Gamma(alpha, 1) |
| 122 | + |
| 123 | + 2 .* rand(G) .+ (rand(n) .^2) |
| 124 | + end |
| 125 | + |
| 126 | + tau > 0.1 ? c1 = exp(-1/tau) : c1 = 0.0 |
| 127 | + |
| 128 | + K = 0.5 * Nf * kB * Tsim |
| 129 | + sigma = 0.5 * Nf * kB * Ttar |
| 130 | + |
| 131 | + Knew = @. ( |
| 132 | + K + (1-c1) * (sigma * (R2 + R1^2) / Nf - K) + |
| 133 | + 2 * R1 * sqrt(K * sigma / Nf * (1-c1) * c1) |
| 134 | + ) |
| 135 | + alpha2 = @. sqrt(Knew / K) |
| 136 | + |
| 137 | + @. v *= alpha2 |
| 138 | +end |
| 139 | +``` |
0 commit comments