Concepts — Algebraic Concepts

Overview

The sangi::concepts namespace defines the major structures of abstract algebra as C++20 concepts. Groups, rings, fields, vector spaces, Hilbert spaces, and more can be verified at compile time, enabling explicit type constraints on template arguments.

  • Compile-time type verification — clear error messages when template arguments do not satisfy algebraic requirements
  • Hierarchical design — AdditiveMonoid → AdditiveGroup → Ring → Field mirrors the mathematical inclusion hierarchy
  • Over 40 concepts — covering basic arithmetic, algebraic structures, vector spaces, linear maps, numeric types, matrices/tensors, and algebras/modules
  • Header-only — no linking required; just #include
  • C++23 extensions — additional concepts like SparseMatrixOf, BandMatrixOf, RealField, ComplexField (C++23 mode)

Mathematical axioms (associativity, commutativity, distributivity, etc.) cannot be verified at compile time. Only syntactic requirements (operator existence, return type compatibility) are checked. Semantic correctness is the user's responsibility.

Concept Hierarchy

sangi's algebraic concepts mirror the mathematical inclusion hierarchy. The diagram below shows the inclusion relationships between the major structures (an arrow A → B means "A is a super-structure of B", i.e. B satisfies A).

HasBasicArithmetic AdditiveMonoid AdditiveGroup AdditiveAbelian Group MultiplicativeMonoid MultiplicativeGroup Ring CommutativeRing IntegralDomain Field OrderedField DivisionRing (e.g. Quaternion) VectorSpace NormedVectorSpace BanachSpace InnerProductSpace HilbertSpace Module Algebra

Algebraic structure of major types

TypeRingIntegralDomainFieldOrderedFieldNotes
int / long longIntegral domain but not a field (no multiplicative inverse)
double / floatOrdered field
sangi::FloatArbitrary-precision floating point
sangi::Complex<double>No total order
sangi::Rational<T>Rational field
sangi::Quaternion<double>Division ring (non-commutative; satisfies DivisionRing)
sangi::Polynomial<double> (default)Integral domain; not a field in general
sangi::Matrix<double>No operator/
unsigned intFails AdditiveGroup (additive inverse not closed)

Basic Arithmetic Concepts

ConceptRequirementsExamples
HasBasicArithmetic<T> +, -, *, /, unary - defined double, float, Int, Float, Rational, Complex<double>
Comparable<T> ==, !=, <, <=, >, >= defined double, int, Int, Float, Rational
EqualityComparable<T> ==, != only Complex<double>, Quaternion<double>, all numeric types
HasMathConstants<T> T::pi(), T::e() defined Float. float/double do not have static methods pi()/e() and therefore do not satisfy this concept. Intended for custom types such as sangi::Float

HasBasicArithmetic<T> conformance table

TypeFitsNotes
int / double / long doubleBuilt-in numeric types
sangi::FloatArbitrary-precision floating point
sangi::Complex<T>Complex numbers
sangi::Rational<T>Rationals
sangi::Quaternion<T>Quaternions (non-commutative, but the five operations are closed)
sangi::Polynomial<T>✓ (conditional)Default build satisfies it (returns the polynomial-division quotient). When SANGI_POLY_DIV_RATIONAL is defined, operator/ returns RationalFunction<T> and the concept is no longer satisfied. See note below
sangi::Matrix<T>No operator/
sangi::Vector<T>No operator/, no operator*
std::stringNo -, *, /, unary -
⚠ Note (Polynomial operator/ switch)

The behaviour of sangi::Polynomial<T>::operator/ depends on a build-time macro:

  • Default: returns the polynomial-division quotient (Polynomial<T>). Satisfies HasBasicArithmetic.
  • With SANGI_POLY_DIV_RATIONAL defined: returns a rational function (RationalFunction<T>). HasBasicArithmetic<Polynomial<T>> is no longer satisfied.

Mathematically, Polynomial<T> is an integral domain (IntegralDomain) and is not a field in general, so Field<Polynomial<T>> is never satisfied regardless of the build setting.

Algebraic Structures

The following inclusion relationships hold:

