Post

Learning Concepts in C++ Part 4: Fields

last edited on 2023-09-28

link to repository https://github.com/jgsuw/math_concepts_cxx

On the path to \(\text{SE}(3)\)

Roadmap

As part of a project to learn how to use Concepts and Templates in C++, I set out to implement a template library for \(\text{SE}(3)\). In my previous post, I introduced the Group concept and implemented template class RealAddition<T> that satisfied the concept AbelianGroup. While the group \(\text{SE}(3)\) as a whole is not Abelian, it turns out that we still need the Abelian group concept to implement the Lie Algebra \(\text{se}(3)\) of \(\text{SE}(3)\). This is because the Lie Algebra of a Lie Group carries a vector space structure, which in turn is defined over a field, and fields have Abelian group structure under two operations (addition and multiplication).

Axioms that Define a Field

Fields in mathematics are structures which are a generalization of certain number sets and the commonly used algebraic operations on those sets. For instance, the Rational Numbers \(\mathbb{Q}\) and the Real Numbers \(\mathbb{R}\) are a field under ordinary addition and multiplication. Other examples of fields exist, such as the field of rational polynomial functions. A triple \((X, +, \cdot)\) is a field if it satsifies:

  • \((X,+)\) is an Abelian group
    • call the identity element of this group \(0\)
  • \((X\backslash 0,\cdot)\) is an Abelian group (\(0\) is excluded because it has no inverse)
    • call the identity element of this group \(1\)
  • Multiplication distributes through addition,
    • \[\forall x,y,z \in X: x \cdot (y + z) = x \cdot y + x \cdot z\]

Field Concept Implementation

Fortunately, much of the heavy lifting in making a Field concept has already been done for us when I wrote the AbelianGroup concept in the last post. However, as we have seen before, one of the axioms we must satisfy quantifies over the set \(X\), and so once again we have a property which cannot be checked by the compiler. As before, I will add a requirement that the multiplication operation declares it holds the distributive property with respect to a specific addition operation.

template <class F>
concept Field = 
        AbelianGroup<typename F::Set, typename F::Add> &&
        AbelianGroup<typename F::Set, typename F::Mul> &&
requires(F::Set::type& x, F::Set::type& y, F::Set::type& z) {
    F::Mul::distributes_through<F::Add>(x,y,z);
};

From the concept definition we have that class F must have an identifier Set which forms an Abelian group with respect to the identifiers Add and Mul. Finally, we require that Mul has a static method that acts on instances of Add. This is the trick we play to have Mul profess its distributivity to the concept Field.

Bringing this together, I provide a template class for the field of real numbers. Since we already have a RealNumbers template, then all we need to define the operations \((+,\cdot)\), and bundle them into a template class. For addition I will use the RealAddition template from the previous post. Multiplication is implemented similarly, but includes

template <typename T>
class RealMultiplication {
public:
    using Domain = CartesianProduct<RealNumbers<T>,RealNumbers<T>>;
    using Target = RealNumbers<T>;

    static inline T identity() { return RealNumbers<T>::one; }
    static void has_identity(const T& x) { 
        static_assert(return apply(identity(), x) == x);
    }

    static inline T inverse (const T& x) {
        if (x == RealNumbers<T>::zero) {
            throw std::domain_error("Zero has no multiplicative inverse");
        }
        return identity/x;
    }
    static void has_inverse(const T& x) { 
        static_assert(apply(inverse(x),x) == identity());
    }

    static inline T apply(const T& x, const T& y) { return x*y; }
    static inline T apply(const Domain::type& x) { return x.first*x.second; }

    static void is_commutative(const T& x, const T& y) {
        static_assert(apply(x,y) == apply(y,x));
    }
    static void is_associative(const T& x, const T& y, const T& z) { 
        static_assert(apply(x,apply(y,z)) == apply(apply(x,y),z));
    }

    template <class Op>
    static void distributes_through(const T& x, const T& y, const T& z) {
        static_assert(apply(z,Op::apply(x,y)) == Op::apply(apply(z,x), apply(z,y)));
    }
};

Our final implementation is the template class RealNumberField, which we test by invoking the template function is_field

template <class F>
bool is_field() {
    return Field<F>;
}

template <typename T>
class RealNumberField {
public:
    using Set = RealNumbers<T>;
    using Add = RealAddition<T>;
    using Mul = RealMultiplication<T>;
};

using F = RealNumberField<float>;
int main(void) {
  if (is_Field<F>()) return 0;
  return -1
}

One of the nice things I encountered up to this point is that by thoughtfully employing templates and concepts it is possible to create expressive and parsimonious abstractions.

This post is licensed under CC BY 4.0 by the author.