// Built-in types:
double run_duration_seconds{42.};
double building_height_meters{99.};
// Unit types:
std::chrono::seconds run_duration{42.}
mp_units::quantity building_height{99. * m};
// Vector with built-in types:
vector v0{1., 2., 3.};
// std::vector<double>
// std::span<double, 3>
// Eigen::Vector<double, 3>
// Vector with unit types:
vector v0{1. * m, 2. * m, 3. * m};
// std::vector<quantity<m>>
// std::span<quantity<m>, 3> ?
// Eigen::Vector<quantity<m>, 3> ??
// Do as the built-in types?  double * double = double 🗸
//                            length * length = area   𐄂
// Vector with heterogeneous unit types:
vector v0{1. * m, 2. * m / s, 3. * m / s2};
// vector type?
// Matrix with heterogeneous unit types:
matrix m0{{1. * m,     2. * m / s },
          {3. * m / s, 1. * m / s2}};
// matrix type??? 
Objective
// Update the estimate uncertainty P of a Kalman filter:
p = (i - k * h) * p * t(i - k * h) + k * r * t(k);

std::println("{}", p);
// [[8.92 m²,      5.95 m²/s,    1.98 m²/s²],
//  [5.95 m²/s,  503.98 m²/s², 334.67 m²/s³],
//  [1.98 m²/s², 334.67 m²/s³, 444.91 m²/s⁴]]
template <typename Matrix,
          typename RowIndexes,
          typename ColumnIndexes>
class typed_matrix {
Some Public Member Types
// [i-th, j-th] element type:
template <int RowIndex,
          int ColumnIndex>
using element =
  product<std::tuple_element_t<RowIndex,
                               typename Matrix::row_indexes,
          std::tuple_element_t<ColumnIndex,
                              typename Matrix::column_indexes>>;
Some Member Variables
// Sizes are static inline constexpr:
int rows    = std::tuple_size_v<row_indexes>;
int columns = std::tuple_size_v<column_indexes>;

private:
// Underlying algebraic backend:
Matrix storage;
Some Constructors
// Safe default:
typed_matrix()
  requires std::default_initializable<Matrix>;

// Vector from values:
typed_matrix(const auto &first_value,
             const auto &second_value,
             const auto &...values)
  requires rank_typed_matrix<typed_matrix, 1>;

// Singleton matrix from convertible value.
typed_matrix(
    const std::convertible_to<element<>> auto &value)
  requires rank_typed_matrix<typed_matrix, 0>;
// Compatible copy conversion:
typed_matrix(const same_as_typed_matrix auto &other);

// Uniformly typed vector from array:
typed_matrix(const element<> (&elements)[rows * columns])
  requires uniform_typed_matrix<typed_matrix>
       and rank_typed_matrix<typed_matrix, 1>;

// Uniformly typed matrix from init-list of init-lists:
template <typename Type>
typed_matrix(
    std::initializer_list<std::initializer_list<Type>> row_list)
  requires uniform_typed_matrix<typed_matrix>;
Some Accessors
// Compile-time bound-checked typed element read/write:
template <auto... Indexes>
decltype(auto) at(this auto &&self)
  requires(sizeof...(Indexes) == rank);

// ! Subscript operator access:
template <typename... Indexes>
decltype(auto) operator[](this auto &&self, Indexes... indexes)
  requires(sizeof...(Indexes) == rank)
       and((index<Indexes> && ...)
        or uniform_typed_matrix<typed_matrix>);
}; // typed_matrix
Some of Many Algorithms
auto operator+      (const same_as_typed_matrix auto &lhs,
                     const same_as_typed_matrix auto &rhs);

auto operator*      (const same_as_typed_matrix auto &lhs,
                     const same_as_typed_matrix auto &rhs);

void matrix_product (const same_as_typed_matrix auto &lhs,
                     const same_as_typed_matrix auto &rhs,
                           same_as_typed_matrix auto &result);

auto transposed     (const same_as_typed_matrix auto &value);

void scale          (const auto &α,
                     same_as_typed_matrix auto &x);
Linear Algebra Layers
Layer Abstraction Implementation
High-Level Math Domain specific Application
Low-level Math Linear systems, least-squares, eigenvalue LAPACK, Eigen, Armadillo, MatX
Performance Primitives Vector, matrices, operations & solvers std::linalg, BLAS
Fundamentals Multidimensional arrays, iteration std::execution, std::mdspan, std::simd
Explored Implementation

Layer Implementation
High-Level Kalman
Low-Level Eigen
std::mdspan
LAPACK
Primitives BLAS
NVBLAS
std::linalg

Element Type:
mp-units, std::chrono, fundamental types

Support
template <typename To, mp_units::Quantity From>
struct element_caster<To, From> {
  To operator()(From value) {
    static_assert(std::same_as<To, typename From::rep>);

    return value.numerical_value_in(value.unit);
  }
};
template <typename... Types>
using column_vector = typed_column_vector<
                        Eigen::Vector<double, sizeof...(Types)>,
                        Types...>;
using position = quantity<mp_units::isq::length[m]>;
using state    = column_vector<position, velocity>;
using stateᵀ   = row_vector<position, velocity>;
Example
state x0{3. * m, 2.5 * m / s};
std::println("{}", x0); // [[3 m], [2.5 m/s]]
std::println("{}", x0.at<1>()); // 2.5 m/s

stateᵀ x0ᵀ{transposed(x0)};
std::println("{}", x0ᵀ * x0); // ?
//                       ^~
// error: static assertion failed:
// Matrix product requires compatible types.
std::println("{}", x0 * x0ᵀ);
// [[9 m², 7.5 m²/s], [7.5 m²/s, 6.25 m²/s²]]
// Non-owning typed vector:
template <typename... Types>
using column_vector =
  typed_column_vector<
    std::mdspan<
      double, 
      std::extents<std::size_t, sizeof...(Types), 1>>,
    Types...>;
