Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowing SHAKE/RATTLE constraints on GPU simulation #175

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/constraints/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Base.length(cc::ConstraintCluster) = length(cc.constraints)
Disables neighbor list interactions between atoms in a constraint.
"""
function disable_constrained_interactions!(neighbor_finder, constraint_clusters)
for cluster in constraint_clusters
CUDA.@allowscalar for cluster in constraint_clusters
for constraint in cluster.constraints
neighbor_finder.eligible[constraint.i, constraint.j] = false
neighbor_finder.eligible[constraint.j, constraint.i] = false
Expand Down
224 changes: 161 additions & 63 deletions src/constraints/shake.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,84 +37,91 @@
clusters, dist_tolerance, vel_tolerance)
end

function apply_position_constraints!(sys, ca::SHAKE_RATTLE, coord_storage;
n_threads::Integer=Threads.nthreads())
function apply_position_constraints!(sys::System{D,false}, ca::SHAKE_RATTLE, coord_storage;
n_threads::Integer=Threads.nthreads()) where D
# SHAKE updates
converged = false

while !converged
for cluster in ca.clusters # Cannot parallelize this
for constraint in cluster.constraints
k1, k2 = constraint.i, constraint.j

# Vector between the atoms after unconstrained update (s)
s12 = vector(sys.coords[k1], sys.coords[k2], sys.boundary)

# Vector between the atoms before unconstrained update (r)
r12 = vector(coord_storage[k1], coord_storage[k2], sys.boundary)

if abs(norm(s12) - constraint.dist) > ca.dist_tolerance
m1_inv = inv(mass(sys.atoms[k1]))
m2_inv = inv(mass(sys.atoms[k2]))
a = (m1_inv + m2_inv)^2 * sum(abs2, r12)
b = -2 * (m1_inv + m2_inv) * dot(r12, s12)
c = sum(abs2, s12) - (constraint.dist)^2
D = b^2 - 4*a*c

if ustrip(D) < 0.0
@warn "SHAKE determinant negative, setting to 0.0"
D = zero(D)
end

# Quadratic solution for g
α1 = (-b + sqrt(D)) / (2*a)
α2 = (-b - sqrt(D)) / (2*a)

g = abs(α1) <= abs(α2) ? α1 : α2

# Update positions
δri1 = r12 .* (g*m1_inv)
δri2 = r12 .* (-g*m2_inv)

sys.coords[k1] += δri1
sys.coords[k2] += δri2
end
end
for cluster in ca.clusters
apply_position_constraints_kernel!(sys.coords, sys.atoms, sys.boundary, cluster, coord_storage, ca.dist_tolerance)
end
converged = check_position_constraints(sys, ca)
end
return sys
end

function apply_position_constraints!(sys::System{D,true}, ca::SHAKE_RATTLE, coord_storage;

Check warning on line 53 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L53

Added line #L53 was not covered by tests
n_threads::Integer=Threads.nthreads()) where {D}
# SHAKE updates
converged = false
clusters = cu(ca.clusters)
n_threads_gpu, n_blocks = cuda_threads_blocks_specific(length(ca.clusters))
while !converged
CUDA.@sync @cuda threads = n_threads_gpu blocks = n_blocks apply_position_constraints_gpu!(

Check warning on line 60 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L56-L60

Added lines #L56 - L60 were not covered by tests
sys.coords, sys.atoms, sys.boundary, clusters, coord_storage, ca.dist_tolerance)
converged = check_position_constraints(sys, ca)
end
return sys
end

function apply_velocity_constraints!(sys, ca::SHAKE_RATTLE; n_threads::Integer=Threads.nthreads())
# RATTLE updates
converged = false
function apply_position_constraints_gpu!(coords, atoms, boundary, clusters, coord_storage, dist_tolerance)
i = (blockIdx().x - 1) * blockDim().x + threadIdx().x
(i > length(clusters)) && return nothing
cluster = clusters[i]
apply_position_constraints_kernel!(coords, atoms, boundary, cluster, coord_storage, dist_tolerance)

Check warning on line 71 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L67-L71

Added lines #L67 - L71 were not covered by tests
end

while !converged
for cluster in ca.clusters # Cannot parallelize this
for constraint in cluster.constraints
k1, k2 = constraint.i, constraint.j
function apply_position_constraints_kernel!(coords, atoms, boundary, cluster, coord_storage, dist_tolerance)
@inbounds for constraint in cluster.constraints # Cannot parallelize this
k1, k2 = constraint.i, constraint.j

# Vector between the atoms after unconstrained update (s)
s12 = vector(coords[k1], coords[k2], boundary)

# Vector between the atoms before unconstrained update (r)
r12 = vector(coord_storage[k1], coord_storage[k2], boundary)
if abs(norm(s12) - constraint.dist) > dist_tolerance
m1_inv = inv(mass(atoms[k1]))
m2_inv = inv(mass(atoms[k2]))
a = (m1_inv + m2_inv)^2 * sum(abs2, r12)
b = -2 * (m1_inv + m2_inv) * dot(r12, s12)
c = sum(abs2, s12) - (constraint.dist)^2
D = b^2 - 4 * a * c

inv_m1 = inv(mass(sys.atoms[k1]))
inv_m2 = inv(mass(sys.atoms[k2]))
if ustrip(D) < 0.0
# @warn "SHAKE determinant negative, setting to 0.0"
D = zero(D)

Check warning on line 93 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L93

Added line #L93 was not covered by tests
end

# Quadratic solution for g
α1 = (-b + sqrt(D)) / (2 * a)
α2 = (-b - sqrt(D)) / (2 * a)

# Vector between the atoms after SHAKE constraint
r_k1k2 = vector(sys.coords[k1], sys.coords[k2], sys.boundary)
g = abs(α1) <= abs(α2) ? α1 : α2

# Difference between unconstrainted velocities
v_k1k2 = sys.velocities[k2] .- sys.velocities[k1]
# Update positions
δri1 = r12 .* (g * m1_inv)
δri2 = r12 .* (-g * m2_inv)

err = abs(dot(r_k1k2, v_k1k2))
if err > ca.vel_tolerance
# Re-arrange constraint equation to solve for Lagrange multiplier
# This has a factor of dt which cancels out in the velocity update
λₖ = -dot(r_k1k2, v_k1k2) / (dot(r_k1k2, r_k1k2) * (inv_m1 + inv_m2))
coords[k1] += δri1
coords[k2] += δri2
end
end
end

# Correct velocities
sys.velocities[k1] -= inv_m1 .* λₖ .* r_k1k2
sys.velocities[k2] += inv_m2 .* λₖ .* r_k1k2
end
function apply_velocity_constraints!(sys, ca::SHAKE_RATTLE; n_threads::Integer=Threads.nthreads())
# RATTLE updates
converged = false

while !converged
if is_on_gpu(sys)
clusters = cu(ca.clusters)
n_threads_gpu, n_blocks = cuda_threads_blocks_specific(length(ca.clusters))
CUDA.@sync @cuda threads = n_threads_gpu blocks = n_blocks apply_velocity_constraints_gpu!(

Check warning on line 120 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L118-L120

Added lines #L118 - L120 were not covered by tests
sys.coords, sys.atoms, sys.velocities, sys.boundary, clusters, ca.vel_tolerance)
else
for cluster in ca.clusters
apply_velocity_constraints_kernel!(sys.coords, sys.atoms, sys.velocities, sys.boundary, cluster, ca.vel_tolerance)
end
end

Expand All @@ -123,12 +130,46 @@
return sys
end

function apply_velocity_constraints_gpu!(coords, atoms, velocities, boundary, clusters, vel_tolerance)
i = (blockIdx().x - 1) * blockDim().x + threadIdx().x
(i > length(clusters)) && return nothing
cluster = clusters[i]
apply_velocity_constraints_kernel!(coords, atoms, velocities, boundary, cluster, vel_tolerance)

Check warning on line 137 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L133-L137

Added lines #L133 - L137 were not covered by tests
end

function apply_velocity_constraints_kernel!(coords, atoms, velocities, boundary, cluster, vel_tolerance)
@inbounds for constraint in cluster.constraints # Cannot parallelize this
k1, k2 = constraint.i, constraint.j

inv_m1 = inv(mass(atoms[k1]))
inv_m2 = inv(mass(atoms[k2]))

# Vector between the atoms after SHAKE constraint
r_k1k2 = vector(coords[k1], coords[k2], boundary)

# Difference between unconstrainted velocities
v_k1k2 = velocities[k2] .- velocities[k1]

err = abs(dot(r_k1k2, v_k1k2))
if err > vel_tolerance
# Re-arrange constraint equation to solve for Lagrange multiplier
# This has a factor of dt which cancels out in the velocity update
λₖ = -dot(r_k1k2, v_k1k2) / (dot(r_k1k2, r_k1k2) * (inv_m1 + inv_m2))

# Correct velocities
velocities[k1] -= inv_m1 .* λₖ .* r_k1k2
velocities[k2] += inv_m2 .* λₖ .* r_k1k2
end
end

end

"""
check_position_constraints(sys, constraints)