$$\text{AdditiveMonoid} \subset \text{AdditiveGroup} \subset \text{AdditiveAbelianGroup}$$ $$\text{Ring} = \text{AdditiveAbelianGroup} \cap \text{MultiplicativeMonoid}$$ $$\text{Ring} \subset \text{CommutativeRing} \subset \text{IntegralDomain} \subset \text{Field} \subset \text{OrderedField}$$
ConceptRequirementsExamples
AdditiveMonoid<T> a + b, T{0} (additive identity) int, float, double, unsigned int, Int, Float
AdditiveGroup<T> AdditiveMonoid + unary -a, a - b int, float, double, Int, Float
AdditiveAbelianGroup<T> AdditiveGroup (commutativity is a semantic marker) int, float, double, Int, Float
MultiplicativeMonoid<T> a * b, T{1} (multiplicative identity) int, float, double, Int, Float
MultiplicativeGroup<T> MultiplicativeMonoid + T{1} / a (multiplicative inverse) float, double, Float, Rational, Complex<double>
MultiplicativeAbelianGroup<T> MultiplicativeGroup (commutativity is a semantic marker) float, double, Float, Rational
Ring<T> AdditiveAbelianGroup + MultiplicativeMonoid int, float, double, Int, Float, Rational
CommutativeRing<T> Ring (multiplicative commutativity is a semantic marker) int, float, double, Int, Float, Rational
IntegralDomain<T> CommutativeRing (no zero divisors is a semantic marker) int, float, double, Int, Float, Rational
DivisionRing<T> Ring + MultiplicativeGroup (includes non-commutative division rings) float, double, Quaternion<double>, Complex<double>
Field<T> +, -, *, /, unary -, T(0), T(1) double, float, Float, Rational, Complex<double>
OrderedField<T> Field + <, <=, >, >= double, float, Float, Rational
Scalar<T> Field<T> || std::integral<T> int, double, Float, Rational

Note on unsigned types: Unsigned integer types such as unsigned int satisfy AdditiveMonoid, but the additive inverse (-a) is not mathematically closed for unsigned types. Therefore, they do not satisfy AdditiveGroup or any higher algebraic structure (Ring, Field, etc.).

About "semantic markers": Mathematical properties such as commutativity, completeness, and the absence of zero divisors cannot be verified at compile time. These concepts function as declarations of intent (markers). No runtime checks are performed; it is the user's responsibility to ensure the type satisfies the stated property.

Ring<T> conformance table

TypeFitsNotes
int / long longRing of integers $\mathbb{Z}$
double / sangi::FloatEvery field is a ring
sangi::Rational<T>Rationals
sangi::Complex<T>Complex numbers
sangi::Polynomial<T> (default)Polynomial ring
sangi::Quaternion<T>Non-commutative ring (does not satisfy CommutativeRing)
unsigned intAdditive inverse not closed (fails AdditiveGroup)
sangi::Matrix<T>No numeric_traits specialization (shapes generally differ)

Field<T> conformance table

TypeFitsNotes
double / float / long doubleFinite-precision field
sangi::FloatArbitrary-precision floating point
sangi::Rational<T>Rational field $\mathbb{Q}$
sangi::Complex<double>Complex field $\mathbb{C}$
int / long longNo multiplicative inverse (integral domain)
sangi::Quaternion<T>Non-commutative (does satisfy DivisionRing)
sangi::Polynomial<T>Integral domain, not a field (see note below)
sangi::Matrix<T>No operator/
⚠ Note (Polynomial and Field)

sangi::Polynomial<T> is not a field even when the coefficient type T is — the polynomial ring T[x] is an integral domain and lacks multiplicative inverses in general. Therefore Field<Polynomial<T>> is never satisfied, regardless of the build setting.

The corresponding field of fractions is sangi::RationalFunction<T>, into which Polynomial::operator/ is promoted when SANGI_POLY_DIV_RATIONAL is defined. If you need field-level operations (e.g. safe_inverse) on polynomial-like objects, use RationalFunction<T> directly.

DivisionRing<T> conformance table

TypeFitsNotes
double / float / sangi::FloatEvery field is a division ring
sangi::Complex<T>Complex field
sangi::Rational<T>Rational field
sangi::Quaternion<T>Non-commutative division ring (does not satisfy Field, but satisfies DivisionRing)
intNo multiplicative inverse
sangi::Polynomial<T>Integral domain but not a division ring

OrderedField<T> conformance table

TypeFitsNotes
double / float / long doubleFinite-precision approximation of the reals
sangi::FloatArbitrary-precision reals
sangi::Rational<T>Rational field is ordered
sangi::Complex<T>No natural total order on the complex numbers
sangi::Quaternion<T>Non-commutative and unordered

