ampsci
High-precision calculations for one- and two-valence atomic systems
Modules.hpp
1#pragma once
2#include <string>
3#include <vector>
4
5namespace IO {
6class InputBlock;
7}
8class Wavefunction;
9
10/*!
11 @brief Modules are user-defined calculations run after the wavefunction has been solved.
12
13 @details
14 A _module_ is a self-contained C++ function that takes an already-computed
15 atomic wavefunction (Hartree-Fock, MBPT, etc.) and performs some downstream
16 calculation: matrix elements, polarisabilities, hyperfine constants, PNC
17 amplitudes, lifetimes, and so on. Any number of modules can be run from a
18 single input file by adding `Module::ModuleName{ ... }` blocks; the ampsci
19 driver looks up each name in the registry below and dispatches to the
20 corresponding function.
21
22 See \ref modules_custom for a more detailed tutorial.
23
24 ## Adding a new module
25
26 Adding a module requires editing _exactly one_ new `.cpp` file. No central
27 list needs to be updated; the build system picks up new `.cpp` files
28 automatically, and the module self-registers at program startup via a static
29 initialiser.
30
31 The minimal template:
32
33 \code{.cpp}
34 // src/Modules/myModule.cpp
35 #include "IO/InputBlock.hpp"
36 #include "Modules/Modules.hpp"
37 #include "Wavefunction/Wavefunction.hpp"
38
39 namespace Module {
40
41 // Declare, register, then define below.
42 void myModule(const IO::InputBlock &input, const Wavefunction &wf);
43 namespace {
44 const Register r_myModule{"myModule", "One-line description shown in -m",
45 &myModule};
46 } // namespace
47
48 void myModule(const IO::InputBlock &input, const Wavefunction &wf) {
49
50 input.check({{"option1", "Description of option1 [default1]"},
51 {"option2", "Description of option2 [default2]"}});
52 if (input.has_option("help"))
53 return;
54
55 const auto option1 = input.get("option1", default1);
56 // ... physics ...
57 }
58
59 } // namespace Module
60 \endcode
61
62 The first string passed to Register is the name used in input files:
63 `Module::myModule{ ... }`. The Register variable itself is never referenced
64 by name -- it exists purely so its constructor fires before `main()` and adds
65 the entry to the singleton Registry. The anonymous namespace gives it
66 internal linkage, so different module files can each use the same identifier
67 (`r_myModule`, or whatever you like) without colliding at link time.
68
69 A complete worked example is provided in `src/Modules/exampleModule.cpp`,
70 intended as a template for new users to copy.
71
72 ## Running a module
73
74 In the input file:
75
76 \code{.unparsed}
77 Module::myModule{
78 option1 = 3.14;
79 option2 = true;
80 }
81 \endcode
82
83 From the command line, to list available modules or query a module's
84 options:
85
86 \code{.unparsed}
87 ./ampsci -m # list all modules
88 ./ampsci -m myModule # show options for `myModule`
89 \endcode
90*/
91namespace Module {
92
93//==============================================================================
94
95//! Function-pointer signature shared by every module.
96using ModuleFn = void (*)(const IO::InputBlock &, const Wavefunction &);
97
98/*!
99 @brief One entry in the module registry.
100 @details
101 Holds everything required to identify and invoke a module:
102
103 - @c name: the string used in input files (`Module::<name>{ ... }`) and
104 when querying with `./ampsci -m <name>`.
105 - @c description: one-line summary printed by `./ampsci -m`.
106 - @c function: pointer to the module's free function (signature
107 @ref ModuleFn).
108*/
110 std::string name;
111 std::string description;
112 ModuleFn function;
113};
114
115//==============================================================================
116
117/*!
118 @brief Singleton registry of all compiled-in modules.
119
120 @details
121 Populated at program startup via static initialisers -- one per module
122 `.cpp` file -- and never modified once `main()` begins.
123
124 The "construct on first use" idiom in get() avoids the static-initialisation
125 order fiasco: the Register constructor calls Registry::get(), which forces
126 the singleton to be created before any module tries to register itself.
127 Order of registration across translation units is unspecified, but for this
128 registry that does not matter.
129
130 Module authors do not normally need to interact with the Registry directly;
131 they self-register via @ref Register. The ampsci driver uses the Registry
132 via @ref runModule, @ref runModules and @ref list_modules.
133*/
134class Registry {
135public:
136 /*!
137 @brief Access the singleton instance.
138 @return Reference to the (only) Registry.
139 */
140 static Registry &get() {
141 static Registry instance;
142 return instance;
143 }
144
145 /*!
146 @brief Append a new entry to the registry.
147 @details
148 Normally called only by the @ref Register constructor.
149
150 @param name Module name as used in input files.
151 @param description One-line description (shown by `./ampsci -m`).
152 @param fn Pointer to the module function.
153 */
154 void add(std::string name, std::string description, ModuleFn fn) {
155 m_entries.push_back({std::move(name), std::move(description), fn});
156 }
157
158 /*!
159 @brief All registered modules, in registration order.
160 @return Const reference to the underlying vector of @ref ModuleEntry.
161 */
162 const std::vector<ModuleEntry> &entries() const { return m_entries; }
163
164 /*!
165 @brief Look up a module by name.
166 @param name Module name (case sensitive; same string passed to @ref Register).
167 @return Pointer to the matching @ref ModuleEntry, or @c nullptr if no
168 module with that name is registered.
169 */
170 const ModuleEntry *find(const std::string &name) const {
171 for (const auto &e : m_entries) {
172 if (e.name == name)
173 return &e;
174 }
175 return nullptr;
176 }
177
178private:
179 Registry() : m_entries{} {}
180 std::vector<ModuleEntry> m_entries;
181};
182
183//==============================================================================
184
185/*!
186 @brief Helper struct: constructing a Register adds a module to the Registry.
187 @details
188 Used at file scope (inside an anonymous namespace) in every module `.cpp`
189 file to self-register that module with the @ref Registry. The constructor
190 is the only thing of interest -- it runs once, before `main()`, as part of
191 static initialisation.
192
193 See the namespace-level documentation above for the usage pattern.
194
195 @note The variable itself is never referenced by name. Pick any identifier
196 you like (`registrar`, `r_myModule`, ...); the anonymous namespace
197 gives it internal linkage so different files can reuse the same
198 identifier without clashing.
199*/
200struct Register {
201
202 /*!
203 @brief Register a module by constructing one of these at file scope.
204 @param name Module name as used in input files: `Module::<name>{}`.
205 @param description One-line description, shown by `./ampsci -m`.
206 @param fn Pointer to the module function.
207 */
208 Register(const char *name, const char *description, ModuleFn fn) {
209 Registry::get().add(name, description, fn);
210 }
211};
212
213//==============================================================================
214
215/*!
216 @brief Iterate over the input blocks and run any that are modules.
217 @details
218 Scans @p input for blocks whose name matches `Module::*` and dispatches
219 each to @ref runModule in turn. Called once by the ampsci driver after the
220 wavefunction has been solved.
221
222 @param input Top-level input block.
223 @param wf Solved wavefunction passed to each module.
224*/
225void runModules(const IO::InputBlock &input, const Wavefunction &wf);
226
227/*!
228 @brief Run a single module.
229 @details
230 Looks up the module by name in the @ref Registry and calls its function.
231 If the name is not found, prints an error message together with a
232 nearest-match suggestion and a list of available modules.
233
234 @param input Input block for this module (typically `Module::Name{}`).
235 @param wf Solved wavefunction.
236*/
237void runModule(const IO::InputBlock &input, const Wavefunction &wf);
238
239/*!
240 @brief Print the list of compiled-in modules (name + description).
241 @details
242 Invoked by `./ampsci -m`. Iterates over Registry::entries() and prints each
243 module's name and description (word-wrapped).
244*/
245void list_modules();
246
247} // namespace Module
Holds a named list of key=value options and nested InputBlocks.
Definition InputBlock.hpp:154
Singleton registry of all compiled-in modules.
Definition Modules.hpp:134
void add(std::string name, std::string description, ModuleFn fn)
Append a new entry to the registry.
Definition Modules.hpp:154
const std::vector< ModuleEntry > & entries() const
All registered modules, in registration order.
Definition Modules.hpp:162
static Registry & get()
Access the singleton instance.
Definition Modules.hpp:140
const ModuleEntry * find(const std::string &name) const
Look up a module by name.
Definition Modules.hpp:170
Stores Wavefunction (set of valence orbitals, grid, HF etc.)
Definition Wavefunction.hpp:37
In-out (timers, profilers, and read/write data)
Definition ChronoTimer.hpp:9
Modules are user-defined calculations run after the wavefunction has been solved.
Definition Module_Kionisation.cpp:21
void(*)(const IO::InputBlock &, const Wavefunction &) ModuleFn
Function-pointer signature shared by every module.
Definition Modules.hpp:96
void runModules(const IO::InputBlock &input, const Wavefunction &wf)
Iterate over the input blocks and run any that are modules.
Definition Modules.cpp:13
void runModule(const IO::InputBlock &module_input, const Wavefunction &wf)
Run a single module.
Definition Modules.cpp:29
void list_modules()
Print the list of compiled-in modules (name + description).
Definition Modules.cpp:58
One entry in the module registry.
Definition Modules.hpp:109
Helper struct: constructing a Register adds a module to the Registry.
Definition Modules.hpp:200
Register(const char *name, const char *description, ModuleFn fn)
Register a module by constructing one of these at file scope.
Definition Modules.hpp:208