Skip to content

Commit 249bf39

Browse files
committed
Fixed the partially-vectorized quantum potential code in the Bohmian module, added multivariate Cauchy-Lorentzian functions as the TBFs
1 parent d47f00c commit 249bf39

1 file changed

Lines changed: 118 additions & 18 deletions

File tree

src/libra_py/dynamics/bohmian/compute.py

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,59 +35,113 @@
3535

3636
import torch
3737
import numpy as np
38+
import math
39+
from scipy.special import gamma
3840

3941
def rho_gaussian(q, Q, sigma):
4042
"""
4143
Args:
4244
* q (Tensor(ndof) ) - coordinate of the current point of interest
4345
* Q (Tensor(ntraj, ndof)) - coordinates of all trajectories
44-
* sigma (Tensor(ntraj, ndof)) - width parameter for each trajectory
46+
* sigma (float) - width parameter for each trajectory
4547
4648
Returns:
4749
Tensor(1) - probability density at the point of interest
4850
4951
"""
5052
_SQRT_2PI = torch.sqrt(torch.tensor(2.0 * torch.pi))
51-
53+
5254
ntraj, ndof = Q.shape[0], Q.shape[1]
5355
return torch.sum( (1.0/ntraj) * torch.prod( torch.exp( - 0.5*(q-Q)**2/sigma**2 )/(sigma * _SQRT_2PI ), 1, False) )
5456

5557

5658
def rho_lorentzian(q, Q, sigma):
5759
"""
5860
Args:
59-
* q (Tensor(ndof) ) - coordinate of the current point of interest
60-
* Q (Tensor(ntraj, ndof)) - coordinates of all trajectories
61-
* sigma (Tensor(ntraj, ndof)) - width parameter for each trajectory
61+
* q (Tensor(..., ndof) ) - coordinate of the current point of interest
62+
* Q (Tensor(..., ntraj, ndof)) - coordinates of all trajectories
63+
* sigma (float) - width parameter for each trajectory
6264
6365
Returns:
6466
Tensor(1) - probability density at the point of interest
6567
"""
66-
ntraj, ndof = Q.shape[0], Q.shape[1]
67-
y = torch.sum( (1.0/ntraj) * torch.prod( (1.0/torch.pi) * sigma/( (q-Q)**2 + sigma**2 ), 1, False) )
68-
return y
68+
# Old way
69+
#ntraj, ndof = Q.shape[0], Q.shape[1]
70+
#y = torch.sum( (1.0/ntraj) * torch.prod( (1.0/torch.pi) * sigma/( (q-Q)**2 + sigma**2 ), 1, False) )
71+
72+
# New way
73+
#ntraj, ndof = Q.shape[-2], Q.shape[-1]
74+
#norm = gamma(ndof / 2) / (math.pi**(ndof / 2) * sigma**(ndof - 1))
75+
#
76+
#LZ = sigma/( ((q-Q)**2).sum(axis=-1) + sigma**2)
77+
#f = torch.tensor( (1.0/ntraj) * norm * LZ.sum(axis=0), requires_grad=True)
78+
79+
ntraj, ndof = Q.shape
80+
81+
gamma_term = torch.exp(torch.lgamma(torch.tensor(ndof / 2.0, dtype=Q.dtype, device=Q.device)))
82+
norm = gamma_term / (math.pi**( 1 + ndof / 2) * sigma**(ndof - 1))
83+
84+
r2 = ((q - Q)**2).sum(dim=1) # shape: (ntraj,)
85+
LZ = sigma / (r2 + sigma**2) # shape: (ntraj,)
86+
87+
f = (1.0 / ntraj) * norm * LZ.sum() # no tensor wrapping
88+
89+
90+
return f
91+
92+
93+
def rho_mv_cauchy(q, Q, sigma):
94+
"""
95+
Multivariate Cauchy: https://en.wikipedia.org/wiki/Cauchy_distribution
96+
97+
Args:
98+
* q (Tensor(..., ndof) ) - coordinate of the current point of interest
99+
* Q (Tensor(..., ntraj, ndof)) - coordinates of all trajectories
100+
* sigma (float) - width parameter for each trajectory
101+
102+
Returns:
103+
Tensor(1) - probability density at the point of interest
104+
"""
105+
106+
ntraj, ndof = Q.shape
107+
# Normalization constant
108+
num = torch.lgamma(torch.tensor((ndof + 1) / 2))
109+
denom = (
110+
torch.lgamma(torch.tensor(0.5)) +
111+
0.5 * ndof * math.log(math.pi) +
112+
ndof * math.log(sigma)
113+
)
114+
norm = torch.exp(num - denom)
115+
116+
r2 = ((q - Q)**2).sum(dim=1) # shape: (ntraj,)
117+
LZ = (torch.tensor(1.0) + r2 / sigma**2) ** (-(ndof + 1) / 2) # shape: (ntraj,)
118+
119+
f = (1.0 / ntraj) * norm * LZ.sum() # no tensor wrapping
120+
121+
return f
122+
69123