Vector Spaces

Vector spaces and their extensions. The scalar type S defaults to double.

Template parameters

ParameterTypeDescription
VtypenameVector type
StypenameScalar type (defaults to double)
ConceptRequirementsExamples
VectorSpace<V, S> v + w, v - w, s * v, v * s, -v Vector<double>, std::array<double, N>
InnerProductSpace<V, S> VectorSpace + inner_product(v, w) → S Vector<double> (when inner_product is defined)
NormedVectorSpace<V, S> VectorSpace + norm(v) → S Vector<double> (when norm is defined)
BanachSpace<V, S> NormedVectorSpace (completeness is a semantic marker — completeness cannot be verified at compile time, so this concept serves as a declaration of intent. No runtime checks are performed) $\mathbb{R}^n$, $\ell^p$ spaces
HilbertSpace<V, S> InnerProductSpace (completeness is a semantic marker — completeness cannot be verified at compile time, so this concept serves as a declaration of intent. No runtime checks are performed) $\mathbb{R}^n$, $\ell^2$ spaces
FiniteDimensionalVectorSpace<V, S> VectorSpace + v.size() → size_t Vector<double>

VectorSpace<V, S> conformance table

(V, S)FitsNotes
(sangi::Vector<double>, double)Standard vector space
(sangi::Vector<Complex<double>>, Complex<double>)Complex vector space
(std::array<double, N>, double)Fixed-length vector
(sangi::Matrix<double>, double)Matrices also form a vector space
(double, double)One-dimensional vector space
(sangi::Vector<double>, int)Return-type mismatch for int * Vector<double>
(std::string, double)No scalar multiplication

Linear Maps

Template parameters

ParameterTypeDescription
FtypenameMap object type (function object)
V1typenameDomain vector space
V2typenameCodomain vector space
StypenameCommon scalar type (defaults to double)
ConceptRequirementsDescription
LinearMap<F, V1, V2, S> f(v) → V2, f(v + w) → V2, f(s * v) → V2 $f(\alpha v + \beta w) = \alpha f(v) + \beta f(w)$. V1, V2 must be VectorSpace
ConjugateLinearMap<F, V1, V2, S> Same syntactic requirements as LinearMap $f(\alpha v) = \bar{\alpha} f(v)$. Conjugate homogeneity cannot be verified at compile time

Numeric Type Concepts

ConceptRequirementsExamples
Numeric<T> HasBasicArithmetic + std::abs, std::max, std::min short, int, float, double, unsigned int
IntegerType<T> std::integral<T> && Ring<T>. Ring requires numeric_traits specialization int, long long. short does not satisfy this concept because it lacks a numeric_traits specialization. Unsigned types do not satisfy the AdditiveGroup requirement
NonNegativeIntegerType<T> std::unsigned_integral<T> unsigned int, size_t, uint64_t, etc. Use for array indices, sizes, and counters where non-negativity is guaranteed. Does not satisfy Ring or higher because additive inverse is not closed
FloatingPointType<T> OrderedField<T> && !std::integral<T> float, double, long double, sangi::Float
ComplexType<T> T::value_type, std::real, std::imag, std::abs, std::arg, std::conj std::complex<double>, std::complex<float>
QuaternionType<T> T::value_type, q.w, q.x, q.y, q.z, q.conj(), q.norm(), q.inverse() Quaternion<double>, Quaternion<float>
RationalType<T> Field<T> + r.numerator(), r.denominator() Rational

Matrix & Tensor Concepts

Template parameters

ParameterTypeDescription
MtypenameMatrix type
VtypenameVector type
T / StypenameElement type (scalar type)
ConceptRequirementsDescription
MatrixOf<M, T> M::value_type, m(i,j) → T, m.rows(), m.cols() Any matrix type. Defined in traits.hpp
VectorOf<V, T> V::value_type, v[i] → T, v.size() Any vector type. Defined in traits.hpp
SquareMatrixOf<M, T> MatrixOf + m.is_square() Square matrix
SymmetricMatrixOf<M, T> SquareMatrixOf (symmetry is a semantic marker) Symmetric matrix. Requires runtime verification
OrthogonalMatrixOf<M, T> SquareMatrixOf (orthogonality is a semantic marker) Orthogonal matrix ($M^T M = I$)
TensorOf<T, S> t.rank(), t.shape(i), t.size(), t(i,j) → S Multi-dimensional tensor
SparseMatrixOf<M, T> C++23 MatrixOf + m.non_zeros() Sparse matrix
BandMatrixOf<M, T> C++23 MatrixOf + m.lower_bandwidth(), m.upper_bandwidth() Band matrix

