Adding New Functionality into Psi
The redesign of Psi, into a single-executable, changed the way that code development is done. The standalone nature of modules in previous versions of Psi made their development very easy, as new functionality could be implemented almost as a standalone executable, which could easily be ported into the Psi code when completed. The new design specifies that these modules are now libraries, not separate executables, which are linked into the main Psi executable. The single-executable design is conducive to code reuse, as it allows common tasks to be implemented as a class instead of a module; the functionality can then be easily obtained throughout the code by creating objects as required. Examples of this are the LibMints class, which provides similar functionality to the old cints module, and LibTrans, which replaces the old transqt code. When codes are wrapped in a library, they should be placed into src/lib, and codes that resemble modules belong in src/bin, as described below.
To be able to use plugins, you should compile your source code with the --with-plugins flag passed to configure; this will enable loading of plugins at runtime. The single-executable design leads to a somewhat cumbersome development cycle, described below, which results from the need to compile the code, archive it into a library, and then re-link the code into the main executable, every time a change is made. It's also daunting to new developers if they're required to learn the structure of the source tree, executable initialization code, and makefile system systems in the existing code in order to add new features, which was never a problem with previous versions due to the independent nature of the modules. To overcome these problems, Psi now has a useful plugin feature. This allows codes to be developed as standalone entities, which are compiled independently of the Psi source, but can still link against Psi's vast library. The plugins can be loaded at run-time from any location. A few examples of plugins for Psi are provided, such as PluginAOIntegrals and PluginDFMP2, both of which can be build with the generic PluginMakefile.
As can be seen from the examples, there are four things that must be present in all Psi plugins.
- A single statement of "INIT_PLUGIN" somewhere in the code - this depends on "#include <libplugin/plugin.h>" being present
- A function with the signature
extern "C" int read_options(std::string name, Options &options)
- A function with the signature
extern "C" PsiReturnType modulename(Options &options)
- All functions should live in the psi::modulename namespace.
The actual module name should be substituted in the final two bullets. The INIT_PLUGIN is just a simple macro that guarantees everything is instantiated correctly. The read_options routine is called by the driver to set up the Options object and tell it which options are expected in the plugin. See LibOptions for more details, and the plugin samples provided for examples. The other function is the entry point to the plugin from the driver, and namespaces are used to prevent clashes of symbols. Notice that the two mandatory functions must be declared as extern "C"; this is to make sure that the linker doesn't mangle the names incorrectly and to place them in the export table. Apart from these four elements, any plugin is free to provide as many functions as needed and can use any of the Psi library.
All files needed to build a plugin are provided in the samples, along with suitable input files. The plugins can be loaded by either specifying the path (either full, or relative to the current directory)
plugin_load("(/full/)path/to/plugin.so") ... plugin("(/full/)path/to/plugin.so")
Alternatively, we can just say
plugin_load("plugin.so") ... plugin("plugin.so")
if the plugin lives in the same directory as the input file, or if the environment has been set up correctly by calling
for bash, or
setenv LD_LIBRARY_PATH "/full/path/to/integrals/plugin:$LD_LIBRARY_PATH"
for csh/tcsh; these can be placed in the .bashrc/.tcshrc files for convenience. When loading options in the input file, only the name of the plugin should be given, which defaults to the folder name using the Makefile provided. As demonstrated in PluginDFMP2, the plugin is not restricted to a single source file.
Integrating Code into Psi
Integrating a plugin into the Psi infrastructure is quite simple. We'll work through the PluginAOIntegrals example to demonstrate the steps needed.
The Makefile System
Because the code is a module, and not a class, it should be placed in PSI4/src/bin. We start my making a new folder called aointegrals in there, and copying the aointegrals.cc file in there. Next, we need a Makefile to describe how to build it. Most of the important information for building code in files called MakeVars and MakeRules, which both live in PSI4/src/bin. A minimal file to include this information is needed to build our new integrals module, and it takes a simple form. The following should be placed in PSI4/src/bin/aointegrals/Makefile.in
srcdir = @srcdir@ VPATH = @srcdir@ include ../MakeVars PSITARGET = $(top_objdir)/lib/libPSI_aointegrals.a CXXSRC = aointegrals.cc BINOBJ = $(CXXSRC:%.cc=%.o) include ../MakeRules ifneq ($(DODEPEND),no) $(BINOBJ:%.o=%.d): $(DEPENDINCLUDE) include $(BINOBJ:%.o=%.d) endif
This only provides a skeleton Makefile, which is converted into a real makefile by the configure script. For this to happen, we need to make this skeleton makefile known to the configure system by adding the line
to the list of targets at the end of PSI/configure.ac. Finally, we should add our new aointegrals subdirectory to the subdirs list in PSI4/src/bin/Makefile.in, so that it gets built. To make sure this is all processed correctly, you should run autoconf in the top source directory, and then ./config.status --recheck followed by ./config.status in the build directory, when the final steps described below have been completed.
Initializing the Driver
The Psi driver needs to know a couple of things about the module in order to be able to execute it. First, the read_options routine should be removed, with its contents placed in PSI4/src/bin/read_options.cc - see LibOptions for more information about this. The signature of the function
extern "C" PsiReturnType aointegrals(Options &options)
in aointegrals.cc should be changed to:
PsiReturnType aointegrals(Options &options)
now that the code is no longer a plugin.
Finally, for the code to be called from the Python driver, a python binding must be provided in PSI4/src/bin/psi4/python.cc, as described in PythonAndPsi. After compilation, the code should be ready to run from the Python input.