std::vector v0(2, 0.);
std::mdspan s0{v0.data(), std::extents<std::size_t, 2, 1>{}};
state x0{s0};

// ...

matrix_product(x0, x0ᵀ, p);
std::println("{}", p);
// [[9 m², 7.5 m²/s], [7.5 m²/s, 6.25 m²/s²]]
Matrix-Matrix Product
auto operator*(const same_as_typed_matrix auto &lhs,
               const same_as_typed_matrix auto &rhs) {

// Type safety?

using row_indexes = product<lhs_row_indexes,
                      std::tuple_element_t<0, lhs_column_indexes>>;
using column_indexes = product<rhs_column_indexes,
                         std::tuple_element_t<0, rhs_row_indexes>>;

return make_typed_matrix<row_indexes, column_indexes>(
  lhs.data() * rhs.data());
}
static_assert(lhs::columns == rhs::rows,
              "Matrix-matrix product requires compatible sizes.");
NAIVE
for_constexpr<0, lhs::rows, 1>([&](auto i) {
 using lhs_row = product<std::tuple_element_t<i, lhs_row_indexes>,
                         lhs_column_indexes>;
 for_constexpr<0, rhs::columns, 1>([&](auto j) {
  using rhs_column = product<rhs_row_indexes,
                    std::tuple_element_t<j, rhs_column_indexes>>;
  for_constexpr<0, lhs::columns, 1>([&](auto k) {

   static_assert(std::is_convertible_v<
      product<std::tuple_element_t<k, lhs_row>,
              std::tuple_element_t<k, rhs_column>>,
      product<std::tuple_element_t<0, lhs_row>,
              std::tuple_element_t<0, rhs_column>>>,
    "Matrix-matrix product requires compatible types.");});});});
User Defined Literal
template <char... Digits>
constexpr auto operator""_i(); // Returns an integral constant.
// Equivalents for heterogenous matrices:
x0.at<0, 0>()
x0[0_i, 0_i]
x0(0_i, 0_i)
Index Safety
state x{3. * m,
        2. * m / s,
        1. * m / s2};

std::println("{}", x.at<2>()); // Is this velocity?

// Index Safety:
std::println("{}", x.at<velocity>());
// 2 m/s
at
template <auto... Indexes>
decltype(auto) at(this auto &&self)
  requires(sizeof...(Indexes) == rank);
{
  // ...
  return cast<qualified_element, qualified_underlying>(
      self.storage[std::get<0>(std::tuple{Indexes...}),
                    std::get<1>(std::tuple{Indexes...})]);
}
Dragons
state x0{3. * m, 2.5 * m / s};
x0.at<0>() = 2 * m / s; // lvalue reference assignment?
template <mp_units::Quantity To, typename From>
struct element_caster<To &, From &> {
  To & operator()(From &value) {
    static_assert(std::same_as<typename To::rep, From>);
    static_assert(sizeof(To) == sizeof(From));
    static_assert(alignof(To) == alignof(From));
   
    To __attribute__((__may_alias__)) *q{
      reinterpret_cast<To *>(&value)};
    return *q; // 𐄂 Undefined Behavior
  }
};
Write Alternatives
Alternative Drawback
lvalue reference Undefined behavior.
Loss of constexpr.
setter Loss of ergonomics.
Loss of structured bindings.
reference wrapper Loss of ergonomics.
Loss of structured bindings.
Requires type support.
strong storage Performance loss?
Strongly Typed Storage
Future Work
  • Positional access replaced by typed access.
  • Exhaustive std::linalg / Eigen algorithms.
  • Sparse matrices, dynamic sizes.
  • Tensor? N-dimension generatlization?
  • Memory ownership: Yes? No? Both?
  • Optimize type-list / std::tuple compile-time scalability.
  • Optimize compile-time.
  • Nondimensionalization.
  • Usage feedback. Let’s play!

Typed Linear Algebra

github.com/FrancoisCarouge

Parking

Abstract

Typed Linear Algebra
How to Not Crash on Mars
Quantity-Safe Linear Algebra Use Case: Eigen + mp-units

A practical approach to achieving quantity-safe linear algebra in C++ through the composition of the Eigen and mp-units libraries. The proposed method integrates dimensional analysis in linear algebra computations, ensuring compile-time enforcement of unit correctness while preserving the efficiency and flexibility of established numerical backends. Design principles, implementation strategies, and accumulated experience, including trade-offs in type representation and performance considerations. Lessons learned highlight the feasibility and challenges of embedding strong typing into numerical computation, while preliminary extensions demonstrate the applicability of the approach beyond physical units.

Summary
  • Composed Backend
  • Forward Operations
  • Inject Conversions
  • Deducing This
  • Constexpr For
  • Expression Template
Opportunities
  • Print Format
  • Template For
  • Compiler Compliance
  • Benchmarking
  • std::linalg
  • Index Safety
  • Frame Safety
  • Taxonomy Safety
// ! Underlying matrix conversion:
explicit typed_matrix(const Matrix &other);
Some Concepts
// A typed matrix concept:
template <typename Type> concept same_as_typed_matrix =
  std::same_as<Type, typed_matrix<typename Type::matrix,
                                  typename Type::row_indexes,
                                  typename Type::column_indexes>>;
// A uniformly typed matrix concept:
template <typename Type> concept uniform_typed_matrix = // ...
  // is a typed matrix, and
  // each element are of the same type.
Learnings
  • Strong type propagation
  • std::tuple scalability
  • Index safety