// Built-in types:
double run_duration_seconds{42.};
double building_height_meters{99.};
// Unit types:
std::chrono::seconds run_duration{42.}
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 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 data:
Matrix storage;
Some Constructors
// Safe default:
typed_matrix()
  requires std::default_initializable<Matrix>;

// Compatible copy conversion:
typed_matrix(const same_as_typed_matrix auto &other);

// Singleton matrix from convertible value.
typed_matrix(
    const std::convertible_to<element<>> auto &value)
  requires singleton_typed_matrix<typed_matrix>;
// Uniformly typed vector from array:
typed_matrix(const element<> (&elements)[rows * columns])
  requires uniform_typed_matrix<typed_matrix>
       and one_dimension_typed_matrix<typed_matrix>;

// 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>;
// Vector from values:
typed_matrix(const auto &first_value,
             const auto &second_value,
             const auto &...values)
  requires one_dimension_typed_matrix<typed_matrix>;

// ! 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.
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>);

// ! Underlying data access:
decltype(auto) data(this auto &&self);
}; // typed_matrix
Some 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

Examples
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>;
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 multiplication 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.");
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.");});});});
PERFORMANCE
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...})]);
}
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)
At Conversion
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
  }
};
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.
Tuple Sizes
Index Safety
Next

Compose More Safeties
Index
Frame
Taxonomy

Compliance, Ergonomics
Regression, Performance
Ecosystem

Typed Linear Algebra

François Carouge
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
---