Using a Driver

Note

Drivers are a planned feature. The APIs on this page are planned APIs that are not yet implemented, and the code is not yet tested. See Designing Drivers for the design.

This page uses the ForceDriver written in Writing a Driver. It computes the force on a particle with charge 1 at the origin, from a charge of 1 at \(x=1\) and a charge of 2 at \(x=2\).

Adding the Driver

A driver is added to the ModuleManager like any other module, and its submodules are set with change_submod. Here the modules the driver configures come from the pluginplay_examples plugin (in Python, from Python versions of the same modules):

pluginplay::ModuleManager mm;
mm.load_plugin<PluginplayExamples>();
mm.add_module<ForceDriver>("Force driver");
mm.change_submod("Force driver", "force", "Classical Force");

Running the Driver

A driver is run like any other module. With its default settings, ForceDriver uses the unscreened electric field, and the force is \((5.5, 0, 0)\):

// Run with the default settings
auto F = mm.run_as<Force>("Force driver", q, m, a, charges);

The first time a driver runs, it is locked, like any module. When a driver is locked, the ModuleManager applies the driver’s settings to overlays of the modules below it: modules that point to the ModuleManager’s modules and hold only what the driver changed. The modules themselves are not changed. Other modules that use “Classical Force”, for example, still get the unscreened electric field. As for any module, the modules below the driver are locked too.

Note

A driver’s settings are applied by the ModuleManager, so a driver must be run with ModuleManager::run_as (or as a submodule of a module that was). Running a driver that has never run with Module::run_as throws.

Changing the Settings

The driver’s settings are its inputs and submodules, so they are changed with change_input and change_submod. Since a driver that has run is locked, using other settings afterwards means copying the driver first. The copy starts from the driver’s settings before it was locked, and applies them again when it runs. With screening on and a threshold of 1.5, only the charge at \(x=1\) contributes to the electric field, and the force is \((5, 0, 0)\):

// Use other settings by copying the driver
mm.copy_module("Force driver", "Screened force driver");
mm.change_input("Screened force driver", "screen", true);
mm.change_input("Screened force driver", "threshold", 1.5);
auto F_screened =
  mm.run_as<Force>("Screened force driver", q, m, a, charges);

Settings With No Effect

A setting has no effect if the module it targets is not called by the driver, or if it belongs to a branch of the driver that is not taken. ForceDriver passes “threshold” on only when “screen” is true. If the user changes “threshold” but leaves “screen” false, the change does nothing. Such settings are inactive. Inactive settings are logged as warnings when the driver is locked, and a locked driver’s inactive_settings() lists them:

// Change "threshold" without turning on "screen"
mm.copy_module("Force driver", "Unscreened force driver");
mm.change_input("Unscreened force driver", "threshold", 1.5);
mm.run_as<Force>("Unscreened force driver", q, m, a, charges);

auto inactive = mm.at("Unscreened force driver").inactive_settings();
// "threshold" is inactive: the branch that forwards it was not taken

With its default settings, ForceDriver reports nothing: “threshold” was not changed, so nothing the user asked for was ignored.

Overriding the Driver’s Choices

Most choices a driver makes are its inputs and submodules, and are changed as above. For the rest, a user can set an input or submodule of a module below the driver to a fixed value. The driver’s call_graph_request() holds the user’s requests, and its set_input and set_submod work like the ones in a chain (see Writing a Driver). call_graph_request() throws if the module is not a driver. Values the user sets are applied after all of the driver’s settings, so they win.

Here the user picks the screened electric field without turning on “screen”. Since the “screen” branch is not taken, the driver does not pass its “threshold” on, so the user also sets the screened module’s threshold directly:

// Override one of the driver's choices
mm.copy_module("Force driver", "My force driver");
auto& request = mm.at("My force driver").call_graph_request();
request.set_submod("Classical Force", "electric field",
                   "Coulomb's Law with screening");
request.set_input("Coulomb's Law with screening", "threshold", 1.5);
auto F_mine = mm.run_as<Force>("My force driver", q, m, a, charges);

Like the driver’s inputs, these values must be set before the driver is locked.