alkahest-cas 3.12.0

High-performance computer algebra kernel: symbolic expressions, polynomials, Gröbner bases, JIT, and Arb ball arithmetic.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
//! [`MatGroupError`] — the refusals of the matrix-group layer.
//!
//! # Why a new enum
//!
//! Every enum re-exported from `alkahest_cas::stable` is exhaustive and public,
//! so adding a variant to one is a semver-major change that `cargo
//! semver-checks` reports as `enum_variant_added`. The matrix-group layer sits
//! on top of two existing subsystems — [`FiniteFieldError`] (`E-GFQ-*`) for the
//! GF(q) linear algebra and [`GroupError`] (`E-GRP-*`) for the permutation
//! actions — and rather than widen either, it carries them through **delegating
//! variants** that forward `code()`, `remediation()` and `Display` to the error
//! they wrap. A caller who sees `E-GFQ-009` (singular matrix) underneath a
//! matrix-group call gets the code the GF(q) layer raised, not a relabelled one.
//!
//! This follows the precedent set by
//! [`LatticeGeometryError::Reduction`](crate::lattice::LatticeGeometryError) and
//! [`ArithmeticError::Input`](crate::number_theory::arith::ArithmeticError).
//!
//! This enum is `#[non_exhaustive]` from birth so that later refusals do not
//! need another break.
//!
//! # Codes
//!
//! `E-MATGRP-001` … `E-MATGRP-013` are this enum's own. `E-GFQ-*` and
//! `E-GRP-*` reach a caller unchanged through
//! [`MatGroupError::FiniteField`] and [`MatGroupError::Permutation`].

use crate::errors::AlkahestError;
use crate::ffield::FiniteFieldError;
use crate::group::GroupError;
use std::fmt;

/// Refusals from the matrix-group layer.
///
/// `#[non_exhaustive]`: match with a `_` arm. New variants will be added.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MatGroupError {
    /// A GF(q) linear-algebra operation refused. Carries its `E-GFQ-*` code
    /// unchanged.
    FiniteField(FiniteFieldError),
    /// A permutation-group operation refused — reached only from the induced
    /// actions. Carries its `E-GRP-*` code unchanged.
    Permutation(GroupError),
    /// A group element has to be square.
    NotSquare {
        /// Rows of the offending matrix.
        rows: usize,
        /// Columns of the offending matrix.
        cols: usize,
    },
    /// A matrix or vector does not have the group's degree.
    ///
    /// Degrees are never padded: `GL(2, q)` and `GL(3, q)` act on different
    /// spaces and which one a caller meant is not recoverable from the
    /// argument.
    DegreeMismatch {
        /// The degree this group acts in.
        expected: usize,
        /// The degree that was supplied.
        got: usize,
    },
    /// A matrix is over a different finite field.
    ///
    /// GF(2³) built from `x³+x+1` and GF(2³) built from `x³+x²+1` are
    /// isomorphic and are *different fields* here, because an element's
    /// coordinates mean different things in each — see [`crate::ffield`].
    FieldMismatch {
        /// The group's field.
        expected: String,
        /// The supplied matrix's field.
        got: String,
    },
    /// A proposed generator is singular, so it generates no group.
    SingularGenerator {
        /// Position in the supplied generator list.
        index: usize,
    },
    /// The degree exceeds [`MAX_MATGROUP_DEGREE`](super::MAX_MATGROUP_DEGREE).
    DegreeTooLarge {
        /// The degree asked for.
        degree: usize,
        /// The largest degree this implementation accepts.
        max: usize,
    },
    /// An orbit outgrew [`MAX_MATGROUP_ORBIT`](super::MAX_MATGROUP_ORBIT).
    ///
    /// Each stabilizer-chain level stores one transversal matrix (and its
    /// inverse) per orbit point, so the memory is `O(Σ_i |Δ_i| · d²)`.
    OrbitTooLarge {
        /// How many points had been found when the cap was hit.
        points: usize,
        /// The cap in force.
        max: usize,
    },
    /// Schreier–Sims ran past its work budget without finishing.
    ///
    /// This is a **refusal, not a partial chain**: an incomplete chain reports
    /// a group order that is a proper divisor of the true one, which is exactly
    /// the confident wrong answer this crate refuses to produce.
    WorkBudgetExhausted {
        /// The budget in force, in elementary matrix operations.
        budget: u64,
    },
    /// The group is too large to enumerate element by element.
    ///
    /// The order is still exact; it is the *list* that is refused.
    EnumerationTooLarge {
        /// Decimal rendering of `|G|` (it may not fit a `u64`).
        order: String,
        /// The cap in force.
        cap: u64,
    },
    /// An operation needs to enumerate GF(q) itself and `q` is past
    /// [`MAX_MATGROUP_FIELD_ORDER`](super::MAX_MATGROUP_FIELD_ORDER).
    FieldTooLarge {
        /// Decimal rendering of `q`.
        order: String,
        /// The cap in force.
        max: u64,
    },
    /// A constructor was asked for a group it does not build.
    UnsupportedConstruction {
        /// Which constructor: `"GL"`, `"Sp"`, `"Singer"`, …
        family: &'static str,
        /// Why this `(n, q)` has no construction here.
        reason: &'static str,
    },
    /// The centralizing algebra is too large to enumerate, so the centre
    /// cannot be read off it.
    CommutantTooLarge {
        /// `dim_{GF(q)}` of the algebra of matrices commuting with every
        /// generator.
        dimension: usize,
        /// Decimal rendering of `q^dimension`.
        elements: String,
        /// The cap in force.
        cap: u64,
    },
    /// The zero vector has no projective point and no canonical scaling.
    ZeroVector,
    /// An internal invariant did not hold.
    ///
    /// A typed refusal rather than a panic: these functions run under a PyO3
    /// boundary, where a panic arrives as `pyo3_runtime.PanicException`, a
    /// `BaseException` that a caller's `except Exception` does not catch.
    Internal {
        /// What failed, concretely.
        detail: &'static str,
    },
}