70124

71125
def quantum_potential_original(Q, sigma, mass, TBF):
72126
"""
73127
Args:
74128
* Q (Tensor(ntraj, ndof)) - coordinates of all trajectories
75-
* sigma (Tensor(ndof)) - width parameters for each trajectory
76-
* mass ( Tensor(1, ndof)) - masses of all DOFs, same for all trajectories
129+
* sigma (float) - width parameters for each trajectory
130+
* mass ( Tensor( ndof)) - masses of all DOFs, same for all trajectories
77131
* TBF (object) - basis function reference (`rho_gaussian` or `rho_lorentzian`)
78132
79133
Returns:
80134
Tensor(1) - quantum potential summed over all trajectory points
81135
"""
82136

83-
ntraj, ndof = Q.shape[0], Q.shape[1]
137+
ntraj, ndof = Q.shape[-2], Q.shape[-1]
84138
U = torch.zeros( (1,), requires_grad=True)
85139
for k in range(ntraj):
86140
f = TBF(Q[k], Q, sigma);
87141
[deriv1] = torch.autograd.grad(f, [Q], create_graph=True, retain_graph=True);
88142
for i in range(ndof):
89143
[deriv2] = torch.autograd.grad(deriv1[k,i], [Q], create_graph=True, retain_graph=True);
90-
u = -(0.25/mass[0,i])*( deriv2[k, i]/f - 0.5 * (deriv1[k,i]/f)**2 );
144+
u = -(0.25/mass[i])*( deriv2[k, i]/f - 0.5 * (deriv1[k,i]/f)**2 );
91145
U = U + u
92146
return U
93147

