Cmake interface building

From Docswiki
Jump to navigation Jump to search

Overview

As we try to constantly add new potentials to our group code, it is necessary to interface these properly in such a way that:

  • 1. the interface is built correctly when the potential is used,
  • 2. the code for other potentials is unaffected,
  • 3. changes to the potential do not affect the interface,
  • 4. and the interface provides initialisation, energies, gradients and second derivaties; but everything else is dealt with in the main program (i.e. GMIN or OPTIM).

The following is a brief overview of the construction of the OPEP interface for GMIN, aiming to show one possible structure of the interface file, the use of pre-processing steps, and the set up for cmake.

The interface structure

In the OPEP interface, we have one subdirectory, which contains all files necessary for the potential, and opep_interface.F90, which takes care of initialisation, energy, gradient and Hessian calls, writing output files, and the termination.

All OPEP specific calls within GMIN are directed to routines within opep_interface.F90, which in turn calls the necessary routines in the potential. The files in the potential still have access to commonly used functions and global variables from commons.f90 such as GETUNIT or MYUNIT.

The interface module itself contains 5 different subroutines:

  • 1. OPEP_INIT, initialisation called from within keywords
  • 2. OPEP_ENERGY_AND_GRADIENT, which returns energy and gradient and is called from potential.f
  • 3. OPEP_NUM_HESS, which calculates the numerical Hessian and is also called from potential.f
  • 4. OPEP_WRITE_PDB, which returns the potential specific coordinate files for a given set of coordinates
  • 5. OPEP_FINISH, which deallocates all saved variables and tidies up.

Clearly, some of the above routines may be implemented in different ways, but the above way has some advantages. Firstly, everything is in one place. The amount of code that is added within the GMIN routines is kept to a minimum, and helps the readability of the core routines. Additional features or potential specific oddities are also kept separate from the main code. Furthermore, as all calls are done in a standard way, additional levels of complexity within GMIN, such as parallel tempering BH, rigid bodies, and/or free energy BH, ought to work without any modification of these routines. This approach clearly means we are less likely to break functioning, pre-existing routines.

Dummy routines vs. pre-processor steps

If we do not use the potential interface, i.e. in this case if -DWITH_OPEP=no, then the code still finds calls to the interface in for example potential. This would stop the code linking, if we don't deal with it. One approach is the use of a dummy module. This is done for various potentials, for example AMBER9. In this dummy module, all called routines and needed variables are contained, but they don't do anything. This approach is simple, but has the major drawback of adding many files to the code base, and potentially confusing debugging efforts.

A different solution is used for example for OPEP and AMBER12. Instead of adding a dummy file, we use the actual interface file, but with pre-processor statements. The pre-processor definition __OPEP will be set when compiling with OPEP. (Remember: the corresponding file then has to be .F or .F90 rather than .f or .f90). Below one subroutine of the OPEP interface is shown.

MODULE OPEP_INTERFACE_MOD
  IMPLICIT NONE
 .
 .
 .
  SUBROUTINE OPEP_ENERGY_AND_GRADIENT(NATOM,COORD,GRAD,EREAL,GRADT)
#ifdef __OPEP
    USE md_defs
#endif
    INTEGER, INTENT(IN) :: NATOM
    DOUBLE PRECISION, INTENT(IN) :: COORD(3*NATOM)
    DOUBLE PRECISION, INTENT(OUT) :: GRAD(3*NATOM), EREAL
    LOGICAL, INTENT(IN) :: GRADT
#ifdef __OPEP
    !call calcforce where we either go for RNA or protein calculation
    !scale factor is set in calcforce as well, use dummy here
    CALL CALCFORCE(1.0D0,COORD,GRAD,EREAL)
    GRAD=-GRAD
#endif
    RETURN
  END SUBROUTINE OPEP_ENERGY_AND_GRADIENT
 .
 .
 .
END MODULE

We have not used the INTERFACE keyword in Fortran here, which would further simplify this environment.

