1use glam::{Vec2, Vec3, Vec4};
4use std::f64::consts::PI;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct Complex {
11 pub re: f64,
12 pub im: f64,
13}
14
15impl Complex {
16 pub const ZERO: Complex = Complex { re: 0.0, im: 0.0 };
17 pub const ONE: Complex = Complex { re: 1.0, im: 0.0 };
18
19 pub fn new(re: f64, im: f64) -> Self {
20 Self { re, im }
21 }
22
23 pub fn norm_sq(self) -> f64 {
24 self.re * self.re + self.im * self.im
25 }
26
27 pub fn norm(self) -> f64 {
28 self.norm_sq().sqrt()
29 }
30
31 pub fn arg(self) -> f64 {
32 self.im.atan2(self.re)
33 }
34
35 pub fn conj(self) -> Self {
36 Self { re: self.re, im: -self.im }
37 }
38
39 pub fn exp(self) -> Self {
40 let r = self.re.exp();
41 Self {
42 re: r * self.im.cos(),
43 im: r * self.im.sin(),
44 }
45 }
46
47 pub fn ln(self) -> Self {
48 Self {
49 re: self.norm().ln(),
50 im: self.arg(),
51 }
52 }
53
54 pub fn powc(self, p: Complex) -> Self {
56 if self.norm_sq() == 0.0 {
57 return Complex::ZERO;
58 }
59 (p * self.ln()).exp()
60 }
61
62 pub fn powr(self, r: f64) -> Self {
64 self.powc(Complex::new(r, 0.0))
65 }
66
67 pub fn sin(self) -> Self {
68 Self {
69 re: self.re.sin() * self.im.cosh(),
70 im: self.re.cos() * self.im.sinh(),
71 }
72 }
73
74 pub fn cos(self) -> Self {
75 Self {
76 re: self.re.cos() * self.im.cosh(),
77 im: -self.re.sin() * self.im.sinh(),
78 }
79 }
80}
81
82impl std::ops::Add for Complex {
83 type Output = Self;
84 fn add(self, rhs: Self) -> Self {
85 Self { re: self.re + rhs.re, im: self.im + rhs.im }
86 }
87}
88
89impl std::ops::Sub for Complex {
90 type Output = Self;
91 fn sub(self, rhs: Self) -> Self {
92 Self { re: self.re - rhs.re, im: self.im - rhs.im }
93 }
94}
95
96impl std::ops::Mul for Complex {
97 type Output = Self;
98 fn mul(self, rhs: Self) -> Self {
99 Self {
100 re: self.re * rhs.re - self.im * rhs.im,
101 im: self.re * rhs.im + self.im * rhs.re,
102 }
103 }
104}
105
106impl std::ops::Div for Complex {
107 type Output = Self;
108 fn div(self, rhs: Self) -> Self {
109 let d = rhs.norm_sq();
110 Self {
111 re: (self.re * rhs.re + self.im * rhs.im) / d,
112 im: (self.im * rhs.re - self.re * rhs.im) / d,
113 }
114 }
115}
116
117impl std::ops::Mul<f64> for Complex {
118 type Output = Self;
119 fn mul(self, rhs: f64) -> Self {
120 Self { re: self.re * rhs, im: self.im * rhs }
121 }
122}
123
124impl std::ops::Mul<Complex> for f64 {
125 type Output = Complex;
126 fn mul(self, rhs: Complex) -> Complex {
127 Complex { re: self * rhs.re, im: self * rhs.im }
128 }
129}
130
131impl std::ops::AddAssign for Complex {
132 fn add_assign(&mut self, rhs: Self) {
133 self.re += rhs.re;
134 self.im += rhs.im;
135 }
136}
137
138impl std::ops::Neg for Complex {
139 type Output = Self;
140 fn neg(self) -> Self {
141 Self { re: -self.re, im: -self.im }
142 }
143}
144
145fn gamma(z: Complex) -> Complex {
148 if z.re < 0.5 {
150 let pi_z = Complex::new(PI, 0.0) * z;
151 return Complex::new(PI, 0.0) / (pi_z.sin() * gamma(Complex::new(1.0, 0.0) - z));
152 }
153
154 let z = z - Complex::ONE;
155 let g = 7.0;
156 let coefs: [f64; 9] = [
157 0.99999999999980993,
158 676.5203681218851,
159 -1259.1392167224028,
160 771.32342877765313,
161 -176.61502916214059,
162 12.507343278686905,
163 -0.13857109526572012,
164 9.9843695780195716e-6,
165 1.5056327351493116e-7,
166 ];
167
168 let mut x = Complex::new(coefs[0], 0.0);
169 for (i, &c) in coefs.iter().enumerate().skip(1) {
170 x += Complex::new(c, 0.0) / (z + Complex::new(i as f64, 0.0));
171 }
172
173 let t = z + Complex::new(g + 0.5, 0.0);
174 let sqrt_2pi = Complex::new((2.0 * PI).sqrt(), 0.0);
175 sqrt_2pi * t.powc(z + Complex::new(0.5, 0.0)) * (-t).exp() * x
176}
177
178pub fn zeta(s: Complex) -> Complex {
185 if (s.re - 1.0).abs() < 1e-14 && s.im.abs() < 1e-14 {
187 return Complex::new(f64::INFINITY, 0.0);
188 }
189
190 if s.re < 0.0 {
192 let one_minus_s = Complex::new(1.0, 0.0) - s;
193 let two_s = Complex::new(2.0, 0.0).powc(s);
194 let pi_s_minus_1 = Complex::new(PI, 0.0).powc(s - Complex::ONE);
195 let sin_term = (Complex::new(PI, 0.0) * s * 0.5).sin();
196 let gam = gamma(one_minus_s);
197 let zeta_1ms = zeta(one_minus_s);
198 return two_s * pi_s_minus_1 * sin_term * gam * zeta_1ms;
199 }
200
201 let n = 30usize;
203 let mut d = vec![0.0f64; n + 1];
205 d[0] = 1.0;
206 {
209 let mut binom = 1.0f64;
210 for k in 0..=n {
211 d[k] = binom;
212 if k < n {
213 binom *= (n - k) as f64 / (k + 1) as f64;
214 }
215 }
216 }
220
221 let nn = 40usize;
227 let mut sum = Complex::ZERO;
228 let mut c = Complex::ZERO; let mut dk = 0.0f64;
231 let dn = {
232 let mut v = 0.0f64;
233 let mut binom = 1.0f64;
234 for j in 0..=nn {
235 v += binom;
236 if j < nn {
237 binom *= (nn - j) as f64 / (j + 1) as f64;
238 }
239 }
240 v
241 };
242
243 let mut binom = 1.0f64;
244 dk = 0.0;
245 for k in 0..=nn {
246 dk += binom;
247 let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
248 let term_weight = sign * (dk - dn);
249 let k1 = Complex::new((k + 1) as f64, 0.0);
250 let term = Complex::new(term_weight, 0.0) / k1.powc(s);
251 let y = term - c;
253 let t = sum + y;
254 c = (t - sum) - y;
255 sum = t;
256 if k < nn {
257 binom *= (nn - k) as f64 / (k + 1) as f64;
258 }
259 }
260
261 let eta = sum * Complex::new(-1.0 / dn, 0.0);
262
263 let two_1ms = Complex::new(2.0, 0.0).powc(Complex::new(1.0, 0.0) - s);
265 let denom = Complex::ONE - two_1ms;
266
267 if denom.norm_sq() < 1e-30 {
268 let mut direct = Complex::ZERO;
270 for k in 1..=200 {
271 direct += Complex::new(1.0, 0.0) / Complex::new(k as f64, 0.0).powc(s);
272 }
273 return direct;
274 }
275
276 eta / denom
277}
278
279pub fn zeta_on_critical_line(t: f64) -> Complex {
281 zeta(Complex::new(0.5, t))
282}
283
284pub fn z_function(t: f64) -> f64 {
289 let theta = riemann_siegel_theta(t);
290 let z = zeta_on_critical_line(t);
291 let rot = Complex::new(theta.cos(), theta.sin());
293 let result = rot * z;
294 result.re
295}
296
297fn riemann_siegel_theta(t: f64) -> f64 {
299 let term1 = (t / 2.0) * ((t / (2.0 * PI)).ln()) - t / 2.0 - PI / 8.0;
303 let term2 = 1.0 / (48.0 * t);
304 let term3 = 7.0 / (5760.0 * t * t * t);
305 term1 + term2 + term3
306}
307
308pub fn find_zeros(t_min: f64, t_max: f64, resolution: usize) -> Vec<f64> {
311 let mut zeros = Vec::new();
312 let step = (t_max - t_min) / resolution as f64;
313 let mut prev = z_function(t_min);
314 let mut t = t_min + step;
315 for _ in 1..=resolution {
316 let current = z_function(t);
317 if prev * current < 0.0 {
318 let zero = bisect_zero(t - step, t, 50);
320 zeros.push(zero);
321 }
322 prev = current;
323 t += step;
324 }
325 zeros
326}
327
328fn bisect_zero(mut a: f64, mut b: f64, iterations: usize) -> f64 {
329 for _ in 0..iterations {
330 let mid = (a + b) / 2.0;
331 let fa = z_function(a);
332 let fm = z_function(mid);
333 if fa * fm <= 0.0 {
334 b = mid;
335 } else {
336 a = mid;
337 }
338 }
339 (a + b) / 2.0
340}
341
342pub struct CriticalStripRenderer {
346 pub re_range: (f64, f64),
347 pub im_range: (f64, f64),
348 pub resolution: (usize, usize),
349}
350
351pub struct StripCell {
353 pub position: Vec2,
354 pub color: Vec4,
355 pub zeta_value: Complex,
356}
357
358impl CriticalStripRenderer {
359 pub fn new(
360 re_range: (f64, f64),
361 im_range: (f64, f64),
362 resolution: (usize, usize),
363 ) -> Self {
364 Self { re_range, im_range, resolution }
365 }
366
367 pub fn default_strip() -> Self {
369 Self::new((0.0, 1.0), (-30.0, 30.0), (50, 300))
370 }
371
372 pub fn render(&self) -> Vec<StripCell> {
374 let (re_min, re_max) = self.re_range;
375 let (im_min, im_max) = self.im_range;
376 let (nx, ny) = self.resolution;
377 let re_step = (re_max - re_min) / nx as f64;
378 let im_step = (im_max - im_min) / ny as f64;
379
380 let mut cells = Vec::with_capacity(nx * ny);
381 for i in 0..nx {
382 for j in 0..ny {
383 let re = re_min + (i as f64 + 0.5) * re_step;
384 let im = im_min + (j as f64 + 0.5) * im_step;
385 let s = Complex::new(re, im);
386 let z = zeta(s);
387 let mag = z.norm().ln().max(-5.0).min(5.0);
388 let phase = z.arg();
389
390 let brightness = ((mag + 5.0) / 10.0) as f32;
392 let hue = ((phase + PI) / (2.0 * PI)) as f32;
393 let color = hsv_to_rgba(hue, 0.8, brightness);
394
395 cells.push(StripCell {
396 position: Vec2::new(i as f32, j as f32),
397 color,
398 zeta_value: z,
399 });
400 }
401 }
402 cells
403 }
404}
405
406pub struct ZeroMarker {
408 pub origin: Vec3,
409 pub scale: f32,
410}
411
412pub struct ZeroGlyph {
414 pub t: f64,
415 pub position: Vec3,
416 pub character: char,
417 pub color: Vec4,
418}
419
420impl ZeroMarker {
421 pub fn new(origin: Vec3, scale: f32) -> Self {
422 Self { origin, scale }
423 }
424
425 pub fn mark_zeros(&self, t_min: f64, t_max: f64, resolution: usize) -> Vec<ZeroGlyph> {
427 let zeros = find_zeros(t_min, t_max, resolution);
428 zeros
429 .into_iter()
430 .enumerate()
431 .map(|(i, t)| {
432 let y = (t - t_min) / (t_max - t_min);
433 ZeroGlyph {
434 t,
435 position: self.origin
436 + Vec3::new(0.5 * self.scale, y as f32 * self.scale, 0.0),
437 character: '\u{2742}', color: Vec4::new(1.0, 0.2, 0.2, 1.0),
439 }
440 })
441 .collect()
442 }
443}
444
445fn hsv_to_rgba(h: f32, s: f32, v: f32) -> Vec4 {
446 let h = h * 6.0;
447 let c = v * s;
448 let x = c * (1.0 - (h % 2.0 - 1.0).abs());
449 let m = v - c;
450 let (r, g, b) = if h < 1.0 {
451 (c, x, 0.0)
452 } else if h < 2.0 {
453 (x, c, 0.0)
454 } else if h < 3.0 {
455 (0.0, c, x)
456 } else if h < 4.0 {
457 (0.0, x, c)
458 } else if h < 5.0 {
459 (x, 0.0, c)
460 } else {
461 (c, 0.0, x)
462 };
463 Vec4::new(r + m, g + m, b + m, 1.0)
464}
465
466#[cfg(test)]
469mod tests {
470 use super::*;
471
472 fn approx(a: f64, b: f64, eps: f64) -> bool {
473 (a - b).abs() < eps
474 }
475
476 #[test]
477 fn complex_arithmetic() {
478 let a = Complex::new(1.0, 2.0);
479 let b = Complex::new(3.0, -1.0);
480 let sum = a + b;
481 assert!(approx(sum.re, 4.0, 1e-12));
482 assert!(approx(sum.im, 1.0, 1e-12));
483 let prod = a * b;
484 assert!(approx(prod.re, 5.0, 1e-12));
486 assert!(approx(prod.im, 5.0, 1e-12));
487 }
488
489 #[test]
490 fn complex_exp_ln() {
491 let z = Complex::new(1.0, PI);
492 let e = z.exp();
493 assert!(approx(e.re, -std::f64::consts::E, 1e-10));
495 assert!(approx(e.im, 0.0, 1e-10));
496 }
497
498 #[test]
499 fn zeta_at_2() {
500 let z = zeta(Complex::new(2.0, 0.0));
502 assert!(approx(z.re, PI * PI / 6.0, 1e-6));
503 assert!(approx(z.im, 0.0, 1e-6));
504 }
505
506 #[test]
507 fn zeta_at_minus_1() {
508 let z = zeta(Complex::new(-1.0, 0.0));
510 assert!(approx(z.re, -1.0 / 12.0, 1e-4));
511 assert!(approx(z.im, 0.0, 1e-4));
512 }
513
514 #[test]
515 fn zeta_at_4() {
516 let z = zeta(Complex::new(4.0, 0.0));
518 assert!(approx(z.re, PI.powi(4) / 90.0, 1e-6));
519 assert!(z.im.abs() < 1e-6);
520 }
521
522 #[test]
523 fn first_zero_approx() {
524 let zeros = find_zeros(13.0, 16.0, 2000);
526 assert!(!zeros.is_empty(), "should find at least one zero");
527 assert!(
528 approx(zeros[0], 14.134725, 0.01),
529 "first zero should be near 14.1347, got {}",
530 zeros[0]
531 );
532 }
533
534 #[test]
535 fn z_function_sign_change() {
536 let a = z_function(14.0);
538 let b = z_function(14.5);
539 assert!(
540 a * b < 0.0,
541 "Z should change sign between 14 and 14.5: Z(14)={}, Z(14.5)={}",
542 a,
543 b
544 );
545 }
546
547 #[test]
548 fn critical_strip_renderer() {
549 let r = CriticalStripRenderer::new((0.0, 1.0), (-5.0, 5.0), (5, 10));
550 let cells = r.render();
551 assert_eq!(cells.len(), 50);
552 }
553
554 #[test]
555 fn zero_marker_produces_glyphs() {
556 let marker = ZeroMarker::new(Vec3::ZERO, 10.0);
557 let glyphs = marker.mark_zeros(13.0, 16.0, 2000);
558 assert!(!glyphs.is_empty());
559 }
560}