Janus 2.0.0
High-performance C++20 dual-mode numerical framework
Loading...
Searching...
No Matches
Function.hpp
Go to the documentation of this file.
1
3#pragma once
4
5#include "JanusError.hpp"
6#include "JanusTypes.hpp"
7#include <Eigen/Dense>
8#include <casadi/casadi.hpp>
9#include <string>
10#include <vector>
11
12#include <atomic>
13#include <tuple>
14#include <utility>
15
16namespace janus {
17
27
28namespace detail {
29inline std::string to_casadi_parallelization(MapParallelization parallelization) {
30 switch (parallelization) {
32 return "openmp";
34 return "serial";
36 return "unroll";
37 }
38 throw InvalidArgument("Function::map: unsupported map parallelization mode");
39}
40} // namespace detail
41
46class Function {
47 public:
55 Function(const std::string &name, const std::vector<SymbolicArg> &inputs,
56 const std::vector<SymbolicArg> &outputs)
57 : fn_(name, convert_args(inputs), convert_args(outputs)) {}
58
64 Function(const std::vector<SymbolicArg> &inputs, const std::vector<SymbolicArg> &outputs)
65 : fn_(generate_unique_name(), convert_args(inputs), convert_args(outputs)) {}
66
67 private:
68 static std::string generate_unique_name() {
69 static std::atomic<uint64_t> counter{0};
70 return "janus_fn_" + std::to_string(counter.fetch_add(1));
71 }
72
73 static std::vector<SymbolicScalar> convert_args(const std::vector<SymbolicArg> &args) {
74 std::vector<SymbolicScalar> ret;
75 ret.reserve(args.size());
76 for (const auto &arg : args) {
77 ret.push_back(arg.get()); // or implicit cast
78 }
79 return ret;
80 }
81
82 public:
83 private:
84 // Helper traits to detect symbolic arguments
85 template <typename T> struct is_symbolic_type : std::false_type {};
86 template <> struct is_symbolic_type<casadi::MX> : std::true_type {};
87
88 // Specialization for Eigen::Matrix
89 template <typename S, int R, int C, int O, int MR, int MC>
90 struct is_symbolic_type<Eigen::Matrix<S, R, C, O, MR, MC>>
91 : std::conditional_t<std::is_same_v<S, casadi::MX>, std::true_type, std::false_type> {};
92
93 // Helper to normalize input to MX
94 template <typename T> casadi::MX normalize_arg(const T &val) const {
95 if constexpr (std::is_arithmetic_v<std::decay_t<T>> ||
96 std::is_same_v<std::decay_t<T>, casadi::MX> ||
97 std::is_same_v<std::decay_t<T>, casadi::DM>) {
98 return casadi::MX(val);
99 } else {
100 return janus::to_mx(val);
101 }
102 }
103
104 // Helper to normalize input to DM (strictly numeric)
105 template <typename T> casadi::DM normalize_arg_dm(const T &val) const {
106 using DecayT = std::decay_t<T>;
107 if constexpr (std::is_arithmetic_v<DecayT>) {
108 return casadi::DM(double(val));
109 } else if constexpr (std::is_same_v<DecayT, std::vector<double>>) {
110 return casadi::DM(val);
111 } else {
112 // Assume Eigen numeric matrix
113 casadi::DM m(val.rows(), val.cols());
114 for (int i = 0; i < val.rows(); ++i)
115 for (int j = 0; j < val.cols(); ++j)
116 m(i, j) = double(val(i, j));
117 return m;
118 }
119 }
120
121 public:
130 template <typename... Args> auto operator()(Args &&...args) const {
131 constexpr bool is_symbolic = (is_symbolic_type<std::decay_t<Args>>::value || ...);
132
133 if constexpr (is_symbolic) {
134 std::vector<casadi::MX> mx_args;
135 mx_args.reserve(sizeof...(args));
136 (mx_args.push_back(normalize_arg(std::forward<Args>(args))), ...);
137
138 std::vector<casadi::MX> res_mx = fn_(mx_args);
139 return to_eigen_vector<casadi::MX>(res_mx);
140 } else {
141 std::vector<casadi::DM> dm_args;
142 dm_args.reserve(sizeof...(args));
143 (dm_args.push_back(normalize_arg_dm(std::forward<Args>(args))), ...);
144
145 std::vector<casadi::DM> res_dm = fn_(dm_args);
146 return to_eigen_vector<double>(res_dm);
147 }
148 }
149
156 template <typename... Args> auto eval(Args &&...args) const {
157 auto results = operator()(std::forward<Args>(args)...);
158 return results[0];
159 }
160
164 std::vector<NumericMatrix> operator()(std::vector<double> &args) const {
165 return operator()(const_cast<const std::vector<double> &>(args));
166 }
167
171 std::vector<NumericMatrix> operator()(const std::vector<double> &args) const {
172 std::vector<casadi::DM> dm_args;
173 dm_args.reserve(args.size());
174 for (double val : args) {
175 dm_args.push_back(casadi::DM(val));
176 }
177
178 auto res_dm = fn_(dm_args);
179 return to_eigen_vector<double>(res_dm);
180 }
181
186 const casadi::Function &casadi_function() const { return fn_; }
187
207 return map(n, detail::to_casadi_parallelization(parallelization));
208 }
209
216 Function map(int n, const std::string &parallelization) const {
217 if (n <= 0) {
218 throw InvalidArgument("Function::map: batch size n must be positive");
219 }
220 return Function(fn_.map(n, parallelization));
221 }
222
231 Function map(int n, MapParallelization parallelization, int max_num_threads) const {
232 return map(n, detail::to_casadi_parallelization(parallelization), max_num_threads);
233 }
234
242 Function map(int n, const std::string &parallelization, int max_num_threads) const {
243 if (n <= 0) {
244 throw InvalidArgument("Function::map: batch size n must be positive");
245 }
246 if (max_num_threads <= 0) {
247 throw InvalidArgument("Function::map: max_num_threads must be positive");
248 }
249 return Function(fn_.map(n, parallelization, max_num_threads));
250 }
251
252 private:
253 explicit Function(casadi::Function fn) : fn_(std::move(fn)) {}
254
255 casadi::Function fn_;
256
257 template <typename Scalar, typename CasadiType>
258 std::vector<JanusMatrix<Scalar>> to_eigen_vector(const std::vector<CasadiType> &dms) const {
259 std::vector<JanusMatrix<Scalar>> ret;
260 ret.reserve(dms.size());
261 for (const auto &dm : dms) {
262 // Use generic converter if possible, or manual
263 if constexpr (std::is_same_v<CasadiType, casadi::MX>) {
264 ret.push_back(janus::to_eigen(dm));
265 } else {
266 using MatType = JanusMatrix<Scalar>;
267 MatType mat(dm.size1(), dm.size2());
268 std::vector<double> elements = static_cast<std::vector<double>>(dm);
269 for (Eigen::Index j = 0; j < mat.cols(); ++j) {
270 for (Eigen::Index i = 0; i < mat.rows(); ++i) {
271 mat(i, j) = elements[j * mat.rows() + i];
272 }
273 }
274 ret.push_back(mat);
275 }
276 }
277 return ret;
278 }
279};
280
281// =============================================================================
282// Lambda-Style Function Construction
283// =============================================================================
284
285namespace detail {
286
290template <typename T> struct is_tuple : std::false_type {};
291template <typename... Ts> struct is_tuple<std::tuple<Ts...>> : std::true_type {};
292template <typename T> inline constexpr bool is_tuple_v = is_tuple<T>::value;
293
297template <typename Tuple, std::size_t... Is>
298std::vector<SymbolicArg> tuple_to_outputs_impl(const Tuple &t, std::index_sequence<Is...>) {
299 return {std::get<Is>(t)...};
300}
301
302template <typename... Ts> std::vector<SymbolicArg> tuple_to_outputs(const std::tuple<Ts...> &t) {
303 return tuple_to_outputs_impl(t, std::index_sequence_for<Ts...>{});
304}
305
309template <typename Func, std::size_t... Is>
310auto invoke_with_symbols_impl(Func &&fn, const std::vector<SymbolicScalar> &syms,
311 std::index_sequence<Is...>) {
312 return fn(syms[Is]...);
313}
314
315template <int NInputs, typename Func>
316auto invoke_with_symbols(Func &&fn, const std::vector<SymbolicScalar> &syms) {
317 return invoke_with_symbols_impl(std::forward<Func>(fn), syms,
318 std::make_index_sequence<NInputs>{});
319}
320
321} // namespace detail
322
348template <int NInputs, int NOutputs, typename Func>
349Function make_function(const std::string &name, Func &&fn) {
350 static_assert(NInputs > 0, "NInputs must be positive");
351 static_assert(NOutputs > 0, "NOutputs must be positive");
352
353 // Create symbolic inputs with auto-generated names
354 std::vector<SymbolicScalar> inputs;
355 inputs.reserve(NInputs);
356 for (int i = 0; i < NInputs; ++i) {
357 inputs.push_back(sym("_x" + std::to_string(i)));
358 }
359
360 // Invoke lambda with unpacked symbols
361 auto result = detail::invoke_with_symbols<NInputs>(std::forward<Func>(fn), inputs);
362
363 // Convert result to output vector
364 std::vector<SymbolicArg> outputs;
365 if constexpr (detail::is_tuple_v<decltype(result)>) {
366 outputs = detail::tuple_to_outputs(result);
367 } else {
368 // Single output
369 outputs = {result};
370 }
371
372 // Convert inputs to SymbolicArg vector
373 std::vector<SymbolicArg> input_args(inputs.begin(), inputs.end());
374
375 return Function(name, input_args, outputs);
376}
377
394template <int NInputs, typename Func>
395Function make_function(const std::string &name, const std::vector<std::string> &input_names,
396 Func &&fn) {
397 static_assert(NInputs > 0, "NInputs must be positive");
398 if (static_cast<int>(input_names.size()) != NInputs) {
399 throw InvalidArgument("make_function: input_names.size() must equal NInputs");
400 }
401
402 // Create symbolic inputs with provided names
403 std::vector<SymbolicScalar> inputs;
404 inputs.reserve(NInputs);
405 for (const auto &iname : input_names) {
406 inputs.push_back(sym(iname));
407 }
408
409 // Invoke lambda with unpacked symbols
410 auto result = detail::invoke_with_symbols<NInputs>(std::forward<Func>(fn), inputs);
411
412 // Convert result to output vector
413 std::vector<SymbolicArg> outputs;
414 if constexpr (detail::is_tuple_v<decltype(result)>) {
415 outputs = detail::tuple_to_outputs(result);
416 } else {
417 outputs = {result};
418 }
419
420 // Convert inputs to SymbolicArg vector
421 std::vector<SymbolicArg> input_args(inputs.begin(), inputs.end());
422
423 return Function(name, input_args, outputs);
424}
425
426} // namespace janus
Custom exception hierarchy for Janus framework.
Core type aliases for numeric and symbolic Eigen/CasADi interop.
Wrapper around casadi::Function providing Eigen-native IO.
Definition Function.hpp:46
const casadi::Function & casadi_function() const
Access the underlying CasADi function.
Definition Function.hpp:186
Function map(int n, MapParallelization parallelization=MapParallelization::Parallel) const
Create a batched version of this function using CasADi's map primitive.
Definition Function.hpp:206
Function map(int n, MapParallelization parallelization, int max_num_threads) const
Create a batched version of this function with an explicit thread cap.
Definition Function.hpp:231
std::vector< NumericMatrix > operator()(const std::vector< double > &args) const
Evaluate with a const vector of doubles.
Definition Function.hpp:171
auto eval(Args &&...args) const
Evaluate function and return first output.
Definition Function.hpp:156
auto operator()(Args &&...args) const
Evaluate function with arbitrary arguments (scalars or Eigen matrices) Handles both numeric (double/M...
Definition Function.hpp:130
Function(const std::string &name, const std::vector< SymbolicArg > &inputs, const std::vector< SymbolicArg > &outputs)
Construct a new Function object.
Definition Function.hpp:55
Function map(int n, const std::string &parallelization) const
Create a batched version using a direct CasADi backend name.
Definition Function.hpp:216
Function map(int n, const std::string &parallelization, int max_num_threads) const
Create a batched version with explicit backend name and thread cap.
Definition Function.hpp:242
std::vector< NumericMatrix > operator()(std::vector< double > &args) const
Evaluate with a mutable vector of doubles.
Definition Function.hpp:164
Function(const std::vector< SymbolicArg > &inputs, const std::vector< SymbolicArg > &outputs)
Constructor with auto-generated name.
Definition Function.hpp:64
Input validation failed (e.g., mismatched sizes, invalid parameters).
Definition JanusError.hpp:31
Smooth approximation of ReLU function: softplus(x) = (1/beta) * log(1 + exp(beta * x)).
Definition Diagnostics.hpp:131
auto invoke_with_symbols_impl(Func &&fn, const std::vector< SymbolicScalar > &syms, std::index_sequence< Is... >)
Invoke lambda with unpacked symbolic arguments.
Definition Function.hpp:310
std::string to_casadi_parallelization(MapParallelization parallelization)
Definition Function.hpp:29
auto invoke_with_symbols(Func &&fn, const std::vector< SymbolicScalar > &syms)
Definition Function.hpp:316
std::vector< SymbolicArg > tuple_to_outputs(const std::tuple< Ts... > &t)
Definition Function.hpp:302
constexpr bool is_tuple_v
Definition Function.hpp:292
std::vector< SymbolicArg > tuple_to_outputs_impl(const Tuple &t, std::index_sequence< Is... >)
Convert a tuple of symbolic scalars to a vector of SymbolicArg.
Definition Function.hpp:298
Definition Diagnostics.hpp:19
Function make_function(const std::string &name, Func &&fn)
Create a Function from a lambda expression.
Definition Function.hpp:349
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > JanusMatrix
Dynamic-size matrix for both numeric and symbolic backends.
Definition JanusTypes.hpp:43
casadi::MX to_mx(const Eigen::MatrixBase< Derived > &e)
Convert Eigen matrix of MX (or numeric) to CasADi MX.
Definition JanusTypes.hpp:189
MapParallelization
Batch mapping backend for janus::Function::map().
Definition Function.hpp:22
@ Unroll
CasADi unrolled map backend.
Definition Function.hpp:25
@ Serial
CasADi serial map backend.
Definition Function.hpp:24
@ Parallel
CasADi OpenMP map backend (falls back to serial if unavailable).
Definition Function.hpp:23
SymbolicScalar sym(const std::string &name)
Create a named symbolic scalar variable.
Definition JanusTypes.hpp:90
Eigen::Matrix< casadi::MX, Eigen::Dynamic, Eigen::Dynamic > to_eigen(const casadi::MX &m)
Convert CasADi MX to Eigen matrix of MX.
Definition JanusTypes.hpp:213
Helper to detect if a type is a std::tuple.
Definition Function.hpp:290