Source
stdlib/linalg/concepts.hpp
1
// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).2
// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.3
#pragma once5
/**6
* @file concepts.hpp7
* @brief cheatah `linalg` — the concept layer the algorithms are written against.8
*9
* The routines in routines.hpp used to be typed concretely against `NDArray` /10
* `CNDArray`, with real and complex handled by hand-duplicated overloads. This header11
* introduces the STL-`std::ranges`-style concept surface that lets a single algorithm12
* template serve **any** conforming container and element type, and adds an orthogonal13
* **location** axis so a container knows whether it lives on the host or on a device.14
*15
* Two axes sit on top of the element ladder already defined in ndarray.hpp16
* (`Numeric ⊂ FloatingPoint`, `Field`, `Element`, `Copyable`):17
* - an **access/shape** surface — @ref ArrayLike / @ref NumericArray / @ref FloatArray —18
* defined structurally so both a host `basic_ndarray` and a device array satisfy it;19
* - a **location** axis — @ref host_location + the @ref location_of trait, yielding20
* @ref HostArray / @ref DeviceArray / @ref SameLocation — so that mixing a host and a21
* device operand in one operation is an **unsatisfied constraint (a compile error)**,22
* never a runtime check.23
*24
* cheatah defines only the host tag here; a GPU extension provides its own location tag25
* and specializes @ref location_of for its own container — so the concept vocabulary is26
* shared without this public header ever naming the (private) extension. Element data is27
* reached only through location-specific customization points (see backend.hpp), so the28
* surface below is deliberately the common denominator: host-side metadata only.29
*30
* This header adds no runtime behavior; it is pure compile-time vocabulary.31
*/32
#include <complex>33
#include <concepts>34
#include <cstddef>35
#include <type_traits>36
#include <vector>38
#include "ndarray.hpp"40
namespace cheatah::linalg {42
/// element_t<A>: the scalar an array-like stores — its nested `value_type`, with any43
/// reference/cv-qualification on `A` stripped first so `const NDArray&` and `NDArray`44
/// yield the same element type. Undefined for a type with no `value_type` (which simply45
/// makes the concepts below unsatisfied for it, never a hard error).46
template <class A>47
using element_t = typename std::remove_cvref_t<A>::value_type;49
/// ArrayLike<A>: the structural surface every linalg-capable container exposes — a stored50
/// @ref cheatah::ndarray::Element plus host-side shape metadata (`shape`, `strides`,51
/// `ndim`, `size`, `offset`). Defined à la `std::ranges` (by shape, not by inheritance) so52
/// a host `basic_ndarray` and a device array both model it. The member surface is checked53
/// FIRST, so a non-array type (e.g. `int`) fails here before `element_t` is ever consulted.54
template <class A>55
concept ArrayLike = requires(const std::remove_cvref_t<A>& a) {56
typename std::remove_cvref_t<A>::value_type;57
{ a.shape() } -> std::convertible_to<const std::vector<std::size_t>&>;58
{ a.strides() } -> std::convertible_to<const std::vector<std::ptrdiff_t>&>;59
{ a.ndim() } -> std::convertible_to<std::size_t>;60
{ a.size() } -> std::convertible_to<std::size_t>;61
{ a.offset() } -> std::convertible_to<std::size_t>;62
} && ndarray::Element<element_t<A>>;64
/// NumericArray<A>: an @ref ArrayLike whose element is a @ref cheatah::ndarray::Field65
/// (a real or complex number). This is the bound the arithmetic routines share; it is what66
/// unifies today's separate `NDArray` and `CNDArray` overloads into one constrained67
/// template (real vs complex becomes a compile-time branch, not a duplicated signature).68
template <class A>69
concept NumericArray = ArrayLike<A> && ndarray::Field<element_t<A>>;71
/// FloatArray<A>: a @ref NumericArray whose element's REAL BASE is floating point — the72
/// bound for routines that need division / √ (solve, inv, det, qr, svd, eig). Constraining73
/// the real base (not the element itself) admits complex containers too, so a Hermitian74
/// complex `eigh` is allowed while an integer array is cleanly rejected.75
template <class A>76
concept FloatArray = NumericArray<A> && ndarray::FloatingPoint<ndarray::real_base_t<element_t<A>>>;78
/// host_location: the location tag for a container whose element buffer lives in ordinary79
/// host (CPU) memory. cheatah defines only this tag; a device extension defines its own80
/// (e.g. a `device_location`) and specializes @ref location_of for its container type.81
struct host_location {};83
/// location_of<A>: the trait naming where an array-like's elements live. Left undefined for84
/// an unknown container (so @ref Located is simply false for it); specialized below for the85
/// host `basic_ndarray`, and specialized by an extension for its own device container. Being86
/// a trait rather than a member keeps `basic_ndarray` itself untouched by this axis.87
template <class A>88
struct location_of;90
/// @cond INTERNAL91
template <ndarray::Element T>92
struct location_of<ndarray::basic_ndarray<T>> {93
using type = host_location;94
};95
/// @endcond97
/// location_t<A>: shorthand for the location tag of `A` (its @ref location_of `::type`),98
/// with any reference/cv-qualification stripped from `A` first.99
template <class A>100
using location_t = typename location_of<std::remove_cvref_t<A>>::type;102
/// Located<A>: an @ref ArrayLike that also advertises a location (its @ref location_of is103
/// specialized). Gates the location-aware concepts below so an un-tagged type fails cleanly104
/// rather than hard-erroring on a missing `location_of` specialization.105
template <class A>106
concept Located = ArrayLike<A> && requires { typename location_t<A>; };108
/// HostArray<A>: a @ref Located container whose elements live in host memory. The host109
/// `basic_ndarray` (`NDArray`, `CNDArray`) models this; it is the domain of the CPU kernels.110
template <class A>111
concept HostArray = Located<A> && std::same_as<location_t<A>, host_location>;113
/// DeviceArray<A>: a @ref Located container whose elements do NOT live in host memory — i.e.114
/// on a GPU/accelerator. Defined structurally as "located and not host" so this public115
/// header can name the CONCEPT without naming any specific device type; an extension's116
/// container satisfies it automatically once it specializes @ref location_of.117
template <class A>118
concept DeviceArray = Located<A> && !std::same_as<location_t<A>, host_location>;120
/// SameLocation<A,B>: the compile-time firewall — two operands share a location. A host⊗device121
/// (or device⊗host) call fails to satisfy this, so it is rejected at compile time with a clean122
/// concept error instead of a runtime guard. Every binary/ternary routine carries this bound.123
template <class A, class B>124
concept SameLocation = Located<A> && Located<B> && std::same_as<location_t<A>, location_t<B>>;126
/// SameField<A,B>: two operands share a real base (real·real or complex·complex over the same127
/// floating type). Mixing e.g. an `f64` and an `f32` container, or real with complex, is not128
/// silently promoted — it is a compile error, and promotion must be an explicit conversion.129
template <class A, class B>130
concept SameField = std::same_as<ndarray::real_base_t<element_t<A>>, ndarray::real_base_t<element_t<B>>>;132
} // namespace cheatah::linalg