Extending bngsim¶
Adding a New Built-in Function (Developer Guide)¶
To add a permanent built-in function to BNGsim (like mratio), edit
bngsim/src/expression.cpp:
Step 1: Implement the function logic. Write a plain C++ function:
static double my_func_impl(double x, double y) {
return x * std::exp(-y); // your math here
}
Step 2: Create an ExprTk adapter class. ExprTk requires inheriting from
exprtk::ifunction<T>. The constructor argument is the number of parameters:
template <typename T>
struct MyFunction : public exprtk::ifunction<T> {
MyFunction() : exprtk::ifunction<T>(2) {} // 2 arguments
T operator()(const T& x, const T& y) override {
return static_cast<T>(my_func_impl(
static_cast<double>(x), static_cast<double>(y)));
}
};
Step 3: Register in the Impl constructor. In ExprTkEvaluator::Impl::Impl():
// Add as member: MyFunction<double> my_func;
// Then in constructor:
symbol_table.add_function("myfunc", my_func);
Step 4: Add to reserved_names(). In the reserved_names() function at the
bottom of expression.cpp, add "myfunc" to the names.functions vector.
Step 5: Add to is_exprtk_reserved() if the name is bngsim-specific.
If myfunc is not on ExprTk’s reserved_symbols[] list (check
bngsim/third_party/exprtk/exprtk.hpp lines 451-466) and is not on
BNG2.pl’s %functions list (bionetgen/bng2/Perl2/Expression.pm:56-96),
add "myfunc" to the is_exprtk_reserved() set under the
“bngsim-registered function aliases” section. This activates the
mangling path so a user parameter with the same name registers under
the key r_myfunc instead of failing with a confusing “name already
registered” error. (Skipping this step is what caused the original
sign-collision bug — see the corresponding CHANGELOG entry under
[Unreleased] / Fixed.)
Step 6: Test. Create a .net test file that uses myfunc(x, y) in a function
expression, verify it compiles and evaluates correctly. If you added the
name to is_exprtk_reserved() in step 5, also add a regression test
with the name used as a user parameter — modeled on
tests/data/sign_as_parameter.net and
python/tests/test_sign_as_parameter.py.
Supported arities: 0 (use allow_zero_parameters() = true), 1, 2, or 3 arguments.
For 0-argument functions that read external state (like time()), store a pointer
in the adapter class. See TimeFunction and MratioFunction in expression.cpp
as examples.