@@ -97,30 +151,76 @@ def quantum_potential_original_gen(q, Q, sigma, mass, TBF):
97151
Args:
98152
* Q (Tensor(ntraj, ndof)) - coordinates of all trajectories
99153
* sigma (Tensor(ndof)) - width parameters for each trajectory
100-
* mass ( Tensor(1, ndof)) - masses of all DOFs, same for all trajectories
154+
* mass ( Tensor(ndof)) - masses of all DOFs, same for all trajectories
101155
* TBF (object) - basis function reference (`rho_gaussian` or `rho_lorentzian`)
102156
103157
Returns:
104158
Tensor(1) - quantum potential summed over all trajectory points
105159
"""
106160

107-
ntraj, ndof = Q.shape[0], Q.shape[1]
161+
ntraj, ndof = Q.shape[-2], Q.shape[-1]
108162
U = torch.zeros( (1,), requires_grad=True)
109163
f = TBF(q, Q, sigma);
110164
[deriv1] = torch.autograd.grad(f, [q], create_graph=True, retain_graph=True);
111165
for i in range(ndof):
112166
[deriv2] = torch.autograd.grad(deriv1[i], [q], create_graph=True, retain_graph=True);
113-
u = -(0.25/mass[0,i])*( deriv2[i]/f - 0.5 * (deriv1[i]/f)**2 );
167+
u = -(0.25/mass[i])*( deriv2[i]/f - 0.5 * (deriv1[i]/f)**2 );
114168
U = U + u
115169
return U
116170

117171

172+
def quantum_potential(Q, sigma, mass, TBF):
173+
"""
174+
Compute quantum potential in a partially vectorized way.
118175
176+
### BUT, HOPEFULLY, THIS IS CORRECT ###
119177
120-
def quantum_potential(Q, sigma, mass, TBF):
178+
Args:
179+
Q (Tensor): shape (ntraj, ndof), requires_grad=True
180+
sigma (float): scalar width
181+
mass (Tensor): shape (ndof,)
182+
TBF (function): q, Q, sigma → scalar
183+
184+
Returns:
185+
Tensor(1,) — scalar quantum potential
186+
"""
187+
188+
ntraj, ndof = Q.shape
189+
assert Q.requires_grad, "Q must have requires_grad=True"
190+
191+
U = 0.0
192+
for k in range(ntraj):
193+
# Compute scalar density f_k = rho(q_k; Q)
194+
f_k = TBF(Q[k], Q, sigma) # scalar
195+
196+
# First derivative: grad f_k w.r.t Q → shape: (ntraj, ndof)
197+
dfk = torch.autograd.grad(f_k, Q, create_graph=True, retain_graph=True)[0]
198+
199+
# Get ∂f_k/∂Q[k, i] across i
200+
dfk_k = dfk[k] # shape: (ndof,)
201+
202+
# Vectorized second derivatives: loop over i, but vectorized
203+
d2fk_k = torch.zeros(ndof, dtype=Q.dtype, device=Q.device)
204+
for i in range(ndof):
205+
grad_i = torch.autograd.grad(dfk[k, i], Q, create_graph=True, retain_graph=True)[0]
206+
d2fk_k[i] = grad_i[k, i] # ∂²f_k / ∂Q[k,i]²
207+
208+
# Compute quantum potential contribution from trajectory k
209+
f_k_safe = f_k + 1e-12 # prevent divide-by-zero
210+
term1 = d2fk_k / f_k_safe
211+
term2 = 0.5 * (dfk_k / f_k_safe) ** 2
212+
U_k = -0.25 / mass * (term1 - term2) # shape: (ndof,)
213+
U += U_k.sum()
214+
215+
return U
216+
217+
218+
def quantum_potential_old(Q, sigma, mass, TBF):
121219
"""
122220
Compute quantum potential in a fully vectorized way.
123221
222+
### THIS WAS INCORRECT MATHEMATICALLY ###
223+
124224
Args:
125225
Q (Tensor): shape (ntraj, ndof), requires_grad=True
126226
sigma (Tensor): shape (ntraj, ndof) or (ndof,)
@@ -261,7 +361,7 @@ def init_variables(ntraj, opt):
261361
q.requires_grad_(True)
262362
p.requires_grad_(True)
263363

264-
masses = torch.tensor([[mass, mass]])
364+
masses = torch.tensor([mass, mass])
265365

266366
return q, p, masses
267367

@@ -272,7 +372,7 @@ def md( q, p, mass_mat, params ):
272372
Args:
273373
* q (Tensor(ntraj, ndof)) - coordinates of all trajectories
274374
* p (Tensor(ntraj, ndof)) - momenta of all trajectories
275-
* mass_mat (Tensor(1, ndof)) - masses for all DOFs (same for all trajectories)
375+
* mass_mat (Tensor(ndof)) - masses for all DOFs (same for all trajectories)
276376
* params:
277377
- nsteps (int) - how many steps to do
278378
- dt (float) - integration timestep [in a.u.]

0 commit comments

Comments
 (0)