-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathfssh.jl
135 lines (110 loc) · 4.13 KB
/
fssh.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
using StatsBase: mean
using NQCDynamics: masses
using NQCDistributions: ElectronicDistribution
export FSSH
"""
FSSH{T} <: SurfaceHopping
Type for fewest-switches surface hopping
```jldoctest
Simulation{FSSH}(Atoms(:H), Free())
# output
Simulation{FSSH{Float64}}:
Atoms{Float64}([:H], [1], [1837.4715941070515])
Free(1)
```
"""
mutable struct FSSH{T} <: SurfaceHopping
density_propagator::Matrix{Complex{T}}
hopping_probability::Vector{T}
state::Int
new_state::Int
rescaling::Symbol
tmp_complex_matrix::Matrix{Complex{T}}
function FSSH{T}(states::Integer, rescaling::Symbol) where {T}
density_propagator = zeros(states, states)
hopping_probability = zeros(states)
tmp_complex_matrix = zeros(Complex{T}, states, states)
new{T}(density_propagator, hopping_probability, 0, 0, rescaling, tmp_complex_matrix)
end
end
function Simulation{FSSH}(atoms::Atoms{T}, model::Model; rescaling=:standard, kwargs...) where {T}
Simulation(atoms, model, FSSH{T}(NQCModels.nstates(model), rescaling); kwargs...)
end
function DynamicsMethods.DynamicsVariables(
sim::AbstractSimulation{<:SurfaceHopping}, v, r, electronic::ElectronicDistribution
)
σ = DynamicsUtils.initialise_adiabatic_density_matrix(electronic, sim.calculator, r)
state = sample(Weights(diag(real.(σ))))
return SurfaceHoppingVariables(ComponentVector(v=v, r=r, σreal=σ, σimag=zero(σ)), state)
end
function DynamicsUtils.acceleration!(dv, v, r, sim::AbstractSimulation{<:FSSH}, t, state)
adiabatic_derivative = Calculators.get_adiabatic_derivative(sim.calculator, r)
for I in eachindex(dv)
dv[I] = -adiabatic_derivative[I][state, state]
end
DynamicsUtils.divide_by_mass!(dv, masses(sim))
return nothing
end
"""
evaluate_hopping_probability!(sim::Simulation{<:FSSH}, u, dt)
Evaluates the probability of hopping from the current state to all other states
# Implementation
- `σ` is Hermitan so the choice `σ[m,s]` or `σ[s,m]` is irrelevant; we take the real part.
- 'd' is skew-symmetric so here the indices are important.
"""
function evaluate_hopping_probability!(sim::AbstractSimulation{<:FSSH}, u, dt)
v = DynamicsUtils.get_hopping_velocity(sim, DynamicsUtils.get_velocities(u))
σ = DynamicsUtils.get_quantum_subsystem(u)
s = sim.method.state
r = DynamicsUtils.get_positions(u)
d = DynamicsUtils.get_hopping_nonadiabatic_coupling(sim, r)
fewest_switches_probability!(sim.method.hopping_probability, v, σ, s, d, dt)
end
function fewest_switches_probability!(probability, v, σ, s, d, dt)
probability .= 0 # Set all entries to 0
for m in axes(σ, 1)
if m != s
for I in eachindex(v)
probability[m] += 2v[I]*real(σ[m,s]/σ[s,s])*d[I][s,m] * dt
end
end
end
clamp!(probability, 0, 1) # Restrict probabilities between 0 and 1
cumsum!(probability, probability)
end
function select_new_state(sim::AbstractSimulation{<:FSSH}, u)
random_number = rand()
for (i, prob) in enumerate(sim.method.hopping_probability)
if i != sim.method.state # Avoid self-hops
if prob > random_number
return i
end
end
end
return sim.method.state
end
"""
unpack_states(sim)
Get the two states that we are hopping between.
"""
function unpack_states(sim::AbstractSimulation{<:FSSH})
return (sim.method.new_state, sim.method.state)
end
function Estimators.diabatic_population(sim::AbstractSimulation{<:FSSH}, u)
r = DynamicsUtils.get_positions(u)
U = DynamicsUtils.evaluate_transformation(sim.calculator, r)
σ = copy(DynamicsUtils.get_quantum_subsystem(u).re)
σ[diagind(σ)] .= 0
σ[u.state, u.state] = 1
return diag(U * σ * U')
end
function Estimators.adiabatic_population(sim::AbstractSimulation{<:FSSH}, u)
population = zeros(NQCModels.nstates(sim.calculator.model))
population[u.state] = 1
return population
end
function DynamicsUtils.classical_potential_energy(sim::Simulation{<:FSSH}, u)
eigs = Calculators.get_eigen(sim.calculator, DynamicsUtils.get_positions(u))
potential = eigs.values[u.state]
return potential
end