Skip to main content

qualia_core_db/specialized_libs/
constructibility.rs

1//! **Constructibility** — compass-and-straightedge feasibility decisions.
2//!
3//! A length, angle, or figure is *constructible* iff it can be produced with compass
4//! and straightedge from a unit segment. This is the **feasibility gate** for the
5//! NL→3D-fabrication pipeline: "can this geometric feature be made by this method?"
6//! ([[project-nl-to-3d-fabrication-purpose]]). It also settles the three classical
7//! impossibilities (doubling the cube, trisecting a general angle, squaring the circle)
8//! and decides which regular polygons are constructible (Gauss–Wantzel).
9//!
10//! ## The decision procedures
11//!
12//! * **Degree criterion (Wantzel).** A constructible number is algebraic of degree a
13//!   **power of two** over ℚ. So [`constructible_from_min_poly_degree`] decides every
14//!   classical case from the minimal-polynomial degree alone: ∛2 (degree 3) → no
15//!   (doubling the cube); `cos(20°)` (degree 3) → no (trisecting 60°).
16//! * **Gauss–Wantzel** for the regular `n`-gon: constructible iff
17//!   `n = 2^a · (product of *distinct* Fermat primes)`. See
18//!   [`is_regular_polygon_constructible`]; the heptadecagon (`n = 17`) is the showcase.
19//! * **Within the CAS** ([`super::symbolic_algebra::Expr`]): any well-formed *real*
20//!   expression over rationals, the field operations, integer powers and **square
21//!   roots** is constructible by construction — square roots are exactly the
22//!   degree-2 tower. [`is_constructible_number`] confirms real-validity and reports the
23//!   field-extension degree bound; it fails closed on a non-real `√(negative)` or a
24//!   division by zero.
25//!
26//! Everything here is decidable and verifiable; nothing is fabricated.
27
28use super::symbolic_algebra::Expr;
29use std::collections::HashMap;
30
31/// `n` is a power of two (`n ≥ 1`).
32pub fn is_power_of_two(n: u64) -> bool {
33    n != 0 && (n & (n - 1)) == 0
34}
35
36/// Wantzel's degree criterion: a number of minimal-polynomial degree `degree` over ℚ
37/// is constructible **only if** `degree` is a power of two. (Necessary and, with the
38/// quadratic-tower construction, the operative test for the classical problems.)
39pub fn constructible_from_min_poly_degree(degree: u64) -> bool {
40    is_power_of_two(degree)
41}
42
43/// Trial-division primality test (small inputs; polygon sides / Fermat candidates).
44fn is_prime(n: u64) -> bool {
45    if n < 2 {
46        return false;
47    }
48    if n % 2 == 0 {
49        return n == 2;
50    }
51    let mut d = 3u64;
52    while d.saturating_mul(d) <= n {
53        if n % d == 0 {
54            return false;
55        }
56        d += 2;
57    }
58    true
59}
60
61/// A Fermat prime is a prime of the form `2^(2^k) + 1` (3, 5, 17, 257, 65537, …). The
62/// constructible odd-prime polygon sides are exactly these.
63pub fn is_fermat_prime(n: u64) -> bool {
64    if !is_prime(n) || n < 3 {
65        return false;
66    }
67    let m = n - 1; // must be 2^(2^k)
68    if !is_power_of_two(m) {
69        return false;
70    }
71    // The exponent of m (= 2^k) must itself be a power of two.
72    let exp = m.trailing_zeros() as u64;
73    is_power_of_two(exp)
74}
75
76/// **Gauss–Wantzel**: the regular `n`-gon is constructible iff `n = 2^a · (product of
77/// distinct Fermat primes)` — i.e. after stripping factors of two, the odd part is a
78/// squarefree product of Fermat primes. (`n ≥ 3`.)
79pub fn is_regular_polygon_constructible(n: u64) -> bool {
80    if n < 3 {
81        return false;
82    }
83    // Strip factors of two.
84    let mut odd = n;
85    while odd % 2 == 0 {
86        odd /= 2;
87    }
88    if odd == 1 {
89        return true; // a power-of-two-gon (square, octagon, …)
90    }
91    // Factor the odd part: each prime factor must be a Fermat prime appearing once.
92    let mut p = 3u64;
93    let mut rem = odd;
94    while p.saturating_mul(p) <= rem {
95        if rem % p == 0 {
96            if !is_fermat_prime(p) {
97                return false;
98            }
99            rem /= p;
100            if rem % p == 0 {
101                return false; // repeated factor → not squarefree → not constructible
102            }
103        }
104        p += 2;
105    }
106    // Whatever remains is a prime factor > sqrt(rem); it must be a Fermat prime.
107    rem == 1 || is_fermat_prime(rem)
108}
109
110/// The angle `2π/n` (a regular-`n`-gon central angle) is constructible iff the regular
111/// `n`-gon is. So a 60° angle (`n = 6`) is constructible; 40° (`n = 9`) is not.
112pub fn is_central_angle_constructible(n: u64) -> bool {
113    is_regular_polygon_constructible(n)
114}
115
116/// The classical impossibilities, decided from the degree criterion (documented facts,
117/// not hardcoded opinions):
118/// **Doubling the cube** needs ∛2 — degree 3, not a power of two.
119pub fn doubling_the_cube_constructible() -> bool {
120    constructible_from_min_poly_degree(3)
121}
122/// **Trisecting a general angle** needs a root of `4x³ − 3x − cos θ` — degree 3.
123pub fn trisecting_general_angle_constructible() -> bool {
124    constructible_from_min_poly_degree(3)
125}
126/// **Squaring the circle** needs √π; π is transcendental (no finite minimal polynomial),
127/// so it is not algebraic of any finite degree, let alone a power of two.
128pub fn squaring_the_circle_constructible() -> bool {
129    false
130}
131
132/// The verdict for a CAS expression.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum ConstructibilityVerdict {
135    /// Constructible; `degree_bound` is an upper bound on the field-extension degree
136    /// (`2^(number of square roots)`), always a power of two.
137    Constructible { degree_bound: u64 },
138    /// `√(negative)` — not a real number, so not a constructible real.
139    NotRealNumber,
140    /// Division by zero — undefined.
141    Undefined,
142    /// A transcendental function (`exp`/`ln`/`sin`/`cos`/`tan`) appears: by
143    /// Lindemann–Weierstrass its value at a nonzero algebraic argument is transcendental,
144    /// hence not constructible (no finite-degree minimal polynomial).
145    Transcendental,
146}
147
148/// Count `Sqrt` nodes (the field-extension tower height bound).
149fn sqrt_count(expr: &Expr) -> u32 {
150    match expr {
151        Expr::Const(_) | Expr::Var(_) => 0,
152        Expr::Neg(a) | Expr::Pow(a, _) => sqrt_count(a),
153        Expr::Add(a, b) | Expr::Sub(a, b) | Expr::Mul(a, b) | Expr::Div(a, b) => {
154            sqrt_count(a) + sqrt_count(b)
155        }
156        Expr::Sqrt(a) => 1 + sqrt_count(a),
157        // Transcendental nodes contribute no square-root tower; count any sqrts in the arg.
158        Expr::Exp(a) | Expr::Ln(a) | Expr::Sin(a) | Expr::Cos(a) | Expr::Tan(a) => sqrt_count(a),
159    }
160}
161
162/// Detect a non-real (`√` of a constant-negative) or undefined (`÷0`) subexpression.
163/// Returns the offending verdict, or `None` if numerically valid (or symbolic).
164fn invalidity(expr: &Expr) -> Option<ConstructibilityVerdict> {
165    let empty = HashMap::new();
166    match expr {
167        Expr::Const(_) | Expr::Var(_) => None,
168        Expr::Neg(a) | Expr::Pow(a, _) => invalidity(a),
169        Expr::Add(a, b) | Expr::Sub(a, b) | Expr::Mul(a, b) => {
170            invalidity(a).or_else(|| invalidity(b))
171        }
172        Expr::Div(a, b) => {
173            if let Some(0.0) = b.eval(&empty).filter(|v| *v == 0.0) {
174                return Some(ConstructibilityVerdict::Undefined);
175            }
176            invalidity(a).or_else(|| invalidity(b))
177        }
178        Expr::Sqrt(a) => {
179            if let Some(v) = a.eval(&empty) {
180                if v < 0.0 {
181                    return Some(ConstructibilityVerdict::NotRealNumber);
182                }
183            }
184            invalidity(a)
185        }
186        // exp/ln/sin/cos/tan: transcendental unless the argument is the trivial 0
187        // (e.g. sin 0 = 0, cos 0 = 1, exp 0 = 1 are constructible). Otherwise non-constructible.
188        Expr::Exp(a) | Expr::Ln(a) | Expr::Sin(a) | Expr::Cos(a) | Expr::Tan(a) => {
189            if let Some(v) = a.eval(&empty) {
190                if v == 0.0 {
191                    return invalidity(a);
192                }
193            }
194            Some(ConstructibilityVerdict::Transcendental)
195        }
196    }
197}
198
199/// Decide constructibility of the number denoted by a CAS expression. Within `Expr`
200/// (rationals + field ops + integer powers + square roots) every well-formed **real**
201/// number is constructible; this confirms real-validity and reports the degree bound,
202/// failing closed on `√(negative)` or `÷0`.
203pub fn is_constructible_number(expr: &Expr) -> ConstructibilityVerdict {
204    if let Some(bad) = invalidity(expr) {
205        return bad;
206    }
207    let n = sqrt_count(expr).min(62);
208    ConstructibilityVerdict::Constructible {
209        degree_bound: 1u64 << n,
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use crate::specialized_libs::symbolic_algebra::{add, c, div, sqrt};
217
218    #[test]
219    fn power_of_two() {
220        for n in [1, 2, 4, 8, 16, 65536] {
221            assert!(is_power_of_two(n));
222        }
223        for n in [0, 3, 6, 12, 17] {
224            assert!(!is_power_of_two(n));
225        }
226    }
227
228    #[test]
229    fn fermat_primes_are_the_known_five() {
230        for p in [3, 5, 17, 257, 65537] {
231            assert!(is_fermat_prime(p), "{p} should be a Fermat prime");
232        }
233        for n in [2, 7, 9, 11, 13, 15, 65539] {
234            assert!(!is_fermat_prime(n), "{n} should not be a Fermat prime");
235        }
236    }
237
238    #[test]
239    fn gauss_wantzel_regular_polygons() {
240        // Constructible: powers of two, Fermat primes, products of *distinct* ones.
241        for n in [3, 4, 5, 6, 8, 10, 12, 15, 16, 17, 20, 257] {
242            assert!(
243                is_regular_polygon_constructible(n),
244                "{n}-gon should be constructible"
245            );
246        }
247        // Not: non-Fermat odd primes, repeated Fermat factor (9 = 3²), their multiples.
248        for n in [7, 9, 11, 13, 14, 18, 19, 21, 23, 25] {
249            assert!(
250                !is_regular_polygon_constructible(n),
251                "{n}-gon should NOT be constructible"
252            );
253        }
254    }
255
256    #[test]
257    fn classical_impossibilities() {
258        assert!(!doubling_the_cube_constructible());
259        assert!(!trisecting_general_angle_constructible());
260        assert!(!squaring_the_circle_constructible());
261        // The 60° angle (n=6) is constructible; trisecting it to 20° (n=18) is not.
262        assert!(is_central_angle_constructible(6));
263        assert!(!is_central_angle_constructible(18));
264    }
265
266    #[test]
267    fn degree_criterion_decides_quadratics_and_cubics() {
268        assert!(constructible_from_min_poly_degree(2)); // √2, golden ratio
269        assert!(constructible_from_min_poly_degree(4)); // nested square roots
270        assert!(!constructible_from_min_poly_degree(3)); // ∛2
271        assert!(!constructible_from_min_poly_degree(0));
272    }
273
274    #[test]
275    fn expression_constructibility_and_degree_bound() {
276        // √2 → constructible, degree bound 2.
277        match is_constructible_number(&sqrt(c(2.0))) {
278            ConstructibilityVerdict::Constructible { degree_bound } => assert_eq!(degree_bound, 2),
279            v => panic!("√2 should be constructible, got {v:?}"),
280        }
281        // √(1+√2) → two nested roots → degree bound 4.
282        let nested = sqrt(add(c(1.0), sqrt(c(2.0))));
283        match is_constructible_number(&nested) {
284            ConstructibilityVerdict::Constructible { degree_bound } => assert_eq!(degree_bound, 4),
285            v => panic!("nested root should be constructible, got {v:?}"),
286        }
287    }
288
289    #[test]
290    fn transcendental_subexpression_is_not_constructible() {
291        use crate::specialized_libs::symbolic_algebra::{cos, sin, var};
292        // sin(1) is transcendental → not constructible.
293        assert_eq!(
294            is_constructible_number(&sin(c(1.0))),
295            ConstructibilityVerdict::Transcendental
296        );
297        // A symbolic cos(x) (unknown argument) is treated transcendental, not fabricated constructible.
298        assert_eq!(
299            is_constructible_number(&cos(var("x"))),
300            ConstructibilityVerdict::Transcendental
301        );
302        // But cos(0) = 1 (trivial argument) remains constructible.
303        assert!(matches!(
304            is_constructible_number(&cos(c(0.0))),
305            ConstructibilityVerdict::Constructible { .. }
306        ));
307    }
308
309    #[test]
310    fn fails_closed_on_non_real_and_undefined() {
311        assert_eq!(
312            is_constructible_number(&sqrt(c(-1.0))),
313            ConstructibilityVerdict::NotRealNumber
314        );
315        assert_eq!(
316            is_constructible_number(&div(c(1.0), c(0.0))),
317            ConstructibilityVerdict::Undefined
318        );
319    }
320}