impl fmt::Display for MatGroupError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MatGroupError::FiniteField(e) => write!(f, "{e}"),
            MatGroupError::Permutation(e) => write!(f, "{e}"),
            MatGroupError::NotSquare { rows, cols } => write!(
                f,
                "a matrix-group element must be square; got {rows}x{cols}"
            ),
            MatGroupError::DegreeMismatch { expected, got } => write!(
                f,
                "this group acts in dimension {expected}; got something of dimension {got}. \
                 Degrees are not padded automatically"
            ),
            MatGroupError::FieldMismatch { expected, got } => write!(
                f,
                "this group is over {expected} and the matrix is over {got}; two isomorphic \
                 fields with different defining polynomials are different fields here"
            ),
            MatGroupError::SingularGenerator { index } => write!(
                f,
                "generator {index} is singular, so it is not an invertible matrix and \
                 generates no group"
            ),
            MatGroupError::DegreeTooLarge { degree, max } => {
                write!(f, "degree {degree} exceeds the matrix-group limit of {max}")
            }
            MatGroupError::OrbitTooLarge { points, max } => write!(
                f,
                "an orbit on vectors passed {points} points, above the cap of {max}; the \
                 stabilizer chain stores a transversal matrix per orbit point"
            ),
            MatGroupError::WorkBudgetExhausted { budget } => write!(
                f,
                "Schreier–Sims exhausted its budget of {budget} matrix operations without \
                 completing the chain; no order is reported, because an incomplete chain \
                 reports a proper divisor of the true order"
            ),
            MatGroupError::EnumerationTooLarge { order, cap } => write!(
                f,
                "the group has order {order}, above the enumeration cap of {cap}; its order \
                 is known exactly but its elements will not be listed"
            ),
            MatGroupError::FieldTooLarge { order, max } => write!(
                f,
                "this operation enumerates GF(q) itself and q = {order} is above the cap of \
                 {max}"
            ),
            MatGroupError::UnsupportedConstruction { family, reason } => {
                write!(f, "no {family} construction here: {reason}")
            }
            MatGroupError::CommutantTooLarge {
                dimension,
                elements,
                cap,
            } => write!(
                f,
                "the algebra commuting with the generators has GF(q)-dimension {dimension}, \
                 so enumerating it means {elements} matrices, above the cap of {cap}"
            ),
            MatGroupError::ZeroVector => write!(
                f,
                "the zero vector spans no 1-dimensional subspace and has no canonical scaling"
            ),
            MatGroupError::Internal { detail } => {
                write!(f, "internal matrix-group invariant violated: {detail}")
            }
        }
    }
}

