Skip to content

Commit 6ca933d

Browse files
authored
Merge pull request #25 from jlapeyre/more-more-iterators
Replace more allocation with iterators in `solve_tdgp` The PR reduces a great deal of allocation in `solve_tdgp`. On the level of `solve_tdgp`, everything is now done with iterators. No `Vec`s are allocated. * Add comments documenting data structures Some of the names could be better chosen here. Some of the `clone`s could very likely be removed.
2 parents 762cefc + 7bb9354 commit 6ca933d

File tree

4 files changed

+122
-91
lines changed

4 files changed

+122
-91
lines changed

src/gridsynth.rs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ use crate::unitary::DOmegaUnitary;
1515
use dashu_float::round::mode::{self, HalfEven};
1616
use dashu_float::{Context, FBig};
1717
use dashu_int::IBig;
18-
use log::{debug, info};
18+
19+
//use log::{debug, info};
20+
use log::debug;
21+
1922
use nalgebra::{Matrix2, Vector2};
2023
use std::cmp::Ordering;
2124
use std::time::{Duration, Instant};
@@ -117,6 +120,7 @@ impl Region for EpsilonRegion {
117120
let term2 = fb_with_prec(&self.z_y * u0.imag());
118121
let temp_sub = fb_with_prec(&self.d - &term1);
119122
let rhs = fb_with_prec(&temp_sub - &term2);
123+
// t0 <= t1
120124
let (t0, t1) = solve_quadratic(a.real(), b.real(), c.real())?;
121125
let zero = fb_with_prec(ib_to_bf_prec(IBig::ZERO));
122126

@@ -200,11 +204,14 @@ fn process_solution_candidate(mut z: DOmega, mut w_val: DOmega) -> DOmegaUnitary
200204
}
201205
}
202206

