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