API Reference
Overview
FluxGraph is a high-performance C++17 signal processing library for real-time simulations. It provides:
- Type-safe signal storage with units
- Declarative graph-based data flow
- Modular transforms and physics models
- Deterministic execution engine
Core API
SignalStore
Central storage for all signal values in your simulation.
#include "fluxgraph/core/signal_store.hpp"
fluxgraph::SignalStore store;
Methods
void write(SignalId id, double value, const char* unit) Write a signal value with specified unit. First write declares the unit.
auto temp_id = namespace.intern("chamber.temp");
store.write(temp_id, 25.0, "degC");
double read_value(SignalId id) Read current signal value. Returns 0.0 for invalid or unwritten signals.
double temp = store.read_value(temp_id);
Signal read(SignalId id) Read full signal including value, unit, and physics-driven flag.
auto signal = store.read(sensor_id);
std::cout << "Value: " << signal.value << " " << signal.unit << std::endl;
void declare_unit(SignalId id, const char* unit) Pre-declare expected unit for a signal. Enforces consistency.
store.declare_unit(temp_id, "degC");
store.write(temp_id, 25.0, "degF"); // Error: Unit mismatch
void clear() Reset all signal values to 0.0. Unit declarations persist.
store.clear(); // Values reset, units retained
SignalNamespace
Maps human-readable paths to integer SignalId handles for fast lookups.
#include "fluxgraph/core/namespace.hpp"
fluxgraph::SignalNamespace namespace;
Methods
SignalId intern(const std::string& path) Register a signal path and get its unique ID. Idempotent.
auto id1 = ns.intern("device.sensor1"); // Creates new ID
auto id2 = ns.intern("device.sensor1"); // Returns same ID
assert(id1 == id2);
SignalId resolve(const std::string& path) Lookup existing signal ID. Returns INVALID_SIGNAL_ID if not found.
auto id = ns.resolve("device.sensor2");
if (id == fluxgraph::INVALID_SIGNAL_ID) {
std::cerr << "Signal not found" << std::endl;
}
std::string lookup(SignalId id) Reverse lookup SignalId to path. Returns empty string for invalid IDs.
std::string path = ns.lookup(signal_id);
size_t size() Get total number of interned signals.
std::vector<std::string> all_paths() Get all interned signal paths.
void clear() Remove all signal mappings. Use with caution - invalidates all SignalIds.
FunctionNamespace
Maps device names and function names to integer IDs for command emission.
#include "fluxgraph/core/namespace.hpp"
fluxgraph::FunctionNamespace fn_namespace;
Methods
DeviceId intern_device(const std::string& device_name) Register device name, returns unique DeviceId.
FunctionId intern_function(const std::string& function_name) Register function name, returns unique FunctionId.
Similar resolve/lookup methods as SignalNamespace.
Engine
Executes the simulation graph each tick using five-stage pipeline.
#include "fluxgraph/engine.hpp"
fluxgraph::Engine engine;
Methods
void load(CompiledProgram program) Load compiled graph into engine for execution.
GraphCompiler compiler;
auto program = compiler.compile(spec, namespace, fn_namespace);
engine.load(std::move(program));
void tick(double dt, SignalStore& store) Execute one simulation step with time delta dt.
engine.tick(0.1, store); // 100ms time step
Five-stage execution:
- Pre-tick snapshot (read all signals)
- Model tick (physics updates)
- Edge execution (transform chains)
- Rule evaluation (command emission)
- Post-tick write (commit changes)
std::vector
auto commands = engine.drain_commands();
for (const auto& cmd : commands) {
// Execute command
}
void reset() Reset all internal state (models, transforms, rules) to initial conditions.
engine.reset();
store.clear();
// Ready to run simulation again from t=0
Graph Construction
GraphSpec
Plain Old Data (POD) structure defining the entire simulation graph.
#include "fluxgraph/graph/spec.hpp"
fluxgraph::GraphSpec spec;
EdgeSpec
Defines signal transformation edges.
fluxgraph::EdgeSpec edge;
edge.source_path = "sensor.raw";
edge.target_path = "sensor.filtered";
edge.transform.type = "first_order_lag";
edge.transform.params["tau_s"] = 1.0; // 1 second time constant
spec.edges.push_back(edge);
Available transform types:
linear- Scale and offset: y = scale*x + offsetfirst_order_lag- Low-pass filter: tau * dy/dt + y = xdelay- Time delay: y(t) = x(t - delay_sec)noise- Add Gaussian noise: y = x + N(0, amplitude)saturation- Clamp to bounds: y = clamp(x, min, max)-
deadband- Zero below threshold: y = (x < threshold) ? 0 : x -
rate_limiter- Limit rate of change:dy/dt <= max_rate moving_average- Sliding window average
ModelSpec
Defines physics models.
fluxgraph::ModelSpec model;
model.id = "thermal_chamber";
model.type = "thermal_mass";
model.params["temp_signal"] = std::string("chamber.temp");
model.params["power_signal"] = std::string("chamber.power");
model.params["ambient_signal"] = std::string("ambient.temp");
model.params["thermal_mass"] = 1000.0; // J/K
model.params["heat_transfer_coeff"] = 10.0; // W/K
model.params["initial_temp"] = 25.0; // degC
model.params["integration_method"] = std::string("forward_euler"); // optional: "forward_euler" (default) or "rk4"
spec.models.push_back(model);
Available model types:
thermal_mass- Heat transfer simulationthermal_rc2- Two-node thermal RC network (coupled temperatures)first_order_process- First-order process primitive (PT1)second_order_process- Second-order process primitive (PT2)mass_spring_damper- Translational mass-spring-damper (mechanical)dc_motor- Armature-controlled DC motor (electromechanical)
RuleSpec
Defines condition -> command mappings (future feature).
fluxgraph::RuleSpec rule;
rule.signal_path = "chamber.temp";
rule.condition_type = "threshold";
rule.threshold = 50.0;
rule.device_name = "controller";
rule.function_name = "emergency_stop";
spec.rules.push_back(rule);
GraphCompiler
Compiles GraphSpec into executable form with validation.
#include "fluxgraph/graph/compiler.hpp"
fluxgraph::GraphCompiler compiler;
Methods
CompiledProgram compile(const GraphSpec& spec, SignalNamespace& ns, FunctionNamespace& fn) Compile graph specification into executable program.
Compilation steps:
- Parse transforms, models, rules from spec
- Resolve all signal paths to IDs via namespace
- Topological sort to determine execution order
- Detect cycles (throws exception if found)
- Validate model stability (checks dt vs dynamics)
- Package into CompiledProgram
Throws:
- std::runtime_error on unknown transform/model types
- std::runtime_error on cyclic dependencies
- std::runtime_error on invalid parameters
try {
auto program = compiler.compile(spec, namespace, fn_namespace);
engine.load(std::move(program));
} catch (const std::exception& e) {
std::cerr << "Compilation failed: " << e.what() << std::endl;
}
Graph Loaders
Optional loaders for creating GraphSpec from JSON or YAML files. These require -DFLUXGRAPH_JSON_ENABLED=ON or -DFLUXGRAPH_YAML_ENABLED=ON at CMake configure time.
JSON Loader
Load graphs from JSON files or strings.
#include "fluxgraph/loaders/json_loader.hpp"
GraphSpec load_json_file(const std::string& filepath) Load graph from JSON file.
auto spec = fluxgraph::loaders::load_json_file("graph.json");
fluxgraph::GraphCompiler compiler;
auto program = compiler.compile(spec, signal_ns, func_ns);
engine.load(std::move(program));
GraphSpec load_json_string(const std::string& json_content) Parse graph from JSON string.
std::string json = R"({
"edges": [
{
"source": "input.x",
"target": "output.y",
"transform": {
"type": "linear",
"params": {
"scale": 2.0,
"offset": 0.0
}
}
}
]
})";
auto spec = fluxgraph::loaders::load_json_string(json);
Errors:
std::runtime_error- JSON parse errors, missing required fields, invalid values- Error messages include JSON pointer paths (e.g.,
/edges/2/transform/type)
See also:
- JSON_SCHEMA.md - Complete schema reference
- examples/03_json_graph/ - Working example
YAML Loader
Load graphs from YAML files or strings.
#include "fluxgraph/loaders/yaml_loader.hpp"
GraphSpec load_yaml_file(const std::string& filepath) Load graph from YAML file.
auto spec = fluxgraph::loaders::load_yaml_file("graph.yaml");
fluxgraph::GraphCompiler compiler;
auto program = compiler.compile(spec, signal_ns, func_ns);
engine.load(std::move(program));
GraphSpec load_yaml_string(const std::string& yaml_content) Parse graph from YAML string.
std::string yaml = R"(
edges:
- source: input.x
target: output.y
transform:
type: linear
params:
scale: 2.0
offset: 0.0
)";
auto spec = fluxgraph::loaders::load_yaml_string(yaml);
Errors:
std::runtime_error- YAML parse errors, missing required fields, invalid values- Error messages include node paths (e.g.,
edges[2].transform.type)
See also:
- YAML_SCHEMA.md - Complete schema reference
- examples/04_yaml_graph/ - Working example
Note: Loaders are completely optional. You can always construct GraphSpec programmatically without any file parsing dependencies.
Transforms
ITransform Interface
All transforms implement this interface.
#include "fluxgraph/transform/interface.hpp"
class ITransform {
public:
virtual double apply(double input, double dt) = 0;
virtual void reset() = 0;
virtual std::unique_ptr<ITransform> clone() const = 0;
virtual ~ITransform() = default;
};
apply(input, dt) - Process one sample with time step dt reset() - Reset internal state to initial conditions clone() - Create deep copy (for multi-instancing)
Custom Transforms
Implement ITransform to create custom transforms.
class MyTransform : public fluxgraph::ITransform {
private:
double m_state = 0.0;
public:
double apply(double input, double dt) override {
m_state = 0.9 * m_state + 0.1 * input;
return m_state;
}
void reset() override {
m_state = 0.0;
}
std::unique_ptr<ITransform> clone() const override {
return std::make_unique<MyTransform>(*this);
}
};
Register with factory (see EMBEDDING.md for details).
Models
IModel Interface
All physics models implement this interface.
#include "fluxgraph/model/interface.hpp"
class IModel {
public:
virtual void tick(double dt, SignalStore& store) = 0;
virtual void reset() = 0;
virtual double compute_stability_limit() const = 0;
virtual std::string describe() const = 0;
virtual std::vector<SignalId> output_signal_ids() const = 0;
virtual ~IModel() = default;
};
tick(dt, store) - Update model by dt seconds using signals from store
reset() - Reset to initial state
compute_stability_limit() - Return maximum stable dt for numerical integration
describe() - Return human-readable description
output_signal_ids() - Declare every signal written by tick() for compile-time single-writer validation (must be interned IDs, never INVALID_SIGNAL)
ThermalMassModel
Lumped thermal mass with heat transfer.
Physics equation:
C * dT/dt = P - h * (T - T_ambient)
Parameters:
- C: thermal_mass (J/degC) - Heat capacity
- h: heat_transfer_coeff (W/degC) - Convection coefficient
- P: power_signal (W) - Heat input
- T_ambient: ambient_signal (degC) - Ambient temperature
- integration_method (optional):
"forward_euler"(default) or"rk4"
Stability limit:
forward_euler: dt < 2*C/hrk4: dt < 2.785293563*C/h (negative real-axis stability bound)
Example usage:
ModelSpec model;
model.type = "thermal_mass";
model.params["temp_signal"] = std::string("chamber.temp");
model.params["power_signal"] = std::string("power");
model.params["ambient_signal"] = std::string("ambient");
model.params["thermal_mass"] = 1000.0;
model.params["heat_transfer_coeff"] = 10.0;
model.params["initial_temp"] = 25.0;
model.params["integration_method"] = std::string("rk4"); // optional
Command
Commands emitted by rules for execution by external systems.
#include "fluxgraph/core/command.hpp"
fluxgraph::Command cmd(device_id, function_id);
cmd.add_arg(42.0); // double
cmd.add_arg(int64_t{100}); // int64_t
cmd.add_arg(true); // bool
cmd.add_arg(std::string{"OK"}); // string
Access arguments:
if (std::holds_alternative<double>(cmd.args[0])) {
double value = std::get<double>(cmd.args[0]);
}
Thread Safety
Single-writer model:
- SignalStore: One writer, multiple readers safe
- Engine: tick() must be called from single thread
- Namespace: intern() not thread-safe, resolve() safe after setup
Best practice: Setup phase (single-threaded):
- Build namespace (intern all signal paths)
- Compile graph
- Load engine
Execution phase (can be threaded):
- Engine tick (single thread)
- Multiple readers can call store.read() concurrently
Error Handling
Invalid SignalId:
auto id = ns.resolve("nonexistent");
if (id == fluxgraph::INVALID_SIGNAL_ID) {
// Handle missing signal
}
Compilation errors:
try {
auto program = compiler.compile(spec, ns, fn);
} catch (const std::runtime_error& e) {
// e.what() contains error description
}
Unit mismatches:
store.declare_unit(temp_id, "degC");
store.write(temp_id, 77.0, "degF"); // Error logged, write rejected
Invalid tick:
engine.tick(dt, store); // Throws if no program loaded
Performance Tips
- Reserve signal IDs upfront:
store.reserve(1000); // If you know signal count
- Reuse SignalIds:
// Cache IDs, avoid repeated resolve()
auto temp_id = ns.intern("chamber.temp");
// Use temp_id many times
- Minimize graph complexity:
- Fewer edges = faster execution
- Long transform chains = more overhead
- Choose appropriate dt:
- Too small: wasted computation
- Too large: instability
- Use model.compute_stability_limit() as guide
- Profile before optimizing:
- Run benchmarks (see tests/benchmarks/)
- Most time typically in models, not transforms
Minimal Example
#include "fluxgraph/core/signal_store.hpp"
#include "fluxgraph/core/namespace.hpp"
#include "fluxgraph/graph/compiler.hpp"
#include "fluxgraph/engine.hpp"
int main() {
using namespace fluxgraph;
// Setup
SignalNamespace ns;
FunctionNamespace fn;
SignalStore store;
Engine engine;
// Build graph
GraphSpec spec;
EdgeSpec edge;
edge.source_path = "input";
edge.target_path = "output";
edge.transform.type = "linear";
edge.transform.params["scale"] = 2.0;
edge.transform.params["offset"] = 1.0;
spec.edges.push_back(edge);
// Compile and load
GraphCompiler compiler;
auto program = compiler.compile(spec, ns, fn);
engine.load(std::move(program));
// Execute
auto input_id = ns.resolve("input");
auto output_id = ns.resolve("output");
store.write(input_id, 10.0, "");
engine.tick(0.1, store);
double result = store.read_value(output_id);
// result = 2*10 + 1 = 21
return 0;
}
See examples/ for more complete examples.