MultiOptPy 1.20.5__py3-none-any.whl → 1.20.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multioptpy/MD/thermostat.py +236 -123
- multioptpy/ModelHessian/fischerd3.py +240 -295
- multioptpy/Optimizer/rsirfo.py +112 -4
- multioptpy/Optimizer/rsprfo.py +1005 -698
- multioptpy/entrypoints.py +406 -16
- multioptpy/moleculardynamics.py +21 -13
- {multioptpy-1.20.5.dist-info → multioptpy-1.20.7.dist-info}/METADATA +10 -10
- {multioptpy-1.20.5.dist-info → multioptpy-1.20.7.dist-info}/RECORD +12 -12
- {multioptpy-1.20.5.dist-info → multioptpy-1.20.7.dist-info}/WHEEL +1 -1
- {multioptpy-1.20.5.dist-info → multioptpy-1.20.7.dist-info}/entry_points.txt +0 -0
- {multioptpy-1.20.5.dist-info → multioptpy-1.20.7.dist-info}/licenses/LICENSE +0 -0
- {multioptpy-1.20.5.dist-info → multioptpy-1.20.7.dist-info}/top_level.txt +0 -0
multioptpy/MD/thermostat.py
CHANGED
|
@@ -1,185 +1,298 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import copy
|
|
3
|
-
|
|
4
3
|
from multioptpy.Parameters.parameter import UnitValueLib, atomic_mass
|
|
5
4
|
|
|
6
|
-
|
|
7
5
|
class Thermostat:
|
|
8
|
-
def __init__(self, momentum_list, temperature, pressure, element_list=
|
|
6
|
+
def __init__(self, momentum_list, temperature, pressure, element_list=None):
|
|
7
|
+
# Mutable default argument fix
|
|
8
|
+
if element_list is None:
|
|
9
|
+
self.element_list = []
|
|
10
|
+
else:
|
|
11
|
+
self.element_list = element_list
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
# ---------------------------------------------------------
|
|
14
|
+
# [Optimization] Pre-compute masses for vectorization
|
|
15
|
+
# shape: (N_atoms, 1) to allow broadcasting: momentum (N,3) / mass (N,1)
|
|
16
|
+
# ---------------------------------------------------------
|
|
17
|
+
self.masses = np.array([atomic_mass(e) for e in self.element_list], dtype=np.float64)[:, None]
|
|
18
|
+
self.inverse_masses = 1.0 / self.masses
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
self.
|
|
14
|
-
self.pressure = pressure * (3.39893 * 10 ** (-11)) #kPa -> a.u.
|
|
15
|
-
self.initial_pressure = pressure * (3.39893 * 10 ** (-11)) #kPa -> a.u.
|
|
20
|
+
# Keep momentum as numpy array internally for performance
|
|
21
|
+
self.momentum_list = np.array(momentum_list, dtype=np.float64)
|
|
16
22
|
|
|
23
|
+
self.temperature = temperature # K
|
|
24
|
+
self.initial_temperature = temperature # K
|
|
25
|
+
|
|
26
|
+
# Pressure conversion
|
|
27
|
+
self.pressure = pressure * (3.39893 * 10 ** (-11)) # kPa -> a.u.
|
|
28
|
+
self.initial_pressure = self.pressure
|
|
29
|
+
|
|
30
|
+
# Thermostat Parameters
|
|
17
31
|
self.Langevin_zeta = 0.01
|
|
18
32
|
self.zeta = 0.0
|
|
19
33
|
self.eta = 0.0
|
|
20
34
|
self.scaling = 1.0
|
|
21
35
|
self.Ps_momentum = 0.0
|
|
22
36
|
|
|
37
|
+
# Degrees of freedom (3N)
|
|
23
38
|
self.g_value = len(momentum_list) * 3
|
|
24
|
-
self.Q_value = 1e-1
|
|
25
|
-
|
|
26
39
|
|
|
40
|
+
# Constants
|
|
41
|
+
self.Q_value = 1e-1
|
|
27
42
|
self.M_value = 1e+12
|
|
28
43
|
self.Boltzmann_constant = 3.16681 * 10 ** (-6) # hartree/K
|
|
29
44
|
self.delta_timescale = 1e-1
|
|
30
|
-
self.volume = 1e-23 * (1/UnitValueLib().bohr2m) ** 3#m^3 -> Bohr^3
|
|
45
|
+
self.volume = 1e-23 * (1/UnitValueLib().bohr2m) ** 3 # m^3 -> Bohr^3
|
|
31
46
|
|
|
32
|
-
# Nose-Hoover
|
|
33
|
-
self.Q_value_chain = [1.0, 2.0, 3.0, 6.0, 10.0, 20, 40, 50, 100, 200]
|
|
34
|
-
self.zeta_chain =
|
|
47
|
+
# Nose-Hoover Chain Parameters
|
|
48
|
+
self.Q_value_chain = np.array([1.0, 2.0, 3.0, 6.0, 10.0, 20, 40, 50, 100, 200], dtype=np.float64)
|
|
49
|
+
self.zeta_chain = np.zeros(len(self.Q_value_chain), dtype=np.float64)
|
|
35
50
|
self.timestep = None
|
|
36
51
|
|
|
52
|
+
# History
|
|
37
53
|
self.Instantaneous_temperatures_list = []
|
|
38
54
|
self.Instantaneous_momentum_list = []
|
|
55
|
+
self.tot_kinetic_ene = 0.0
|
|
56
|
+
self.Instantaneous_temperature = 0.0
|
|
57
|
+
|
|
58
|
+
# =========================================================================
|
|
59
|
+
# Internal Helpers (Vectorized & Logical Separation)
|
|
60
|
+
# =========================================================================
|
|
61
|
+
|
|
62
|
+
def _update_momentum(self, forces, scaling=1.0):
|
|
63
|
+
"""Vectorized momentum update"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def _update_position(self, current_geometry_arr, dt):
|
|
67
|
+
"""Vectorized position update: r(t+dt) = r(t) + v(t)*dt"""
|
|
68
|
+
# v = p / m
|
|
69
|
+
velocities = self.momentum_list * self.inverse_masses
|
|
70
|
+
return current_geometry_arr + velocities * dt
|
|
71
|
+
|
|
72
|
+
def _propagate_nhc_zeta(self, dt, kinetic_energy_2x):
|
|
73
|
+
"""
|
|
74
|
+
Propagate Nose-Hoover Chain variables.
|
|
75
|
+
"""
|
|
76
|
+
# 1. Update first chain link force
|
|
77
|
+
driving_force = (kinetic_energy_2x - self.g_value * self.Boltzmann_constant * self.initial_temperature)
|
|
39
78
|
|
|
40
|
-
self.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
79
|
+
self.zeta_chain[0] += dt * driving_force / self.Q_value_chain[0]
|
|
80
|
+
self.zeta_chain[0] -= dt * self.zeta_chain[0] * self.zeta_chain[1] # Coupling with next
|
|
81
|
+
|
|
82
|
+
# 2. Update middle chain links
|
|
83
|
+
for j in range(1, len(self.zeta_chain)-1):
|
|
84
|
+
driving_force_j = (self.Q_value_chain[j-1] * self.zeta_chain[j-1]**2 - self.Boltzmann_constant * self.initial_temperature)
|
|
85
|
+
self.zeta_chain[j] += dt * driving_force_j / self.Q_value_chain[j]
|
|
86
|
+
self.zeta_chain[j] -= dt * self.zeta_chain[j] * self.zeta_chain[j+1]
|
|
87
|
+
|
|
88
|
+
# 3. Update last chain link
|
|
89
|
+
last = -1
|
|
90
|
+
driving_force_last = (self.Q_value_chain[last-1] * self.zeta_chain[last-1]**2 - self.Boltzmann_constant * self.initial_temperature)
|
|
91
|
+
self.zeta_chain[last] += dt * driving_force_last / self.Q_value_chain[last]
|
|
92
|
+
|
|
93
|
+
# =========================================================================
|
|
94
|
+
# Public API (Compatible with moleculardynamics.py)
|
|
95
|
+
# =========================================================================
|
|
96
|
+
|
|
44
97
|
def calc_tot_kinetic_energy(self):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self.
|
|
50
|
-
|
|
98
|
+
"""
|
|
99
|
+
Vectorized calculation of total kinetic energy.
|
|
100
|
+
KE = sum(p^2 / 2m)
|
|
101
|
+
"""
|
|
102
|
+
p_sq = self.momentum_list ** 2
|
|
103
|
+
p_sq_atom = np.sum(p_sq, axis=1)
|
|
104
|
+
self.tot_kinetic_ene = np.sum(p_sq_atom / (2.0 * self.masses.flatten()))
|
|
105
|
+
return self.tot_kinetic_ene
|
|
51
106
|
|
|
52
107
|
def calc_inst_temperature(self):
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Instantaneous_temperature = 2 * tot_kinetic_ene / (self.g_value * self.Boltzmann_constant)
|
|
56
|
-
print("Instantaneous_temperature:
|
|
57
|
-
|
|
58
|
-
self.Instantaneous_temperature = Instantaneous_temperature
|
|
59
|
-
#-----------------
|
|
60
|
-
return Instantaneous_temperature
|
|
108
|
+
"""Calculates and stores instantaneous temperature."""
|
|
109
|
+
self.calc_tot_kinetic_energy()
|
|
110
|
+
self.Instantaneous_temperature = 2.0 * self.tot_kinetic_ene / (self.g_value * self.Boltzmann_constant)
|
|
111
|
+
print(f"Instantaneous_temperature: {self.Instantaneous_temperature:.8f} K")
|
|
112
|
+
return self.Instantaneous_temperature
|
|
61
113
|
|
|
62
114
|
def add_inst_temperature_list(self):
|
|
63
|
-
#self.add_inst_temperature_list()
|
|
64
115
|
self.Instantaneous_temperatures_list.append(self.Instantaneous_temperature)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def Nose_Hoover_thermostat(self, geom_num_list, new_g):#fixed volume #NVT ensemble
|
|
68
|
-
new_g *= -1
|
|
69
|
-
self.momentum_list = self.momentum_list * np.exp(-self.delta_timescale * self.zeta * 0.5)
|
|
70
116
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
117
|
+
def Nose_Hoover_thermostat(self, geom_num_list, new_g): # fixed volume #NVT ensemble
|
|
118
|
+
"""
|
|
119
|
+
Single Nose-Hoover implementation.
|
|
120
|
+
"""
|
|
121
|
+
geom_arr = np.array(geom_num_list, dtype=np.float64)
|
|
122
|
+
force = -1.0 * np.array(new_g, dtype=np.float64)
|
|
123
|
+
|
|
124
|
+
# 1. First half-step thermostat scaling
|
|
125
|
+
self.momentum_list *= np.exp(-self.delta_timescale * self.zeta * 0.5)
|
|
126
|
+
|
|
127
|
+
# 2. First half-step momentum update (Force contribution)
|
|
128
|
+
self.momentum_list += force * self.delta_timescale * 0.5
|
|
76
129
|
|
|
77
|
-
|
|
78
|
-
|
|
130
|
+
print("NVT ensemble (Nose_Hoover) : Sum of momenta (absolute value):", np.sum(np.abs(self.momentum_list)))
|
|
131
|
+
|
|
132
|
+
# 3. Position update (Full step)
|
|
133
|
+
new_geometry = self._update_position(geom_arr, self.delta_timescale)
|
|
134
|
+
|
|
135
|
+
# 4. Thermostat Propagation
|
|
79
136
|
self.calc_inst_temperature()
|
|
80
137
|
self.add_inst_temperature_list()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
138
|
+
|
|
139
|
+
driving_force = (2 * self.tot_kinetic_ene - self.g_value * self.Boltzmann_constant * self.initial_temperature)
|
|
140
|
+
self.zeta += self.delta_timescale * driving_force / self.Q_value
|
|
141
|
+
|
|
142
|
+
# 5. Second half-step momentum update
|
|
143
|
+
self.momentum_list += force * self.delta_timescale * 0.5
|
|
144
|
+
|
|
145
|
+
# 6. Second half-step thermostat scaling
|
|
146
|
+
self.momentum_list *= np.exp(-self.delta_timescale * self.zeta * 0.5)
|
|
147
|
+
|
|
148
|
+
return new_geometry # Corrected: Returns numpy array, not list
|
|
149
|
+
|
|
150
|
+
def Nose_Hoover_chain_thermostat(self, geom_num_list, new_g): # fixed volume #NVT ensemble
|
|
151
|
+
"""
|
|
152
|
+
Nose-Hoover Chain implementation.
|
|
153
|
+
"""
|
|
154
|
+
geom_arr = np.array(geom_num_list, dtype=np.float64)
|
|
155
|
+
force = -1.0 * np.array(new_g, dtype=np.float64)
|
|
156
|
+
|
|
157
|
+
# 1. First half-step thermostat scaling
|
|
158
|
+
self.momentum_list *= np.exp(-self.delta_timescale * self.zeta_chain[0] * 0.5)
|
|
159
|
+
|
|
160
|
+
# 2. First half-step momentum update
|
|
161
|
+
self.momentum_list += force * self.delta_timescale * 0.5
|
|
85
162
|
|
|
163
|
+
print("NVT ensemble (Nose_Hoover chain) : Sum of momenta (absolute value):", np.sum(np.abs(self.momentum_list)))
|
|
86
164
|
|
|
87
|
-
|
|
88
|
-
|
|
165
|
+
# 3. Position update
|
|
166
|
+
new_geometry = self._update_position(geom_arr, self.delta_timescale)
|
|
167
|
+
|
|
168
|
+
# 4. Thermostat Propagation
|
|
169
|
+
self.calc_inst_temperature()
|
|
170
|
+
self.add_inst_temperature_list()
|
|
89
171
|
|
|
172
|
+
self._propagate_nhc_zeta(self.delta_timescale, 2 * self.tot_kinetic_ene)
|
|
90
173
|
|
|
91
|
-
|
|
174
|
+
print("zeta_list (Coefficient of friction): ", self.zeta_chain)
|
|
92
175
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
new_g *= -1
|
|
96
|
-
self.momentum_list = self.momentum_list * np.exp(-self.delta_timescale * self.zeta_chain[0] * 0.5)
|
|
176
|
+
# 5. Second half-step momentum update
|
|
177
|
+
self.momentum_list += force * self.delta_timescale * 0.5
|
|
97
178
|
|
|
98
|
-
|
|
99
|
-
|
|
179
|
+
# 6. Second half-step thermostat scaling
|
|
180
|
+
self.momentum_list *= np.exp(-self.delta_timescale * self.zeta_chain[0] * 0.5)
|
|
100
181
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
182
|
+
return new_geometry # Corrected: Returns numpy array, not list
|
|
183
|
+
|
|
184
|
+
def Velocity_Verlet(self, geom_num_list, new_g, prev_g, iter): # NVE ensemble
|
|
185
|
+
"""
|
|
186
|
+
Velocity Verlet integration.
|
|
187
|
+
"""
|
|
188
|
+
geom_arr = np.array(geom_num_list, dtype=np.float64)
|
|
104
189
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
self.calc_inst_temperature()
|
|
108
|
-
self.add_inst_temperature_list()
|
|
109
|
-
#----------
|
|
110
|
-
self.zeta_chain[0] += self.delta_timescale * (2 * self.tot_kinetic_ene - self.g_value * self.Boltzmann_constant * self.initial_temperature) / self.Q_value_chain[0] -1* self.delta_timescale * (self.zeta_chain[0] * self.zeta_chain[1])
|
|
190
|
+
force_new = -1.0 * np.array(new_g, dtype=np.float64)
|
|
191
|
+
force_prev = -1.0 * np.array(prev_g, dtype=np.float64)
|
|
111
192
|
|
|
112
|
-
|
|
113
|
-
|
|
193
|
+
# 1. Update Momentum
|
|
194
|
+
self.momentum_list += (force_new + force_prev) * self.delta_timescale * 0.5
|
|
195
|
+
|
|
196
|
+
# 2. Position Update
|
|
197
|
+
term1 = (self.momentum_list * self.inverse_masses) * self.delta_timescale
|
|
198
|
+
term2 = (force_new * self.inverse_masses) * (self.delta_timescale**2 / 2.0)
|
|
114
199
|
|
|
115
|
-
|
|
200
|
+
new_geometry = geom_arr + term1 + term2
|
|
116
201
|
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
self.momentum_list += new_g * self.delta_timescale * 0.5
|
|
121
|
-
self.momentum_list = self.momentum_list * np.exp(-self.delta_timescale * self.zeta_chain[0] * 0.5)
|
|
122
|
-
print("zeta_list (Coefficient of friction): ", self.zeta_chain)
|
|
123
|
-
return new_geometry
|
|
124
|
-
|
|
125
|
-
def Velocity_Verlet(self, geom_num_list, new_g, prev_g, iter):#NVE ensemble
|
|
126
|
-
tmp_new_g = copy.copy(-1*new_g)
|
|
127
|
-
tmp_prev_g = copy.copy(-1*prev_g)
|
|
128
|
-
#print("NVE ensemble (Velocity_Verlet)")
|
|
129
|
-
self.momentum_list += ( tmp_new_g + tmp_prev_g ) * (self.delta_timescale) * 0.5 #+ third_term + fourth_term
|
|
130
|
-
#-----------
|
|
131
|
-
tmp_list = []
|
|
132
|
-
for i, elem in enumerate(self.element_list):
|
|
133
|
-
tmp_list.append(self.delta_timescale * self.momentum_list[i] / atomic_mass(elem) + self.delta_timescale ** 2 * tmp_new_g[i] / (2.0 * atomic_mass(elem)))
|
|
134
|
-
new_geometry = geom_num_list + tmp_list
|
|
135
|
-
#------------
|
|
202
|
+
# Stats
|
|
136
203
|
self.calc_inst_temperature()
|
|
137
204
|
self.add_inst_temperature_list()
|
|
138
|
-
#-------------
|
|
139
205
|
|
|
140
|
-
return new_geometry
|
|
141
|
-
|
|
206
|
+
return new_geometry # Corrected: Returns numpy array, not list
|
|
142
207
|
|
|
143
|
-
|
|
144
208
|
def generate_normal_random_variables(self, n_variables):
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
z1 = np.sqrt(-2 * np.log(u1)) * np.cos(2 * np.pi * u2)
|
|
150
|
-
z2 = np.sqrt(-2 * np.log(u1)) * np.sin(2 * np.pi * u2)
|
|
151
|
-
random_variables.extend([z1, z2])
|
|
209
|
+
"""Vectorized Box-Muller transformation"""
|
|
210
|
+
n_pairs = (n_variables + 1) // 2
|
|
211
|
+
u1 = np.random.rand(n_pairs)
|
|
212
|
+
u2 = np.random.rand(n_pairs)
|
|
152
213
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
z1 = np.sqrt(-2 * np.log(u1)) * np.cos(2 * np.pi * u2)
|
|
156
|
-
random_variables.append(z1)
|
|
214
|
+
r = np.sqrt(-2 * np.log(u1))
|
|
215
|
+
theta = 2 * np.pi * u2
|
|
157
216
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def calc_rand_moment_based_on_boltzman_const(self, random_variables):
|
|
161
|
-
rand_moment = random_variables
|
|
162
|
-
|
|
163
|
-
for i in range(len(self.element_list)):
|
|
164
|
-
random_variables[i] *= np.sqrt(self.Boltzmann_constant * self.temperature / atomic_mass(self.element_list[i]))
|
|
217
|
+
z1 = r * np.cos(theta)
|
|
218
|
+
z2 = r * np.sin(theta)
|
|
165
219
|
|
|
220
|
+
result = np.empty(n_pairs * 2)
|
|
221
|
+
result[0::2] = z1
|
|
222
|
+
result[1::2] = z2
|
|
166
223
|
|
|
224
|
+
return result[:n_variables]
|
|
225
|
+
|
|
226
|
+
def calc_rand_moment_based_on_boltzman_const(self, random_variables):
|
|
227
|
+
"""
|
|
228
|
+
Scales random variables by sqrt(kB * T * m).
|
|
229
|
+
"""
|
|
230
|
+
rand_moment = np.array(random_variables, dtype=np.float64)
|
|
231
|
+
scale_factors = np.sqrt(self.Boltzmann_constant * self.temperature * self.masses)
|
|
232
|
+
rand_moment *= scale_factors
|
|
167
233
|
return rand_moment
|
|
168
234
|
|
|
169
|
-
|
|
170
235
|
def init_purtubation(self, geometry, B_e, B_g):
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
for i in range(len(self.element_list)):
|
|
177
|
-
init_momentum[i] += addtional_velocity[i] * atomic_mass(self.element_list[i])
|
|
178
|
-
|
|
236
|
+
"""Initializes momenta with random thermal noise."""
|
|
237
|
+
N = len(self.element_list)
|
|
238
|
+
random_vars = self.generate_normal_random_variables(N * 3).reshape(N, 3)
|
|
239
|
+
v_thermal = random_vars * np.sqrt(self.Boltzmann_constant * self.temperature * self.inverse_masses)
|
|
240
|
+
init_momentum = v_thermal * self.masses
|
|
179
241
|
|
|
180
242
|
self.momentum_list += init_momentum
|
|
181
243
|
self.init_energy = B_e
|
|
182
|
-
#self.init_hamiltonian = B_e
|
|
183
|
-
#for i, elem in enumerate(element_list):
|
|
184
|
-
# self.init_hamiltonian += (np.sum(self.momentum_list[i]) ** 2 / (2 * atomic_mass(elem)))
|
|
185
244
|
return
|
|
245
|
+
def Langevin_thermostat(self, geom_num_list, new_g):
|
|
246
|
+
"""
|
|
247
|
+
Langevin Dynamics (BAOAB integrator)
|
|
248
|
+
Reference: B. Leimkuhler and C. Matthews, J. Chem. Phys. 138, 174102 (2013).
|
|
249
|
+
|
|
250
|
+
Structure:
|
|
251
|
+
B: Momentum += 0.5 * dt * Force
|
|
252
|
+
A: Position += 0.5 * dt * Velocity
|
|
253
|
+
O: Momentum = c1 * Momentum + c2 * Noise (Ornstein-Uhlenbeck)
|
|
254
|
+
A: Position += 0.5 * dt * Velocity
|
|
255
|
+
B: Momentum += 0.5 * dt * Force
|
|
256
|
+
"""
|
|
257
|
+
geom_arr = np.array(geom_num_list, dtype=np.float64)
|
|
258
|
+
force = -1.0 * np.array(new_g, dtype=np.float64)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
dt = self.delta_timescale
|
|
262
|
+
gamma = self.Langevin_zeta # (1/time)
|
|
263
|
+
target_temp = self.initial_temperature
|
|
264
|
+
|
|
265
|
+
#
|
|
266
|
+
c1 = np.exp(-gamma * dt)
|
|
267
|
+
c2 = np.sqrt(1.0 - c1**2)
|
|
268
|
+
|
|
269
|
+
# sigma = sqrt(m * kB * T)
|
|
270
|
+
# self.masses shape: (N, 1) -> broadcasting works
|
|
271
|
+
sigma = np.sqrt(self.masses * self.Boltzmann_constant * target_temp)
|
|
272
|
+
|
|
273
|
+
# 1. B: Half-step Momentum Update (Force)
|
|
274
|
+
self.momentum_list += 0.5 * dt * force
|
|
275
|
+
|
|
276
|
+
# 2. A: Half-step Position Update
|
|
277
|
+
# r(t+dt/2) = r(t) + 0.5 * dt * v(t+dt/2)
|
|
278
|
+
new_geometry = self._update_position(geom_arr, 0.5 * dt)
|
|
279
|
+
|
|
280
|
+
# 3. O: Fluctuation-Dissipation (Thermostat)
|
|
281
|
+
# p' = c1 * p + c2 * sigma * noise
|
|
282
|
+
|
|
283
|
+
noise = np.random.normal(0.0, 1.0, self.momentum_list.shape)
|
|
284
|
+
self.momentum_list = c1 * self.momentum_list + c2 * sigma * noise
|
|
285
|
+
|
|
286
|
+
# 4. A: Half-step Position Update
|
|
287
|
+
# r(t+dt) = r(t+dt/2) + 0.5 * dt * v'
|
|
288
|
+
new_geometry = self._update_position(new_geometry, 0.5 * dt)
|
|
289
|
+
|
|
290
|
+
# 5. B: Half-step Momentum Update (Force)
|
|
291
|
+
|
|
292
|
+
self.momentum_list += 0.5 * dt * force
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
self.calc_inst_temperature()
|
|
296
|
+
self.add_inst_temperature_list()
|
|
297
|
+
|
|
298
|
+
return new_geometry
|