Use pARTn with a non-supported E/F engine

pARTn can also be used with E/F engines which are not directly supported through an interface. To do that, a separate program/script has to be implemented, which calls the desired E/F engine, and executes prescribed ARTn steps.

Note

In order to use it, pARTn can be compiled without any specific engine, i.e. it suffices to:

./configure && make lib

or:

cmake -B <my_builddir> && cmake --build <my_builddir>

The underlying routine here is called artn_step(), which gives the displacement vector associated to the current step in the ARTn algorithm. Any other interaction with pARTn is done through the API.

Pseudocode

To use the function in action, a pseudocode might look something like (emphasized lines for setting the engine units, and the loop over ARTn steps):

Pseudocode
 1use artn_api, only: artn_rp => DP
 2use artn_api, only: artn_create, artn_set, artn_extract, artn_step, get_error
 3implicit none
 4type( other_engine ) :: my_engine
 5integer :: maxsteps
 6integer :: ierr
 7logical :: lerr, lconv
 8real( artn_rp ), allocatable :: displ_vec(:,:)
 9
10maxsteps = 500
11
12!! initialize your E/F engine of choice
13my_engine = engine( ... )
14
15!! create artn instance
16ierr = artn_create()
17
18!! set your parameters to artn (remember to check ierr value)
19call artn_set( "engine_units", "lammps/metal", ierr=ierr )
20call artn_set( "verbose", 0, ierr=ierr )
21call artn_set( "struc_format_out", "none", ierr=ierr )
22call artn_set( "nevalf_max", maxsteps, ierr=ierr )
23call artn_set( "forc_thr", force_thr, ierr=ierr )
24!! etc ...
25
26!! call setup (not strictly necessary, but gives option to modify the runparams from here)
27call setup_artn2( nat, lerr )
28
29!! allocate the array for dispalcement vector
30allocate( displ_vec(1:3, 1:nat) )
31
32istep_: do istep = 1, maxsteps
33
34   !! get energy, force of current atomic configuration
35   call my_engine% run( positions, types, box, ... , energy, force )
36
37   !! call artn_step, remember it is only on one cpu (``me==0``). Units are as specified above
38   if( me==0 ) call artn_step( nat, energy, force, types, positions, box, if_pos, displ_vec, lconv )
39
40   !! bcast the lconv flag, if mpi
41   call mpi_bcast( lconv, .... )
42
43   !! exit the loop if convergence flag is .true.
44   if( lconv ) exit istep_
45
46   !! apply displacement vector (it's only available on ``me==0`` in mpi), in proper precision
47   if( me==0 ) positions = positions + real( displ_vec, kind=positions )
48
49   !! bcast the new positions
50   call mpi_bcast( positions, ... )
51
52end do istep_
53
54!! check for error (all data is only on ``me==0`` in mpi):
55if( me==0 ) ierr = artn_extract( "has_error", lsuccess )
56call mpi_bcast( lsuccess, ... )
57
58if( lsuccess ) then
59   !! extract data from ``me===0`` and bcast if needed
60   !! etcetc ...
61else
62   !! get errmsg
63   if(me==0) then
64      ierr = get_error( msg )
65      write(*,*) msg
66   end if
67   !! whatever error management ...
68   call mpi_barrier( ... )
69   call mpi_abort( ... )
70end if

(under construction. Check the fortran version in the meantime, it should be similar for C)

Pseudocode
 1import pypARTn
 2
 3## initialize artn, specify the engine as "other"
 4a = pypARTn.artn( engine="other" )
 5
 6# set the set of units to use: lammps/metal is Ang, eV
 7a.set_param( "engine_units", "lammps/metal")
 8
 9# set some other params to artn:
10a.set_param( "verbose", 0)
11a.set_param( "forc_thr", 1e-2)
12a.set_param( "ninit", 2)
13a.set_param( "push_step_size", 0.1)
14a.set_param( "nnewchance", 5)
15a.set_param( "struc_format_out", "none")
16
17# create an initial push vector
18push_in = np.ndarray( [nat,3], dtype=np.float64 )
19push_in = ....
20a.set("push_init", push_in )
21
22maxsteps = 500
23a.set_param( "nevalf_max", maxsteps-1 )
24
25## loop over the steps
26for istep in range( maxsteps ):
27
28    ## compute E/F with the engine of choice, with whatever arguments are needed,
29    ## return energy and force of current atomic configuration
30    my_engine.run( positions, types, ... , energy, force )
31
32    ## get next displacement vector from artn. Use the previously computed
33    ## energy and force, they should be in units of Ang, eV (eqv. to "lammps/metal")
34    displ_vec, lconv = a.next_displ( nat, energy, force, types, positions, box, if_pos )
35
36    ## `lconv` is the convergence flag, is `True` when converged, or error.
37    if( lconv ):
38        break
39
40    ## `displ_vec` is the next displacement to make
41    positions = positions + displ_vec
42
43# end of run, check for error
44if( a.extract("has_error") ):
45    ierr, errmsg=a.get_error()
46    print( errmsg )
47
48# extract desired data
49a.extract( ... )
50
51# etcetc.
52
53## If another exploration will be launched in the same script,
54## the internal data and parameters of ARTn need to be cleaned before-hand:
55## (this will keep the input parameters as previously defined)
56#a.clean()
57
58## If you wish to completely clean (destroy) the ARTn instance, including
59## resetting the input parameters to their default values, call:
60a.destroy()

Function reference

From fortran, we directly call artn_step() routine. It has the following documentation:

group artn_step

Functions

subroutine, public artn_step (nat, etot, eng_force, ityp, pos, box, if_pos, displ_vec, lconv)

Routine to perform single step of artn research.

Parameters:
  • nat[in] number of atoms

  • etot[in] total energy of the engine

  • eng_force[in] force calculated by the engine

  • ityp[in] list of type of atoms

  • pos[in] atomic position

  • box[in] lattice vectors in columns

  • if_pos[in] list of fixed atomic degrees of freedom. 3 integers per atom, value 0 to fix the atom in corresponding direction, or value 1 to allow move.

  • displ_vec[out] displacement vector communicated to move_mode

  • lconv[out] flag for controlling convergence

There is a C-bound routine to artn_step(), with the same name:

group c_artn_step

Functions

subroutine artn_cstep(cnat, cetot, ceng_force, ctyp, cpos, cbox, cif_pos, cdispl_vec, clconv)

C wrapper to artn_step()

C-header

void artn_step(
               const int cnat,
               const double cetot,
               double *const ceng_force,
               int const *ctyp,
               double *const cpos,
               const double *cbox,
               const int *cif_pos,
               double *cdispl_vec,
               bool *clconv);

Parameters:
  • cnat[in] number of atoms

  • cetot[in] total energy of the engine

  • ceng_force[in] force calculated by the engine

  • ctyp[in] list of type of atoms

  • cpos[in] atomic position

  • cbox[in] lattice vectors in columns

  • cif_pos[in] list of fixed atomic degrees of freedom. 3 integers per atom, value 0 to fix the atom in corresponding direction, or value 1 to allow move.

  • cdispl_vec[out] displacement vector communicated to move_mode

  • clconv[out] flag for controlling convergence

In Python, the wrapper to artn_step() routine is called artn.next_displ(), and has the documentation:

class pypARTn.artn(engine=None, shlib=None)[source]
next_displ(nat, etot, force, typ, pos, box, if_pos)[source]

Obtain the next displacement vector according to ARTn algorithm. This calls the artn_step routine.

The units of etot and force input should be in accordance to the engine_units parameter.

NOTE: ARTn has an internal state, which will advance assuming the displacement returned from this function is the displacement that is really applied. Take care when performing displacements other than the one returned from this routine.

NOTE: At convergence, the system remains at the last position. It does NOT return to the initial structure.

== input: ==

Parameters:
  • nat (integer) – number of atoms

  • etot (float) – current total energy, in units specified by engine_units parameter

  • force (float [nat,3]) – current force vector on all atoms, in units specified by engine_units parameter

  • typ (integer [nat]) – integer atomic types

  • pos (float [nat,3]) – current atomic positions

  • box (float [3,3]) – lattice vectors in rows

  • if_pos (integer [nat,3]) – indicate fixed atoms (analogous to QE), each atom gets integer 3-vector {u,v,j}, where any value 0 means the atom is fixed in that direction, and value 1 means the atom is free to move (i.e. mask on force).

== output: ==

Parameters:
  • dr (float [nat,3]) – next displacement vector according to ARTn

  • lconv (logical) – convergence flag, true when system is converged (or error), false otherwise

== Example: ==

>>> ## loop for a number of steps
>>> for istep in range( maxstep ):
>>>    ## compute energy and force for current positions
>>>    ## ...
>>>
>>>    ## get artn displacement
>>>    dr, lconv = artn.next_displ( nat, Etot, Force, typ, pos, box, if_pos )
>>>
>>>    ## convergence criterion achieved
>>>    if( lconv ):
>>>       break
>>>
>>>    ## apply displacement
>>>    pos += dr
>>>
>>> ## check for error
>>> if( artn.extract("has_error") ):
>>>    ierr, errmsg=artn.get_error()
>>>    print(errmsg)