Instructions for writing your own custom ampsci modules.
A module is the standard way to do calculations with ampsci wavefunctions – if none of the built-in modules do what you need, writing your own is straightforward. Modules are regular C++ functions that take in the pre-computed wavefunction (calculated by regular ampsci) and optional additional user imput: you can then implement any physics calculation you desire using the wavefunctions.
- Any number of modules can be run by adding
Module::moduleName{} blocks to the input file.
- Get a list of available modules:
./ampsci -m
- Get a list of input options for a specific module:
./ampsci -m <ModuleName>
- See modules for details of currently available modules.
- See Matrix Elements for a hands-on introduction to using modules.
- You can also write your own operators – see Writing a custom operator.
- Note
- If you plan to add your module directly to the ampsci codebase (rather than as an external module), see the Contributing guidelines for how to structure and submit your contribution.
Internal vs external modules
Modules can live inside or outside the ampsci source tree:
- External (recommended): keep your module(s) in a separate directory outside
src/, and list them in your Makefile. No changes to the ampsci source are required. This is the preferred approach – your modules can be version-controlled and shared independently from ampsci.
- Internal: drop the file anywhere under
src/ and the build system picks it up automatically. Do this if you intend to contribute the module back to ampsci.
- (It's easy to change later, so it doesn't matter which choice you make initially)
VS Code / linter: if your module lives outside the ampsci source tree (i.e., external module), the vscode linter won't find the ampsci headers, and so may create annoying red squiggles. Fix: open (or create) .vscode/c_cpp_properties.json inside your module's directory, and add the path to ampsci/src to the includePath list:
"includePath": [
"${workspaceFolder}/**",
"/absolute/path/to/ampsci/src/"
]
- Note
- Compatibility note: ampsci's API (Wavefunction, DiracSpinor, DiracOperator, etc.) evolves over time. External modules written against one version of ampsci are not guaranteed to compile or produce correct results with a later version. As a result, you may need to make slight updates to your module to keep up-to-date with ampsci. Alternatively, pin your module to a particular ampsci version or git commit - the full git history is available, so you can keep using an older ampsci version is you must.
Writing your module
Writing a module consists of three steps:
- Create the file
- Write the physics
- Register the module
Create the file
- Copy
src/Modules/exampleModule.cpp to a new file and rename exampleModule to your module name throughout. Place it anywhere – inside or outside src/ (see above).
You can name the module whatever you like (myModule in this example), but it must be in the Module namespace, must have a unique name (not clash with other modules), and the function signature must be exactly:
Stores Wavefunction (set of valence orbitals, grid, HF etc.)
Definition Wavefunction.hpp:37
External: place the file anywhere outside src/, then add it to your Makefile:
EXTERNAL_MODULES = path/to/myModule.cpp
- Globs are supported:
EXTERNAL_MODULES = mymodules/*.cpp. The modules are then compiled and linked into ampsci automatically.
- Internal: place the file anywhere under
src/ (use src/Modules/ by default); the build system picks it up automatically.
Write the physics
- Replace the body of your function with whatever calculation you need.
- Inside it,
input (IO::InputBlock) gives you access to the run-time user options, and wf (Wavefunction) gives you the solved atomic wavefunction.
- See examples below.
- See detailed API documentation and the provided examples for details on how to use the Wavefunction
Register the module
- Add a single
Register line inside an anonymous namespace (see below for description/explanation of entries)
- This must be in the same .cpp file as your module (after the module has been declared).
- This is what makes the module visible to ampsci, and allows users to run it.
namespace {
const Register r_myModule{"myModule", "Short description of myModule", &myModule};
}
Finally: Recompile and run
- Add
Module::myModule{} to an input file, or query its options from the command-line with ./ampsci -m myModule.
- You must recompile after adding a new module for it to be visible to ampsci.
No other files need to be edited. The module self-registers at program startup.
Example
Each module file declares its function, registers it with the Module namespace, then defines it – all in a single .cpp file. The structure of a module will be like the following example:
input.
check({{
"value1",
"Short description of value1 [3.14]"},
{"value2", "Short description of value2 [default2]"}});
return;
const double value1_default = 3.14;
const double value1 = input.
get(
"value1", value1_default);
for (const auto &v : wf.valence()){
std::cout << v.shortSymbol() << " " << v.en() <<"\n";
}
}
namespace {
const Register r_myModule{"myModule",
"Short description of myModule",
&myModule};
}
}
Modules are user-defined calculations run after the wavefunction has been solved.
Definition Module_Kionisation.cpp:21
Registration
- name (
"myModule"): the string used to invoke the module from an input file (Module::myModule{...}), and as the lookup key for ./ampsci -m myModule. Conventionally the same as the function name.
- Must be unique across all compiled modules. Lookup is case-insensitive, so capitalisation alone is not enough. Duplicates don't compile errors; however if there are two modules with the same name, it is undefined which will actually be invoked/run.
- Must contain no spaces and only standard characters (letters, digits,
_): "my_module" is fine, "my module" is not.
- description: a short, one-line summary shown by
./ampsci -m.
- This is technically optional, but is there to be informative to users
- function (
&myModule): pointer to the module function.
- Signature must match exactly
void name(const IO::InputBlock&, const Wavefunction&)
- The C++ function name must be unique across all compiled modules – duplicates produce a link error.
- The
r_myModule name for the Register is just convention – it can be anything.
- In fact, the only restriction is that multiple modules within the same .cpp file (compilation unit) have unique names; the names do not even need to be unique across different files. (These names are not used anywhere in the code)
- The registration must appear after the module function has been declared (a forward declaration is enough; the definition can come later).
- Keep the registration in the same
.cpp file as the module.
Highly recommended: input checking
Add an input.check() call listing all options used by your module:
input.
check({{
"option1",
"Short description of option1 [default1]"},
{"option2", "Short description of option2 [default2]"}});
- Catches spelling mistakes in user input – a mistyped option would be silently ignored otherwise.
- The descriptions are printed when the user runs
./ampsci -m myModule.
- Without this it is much harder for other users to know how to use your module, and it provides (basic) automatic input checking.
Return immediately after check() if help was requested, to avoid running the module unnecessarily (this is what makes ampsci -m moduleName work):
Key API reference
The following links are to the detailed documentation for the specific namespaces/classes that will be most useful for writing your own module:
- DiracSpinor – single relativistic orbital \( F_{n\kappa} = (f, g) \); radial components, quantum numbers, arithmetic, inner products.
- Wavefunction – full atomic state: core/valence/basis orbital lists, radial grid, nuclear potential, and the HF object. Access valence orbitals via
wf.valence(), core via wf.core(), basis via wf.basis().
- HF::HartreeFock – self-consistent HF field; accessible via
wf.vHF().
- Angular – Angular coeficients, 3j/6j symbols etc.
- Coulomb – Coulomb integrals etc.
- PhysConst – Physical constants, unit conversions
- Grid – numerical grids, including Jacobian
- DiracOperator::TensorOperator – virtual base class for single-particle tensor operators (E1, hfs, pnc, ...); reduced matrix elements, selection rules, radial integrals.
- IO::InputBlock – the input block passed to your module; use
input.get<T>("key", default_value) to read user-supplied options, and input.check({...}) to declare and validate them.
- Module – the namespace containing the registration mechanism (Module::Register, Module::Registry)
- Namespaces – full ampsci API docs