C API (C, C++ and everything else)¶
Header: src/UltrafastEngine.Native/include/ultrafast.h. Library: ultrafast.dll / libultrafast.so /
libultrafast.dylib, built with NativeAOT -- a self-contained native library, no .NET installation needed by
the caller:
dotnet publish src/UltrafastEngine.Native -c Release -r win-x64 # linux-x64, linux-arm64, osx-x64, osx-arm64
(NativeAOT needs the C++ build tools: Visual Studio "Desktop development with C++" on Windows, clang on
Linux/macOS. -p:UF_NO_AOT=true builds the managed, hosted variant instead.)
Life cycle¶
uf_system* sys; uf_system_load_file("stretcher.ufs", &sys); // or uf_system_load_json(json, &sys)
uf_run* run; uf_run_create_file(sys, "run.json", &run); // or uf_run_create(sys, json, &run)
uf_run_set_time_response_callback(run, "plasma", my_plasma, &state, 0);
uf_run_set_progress_callback(run, my_progress, NULL);
uf_result* res; uf_run_execute(run, &res);
char* json; uf_result_summary_json(res, &json); ...; uf_free(json);
int32_t nd; int64_t shape[4], n; uf_result_array_info(res, "output_spectrum", &nd, shape, &n);
double* buf = malloc(n * sizeof(double)); uf_result_array_copy(res, "output_spectrum", buf, n);
uf_result_write(res, "out");
uf_release(res); uf_release(run); uf_release(sys);
- Every function returns
UF_OK(0) or a negativeUF_E_*code;uf_last_error()gives the message (thread-local). Codes:ARGUMENT -1,PARSE -2,MODEL -3,RUN -4,NOT_FOUND -5,CALLBACK -6,CANCELLED -7,BUFFER -8. - Strings returned through
char**are UTF-8 and freed withuf_free; handles withuf_release. - Different handles may be used from different threads; one handle must not be used concurrently.
uf_capabilitiesreturns the built-in model kinds, materials, element types and instruments (JSON).
All functions¶
| group | functions |
|---|---|
| library | uf_version(), uf_last_error(), uf_free(p), uf_release(h), uf_capabilities(&json), uf_command(request, &json), uf_devices_json(options, &json), uf_set_default_backend(name), uf_crystal_phase_match(request, &json), uf_pulse_calc(request, &json) |
| system | uf_system_load_file(path, &sys), uf_system_load_json(json, &sys), uf_system_to_json(sys, &json), uf_system_validate(sys, &report) |
| run | uf_run_create(sys, json, &run), uf_run_create_file(sys, path, &run), the uf_run_set_*_callback functions below, uf_run_execute(run, &res) |
| result | uf_result_summary_json(res, &json), uf_result_provenance_json(res, &json), uf_result_array_names(res, &json), uf_result_array_info(res, name, &ndim, shape4, &length), uf_result_array_copy(res, name, dest, capacity), uf_result_write(res, directory) |
uf_version and uf_last_error return strings owned by the library: do not free them. A Branched system is the same file with "topology": "branched", so the C API has no calls of its own for it: uf_system_load_* takes either form, and uf_result_summary_json gains graph (Branched systems).
Callbacks¶
All callbacks are vectorised (whole arrays per call) and return 0 for success (non-zero aborts the run with
UF_E_CALLBACK). They are registered under a name and referenced from JSON as {"kind": "callback", "name": ...}
or "@name".
The header names these types uf_time_response_fn, uf_index_fn, uf_filter_fn, uf_beam_fn, uf_array_fn, uf_element_fn, uf_field_fn and uf_progress_fn.
| registration | signature | used as |
|---|---|---|
uf_run_set_time_response_callback(run, name, fn, user, carrier_resolved) |
int fn(void* user, const uf_response_ctx* ctx, double* dn_re, double* dn_im) |
timeResponse |
uf_run_set_index_callback |
int fn(void* user, int32_t n, const double* omega, double* n_out) |
material index |
uf_run_set_filter_callback |
int fn(void* user, int32_t n, const double* omega, double* T, double* phase) |
filter |
uf_run_set_beam_callback |
int fn(void* user, int32_t n, const double* z, double* wx, double* wy) |
beam |
uf_run_set_array_callback |
int fn(void* user, int32_t n, const double* x, double* y) |
raman (h(t)); as name#index for attenuation |
uf_run_set_element_callback(run, name, fn, user) |
int fn(void* user, uf_element_ctx* ctx) -- modify ctx->ax_re/ax_im/ay_re/ay_im in place |
user element ("callback": "@name") |
uf_run_set_field_callback(run, name, fn, user) |
int fn(void* user, int32_t n, const double* x, const double* y, const double* z, double* value) |
position-dependent properties ("@name", spatial-properties.md) |
uf_run_set_progress_callback |
int fn(void* user, const char* element_id, int32_t i, int32_t count, double fraction) |
return non-zero to cancel |
uf_response_ctx carries n, t, t_lab, t_arrival, z, length, intensity, intensity_x,
intensity_y, instant_intensity, field (NULL in envelope mode), omega0, area, carrier_resolved,
element_id, and (at the end of the struct) x, y (m) and spatial (1 on spatial grids). Output arrays arrive zeroed.
uf_element_ctx carries n, omega (absolute, ascending), t, omega0, dt, the writable spectra
ax_re, ax_im, ay_re, ay_im, arrival, element_id, params_json.
The phase-matching calculator is a plain function: uf_crystal_phase_match(const char* request_json, char** result_json)
(free the result with uf_free); request and result as in Crystals. uf_pulse_calc(request_json, &result_json) is the same for the pulse calculator (op: shapes, limit, analyze, measure).
Studies, retrieval and the material library go through one JSON entry point,
uf_command(const char* request_json, char** result_json) (free the result with uf_free; relative paths are
relative to the current directory):
char* out;
uf_command("{\"command\": \"retrieve\", \"method\": \"pcgpa\", \"trace\": \"results/result.npz\", \"output\": \"retrieved\"}", &out);
uf_command("{\"command\": \"study\", \"system\": \"chain.ufs\", \"run\": \"run.json\", \"mode\": \"scan\","
" \"parameters\": [{\"path\": \"pulse/energyJ\", \"min\": 1e-4, \"max\": 1e-2, \"scale\": \"log\", \"points\": 5}],"
" \"outputs\": [\"record:rod/BIntegralRad\"], \"output\": \"study-results\"}", &out);
uf_command("{\"command\": \"materials.show\", \"name\": \"main/SiO2/Malitson\"}", &out);
Commands: study (Parameter studies), retrieve (Pulse retrieval),
materials.list, materials.show, materials.import (Materials),
devices and compute.default (below).
Compute devices (GPU)¶
char* json;
uf_devices_json(NULL, &json); /* {"defaultBackend", "fft", "backends": [...], "devices": [...]} */
uf_free(json);
uf_devices_json("{\"selftest\": true, \"benchmark\": false, \"backend\": \"cuda\", \"device\": 0}", &json);
uf_set_default_backend("auto"); /* runs without "compute": {"backend"} use the GPU when it helps */
uf_devices_json lists the CPU and every CUDA / OpenCL device; with "selftest": true each device is compared with
the CPU on standard problems ("selftest": [{"backend", "device", "passed", "checks": [...]}]), with
"benchmark": true it is also timed. uf_set_default_backend sets the back-end of runs whose spec names none
("cpu", "auto", "cuda", "opencl"; NULL restores ULTRAFAST_BACKEND or "cpu"); an unknown name returns
UF_E_ARGUMENT. The library has the gpu plugin compiled in (unless it was packaged without it);
docs/examples/Interop/cpp/devices.cpp is a complete program.
A complete C++ program is in docs/examples/Interop/cpp (CMake; opens directly in Visual Studio).
Other languages¶
Anything with a C FFI works: MATLAB (loadlibrary('ultrafast', 'ultrafast.h')), Julia (ccall), Fortran
(bind(C)), LabVIEW (Call Library Function), Rust (bindgen). The Python package is a thin ctypes layer
over this same API.