MatrixOf<M, T> conformance table

(M, T)FitsNotes
(sangi::Matrix<double>, double)Dense matrix
(sangi::Matrix<Complex<double>>, Complex<double>)Complex dense matrix
(sangi::SparseMatrix<double>, double)Sparse matrix; also satisfies SparseMatrixOf
(sangi::Matrix<double>, float)value_type mismatch
(sangi::Vector<double>, double)No operator(i,j)
(std::vector<std::vector<double>>, double)No rows()/cols()/value_type

Algebras & Modules

Template parameters

ParameterTypeDescription
MtypenameModule type
RtypenameCoefficient ring type
A / LtypenameVector space underlying the algebra / Lie algebra
FtypenameCoefficient field type
ConceptRequirementsDescription
Module<M, R> AdditiveAbelianGroup<M> + Ring<R> + r * m, m * r Module (generalization of vector space where scalars form a ring, not a field)
Algebra<A, F> VectorSpace<A,F> + Ring<A> + f * (a * b) Algebra (vector space with multiplicative structure)
AssociativeAlgebra<A, F> Algebra (associativity is a semantic marker) Associative algebra, e.g. matrix rings
LieAlgebra<L, F> VectorSpace<L,F> + a * b → L (Lie bracket) Lie algebra, e.g. $\mathfrak{so}(3)$, $\mathfrak{se}(3)$

Examples

Generic function constrained by Field

#include <math/concepts/algebraic_concepts.hpp>
using namespace sangi::concepts;

// Inverse computation for any type satisfying Field
template<Field T>
T safe_inverse(const T& x) {
    if (x == T(0)) return T(0);  // avoid division by zero
    return T(1) / x;
}

// Works with double, Float, Rational, Complex<double>
auto inv_d = safe_inverse(3.14);
auto inv_r = safe_inverse(Rational(3, 7));
// Run output:
//   inv_d = 0.318471337579618
//   inv_r = 7/3
//   safe_inverse(0.0) = 0     (zero guard fires)

Vector space algorithm

// Linear combination for any type satisfying VectorSpace
template<typename V, typename S>
    requires VectorSpace<V, S>
V linear_combination(S a, const V& v, S b, const V& w) {
    return a * v + b * w;
}
// Run output:
//   v = {1,2,3}, w = {4,5,6}, a = 2.0, b = 3.0
//   linear_combination = {14, 19, 24}

Polynomial evaluation over a Ring

// Horner's method for any type satisfying Ring
template<Ring T>
T horner(const std::vector<T>& coeffs, const T& x) {
    T result = T{0};
    for (auto it = coeffs.rbegin(); it != coeffs.rend(); ++it) {
        result = result * x + *it;
    }
    return result;
}

// Works with int, double, Int, Float, Rational
std::vector<double> poly = {1.0, -2.0, 3.0}; // 3x^2 - 2x + 1
double val = horner(poly, 2.0); // = 9.0
// Run output: val = 9   (= 3·4 − 2·2 + 1)

Quaternion type detection

// Combining QuaternionType and DivisionRing constraints
template<typename T>
    requires QuaternionType<T> && DivisionRing<T>
auto rotation_axis(const T& q) {
    auto v_norm = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z);
    return std::make_tuple(q.x / v_norm, q.y / v_norm, q.z / v_norm);
}
// Run output:
//   Quaternion(0, 1, 2, 2) → axis = (1/3, 2/3, 2/3),  |axis| = 1

Concept-based overloading

// OrderedField: field with ordering (double, Float, Rational)
template<OrderedField T>
T clamp(const T& x, const T& lo, const T& hi) {
    if (x < lo) return lo;
    if (x > hi) return hi;
    return x;
}

// Field: includes non-orderable fields (Complex<double>)
template<Field T>
T midpoint(const T& a, const T& b) {
    return (a + b) / T(2);
}
// Run output:
//   clamp(2.5, 0, 1)         = 1
//   clamp(-0.3, 0, 1)        = 0
//   midpoint(1.0, 5.0)       = 3
//   midpoint(1+0i, 3+4i)     = 2+2i

Concept membership can be verified with static_assert sanity checks.