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}