Plugin system¶
A plugin adds capabilities to the engine without the core depending on it: element types, instrument kinds, models, run modes, hooks that post-process instrument readings, summary sections and result files, CLI commands, console reports, and (optionally) a Python module with builders and figures. The first-party plugins are fitting, ao, twin, zemax, gpu and hitran; the live-data plugin and ML plugins follow after the stable release.
Using plugins¶
Installing, checking and troubleshooting plugins is described step by step in Installing plugins.
ultrafast plugins list # installed plugins, versions, where they were found, load errors
ultrafast plugins paths # the folders searched
ultrafast plugins install my-plugin/ # or my-plugin.zip: copies it into the user plugin folder
ultrafast plugins remove my-plugin
import ultrafast as uf
[p.id for p in uf.plugins.installed()] # ['fitting', 'gpu', 'hitran', 'zemax']
fitting = uf.plugins.module("fitting") # the plugin's Python module, or None
Search order (the first folder that has a plugin id wins):
| folder | |
|---|---|
| 1 | ULTRAFAST_PLUGIN_PATH (folders separated by : on macOS/Linux, ; on Windows) |
| 2 | plugins/ next to the engine assembly (the CLI's folder; ultrafast/bin/hosted/plugins in the Python package) |
| 3 | artifacts/plugins of a source checkout (Python package used from the checkout) |
| 4 | the user folder: $ULTRAFAST_HOME/plugins, default ~/.ultrafast/plugins (where install copies to) |
ULTRAFAST_NO_PLUGINS=1 runs the bare core. Plugins are loaded once, on first use of the engine. A plugin that
fails to load is listed with its error and otherwise ignored; it never stops the engine.
What happens without a plugin. A run spec with "fit" or "backward", or a system with a transform element,
stops with a message naming the fitting plugin; an unknown element type or CLI command says which plugin provides it
when it is a first-party one. Recorded data (an instrument's "measured", a measured input spectrum) is part of the
core and works without any plugin (Measured data).
NativeAOT builds (the self-contained C library) cannot load assemblies at run time: plugins must be compiled in
and registered in code (PluginManager.Register(new FittingPlugin())). The hosted engine (Python's default, the
CLI, .NET hosts) loads them dynamically.
Plugin folder layout¶
my-plugin/
plugin.json
lib/net10.0/MyPlugin.dll # .NET 10 build
lib/net48/MyPlugin.dll # .NET Framework 4.8 build (optional)
python/my_plugin/__init__.py # optional Python module
tools/... # optional programs (the zemax plugin ships UltrafastEngine.Zemax.exe here)
README.md
plugin.json:
{
"id": "my-plugin",
"name": "My plugin",
"version": "1.0.0",
"description": "One sentence: what it adds.",
"engine": "2.2.0",
"entry": "MyCompany.MyPlugin.Plugin",
"assemblies": { "net10.0": "lib/net10.0/MyPlugin.dll", "net48": "lib/net48/MyPlugin.dll" },
"python": { "module": "my_plugin" },
"commands": ["my-command"]
}
| key | meaning |
|---|---|
id |
unique, lower-case; must equal IPlugin.Id |
version |
the plugin's version |
engine |
minimum engine version (2.2.0); older engines list the plugin as not loaded |
entry |
full name of the IPlugin class (optional: the first IPlugin in the assembly) |
assemblies |
assembly per target framework; a plugin without assemblies is Python-only |
python.module |
module imported from python/ by the ultrafast package |
commands |
informational list of CLI commands |
Writing a plugin (C#)¶
Reference UltrafastEngine.Core (do not ship it in the plugin folder: it is the host's) and implement
UltrafastEngine.Plugins.IPlugin:
using UltrafastEngine.Plugins;
using UltrafastEngine.Schema;
public sealed class Plugin : IPlugin
{
public string Id => "my-plugin";
public string Name => "My plugin";
public string Version => "1.0.0";
public string Description => "A flat attenuator element and a peak-power instrument.";
public void Register(PluginContext ctx)
{
// an element type: compile it into linear terms / a nonlinear segment / an active element
ctx.RegisterElement("attenuator",
(services, element, compiled) => compiled.Post.Add(new FilterTerm(new ConstantFilter(services.Num(element, "transmission", 0.5)), "attenuator")),
new ComponentSpec("element", "attenuator", "A flat loss.").Num("transmission", "", "Power transmission", "0.5"));
// an instrument kind
ctx.RegisterInstrument("peakPower", spec => new PeakPowerMeter(),
new ComponentSpec("instrument", "peakPower", "Peak power."));
// models available in every model registry (index, timeResponse, filter, beam, ...)
ctx.RegisterModels(reg => reg.Register<ISpectralFilter>("filter", "myFilter", (s, r) => new MyFilter(s)));
}
}
Declaring a ComponentSpec for every element and instrument puts its options into the
JSON Schemas, the option reference and the unknown-key checks automatically.
services.Num(element, key, default) and services.Str(...) read values through overrides, so run-spec
overrides work for plugin elements like for built-in ones.
Extension points¶
PluginContext method |
what it adds | used by |
|---|---|---|
RegisterElement(type, compiler, spec) |
an element type (ElementCompiler(services, element, compiled)) |
|
RegisterInstrument(kind, factory, spec) |
an instrument kind (IInstrument) |
|
RegisterModels(configure) |
model kinds in every ModelRegistry (index, attenuation, timeResponse, filter, beam, delayedResponse, aperture) |
|
RegisterInstrumentHook(hook) |
IInstrumentHook.AfterMeasure runs after every instrument whose spec has one of the hook's Keys (after the core's measured comparison) |
|
RegisterRunMode(mode) |
IRunMode.Run replaces a plain run when the run spec has the mode's top-level Key |
fitting (fit) |
RegisterResultExtension(ext) |
sections in summary.json and extra files in the results folder |
fitting (fit, fit.json, backward) |
RegisterReporter(reporter) |
lines printed by ultrafast run |
fitting |
RegisterCommand(command) |
an ultrafast <name> subcommand (ICliCommand) |
zemax (zemax-demo, ...) |
RegisterSpecKeys(spec) |
extra keys for the checks and schemas: category runTop (top-level run-spec keys) or instrumentCommon (keys of every instrument) |
fitting |
RegisterBackend(backend) |
a compute back-end (IComputeBackend) selectable with run.compute.backend (Speed) |
GPU back-ends |
RegisterRetrieval(retrieval) |
a pulse-retrieval method (IRetrieval) for ultrafast retrieve / uf.retrieve (Pulse retrieval) |
Plugins attach their own data to a result in RunResult.Extensions[key] (the fitting plugin offers typed access
with the extension method result.Fit()). Comparisons with recorded data are core: result.Comparisons
(UltrafastEngine.Measured.Comparison).
Building the folder¶
The first-party plugins import build/Plugin.targets, which lays out artifacts/plugins/<id>/ after every build
(PluginId and PluginFolder set in the project). Your own plugin can import it too, or produce the same layout
by hand.
Writing the Python part¶
python/my_plugin/__init__.py may define register(host); host.register_figure(name, maker, after=None) adds a
figure to ultrafast-plot and ultrafast.plotting.report (maker(result) returns a matplotlib figure, or None
when the result has no data for it). Anything else in the module is the plugin's Python API
(uf.plugins.module("my-plugin")).
Tests¶
PluginTests registers a test plugin with an element and an instrument and runs them; checks that the first-party
plugins are registered and their installable folders load; installs and removes a plugin in a temporary
ULTRAFAST_HOME; refuses a plugin that needs a newer engine; orders pre-release versions.