relax_geometry_fire.lua

--[[
Example on how to relax a geometry using the FIRE
algorithm.

This example can take any geometry and will relax it
according to the siesta input options:

 - MD.MaxForceTol
 - MD.MaxCGDispl

One should note that the FIRE algorithm first converges
when the total force (norm) on the atoms are below the
tolerance. This is contrary to the SIESTA default which
is a force tolerance for the individual directions,
i.e. max-direction force.

This example is prepared to easily create
a combined relaxation of several FIRE algorithms
simultaneously. In some cases this is shown to
speed up the convergence because an average is taken
over several optimizations.

This example defaults to two simultaneous FIRE algorithms
which seems adequate in most situations.

--]]

-- Load the FLOS module
local flos = require "flos"

local FIRE = {}
-- In this example we take a mean of 4 different methods
local dt_init = 0.5
FIRE[1] = flos.FIRE{dt_init = dt_init, direction="global", correct="local"}
FIRE[2] = flos.FIRE{dt_init = dt_init, direction="global", correct="global"}
FIRE[3] = flos.FIRE{dt_init = dt_init, direction="local", correct="local"}
FIRE[4] = flos.FIRE{dt_init = dt_init, direction="local", correct="global"}
-- To use more simultaneously simply add a
-- new line... with a separate FIRE algorithm.

-- Grab the unit table of siesta (it is already created
-- by SIESTA)
local Unit = siesta.Units

function siesta_comm()

   -- This routine does exchange of data with SIESTA
   local ret_tbl = {}

   -- Do the actual communication with SIESTA
   if siesta.state == siesta.INITIALIZE then

      -- In the initialization step we request the
      -- convergence criteria
      --  MD.MaxDispl
      --  MD.MaxForceTol
      -- We also need the mass for scaling the displacments
      siesta.receive({"MD.MaxDispl",
		      "MD.MaxForceTol",
		      "geom.mass"})

      -- Print information
      IOprint("\nLUA convergence information for the FIRE algorithms:")

      -- Ensure we update the convergence criteria
      -- from SIESTA (in this way one can ensure siesta options)
      for i = 1, #FIRE do

	 FIRE[i].tolerance = siesta.MD.MaxForceTol * Unit.Ang / Unit.eV
	 FIRE[i].max_dF = siesta.MD.MaxDispl / Unit.Ang
	 FIRE[i].set_mass(siesta.geom.mass)

	 -- Print information
	 if siesta.IONode then
	    FIRE[i]:info()
	 end
      end

   end

   if siesta.state == siesta.MOVE then

      -- Here we are doing the actual FIRE algorithm.
      -- We retrieve the current coordinates, the forces
      -- and whether the geometry has relaxed
      siesta.receive({"geom.xa",
		      "geom.fa",
		      "MD.Relaxed"})

      ret_tbl = siesta_move(siesta)

   end

   siesta.send(ret_tbl)
end

function siesta_move(siesta)

   -- Retrieve the atomic coordinates and the forces
   local xa = flos.Array.from(siesta.geom.xa) / Unit.Ang
   -- Note the FIRE requires the gradient, and
   -- the force is the negative gradient.
   local fa = flos.Array.from(siesta.geom.fa) * Unit.Ang / Unit.eV

   -- Perform step (initialize arrays to do averaging if more
   -- FIRE algorithms are in use).
   local all_xa = {}
   local weight = flos.Array.empty(#FIRE)
   for i = 1, #FIRE do

      -- Calculate the next optimized structure (that
      -- minimizes the Hessian)
      all_xa[i] = FIRE[i]:optimize(xa, fa)

      -- Get the optimization length for calculating
      -- the best average.
      weight[i] = FIRE[i].weight

   end

   -- Normalize weight
   weight = weight / weight:sum()
   if #FIRE > 1 then
      IOprint("\nFIRE weighted average: ", weight)
   end

   -- Calculate the new coordinates and figure out
   -- if the algorithms has been optimized.
   local out_xa = all_xa[1] * weight[1]
   local relaxed = FIRE[1]:optimized()
   for i = 2, #FIRE do

      out_xa = out_xa + all_xa[i] * weight[i]
      relaxed = relaxed and FIRE[i]:optimized()

   end
   -- Immediately clean-up to reduce memory overhead (force GC)
   all_xa = nil

   -- Send back new coordinates (convert to Bohr)
   siesta.geom.xa = out_xa * Unit.Ang
   siesta.MD.Relaxed = relaxed

   return {"geom.xa",
	   "MD.Relaxed"}
end
generated by LDoc 1.4.6 Last updated 2019-05-23 08:36:38