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
//! Refusals from the theta / modular-function surface.
//!
//! The rule this module is built around: **a ball whose radius swamps its
//! midpoint is not an answer.** Every entry point either returns an enclosure
//! the caller can interrogate, or one of these — never a plausible-looking
//! `f64` with nothing behind it.
use std::fmt;
/// Why a theta, modular or elliptic evaluation refused.
///
/// Codes are `E-THETA-001` … `E-THETA-012`; see
/// [`crate::errors::codes::REGISTRY`].
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ThetaError {
/// The FLINT this crate was built against does not carry the Arb/Acb
/// layer, or does not carry the FLINT 3.2+ `acb_theta` interface.
///
/// Arb became part of FLINT in 3.0 and `acb_theta` was rewritten in 3.2;
/// `build.rs` probes `libflint`'s symbol table and sets `flint_arb` /
/// `flint_acb_theta` accordingly. A build against FLINT 2.x — which the
/// manylinux wheel job still falls back to — compiles this module but
/// cannot evaluate anything in it.
BackendUnavailable {
/// Which capability was missing: `"arb"` or `"acb_theta"`.
capability: &'static str,
},
/// The installed FLINT's `arb_struct` / `acb_struct` layout is not the one
/// this build was compiled against.
///
/// Detected at run time by differencing two `acb_mat_entry_ptr` results
/// and by reading back a ball set to an exactly representable value. This
/// is deliberately a refusal and not an assertion: the alternative to
/// noticing is silent memory corruption.
AbiMismatch {
/// What was measured, naming the offending number.
detail: String,
},
/// The requested working precision is outside `2 ..= MAX_PRECISION_BITS`.
PrecisionOutOfRange {
/// Bits requested.
bits: u64,
/// The largest this module accepts.
max: u32,
},
/// The genus is outside `1 ..= MAX_GENUS`.
///
/// `acb_theta_all` returns `4^g` values, so the output alone is
/// exponential in `g` before any work is done. The cap is a refusal, not a
/// silent truncation.
GenusOutOfRange {
/// Genus asked for.
genus: usize,
/// The largest this module accepts.
max: usize,
},
/// A vector or matrix had the wrong length for the genus.
DimensionMismatch {
/// What was being built, e.g. `"period matrix"`.
what: &'static str,
/// Entries expected.
expected: usize,
/// Entries supplied.
got: usize,
},
/// The period matrix is not symmetric.
///
/// Symmetry is checked as **ball identity** (`acb_equal`), not as overlap:
/// two different enclosures of the same number are two different inputs,
/// and choosing one of them for the caller would be a guess. Build the
/// matrix with [`crate::theta::SiegelMatrix::from_upper_triangle`] if the
/// entries were computed separately.
NotSymmetric {
/// Row of the offending pair.
i: usize,
/// Column of the offending pair.
j: usize,
},
/// `Im(tau)` could not be **proved** positive definite at the working
/// precision, so `tau` is not certainly in the Siegel upper half-space
/// `H_g` and theta does not converge there.
///
/// A refusal here means "not proved", never "proved false": raising the
/// precision may settle it.
NotInSiegelUpperHalfSpace {
/// Genus of the matrix that was offered.
genus: usize,
/// Precision at which positive-definiteness was attempted.
prec: u32,
},
/// The characteristic index `ab` is outside `0 .. 4^g`.
CharacteristicOutOfRange {
/// The index offered.
ab: u64,
/// Genus.
genus: usize,
},
/// `Im(tau)` could not be proved positive, so `tau` is not certainly in the
/// upper half-plane, where the genus-1 modular functions live.
NotInUpperHalfPlane {
/// Which function was asked for, e.g. `"j"`.
function: &'static str,
},
/// A value was computed, and then **withheld** because its enclosure was
/// not accurate enough to be worth reading.
///
/// Raised only by the [`crate::theta::Precision::AccurateTo`] mode, after
/// the working precision has been raised as far as this module will go.
/// The midpoint is not returned: a midpoint with no bits behind it is the
/// confident-wrong-answer shape this whole module exists to avoid.
InsufficientAccuracy {
/// Which function was asked for.
function: &'static str,
/// Bits of relative accuracy the caller asked for.
requested_bits: u32,
/// Bits of relative accuracy actually achieved, or `None` when the
/// result came back with an infinite radius.
achieved_bits: Option<i64>,
/// Highest working precision that was tried.
at_precision: u32,
},
/// The result came back with an infinite radius: FLINT could not bound it
/// at all at this precision.
///
/// Typically the argument is outside the domain — `℘` at a lattice point,
/// a `tau` whose imaginary part is indistinguishable from zero — or the
/// input ball was already indeterminate.
Indeterminate {
/// Which function was asked for.
function: &'static str,
},
/// A midpoint or radius could not cross between FLINT and `rug::Float`
/// without rounding, because its exponent or mantissa is outside the range
/// MPFR can hold.
///
/// Refused rather than rounded: an inflated radius would still be a valid
/// enclosure, but a silently *deflated* one would not, and the conversion
/// is not in a position to tell the two apart cheaply.
NotRepresentable {
/// What failed to convert, e.g. `"midpoint"`.
what: &'static str,
},
}
impl fmt::Display for ThetaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ThetaError::BackendUnavailable { capability } => write!(
f,
"this build's FLINT does not provide the `{capability}` layer; \
theta and modular functions need FLINT >= 3.1 (>= 3.4 for acb_theta)"
),
ThetaError::AbiMismatch { detail } => {
write!(f, "FLINT ball ABI mismatch: {detail}")
}
ThetaError::PrecisionOutOfRange { bits, max } => {
write!(f, "working precision {bits} is outside 2..={max} bits")
}
ThetaError::GenusOutOfRange { genus, max } => write!(
f,
"genus {genus} is outside 1..={max}; theta returns 4^g values, \
so the output alone is exponential in the genus"
),
ThetaError::DimensionMismatch {
what,
expected,
got,
} => write!(f, "{what} needs {expected} entries but {got} were given"),
ThetaError::NotSymmetric { i, j } => write!(
f,
"the period matrix is not symmetric: entry ({i}, {j}) and ({j}, {i}) \
are different balls"
),
ThetaError::NotInSiegelUpperHalfSpace { genus, prec } => write!(
f,
"the imaginary part of this genus-{genus} period matrix could not be \
proved positive definite at {prec} bits, so it is not certainly in \
the Siegel upper half-space"
),
ThetaError::CharacteristicOutOfRange { ab, genus } => {
write!(f, "theta characteristic {ab} is outside 0..4^{genus}")
}
ThetaError::NotInUpperHalfPlane { function } => write!(
f,
"{function} needs tau with a certainly positive imaginary part"
),
ThetaError::InsufficientAccuracy {
function,
requested_bits,
achieved_bits,
at_precision,
} => match achieved_bits {
Some(a) => write!(
f,
"{function} reached {a} bits of relative accuracy at a working \
precision of {at_precision} bits, short of the {requested_bits} asked for"
),
None => write!(
f,
"{function} came back with an infinite radius at a working precision \
of {at_precision} bits, short of the {requested_bits} bits asked for"
),
},
ThetaError::Indeterminate { function } => write!(
f,
"{function} could not be bounded at all: the result has an infinite radius"
),
ThetaError::NotRepresentable { what } => write!(
f,
"the {what} has an exponent outside the range that can be moved between \
FLINT and MPFR without rounding"
),
}
}
}
impl std::error::Error for ThetaError {}
impl crate::errors::AlkahestError for ThetaError {
fn code(&self) -> &'static str {
match self {
ThetaError::BackendUnavailable { .. } => "E-THETA-001",
ThetaError::AbiMismatch { .. } => "E-THETA-002",
ThetaError::PrecisionOutOfRange { .. } => "E-THETA-003",
ThetaError::GenusOutOfRange { .. } => "E-THETA-004",
ThetaError::DimensionMismatch { .. } => "E-THETA-005",
ThetaError::NotSymmetric { .. } => "E-THETA-006",
ThetaError::NotInSiegelUpperHalfSpace { .. } => "E-THETA-007",
ThetaError::CharacteristicOutOfRange { .. } => "E-THETA-008",
ThetaError::NotInUpperHalfPlane { .. } => "E-THETA-009",
ThetaError::InsufficientAccuracy { .. } => "E-THETA-010",
ThetaError::Indeterminate { .. } => "E-THETA-011",
ThetaError::NotRepresentable { .. } => "E-THETA-012",
}
}
fn remediation(&self) -> Option<&'static str> {
crate::errors::codes::REGISTRY
.iter()
.find(|spec| spec.code == crate::errors::AlkahestError::code(self))
.and_then(|spec| spec.remediation)
}
}