4. Calling generated code¶
In the previous section we created a simple symbolic smoothstep function and then generated the equivalent code. In this section we will walk through the process of using our new function.
Note
For examples of integrating generated functions with GTSAM, Ceres, and Sophus see the wrenfold-extra-examples repository.
4.1. C++¶
The step_clamped
function we generated begins with:
template <typename Scalar, typename T1>
Scalar step_clamped(const Scalar x, T1&& df)
{
auto _df = wf::make_optional_output_span<2, 1>(df);
// ...
}
Where Scalar
is expected to be a floating point type. But what of T1
? This is the type of
our optional vector-valued output argument df
. By default, wrenfold emits a generic forwarding
reference for all output matrices and vectors. The rationale is twofold:
We can maximize the number of linear algebra libraries that work with generated code. The user need only specialize wf::convert_to_span (more on this below) for their preferred types.
With a forwarding reference we can accept non-const r-value arguments (such as temporary views into larger buffers).
Of course, you can customize code generation to your liking if this behavior is undesirable.
The generated function invokes wf::make_optional_output_span<2, 1>
on df
in order to create
a 2D span with dimensions (2, 1)
. Because this argument is optional, we can pass one of two
things:
Any type that implements the wf::convert_to_span trait.
Or
std::nullptr_t
, which indicates we do not care about filling this output.
The span type itself resides in the wrenfold runtime, a small header only C++17 library that provides an n-dimensional span type used to pass arguments to and from generated functions. You should ensure these headers are on your project’s include path.
4.1.1. Implementing convert_to_span
¶
In the interest of providing a complete example, we will assume you have a custom matrix type that looks something like:
class simple_matrix {
public:
// ...
constexpr int rows() const { return rows_; }
constexpr int cols() const { return cols_; }
const double* data() const { return values_.data(); }
double* data() { return values_.data(); }
// ...
private:
int rows_;
int cols_;
std::vector<double> values_;
};
Next we will specialize wf::convert_to_span
for simple_matrix
:
// `Dimensions` is a wf::value_pack of compile-time constants.
template <typename Dimensions>
struct wf::convert_to_span<Dimensions, simple_matrix> {
// We will accept our matrix by forwarding reference, which allows us to easily handle
// const (input) and non-const (output) matrices.
template <typename U>
auto convert(U&& mat) const {
// Double check our dynamically-sized matrix matches the expected dimensions.
assert(wf::constant_value_pack_axis_v<0, Dimensions> == mat.rows());
assert(wf::constant_value_pack_axis_v<1, Dimensions> == mat.cols());
// For column major our strides will be (1, rows). For row major they would be
// (cols, 1).
auto strides = wf::make_value_pack(wf::constant<1>{}, mat.rows());
return wf::make_span(mat.data(), Dimensions{}, strides);
}
};
The example above is simplified. In practice you may wish to have different specializations for
dynamic vs. static matrices, or support a matrix type with non-contiguous data. See the
wrenfold/span.h
header for an example implementation for Eigen.
With our custom specialization in hand, we can call step_clamped
with our matrix class:
// Fill `diff` with the optional output argument.
simple_matrix diff(2, 1);
const double step_1 = step_clamped(0.237, diff);
// In cases where we do not care about the optional output, pass nullptr.
const double step_2 = step_clamped(0.781, nullptr);
4.1.2. Using Eigen¶
A default implementation of wf::convert_to_span
is provided for use with
Eigen.
To activate it, #define WF_SPAN_EIGEN_SUPPORT
prior to including wrenfold/span.h
. This
will enable conversion of all types that inherit from Eigen::MatrixBase
or
Eigen::QuaternionBase
.
#define WF_SPAN_EIGEN_SUPPORT
#include <wrenfold/span.h>
// ... later at the call-site:
Eigen::Vector2d diff{};
const double step_1 = step_clamped(0.237, diff);
// We can also pass views or blocks from larger matrices.
// Place the two derivative values into the top (1, 2) corner:
Eigen::Matrix4d buffer{};
const double step_2 = step_clamped(0.448, buffer.topLeftCorner<1, 2>().transpose());
4.1.3. Including requisite headers¶
Generated C++ functions depend directly on:
The C++ STL headers
<cmath>
and<cstdint>
.The wrenfold runtime, a header-only C++17 library that provides the
span
type. The runtime depends on<tuple>
and<type_traits>
.
You can add these includes to your output code manually, or use the provided convenience function:
wrenfold.code_generation.CppGenerator.apply_preamble()
.
4.2. Rust¶
In rust, our sample function step_clamped
begins with:
#[inline]
#[allow(non_snake_case, clippy::unused_unit, clippy::collapsible_else_if,
clippy::needless_late_init, unused_variables)]
pub fn step_clamped<T1, >(x: f64, df: Option<&mut T1>) -> f64
where
T1: wrenfold_traits::OutputSpan2D<2, 1, ValueType = f64>, {
// ...
}
In rust, the span trait is an explicit constraint on the generic type T1
. The traits are defined
in the wrenfold-traits crate. In this example, we can
pass any type that implements OutputSpan2D
for (D0 = 2, D1 = 1)
:
/// A two-dimensional mutable output span with shape `(D0, D1)`.
pub trait OutputSpan2D<const D0: usize, const D1: usize> {
/// The spanned scalar type.
type ValueType;
/// Set element `(i, j)` to `val`.
fn set(&mut self, i: usize, j: usize, val: Self::ValueType);
}
A default implementation is provided for nalgebra
matrices and vectors. The nalgebra
feature must enabled to use this feature.
Warning
The rust code generator is currently limited to emitting functions for a single scalar type at once.