neb_simple.lua
local flos = require "flos"
local image_label = "siesta."
local n_images = 6
local images = {}
local label = "siesta"
local read_geom = function(filename)
local file = io.open(filename, "r")
local na = tonumber(file:read())
local R = flos.Array.zeros(na, 3)
file:read()
local i = 0
local function tovector(s)
local t = {}
s:gsub('%S+', function(n) t[#t+1] = tonumber(n) end)
return t
end
for i = 1, na do
local line = file:read()
if line == nil then break end
local v = tovector(line)
R[i][1] = v[1]
R[i][2] = v[2]
R[i][3] = v[3]
end
file:close()
return R
end
for i = 0, n_images + 1 do
images[#images+1] = flos.MDStep{R=read_geom(image_label .. i .. ".xyz")}
end
function is_correct_size(images)
local na = #images[1].R
IOprint("\nLUA NEB images has " .. na .. " atoms.")
for i = 2, #images do
if na ~= #images[i].R then
print("\nLUA NEB error in initialized images")
print("LUA NEB error images does not have the same number of atoms!")
return false
end
end
return true
end
if not is_correct_size(images) then
function siesta_comm()
siesta.Stop = true
siesta.send({'Stop'})
end
os.exit(1)
end
local NEB = flos.NEB(images)
if siesta.IONode then
NEB:info()
end
n_images = nil
local relax = {}
for i = 1, NEB.n_images do
relax[i] = {}
relax[i][1] = flos.LBFGS{H0 = 1. / 75}
end
local current_image = 1
local Unit = siesta.Units
function siesta_comm()
local ret_tbl = {}
if siesta.state == siesta.INITIALIZE then
siesta.receive({"Label",
"geom.xa",
"MD.MaxDispl",
"MD.MaxForceTol"})
label = tostring(siesta.Label)
IOprint("\nLUA NEB calculator")
for img = 1, NEB.n_images do
IOprint(("\nLUA NEB relaxation method for image %d:"):format(img))
for i = 1, #relax[img] do
relax[img][i].tolerance = siesta.MD.MaxForceTol * Unit.Ang / Unit.eV
relax[img][i].max_dF = siesta.MD.MaxDispl / Unit.Ang
if siesta.IONode then
relax[img][i]:info()
end
end
end
if #NEB.initial.R ~= #siesta.geom.xa then
siesta.Stop = true
print("\nLUA NEB error in images vs. Siesta atomic structure!")
print("LUA NEB error images does not have the same number of atoms as the Siesta calculation!")
siesta.send({'Stop'})
return
os.exit(1)
end
siesta.geom.xa = NEB.initial.R * Unit.Ang
IOprint("\nLUA/NEB initial state\n")
current_image = 0
ret_tbl = {'geom.xa'}
end
if siesta.state == siesta.MOVE then
siesta.receive({"geom.fa",
"E.total",
"MD.Relaxed"})
local old_image = current_image
ret_tbl = siesta_move(siesta)
siesta_update_DM(old_image, current_image)
end
siesta.send(ret_tbl)
end
function siesta_move(siesta)
local fa = flos.Array.from(siesta.geom.fa) * Unit.Ang / Unit.eV
local E = siesta.E.total / Unit.eV
NEB[current_image]:set{F=fa, E=E}
if current_image == 0 then
current_image = NEB.n_images + 1
siesta.geom.xa = NEB[current_image].R * Unit.Ang
IOprint("\nLUA/NEB final state\n")
return {'geom.xa'}
elseif current_image == NEB.n_images + 1 then
current_image = 1
siesta.geom.xa = NEB[current_image].R * Unit.Ang
IOprint(("\nLUA/NEB running NEB image %d / %d\n"):format(current_image, NEB.n_images))
return {'geom.xa'}
elseif current_image < NEB.n_images then
current_image = current_image + 1
siesta.geom.xa = NEB[current_image].R * Unit.Ang
IOprint(("\nLUA/NEB running NEB image %d / %d\n"):format(current_image, NEB.n_images))
return {'geom.xa'}
end
local relaxed = true
IOprint("\nNEB step")
local out_R = {}
for img = 1, NEB.n_images do
local F = NEB:force(img, siesta.IONode)
IOprint("NEB: max F on image ".. img ..
(" = %10.5f, climbing = %s"):format(F:norm():max(),
tostring(NEB:climbing(img))) )
local all_xa, weight = {}, flos.Array( #relax[img] )
for i = 1, #relax[img] do
all_xa[i] = relax[img][i]:optimize(NEB[img].R, F)
weight[i] = relax[img][i].weight
end
weight = weight / weight:sum()
if #relax[img] > 1 then
IOprint("\n weighted average for relaxation: ", tostring(weight))
end
local out_xa = all_xa[1] * weight[1]
relaxed = relaxed and relax[img][1]:optimized()
for i = 2, #relax[img] do
out_xa = out_xa + all_xa[i] * weight[i]
relaxed = relaxed and relax[img][i]:optimized()
end
out_R[img] = out_xa
end
NEB:save( siesta.IONode )
for img = 1, NEB.n_images do
NEB[img]:set{R=out_R[img]}
end
current_image = 1
if relaxed then
siesta.geom.xa = NEB.final.R * Unit.Ang
IOprint("\nLUA/NEB complete\n")
else
siesta.geom.xa = NEB[1].R * Unit.Ang
IOprint(("\nLUA/NEB running NEB image %d / %d\n"):format(current_image, NEB.n_images))
end
siesta.MD.Relaxed = relaxed
return {"geom.xa",
"MD.Relaxed"}
end
function file_exists(name)
local f = io.open(name, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
function siesta_update_DM(old, current)
if not siesta.IONode then
return
end
local DM = label .. ".DM"
local old_DM = DM .. "." .. tostring(old)
local current_DM = DM .. "." .. tostring(current)
if 1 <= old and old <= NEB.n_images and file_exists(DM) then
IOprint("Saving " .. DM .. " to " .. old_DM)
os.execute("mv " .. DM .. " " .. old_DM)
elseif file_exists(DM) then
IOprint("Deleting " .. DM .. " for a clean restart...")
os.execute("rm " .. DM)
end
if file_exists(current_DM) then
IOprint("Restoring " .. current_DM .. " to " .. DM)
os.execute("cp " .. current_DM .. " " .. DM)
end
end