cheatah
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 once
5/**
6 * @file concepts.hpp
7 * @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 header
11 * introduces the STL-`std::ranges`-style concept surface that lets a single algorithm
12 * template serve **any** conforming container and element type, and adds an orthogonal
13 * **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.hpp
16 * (`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, yielding
20 * @ref HostArray / @ref DeviceArray / @ref SameLocation — so that mixing a host and a
21 * 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 tag
25 * and specializes @ref location_of for its own container — so the concept vocabulary is
26 * shared without this public header ever naming the (private) extension. Element data is
27 * reached only through location-specific customization points (see backend.hpp), so the
28 * 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"
40namespace cheatah::linalg {
42/// element_t<A>: the scalar an array-like stores — its nested `value_type`, with any
43/// 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 simply
45/// makes the concepts below unsatisfied for it, never a hard error).
46template <class A>
47using element_t = typename std::remove_cvref_t<A>::value_type;
49/// ArrayLike<A>: the structural surface every linalg-capable container exposes — a stored
50/// @ref cheatah::ndarray::Element plus host-side shape metadata (`shape`, `strides`,
51/// `ndim`, `size`, `offset`). Defined à la `std::ranges` (by shape, not by inheritance) so
52/// a host `basic_ndarray` and a device array both model it. The member surface is checked
53/// FIRST, so a non-array type (e.g. `int`) fails here before `element_t` is ever consulted.
54template <class A>
55concept 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::Field
65/// (a real or complex number). This is the bound the arithmetic routines share; it is what
66/// unifies today's separate `NDArray` and `CNDArray` overloads into one constrained
67/// template (real vs complex becomes a compile-time branch, not a duplicated signature).
68template <class A>
69concept NumericArray = ArrayLike<A> && ndarray::Field<element_t<A>>;
71/// FloatArray<A>: a @ref NumericArray whose element's REAL BASE is floating point — the
72/// bound for routines that need division / √ (solve, inv, det, qr, svd, eig). Constraining
73/// the real base (not the element itself) admits complex containers too, so a Hermitian
74/// complex `eigh` is allowed while an integer array is cleanly rejected.
75template <class A>
76concept 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 ordinary
79/// host (CPU) memory. cheatah defines only this tag; a device extension defines its own
80/// (e.g. a `device_location`) and specializes @ref location_of for its container type.
81struct host_location {};
83/// location_of<A>: the trait naming where an array-like's elements live. Left undefined for
84/// an unknown container (so @ref Located is simply false for it); specialized below for the
85/// host `basic_ndarray`, and specialized by an extension for its own device container. Being
86/// a trait rather than a member keeps `basic_ndarray` itself untouched by this axis.
87template <class A>
88struct location_of;
90/// @cond INTERNAL
91template <ndarray::Element T>
92struct location_of<ndarray::basic_ndarray<T>> {
93 using type = host_location;
94};
95/// @endcond
97/// 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.
99template <class A>
100using 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 is
103/// specialized). Gates the location-aware concepts below so an un-tagged type fails cleanly
104/// rather than hard-erroring on a missing `location_of` specialization.
105template <class A>
106concept Located = ArrayLike<A> && requires { typename location_t<A>; };
108/// HostArray<A>: a @ref Located container whose elements live in host memory. The host
109/// `basic_ndarray` (`NDArray`, `CNDArray`) models this; it is the domain of the CPU kernels.
110template <class A>
111concept 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 public
115/// header can name the CONCEPT without naming any specific device type; an extension's
116/// container satisfies it automatically once it specializes @ref location_of.
117template <class A>
118concept 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⊗device
121/// (or device⊗host) call fails to satisfy this, so it is rejected at compile time with a clean
122/// concept error instead of a runtime guard. Every binary/ternary routine carries this bound.
123template <class A, class B>
124concept 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 same
127/// floating type). Mixing e.g. an `f64` and an `f32` container, or real with complex, is not
128/// silently promoted — it is a compile error, and promotion must be an explicit conversion.
129template <class A, class B>
130concept SameField = std::same_as<ndarray::real_base_t<element_t<A>>, ndarray::real_base_t<element_t<B>>>;
132} // namespace cheatah::linalg