C(++)ECCO
C++ Error Control COding: a header-only library for ECC simulations and experiments, modeling complete coding systems across arbitrary finite fields and complex inter-field relationships - Christian Senger <senger@inue.uni-stuttgart.de>
Loading...
Searching...
No Matches
fields.hpp
Go to the documentation of this file.
1/**
2 * @file fields.hpp
3 * @brief Finite field arithmetic library
4 * @author Christian Senger <senger@inue.uni-stuttgart.de>
5 * @version 2.3.17
6 * @date 2026
7 *
8 * @copyright
9 * Copyright (c) 2026, Christian Senger <senger@inue.uni-stuttgart.de>
10 *
11 * Licensed for noncommercial use only, including academic teaching, research, and personal non-profit purposes.
12 * Commercial use is prohibited without a separate commercial license. See the [LICENSE](../../LICENSE) file in the
13 * repository root for full terms and how to request a commercial license.
14 *
15 *
16 * @section Description
17 *
18 * Finite-field arithmetic. The library provides:
19 *
20 * - prime fields 𝔽_p β‰… β„€/pβ„€ via @ref CECCO::Fp;
21 * - extension fields 𝔽_{q^m} β‰… B[x]/(f(x)) over a base field B and an irreducible monic modulus
22 * f(x), via @ref CECCO::Ext, with selectable LUT generation mode (runtime or compile-time);
23 * - merged field towers via @ref CECCO::Iso, which exposes one logical field from several
24 * isomorphic representations and connects otherwise-disjoint construction towers;
25 * - cross-field constructors that pick the bridge field with @ref CECCO::details::largest_common_subfield_t;
26 * - rational numbers β„š with selectable precision via @ref CECCO::Rationals.
27 *
28 * A *field tower* in this library is a sequence of constructed extensions. Mathematical
29 * intermediate fields that were never constructed are not part of the tower. Two towers ending
30 * at isomorphic fields can be glued together by wrapping the two endpoints in an `Iso`.
31 *
32 * @section Usage_Example
33 *
34 * @code{.cpp}
35 * using F2 = Fp<2>;
36 * using F3 = Fp<3>;
37 * using F4 = Ext<F2, {1, 1, 1}, LutMode::CompileTime>; // 𝔽₂[x]/(xΒ² + x + 1)
38 * using F9 = Ext<F3, {2, 2, 1}, LutMode::CompileTime>;
39 * using F16_a = Ext<F2, {1, 0, 0, 1, 1}>; // RunTime LUTs (default)
40 * using F16_b = Ext<F4, {2, 1, 1}>;
41 * using F16 = Iso<F16_a, F16_b>; // merge the two F16 towers
42 * using F256_a = Ext<F2, {1, 1, 0, 1, 0, 0, 0, 1, 1}>;
43 * using F256_b = Ext<F4, {2, 2, 2, 0, 1}>;
44 * using F256_c = Ext<F16, {6, 13, 1}>;
45 * using F256 = Iso<F256_a, F256_b, F256_c>;
46 *
47 * F9 a(5), b(7);
48 * auto c = a * b + F9(1); // arithmetic
49 * size_t ord = a.get_multiplicative_order();
50 * Vector<F3> v = a.as_vector<F3>(); // coordinate vector over F3
51 *
52 * F16 d(1);
53 * F256 e(d); // upcast: F16 βŠ† F256
54 * @endcode
55 *
56 * @section Performance_Features
57 *
58 * - LUT modes per @ref CECCO::Ext instantiation: `LutMode::RunTime` (default; faster
59 * compilation, lazy first-access initialisation) or `LutMode::CompileTime` (zero startup,
60 * tables baked into the binary). Mix freely within a tower.
61 * - For extension fields with q β‰₯ @ref CECCO_COMPRESS_LUTS_FROM_Q, the addition and
62 * multiplication tables are stored compressed (upper triangle only), saving ~50 % memory.
63 * - Pure CRTP, no virtual dispatch; concepts (@ref CECCO::FieldType, @ref CECCO::FiniteFieldType)
64 * enforce the interface at compile time.
65 *
66 * @note CompileTime mode can exceed compiler recursion / step limits for larger fields. If
67 * needed, raise with `-fconstexpr-depth=4294967295 -fconstexpr-steps=4294967295` (clang) or
68 * `-fconstexpr-ops-limit=4294967295` (g++). Rule of thumb: CompileTime up to ~150 elements,
69 * RunTime above.
70 *
71 * @section Irreducible_Polynomial_Construction
72 *
73 * @ref CECCO::Ext requires the modulus to be a monic irreducible polynomial of degree β‰₯ 2,
74 * coefficients given low-to-high (constant term first). To find one inside the library:
75 *
76 * @code{.cpp}
77 * using B = Fp<3>;
78 * auto p = find_irreducible<B>(4); // monic, degree 4, irreducible over B
79 * auto v = Vector(p); // vector form, ready to paste as modulus into Ext<B, …>
80 * @endcode
81 *
82 * Externally (Magma):
83 * @code
84 * p := 2; m := 6; F := GaloisField(p);
85 * P<x> := PolynomialRing(F);
86 * px := IrreduciblePolynomial(F, m);
87 * Reverse(Coefficients(px));
88 * @endcode
89 *
90 * @see @ref field_concepts_traits.hpp for the concepts and traits (FieldType, SubfieldOf, …)
91 * @see @ref vectors.hpp, @ref matrices.hpp, @ref polynomials.hpp
92 */
93
94#ifndef FIELDS_HPP
95#define FIELDS_HPP
96
97/**
98 * @def CECCO_ERASURE_SUPPORT
99 * @brief Enables `erase()` / `unerase()` / `is_erased()` on every field type
100 *
101 * Define only if needed (error-correction with erasures); enabling it costs some performance.
102 */
103#ifdef DOXYGEN
104#define CECCO_ERASURE_SUPPORT
105#endif
106
107/**
108 * @def CECCO_USE_LUTS_FOR_FP
109 * @brief Force prime fields to use lookup tables instead of modular arithmetic
110 *
111 * Almost always slower than direct mod-p arithmetic. Leave undefined unless you have a reason.
112 */
113#ifdef DOXYGEN
114#define CECCO_USE_LUTS_FOR_FP
115#endif
116
117/**
118 * @def CECCO_COMPRESS_LUTS_FROM_Q
119 * @brief Compression threshold for 2D lookup tables in extension fields
120 *
121 * For fields with at least this many elements, the addition and multiplication tables store
122 * only the upper triangle (exploiting commutativity), saving ~50 % memory. Optimal value
123 * depends on cache size.
124 */
125#ifndef CECCO_COMPRESS_LUTS_FROM_Q
126#define CECCO_COMPRESS_LUTS_FROM_Q 256
127#endif
128
129#include <span>
130
131#include "polynomials.hpp"
132/*
133//transitive
134#include <algorithm>
135#include <array>
136#include <cstdint>
137#include <iomanip>
138#include <iostream>
139#include <iterator>
140#include <limits>
141#include <map>
142#include <numeric>
143#include <optional>
144#include <random>
145#include <ranges>
146#include <sstream>
147#include <stdexcept>
148#include <string>
149#include <string_view>
150#include <tuple>
151#include <type_traits>
152#include <utility>
153#include <vector>
154
155#include "InfInt.hpp"
156#include "field_concepts_traits.hpp"
157#include "helpers.hpp"
158#include "matrices.hpp"
159*/
160
161namespace CECCO {
162
163template <ComponentType T>
164class Vector;
165template <CoefficientType T>
166class Polynomial;
167template <ComponentType T>
168class Matrix;
169template <FiniteFieldType MAIN, FiniteFieldType... OTHERS>
170class Iso;
171
172namespace details {
173/**
174 * @brief Tag base for the CRTP-protection idiom
175 *
176 * Inherited by every CECCO field type so that the templated @ref CECCO::operator+ and friends
177 * (which dispatch on @ref CECCO::FieldType) cannot accidentally match unrelated operands like
178 * `int + int`. The protected constructor blocks direct instantiation.
179 */
180class Base {
181 protected:
182 Base() = default;
183};
184
185/**
186 * @brief CRTP base documenting the interface every field type must provide
187 *
188 * @tparam T Derived field type (CRTP parameter)
189 *
190 * Most members are `= delete`, present solely to advertise the interface. Derived types
191 * shadow them with their own definitions. The free arithmetic operators in this file template
192 * on @ref CECCO::FieldType and dispatch directly to `T`'s compound assignments; there is no
193 * virtual dispatch.
194 *
195 * Derived types must supply: assignment from `T`, `T&&`, and `int`; `operator==`; unary `-`;
196 * the four compound assignments `+=`, `βˆ’=`, `*=`, `/=`; `randomize()` /
197 * `randomize_force_change()`; the property queries `is_zero()`, `has_positive_sign()`,
198 * `get_multiplicative_order()`, `get_additive_order()`. When @ref CECCO_ERASURE_SUPPORT is
199 * defined, also `erase()` / `unerase()` / `is_erased()`.
200 *
201 * Provided here (not deleted): `operator!=` (delegates to `==`) and the two unary `operator+`
202 * overloads (identity).
203 */
204template <class T>
205class Field : public details::Base {
206 protected:
207 ~Field() noexcept = default;
208
209 public:
210 /// @name Assignment Operators
211 /// @{
212
213 /// @brief Assign from `int` (derived must implement)
214 T& operator=(int l) = delete;
215 /// @brief Copy assignment (derived must implement)
216 T& operator=(const T& rhs) noexcept = delete;
217 /// @brief Move assignment (derived must implement)
218 T& operator=(T&& rhs) noexcept = delete;
219
220 /// @}
221 /// @name Comparison
222 /// @{
223
224 /// @brief Inequality, defined as `!(*this == rhs)`
225 constexpr bool operator!=(const T& rhs) const { return !(static_cast<const T&>(*this) == rhs); }
226
227 /// @}
228 /// @name Unary Operations
229 /// @{
230
231 /// @brief Unary `+` on an lvalue: returns a copy
232 constexpr T operator+() const& { return static_cast<const T&>(*this); }
233 /// @brief Unary `+` on an rvalue: returns the rvalue itself
234 constexpr T&& operator+() && noexcept { return static_cast<T&&>(*this); }
235
236 /// @brief Additive inverse on an lvalue (derived must implement)
237 T operator-() const& noexcept = delete;
238 /// @brief In-place additive inverse on an rvalue (derived must implement)
239 T& operator-() && noexcept = delete;
240
241 /// @}
242 /// @name Compound Assignment
243 /// @{
244
245 /// @brief `*this += rhs` (derived must implement)
246 T& operator+=(const T& rhs) noexcept = delete;
247 /// @brief `*this -= rhs` (derived must implement)
248 T& operator-=(const T& rhs) noexcept = delete;
249 /// @brief `*this *= rhs` (derived must implement)
250 T& operator*=(const T& rhs) noexcept = delete;
251 /// @brief `*this /= rhs` (derived must implement), throws `std::invalid_argument` if rhs is zero
252 T& operator/=(const T& rhs) = delete;
253
254 /// @}
255 /// @name Randomization
256 /// @{
257
258 /// @brief Uniform random element of the field (derived must implement), may return the same value
259 Field& randomize() = delete;
260 /// @brief Like @ref randomize but guaranteed to differ from the current value (derived must implement)
262
263 /// @}
264 /// @name Properties
265 /// @{
266
267 /// @brief Smallest k > 0 with `this^k == 1`; throws `std::invalid_argument` if `*this` is zero
269 /// @brief Smallest k > 0 with `k * *this == 0`; for finite fields of characteristic p this is p (or 1 for zero);
270 /// for β„š it is 1 for zero and 0 (infinite) otherwise
271 size_t get_additive_order() const = delete;
272 /// @brief True if the element is "positive" (always true for finite fields; sign of numerator for β„š)
273 bool has_positive_sign() const noexcept = delete;
274 /// @brief True if `*this` is the additive identity
275 bool is_zero() const noexcept = delete;
276
277#ifdef CECCO_ERASURE_SUPPORT
278 /// @brief Mark this element as erased (out-of-field marker for erasure decoding)
279 /// @warning Erased elements must not participate in field arithmetic; correct use is the caller's responsibility
280 Field& erase() noexcept = delete;
281 /// @brief Clear the erasure flag, resetting to additive identity
282 Field& unerase() noexcept = delete;
283 /// @brief Test whether this element is currently erased
284 bool is_erased() const noexcept = delete;
285#endif
286 /// @}
287};
288
289} // namespace details
290
291/**
292 * @name Field Arithmetic Operators
293 * @brief Free CRTP operators (`+`, `βˆ’`, `*`, `/`, `^`) for any @ref CECCO::FieldType
294 *
295 * Each operator constructs a result by calling `T`'s compound assignment, with rvalue overloads
296 * that move from a temporary instead of copying. Scalar multiplication takes the integer on
297 * either side. Division throws `std::invalid_argument` if the right operand is zero.
298 * `operator^` exponentiates by square-and-multiply via @ref CECCO::sqm.
299 *
300 * @warning `operator^` does **not** follow C++ precedence for `^`: in `b * a ^ p` the parser
301 * evaluates `(b * a) ^ p`. Parenthesise as `b * (a ^ p)`, or call @ref CECCO::sqm directly.
302 * @{
303 */
304
305template <FieldType T>
306constexpr T operator+(const T& lhs, const T& rhs) {
307 T res(lhs);
308 res += rhs;
309 return res;
310}
311
312template <FieldType T>
313constexpr T operator+(T&& lhs, const T& rhs) {
314 T res(std::move(lhs));
315 res += rhs;
316 return res;
317}
318
319template <FieldType T>
320constexpr T operator+(const T& lhs, T&& rhs) {
321 T res(std::move(rhs));
322 res += lhs;
323 return res;
324}
325
326template <FieldType T>
327constexpr T operator+(T&& lhs, T&& rhs) {
328 T res(std::move(lhs));
329 res += rhs;
330 return res;
331}
332
333template <FieldType T>
334constexpr T operator-(const T& lhs, const T& rhs) {
335 T res(lhs);
336 res -= rhs;
337 return res;
338}
339
340template <FieldType T>
341constexpr T operator-(T&& lhs, const T& rhs) {
342 T res(std::move(lhs));
343 res -= rhs;
344 return res;
345}
346
347template <FieldType T>
348constexpr T operator-(const T& lhs, T&& rhs) {
349 T res(-std::move(rhs));
350 res += lhs;
351 return res;
352}
353
354template <FieldType T>
355constexpr T operator-(T&& lhs, T&& rhs) {
356 T res(std::move(lhs));
357 res -= rhs;
358 return res;
359}
360
361template <FieldType T>
362constexpr T operator*(const T& lhs, const T& rhs) {
363 T res(lhs);
364 res *= rhs;
365 return res;
366}
367
368template <FieldType T>
369constexpr T operator*(T&& lhs, const T& rhs) {
370 T res(std::move(lhs));
371 res *= rhs;
372 return res;
373}
374
375template <FieldType T>
376constexpr T operator*(const T& lhs, T&& rhs) {
377 T res(std::move(rhs));
378 res *= lhs;
379 return res;
380}
381
382template <FieldType T>
383constexpr T operator*(T&& lhs, T&& rhs) {
384 T res(std::move(lhs));
385 res *= rhs;
386 return res;
387}
388
389template <FieldType T>
390constexpr T operator*(const T& lhs, int rhs) {
391 T res(lhs);
392 res *= rhs;
393 return res;
394}
395
396template <FieldType T>
397constexpr T operator*(int lhs, const T& rhs) {
398 T res(rhs);
399 res *= lhs;
400 return res;
401}
402
403template <FieldType T>
404constexpr T operator*(T&& lhs, int rhs) {
405 T res(std::move(lhs));
406 res *= rhs;
407 return res;
408}
409
410template <FieldType T>
411constexpr T operator*(int lhs, T&& rhs) {
412 T res(std::move(rhs));
413 res *= lhs;
414 return res;
415}
416
417template <FieldType T>
418T operator/(const T& lhs, const T& rhs) {
419 T res(lhs);
420 res /= rhs;
421 return res;
422}
423
424template <FieldType T>
425T operator/(T&& lhs, const T& rhs) {
426 T res(std::move(lhs));
427 res /= rhs;
428 return res;
429}
430
431template <FieldType T>
432constexpr T operator^(const T& base, int exponent) {
433 return sqm<T>(base, exponent);
434}
435
436template <FieldType T>
437constexpr T operator^(T&& base, int exponent) {
438 return sqm<T>(std::move(base), exponent);
439}
440
441/** @} */
442
443#ifdef CECCO_ERASURE_SUPPORT
444inline constexpr std::string_view ERASURE_MARKER = "X";
445#endif
446
447/**
448 * @brief Field of rational numbers β„š = { p/q : p, q ∈ β„€, q β‰  0 } with selectable precision
449 *
450 * @tparam T Numerator/denominator type satisfying @ref CECCO::SignedIntType (default `InfInt`)
451 *
452 * Characteristic 0. Values are kept in lowest terms with positive denominator at all times,
453 * so equality is `numerator_a * denominator_b == numerator_b * denominator_a`. Construction
454 * with a zero denominator throws `std::invalid_argument`.
455 *
456 * Pick `T = InfInt` for true β„š. A fixed-width `T` (e.g. `int`, `long long`) caps numerator
457 * and denominator and silently overflows past that range.
458 *
459 * @section Usage_Example
460 *
461 * @code{.cpp}
462 * Rationals<> a(3, 4);
463 * Rationals<> b(5, 6);
464 * auto c = a + b; // 19/12 (auto-simplified)
465 * auto d = a / b; // 9/10
466 * std::cout << c; // "19/12"
467 * @endcode
468 */
469template <SignedIntType T = InfInt> // use InfInt for infinite precision... and bad performance
470class Rationals : public details::Field<Rationals<T>> {
471 public:
472 /**
473 * @brief Construct n / d, simplified to lowest terms with positive denominator
474 *
475 * @param n Numerator (default 0)
476 * @param d Denominator (default 1)
477 * @throws std::invalid_argument if d == 0
478 */
479 Rationals(int n = 0, int d = 1);
480
481 Rationals(const Rationals& other) = default;
482 Rationals(Rationals&& other) = default;
483
484 /// @brief Assign the integer @p l as `l / 1`
485 constexpr Rationals& operator=(int l);
486
487 constexpr Rationals& operator=(const Rationals& rhs) = default;
489
490 /// @brief Cross-multiplication equality
491 constexpr bool operator==(const Rationals<T>& rhs) const {
492#ifdef CECCO_ERASURE_SUPPORT
493 if (is_erased() != rhs.is_erased()) return false;
494#endif
495 return numerator * rhs.get_denominator() == rhs.get_numerator() * denominator;
496 }
497
498 /// @brief Additive inverse (lvalue overload returns a copy with negated numerator)
499 constexpr Rationals operator-() const&;
500 /// @brief Additive inverse (rvalue overload negates in place)
501 constexpr Rationals& operator-() &&;
502
503 /// @brief `*this += rhs`, result kept in lowest terms
504 constexpr Rationals& operator+=(const Rationals& rhs);
505 /// @brief `*this -= rhs`, result kept in lowest terms
506 constexpr Rationals& operator-=(const Rationals& rhs);
507 /// @brief `*this *= rhs`, result kept in lowest terms
508 constexpr Rationals& operator*=(const Rationals& rhs);
509 /// @brief `*this /= rhs`; throws `std::invalid_argument` if rhs is zero
510 Rationals& operator/=(const Rationals& rhs);
511
512 /// @brief Random rational (bounded numerator and denominator), simplified
514 /// @brief Like @ref randomize but guaranteed to differ from the current value
516
517 /**
518 * @brief Multiplicative order
519 *
520 * @return 1 for `1/1`, 2 for `βˆ’1/1`, 0 (interpreted as infinite) for everything else
521 * @throws std::invalid_argument if `*this` is zero
522 */
524
525 /// @brief Additive order: 1 for zero, 0 (infinite) otherwise (characteristic 0)
526 size_t get_additive_order() const noexcept;
527
528 /// @brief Human-readable description: `"rational number"`
529 static std::string get_info() {
530 static const std::string info = "rational number";
531 return info;
532 }
533
534 /// @brief Characteristic of β„š: 0
535 static constexpr size_t get_characteristic() noexcept { return 0; }
536
537 /// @brief Sign predicate: true iff the numerator is non-negative (always false for erased elements)
538 constexpr bool has_positive_sign() const noexcept { return numerator >= 0 && denominator > 0; }
539
540 /// @brief True iff numerator is zero
541 constexpr bool is_zero() const noexcept { return numerator == 0; }
542
543#ifdef CECCO_ERASURE_SUPPORT
544 /**
545 * @brief Mark this element as erased
546 *
547 * @warning Erased elements must not participate in field arithmetic; correct use is the
548 * caller's responsibility (cf. @ref CECCO_ERASURE_SUPPORT).
549 */
550 constexpr Rationals& erase() noexcept;
551 /// @brief Clear the erasure flag, resetting to additive identity 0/1
552 constexpr Rationals& unerase() noexcept;
553 /// @brief Test whether this element is currently erased (encoded as denominator == 0)
554 constexpr bool is_erased() const noexcept { return denominator == 0; }
555#endif
556
557 /// @brief Numerator (sign carrier)
558 constexpr const T& get_numerator() const noexcept { return numerator; }
559 /// @brief Denominator (always positive after simplification)
560 constexpr const T& get_denominator() const noexcept { return denominator; }
561
562 private:
563 T numerator;
564 T denominator;
565
566 /// @brief Reduce to lowest terms with positive denominator; called automatically
567 constexpr void simplify();
568};
569
570/* member functions for Rationals */
571
572template <SignedIntType T>
573Rationals<T>::Rationals(int n, int d) : numerator(n), denominator(d) {
574 if (d == 0) throw std::invalid_argument("denominator must not be zero");
575 simplify();
576}
577
578template <SignedIntType T>
579constexpr Rationals<T>& Rationals<T>::operator=(int l) {
580 numerator = l;
581 denominator = 1;
582 return *this;
583}
584
585template <SignedIntType T>
587 if (this == &rhs) return *this;
588 numerator = std::move(rhs.numerator);
589 denominator = std::move(rhs.denominator);
590 return *this;
591}
592
593template <SignedIntType T>
594constexpr Rationals<T> Rationals<T>::operator-() const& {
595#ifdef CECCO_ERASURE_SUPPORT
596 if (this->is_erased()) return Rationals().erase();
597#endif
598 Rationals res(*this);
599 res.numerator = -res.numerator;
600 return res;
601}
602
603template <SignedIntType T>
604constexpr Rationals<T>& Rationals<T>::operator-() && {
605#ifdef CECCO_ERASURE_SUPPORT
606 if (this->is_erased()) return this->erase();
607#endif
608 numerator = -numerator;
609 return *this;
610}
611
612template <SignedIntType T>
613constexpr Rationals<T>& Rationals<T>::operator+=(const Rationals& rhs) {
614#ifdef CECCO_ERASURE_SUPPORT
615 if (this->is_erased() || rhs.is_erased()) return this->erase();
616#endif
617 auto tn = numerator * rhs.get_denominator() + denominator * rhs.get_numerator();
618 auto td = denominator * rhs.get_denominator();
619 numerator = tn;
620 denominator = td;
621 simplify();
622 return *this;
623}
624
625template <SignedIntType T>
626constexpr Rationals<T>& Rationals<T>::operator-=(const Rationals& rhs) {
627#ifdef CECCO_ERASURE_SUPPORT
628 if (this->is_erased() || rhs.is_erased()) return this->erase();
629#endif
630 auto tn = numerator * rhs.get_denominator() - denominator * rhs.get_numerator();
631 auto td = denominator * rhs.get_denominator();
632 numerator = tn;
633 denominator = td;
634 simplify();
635 return *this;
636}
637
638template <SignedIntType T>
639constexpr Rationals<T>& Rationals<T>::operator*=(const Rationals& rhs) {
640#ifdef CECCO_ERASURE_SUPPORT
641 if (this->is_erased() || rhs.is_erased()) return this->erase();
642#endif
643 auto tn = numerator * rhs.get_numerator();
644 auto td = denominator * rhs.get_denominator();
645 numerator = tn;
646 denominator = td;
647 simplify();
648 return *this;
649}
650
651template <SignedIntType T>
652Rationals<T>& Rationals<T>::operator/=(const Rationals& rhs) {
653#ifdef CECCO_ERASURE_SUPPORT
654 if (this->is_erased() || rhs.is_erased()) return this->erase();
655#endif
656 if (rhs.numerator == 0) throw std::invalid_argument("division by zero");
657 auto tn = numerator * rhs.get_denominator();
658 auto td = denominator * rhs.get_numerator();
659 numerator = tn;
660 denominator = td;
661 simplify();
662 return *this;
663}
664
665template <SignedIntType T>
667#ifdef CECCO_ERASURE_SUPPORT
668 this->unerase();
669#endif
670 thread_local std::uniform_int_distribution<int> dist(-100, 100);
671 numerator = dist(gen());
672 do {
673 denominator = dist(gen());
674 } while (denominator == 0);
675 simplify();
676 return *this;
677}
678
679template <SignedIntType T>
681#ifdef CECCO_ERASURE_SUPPORT
682 this->unerase();
683#endif
684 thread_local std::uniform_int_distribution<int> dist(-100, 100);
685 T n;
686 T d;
687 do {
688 n = dist(gen());
689 do {
690 d = dist(gen());
691 } while (d == 0);
692 } while (T(n) * denominator == numerator * T(d));
693 numerator = n;
694 denominator = d;
695 simplify();
696 return *this;
697}
698
699template <SignedIntType T>
701 if (numerator == 0)
702 throw std::invalid_argument(
703 "trying to calculate multiplicative order "
704 "of additive neutral element");
705 if (numerator == denominator)
706 return 1;
707 else if (numerator == -denominator)
708 return 2;
709 return 0;
710}
711
712template <SignedIntType T>
713size_t Rationals<T>::get_additive_order() const noexcept {
714 if (numerator == 0) {
715 return 1;
716 }
717 return 0;
718}
719
720#ifdef CECCO_ERASURE_SUPPORT
721template <SignedIntType T>
722constexpr Rationals<T>& Rationals<T>::erase() noexcept {
723 denominator = 0;
724 return *this;
725}
726
727template <SignedIntType T>
728constexpr Rationals<T>& Rationals<T>::unerase() noexcept {
729 if (is_erased()) {
730 numerator = 0;
731 denominator = 1;
732 }
733 return *this;
734}
735#endif
736
737template <SignedIntType T>
738constexpr void Rationals<T>::simplify() {
739 if (numerator == 0) {
740 denominator = 1;
741 return;
742 }
743
744 auto d = GCD<T>(numerator, denominator);
745 numerator /= d;
746 denominator /= d;
747 if (denominator < 0) {
748 numerator *= -1;
749 denominator *= -1;
750 }
751}
752
753/**
754 * @brief Print as `"numerator/denominator"` (or just `"numerator"` when denominator is 1)
755 *
756 * Single stream insertion for `std::setw` compatibility.
757 */
758template <SignedIntType T>
759std::ostream& operator<<(std::ostream& os, const Rationals<T>& e) {
760#ifdef CECCO_ERASURE_SUPPORT
761 if (e.is_erased()) {
762 os << ERASURE_MARKER;
763 } else {
764#endif
765 std::string temp = std::to_string(e.get_numerator());
766 if (e.get_denominator() != 1) temp += "/" + std::to_string(e.get_denominator());
767 os << temp;
768#ifdef CECCO_ERASURE_SUPPORT
769 }
770#endif
771 return os;
772}
773
774template <FiniteFieldType B, MOD modulus, LutMode mode>
775class Ext;
776
777// The <255 (instead of <256) is important since we're using std::numeric_limits<>::max() as erasure flag
778template <uint16_t p, uint8_t m = 1>
779using label_t = typename std::conditional_t<sqm(p, m) < 255, uint8_t, uint16_t>;
780
781namespace details {
782
787
788constexpr SignedLabel parse_signed_label(int l, size_t field_size) {
789 const bool negative = l < 0;
790 const size_t magnitude = negative ? static_cast<size_t>(-(l + 1)) + 1 : static_cast<size_t>(l);
791 if (magnitude >= field_size)
792 throw std::invalid_argument("absolute value of l must be no larger than the field size minus one");
793 return {magnitude, negative};
794}
795
796/**
797 * @brief Encode polynomial coefficients (low-to-high) as a base-q integer label
798 *
799 * @tparam q Base field size
800 * @tparam m Extension degree (number of coefficients used)
801 * @tparam SIZE Length of @p coeffs (must be β‰₯ m)
802 */
803template <uint16_t q, uint8_t m, size_t SIZE>
804static size_t constexpr integer_from_coeffs(const std::array<size_t, SIZE>& coeffs) noexcept {
805 static_assert(SIZE >= static_cast<size_t>(m), "array size must be at least the extension degree m");
806 size_t res = coeffs[0];
807 size_t t = q;
808 for (uint8_t i = 1; i < m; ++i) {
809 res += coeffs[i] * t;
810 t *= q;
811 }
812 return res;
813}
814
815/**
816 * @brief 1D lookup table for unary field operations (negation, inversion, order)
817 *
818 * @tparam LabelType Label integer type
819 * @tparam FieldSize Number of field elements
820 */
821template <typename LabelType, size_t FieldSize>
822struct Lut1D {
823 constexpr LabelType operator()(size_t i) const noexcept { return values[i]; }
824 std::array<LabelType, FieldSize> values{};
825};
826
827/**
828 * @brief 2D lookup table for commutative binary operations, optionally compressed
829 *
830 * @tparam LabelType Label integer type
831 * @tparam FieldSize Number of field elements
832 *
833 * For `FieldSize` β‰₯ @ref CECCO_COMPRESS_LUTS_FROM_Q, only the upper triangle is stored
834 * (saving ~50 % memory); the access operators normalise the index pair so callers don't
835 * see the difference.
836 */
837template <typename LabelType, size_t FieldSize>
838struct Lut2D {
839 /// @brief Mutable access (used during table construction)
840 constexpr LabelType& operator()(size_t i, size_t j) {
841 if (i > j) return operator()(j, i);
842 if constexpr (FieldSize < CECCO_COMPRESS_LUTS_FROM_Q) {
843 return values[i][j];
844 } else {
845 if (i > FieldSize / 2) {
846 return values[FieldSize - i][j - i];
847 } else {
848 return values[i][j];
849 }
850 }
851 }
852
853 /// @brief Const access (used during field operations)
854 constexpr LabelType operator()(size_t i, size_t j) const noexcept {
855 if (i > j) return operator()(j, i);
856 if constexpr (FieldSize < CECCO_COMPRESS_LUTS_FROM_Q) {
857 return values[i][j];
858 } else {
859 if (i > FieldSize / 2) {
860 return values[FieldSize - i][j - i];
861 } else {
862 return values[i][j];
863 }
864 }
865 }
866
867 /// @brief Backing storage; full square below the compression threshold, upper triangle above
868 std::array<std::array<LabelType, FieldSize>,
869 (FieldSize < CECCO_COMPRESS_LUTS_FROM_Q ? FieldSize : FieldSize / 2 + 1)>
871};
872
873/**
874 * @brief Lookup table mapping each extension-field label to its base-field coefficient vector
875 *
876 * @tparam LabelType Coefficient label type
877 * @tparam ExtensionDegree Number of coefficients per element (m)
878 * @tparam FieldSize Number of field elements
879 */
880template <typename LabelType, size_t ExtensionDegree, size_t FieldSize>
882 std::array<std::array<LabelType, ExtensionDegree>, FieldSize> values{};
883};
884
885/// @brief Build the table of multiplicative orders for all elements of 𝔽_Q
886///
887/// Each entry is the smallest k > 0 with `a^k == 1`; element 0 carries the sentinel order 0.
888/// All orders divide Q βˆ’ 1.
889template <typename LabelType, LabelType FieldSize>
890constexpr auto compute_multiplicative_orders(const Lut2D<LabelType, FieldSize>& mul_lut) {
891 Lut1D<LabelType, FieldSize> lut_mul_ord;
892
893 lut_mul_ord.values[0] = 0;
894 lut_mul_ord.values[1] = 1;
895 for (LabelType i = 2; i < FieldSize; ++i) {
896 LabelType temp = i;
897 for (LabelType j = 1; j < FieldSize; ++j) {
898 if (temp == 1) {
899 lut_mul_ord.values[i] = j;
900 break;
901 }
902 temp = mul_lut(temp, i);
903 }
904 }
905
906 return lut_mul_ord;
907}
908
909/// @brief Build multiplicative-inverse table for 𝔽_p via extended Euclidean algorithm
910///
911/// Sentinel: `lut_inv[0] == 0`. Prime-field path; for extension fields use
912/// @ref compute_multiplicative_inverses_search.
913template <typename LabelType, LabelType FieldSize>
915 requires(is_prime<LabelType>(FieldSize))
916{
918 lut_inv.values[0] = 0;
919 for (LabelType i = 1; i < FieldSize; ++i) {
920 // int64_t avoids overflow in extended GCD for primes near 2^16.
922 if (s <= -static_cast<int64_t>(FieldSize) || s >= static_cast<int64_t>(FieldSize))
923 s %= static_cast<int64_t>(FieldSize);
924 if (s < 0) s += static_cast<int64_t>(FieldSize);
925 lut_inv.values[i] = static_cast<LabelType>(s);
926 }
927 return lut_inv;
928}
929
930/// @brief Build multiplicative-inverse table by searching the multiplication LUT
931///
932/// Used for extension fields, where extended Euclidean over the prime is not applicable.
933/// Exploits the symmetry `a Β· b = 1 β‡’ b Β· a = 1` to fill two entries per match.
934template <typename LabelType, LabelType FieldSize>
937
938 lut_inv.values[0] = 0;
939 for (LabelType i = 1; i < FieldSize; ++i) {
940 if (lut_inv.values[i] != 0) continue;
941 for (LabelType j = i; j < FieldSize; ++j) {
942 if (mul_lut(i, j) == 1) {
943 lut_inv.values[i] = j;
944 lut_inv.values[j] = i;
945 break;
946 }
947 }
948 }
949
950 return lut_inv;
951}
952
953/// @brief Build additive-inverse (negation) table for 𝔽_p
954///
955/// Direct formula: βˆ’0 = 0, βˆ’a = p βˆ’ a otherwise. Prime-field path; extension fields use
956/// @ref compute_additive_inverses_search.
957template <typename LabelType, LabelType FieldSize>
960{
962 lut_neg.values[0] = 0;
963 for (LabelType i = 1; i < FieldSize; ++i) lut_neg.values[i] = FieldSize - i;
964 return lut_neg;
965}
966
967/// @brief Build additive-inverse table by searching the addition LUT
968///
969/// Used for extension fields. Symmetry `a + b = 0 β‡’ b + a = 0` fills two entries per match.
970template <typename LabelType, LabelType FieldSize>
973 lut_neg.values[0] = 0;
974 for (LabelType i = 1; i < FieldSize; ++i) {
975 if (lut_neg.values[i] != 0) continue;
976 for (LabelType j = i; j < FieldSize; ++j) {
977 if (add_lut(i, j) == 0) {
978 lut_neg.values[i] = j;
979 lut_neg.values[j] = i;
980 break;
981 }
982 }
983 }
984 return lut_neg;
985}
986
987/// @brief Build the addition table for 𝔽_p: `lut_add(a, b) = (a + b) mod p`
988///
989/// Stores the upper triangle only (commutativity). Extension-field analogue:
990/// @ref compute_polynomial_addition_table.
991template <typename LabelType, LabelType FieldSize>
994{
996
997 for (LabelType i = 0; i < FieldSize; ++i) {
998 for (LabelType j = i; j < FieldSize; ++j) lut_add(i, j) = (i + j) % FieldSize;
999 }
1000
1001 return lut_add;
1002}
1003
1004/// @brief Smallest primitive root of F* by direct power enumeration
1005///
1006/// For each candidate g ∈ {2, …, pβˆ’1} walks g, gΒ², …, g^{pβˆ’2}; g is primitive iff none of those
1007/// equals 1. Evaluated at compile time via F's constexpr `operator*=`.
1008template <typename F>
1009constexpr typename F::label_t compute_primitive_root() {
1010 constexpr size_t p = F::get_p();
1011 if constexpr (p == 2) return typename F::label_t{1};
1012
1013 for (uint16_t g_int = 2; g_int < p; ++g_int) {
1014 const F g(g_int);
1015 F power(1);
1016 bool is_primitive = true;
1017 for (uint16_t k = 1; k < p - 1; ++k) {
1018 power *= g;
1019 if (power == F(1)) {
1020 is_primitive = false;
1021 break;
1022 }
1023 }
1024 if (is_primitive) return static_cast<typename F::label_t>(g_int);
1025 }
1026 return typename F::label_t{0}; // unreachable for prime p
1027}
1028
1029/// @brief Build the addition table for 𝔽_{q^m} via coefficient-wise base-field addition
1030///
1031/// Each element is a polynomial over 𝔽_q; addition reduces to base-field addition on the
1032/// coefficient vectors taken from @p lut_coeff.
1033template <typename LabelType, LabelType FieldSize, typename LutCoeffType, uint8_t ExtensionDegree,
1034 typename BaseFieldType>
1037
1038 for (LabelType i = 0; i < FieldSize; ++i) {
1039 lut_add(0, i) = i;
1040 lut_add(i, 0) = i;
1041 }
1042
1043 for (LabelType i = 1; i < FieldSize; ++i) {
1044 for (LabelType j = i; j < FieldSize; ++j) {
1046
1047 for (uint8_t s = 0; s < ExtensionDegree; ++s) {
1050 }
1051
1053 }
1054 }
1055
1056 return lut_add;
1057}
1058
1059/// @brief Multiplicative order of @p element by repeated multiplication
1060///
1061/// @throws std::invalid_argument if @p element is zero
1062///
1063/// Single-element runtime path; batch precomputation lives in @ref compute_multiplicative_orders.
1064/// Order always divides Q βˆ’ 1.
1065template <typename FieldType>
1067 if (element == FieldType(0))
1068 throw std::invalid_argument(
1069 "trying to calculate multiplicative order "
1070 "of additive neutral element");
1071
1072 size_t i = 1;
1074 const FieldType one(1);
1075 for (;;) {
1076 if (temp == one) return i;
1077 temp *= element;
1078 ++i;
1079 }
1080}
1081
1082/// @brief First element with multiplicative order Q βˆ’ 1 (a generator of 𝔽_Q*)
1083template <typename LabelType, LabelType FieldSize>
1085 for (LabelType i = 1; i < FieldSize; ++i)
1086 if (lut_mul_ord(i) == FieldSize - 1) return i;
1087
1088 return LabelType{0}; // cannot happen
1089}
1090
1091/// @brief Build the multiplication table for 𝔽_p: `lut_mul(a, b) = (a Β· b) mod p`
1092///
1093/// Stores the upper triangle only (commutativity). Extension-field analogue:
1094/// @ref compute_polynomial_multiplication_table.
1095template <typename LabelType, LabelType FieldSize>
1098{
1100
1101 // uint32_t intermediate avoids overflow for primes near 2^16
1102 // (matches the cast in Fp::operator*=).
1103 for (LabelType i = 0; i < FieldSize; ++i) {
1104 for (LabelType j = i; j < FieldSize; ++j)
1105 lut_mul(i, j) = static_cast<LabelType>((static_cast<uint32_t>(i) * j) % FieldSize);
1106 }
1107
1108 return lut_mul;
1109}
1110
1111/// @brief Build the multiplication table for 𝔽_{q^m}: polynomial multiply, then reduce mod f(x)
1112///
1113/// `f(x) = Modulus` must be monic and irreducible over 𝔽_q. Reducibility is detected during
1114/// table construction (a non-zero label that maps to 0 is impossible in a field) and surfaces as
1115/// `std::invalid_argument`.
1116///
1117/// @throws std::invalid_argument if @p Modulus is not irreducible over the base field
1118template <typename LabelType, LabelType FieldSize, typename LutCoeffType, uint8_t ExtensionDegree,
1119 typename BaseFieldType, auto Modulus>
1122 constexpr size_t q = BaseFieldType::get_size();
1123 constexpr uint8_t m = ExtensionDegree;
1124
1125 for (LabelType i = 0; i < FieldSize; ++i) {
1126 lut_mul(0, i) = 0;
1127 lut_mul(i, 0) = 0;
1128 lut_mul(1, i) = i;
1129 lut_mul(i, 1) = i;
1130 }
1131
1132 for (LabelType i = 2; i < FieldSize; ++i) {
1133 const auto lhs = lut_coeff.values[i];
1134
1135 uint8_t lhs_deg = m - 1;
1136 for (uint8_t s = 0; s < m; ++s) {
1137 if (lhs[s] != 0)
1138 break;
1139 else
1140 --lhs_deg;
1141 }
1142
1143 for (LabelType j = i; j < FieldSize; ++j) {
1144 const auto rhs = lut_coeff.values[j];
1145
1146 uint8_t rhs_deg = m - 1;
1147 for (uint8_t t = 0; t < m; ++t) {
1148 if (rhs[t] != 0)
1149 break;
1150 else
1151 --rhs_deg;
1152 }
1153
1154 // Early termination for zero polynomials
1155 if (lhs_deg == 0 && lhs[m - 1] == 0) {
1156 lut_mul(i, j) = 0;
1157 continue;
1158 }
1159 if (rhs_deg == 0 && rhs[m - 1] == 0) {
1160 lut_mul(i, j) = 0;
1161 continue;
1162 }
1163
1164 std::array<size_t, 2 * m - 1> temp{};
1165
1166 // Polynomial multiplication
1167 for (uint8_t s = 0; s <= lhs_deg; ++s) {
1168 const auto S = lhs[m - 1 - s];
1169 if (S == 0) continue;
1170 for (uint8_t t = 0; t <= rhs_deg; ++t) {
1171 const auto T = rhs[m - 1 - t];
1172 if (T == 0) continue; // Skip zero coefficient multiplication
1174 }
1175 }
1176
1177 // Reduction modulo irreducible polynomial
1178 for (uint8_t s = 0; s < m - 1; ++s) {
1179 const size_t flag_idx = 2 * m - 2 - s;
1180 const auto flag = temp[flag_idx];
1181 if (flag == 0) continue;
1182 for (uint8_t t = 0; t <= m; ++t) {
1183 const auto mod_coeff = Modulus[m - t];
1184 if (mod_coeff == 0) continue;
1185 const size_t target_idx = flag_idx - t;
1188 }
1189 }
1190
1192 if (lut_mul(i, j) == 0) {
1193 // In CompileTime mode this throw surfaces upstream as
1194 // "must be initialized by a constant expression" at LutHolder::lut.
1195 throw std::invalid_argument("extension field construction requires irreducible modulus");
1196 }
1197 }
1198 }
1199
1200 return lut_mul;
1201}
1202
1203/**
1204 * @brief Holds a LUT generated by `F()` (no dependencies); selects compile-time or lazy storage
1205 *
1206 * @tparam LutType Generated table type
1207 * @tparam F Generator function pointer
1208 * @tparam mode @ref LutMode::CompileTime or @ref LutMode::RunTime
1209 *
1210 * Used for self-contained tables (e.g. mod-p arithmetic in prime fields).
1211 */
1212template <typename LutType, LutType (*F)(), LutMode mode>
1214
1215/// @brief CompileTime specialisation: table is a constexpr static member
1216template <typename LutType, LutType (*F)()>
1218 // If the compiler reports "must be initialized by a constant expression" here, the constexpr
1219 // step budget was likely exhausted: switch to LutMode::RunTime or raise -fconstexpr-steps /
1220 // -fconstexpr-ops-limit (see the file-header @note on CompileTime mode).
1221 static constexpr LutType lut = F();
1222 static constexpr const LutType& get_lut() { return lut; }
1223};
1224
1225/// @brief RunTime specialisation: thread-safe lazy initialisation on first access
1226template <typename LutType, LutType (*F)()>
1228 static void fill_lut() {
1229 get_lut(); // Ensure initialization
1230 }
1231 static const LutType& get_lut() {
1232 static LutType lut = F(); // Thread-safe lazy initialization
1233 return lut;
1234 }
1235};
1236
1237/**
1238 * @brief Holds a LUT generated by `F(P)`; `P` provides a dependency table
1239 *
1240 * @tparam LutType Generated table type
1241 * @tparam ProviderLutType Dependency table type
1242 * @tparam P Provider accessor (returns the dependency table)
1243 * @tparam F Generator function consuming the provider
1244 * @tparam mode @ref LutMode::CompileTime or @ref LutMode::RunTime
1245 *
1246 * Used for extension-field tables that consume a coefficient table from the base field.
1247 */
1248template <typename LutType, typename ProviderLutType, const ProviderLutType& (*P)(),
1249 LutType (*F)(const ProviderLutType& (*)()), LutMode mode>
1251
1252/// @brief CompileTime specialisation: table baked into the binary
1253template <typename LutType, typename ProviderLutType, const ProviderLutType& (*P)(),
1254 LutType (*F)(const ProviderLutType& (*)())>
1256 // If the compiler reports "must be initialized by a constant expression" here: either the
1257 // constexpr step budget was exhausted (switch to LutMode::RunTime or raise -fconstexpr-steps
1258 // / -fconstexpr-ops-limit; see the file-header @note) or a reducible modulus reached the
1259 // throw in compute_polynomial_multiplication_table.
1260 static constexpr LutType lut = F(P);
1261 static constexpr const LutType& get_lut() { return lut; }
1262};
1263
1264/// @brief RunTime specialisation: thread-safe lazy initialisation, dependency resolved on first access
1265template <typename LutType, typename ProviderLutType, const ProviderLutType& (*P)(),
1266 LutType (*F)(const ProviderLutType& (*)())>
1268 static void fill_lut() {
1269 get_lut(); // Ensure initialization
1270 }
1271 static const LutType& get_lut() {
1272 static LutType lut = F(P); // Thread-safe lazy initialization
1273 return lut;
1274 }
1275};
1276
1277} // namespace details
1278
1279/**
1280 * @brief Functor representing the field embedding Ο†: SUBFIELD β†’ SUPERFIELD, with reverse lookup
1281 *
1282 * @tparam SUBFIELD Subfield (finite-field type)
1283 * @tparam SUPERFIELD Superfield (finite-field type, must satisfy `SubfieldOf<SUPERFIELD, SUBFIELD>`)
1284 *
1285 * Maps zero and one identically and preserves addition and multiplication. The identity map on
1286 * labels is tried first: it is the natural embedding whenever `SUBFIELD` sits in the tower that
1287 * defines the labels of `SUPERFIELD` (same field, prime subfield, plain `Ext` towers), and it is
1288 * the unique choice consistent with label-based conversions such as `Ext(const Vector<B>&)` and
1289 * `as_vector()`. Only for relationships where labels do not correspond (`Iso`-mediated towers)
1290 * the image of a multiplicative generator is selected among the generators of the unique
1291 * subgroup of `SUPERFIELD*` of order `|SUBFIELD| βˆ’ 1`; candidates are accepted only when
1292 * `Ο†(x + 1) = Ο†(x) + 1` for every `x`, which together with multiplicativity implies additivity.
1293 * The table is computed once per template instantiation and cached. Construction uses O(q) memory
1294 * and O(q) time in the identity case, O(qΒ²) in the worst case, for q = |SUBFIELD|; applying the
1295 * resulting map is O(1).
1296 *
1297 * Forward map (`operator()`) is an O(1) array lookup; @ref extract reverses it via linear search
1298 * through the cached map. When `SUPERFIELD` is an @ref CECCO::Iso, @ref extract walks the MAIN
1299 * representation first, then each of OTHERS, until it finds one that contains `SUBFIELD`.
1300 *
1301 * @warning Mathematical containment is necessary but not sufficient: `SUBFIELD` must appear in
1302 * the construction tower of `SUPERFIELD` (or in one of an `Iso`'s components). Use an `Iso` to
1303 * merge towers when needed.
1304 *
1305 * @throws std::logic_error if the declared finite-field relationship admits no additive embedding
1306 *
1307 * @section Usage_Example
1308 *
1309 * @code{.cpp}
1310 * using F2 = Fp<2>;
1311 * using F4 = Ext<F2, {1, 1, 1}>;
1312 * using F16 = Ext<F4, {2, 1, 1}>;
1313 *
1314 * Embedding<F4, F16> phi;
1315 * F4 a(2);
1316 * F16 b = phi(a); // upcast, always succeeds
1317 * F4 c = phi.extract(b); // throws std::invalid_argument if b βˆ‰ Ο†(F4)
1318 * @endcode
1319 */
1323 public:
1324 /// @brief Constructs the embedding (cached on first instantiation per template arguments)
1325 Embedding();
1326
1327 /// @brief Apply Ο†: SUBFIELD β†’ SUPERFIELD
1328 constexpr SUPERFIELD operator()(const SUBFIELD& sub) const {
1329#ifdef CECCO_ERASURE_SUPPORT
1330 if (sub.is_erased()) return SUPERFIELD().erase();
1331#endif
1332 return SUPERFIELD(embedding_map[sub.get_label()]);
1333 }
1334
1335 /**
1336 * @brief Reverse Ο†: find the unique `s ∈ SUBFIELD` with `Ο†(s) == super`
1337 *
1338 * @throws std::invalid_argument if @p super is not in the image of Ο†
1339 *
1340 * O(|SUBFIELD|) for regular fields; for `Iso` superfields, O(k Β· |SUBFIELD|) where k is
1341 * the number of inspected components.
1342 */
1343 constexpr SUBFIELD extract(const SUPERFIELD& super) const;
1344
1345 private:
1346 std::span<const size_t> embedding_map;
1347
1348 /// @brief Compute the table `embedding_map[i] = Ο†(SUBFIELD(i))`
1349 static std::vector<size_t> compute_embedding();
1350};
1351
1352/*
1353Embedding member functions
1354*/
1355
1362
1365constexpr SUBFIELD Embedding<SUBFIELD, SUPERFIELD>::extract(const SUPERFIELD& super) const {
1366#ifdef CECCO_ERASURE_SUPPORT
1367 if (super.is_erased()) return SUBFIELD().erase();
1368#endif
1369 if constexpr (details::iso_info<SUPERFIELD>::is_iso) {
1370 // SUPERFIELD is an Iso type - find the correct component containing SUBFIELD
1371
1372 // Try to extract from MAIN first...
1373 using MainType = typename details::iso_info<SUPERFIELD>::main_type;
1374 if constexpr (SubfieldOf<MainType, SUBFIELD>) {
1376 return embedding.extract(super.main());
1377 } else {
1378 // ... then extract OTHERS from SUPERFIELD
1380
1381 SUBFIELD result{};
1382 bool extraction_done = false;
1383 auto try_extracting = [&]<typename OtherType>() {
1384 if constexpr (SubfieldOf<OtherType, SUBFIELD>) {
1385 if (!extraction_done) {
1389 extraction_done = true;
1390 }
1391 }
1392 };
1393
1394 // Apply the lambda to each type in the tuple
1395 std::apply([&]<typename... Types>(Types...) { (try_extracting.template operator()<Types>(), ...); },
1396 OthersTuple{});
1397
1398 if (!extraction_done) {
1399 throw std::invalid_argument("subfield not found in any Iso component");
1400 }
1401 return result;
1402 }
1403 } else {
1404 // SUPERFIELD is a regular field type
1406 if (it == embedding_map.end()) throw std::invalid_argument("superfield element is not in subfield");
1408 }
1409}
1410
1414 constexpr size_t sub_size = SUBFIELD::get_size();
1415 constexpr size_t super_size = SUPERFIELD::get_size();
1417
1418 const auto sub_gen = SUBFIELD::get_generator();
1419
1420 const auto is_additive = [&] {
1421 const SUBFIELD sub_one(1);
1422 const SUPERFIELD super_one(1);
1423 for (size_t i = 0; i < sub_size; ++i) {
1424 const SUBFIELD x(i);
1425 const auto lhs = SUPERFIELD(embedding[(x + sub_one).get_label()]);
1426 const auto rhs = SUPERFIELD(embedding[i]) + super_one;
1427 if (lhs != rhs) return false;
1428 }
1429 return true;
1430 };
1431
1432 for (size_t i = 0; i < sub_size; ++i) embedding[i] = i;
1433 // Ο†(1) = 1, so multiplicativity on the generator column extends to all products
1434 const auto identity_is_multiplicative = [&] {
1436 for (size_t i = 0; i < sub_size; ++i) {
1437 const SUBFIELD x(i);
1438 if (SUPERFIELD(embedding[(sub_gen * x).get_label()]) != phi_gen * SUPERFIELD(embedding[i])) return false;
1439 }
1440 return true;
1441 };
1443
1444 embedding[0] = 0;
1445 embedding[1] = 1;
1446
1447 const auto super_gen = SUPERFIELD::get_generator();
1448 constexpr size_t factor = (super_size - 1) / (sub_size - 1);
1449 const auto subgroup_gen = sqm<SUPERFIELD>(super_gen, factor);
1450
1451 const auto fill_embedding = [&](const SUPERFIELD& image_gen) {
1452 auto sub_elem = sub_gen;
1453 auto super_elem = image_gen;
1454 for (size_t i = 1; i < sub_size - 1; ++i) {
1456 sub_elem *= sub_gen;
1458 }
1459 };
1460
1461 for (size_t e = 1; e < sub_size - 1; ++e) {
1462 if (std::gcd(e, sub_size - 1) != 1) continue;
1464 if (is_additive()) return embedding;
1465 }
1466
1467 throw std::logic_error("Cannot construct additive subfield embedding");
1468}
1469
1470namespace details {
1471
1472/**
1473 * @brief Shared static storage of the forward and reverse maps for the isomorphism A ↔ B
1474 *
1475 * @tparam A First finite field
1476 * @tparam B Second finite field (`Isomorphic<A, B>`)
1477 *
1478 * Lets both `Isomorphism<A, B>` and `Isomorphism<B, A>` share one cached pair of maps,
1479 * computed exactly once per template instantiation.
1480 */
1485 inline static std::vector<size_t> forward_iso; // A -> B
1486 inline static std::vector<size_t> reverse_iso; // B -> A
1487
1488 /// @brief Compute and cache both maps on first call (no-op afterwards)
1489 static void compute_if_needed() {
1490 std::call_once(computed_flag, []() {
1491 constexpr size_t size = A::get_size();
1494
1495 const size_t m = A(0).template as_vector<Fp<A::get_p()>>().get_n();
1496
1497 A alpha;
1498 B beta;
1499
1501
1502 // Find root of h in A - is always a generator (property of Conway polynomials)
1503 Polynomial<A> h_A(h);
1504 for (size_t i = 1; i < size; ++i) {
1505 A a(i);
1506 if (h_A(a) == A(0)) {
1507 alpha = a;
1508 break;
1509 }
1510 }
1511
1512 // Find root of h in B - is always a generator (property of Conway polynomials)
1513 Polynomial<B> h_B(h);
1514 for (size_t i = 1; i < size; ++i) {
1515 B b(i);
1516 if (h_B(b) == B(0)) {
1517 beta = b;
1518 break;
1519 }
1520 }
1521
1522 // Construct new basis from generator
1523 std::vector<A> basis(m);
1524 A a(1);
1525 for (size_t i = 0; i < m; ++i) {
1526 basis[i] = a;
1527 a *= alpha;
1528 }
1529
1530 // Calculate change-of-basis matrix
1531 Matrix<Fp<A::get_p()>> Mi(m, m);
1532 for (size_t i = 0; i < m; ++i) {
1533 Mi.set_submatrix(0, i,
1534 transpose(Matrix<Fp<A::get_p()>>(basis[i].template as_vector<Fp<A::get_p()>>())));
1535 }
1536 Mi.invert();
1537
1538 // Compute forward mapping A -> B
1539 for (size_t i = 0; i < size; ++i) {
1540 A a(i);
1541 auto v = a.template as_vector<Fp<A::get_p()>>();
1542 Vector<B> w = v * transpose(Mi);
1543 auto p = Polynomial<B>(w);
1544 auto b = p(beta);
1545 forward_iso[i] = b.get_label();
1546 }
1547
1548 // Compute reverse mapping B -> A (inverse of forward mapping)
1549 for (size_t i = 0; i < size; ++i) reverse_iso[forward_iso[i]] = i;
1550 });
1551 }
1552};
1553
1554} // namespace details
1555
1556/**
1557 * @brief Functor representing the field isomorphism Ο†: A β†’ B between two same-size finite fields
1558 *
1559 * @tparam A Source finite field
1560 * @tparam B Target finite field (`Isomorphic<A, B>`)
1561 *
1562 * The isomorphism is built deterministically: a Conway polynomial of the prime field gives
1563 * generators Ξ± ∈ A and Ξ² ∈ B as common roots, and Ο† is extended linearly via a change-of-basis
1564 * matrix over the prime subfield. The resulting table is stored in a single
1565 * @ref details::IsomorphismPair shared between `Isomorphism<A, B>` and `Isomorphism<B, A>`.
1566 * It is a field homomorphism: Ο†(a + b) = Ο†(a) + Ο†(b), Ο†(a Β· b) = Ο†(a) Β· Ο†(b), Ο†(0) = 0,
1567 * Ο†(1) = 1.
1568 *
1569 * @section Usage_Example
1570 *
1571 * @code{.cpp}
1572 * using F2 = Fp<2>;
1573 * using F4 = Ext<F2, {1, 1, 1}>;
1574 * using F16_a = Ext<F4, {2, 1, 1}>;
1575 * using F16_b = Ext<F4, {1, 2, 1}>;
1576 *
1577 * Isomorphism<F16_a, F16_b> phi;
1578 * F16_a a(4);
1579 * F16_b b = phi(a);
1580 * F16_a c = phi.inverse()(b);
1581 * assert(a == c);
1582 * @endcode
1583 */
1587 public:
1588 /// @brief Construct (or retrieve from cache) the isomorphism map A β†’ B
1589 Isomorphism();
1590
1591 /// @brief Apply Ο† to @p a
1592 constexpr B operator()(const A& a) const {
1593#ifdef CECCO_ERASURE_SUPPORT
1594 if (a.is_erased()) return B().erase();
1595#endif
1596 return B(iso[a.get_label()]);
1597 }
1598
1599 /// @brief Inverse isomorphism φ⁻¹: B β†’ A
1600 Isomorphism<B, A> inverse() const;
1601
1602 private:
1603 std::span<const size_t> iso;
1604};
1605
1606/*
1607Isomorphism member functions
1608*/
1609
1613 if constexpr (std::is_same_v<A, B>) {
1614 static const std::vector<size_t> identity = [] {
1616 std::iota(table.begin(), table.end(), 0);
1617 return table;
1618 }();
1619 iso = std::span<const size_t>(identity);
1620 } else {
1621 // process-local total ordering via the addresses of the pair statics (under std::less, and
1622 // the two directions evaluate complementary comparisons), so that Isomorphism<A,B> and
1623 // Isomorphism<B,A> share one details::IsomorphismPair even for types with equal get_info()
1624 static const bool a_first = std::less<const std::once_flag*>{}(&details::IsomorphismPair<A, B>::computed_flag,
1626 if (a_first) {
1629 } else {
1632 }
1633 }
1634}
1635
1639 // the shared details::IsomorphismPair already stores both directions
1640 return Isomorphism<B, A>();
1641}
1642
1643/**
1644 * @brief Prime field 𝔽_p β‰… β„€/pβ„€
1645 *
1646 * @tparam p Prime modulus, 2 ≀ p ≀ 65521 (largest prime fitting in `uint16_t`)
1647 *
1648 * Elements are stored as `label_t` integers in {0, 1, …, p βˆ’ 1} (width is the smallest unsigned
1649 * type that fits p). Arithmetic is direct mod-p by default; define @ref CECCO_USE_LUTS_FOR_FP
1650 * to switch to LUTs (rarely advisable). The primality of @p p is enforced by `static_assert`.
1651 *
1652 * @section Usage_Example
1653 *
1654 * @code{.cpp}
1655 * using F5 = Fp<5>;
1656 * F5 a(3), b(4);
1657 * auto c = a + b; // 2 (7 mod 5)
1658 * auto d = a / b; // 3 Β· 4⁻¹ = 3 Β· 4 = 2 in 𝔽₅
1659 * size_t ord = a.get_multiplicative_order(); // order of 3 in 𝔽₅*
1660 * @endcode
1661 */
1662template <uint16_t p>
1663class Fp : public details::Field<Fp<p>> {
1664 static_assert(is_prime(p), "p is not a prime");
1665
1666 public:
1667 using label_t = ::CECCO::label_t<p>;
1668
1669 /// @brief Default constructor: 0
1670 constexpr Fp() noexcept : label(0) {}
1671
1672 /// @brief Construct from a signed element label `l ∈ {βˆ’(p βˆ’ 1), …, p βˆ’ 1}`
1673 ///
1674 /// A non-negative value selects that label; a negative value selects the additive inverse
1675 /// of the element with label `|l|`. This is label construction, not reduction modulo p.
1676 ///
1677 /// @throws std::invalid_argument if `|l| >= p`
1678 constexpr Fp(int l);
1679
1680 constexpr Fp(const Fp& other) noexcept = default;
1681 constexpr Fp(Fp&& other) noexcept = default;
1682
1683 /**
1684 * @brief Extract a prime-field element from an extension field (downcast)
1685 *
1686 * @tparam S Base of the source extension field
1687 * @tparam ext_modulus Modulus of the source extension field
1688 * @throws std::invalid_argument if @p other is not in the prime subfield
1689 *
1690 * Uses a cached @ref CECCO::Embedding from `Fp<p>` to `Ext<S, ext_modulus, mode>`; since
1691 * 𝔽_p is the minimal subfield of any extension over it, this is the canonical reverse
1692 * lookup. Source and target must share the characteristic (enforced by `static_assert`).
1693 */
1694 template <FiniteFieldType S, MOD ext_modulus, LutMode mode>
1695 Fp(const Ext<S, ext_modulus, mode>& other);
1696
1697 /**
1698 * @brief Extract a prime-field element from an `Iso` containing this prime subfield
1699 *
1700 * @throws std::invalid_argument if no `Iso` component contains an element in 𝔽_p
1701 *
1702 * Tries the MAIN representation first, then each of OTHERS, until one yields a successful
1703 * downcast to `Fp<p>`.
1704 */
1705 template <FiniteFieldType MAIN, FiniteFieldType... OTHERS>
1706 Fp(const Iso<MAIN, OTHERS...>& other)
1708
1709 ~Fp() noexcept = default;
1710
1711 /// @brief Assign a signed element label (same semantics and range as @ref Fp(int))
1712 /// @throws std::invalid_argument if `|l| >= p`
1713 constexpr Fp& operator=(int l);
1714
1715 constexpr Fp& operator=(const Fp& rhs) noexcept = default;
1716 constexpr Fp& operator=(Fp&& rhs) noexcept = default;
1717
1718 /// @brief Project an extension-field element to 𝔽_p (copy-and-swap; same semantics as the constructor)
1719 template <FiniteFieldType S, MOD ext_modulus, LutMode mode>
1720 Fp& operator=(const Ext<S, ext_modulus, mode>& other);
1721
1722 /// @brief Project an `Iso` element to 𝔽_p (copy-and-swap; same semantics as the constructor)
1723 template <FiniteFieldType MAIN, FiniteFieldType... OTHERS>
1724 requires(MAIN::get_characteristic() == p)
1725 Fp& operator=(const Iso<MAIN, OTHERS...>& other);
1726
1727 constexpr bool operator==(const Fp& rhs) const noexcept { return label == rhs.get_label(); }
1728
1729 /// @brief Additive inverse (lvalue): returns a new element with `-label mod p`
1730 constexpr Fp operator-() const& noexcept;
1731 /// @brief Additive inverse (rvalue): in place
1732 constexpr Fp& operator-() && noexcept;
1733
1734 /// @brief `*this = (label + rhs.label) mod p`
1735 constexpr Fp& operator+=(const Fp& rhs) noexcept;
1736 /// @brief `*this = (label βˆ’ rhs.label) mod p`
1737 constexpr Fp& operator-=(const Fp& rhs) noexcept;
1738 /// @brief `*this = (label Β· rhs.label) mod p`
1739 constexpr Fp& operator*=(const Fp& rhs) noexcept;
1740 /// @brief `*this = (label Β· s) mod p` (repeated addition)
1741 constexpr Fp& operator*=(int s) noexcept;
1742 /// @brief `*this = (label · rhs.label⁻¹) mod p`; throws `std::invalid_argument` if rhs is zero
1743 Fp& operator/=(const Fp& rhs);
1744
1745 /// @brief Uniform random element in {0, …, p βˆ’ 1}
1747 /// @brief Like @ref randomize but guaranteed to differ from the current value
1749
1750 /**
1751 * @brief Multiplicative order in 𝔽_p*
1752 *
1753 * @throws std::invalid_argument if `*this` is zero
1754 */
1756
1757 /// @brief Additive order: 1 for zero, p otherwise
1759
1760 /// @brief Human-readable description: `"prime field with p elements"`
1761 static std::string get_info() {
1762 static const std::string info = "prime field with " + std::to_string(p) + " elements";
1763 return info;
1764 }
1765
1766 static constexpr size_t get_characteristic() noexcept { return p; }
1767
1768 /// @brief Underlying integer label in {0, …, p βˆ’ 1}
1769 constexpr size_t get_label() const noexcept { return label; }
1770
1771 /// @brief Generator (primitive root) of 𝔽_p*; precomputed label is @ref Gen
1772 static constexpr Fp get_generator() noexcept { return Fp(Gen); }
1773
1774 static constexpr size_t get_p() noexcept { return p; }
1775 static constexpr size_t get_m() noexcept { return 1; }
1776 static constexpr size_t get_q() noexcept { return p; }
1777 static constexpr size_t get_size() noexcept { return p; }
1778
1779 /**
1780 * @brief Always true for prime fields
1781 *
1782 * Prime fields' arithmetic interface is constexpr regardless of @ref CECCO_USE_LUTS_FOR_FP,
1783 * so a `CompileTime` @ref CECCO::Ext can always be built on top.
1784 */
1785 static constexpr bool is_constexpr_ready() noexcept { return true; }
1786
1787 /// @brief Print all lookup tables to `std::cout` (debugging aid)
1788 static void show_tables();
1789
1790 /// @brief Always true (finite fields are unordered)
1791 constexpr bool has_positive_sign() const noexcept { return true; }
1792 /// @brief True iff this is the additive identity
1793 constexpr bool is_zero() const noexcept { return label == 0; }
1794
1795#ifdef CECCO_ERASURE_SUPPORT
1796 /**
1797 * @brief Mark this element as erased (encoded as `label == max(label_t)`)
1798 *
1799 * @warning Erased elements must not participate in field arithmetic, see
1800 * @ref CECCO_ERASURE_SUPPORT.
1801 */
1802 constexpr Fp& erase() noexcept;
1803 /// @brief Clear the erasure flag, resetting to the additive identity
1804 constexpr Fp& unerase() noexcept;
1805 /// @brief Test whether this element is currently erased
1806 constexpr bool is_erased() const noexcept { return label == std::numeric_limits<label_t>::max(); }
1807#endif
1808
1809 /**
1810 * @brief Compile-time signal that all LUTs are constructed
1811 *
1812 * Used by extension fields built on top of 𝔽_p to defer their own LUT computation until
1813 * the base field is fully instantiated, avoiding compiler recursion-depth issues.
1814 */
1815 static constexpr bool ready() {
1816#ifdef CECCO_USE_LUTS_FOR_FP
1817 return luts_ready;
1818#else
1819 return true; // Always ready when not using LUTs
1820#endif
1821 }
1822
1823 // LUT-compatible interface for (default) non-LUT mode. This allows Fp to be used as base field B in Ext without
1824 // enabling CECCO_USE_LUTS_FOR_FP. These functions delegate to the optimized operator implementations to avoid
1825 // duplication.
1826#ifndef CECCO_USE_LUTS_FOR_FP
1827 /// @brief Addition function with LUT-compatible interface: lut_add(a,b) = (a + b) mod p
1828 static constexpr label_t lut_add(label_t a, label_t b) noexcept {
1829 Fp temp_a(a), temp_b(b);
1830 temp_a += temp_b;
1831 return temp_a.get_label();
1832 }
1833
1834 /// @brief Multiplication function with LUT-compatible interface: lut_mul(a,b) = (a * b) mod p
1835 static constexpr label_t lut_mul(label_t a, label_t b) noexcept {
1836 Fp temp_a(a), temp_b(b);
1837 temp_a *= temp_b;
1838 return temp_a.get_label();
1839 }
1840
1841 /// @brief Negation function with LUT-compatible interface: lut_neg(a) = (-a) mod p
1842 static constexpr label_t lut_neg(label_t a) noexcept {
1843 Fp temp(a);
1844 temp = -std::move(temp);
1845 return temp.get_label();
1846 }
1847#endif
1848
1849 /// @brief Primitive element (generator) of F_p*
1850 ///
1851 /// Smallest primitive root mod p, computed at compile time via constexpr power enumeration
1852 /// using Fp's own multiplication.
1854
1855 private:
1856 label_t label; ///< Element value in {0, 1, ..., p-1}
1857
1858#ifdef CECCO_USE_LUTS_FOR_FP
1859 /// @brief Type alias for 1D lookup tables
1860 using Lut1D = details::Lut1D<label_t, p>;
1861 /// @brief Type alias for 2D lookup tables
1862 using Lut2D = details::Lut2D<label_t, p>;
1863
1864 public:
1865 /**
1866 * @name Precomputed Lookup Tables
1867 * @brief Compile-time generated tables for field operations
1868 * @{
1869 */
1870
1871 /// @brief Addition lookup table: lut_add(a,b) = (a + b) mod p
1873
1874 /// @brief Multiplication lookup table: lut_mul(a,b) = (a * b) mod p
1876
1877 /// @brief Additive inverse lookup table: lut_neg[a] = (-a) mod p
1879
1880 /// @brief Multiplicative inverse lookup table: lut_inv[a] = a^(-1) mod p
1882
1883 /// @brief Multiplicative order lookup table: lut_mul_ord[a] = multiplicative order of a in 𝔽_p\c\{0}
1885
1886 static constexpr bool luts_ready = []() constexpr {
1887 static_assert(lut_add(0, 0) == 0); // Forces immediate calculation of lut_add
1888 static_assert(lut_neg(0) == 0); // Forces immediate calculation lut_neg
1889 static_assert(lut_mul(0, 1) == 0); // Forces immediate calculation lut_mul
1890 return true;
1891 }();
1892
1893 /** @} */
1894
1895#endif
1896};
1897
1898/* member functions for Fp */
1899
1900template <uint16_t p>
1901constexpr Fp<p>::Fp(int l) {
1902 const auto parsed = details::parse_signed_label(l, p);
1903 label = static_cast<label_t>(parsed.magnitude);
1904 if (parsed.negative) label = lut_neg(label);
1905}
1906
1907template <uint16_t p>
1908template <FiniteFieldType S, MOD ext_modulus, LutMode mode>
1909Fp<p>::Fp(const Ext<S, ext_modulus, mode>& other) {
1910 // Ensure same characteristic
1911 static_assert(Fp<p>::get_characteristic() == Ext<S, ext_modulus, mode>::get_characteristic(),
1912 "trying to convert between fields with different characteristic");
1913
1914#ifdef CECCO_ERASURE_SUPPORT
1915 if (other.is_erased()) {
1916 this->erase();
1917 return;
1918 }
1919#endif
1920
1921 // Extract Fp<p> element from extension field (downcast via largest common subfield)
1922 // Since Fp<p> is minimal, details::largest_common_subfield_t<Fp<p>, Ext<S, ...>> is always Fp<p>
1923 auto embedding = Embedding<Fp<p>, Ext<S, ext_modulus, mode>>();
1924 Fp<p> result = embedding.extract(other);
1925 label = result.get_label();
1926}
1927
1928template <uint16_t p>
1929template <FiniteFieldType MAIN, FiniteFieldType... OTHERS>
1930Fp<p>::Fp(const Iso<MAIN, OTHERS...>& other)
1932{
1933#ifdef CECCO_ERASURE_SUPPORT
1934 if (other.is_erased()) {
1935 this->erase();
1936 return;
1937 }
1938#endif
1939
1940 // Try to extract from MAIN first
1941 if constexpr (SubfieldOf<MAIN, Fp<p>>) {
1942 *this = Fp(other.main());
1943 } else {
1944 // Try to extract from each OTHER component
1945 bool converted = false;
1946 (([&]() {
1947 if constexpr (SubfieldOf<OTHERS, Fp<p>>) {
1948 if (!converted) {
1949 *this = Fp(other.template as<OTHERS>());
1950 converted = true;
1951 }
1952 }
1953 }()),
1954 ...);
1955
1956 if (!converted) {
1957 throw std::invalid_argument("no conversion path found from Iso to prime field");
1958 }
1959 }
1960}
1961
1962template <uint16_t p>
1963constexpr Fp<p>& Fp<p>::operator=(int l) {
1964 const auto parsed = details::parse_signed_label(l, p);
1965 label = static_cast<label_t>(parsed.magnitude);
1966 if (parsed.negative) label = lut_neg(label);
1967 return *this;
1968}
1969
1970template <uint16_t p>
1971template <FiniteFieldType S, MOD ext_modulus, LutMode mode>
1972Fp<p>& Fp<p>::operator=(const Ext<S, ext_modulus, mode>& rhs) {
1973 Fp temp(rhs);
1974 std::swap(*this, temp);
1975 return *this;
1976}
1977
1978template <uint16_t p>
1981Fp<p>& Fp<p>::operator=(const Iso<MAIN, OTHERS...>& rhs) {
1982 Fp temp(rhs);
1983 std::swap(*this, temp);
1984 return *this;
1985}
1986
1987template <uint16_t p>
1988constexpr Fp<p> Fp<p>::operator-() const& noexcept {
1989#ifdef CECCO_ERASURE_SUPPORT
1990 if (this->is_erased()) return Fp().erase();
1991#endif
1992 Fp res(*this);
1993 if (res.label != 0) {
1994#ifndef CECCO_USE_LUTS_FOR_FP
1995 int temp = -(int)res.label + (int)p;
1996 res.label = temp;
1997#else
1999#endif
2000 }
2001 return res;
2002}
2003
2004template <uint16_t p>
2005constexpr Fp<p>& Fp<p>::operator-() && noexcept {
2006#ifdef CECCO_ERASURE_SUPPORT
2007 if (this->is_erased()) return this->erase();
2008#endif
2009 if (label != 0) {
2010#ifndef CECCO_USE_LUTS_FOR_FP
2011 label = -(int)label + (int)p;
2012#else
2013 label = lut_neg(label);
2014#endif
2015 }
2016 return *this;
2017}
2018
2019template <uint16_t p>
2020constexpr Fp<p>& Fp<p>::operator+=(const Fp& rhs) noexcept {
2021#ifdef CECCO_ERASURE_SUPPORT
2022 if (this->is_erased() || rhs.is_erased()) return this->erase();
2023#endif
2024#ifndef CECCO_USE_LUTS_FOR_FP
2025 int temp = label + rhs.get_label();
2026 if (temp < p)
2027 label = temp;
2028 else
2029 label = temp - p;
2030#else
2032#endif
2033 return *this;
2034}
2035
2036template <uint16_t p>
2037constexpr Fp<p>& Fp<p>::operator-=(const Fp& rhs) noexcept {
2038#ifdef CECCO_ERASURE_SUPPORT
2039 if (this->is_erased() || rhs.is_erased()) return this->erase();
2040#endif
2041#ifndef CECCO_USE_LUTS_FOR_FP
2042 int temp = (int)label - (int)rhs.get_label();
2043 if (temp >= 0)
2044 label = temp;
2045 else
2046 label = temp + p;
2047#else
2049#endif
2050 return *this;
2051}
2052
2053template <uint16_t p>
2054constexpr Fp<p>& Fp<p>::operator*=(const Fp& rhs) noexcept {
2055#ifdef CECCO_ERASURE_SUPPORT
2056 if (this->is_erased() || rhs.is_erased()) return this->erase();
2057#endif
2058#ifndef CECCO_USE_LUTS_FOR_FP
2059 // uint32_t intermediate avoids signed-int overflow for primes near 2^16
2060 // (max product (p-1)^2 β‰ˆ 4.29e9 fits in uint32_t but not in signed int).
2061 uint32_t temp = static_cast<uint32_t>(label) * rhs.get_label();
2062 if (temp < p)
2063 label = static_cast<label_t>(temp);
2064 else
2065 label = static_cast<label_t>(temp % p);
2066#else
2068#endif
2069 return *this;
2070}
2071
2072template <uint16_t p>
2073constexpr Fp<p>& Fp<p>::operator*=(int s) noexcept {
2074#ifdef CECCO_ERASURE_SUPPORT
2075 if (this->is_erased()) return *this;
2076#endif
2077 s %= p;
2078 Fp res = daa<Fp>(*this, s);
2079 *this = std::move(res);
2080 return *this;
2081}
2082
2083template <uint16_t p>
2084Fp<p>& Fp<p>::operator/=(const Fp& rhs) {
2085#ifdef CECCO_ERASURE_SUPPORT
2086 if (this->is_erased() || rhs.is_erased()) return this->erase();
2087#endif
2088 if (rhs.label == 0) throw std::invalid_argument("trying to divide by zero");
2089#ifndef CECCO_USE_LUTS_FOR_FP
2090 *this *= Fp(modinv<p, int64_t>(rhs.get_label()));
2091#else
2093#endif
2094 return *this;
2095}
2096
2097template <uint16_t p>
2099#ifdef CECCO_ERASURE_SUPPORT
2100 this->unerase();
2101#endif
2102 thread_local std::uniform_int_distribution<int> dist(0, p - 1);
2103#ifndef CECCO_USE_LUTS_FOR_FP
2104 int temp = label + dist(gen());
2105 if (temp < p)
2106 label = temp;
2107 else
2108 label = temp - p;
2109#else
2110 label = lut_add(label, dist(gen()));
2111#endif
2112 return *this;
2113}
2114
2115template <uint16_t p>
2117#ifdef CECCO_ERASURE_SUPPORT
2118 this->unerase();
2119#endif
2120 thread_local std::uniform_int_distribution<int> dist(1, p - 1);
2121#ifndef CECCO_USE_LUTS_FOR_FP
2122 int temp = label + dist(gen());
2123 if (temp < p)
2124 label = temp;
2125 else
2126 label = temp - p;
2127#else
2128 label = lut_add(label, dist(gen()));
2129#endif
2130 return *this;
2131}
2132
2133template <uint16_t p>
2135#ifdef CECCO_ERASURE_SUPPORT
2136 if (is_erased()) throw std::invalid_argument("trying to calculate multiplicative order of erased element");
2137#endif
2138 if (label == 0) throw std::invalid_argument("trying to calculate multiplicative order of additive neutral element");
2139#ifndef CECCO_USE_LUTS_FOR_FP
2141#else
2142 return lut_mul_ord(label);
2143#endif
2144}
2145
2146template <uint16_t p>
2148#ifdef CECCO_ERASURE_SUPPORT
2149 if (is_erased()) throw std::invalid_argument("trying to calculate additive order of erased element");
2150#endif
2151 if (label == 0) return 1;
2152 return p;
2153}
2154
2155template <uint16_t p>
2156void Fp<p>::show_tables() {
2157 std::cout << "addition table (row and column headers omitted)" << std::endl;
2158 for (label_t i = 0; i < p; ++i) {
2159 for (label_t j = 0; j < p; ++j) std::cout << (int)lut_add(i, j) << ", ";
2160 std::cout << std::endl;
2161 }
2162
2163#ifdef CECCO_USE_LUTS_FOR_FP
2164 std::cout << "additive inverse table (row headers omitted)" << std::endl;
2165 for (label_t i = 0; i < p; ++i) std::cout << (int)lut_neg(i) << std::endl;
2166#endif
2167
2168 std::cout << "multiplication table (row and column headers omitted)" << std::endl;
2169 for (label_t i = 0; i < p; ++i) {
2170 for (label_t j = 0; j < p; ++j) std::cout << (int)lut_mul(i, j) << ", ";
2171 std::cout << std::endl;
2172 }
2173
2174#ifdef CECCO_USE_LUTS_FOR_FP
2175 std::cout << "multiplicative inverse table (row headers omitted)" << std::endl;
2176 for (label_t i = 0; i < p; ++i) std::cout << (int)lut_inv(i) << std::endl;
2177#endif
2178}
2179
2180#ifdef CECCO_ERASURE_SUPPORT
2181template <uint16_t p>
2182constexpr Fp<p>& Fp<p>::erase() noexcept {
2184 return *this;
2185}
2186
2187template <uint16_t p>
2188constexpr Fp<p>& Fp<p>::unerase() noexcept {
2189 if (is_erased()) (*this) = 0;
2190 return *this;
2191}
2192#endif
2193
2194/// @brief Print as the integer label (or @ref ERASURE_MARKER if erased)
2195template <uint16_t p>
2196std::ostream& operator<<(std::ostream& os, const Fp<p>& e) {
2197#ifdef CECCO_ERASURE_SUPPORT
2198 if (e.is_erased()) {
2199 os << ERASURE_MARKER;
2200 return os;
2201 }
2202#endif
2203 os << (int)e.get_label();
2204 return os;
2205}
2206
2207/**
2208 * @brief Extension field 𝔽_{q^m} β‰… B[x]/(f(x)), constructed from a base field and an
2209 * irreducible monic modulus polynomial
2210 *
2211 * @tparam B Base field, either @ref CECCO::Fp, another @ref CECCO::Ext, or @ref CECCO::Iso
2212 * @tparam modulus Coefficients of f(x) low-to-high (constant term first), given as reduced
2213 * base-field labels in {0, …, q βˆ’ 1}; leading coefficient must be 1, degree
2214 * m β‰₯ 2; f(x) must be irreducible over B
2215 * @tparam mode @ref CECCO::LutMode::RunTime (default) or @ref CECCO::LutMode::CompileTime
2216 *
2217 * The result has q = |B| and Q = q^m elements; elements are stored as `label_t` integers in
2218 * {0, …, Q βˆ’ 1}. Towers are built by re-using `Ext` as the base. Pick `CompileTime` for small
2219 * fields (zero startup, larger binary) or `RunTime` for large fields (faster compilation,
2220 * lazy initialisation on first use). LUT modes mix freely across a tower; a `CompileTime`
2221 * extension can only be built when its base satisfies @ref CECCO::Ext::is_constexpr_ready.
2222 *
2223 * @warning A non-irreducible @p modulus is detected during LUT construction and surfaces as
2224 * `std::invalid_argument` at runtime, or as a constexpr-evaluation error at compile time when
2225 * `mode == LutMode::CompileTime`. Use @ref CECCO::find_irreducible to obtain a valid one.
2226 *
2227 * @section Usage_Example
2228 *
2229 * @code{.cpp}
2230 * using F3 = Fp<3>;
2231 * using F9 = Ext<F3, {2, 2, 1}>; // 𝔽₃[x]/(2 + 2x + xΒ²)
2232 * using F27 = Ext<F9, {1, 2, 1}>; // 3-level tower 𝔽₃ βŠ‚ 𝔽₉ βŠ‚ 𝔽₂₇
2233 *
2234 * F9 a(5), b(7);
2235 * auto c = a * b + F9(1); // arithmetic
2236 * Vector<F3> coeffs = a.as_vector<F3>(); // coefficient vector over the prime subfield
2237 * size_t ord = a.get_multiplicative_order();
2238 *
2239 * F27 x(100);
2240 * Vector<F3> v = x.as_vector<F3>(); // descend straight to the prime subfield
2241 * @endcode
2242 */
2244class Ext : public details::Field<Ext<B, modulus, mode>> {
2245 static_assert(modulus.back() == 1, "provided polynomial is not monic");
2246 static_assert(modulus.size() > 2, "provided polynomial has degree less than 2");
2247 static_assert(
2248 []() constexpr {
2249 for (size_t i = 0; i < modulus.size(); ++i)
2250 if (modulus[i] < 0 || static_cast<size_t>(modulus[i]) >= B::get_size()) return false;
2251 return true;
2252 }(),
2253 "modulus coefficients must be reduced base field labels in {0, ..., q - 1}");
2254 static_assert(B::ready(), "base field must be fully instantiated");
2255 static_assert(mode == LutMode::RunTime || B::is_constexpr_ready(),
2256 "CompileTime extension fields require all base fields to have constexpr-ready interfaces");
2257
2258 private:
2259 static constexpr uint8_t m = modulus.size() - 1;
2260 static constexpr size_t q = B::get_size();
2261 static constexpr size_t Q = sqm(q, m);
2262
2263 public:
2264 using label_t = ::CECCO::label_t<Q>;
2265 using BASE_FIELD = B;
2266
2267 /// @brief Default constructor: 0
2268 constexpr Ext() noexcept : label{0} {}
2269
2270 /**
2271 * @brief Construct from a signed element label `l ∈ {βˆ’(Q βˆ’ 1), …, Q βˆ’ 1}`
2272 *
2273 * A non-negative value selects that label; a negative value selects the additive inverse
2274 * of the element with label `|l|`. This is label construction, not reduction modulo the
2275 * characteristic.
2276 *
2277 * @throws std::invalid_argument if `|l| >= Q`
2278 */
2279 Ext(int l);
2280
2281 constexpr Ext(const Ext& other) noexcept = default;
2282 constexpr Ext(Ext&& other) noexcept = default;
2283
2284 /// @brief Embed a base-field element via the natural embedding B β†’ Ext<B, modulus, mode>
2285 Ext(const B& other);
2286
2287 /**
2288 * @brief Cross-field conversion from another extension field of the same characteristic
2289 *
2290 * @tparam S Base of the source extension field
2291 * @tparam ext_modulus Modulus of the source extension field
2292 * @throws std::invalid_argument on a downcast whose source value lies outside the target
2293 *
2294 * Picks the cheapest available path: direct copy if the type matches; cached @ref Isomorphism
2295 * for isomorphic fields; cached @ref Embedding for tower relationships (upcast cannot fail,
2296 * downcast may); two-step conversion via @ref details::largest_common_subfield_t for
2297 * unrelated towers.
2298 */
2301 Ext(const Ext<S, ext_modulus, ext_mode>& other);
2302
2303 /**
2304 * @brief Construct from a coefficient vector over a subfield
2305 *
2306 * @tparam T Subfield type
2307 * @throws std::invalid_argument if `v.length()` does not match the extension degree of this field over T
2308 *
2309 * Reads @p v as base-|T| coefficients (low-to-high). With @ref CECCO_ERASURE_SUPPORT, an
2310 * erased component in @p v produces an erased element here.
2311 */
2312 template <FiniteFieldType T>
2313 Ext(const Vector<T>& v);
2314
2315 /**
2316 * @brief Cross-field conversion from an `Iso` of the same characteristic
2317 *
2318 * Delegates to the `Ext(other.main())` overload, letting the Ext-to-Ext logic choose the
2319 * conversion path; works equally for downcasts, upcasts, and cross-tower bridges.
2320 *
2321 * @throws std::invalid_argument if no conversion path exists
2322 */
2324 Ext(const Iso<MAIN, OTHERS...>& other);
2325
2326 /**
2327 * @brief Embed a prime-field element when 𝔽_p is a (possibly indirect) subfield
2328 *
2329 * Uses the cached @ref Embedding from `Fp<p>` to this `Ext`; cannot fail for any value
2330 * permitted by `SubfieldOf<Ext, Fp<p>>`.
2331 */
2332 template <uint16_t p>
2333 constexpr Ext(const Fp<p>& other)
2334 requires SubfieldOf<Ext<B, modulus, mode>, Fp<p>> && (!std::is_same_v<B, Fp<p>>);
2335
2336 /// @brief Assign a signed element label (same semantics and range as @ref Ext(int))
2337 /// @throws std::invalid_argument if `|l| >= Q`
2338 constexpr Ext& operator=(int l);
2339
2340 constexpr Ext& operator=(const Ext& rhs) noexcept = default;
2341 Ext& operator=(Ext&& rhs) noexcept = default;
2342
2343 /// @brief Cross-field assignment from another extension (copy-and-swap; same semantics as the constructor)
2347
2348 /// @brief Embed an `Fp` element of matching characteristic (copy-and-swap)
2349 template <uint16_t p>
2351 Ext& operator=(const Fp<p>& other);
2352
2353 /// @brief Cross-field assignment from an `Iso` of matching characteristic (copy-and-swap)
2357
2358 constexpr bool operator==(const Ext& rhs) const noexcept { return label == rhs.get_label(); }
2359
2360 /// @brief Additive inverse (lvalue): returns a new element
2361 constexpr Ext operator-() const&;
2362 /// @brief Additive inverse (rvalue): in place
2363 constexpr Ext& operator-() &&;
2364
2365 /// @brief `*this += rhs` via the addition LUT
2366 constexpr Ext& operator+=(const Ext& rhs);
2367 /// @brief `*this -= rhs` via the addition LUT applied to `βˆ’rhs`
2368 constexpr Ext& operator-=(const Ext& rhs);
2369 /// @brief `*this *= rhs` via the multiplication LUT
2370 constexpr Ext& operator*=(const Ext& rhs);
2371 /// @brief Scalar multiplication by an `int` (repeated addition, reduced mod characteristic)
2372 constexpr Ext& operator*=(int s);
2373 /// @brief `*this /= rhs`; throws `std::invalid_argument` if rhs is zero
2374 Ext& operator/=(const Ext& rhs);
2375
2376 /// @brief Uniform random element in {0, …, Q βˆ’ 1}
2377 Ext& randomize();
2378 /// @brief Like @ref randomize but guaranteed to differ from the current value
2380
2381 /**
2382 * @brief Multiplicative order in the field's multiplicative group
2383 *
2384 * @throws std::invalid_argument if `*this` is zero
2385 */
2387
2388 /// @brief Additive order: 1 for zero, characteristic p otherwise
2389 size_t get_additive_order() const;
2390
2391 /**
2392 * @brief Minimal polynomial of this element over a subfield S (defaults to immediate base field B)
2393 *
2394 * @tparam S Subfield (`SubfieldOf<Ext, S>`)
2395 *
2396 * Computed from the S-conjugacy orbit { Ξ±, Ξ±^{|S|}, Ξ±^{|S|Β²}, … } as the polynomial whose
2397 * roots are exactly those conjugates. Useful for working with polynomials over an
2398 * intermediate or the prime subfield in a tower.
2399 *
2400 * @code{.cpp}
2401 * F16 alpha = F16::get_generator();
2402 * auto over_F4 = alpha.get_minimal_polynomial<F4>();
2403 * auto over_F2 = alpha.get_minimal_polynomial<F2>(); // absolute
2404 * @endcode
2405 */
2406 template <FiniteFieldType S = B>
2409
2410 /// @brief Human-readable description (size, base field, modulus)
2411 static std::string get_info();
2412
2413 static constexpr size_t get_characteristic() noexcept { return B::get_p(); }
2414 /// @brief Underlying integer label in {0, …, Q βˆ’ 1}
2415 constexpr size_t get_label() const noexcept { return label; }
2416
2417 /**
2418 * @brief True for `LutMode::CompileTime`, false for `LutMode::RunTime`
2419 *
2420 * Used by extensions further up in a tower to gate their own `CompileTime` instantiation
2421 * (a `CompileTime` field cannot be built on a `RunTime` base).
2422 */
2423 static constexpr bool is_constexpr_ready() noexcept { return mode == LutMode::CompileTime; }
2424
2425 /// @brief Modulus polynomial f(x): the irreducible used to construct this field
2426 static constexpr Polynomial<B> get_modulus();
2427
2428 /**
2429 * @brief Generator (primitive element) of the multiplicative group
2430 *
2431 * Smallest label with multiplicative order |field| βˆ’ 1; cached statically.
2432 */
2433 static Ext get_generator();
2434
2435 static constexpr size_t get_p() noexcept { return B::get_p(); }
2436 static constexpr size_t get_m() noexcept { return m; }
2437 static constexpr size_t get_q() noexcept { return Q; }
2438 static constexpr size_t get_size() noexcept { return Q; }
2439
2440 /// @brief Isomorphism mapping `Ext β†’ T` for any `Isomorphic<Ext, T>` target
2441 template <FiniteFieldType T>
2443
2444 /// @brief Print all lookup tables to `std::cout` (debugging aid)
2445 static void show_tables();
2446
2447 /// @brief Always true (finite fields are unordered)
2448 constexpr bool has_positive_sign() const noexcept { return true; }
2449 /// @brief True iff this is the additive identity
2450 constexpr bool is_zero() const noexcept { return label == 0; }
2451
2452#ifdef CECCO_ERASURE_SUPPORT
2453 /**
2454 * @brief Mark this element as erased (encoded as `label == max(label_t)`)
2455 *
2456 * @warning Erased elements must not participate in field arithmetic, see
2457 * @ref CECCO_ERASURE_SUPPORT.
2458 */
2459 constexpr Ext& erase() noexcept;
2460 /// @brief Clear the erasure flag, resetting to the additive identity
2461 constexpr Ext& unerase() noexcept;
2462 /// @brief Test whether this element is currently erased
2463 constexpr bool is_erased() const noexcept { return label == std::numeric_limits<label_t>::max(); }
2464#endif
2465
2466 /**
2467 * @brief Coordinate vector over a proper subfield T (defaults to the base field B)
2468 *
2469 * @tparam T Subfield (`SubfieldOf<Ext, T>` and `T β‰  Ext`)
2470 * @return Vector of length [Ext : T]; round-trip through the `Ext(Vector<T>)` constructor
2471 *
2472 * @code{.cpp}
2473 * F16 x(10);
2474 * Vector<F4> v4 = x.as_vector<F4>(); // length 2
2475 * Vector<F2> v2 = x.as_vector<F2>(); // length 4
2476 * F16 y = F16(v2); // round trip
2477 * @endcode
2478 */
2479 template <FiniteFieldType T = B>
2481 Vector<T> as_vector() const;
2482
2483 /**
2484 * @brief Compile-time signal that all LUTs are constructed
2485 *
2486 * Used by extensions further up in a tower to defer their own LUT computation until this
2487 * one is fully instantiated, preventing compiler recursion-depth issues. The implementation
2488 * is the constexpr `luts_ready` flag, which forces immediate evaluation of every LUT on
2489 * a `CompileTime` instantiation.
2490 *
2491 * @warning Only well-defined after all LUT declarations in the class body have been seen.
2492 */
2493 static constexpr bool ready() { return luts_ready; }
2494
2495 private:
2496 label_t label; ///< Element label in {0, 1, ..., Q - 1}
2497
2498 /// @brief Generator element storage
2499 struct Gen {
2500 label_t value{}; ///< Label of primitive element
2501 };
2502
2503 /// @brief Type alias for 1D lookup tables
2504 using Lut1D = details::Lut1D<label_t, Q>;
2505 /// @brief Type alias for 2D lookup tables
2506 using Lut2D = details::Lut2D<label_t, Q>;
2507 /// @brief Type alias for coefficient lookup tables
2508 using Lut2Dcoeff = details::Lut2Dcoeff<typename B::label_t, m, Q>;
2509
2510 public:
2511 /**
2512 * @name Precomputed Lookup Tables
2513 * @brief Compile-time generated tables for field operations
2514 * @{
2515 */
2516
2517 /// @brief Element coefficients: lut_coeff[i] = polynomial coefficients of element i
2518 static constexpr auto lambda = []() constexpr -> Lut2Dcoeff {
2520
2521 // calculate base-q representation of all labels (MSB left)
2522 for (label_t i = 0; i < Q; ++i) {
2523 lut_coeff.values[i][m - 1] = i % q;
2524 label_t t = q;
2525 for (uint8_t s = 1; s < m; ++s) {
2526 lut_coeff.values[i][m - 1 - s] = (i / t) % q;
2527 t *= q;
2528 }
2529 }
2530
2531 return lut_coeff;
2532 };
2533
2535 static constexpr auto& lut_coeff() { return LUT_COEFF::get_lut(); }
2536
2537 /// @brief Addition table: lut_add(a,b) = (polynomial_a + polynomial_b) mod f(X)
2543 static constexpr auto& lut_add() { return LUT_ADD::get_lut(); }
2544
2545 /// @brief Multiplication table: lut_mul(a,b) = (polynomial_a * polynomial_b) mod f(X)
2551 static constexpr auto& lut_mul() { return LUT_MUL::get_lut(); }
2552
2553 /// @brief Additive inverse table: lut_neg[a] = -a
2554 static constexpr Lut1D compute_neg_lut_wrapper(const Lut2D& (*provider)()) {
2555 const Lut2D& add = provider();
2557 }
2559 static constexpr auto& lut_neg() { return LUT_NEG::get_lut(); }
2560
2561 /// @brief Multiplicative inverse table: lut_inv[a] = a^(-1)
2562 static constexpr Lut1D compute_inv_lut_wrapper(const Lut2D& (*provider)()) {
2563 const Lut2D& mul = provider();
2565 }
2567 static constexpr auto& lut_inv() { return LUT_INV::get_lut(); }
2568
2569 /// @brief Multiplicative order table: lut_mul_ord[a] = order of a in multiplicative group
2570 static constexpr Lut1D compute_mul_ord_lut_wrapper(const Lut2D& (*provider)()) {
2571 const Lut2D& mul = provider();
2573 }
2575 static constexpr auto& lut_mul_ord() { return LUT_MUL_ORD::get_lut(); }
2576
2577 /// @brief Primitive element (generator) of the multiplicative group
2578 static constexpr Gen compute_generator_wrapper(const Lut1D& (*provider)()) {
2579 const Lut1D& mul_ord = provider();
2581 }
2583 static constexpr auto& g() { return LUT_GEN::get_lut(); }
2584
2585 // LUT-compatible interface for use as BaseFieldType
2586 // These allow Ext fields to be used as base fields for higher-order extensions
2587 static constexpr label_t lut_add(label_t a, label_t b) { return lut_add()(a, b); }
2588 static constexpr label_t lut_mul(label_t a, label_t b) { return lut_mul()(a, b); }
2589 static constexpr label_t lut_neg(label_t a) { return lut_neg()(a); }
2590 static constexpr label_t lut_inv(label_t a) { return lut_inv()(a); }
2591
2592 static constexpr bool luts_ready = []() constexpr {
2593 // Ensure base field LUTs are ready first
2594 static_assert(B::ready()); // Forces base field LUT computation
2595
2596 if constexpr (mode == LutMode::CompileTime) {
2597 // For CompileTime mode, force LUT computation during compilation
2598 static_assert(lut_coeff().values[0][0] == 0); // Forces immediate calculation lut_coeff
2599 static_assert(lut_add()(0, 0) == 0); // Forces immediate calculation of lut_add
2600 static_assert(lut_neg()(0) == 0); // Forces immediate calculation of lut_neg
2601 static_assert(lut_mul()(0, 1) == 0); // Forces immediate calculation of lut_mul
2602 static_assert(lut_mul_ord()(0) == 0); // Forces immediate calculation of lut_mul_ord
2603 static_assert(g().value != 1); // Forces immediate calculation of generator g
2604 }
2605 // For Runtime mode, LUTs will be computed when first accessed
2606 return true;
2607 }();
2608
2609 /** @} */
2610};
2611
2612/* member functions for Ext */
2613
2615Ext<B, modulus, mode>::Ext(int l) {
2616 const auto parsed = details::parse_signed_label(l, Q);
2617 label = static_cast<label_t>(parsed.magnitude);
2618 if (parsed.negative) label = lut_neg()(label);
2619}
2620
2622Ext<B, modulus, mode>::Ext(const B& other) {
2623#ifdef CECCO_ERASURE_SUPPORT
2624 if (other.is_erased()) {
2625 this->erase();
2626 return;
2627 }
2628#endif
2629 // Use cached subfield embedding for mathematically correct embedding
2630 auto embedding = Embedding<B, Ext>();
2633}
2634
2636template <FiniteFieldType T>
2640
2641#ifdef CECCO_ERASURE_SUPPORT
2643constexpr Ext<B, modulus, mode>& Ext<B, modulus, mode>::erase() noexcept {
2645 return *this;
2646}
2647
2649constexpr Ext<B, modulus, mode>& Ext<B, modulus, mode>::unerase() noexcept {
2650 if (is_erased()) (*this) = 0;
2651 return *this;
2652}
2653#endif
2654
2659 // Ensure same characteristic
2661 "trying to convert between fields with different characteristic");
2662
2663#ifdef CECCO_ERASURE_SUPPORT
2664 if (other.is_erased()) {
2665 this->erase();
2666 return;
2667 }
2668#endif
2669
2670 using IN = Ext<S, ext_modulus, ext_mode>;
2671 using OUT = Ext;
2672
2673 // Note: same-field case handled by simple copy constructor
2674 if constexpr (Isomorphic<OUT, IN>) {
2675 // Isomorphic fields - use isomorphism for conversion
2676 auto iso = Isomorphism<IN, OUT>();
2677 OUT result = iso(other);
2679 } else if constexpr (SubfieldOf<OUT, IN>) {
2680 // Upcast: Source βŠ† Target (ExtensionOf<Source, Target>) - cannot throw
2681 // Use cached subfield embedding for mathematically correct embedding
2682 auto embedding = Embedding<IN, OUT>();
2685 } else if constexpr (SubfieldOf<IN, OUT>) {
2686 // Downcast: Target βŠ† Source (SubfieldOf<Target, Source>) - may throw
2687 // Use cached subfield embedding to find if superfield element is in subfield
2688 auto embedding = Embedding<OUT, IN>();
2691 } else {
2692 // Fields with same characteristic but not directly related - convert via largest common subfield in order
2693 // to maximize
2695
2696 CommonField intermediate(other); // Extract to largest common subfield, throws if not possible
2697 *this = Ext(intermediate); // Embed from largest common subfield, works always
2698 }
2699}
2700
2702template <FiniteFieldType T>
2703Ext<B, modulus, mode>::Ext(const Vector<T>& v) {
2704 if constexpr (std::is_same_v<B, T>) {
2705 if (v.get_n() != m)
2706 throw std::invalid_argument(
2707 "trying to construct extension field element using base field vector of wrong length");
2708
2709#ifdef CECCO_ERASURE_SUPPORT
2710 bool erased = false;
2711 for (size_t i = 0; i < v.get_n(); ++i)
2712 if (v[i].is_erased()) {
2713 erased = true;
2714 break;
2715 }
2716 if (erased) {
2717 this->erase();
2718 return;
2719 }
2720#endif
2721 label = v.as_integer();
2722
2723 } else {
2724 static_assert(SubfieldOf<Ext<B, modulus, mode>, T>,
2725 "extension field elements can only be constructed from vectors over subfields");
2726
2727 // [B:T] is the length of one B component in T coordinates (T need not be B's immediate base)
2729
2730 if (v.get_n() != get_m() * mBT)
2731 throw std::invalid_argument(
2732 "trying to construct extension field element using subfield vector of wrong length");
2733
2734#ifdef CECCO_ERASURE_SUPPORT
2735 bool erased = false;
2736 for (size_t i = 0; i < v.get_n(); ++i)
2737 if (v[i].is_erased()) {
2738 erased = true;
2739 break;
2740 }
2741 if (erased) {
2742 this->erase();
2743 return;
2744 }
2745#endif
2746 {
2748 for (uint8_t i = 0; i < get_m(); ++i) {
2749 Vector<T> sub(mBT);
2750 for (uint8_t j = 0; j < mBT; ++j) sub.set_component(j, v[i * mBT + j]);
2752 }
2753
2754 *this = Ext(intermediate);
2755 }
2756 }
2757}
2758
2762 static_assert(Ext<B, modulus, mode>::get_characteristic() == Iso<MAIN, OTHERS...>::get_characteristic(),
2763 "trying to convert between fields with different characteristic");
2764
2765#ifdef CECCO_ERASURE_SUPPORT
2766 if (other.is_erased()) {
2767 this->erase();
2768 return;
2769 }
2770#endif
2771
2772 using IN = Iso<MAIN, OTHERS...>;
2773 using OUT = Ext;
2774
2775 // Branch 1: If IN is isomorphic to out -> implies IN is isomorphic to MAIN
2776 if constexpr (Isomorphic<IN, OUT>) {
2777 auto isomorphism = Isomorphism<MAIN, OUT>();
2780
2781 // Branch 2 (upcast): If IN is subfield of OUT
2782 } else if constexpr (SubfieldOf<OUT, IN>) {
2783 if constexpr (SubfieldOf<OUT, MAIN>) {
2784 auto embedding = Embedding<MAIN, OUT>();
2787 } else if constexpr ((SubfieldOf<OUT, OTHERS> || ...)) {
2788 auto try_embedding = [&]<typename OtherType>() {
2789 if constexpr (SubfieldOf<OUT, OtherType>) {
2790 auto embedding = Embedding<OtherType, OUT>();
2794 }
2795 };
2796 (try_embedding.template operator()<OTHERS>(), ...);
2797 }
2798
2799 // Branch 3 (downcast): If IN is superfield of OUT
2800 } else if constexpr (SubfieldOf<IN, OUT>) {
2801 if constexpr (SubfieldOf<MAIN, OUT>) {
2802 auto embedding = Embedding<OUT, MAIN>();
2805 } else if constexpr ((SubfieldOf<OTHERS, OUT> || ...)) {
2806 auto try_downcast = [&]<typename OtherType>() {
2807 if constexpr (SubfieldOf<OtherType, OUT>) {
2808 auto embedding = Embedding<OUT, OtherType>();
2812 }
2813 };
2814 (try_downcast.template operator()<OTHERS>(), ...);
2815 }
2816
2817 // Branch 4 (cross-cast through largest common subfield): This is the else case
2818 } else {
2820
2821 if constexpr (details::iso_info<CommonField>::is_iso) {
2822 // CommonField is an Iso, continue with its MAIN
2824 CommonMainField intermediate(other); // Downcast other to CommonField's MAIN
2825 *this = OUT(intermediate);
2826 } else {
2827 // CommonField is an Ext, continue with CommonField
2828 CommonField intermediate(other); // Downcast other to CommonField
2829 *this = OUT(intermediate);
2830 }
2831 }
2832}
2833
2835template <uint16_t p>
2836constexpr Ext<B, modulus, mode>::Ext(const Fp<p>& other)
2838{
2839 static_assert(Ext<B, modulus, mode>::get_characteristic() == p,
2840 "Prime field characteristic must match extension field characteristic");
2841
2842#ifdef CECCO_ERASURE_SUPPORT
2843 if (other.is_erased()) {
2844 this->erase();
2845 return;
2846 }
2847#endif
2848
2849 // Use the cached embedding for mathematically correct embedding
2850 auto embedding = Embedding<Fp<p>, Ext<B, modulus, mode>>();
2853}
2854
2856constexpr Ext<B, modulus, mode>& Ext<B, modulus, mode>::operator=(int l) {
2857 const auto parsed = details::parse_signed_label(l, Q);
2858 label = static_cast<label_t>(parsed.magnitude);
2859 if (parsed.negative) label = lut_neg()(label);
2860 return *this;
2861}
2862
2867 Ext temp(rhs);
2868 std::swap(*this, temp);
2869 return *this;
2870}
2871
2873template <uint16_t p>
2876 Ext temp(rhs);
2877 std::swap(*this, temp);
2878 return *this;
2879}
2880
2885 Ext temp(rhs);
2886 std::swap(*this, temp);
2887 return *this;
2888}
2889
2891constexpr Ext<B, modulus, mode> Ext<B, modulus, mode>::operator-() const& {
2892#ifdef CECCO_ERASURE_SUPPORT
2893 if (this->is_erased()) return Ext().erase();
2894#endif
2895 Ext res(*this);
2896 if (res.label != 0) res.label = lut_neg()(res.label);
2897 return res;
2898}
2899
2901constexpr Ext<B, modulus, mode>& Ext<B, modulus, mode>::operator-() && {
2902#ifdef CECCO_ERASURE_SUPPORT
2903 if (this->is_erased()) return this->erase();
2904#endif
2905 if (label != 0) label = lut_neg()(label);
2906 return *this;
2907}
2908
2910constexpr Ext<B, modulus, mode>& Ext<B, modulus, mode>::operator+=(const Ext& rhs) {
2911#ifdef CECCO_ERASURE_SUPPORT
2912 if (this->is_erased() || rhs.is_erased()) return this->erase();
2913#endif
2915 return *this;
2916}
2917
2919constexpr Ext<B, modulus, mode>& Ext<B, modulus, mode>::operator-=(const Ext& rhs) {
2920#ifdef CECCO_ERASURE_SUPPORT
2921 if (this->is_erased() || rhs.is_erased()) return this->erase();
2922#endif
2924 return *this;
2925}
2926
2928constexpr Ext<B, modulus, mode>& Ext<B, modulus, mode>::operator*=(const Ext& rhs) {
2929#ifdef CECCO_ERASURE_SUPPORT
2930 if (this->is_erased() || rhs.is_erased()) return this->erase();
2931#endif
2933 return *this;
2934}
2935
2937constexpr Ext<B, modulus, mode>& Ext<B, modulus, mode>::operator*=(int s) {
2938#ifdef CECCO_ERASURE_SUPPORT
2939 if (this->is_erased()) return *this;
2940#endif
2941 if constexpr (get_characteristic() != 0) s %= static_cast<int>(get_characteristic());
2942 Ext res = daa<Ext>(*this, s);
2943 *this = std::move(res);
2944 return *this;
2945}
2946
2949#ifdef CECCO_ERASURE_SUPPORT
2950 if (this->is_erased() || rhs.is_erased()) return this->erase();
2951#endif
2952 if (rhs.label == 0) throw std::invalid_argument("trying to divide by zero");
2954 return *this;
2955}
2956
2959#ifdef CECCO_ERASURE_SUPPORT
2960 this->unerase();
2961#endif
2962 thread_local std::uniform_int_distribution<size_t> dist(0, Q - 1);
2963 label = static_cast<label_t>(dist(gen()));
2964 return *this;
2965}
2966
2969#ifdef CECCO_ERASURE_SUPPORT
2970 this->unerase();
2971#endif
2972 thread_local std::uniform_int_distribution<size_t> dist(1, Q - 1);
2973 label = lut_add()(label, static_cast<label_t>(dist(gen())));
2974 return *this;
2975}
2976
2979#ifdef CECCO_ERASURE_SUPPORT
2980 if (is_erased()) throw std::invalid_argument("trying to calculate multiplicative order of erased element");
2981#endif
2982 if (label == 0) throw std::invalid_argument("trying to calculate multiplicative order of additive neutral element");
2983 return lut_mul_ord()(label);
2984}
2985
2988#ifdef CECCO_ERASURE_SUPPORT
2989 if (is_erased()) throw std::invalid_argument("trying to calculate additive order of erased element");
2990#endif
2991 if (label == 0) return 1;
2992 return get_characteristic();
2993}
2994
2996template <FiniteFieldType S>
2999{
3000#ifdef CECCO_ERASURE_SUPPORT
3001 if (is_erased()) throw std::invalid_argument("trying to compute minimal polynomial of erased element");
3002#endif
3003
3004 Polynomial<Ext> res = {1};
3005 size_t i = 0;
3006 for (;;) {
3007 const Ext beta = sqm<Ext>(*this, sqm<size_t>(S::get_size(), i));
3008 if (i > 0 && beta == *this) break;
3009 res *= Polynomial<Ext>({-beta, 1});
3010 ++i;
3011 }
3012
3013 return Polynomial<S>(res);
3014}
3015
3018 static const std::string info = [] {
3020 ss << "finite field with " + std::to_string(Q) + " elements, specified as degree " + std::to_string(m) +
3021 " extension of (" + B::get_info() + "), irreducible polynomial ";
3022 ss << get_modulus();
3023 return ss.str();
3024 }();
3025 return info;
3026}
3027
3030 Polynomial<B> rho;
3031 uint8_t i = 0;
3032 for (auto it = modulus.cbegin(); it != modulus.cend(); ++it) {
3034 ++i;
3035 }
3036 return rho;
3037}
3038
3041 static const Ext cached_generator = Ext(g().value);
3042 return cached_generator;
3043}
3044
3046template <FiniteFieldType T>
3049 if constexpr (std::is_same_v<B, T>) {
3050 Vector<T> res(m);
3051#ifdef CECCO_ERASURE_SUPPORT
3052 if (is_erased()) {
3054 std::iota(indices.begin(), indices.end(), 0);
3056 return res;
3057 }
3058#endif
3059 const auto coeffs = lut_coeff().values[label];
3060 for (uint8_t i = 0; i < m; ++i) res.set_component(i, coeffs[i]);
3061 return res;
3062 } else {
3063 static_assert(SubfieldOf<Ext<B, modulus, mode>, T>,
3064 "extension field elements can only be converted into vectors over subfields");
3065 // [B:T] is the length of one B component in T coordinates (T need not be B's immediate base)
3067 const auto intermediate = as_vector<B>(); // Explicitly call with B
3068 Vector<T> res(get_m() * mBT);
3069 for (uint8_t i = 0; i < get_m(); ++i) {
3070 auto sub = intermediate[i].template as_vector<T>();
3071 // Copy sub_vector elements into result at appropriate positions
3072 for (uint8_t j = 0; j < mBT; ++j) {
3073 res.set_component(i * mBT + j, sub[j]);
3074 }
3075 }
3076 return res;
3077 }
3078}
3079
3082 std::cout << "addition table (row and column headers omitted)" << std::endl;
3083 for (label_t i = 0; i < Q; ++i) {
3084 for (label_t j = 0; j < Q; ++j) std::cout << (int)(lut_add()(i, j)) << ", ";
3085 std::cout << std::endl;
3086 }
3087
3088 std::cout << "additive inverse table (row headers omitted)" << std::endl;
3089 for (label_t i = 0; i < Q; ++i) std::cout << (int)(lut_neg()(i)) << std::endl;
3090
3091 std::cout << "multiplication table (row and column headers omitted)" << std::endl;
3092 for (label_t i = 0; i < Q; ++i) {
3093 for (label_t j = 0; j < Q; ++j) std::cout << (int)(lut_mul()(i, j)) << ", ";
3094 std::cout << std::endl;
3095 }
3096
3097 std::cout << "multiplicative inverse table (row headers omitted)" << std::endl;
3098 for (label_t i = 0; i < Q; ++i) std::cout << (int)(lut_inv()(i)) << std::endl;
3099
3100 std::cout << "multiplicative order table (row headers omitted)" << std::endl;
3101 for (label_t i = 0; i < Q; ++i) std::cout << (int)(lut_mul_ord()(i)) << std::endl;
3102
3103 std::cout << "element coefficients table (column headers omitted)" << std::endl;
3104 for (label_t i = 0; i < Q; ++i) {
3105 std::cout << (int)i << ": ";
3106 for (uint8_t j = 0; j < m; ++j) std::cout << (int)(lut_coeff().values[i][j]) << ", ";
3107 std::cout << std::endl;
3108 }
3109
3110 std::cout << "generator (with mult. order)" << std::endl;
3111 std::cout << get_generator() << " (" << get_generator().get_multiplicative_order() << ")" << std::endl;
3112}
3113
3114/// @brief Print as the integer label (or @ref ERASURE_MARKER if erased)
3117#ifdef CECCO_ERASURE_SUPPORT
3118 if (e.is_erased()) {
3119 os << ERASURE_MARKER;
3120 return os;
3121 }
3122#endif
3123 os << (int)e.get_label();
3124 return os;
3125}
3126
3127/**
3128 * @brief Single logical field unifying several pairwise-isomorphic representations
3129 *
3130 * @tparam MAIN Primary representation; satisfies @ref CECCO::FiniteFieldType. All operations
3131 * are forwarded to it, so the `Iso` inherits its @ref CECCO::LutMode and
3132 * performance characteristics.
3133 * @tparam OTHERS Alternative representations of the same abstract field (each `Isomorphic<MAIN, OTHER>`).
3134 * Pairwise distinctness is enforced at instantiation.
3135 *
3136 * Useful for merging two construction towers that meet at the same mathematical field, e.g.
3137 * 𝔽₁₆ built once from 𝔽₂ and once from 𝔽₄. With those two `Ext` types wrapped in an `Iso`,
3138 * @ref CECCO::SubfieldOf can recognise both as containing 𝔽₂ and 𝔽₄, which makes the
3139 * cross-field constructors of @ref CECCO::Ext and @ref CECCO::Iso pick optimal paths. The
3140 * `OTHERS` representations may use different LUT modes from `MAIN`; isomorphism conversions
3141 * across representations happen at runtime via cached maps.
3142 *
3143 * @section Usage_Example
3144 *
3145 * @code{.cpp}
3146 * using F2 = Fp<2>;
3147 * using F4_a = Ext<F2, {1, 1, 1}>;
3148 * using F4_b = Ext<F2, {1, 0, 1}>;
3149 * using F4 = Iso<F4_a, F4_b>;
3150 *
3151 * F4_a a(2);
3152 * F4_b b(3);
3153 * F4 c(a); // wrap a into the unified field
3154 * c += b; // OTHERS-overload converts b into MAIN behind the scenes
3155 * F4_b d = c.as<F4_b>(); // explicit projection back to F4_b
3156 * @endcode
3157 */
3159class Iso : public details::Base {
3160 // Type safety: validate isomorphism at template instantiation
3161 static_assert((Isomorphic<MAIN, OTHERS> && ...), "All OTHERS must be isomorphic to MAIN");
3162 // Ensure we have multiple representations to unify, otherwise Iso doesn't make sense
3163 static_assert(sizeof...(OTHERS) > 0, "Iso requires at least two field representations");
3164 // Ensure all fields in the union of MAIN and OTHERS... are pairwise distinct
3165 static_assert(details::pairwise_distinct_v<MAIN, OTHERS...>,
3166 "All field representations in Iso must be pairwise distinct");
3167 // Comment: the last two assert that prime fields cannot occur in Isos
3168
3169 public:
3170 using label_t = typename MAIN::label_t;
3171 using BASE_FIELD = typename MAIN::BASE_FIELD;
3172
3173 private:
3174 MAIN main_;
3175
3176 public:
3177 /// @brief Default constructor: 0 (consistent across all representations)
3178 constexpr Iso() noexcept : main_() {}
3179
3180 /// @brief Construct from a signed element label via the MAIN representation
3181 constexpr Iso(int l) : main_(l) {}
3182
3183 /// @brief Wrap a MAIN-representation element
3184 constexpr Iso(const MAIN& other) noexcept : main_(other) {}
3185 /// @brief Wrap a MAIN-representation rvalue
3186 constexpr Iso(MAIN&& other) noexcept : main_(std::move(other)) {}
3187
3188 /// @brief Wrap an `OTHERS` element by converting to MAIN
3189 template <BelongsTo<OTHERS...> OTHER>
3190 constexpr Iso(const OTHER& other) : main_(MAIN(other)) {}
3191 /// @brief Wrap an `OTHERS` rvalue by converting to MAIN
3192 template <BelongsTo<OTHERS...> OTHER>
3193 constexpr Iso(OTHER&& other) : main_(MAIN(std::move(other))) {}
3194
3195 constexpr Iso(const Iso& other) noexcept = default;
3196 constexpr Iso(Iso&& other) noexcept = default;
3197
3198 /**
3199 * @brief Construct from a coefficient vector over a subfield T
3200 *
3201 * @tparam T Subfield type
3202 * @throws std::invalid_argument if no representation in the `Iso` can be constructed from @p v
3203 *
3204 * Tries MAIN first, then each of OTHERS until one succeeds. Round-trip correct with
3205 * @ref as_vector.
3206 */
3207 template <FiniteFieldType T>
3208 Iso(const Vector<T>& v);
3209
3210 /**
3211 * @brief Cross-field conversion from an extension field
3212 *
3213 * Delegates to `MAIN(Ext(...))` (or to an `OTHERS` representation when MAIN cannot reach
3214 * the source directly), then stores the MAIN result. All paths supported by the
3215 * `Ext`-from-`Ext` constructor are available: direct copy, isomorphism, upcast, downcast,
3216 * cross-tower bridge.
3217 *
3218 * @throws std::invalid_argument if no conversion path exists (typically a downcast where
3219 * the source value lies outside the target)
3220 */
3222 Iso(const Ext<B, modulus, mode>& other);
3223
3224 /**
3225 * @brief Embed a prime-field element when 𝔽_p is a (possibly indirect) subfield of the `Iso`
3226 *
3227 * Picks the first of MAIN / OTHERS that contains 𝔽_p and embeds via the corresponding
3228 * @ref Embedding. The constraints rule out the trivial cases where 𝔽_p already appears
3229 * literally as MAIN or one of OTHERS (those use the wrapping constructors above).
3230 */
3231 template <uint16_t p>
3232 constexpr Iso(const Fp<p>& other)
3233 requires SubfieldOf<Iso<MAIN, OTHERS...>, Fp<p>> && (!std::is_same_v<MAIN, Fp<p>>) &&
3234 (!BelongsTo<Fp<p>, OTHERS...>);
3235
3236 /**
3237 * @brief Cross-field conversion from another `Iso` of the same characteristic
3238 *
3239 * Same four-way decision as the @ref Ext cross-field constructor (direct isomorphism,
3240 * upcast, downcast, or bridge via @ref details::largest_common_subfield_t), but extended
3241 * to handle every (MAIN, OTHERS...) pairing on both sides. Each side's representation
3242 * tower is searched for the cheapest viable path.
3243 *
3244 * @throws std::invalid_argument on a downcast whose source value lies outside the target
3245 */
3247 Iso(const Iso<OTHER_MAIN, OTHER_OTHERS...>& other);
3248
3249 /// @brief Additive inverse (delegates to MAIN)
3250 constexpr Iso operator-() const { return Iso{-main_}; }
3251
3252 /// @brief `*this += other` (delegates to MAIN)
3253 constexpr Iso& operator+=(const Iso& other);
3254 /// @brief `*this += other` after converting an `OTHERS` operand to MAIN
3255 template <typename OTHER>
3256 constexpr Iso& operator+=(const OTHER& other)
3258
3259 /// @brief `*this -= other` (delegates to MAIN)
3260 constexpr Iso& operator-=(const Iso& other);
3261 /// @brief `*this -= other` after converting an `OTHERS` operand to MAIN
3262 template <typename OTHER>
3263 constexpr Iso& operator-=(const OTHER& other)
3265
3266 /// @brief `*this *= other` (delegates to MAIN)
3267 constexpr Iso& operator*=(const Iso& other);
3268 /// @brief `*this *= other` after converting an `OTHERS` operand to MAIN
3269 template <typename OTHER>
3270 constexpr Iso& operator*=(const OTHER& other)
3272
3273 /// @brief Scalar multiplication by an `int` (delegates to MAIN)
3274 constexpr Iso& operator*=(int s);
3275
3276 /// @brief `*this /= other`; throws `std::invalid_argument` if other is zero
3277 Iso& operator/=(const Iso& other);
3278 /// @brief `*this /= other` after converting an `OTHERS` operand to MAIN; same exception
3279 template <typename OTHER>
3282
3283 /**
3284 * @brief Project to a specific representation by applying the cached @ref Isomorphism
3285 *
3286 * @tparam TO Target representation, one of the `OTHERS`
3287 */
3288 template <typename TO>
3289 constexpr TO as() const
3291
3292 /// @brief Read-only access to the underlying MAIN-representation element
3293 constexpr const MAIN& main() const noexcept { return main_; }
3294
3295 constexpr bool is_zero() const noexcept { return main_.is_zero(); }
3296
3297 constexpr bool has_positive_sign() const noexcept { return main_.has_positive_sign(); }
3298
3299 constexpr Iso& randomize() {
3300 main_.randomize();
3301 return *this;
3302 }
3303
3306 return *this;
3307 }
3308
3310
3312
3313 constexpr auto get_label() const noexcept { return main_.get_label(); }
3314
3315#ifdef CECCO_ERASURE_SUPPORT
3316 /**
3317 * @brief Mark this element as erased (delegates to MAIN)
3318 *
3319 * @warning Erased elements must not participate in field arithmetic, see
3320 * @ref CECCO_ERASURE_SUPPORT.
3321 */
3322 constexpr Iso& erase() noexcept;
3323 /// @brief Clear the erasure flag, resetting MAIN to its additive identity
3324 constexpr Iso& unerase() noexcept;
3325 /// @brief Test whether this element is currently erased
3326 constexpr bool is_erased() const noexcept { return main_.is_erased(); }
3327#endif
3328
3329 constexpr Iso& operator=(const Iso& other) = default;
3330 constexpr Iso& operator=(Iso&& other) noexcept = default;
3331
3332 /// @brief Assign a MAIN-representation element directly
3333 constexpr Iso& operator=(const MAIN& other);
3334 /// @brief Assign a signed element label via MAIN
3335 constexpr Iso& operator=(int other);
3336
3337 /// @brief Assign from an `OTHERS` representation (copy-and-swap; same semantics as the constructor)
3338 template <typename OTHER>
3341
3342 /// @brief Assign from an `Fp` of matching characteristic (copy-and-swap)
3343 template <uint16_t p>
3345 Iso& operator=(const Fp<p>& other);
3346
3347 /// @brief Assign from an `Ext` of matching characteristic (copy-and-swap)
3351
3352 /// @brief Assign from another `Iso` of matching characteristic (copy-and-swap)
3357
3358 // Equality operators
3359 constexpr bool operator==(const Iso& other) const noexcept { return main_ == other.main_; }
3360
3361 constexpr bool operator==(const MAIN& other) const noexcept { return main_ == other; }
3362
3363 constexpr bool operator!=(const Iso& other) const { return main_ != other.main_; }
3364
3365 constexpr bool operator!=(const MAIN& other) const { return main_ != other; }
3366
3367 // Binary arithmetic operators handled by global template functions (like Fp and Ext)
3368
3369 // Stream operator - delegate to underlying value
3370 friend std::ostream& operator<<(std::ostream& os, const Iso& iso) { return os << iso.main_; }
3371
3372 static std::string get_info();
3373
3374 // Static methods required by FiniteFieldType concept
3375 static constexpr size_t get_characteristic() noexcept { return MAIN::get_characteristic(); }
3376
3377 static constexpr size_t get_p() noexcept { return MAIN::get_p(); }
3378
3379 static constexpr size_t get_q() noexcept { return MAIN::get_q(); }
3380
3381 static constexpr size_t get_m() noexcept { return MAIN::get_m(); }
3382
3383 static constexpr size_t get_size() noexcept { return MAIN::get_size(); }
3384
3385 static constexpr Polynomial<BASE_FIELD> get_modulus() { return MAIN::get_modulus(); }
3386
3387 static constexpr Iso get_generator() { return Iso{MAIN::get_generator()}; }
3388
3389 /// @brief Inherits constexpr-readiness from MAIN (`OTHERS` may differ; isomorphisms run at runtime)
3390 static constexpr bool is_constexpr_ready() noexcept { return MAIN::is_constexpr_ready(); }
3391
3392 // Required for Ext to use Iso as base field
3393 static constexpr bool ready() { return MAIN::ready() && (OTHERS::ready() && ...); }
3394
3395 // Simple reference forwarding to MAIN's LUTs - no copying
3396 // The ready() mechanism ensures proper ordering
3397 static constexpr auto& lut_add() { return MAIN::lut_add(); }
3398
3399 static constexpr auto& lut_neg() { return MAIN::lut_neg(); }
3400
3401 static constexpr auto& lut_mul() { return MAIN::lut_mul(); }
3402
3403 static constexpr auto& lut_inv() { return MAIN::lut_inv(); }
3404
3405 // LUT-compatible interface for use as BaseFieldType
3406 // These allow Iso fields to be used as base fields for higher-order extensions
3407 static constexpr label_t lut_add(label_t a, label_t b) { return lut_add()(a, b); }
3408
3409 static constexpr label_t lut_mul(label_t a, label_t b) { return lut_mul()(a, b); }
3410
3411 static constexpr label_t lut_neg(label_t a) { return lut_neg()(a); }
3412
3413 static constexpr label_t lut_inv(label_t a) { return lut_inv()(a); }
3414
3415 /**
3416 * @brief Coordinate vector over a subfield T of MAIN or any `OTHERS` representation
3417 *
3418 * @tparam T Subfield reachable from MAIN or one of `OTHERS`
3419 */
3420 template <FiniteFieldType T>
3421 Vector<T> as_vector() const
3422 requires((SubfieldOf<MAIN, T> || ((SubfieldOf<OTHERS, T>) || ...))) &&
3423 (!std::is_same_v<Iso<MAIN, OTHERS...>, T>);
3424};
3425
3426/* member functions for Iso */
3427
3429constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator=(const MAIN& other) {
3430 main_ = other;
3431 return *this;
3432}
3433
3435constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator=(int other) {
3436 main_ = other;
3437 return *this;
3438}
3439
3441template <typename OTHER>
3444{
3445 Iso temp(other);
3446 std::swap(*this, temp);
3447 return *this;
3448}
3449
3451template <uint16_t p>
3452 requires(p == MAIN::get_characteristic())
3453Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator=(const Fp<p>& other) {
3454 Iso temp(other);
3455 std::swap(*this, temp);
3456 return *this;
3457}
3458
3463 Iso temp(other);
3464 std::swap(*this, temp);
3465 return *this;
3466}
3467
3473{
3474 Iso temp(other);
3475 std::swap(*this, temp);
3476 return *this;
3477}
3478
3482 static_assert(MAIN::get_characteristic() == Ext<B, modulus, mode>::get_characteristic(),
3483 "trying to convert between fields with different characteristic");
3484
3485#ifdef CECCO_ERASURE_SUPPORT
3486 if (other.is_erased()) {
3487 this->erase();
3488 return;
3489 }
3490#endif
3491
3492 using IN = Ext<B, modulus, mode>;
3493 using OUT = Iso;
3494
3495 // Branch 1: If MAIN or any of the OTHERS is isomorphic to Ext then use the correct isomorphism
3496 if constexpr (Isomorphic<MAIN, IN>) {
3497 auto isomorphism = Isomorphism<IN, MAIN>();
3499
3500 // Branch 2 (upcast): If IN is a subfield to OUT
3501 } else if constexpr (SubfieldOf<OUT, IN>) {
3502 if constexpr (SubfieldOf<MAIN, IN>) {
3503 // Ext βŠ† MAIN - use cached embedding from Ext to MAIN
3504 auto embedding = Embedding<IN, MAIN>();
3506 } else if constexpr ((SubfieldOf<OTHERS, IN> || ...)) {
3507 // Check which OTHERS type contains ExtType as subfield
3508 auto try_embedding = [&]<typename OtherType>() {
3509 if constexpr (SubfieldOf<OtherType, IN>) {
3510 auto embedding = Embedding<IN, OtherType>();
3514 }
3515 };
3516 (try_embedding.template operator()<OTHERS>(), ...);
3517 }
3518 // Branch 3 (downcast): If Iso or MAIN or any of the OTHERS is a subfield of Ext
3519 } else if constexpr (SubfieldOf<IN, OUT>) {
3520 if constexpr (SubfieldOf<IN, MAIN>) {
3521 auto embedding = Embedding<MAIN, IN>();
3523 } else if constexpr ((SubfieldOf<OTHERS, IN> || ...)) {
3524 auto try_downcast = [&]<typename OtherType>() {
3525 if constexpr (SubfieldOf<OtherType, IN>) {
3526 auto embedding = Embedding<IN, OtherType>();
3530 }
3531 };
3532 (try_downcast.template operator()<OTHERS>(), ...);
3533 }
3534 } else {
3535 // Branch 4 (cross-cast through largest common subfield): This is the else case
3537
3538 if constexpr (details::iso_info<CommonField>::is_iso) {
3539 // CommonField is an Iso, continue with its MAIN
3541 CommonMainField intermediate(other); // Downcast other to CommonField's MAIN
3543 } else {
3544 // CommonField is an Ext, continue with CommonField
3545 CommonField intermediate(other); // Downcast other to CommonField
3547 }
3548 }
3549}
3550
3552template <uint16_t p>
3553constexpr Iso<MAIN, OTHERS...>::Iso(const Fp<p>& other)
3554 requires SubfieldOf<Iso<MAIN, OTHERS...>, Fp<p>> && (!std::is_same_v<MAIN, Fp<p>>) && (!BelongsTo<Fp<p>, OTHERS...>)
3555{
3556 static_assert(MAIN::get_characteristic() == p, "Prime field characteristic must match Iso field characteristic");
3557
3558#ifdef CECCO_ERASURE_SUPPORT
3559 if (other.is_erased()) {
3560 this->erase();
3561 return;
3562 }
3563#endif
3564
3565 // Try direct embedding into MAIN first
3566 if constexpr (SubfieldOf<MAIN, Fp<p>>) {
3567 main_ = MAIN(other);
3568 } else {
3569 // Try embedding via any OTHERS representation that contains Fp<p>
3570 bool conversion_done = false;
3571 auto try_others_embedding = [&]<typename OtherType>() {
3572 if constexpr (SubfieldOf<OtherType, Fp<p>>) {
3573 if (!conversion_done) {
3574 // Embed Fp<p> -> OtherType, then convert OtherType -> MAIN via isomorphism
3578 conversion_done = true;
3579 }
3580 }
3581 };
3582 (try_others_embedding.template operator()<OTHERS>(), ...);
3583 }
3584}
3585
3590 "trying to convert between fields with different characteristic");
3591
3592#ifdef CECCO_ERASURE_SUPPORT
3593 if (other.is_erased()) {
3594 this->erase();
3595 return;
3596 }
3597#endif
3598
3599 using IN = Iso<OTHER_MAIN, OTHER_OTHERS...>;
3600 using OUT = Iso;
3601
3602 // Branch 1: IN and OUT are isomorphic (implies: MAIN and OTHER_MAIN are isomorphic)
3603 if constexpr (Isomorphic<OTHER_MAIN, MAIN>) {
3606
3607 // Branch 2 (upcast): IN is subfield of OUT (but not the same since this would be caught by copy constr.)
3608 } else if constexpr (SubfieldOf<OUT, IN>) {
3609 // Sub-branch 2a: OTHER_MAIN is subfield of MAIN
3610 if constexpr (SubfieldOf<MAIN, OTHER_MAIN>) {
3611 main_ = MAIN(other.main());
3612
3613 // Sub-branch 2b: OTHER_MAIN is subfield of one of OTHERS
3614 } else if constexpr ((SubfieldOf<OTHERS, OTHER_MAIN> || ...)) {
3615 bool conversion_done = false;
3616 auto try_main_to_others = [&]<typename OutputOtherType>() {
3617 if constexpr (SubfieldOf<OutputOtherType, OTHER_MAIN>) {
3618 if (!conversion_done) {
3622 conversion_done = true;
3623 }
3624 }
3625 };
3626 (try_main_to_others.template operator()<OTHERS>(), ...);
3627
3628 // Sub-branch 2c: One of OTHER_OTHERS is subfield of MAIN
3629 } else if constexpr ((SubfieldOf<MAIN, OTHER_OTHERS> || ...)) {
3630 bool conversion_done = false;
3631 auto try_others_to_main = [&]<typename InputOtherType>() {
3632 if constexpr (SubfieldOf<MAIN, InputOtherType>) {
3633 if (!conversion_done) {
3636 conversion_done = true;
3637 }
3638 }
3639 };
3640 (try_others_to_main.template operator()<OTHER_OTHERS>(), ...);
3641
3642 // Sub-branch 2d: One of OTHER_OTHERS is subfield of one of OTHERS
3643 } else {
3644 bool conversion_done = false;
3645 auto try_others_to_others = [&]<typename OutputOtherType>() {
3646 if (conversion_done) return;
3647 auto try_input_others = [&]<typename InputOtherType>() {
3648 if constexpr (SubfieldOf<OutputOtherType, InputOtherType>) {
3649 if (!conversion_done) {
3654 conversion_done = true;
3655 }
3656 }
3657 };
3658 (try_input_others.template operator()<OTHER_OTHERS>(), ...);
3659 };
3660 (try_others_to_others.template operator()<OTHERS>(), ...);
3661 }
3662
3663 // Branch 3 (downcast): OUT is subfield of IN (but not the same since this would be caught by copy constr.)
3664 } else if constexpr (SubfieldOf<IN, OUT>) {
3665 // Sub-branch 3a: OTHER_MAIN is superfield of MAIN
3666 if constexpr (SubfieldOf<OTHER_MAIN, MAIN>) {
3667 main_ = MAIN(other.main());
3668
3669 // Sub-branch 3b: OTHER_MAIN is superfield of one of OTHERS
3670 } else if constexpr ((SubfieldOf<OTHER_MAIN, OTHERS> || ...)) {
3671 bool conversion_done = false;
3672 auto try_main_to_others = [&]<typename OutputOtherType>() {
3673 if constexpr (SubfieldOf<OTHER_MAIN, OutputOtherType>) {
3674 if (!conversion_done) {
3678 conversion_done = true;
3679 }
3680 }
3681 };
3682 (try_main_to_others.template operator()<OTHERS>(), ...);
3683
3684 // Sub-branch 3c: One of OTHER_OTHERS is superfield of MAIN
3685 } else if constexpr ((SubfieldOf<OTHER_OTHERS, MAIN> || ...)) {
3686 bool conversion_done = false;
3687 auto try_others_to_main = [&]<typename InputOtherType>() {
3688 if constexpr (SubfieldOf<InputOtherType, MAIN>) {
3689 if (!conversion_done) {
3692 conversion_done = true;
3693 }
3694 }
3695 };
3696 (try_others_to_main.template operator()<OTHER_OTHERS>(), ...);
3697
3698 // Sub-branch 3d: One of OTHER_OTHERS is superfield of one of OTHERS
3699 } else {
3700 bool conversion_done = false;
3701 auto try_others_to_others = [&]<typename OutputOtherType>() {
3702 if (conversion_done) return;
3703 auto try_input_others = [&]<typename InputOtherType>() {
3704 if constexpr (SubfieldOf<InputOtherType, OutputOtherType>) {
3705 if (!conversion_done) {
3710 conversion_done = true;
3711 }
3712 }
3713 };
3714 (try_input_others.template operator()<OTHER_OTHERS>(), ...);
3715 };
3716 (try_others_to_others.template operator()<OTHERS>(), ...);
3717 }
3718
3719 } else {
3720 // Branch 4 (cross-cast through largest common subfield): Consistent with Ext-from-Iso constructor
3722
3724 }
3725}
3726
3728template <FiniteFieldType T>
3729Iso<MAIN, OTHERS...>::Iso(const Vector<T>& v) {
3730 bool conversion_done = false;
3731
3732 // Branch 1: Try MAIN directly (compile-time check)
3733 if constexpr (SubfieldOf<MAIN, T>) {
3734 main_ = MAIN(v);
3735 conversion_done = true;
3736 }
3737
3738 // Branch 2: Try compatible OTHERS
3739 if (!conversion_done && sizeof...(OTHERS) > 0) {
3740 auto try_others = [&]<typename OtherType>() {
3741 if constexpr (SubfieldOf<OtherType, T>) {
3742 if (!conversion_done) {
3746 conversion_done = true;
3747 }
3748 }
3749 };
3750 (try_others.template operator()<OTHERS>(), ...);
3751 }
3752
3753 // Branch 3: Error case
3754 if (!conversion_done) throw std::invalid_argument("Vector incompatible with Iso stack");
3755}
3756
3758constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator+=(const Iso& other) {
3759 main_ += other.main_;
3760 return *this;
3761}
3762
3764template <typename OTHER>
3765constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator+=(const OTHER& other)
3767{
3768 main_ += Iso(other).main_;
3769 return *this;
3770}
3771
3773constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator-=(const Iso& other) {
3774 main_ -= other.main_;
3775 return *this;
3776}
3777
3779template <typename OTHER>
3780constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator-=(const OTHER& other)
3782{
3783 main_ -= Iso(other).main_;
3784 return *this;
3785}
3786
3788constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator*=(const Iso& other) {
3789 main_ *= other.main_;
3790 return *this;
3791}
3792
3794template <typename OTHER>
3795constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator*=(const OTHER& other)
3797{
3798 main_ *= Iso(other).main_;
3799 return *this;
3800}
3801
3803constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator*=(int s) {
3804 main_ *= s;
3805 return *this;
3806}
3807
3809Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::operator/=(const Iso& other) {
3810 main_ /= other.main_;
3811 return *this;
3812}
3813
3815template <typename OTHER>
3818{
3819 main_ /= Iso(other).main_;
3820 return *this;
3821}
3822
3824template <typename TO>
3825constexpr TO Iso<MAIN, OTHERS...>::as() const
3827{
3828#ifdef CECCO_ERASURE_SUPPORT
3829 if (this->is_erased()) return TO().erase();
3830#endif
3831
3832 auto phi = Isomorphism<MAIN, TO>();
3833 return phi(main_);
3834}
3835
3838 static const std::string info = "stack of isomorphic fields, main field: " + MAIN::get_info();
3839 return info;
3840}
3841
3843template <FiniteFieldType T>
3845 requires((SubfieldOf<MAIN, T> || ((SubfieldOf<OTHERS, T>) || ...))) && (!std::is_same_v<Iso<MAIN, OTHERS...>, T>)
3846{
3847 if constexpr (std::is_same_v<T, MAIN>) {
3848 return Vector<T>(1, main_); // one-dimensional over itself
3849 } else if constexpr (((std::is_same_v<T, OTHERS>) || ...)) {
3850 return Vector<T>(1, as<T>()); // one-dimensional over itself
3851 } else if constexpr (SubfieldOf<MAIN, T>) {
3852 // T is subfield of MAIN - use main_ directly
3853 return main_.template as_vector<T>();
3854 } else {
3855 // T might be subfield of one of OTHERS - try each one
3856 bool converted = false;
3857 Vector<T> result;
3858 (([&]() {
3859 if constexpr (SubfieldOf<OTHERS, T>) {
3860 if (!converted) {
3861 result = as<OTHERS>().template as_vector<T>();
3862 converted = true;
3863 }
3864 }
3865 }()),
3866 ...);
3867
3868 return result;
3869 }
3870}
3871
3872#ifdef CECCO_ERASURE_SUPPORT
3874constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::erase() noexcept {
3875 main_.erase();
3876 return *this;
3877}
3878
3880constexpr Iso<MAIN, OTHERS...>& Iso<MAIN, OTHERS...>::unerase() noexcept {
3881 main_.unerase();
3882 return *this;
3883}
3884#endif
3885
3886} // namespace CECCO
3887
3888#endif
Functor representing the field embedding Ο†: SUBFIELD β†’ SUPERFIELD, with reverse lookup.
Definition fields.hpp:1322
constexpr SUPERFIELD operator()(const SUBFIELD &sub) const
Apply Ο†: SUBFIELD β†’ SUPERFIELD.
Definition fields.hpp:1328
Embedding()
Constructs the embedding (cached on first instantiation per template arguments).
Definition fields.hpp:1358
constexpr SUBFIELD extract(const SUPERFIELD &super) const
Reverse Ο†: find the unique s ∈ SUBFIELD with Ο†(s) == super.
Definition fields.hpp:1365
Extension field 𝔽_{q^m} β‰… B[x]/(f(x)), constructed from a base field and an irreducible monic modulus...
Definition fields.hpp:2244
Prime field 𝔽_p β‰… β„€/pβ„€
Definition fields.hpp:1663
constexpr Fp() noexcept
Default constructor: 0.
Definition fields.hpp:1670
Fp & randomize_force_change()
Like randomize but guaranteed to differ from the current value.
Definition fields.hpp:2116
constexpr Fp(const Fp &other) noexcept=default
static std::string get_info()
Human-readable description: "prime field with p elements".
Definition fields.hpp:1761
static constexpr size_t get_characteristic() noexcept
Definition fields.hpp:1766
size_t get_additive_order() const
Additive order: 1 for zero, p otherwise.
Definition fields.hpp:2147
constexpr Fp(int l)
Construct from a signed element label l ∈ {βˆ’(p βˆ’ 1), …, p βˆ’ 1}.
Definition fields.hpp:1901
Fp & operator=(const Ext< S, ext_modulus, mode > &other)
Project an extension-field element to 𝔽_p (copy-and-swap; same semantics as the constructor).
constexpr Fp operator-() const &noexcept
Additive inverse (lvalue): returns a new element with -label mod p.
Definition fields.hpp:1988
constexpr Fp & operator-() &&noexcept
Additive inverse (rvalue): in place.
Definition fields.hpp:2005
static constexpr size_t get_q() noexcept
Definition fields.hpp:1776
constexpr Fp & operator=(Fp &&rhs) noexcept=default
static void show_tables()
Print all lookup tables to std::cout (debugging aid).
Definition fields.hpp:2156
Fp & randomize()
Uniform random element in {0, …, p βˆ’ 1}.
Definition fields.hpp:2098
static constexpr size_t get_size() noexcept
Definition fields.hpp:1777
constexpr Fp & operator+=(const Fp &rhs) noexcept
*this = (label + rhs.label) mod p
Definition fields.hpp:2020
constexpr bool operator==(const Fp &rhs) const noexcept
Definition fields.hpp:1727
constexpr bool is_zero() const noexcept
True iff this is the additive identity.
Definition fields.hpp:1793
static constexpr Fp get_generator() noexcept
Generator (primitive root) of 𝔽_p*; precomputed label is Gen.
Definition fields.hpp:1772
constexpr Fp & operator=(const Fp &rhs) noexcept=default
static constexpr size_t get_m() noexcept
Definition fields.hpp:1775
constexpr Fp & operator=(int l)
Assign a signed element label (same semantics and range as Fp(int)).
Definition fields.hpp:1963
size_t get_multiplicative_order() const
Multiplicative order in 𝔽_p*.
Definition fields.hpp:2134
static constexpr label_t Gen
Primitive element (generator) of F_p*.
Definition fields.hpp:1853
Fp(const Iso< MAIN, OTHERS... > &other)
Extract a prime-field element from an Iso containing this prime subfield.
Definition fields.hpp:1930
Fp & operator/=(const Fp &rhs)
*this = (label · rhs.label⁻¹) mod p; throws std::invalid_argument if rhs is zero
Definition fields.hpp:2084
static constexpr bool is_constexpr_ready() noexcept
Always true for prime fields.
Definition fields.hpp:1785
constexpr Fp & operator*=(const Fp &rhs) noexcept
*this = (label Β· rhs.label) mod p
Definition fields.hpp:2054
Fp(const Ext< S, ext_modulus, mode > &other)
Extract a prime-field element from an extension field (downcast).
Definition fields.hpp:1909
constexpr bool has_positive_sign() const noexcept
Always true (finite fields are unordered).
Definition fields.hpp:1791
constexpr Fp & operator*=(int s) noexcept
*this = (label Β· s) mod p (repeated addition)
Definition fields.hpp:2073
constexpr Fp & operator-=(const Fp &rhs) noexcept
*this = (label βˆ’ rhs.label) mod p
Definition fields.hpp:2037
static constexpr size_t get_p() noexcept
Definition fields.hpp:1774
constexpr Fp(Fp &&other) noexcept=default
static constexpr bool ready()
Compile-time signal that all LUTs are constructed.
Definition fields.hpp:1815
~Fp() noexcept=default
constexpr size_t get_label() const noexcept
Underlying integer label in {0, …, p βˆ’ 1}.
Definition fields.hpp:1769
Single logical field unifying several pairwise-isomorphic representations.
Definition fields.hpp:3159
Dense m Γ— n matrix over a CECCO::ComponentType.
Definition matrices.hpp:175
Univariate polynomial p(x) = aβ‚€ + a₁x + … + aβ‚™xⁿ over a CECCO::CoefficientType.
Field of rational numbers β„š = { p/q : p, q ∈ β„€, q β‰  0 } with selectable precision.
Definition fields.hpp:470
static constexpr size_t get_characteristic() noexcept
Characteristic of β„š: 0.
Definition fields.hpp:535
constexpr bool operator==(const Rationals< T > &rhs) const
Cross-multiplication equality.
Definition fields.hpp:491
constexpr bool has_positive_sign() const noexcept
Sign predicate: true iff the numerator is non-negative (always false for erased elements).
Definition fields.hpp:538
constexpr Rationals & operator=(int l)
Assign the integer l as l / 1.
Definition fields.hpp:579
Rationals(int n=0, int d=1)
Construct n / d, simplified to lowest terms with positive denominator.
Definition fields.hpp:573
constexpr Rationals & operator*=(const Rationals &rhs)
*this *= rhs, result kept in lowest terms
Definition fields.hpp:639
Rationals & randomize_force_change()
Like randomize but guaranteed to differ from the current value.
Definition fields.hpp:680
size_t get_multiplicative_order() const
Multiplicative order.
Definition fields.hpp:700
constexpr Rationals operator-() const &
Additive inverse (lvalue overload returns a copy with negated numerator).
Definition fields.hpp:594
constexpr Rationals & operator-=(const Rationals &rhs)
*this -= rhs, result kept in lowest terms
Definition fields.hpp:626
Rationals & randomize()
Random rational (bounded numerator and denominator), simplified.
Definition fields.hpp:666
Rationals(Rationals &&other)=default
constexpr const T & get_denominator() const noexcept
Denominator (always positive after simplification).
Definition fields.hpp:560
static std::string get_info()
Human-readable description: "rational number".
Definition fields.hpp:529
Rationals & operator/=(const Rationals &rhs)
*this /= rhs; throws std::invalid_argument if rhs is zero
Definition fields.hpp:652
constexpr const T & get_numerator() const noexcept
Numerator (sign carrier).
Definition fields.hpp:558
Rationals(const Rationals &other)=default
Rationals & operator=(Rationals &&rhs)
Definition fields.hpp:586
constexpr Rationals & operator-() &&
Additive inverse (rvalue overload negates in place).
Definition fields.hpp:604
constexpr Rationals & operator=(const Rationals &rhs)=default
constexpr Rationals & operator+=(const Rationals &rhs)
*this += rhs, result kept in lowest terms
Definition fields.hpp:613
constexpr bool is_zero() const noexcept
True iff numerator is zero.
Definition fields.hpp:541
size_t get_additive_order() const noexcept
Additive order: 1 for zero, 0 (infinite) otherwise (characteristic 0).
Definition fields.hpp:713
Vector v = (vβ‚€, v₁, …, vₙ₋₁) over a CECCO::ComponentType.
Definition vectors.hpp:124
Tag base for the CRTP-protection idiom.
Definition fields.hpp:180
CRTP base documenting the interface every field type must provide.
Definition fields.hpp:205
T & operator=(T &&rhs) noexcept=delete
Move assignment (derived must implement).
T & operator+=(const T &rhs) noexcept=delete
*this += rhs (derived must implement)
T & operator-=(const T &rhs) noexcept=delete
*this -= rhs (derived must implement)
T & operator*=(const T &rhs) noexcept=delete
*this *= rhs (derived must implement)
bool is_zero() const noexcept=delete
True if *this is the additive identity.
T & operator/=(const T &rhs)=delete
*this /= rhs (derived must implement), throws std::invalid_argument if rhs is zero
size_t get_multiplicative_order() const =delete
Smallest k > 0 with this^k == 1; throws std::invalid_argument if *this is zero.
Field & randomize_force_change()=delete
Like randomize but guaranteed to differ from the current value (derived must implement).
~Field() noexcept=default
T & operator=(int l)=delete
Assign from int (derived must implement).
constexpr T operator+() const &
Unary + on an lvalue: returns a copy.
Definition fields.hpp:232
constexpr T && operator+() &&noexcept
Unary + on an rvalue: returns the rvalue itself.
Definition fields.hpp:234
Field & randomize()=delete
Uniform random element of the field (derived must implement), may return the same value.
size_t get_additive_order() const =delete
Smallest k > 0 with k * *this == 0; for finite fields of characteristic p this is p (or 1 for zero); ...
T & operator-() &&noexcept=delete
In-place additive inverse on an rvalue (derived must implement).
T & operator=(const T &rhs) noexcept=delete
Copy assignment (derived must implement).
constexpr bool operator!=(const T &rhs) const
Inequality, defined as !(*this == rhs).
Definition fields.hpp:225
T operator-() const &noexcept=delete
Additive inverse on an lvalue (derived must implement).
bool has_positive_sign() const noexcept=delete
True if the element is "positive" (always true for finite fields; sign of numerator for β„š).
#define MOD
Alias for std::array, used to spell modulus polynomials in CECCO::Ext.
#define CECCO_COMPRESS_LUTS_FROM_Q
Compression threshold for 2D lookup tables in extension fields.
Definition fields.hpp:126
Contains implementation details not to be exposed to the user. Functions and classes here may change ...
Definition blocks.hpp:59
constexpr auto compute_multiplicative_orders(const Lut2D< LabelType, FieldSize > &mul_lut)
Build the table of multiplicative orders for all elements of 𝔽_Q.
Definition fields.hpp:890
constexpr SignedLabel parse_signed_label(int l, size_t field_size)
Definition fields.hpp:788
constexpr auto compute_multiplicative_inverses_direct()
Build multiplicative-inverse table for 𝔽_p via extended Euclidean algorithm.
Definition fields.hpp:914
static size_t constexpr integer_from_coeffs(const std::array< size_t, SIZE > &coeffs) noexcept
Encode polynomial coefficients (low-to-high) as a base-q integer label.
Definition fields.hpp:804
Provides a framework for error correcting codes.
Definition blocks.hpp:57
constexpr T operator*(int lhs, const T &rhs)
Definition fields.hpp:397
constexpr T operator-(T &&lhs, const T &rhs)
Definition fields.hpp:341
constexpr T operator+(T &&lhs, const T &rhs)
Definition fields.hpp:313
constexpr T operator*(const T &lhs, T &&rhs)
Definition fields.hpp:376
constexpr T operator*(T &&lhs, T &&rhs)
Definition fields.hpp:383
T operator/(const T &lhs, const T &rhs)
Definition fields.hpp:418
constexpr T operator+(const T &lhs, T &&rhs)
Definition fields.hpp:320
constexpr T operator+(const T &lhs, const T &rhs)
Definition fields.hpp:306
constexpr T operator*(const T &lhs, const T &rhs)
Definition fields.hpp:362
constexpr T operator-(const T &lhs, const T &rhs)
Definition fields.hpp:334
constexpr T operator+(T &&lhs, T &&rhs)
Definition fields.hpp:327
constexpr T operator*(const T &lhs, int rhs)
Definition fields.hpp:390
constexpr T operator*(int lhs, T &&rhs)
Definition fields.hpp:411
constexpr T operator^(const T &base, int exponent)
Definition fields.hpp:432
constexpr T operator^(T &&base, int exponent)
Definition fields.hpp:437
constexpr T operator-(const T &lhs, T &&rhs)
Definition fields.hpp:348
T operator/(T &&lhs, const T &rhs)
Definition fields.hpp:425
constexpr T operator-(T &&lhs, T &&rhs)
Definition fields.hpp:355
LutMode
LUT generation mode for field operations.
constexpr T operator*(T &&lhs, const T &rhs)
Definition fields.hpp:369
constexpr T operator*(T &&lhs, int rhs)
Definition fields.hpp:404
1D lookup table for unary field operations (negation, inversion, order)
Definition fields.hpp:822
std::array< LabelType, FieldSize > values
Definition fields.hpp:824
constexpr LabelType operator()(size_t i) const noexcept
Definition fields.hpp:823
2D lookup table for commutative binary operations, optionally compressed
Definition fields.hpp:838
constexpr LabelType operator()(size_t i, size_t j) const noexcept
Const access (used during field operations).
Definition fields.hpp:854
constexpr LabelType & operator()(size_t i, size_t j)
Mutable access (used during table construction).
Definition fields.hpp:840
std::array< std::array< LabelType, FieldSize >,(FieldSize< CECCO_COMPRESS_LUTS_FROM_Q ? FieldSize :FieldSize/2+1)> values
Backing storage; full square below the compression threshold, upper triangle above.
Definition fields.hpp:870
Lookup table mapping each extension-field label to its base-field coefficient vector.
Definition fields.hpp:881
std::array< std::array< LabelType, ExtensionDegree >, FieldSize > values
Definition fields.hpp:882
CompileTime specialisation: table is a constexpr static member.
Definition fields.hpp:1217
Holds a LUT generated by F(P); P provides a dependency table.
Definition fields.hpp:1250