impl std::error::Error for MatGroupError {}

impl From<FiniteFieldError> for MatGroupError {
    fn from(e: FiniteFieldError) -> Self {
        MatGroupError::FiniteField(e)
    }
}

impl From<GroupError> for MatGroupError {
    fn from(e: GroupError) -> Self {
        MatGroupError::Permutation(e)
    }
}

impl AlkahestError for MatGroupError {
    fn code(&self) -> &'static str {
        match self {
            MatGroupError::FiniteField(e) => e.code(),
            MatGroupError::Permutation(e) => e.code(),
            MatGroupError::NotSquare { .. } => "E-MATGRP-001",
            MatGroupError::DegreeMismatch { .. } => "E-MATGRP-002",
            MatGroupError::FieldMismatch { .. } => "E-MATGRP-003",
            MatGroupError::SingularGenerator { .. } => "E-MATGRP-004",
            MatGroupError::DegreeTooLarge { .. } => "E-MATGRP-005",
            MatGroupError::OrbitTooLarge { .. } => "E-MATGRP-006",
            MatGroupError::WorkBudgetExhausted { .. } => "E-MATGRP-007",
            MatGroupError::EnumerationTooLarge { .. } => "E-MATGRP-008",
            MatGroupError::FieldTooLarge { .. } => "E-MATGRP-009",
            MatGroupError::UnsupportedConstruction { .. } => "E-MATGRP-010",
            MatGroupError::CommutantTooLarge { .. } => "E-MATGRP-011",
            MatGroupError::ZeroVector => "E-MATGRP-012",
            MatGroupError::Internal { .. } => "E-MATGRP-013",
        }
    }

    fn remediation(&self) -> Option<&'static str> {
        match self {
            MatGroupError::FiniteField(e) => e.remediation(),
            MatGroupError::Permutation(e) => e.remediation(),
            MatGroupError::NotSquare { .. } => {
                Some("group elements are d x d matrices; pass a square matrix")
            }
            MatGroupError::DegreeMismatch { .. } => Some(
                "build the matrix at the group's degree; vectors are 1 x d row vectors, since \
                 the action here is v -> v*M",
            ),
            MatGroupError::FieldMismatch { .. } => Some(
                "build the matrix over the same FiniteField object the group was built with, \
                 or rebuild the group over the matrix's field",
            ),
            MatGroupError::SingularGenerator { .. } => {
                Some("check the determinant of every generator before building the group")
            }
            MatGroupError::DegreeTooLarge { .. } => Some(
                "the matrix-group algorithms here store explicit transversals and are built \
                 for small degrees; GL(d, q) for large d needs the Aschbacher/constructive \
                 recognition machinery, which is not implemented",
            ),
            MatGroupError::OrbitTooLarge { .. } => Some(
                "an orbit on vectors is bounded by q^d - 1, so this is a cap on q^d rather \
                 than on |G|; use a smaller field or degree, or work with the projective \
                 action, whose orbits are (q - 1) times smaller",
            ),
            MatGroupError::WorkBudgetExhausted { .. } => Some(
                "raise the budget with `MatGroup::with_budget` (Python `with_budget`) if the \
                 group really is this large, or supply a smaller generating set; no order is \
                 reported, because an incomplete chain reports a proper divisor of the true one",
            ),
            MatGroupError::EnumerationTooLarge { .. } => Some(
                "use `order()`, `contains()` or `random_element()` instead of listing the \
                 elements, or raise the cap with `elements_with_cap`",
            ),
            MatGroupError::FieldTooLarge { .. } => Some(
                "orbits, order and membership do not enumerate GF(q); the operations that do \
                 (projective point lists, classical constructors, the centre) need a small q",
            ),
            MatGroupError::UnsupportedConstruction { .. } => Some(
                "build the group from explicit generators with `MatGroup::new`; the classical \
                 constructors here cover GL, SL, Sp and Singer cycles only",
            ),
            MatGroupError::CommutantTooLarge { .. } => Some(
                "a large centralizing algebra means the module is very reducible; split it \
                 first, or compute the centre of a smaller group",
            ),
            MatGroupError::ZeroVector => {
                Some("pass a non-zero row vector; the zero vector is fixed by every matrix")
            }
            MatGroupError::Internal { .. } => {
                Some("this is a bug: please report it with the generators that produced it")
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Wrapped GF(q) and permutation errors keep their own codes; the split is
    /// a versioning artefact and must not give one condition two identities.
    #[test]
    fn wrapped_errors_delegate_their_code() {
        let inner = FiniteFieldError::Singular { shape: (2, 2) };
        let wrapped: MatGroupError = inner.clone().into();
        assert_eq!(wrapped.code(), inner.code());
        assert_eq!(wrapped.remediation(), inner.remediation());
        assert_eq!(wrapped.to_string(), inner.to_string());

        let inner = GroupError::DegreeMismatch { left: 3, right: 4 };
        let wrapped: MatGroupError = inner.clone().into();
        assert_eq!(wrapped.code(), "E-GRP-002");
        assert_eq!(wrapped.code(), inner.code());
        assert_eq!(wrapped.to_string(), inner.to_string());
    }

    #[test]
    fn every_variant_has_a_distinct_code_and_remediation() {
        let all = [
            MatGroupError::NotSquare { rows: 2, cols: 3 },
            MatGroupError::DegreeMismatch {
                expected: 3,
                got: 2,
            },
            MatGroupError::FieldMismatch {
                expected: "GF(2)".into(),
                got: "GF(3)".into(),
            },
            MatGroupError::SingularGenerator { index: 0 },
            MatGroupError::DegreeTooLarge {
                degree: 99,
                max: 16,
            },
            MatGroupError::OrbitTooLarge {
                points: 5000,
                max: 4096,
            },
            MatGroupError::WorkBudgetExhausted { budget: 1 },
            MatGroupError::EnumerationTooLarge {
                order: "10".into(),
                cap: 1,
            },
            MatGroupError::FieldTooLarge {
                order: "10".into(),
                max: 1,
            },
            MatGroupError::UnsupportedConstruction {
                family: "GU",
                reason: "x",
            },
            MatGroupError::CommutantTooLarge {
                dimension: 9,
                elements: "512".into(),
                cap: 1,
            },
            MatGroupError::ZeroVector,
            MatGroupError::Internal { detail: "x" },
        ];
        let codes: Vec<&str> = all.iter().map(|e| e.code()).collect();
        assert_eq!(
            codes,
            [
                "E-MATGRP-001",
                "E-MATGRP-002",
                "E-MATGRP-003",
                "E-MATGRP-004",
                "E-MATGRP-005",
                "E-MATGRP-006",
                "E-MATGRP-007",
                "E-MATGRP-008",
                "E-MATGRP-009",
                "E-MATGRP-010",
                "E-MATGRP-011",
                "E-MATGRP-012",
                "E-MATGRP-013",
            ]
        );
        for e in &all {
            assert!(e.remediation().is_some(), "{e} has no remediation");
            assert!(!e.to_string().is_empty());
        }
    }
}