Key to the pre-processing is that we remove all module dependencies for the file, and the commands within the actual routine. If __OPEP is not defined these routines are seen as follows:

SUBROUTINE EXAMPLE(A,B,C)
INTEGER :: A,B
LOGICAL :: C
RETURN
END SUBROUTINE

This means CALL commands in the main body of the code still find the correct subroutines to call, and all variables are declared within the subroutines, but nothing happens. However, this will only work if we modify CMakeLists.txt accordingly.

CMakeLists

There are two CMakeLists.txt files that we have to change/generate.

The first one needs to be in the subdirectory, where all our files for the potential are.

include_directories(${CMAKE_BINARY_DIR})
file(GLOB OPEP_SOURCES *.f90 *.f *.F *.F90)

# Remove the interface file
file(GLOB NOT_OPEP_SOURCES opep_interface.F90)
list(REMOVE_ITEM OPEP_SOURCES ${NOT_OPEP_SOURCES} )

# Create the library
add_library(OPEP ${OPEP_SOURCES})
add_dependencies(OPEP gminlib)

At the beginning, we add all Fortran files as source files, before we remove the interface file from this list. This step is necessary, as the interface always needs to be included. We then add the source files as a new library and add the dependency on gminlib to access common global variables and functions from GMIN's core code.

Now, we have to change the CMakeLists.txt in the GMIN source directory.

The first step is to create a DUMMY_OPEP variable, which contains the interface file, and add it to the set of all DUMMY files.

 .
 .
 .
file(GLOB DUMMY_TESTING dummy_testing.f90)
file(GLOB DUMMY_OPEP    OPEPinterface/opep_interface.F90)
file(GLOB DUMMY_OPTIM   dummyoptim.f90)
 .
 .
 .
set(ALL_DUMMIES ${DUMMY_AMH}
                  ...
                ${DUMMY_TESTING} 
                ${DUMMY_OPEP}
                ${DUMMY_OPTIM} 
                ... )
 .
 .
 .

Next, we create extra libraries for these dummies, as our interface file is always used by some GMIN modules. No changes should be necessary here.

# Make an extras library for dummies, this is necessary because some of the
# files in gminlib use modules made by extralib
add_library(extralib ${ALL_EXTRA_SOURCES})
set_module_dir(extralib)
set_target_properties(extralib PROPERTIES LINKER_LANGUAGE "Fortran")
set_target_properties(extralib PROPERTIES COMPILE_DEFINITIONS 
                      "${COMPILE_DEFINITIONS};${DUMMY_CPP_FLAGS};__SPARSE")

Finally, we add our own executable:

#OPEP interface
option(WITH_OPEP "Enable OPEPGMIN compilation" OFF)
if(WITH_OPEP)
  SET(EXTRA_SOURCES ${ALL_EXTRA_SOURCES})
  add_subdirectory(OPEPinterface)
  add_executable(OPEPGMIN main.F ${EXTRA_SOURCES})
  set_module_dir(OPEPGMIN)
  set_module_depends(OPEPGMIN gminlib)
  set_module_dir(OPEP)
  set_module_depends(OPEP gminlib)
  set_target_properties(OPEPGMIN PROPERTIES LINKER_LANGUAGE "Fortran")
  set_target_properties(OPEPGMIN PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS};${DUMMY_CPP_FLAGS};__OPEP")
  target_link_libraries(OPEPGMIN gminlib
                                 OPEP
                                 ${MYLAPACK_LIBS})
endif(WITH_OPEP)

Here, we add a new target executable, OPEPGMIN, specifying that it requires the dummy sources, the added subdirectory OPEPinterface and the main gminlib library. The definition of the OPEP library is in the CMakeLists.txt in the subdirectory. By adding __OPEP to the end of the COMPILE_DEFINITIONS, we ensure that opep_interface.F90 is recompiled with that pre-processor defintion set.

Once this is done, the code should compile correctly with and without the new potential. (Make sure you test this before you commit your changes.)