Checks if the position constraints are satisfied by the current coordinates of `sys`.
"""
function check_position_constraints(sys, ca::SHAKE_RATTLE)
function check_position_constraints(sys::System{D,false}, ca::SHAKE_RATTLE) where {D}
max_err = typemin(float_type(sys)) * unit(eltype(eltype(sys.coords)))
for cluster in ca.clusters
for constraint in cluster.constraints
Expand All @@ -142,6 +183,33 @@
return max_err < ca.dist_tolerance
end

function check_position_constraints(sys::System{D,true}, ca::SHAKE_RATTLE) where {D}
max_err = typemin(float_type(sys)) * unit(eltype(eltype(sys.coords)))
deltas = CUDA.fill(max_err, length(ca.clusters))
clusters = cu(ca.clusters)

Check warning on line 189 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L186-L189

Added lines #L186 - L189 were not covered by tests

n_threads_gpu, n_blocks = cuda_threads_blocks_specific(length(ca.clusters))
CUDA.@sync @cuda threads=n_threads_gpu blocks=

Check warning on line 192 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L191-L192

Added lines #L191 - L192 were not covered by tests
n_blocks check_position_kernel!(sys.coords, clusters, sys.boundary, deltas)

max_err = maximum(deltas)
return max_err < ca.dist_tolerance

Check warning on line 196 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L195-L196

Added lines #L195 - L196 were not covered by tests
end

function check_position_kernel!(coords, clusters, boundary, deltas)
i = (blockIdx().x - 1) * blockDim().x + threadIdx().x
(i > length(clusters)) && return nothing
cluster = clusters[i]

Check warning on line 202 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L199-L202

Added lines #L199 - L202 were not covered by tests

for constraint in cluster.constraints
dr = vector(coords[constraint.i], coords[constraint.j], boundary)
err = abs(norm(dr) - constraint.dist)

Check warning on line 206 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L204-L206

Added lines #L204 - L206 were not covered by tests

if deltas[i] < err
deltas[i] = err

Check warning on line 209 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L208-L209

Added lines #L208 - L209 were not covered by tests
end
end

Check warning on line 211 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L211

Added line #L211 was not covered by tests
end
"""
check_velocity_constraints(sys, constraints)

