malachite_nz/gaussian_integer/arithmetic/root.rs
1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::gaussian_integer::{ComparableGaussianIntegerRef, GaussianInteger};
10use crate::integer::Integer;
11use crate::natural::Natural;
12use alloc::vec::Vec;
13use malachite_base::num::arithmetic::traits::{
14 CanonicalizeUnit, CheckedRoot, CheckedSqrt, ContentAndPrimitivePart, DivRem, Gcd, ModPowerOf2,
15 MulIPow, MulIPowAssign, Parity, Pow, PowerOf2, Square,
16};
17use malachite_base::num::basic::traits::Zero;
18use malachite_base::num::conversion::traits::ExactFrom;
19use malachite_base::num::logic::traits::TrailingZeros;
20
21fn norm(z: &GaussianInteger) -> Natural {
22 z.real.unsigned_abs_ref().square() + z.imaginary.unsigned_abs_ref().square()
23}
24
25// The unique kth root of a nonzero z for odd k >= 3, if it exists.
26//
27// Strip the prime 1 + i, whose multiplicity must be a multiple of k, and split what remains into
28// its content C and primitive part Q. If z = w^k, then C is the kth power of the content of the
29// corresponding part of w, a Natural root check, and Q is a unit times P^k for the primitive part P
30// of w. A primitive Gaussian integer with no factor 1 + i has, for each pair of conjugate split
31// primes, only one of the two, so gcd(Q, N(P)) = gcd(u P^k, P conj(P)) is P up to a unit.
32// Reassembling gives w up to a unit, and the unit is fixed by comparing the kth power with z: for
33// odd k the map from units to their kth powers is a bijection.
34fn odd_root(z: &GaussianInteger, k: u64) -> Option<GaussianInteger> {
35 let (stripped, one_plus_i_exp) = z.remove_one_plus_i();
36 let (w_one_plus_i_exp, r) = one_plus_i_exp.div_rem(k);
37 if r != 0 {
38 return None;
39 }
40 let (content, primitive) = stripped.content_and_primitive_part();
41 let w_content = Integer::from(content.checked_root(k)?);
42 let primitive_norm = norm(&primitive).checked_root(k)?;
43 let w_primitive = (&primitive).gcd(GaussianInteger::from(Integer::from(primitive_norm)));
44 let mut w = GaussianInteger {
45 real: w_primitive.real * &w_content,
46 imaginary: w_primitive.imaginary * w_content,
47 };
48 // multiply by (1 + i)^e = (2i)^(e / 2) (1 + i)^(e mod 2)
49 let half = w_one_plus_i_exp >> 1;
50 w <<= half;
51 w.mul_i_pow_assign(half);
52 if w_one_plus_i_exp.odd() {
53 // (a + bi)(1 + i) = (a - b) + (a + b)i
54 let sum = &w.real + &w.imaginary;
55 w.real -= &w.imaginary;
56 w.imaginary = sum;
57 }
58 // z = (i^j w)^k = i^(jk) w^k, so w^k = i^(-jk) z; k is odd, so k is its own inverse mod 4
59 let w_pow = (&w).pow(k);
60 let j = (0..4).find(|&j| (&w_pow).mul_i_pow(j) == *z)?;
61 Some(w.mul_i_pow((j * k).mod_power_of_2(2)))
62}
63
64// The principal exp-th root, as described in the documentation of `checked_root`.
65fn principal_root(z: &GaussianInteger, exp: u64) -> Option<GaussianInteger> {
66 assert_ne!(exp, 0, "Cannot take the 0th root of a Gaussian integer");
67 if *z == 0u32 {
68 return Some(GaussianInteger::ZERO);
69 } else if exp == 1 {
70 return Some(z.clone());
71 }
72 let e = TrailingZeros::trailing_zeros(exp);
73 let m = exp >> e;
74 // The 2^e-th roots of the odd-part root are rotations of one another, but a rotation of a
75 // square need not be a square (i is not a unit square), so following one chain of principal
76 // square roots can dead-end where another succeeds: 16 = (1+i)^8, yet 16, 4, 2 stops at 2 while
77 // 16, -4, 2i, 1+i reaches the root. So every square root is kept; the candidate set never
78 // exceeds four elements.
79 let mut candidates = vec![if m == 1 { z.clone() } else { odd_root(z, m)? }];
80 for _ in 0..e {
81 candidates = candidates
82 .into_iter()
83 .filter_map(CheckedSqrt::checked_sqrt)
84 .flat_map(|w| [-&w, w])
85 .collect();
86 if candidates.is_empty() {
87 return None;
88 }
89 }
90 let candidate = candidates.pop()?;
91 Some(match e {
92 0 => candidate,
93 1 => {
94 if (&candidate.real, &candidate.imaginary) > (&Integer::ZERO, &Integer::ZERO) {
95 candidate
96 } else {
97 -candidate
98 }
99 }
100 _ => candidate.canonicalize_unit(),
101 })
102}
103
104impl CheckedRoot<u64> for GaussianInteger {
105 type Output = Self;
106
107 /// Returns the principal $n$th root of a [`GaussianInteger`], or `None` if it is not a perfect
108 /// $n$th power. The [`GaussianInteger`] is taken by value.
109 ///
110 /// A nonzero Gaussian integer has either no $n$th roots or exactly $\gcd(n, 4)$ of them: if $w$
111 /// is one, the others are $w\zeta$ for the units $\zeta$ with $\zeta^n = 1$. The one returned
112 /// is the principal root, whose argument lies in $(-\pi/g, \pi/g]$ for $g = \gcd(n, 4)$: the
113 /// unique root for odd $n$, the root with positive real part (or zero real part and positive
114 /// imaginary part) for $n \equiv 2 \pmod 4$, and the root in canonical unit form for $4 \mid
115 /// n$.
116 ///
117 /// Writing $n = 2^e m$ with $m$ odd, the unique $m$th root is found exactly through the norm:
118 /// with $N = N(z)^{1/m}$ and $d = \gcd(z, N)$, the quotient $N d / \bar{d}$ is the square of
119 /// the root up to a unit, and the unit is fixed by raising to the $m$th power. Square roots are
120 /// then taken $e$ times over the candidate set, which never exceeds four roots.
121 ///
122 /// $$
123 /// f(z, n) = \begin{cases}
124 /// \operatorname{Some}(\sqrt\[n\]{z}) & \text{if} \quad \sqrt\[n\]{z} \in \Z\[i\], \\\\
125 /// \operatorname{None} & \textrm{otherwise}.
126 /// \end{cases}
127 /// $$
128 ///
129 /// # Worst-case complexity
130 /// $T(n) = O(n^2)$
131 ///
132 /// $M(n) = O(n)$
133 ///
134 /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
135 /// bits of the real and imaginary parts of `self`.
136 ///
137 /// # Panics
138 /// Panics if `exp` is zero.
139 ///
140 /// # Examples
141 /// ```
142 /// use malachite_base::num::arithmetic::traits::CheckedRoot;
143 /// use malachite_nz::gaussian_integer::GaussianInteger;
144 /// use std::str::FromStr;
145 ///
146 /// let root = |s, exp| {
147 /// GaussianInteger::from_str(s)
148 /// .unwrap()
149 /// .checked_root(exp)
150 /// .map(|r| r.to_string())
151 /// };
152 /// // (2+i)^5 = -38+41i
153 /// assert_eq!(root("-38+41i", 5), Some("2+i".to_string()));
154 /// // -4 = (1+i)^4, and 1+i is the principal root of the four
155 /// assert_eq!(root("-4", 4), Some("1+i".to_string()));
156 /// // the unique cube root of -8 is -2
157 /// assert_eq!(root("-8", 3), Some("-2".to_string()));
158 /// assert_eq!(root("3+4i", 3), None);
159 /// ```
160 #[inline]
161 fn checked_root(self, exp: u64) -> Option<Self> {
162 principal_root(&self, exp)
163 }
164}
165
166impl CheckedRoot<u64> for &GaussianInteger {
167 type Output = GaussianInteger;
168
169 /// Returns the principal $n$th root of a [`GaussianInteger`], or `None` if it is not a perfect
170 /// $n$th power. The [`GaussianInteger`] is taken by reference.
171 ///
172 /// A nonzero Gaussian integer has either no $n$th roots or exactly $\gcd(n, 4)$ of them: if $w$
173 /// is one, the others are $w\zeta$ for the units $\zeta$ with $\zeta^n = 1$. The one returned
174 /// is the principal root, whose argument lies in $(-\pi/g, \pi/g]$ for $g = \gcd(n, 4)$: the
175 /// unique root for odd $n$, the root with positive real part (or zero real part and positive
176 /// imaginary part) for $n \equiv 2 \pmod 4$, and the root in canonical unit form for $4 \mid
177 /// n$.
178 ///
179 /// Writing $n = 2^e m$ with $m$ odd, the unique $m$th root is found exactly through the norm:
180 /// with $N = N(z)^{1/m}$ and $d = \gcd(z, N)$, the quotient $N d / \bar{d}$ is the square of
181 /// the root up to a unit, and the unit is fixed by raising to the $m$th power. Square roots are
182 /// then taken $e$ times over the candidate set, which never exceeds four roots.
183 ///
184 /// $$
185 /// f(z, n) = \begin{cases}
186 /// \operatorname{Some}(\sqrt\[n\]{z}) & \text{if} \quad \sqrt\[n\]{z} \in \Z\[i\], \\\\
187 /// \operatorname{None} & \textrm{otherwise}.
188 /// \end{cases}
189 /// $$
190 ///
191 /// # Worst-case complexity
192 /// $T(n) = O(n^2)$
193 ///
194 /// $M(n) = O(n)$
195 ///
196 /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
197 /// bits of the real and imaginary parts of `self`.
198 ///
199 /// # Panics
200 /// Panics if `exp` is zero.
201 ///
202 /// # Examples
203 /// ```
204 /// use malachite_base::num::arithmetic::traits::CheckedRoot;
205 /// use malachite_nz::gaussian_integer::GaussianInteger;
206 /// use std::str::FromStr;
207 ///
208 /// let root = |s, exp| {
209 /// (&GaussianInteger::from_str(s).unwrap())
210 /// .checked_root(exp)
211 /// .map(|r| r.to_string())
212 /// };
213 /// // (2+i)^5 = -38+41i
214 /// assert_eq!(root("-38+41i", 5), Some("2+i".to_string()));
215 /// // -4 = (1+i)^4, and 1+i is the principal root of the four
216 /// assert_eq!(root("-4", 4), Some("1+i".to_string()));
217 /// // the unique cube root of -8 is -2
218 /// assert_eq!(root("-8", 3), Some("-2".to_string()));
219 /// assert_eq!(root("3+4i", 3), None);
220 /// ```
221 #[inline]
222 fn checked_root(self, exp: u64) -> Option<GaussianInteger> {
223 principal_root(self, exp)
224 }
225}
226
227impl GaussianInteger {
228 /// Returns all the $n$th roots of a [`GaussianInteger`]: none if it is not a perfect $n$th
229 /// power, one if it is zero, and otherwise $\gcd(n, 4)$ of them, in the canonical order of
230 /// [`ComparableGaussianInteger`](crate::gaussian_integer::ComparableGaussianInteger),
231 /// lexicographic by real part and then imaginary part.
232 ///
233 /// The principal root is the one whose argument lies in $(-\pi/g, \pi/g]$ for $g = \gcd(n, 4)$;
234 /// see [`CheckedRoot`].
235 ///
236 /// $$
237 /// f(z, n) = \\{ w \in \Z\[i\] : w^n = z \\}.
238 /// $$
239 ///
240 /// # Worst-case complexity
241 /// $T(n) = O(n^2)$
242 ///
243 /// $M(n) = O(n)$
244 ///
245 /// where $T$ is time, $M$ is additional memory, and $n$ is the maximum number of significant
246 /// bits of the real and imaginary parts of `self`.
247 ///
248 /// # Panics
249 /// Panics if `exp` is zero.
250 ///
251 /// # Examples
252 /// ```
253 /// use malachite_base::num::basic::traits::Zero;
254 /// use malachite_nz::gaussian_integer::GaussianInteger;
255 /// use std::str::FromStr;
256 ///
257 /// let roots = |s, exp| {
258 /// GaussianInteger::from_str(s)
259 /// .unwrap()
260 /// .checked_roots(exp)
261 /// .iter()
262 /// .map(ToString::to_string)
263 /// .collect::<Vec<_>>()
264 /// };
265 /// assert_eq!(roots("-4", 4), ["-1-i", "-1+i", "1-i", "1+i"]);
266 /// assert_eq!(roots("-4", 2), ["-2i", "2i"]);
267 /// assert_eq!(roots("-8", 3), ["-2"]);
268 /// assert_eq!(roots("3+4i", 3), Vec::<String>::new());
269 /// assert_eq!(
270 /// GaussianInteger::ZERO.checked_roots(7),
271 /// [GaussianInteger::ZERO]
272 /// );
273 /// ```
274 pub fn checked_roots(&self, exp: u64) -> Vec<Self> {
275 let Some(principal) = principal_root(self, exp) else {
276 return Vec::new();
277 };
278 if principal == 0u32 {
279 return vec![principal];
280 }
281 // g = gcd(exp, 4) roots, each the previous one rotated by 2 pi / g, then sorted
282 let g = u64::power_of_2(TrailingZeros::trailing_zeros(exp).min(2));
283 let step = 4 / g;
284 let mut roots = Vec::with_capacity(usize::exact_from(g));
285 let mut root = principal;
286 for _ in 1..g {
287 let next = (&root).mul_i_pow(step);
288 roots.push(root);
289 root = next;
290 }
291 roots.push(root);
292 roots.sort_by(|a, b| ComparableGaussianIntegerRef(a).cmp(&ComparableGaussianIntegerRef(b)));
293 roots
294 }
295}