203-
fn process_solutions(
207+
fn process_solutions<I>(
204208
config: &mut GridSynthConfig,
205-
solutions: Vec<DOmega>,
209+
solutions: I,
206210
time_of_diophantine_dyadic: &mut Duration,
207-
) -> Option<DOmegaUnitary> {
211+
) -> Option<DOmegaUnitary>
212+
where
213+
I: Iterator<Item = DOmega>,
214+
{
208215
let start_diophantine = if config.measure_time {
209216
Some(Instant::now())
210217
} else {
@@ -303,7 +310,7 @@ fn search_for_solution(
303310
} else {
304311
None
305312
};
306-
let solution = solve_tdgp(
313+
let solutions = solve_tdgp(
307314
epsilon_region,
308315
unit_disk,
309316
&transformed.0,
@@ -312,23 +319,31 @@ fn search_for_solution(
312319
k,
313320
config.verbose,
314321
);
315-
if config.verbose {
316-
info!("k = {}, found {} candidates", k, solution.len());
317-
}
322+
// TODO: Reenable
323+
// if config.verbose {
324+
// // Warning! Printing the length will materialize a potentially large iterator.
325+
// let lensol = match &solutions {
326+
// None => 0,
327+
// Some(sols) => sols.len(),
328+
// };
329+
// info!("k = {}, found {} candidates", k, lensol);
330+
// }
318331
if let Some(start) = start_tdgp {
319332
time_of_solve_tdgp += start.elapsed();
320333
}
321-
322-
if let Some(result) = process_solutions(config, solution, &mut time_of_diophantine_dyadic) {
323-
if config.measure_time {
324-
debug!(
325-
"time of solve_TDGP: {:.3} ms",
326-
time_of_solve_tdgp.as_secs_f64() * 1000.0
327-
);
334+
if let Some(solutions) = solutions {
335+
if let Some(result) =
336+
process_solutions(config, solutions, &mut time_of_diophantine_dyadic)
337+
{
338+
if config.measure_time {
339+
debug!(
340+
"time of solve_TDGP: {:.3} ms",
341+
time_of_solve_tdgp.as_secs_f64() * 1000.0
342+
);
343+
}
344+
return result;
328345
}
329-
return result;
330346
}
331-
332347
k += 1;
333348
}
334349
}

src/math.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,17 @@ fn compute_logarithm(
130130
(n, r)
131131
}
132132

133+
/// Solves the quadratic equation ax^2 + bx + c = 0 for real roots.
134+
///
135+
/// # Arguments
136+
///
137+
/// - `a`: Coefficient of x^2, assumed to be non-zero for a valid quadratic equation.
138+
/// - `b`: Coefficient of x.
139+
/// - `c`: Constant term.
140+
///
141+
/// # Returns
142+
///
143+
/// An `Option` containing a tuple of two roots if they exist; otherwise, `None` if the roots are not real (i.e., the discriminant is negative).
133144
pub fn solve_quadratic(
134145
a: &FBig<HalfEven>,
135146
b: &FBig<HalfEven>,

src/odgp.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ pub fn solve_odgp_with_parity(
133133
.map(move |alpha| (alpha * ZRootTwo::new(IBig::ZERO, IBig::ONE)) + &p)
134134
}
135135

136+
pub fn first_solve_scaled_odgp(i: &Interval, j: &Interval, k: i64) -> Option<DRootTwo> {
137+
solve_scaled_odgp(i, j, k).next()
138+
}
139+
136140
pub fn solve_scaled_odgp(i: &Interval, j: &Interval, k: i64) -> impl Iterator<Item = DRootTwo> {
137141
let scale = pow_sqrt2(k);
138142
let neg_scale = -scale.clone();

src/tdgp.rs

Lines changed: 75 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,43 @@ use dashu_int::IBig;
77

88
use crate::common::{fb_with_prec, ib_to_bf_prec};
99
use crate::grid_op::GridOp;
10-
use crate::odgp::{solve_scaled_odgp, solve_scaled_odgp_with_parity_k_ne_0};
10+
use crate::odgp::{
11+
first_solve_scaled_odgp, solve_scaled_odgp, solve_scaled_odgp_with_parity_k_ne_0,
12+
};
1113
use crate::region::{Ellipse, Interval, Rectangle};
1214
use crate::ring::{DOmega, DRootTwo};
1315

16+
/// See Remark 5.4, page 5, Ross and Selinger arXiv:1403.2975v3
1417
pub trait Region {
18+
/// An ellipse bounding the region A
1519
fn ellipse(&self) -> Ellipse;
20+
21+
/// Returns `true` if `u` is inside the region A
1622
fn inside(&self, u: &DOmega) -> bool;
23+
24+
/// Intersection of the line with the region A
25+
/// Given L(t) = u + tv, return endpoints of the interval {t | L(t) ∈ A}
1726
fn intersect(&self, u: &DOmega, v: &DOmega) -> Option<(FBig<HalfEven>, FBig<HalfEven>)>;
1827
}
1928

20-
pub fn solve_tdgp(
21-
set_a: &impl Region,
22-
set_b: &impl Region,
23-
op_g: &GridOp,
24-
bbox_a: &Rectangle,
25-
bbox_b: &Rectangle,
29+
pub fn solve_tdgp<'a>(
30+
set_a: &'a impl Region,
31+
set_b: &'a impl Region,
32+
op_g: &'a GridOp,
33+
bbox_a: &'a Rectangle,
34+
bbox_b: &'a Rectangle,
2635
k: i64,
2736
_verbose: bool,
28-
) -> Vec<DOmega> {
29-
let mut sol_sufficient = Vec::with_capacity(100); // Pre-allocate reasonable capacity
30-
31-
let mut sol_x = solve_scaled_odgp(&bbox_a.x, &bbox_b.x, k + 1);
32-
33-
let alpha0 = match sol_x.next() {
34-
Some(val) => val,
35-
None => return vec![],
36-
};
37-
38-
let droot_zero = DRootTwo::from_int(IBig::ZERO);
37+
) -> Option<impl Iterator<Item = DOmega> + 'a> {
38+
let alpha0 = first_solve_scaled_odgp(&bbox_a.x, &bbox_b.x, k + 1)?;
3939
let _k_ibig = IBig::from(k);
4040
let dx = DRootTwo::power_of_inv_sqrt2(k);
4141
let op_g_inv_result = op_g.inv();
4242

4343
let op_g_inv = op_g_inv_result.unwrap();
4444
let zero_droottwo = DRootTwo::from_int(IBig::ZERO);
4545
let v = op_g_inv * DOmega::from_droottwo_vector(&dx, &zero_droottwo, k);
46-
47-
let v_conj_sq2 = v.conj_sq2();
46+
let v_conj_sq2 = v.conj_sq2().clone();
4847

4948
let bbox_a_new = bbox_a
5049
.y
@@ -54,63 +53,65 @@ pub fn solve_tdgp(
5453
.fatten(&(bbox_b.y.width() / ib_to_bf_prec(IBig::from(10000))));
5554
let sol_y = solve_scaled_odgp(&bbox_a_new, &bbox_b_new, k + 1);
5655

57-
for beta in sol_y {
58-
let dx = DRootTwo::power_of_inv_sqrt2(k);
59-
let z0 = op_g.inv().unwrap() * DOmega::from_droottwo_vector(&alpha0, &beta, k + 1);
60-
let v = op_g.inv().unwrap() * DOmega::from_droottwo_vector(&dx, &droot_zero, k);
61-
62-
let t_a = set_a.intersect(&z0, &v);
63-
let t_b = set_b.intersect(z0.conj_sq2(), v_conj_sq2);
64-
if t_a.is_none() || t_b.is_none() {
65-
continue;
66-
}
67-
let (t_a, t_b) = (t_a.unwrap(), t_b.unwrap());
68-
69-
let parity = (&beta - &alpha0).mul_by_sqrt2_power_renewing_denomexp(k);
70-
let (mut int_a, mut int_b) = (Interval::new(t_a.0, t_a.1), Interval::new(t_b.0, t_b.1));
71-
let dt_a = {
72-
let ten = ib_to_bf_prec(IBig::from(10));
56+
let sol_sufficient = sol_y.flat_map(move |y| {
57+
newproc(y, set_a, set_b, op_g, alpha0.clone(), v_conj_sq2.clone(), k)
58+
.into_iter()
59+
.flatten()
60+
});
7361

74-
let shift_k = IBig::ONE << (k as usize);
75-
let width_product = shift_k * int_b.width();
76-
let max_val = {
77-
if ten > width_product {
78-
&ten
79-
} else {
80-
&width_product
81-
}
82-
};
83-
fb_with_prec(&ten / max_val)
84-
};
85-
let dt_b = {
86-
let ten = ib_to_bf_prec(IBig::from(10));
87-
let shift_k = IBig::from(1) << (k as usize);
88-
let width_product = shift_k * int_a.width();
89-
let max_val = {
90-
if ten > width_product {
91-
&ten
92-
} else {
93-
&width_product
94-
}
95-
};
96-
fb_with_prec(&ten / max_val)
97-
};
62+
let solutions = sol_sufficient
63+
.map(|z| op_g.inv().unwrap() * z)
64+
.filter(|z| set_a.inside(z) && set_b.inside(z.conj_sq2()));
65+
Some(solutions)
66+
}
9867

99-
int_a = int_a.fatten(&dt_a);
100-
int_b = int_b.fatten(&dt_b);
68+
fn newproc<'a>(
69+
beta: DRootTwo,
70+
set_a: &'a impl Region,
71+
set_b: &'a impl Region,
72+
op_g: &'a GridOp,
73+
alpha0: DRootTwo,
74+
// alpha0: &'a DRootTwo,
75+
v_conj_sq2: DOmega,
76+
// v_conj_sq2: &'a DOmega,
77+
k: i64,
78+
) -> Option<impl Iterator<Item = DOmega> + 'a> {
79+
let alpha0 = alpha0.clone();
80+
let droot_zero = DRootTwo::from_int(IBig::ZERO);
81+
let dx = DRootTwo::power_of_inv_sqrt2(k);
82+
let z0 = op_g.inv().unwrap() * DOmega::from_droottwo_vector(&alpha0, &beta, k + 1);
83+
let v = op_g.inv().unwrap() * DOmega::from_droottwo_vector(&dx, &droot_zero, k);
10184

102-
let sol_t = solve_scaled_odgp_with_parity_k_ne_0(int_a, int_b, 1, &parity);
103-
let sol_x = sol_t.map(|alpha| alpha * dx.clone() + alpha0.clone());
104-
for alpha in sol_x {
105-
sol_sufficient.push(DOmega::from_droottwo_vector(&alpha, &beta, k));
106-
}
85+
let t_a = set_a.intersect(&z0, &v);
86+
let t_b = set_b.intersect(z0.conj_sq2(), &v_conj_sq2);
87+
if t_a.is_none() || t_b.is_none() {
88+
return None;
10789
}
90+
let (t_a, t_b) = (t_a.unwrap(), t_b.unwrap());
91+
92+
let parity = (&beta - &alpha0).mul_by_sqrt2_power_renewing_denomexp(k);
93+
let (mut int_a, mut int_b) = (Interval::new(t_a.0, t_a.1), Interval::new(t_b.0, t_b.1));
94+
let dt_a = get_dt_x(k, &int_b);
95+
let dt_b = get_dt_x(k, &int_a);
96+
int_a = int_a.fatten(&dt_a);
97+
int_b = int_b.fatten(&dt_b);
98+
99+
let sol_t = solve_scaled_odgp_with_parity_k_ne_0(int_a, int_b, 1, &parity);
100+
let sol_x = sol_t.map(move |alpha| alpha * dx.clone() + alpha0.clone());
101+
let sol_xx = sol_x.map(move |alpha| DOmega::from_droottwo_vector(&alpha, &beta, k));
102+
Some(sol_xx)
103+
}
108104

109-
let op_g_inv = op_g.inv().unwrap();
110-
111-
sol_sufficient
112-
.into_iter()
113-
.map(|z| op_g_inv.clone() * z)
114-
.filter(|z| set_a.inside(z) && set_b.inside(z.conj_sq2()))
115-
.collect()
105+
fn get_dt_x(k: i64, int_y: &Interval) -> FBig<HalfEven> {
106+
let ten = ib_to_bf_prec(IBig::from(10));
107+
let shift_k = IBig::from(1) << (k as usize);
108+
let width_product = shift_k * int_y.width();
109+
let max_val = {
110+
if ten > width_product {
111+
&ten
112+
} else {
113+
&width_product
114+
}
115+
};
116+
fb_with_prec(&ten / max_val)
116117
}

0 commit comments

Comments
 (0)