Expand All @@ -161,3 +229,33 @@
end
return max_err < ca.vel_tolerance
end

function check_velocity_constraints(sys::System{D,true}, ca::SHAKE_RATTLE) where {D}
max_err = typemin(float_type(sys)) * unit(eltype(eltype(sys.velocities))) * unit(eltype(eltype(sys.coords)))
deltas = CUDA.fill(max_err, length(ca.clusters))
clusters = cu(ca.clusters)

Check warning on line 236 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L233-L236

Added lines #L233 - L236 were not covered by tests

n_threads_gpu, n_blocks = cuda_threads_blocks_specific(length(ca.clusters))
CUDA.@sync @cuda threads=n_threads_gpu blocks=n_blocks check_velocity_kernel!(

Check warning on line 239 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L238-L239

Added lines #L238 - L239 were not covered by tests
sys.coords, sys.velocities, clusters, sys.boundary, deltas)

max_err = maximum(deltas)
return max_err < ca.vel_tolerance

Check warning on line 243 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L242-L243

Added lines #L242 - L243 were not covered by tests
end

function check_velocity_kernel!(coords, velocities, clusters, boundary, deltas)
i = (blockIdx().x - 1) * blockDim().x + threadIdx().x
(i > length(clusters)) && return nothing
cluster = clusters[i]

Check warning on line 249 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L246-L249

Added lines #L246 - L249 were not covered by tests

for constraint in cluster.constraints
dr = vector(coords[constraint.i], coords[constraint.j], boundary)
v_diff = velocities[constraint.j] .- velocities[constraint.i]
err = abs(dot(dr, v_diff))
if deltas[i] < err
deltas[i] = err

Check warning on line 256 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L251-L256

Added lines #L251 - L256 were not covered by tests
end
end

Check warning on line 258 in src/constraints/shake.jl

View check run for this annotation

Codecov / codecov/patch

src/constraints/shake.jl#L258

Added line #L258 was not covered by tests
end


3 changes: 0 additions & 3 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -559,9 +559,6 @@ function System(;
if isa(vels, CuArray) && !isa(atoms, CuArray)
throw(ArgumentError("the velocities are on the GPU but the atoms are not"))
end
if isa(atoms, CuArray) && length(constraints) > 0
@warn "Constraints are not currently compatible with simulation on the GPU"
end

atom_masses = mass.(atoms)
M = typeof(atom_masses)
Expand Down
Loading