Skip to content

Commit 3900c20

Browse files
authored
Merge pull request #14 from jackyarndley/development
added in optimized series approximation
2 parents 7a7bc89 + 07c79ac commit 3900c20

File tree

10 files changed

+210
-240
lines changed

10 files changed

+210
-240
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
/target
22
**/*.rs.bk
33
.idea
4-
*.kfr
4+
*.kfr
5+
*.svg
6+
*.data
7+
*.old

Cargo.lock

Lines changed: 57 additions & 57 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ rug = "1.9.0"
1313
itertools = "^0.9.0"
1414

1515
#Additional commands that can improve performance (maybe by around 5-10%)
16-
#[profile.release]
16+
[profile.release]
1717
#lto = "fat"
1818
#codegen-units = 1
19+
#debug = true

output.png

-54.4 MB
Loading

output2.png

8.98 MB
Loading

src/bin/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ fn main() {
5353
println!("Zoom: {}", zoom);
5454

5555
let mut renderer = FractalRenderer::new(
56-
5000,
57-
5000,
56+
1000,
57+
1000,
5858
zoom,
5959
iterations.parse::<usize>().unwrap(),
6060
center_re,
6161
center_im,
6262
0.01,
6363
false,
64-
32
64+
0
6565
);
6666

6767
let time = Instant::now();

src/math/perturbation.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::util::PixelData;
1+
use crate::util::{PixelData, FloatExp};
22

33
use rayon::prelude::*;
44
use crate::math::reference::Reference;
@@ -7,12 +7,12 @@ pub struct Perturbation {}
77

88
impl Perturbation {
99
pub fn iterate(pixel_data: &mut Vec<PixelData>, reference: &Reference, reference_current_iteration: usize) {
10-
pixel_data.par_chunks_mut(4)
10+
pixel_data.par_chunks_mut(64)
1111
.for_each(|pixel_data| {
1212
for pixel in pixel_data {
1313
let mut scaled_iterations = 0;
14-
let mut scaled_scale_factor_1 = 2.0f64.powi(pixel.delta_current.exponent);
15-
let mut scaled_delta_reference = 2.0f64.powi(pixel.delta_reference.exponent - pixel.delta_current.exponent) * pixel.delta_reference.mantissa;
14+
let mut scaled_scale_factor_1 = 1.0f64.ldexp(pixel.delta_current.exponent);
15+
let mut scaled_delta_reference = 1.0f64.ldexp(pixel.delta_reference.exponent - pixel.delta_current.exponent) * pixel.delta_reference.mantissa;
1616

1717
while pixel.iteration < reference_current_iteration {
1818
let delta_current_float = scaled_scale_factor_1 * pixel.delta_current.mantissa;
@@ -43,8 +43,8 @@ impl Perturbation {
4343
// reset the scaled counter
4444
pixel.delta_current.reduce();
4545

46-
scaled_scale_factor_1 = 2.0f64.powi(pixel.delta_current.exponent);
47-
scaled_delta_reference = 2.0f64.powi(pixel.delta_reference.exponent - pixel.delta_current.exponent) * pixel.delta_reference.mantissa;
46+
scaled_scale_factor_1 = 1.0f64.ldexp(pixel.delta_current.exponent);
47+
scaled_delta_reference = 1.0f64.ldexp(pixel.delta_reference.exponent - pixel.delta_current.exponent) * pixel.delta_reference.mantissa;
4848

4949
scaled_iterations = 0;
5050
},
@@ -61,8 +61,8 @@ impl Perturbation {
6161
if scaled_iterations > 250 {
6262
pixel.delta_current.reduce();
6363

64-
scaled_scale_factor_1 = 2.0f64.powi(pixel.delta_current.exponent);
65-
scaled_delta_reference = 2.0f64.powi(pixel.delta_reference.exponent - pixel.delta_current.exponent) * pixel.delta_reference.mantissa;
64+
scaled_scale_factor_1 = 1.0f64.ldexp(pixel.delta_current.exponent);
65+
scaled_delta_reference = 1.0f64.ldexp(pixel.delta_reference.exponent - pixel.delta_current.exponent) * pixel.delta_reference.mantissa;
6666

6767
scaled_iterations = 0;
6868
}

src/math/series_approximation.rs

Lines changed: 77 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -46,88 +46,88 @@ impl SeriesApproximation {
4646
}
4747
}
4848

49-
pub fn step(&mut self) -> bool {
50-
self.z.square_mut();
51-
self.z += &self.c;
52-
53-
let z_fixed = to_fixed(&self.z);
54-
55-
if z_fixed.re.abs() < 1e-300 && z_fixed.im.abs() < 1e-300 {
56-
println!("found slow at: {}", self.current_iteration);
57-
}
49+
pub fn run(&mut self) {
50+
self.add_probe(ComplexExtended::new2(self.delta_top_left.mantissa.re, self.delta_top_left.mantissa.im, self.delta_top_left.exponent));
51+
self.add_probe(ComplexExtended::new2(self.delta_top_left.mantissa.re, -self.delta_top_left.mantissa.im, self.delta_top_left.exponent));
52+
self.add_probe(ComplexExtended::new2(-self.delta_top_left.mantissa.re, self.delta_top_left.mantissa.im, self.delta_top_left.exponent));
53+
self.add_probe(ComplexExtended::new2(-self.delta_top_left.mantissa.re, -self.delta_top_left.mantissa.im, self.delta_top_left.exponent));
5854

59-
self.next_coefficients[0] = to_extended(&self.z);
60-
self.next_coefficients[1] = self.coefficients[0] * self.coefficients[1] * 2.0 + ComplexExtended::new2(1.0, 0.0, 0);
55+
let add_value = ComplexExtended::new2(1.0, 0.0, 0);
6156

62-
// Calculate the new coefficents
63-
for k in 2..=self.order {
64-
let mut sum = self.coefficients[0] * self.coefficients[k];
57+
// Can be changed later into a better loop - this function could also return some more information
58+
while self.current_iteration < self.maximum_iteration {
59+
self.z.square_mut();
60+
self.z += &self.c;
6561

66-
for j in 1..=((k - 1) / 2) {
67-
sum += self.coefficients[j] * self.coefficients[k - j];
68-
}
69-
sum *= 2.0;
62+
let z_fixed = to_fixed(&self.z);
7063

71-
// If even, we include the mid term as well
72-
if k % 2 == 0 {
73-
sum += self.coefficients[k / 2] * self.coefficients[k / 2];
64+
if z_fixed.re.abs() < 1e-300 && z_fixed.im.abs() < 1e-300 {
65+
println!("found slow at: {}", self.current_iteration);
7466
}
7567

76-
sum.reduce();
77-
self.next_coefficients[k] = sum;
78-
}
79-
80-
// this section takes about half of the time
81-
for i in 0..self.original_probes.len() {
82-
// step the probe points using perturbation
83-
self.current_probes[i] = self.current_probes[i] * (self.coefficients[0] * 2.0 + self.current_probes[i]);
84-
self.current_probes[i] += self.original_probes[i];
85-
86-
// TODO this probably does not need to be done every iteration
87-
self.current_probes[i].reduce();
88-
89-
// get the new approximations
90-
let mut series_probe = self.next_coefficients[1] * self.approximation_probes[i][0];
91-
let mut derivative_probe = self.next_coefficients[1] * self.approximation_probes_derivative[i][0];
68+
self.next_coefficients[0] = to_extended(&self.z);
69+
self.next_coefficients[1] = self.coefficients[0] * self.coefficients[1] * 2.0 + add_value;
70+
self.next_coefficients[0].reduce();
71+
self.next_coefficients[1].reduce();
9272

73+
// Calculate the new coefficents
9374
for k in 2..=self.order {
94-
series_probe += self.next_coefficients[k] * self.approximation_probes[i][k - 1];
95-
derivative_probe += self.next_coefficients[k] * self.approximation_probes_derivative[i][k - 1];
96-
};
75+
let mut sum = self.coefficients[0] * self.coefficients[k];
9776

98-
let relative_error = (self.current_probes[i] - series_probe).norm_square();
99-
let mut derivative = derivative_probe.norm_square();
77+
for j in 1..=((k - 1) / 2) {
78+
sum += self.coefficients[j] * self.coefficients[k - j];
79+
}
80+
sum *= 2.0;
10081

101-
// Check to make sure that the derivative is greater than or equal to 1
102-
if derivative.to_float() < 1.0 {
103-
derivative = FloatExtended::new(1.0, 0);
104-
}
82+
// If even, we include the mid term as well
83+
if k % 2 == 0 {
84+
sum += self.coefficients[k / 2] * self.coefficients[k / 2];
85+
}
10586

106-
// Check that the error over the derivative is less than the pixel spacing
107-
if relative_error / derivative > self.delta_pixel_square {
108-
self.z -= &self.c;
109-
self.z.sqrt_mut();
110-
return false;
87+
sum.reduce();
88+
self.next_coefficients[k] = sum;
11189
}
112-
}
11390

114-
// If the approximation is valid, continue
115-
self.current_iteration += 1;
116-
117-
// Swap the two coefficients buffers
118-
swap(&mut self.coefficients, &mut self.next_coefficients);
119-
self.current_iteration < self.maximum_iteration
120-
}
91+
// this section takes about half of the time
92+
for i in 0..self.original_probes.len() {
93+
// step the probe points using perturbation
94+
self.current_probes[i] = self.current_probes[i] * (self.coefficients[0] * 2.0 + self.current_probes[i]);
95+
self.current_probes[i] += self.original_probes[i];
96+
97+
// TODO this probably does not need to be done every iteration
98+
self.current_probes[i].reduce();
99+
100+
// get the new approximations
101+
let mut series_probe = self.next_coefficients[1] * self.approximation_probes[i][0];
102+
let mut derivative_probe = self.next_coefficients[1] * self.approximation_probes_derivative[i][0];
103+
104+
for k in 2..=self.order {
105+
series_probe += self.next_coefficients[k] * self.approximation_probes[i][k - 1];
106+
derivative_probe += self.next_coefficients[k] * self.approximation_probes_derivative[i][k - 1];
107+
};
108+
109+
let relative_error = (self.current_probes[i] - series_probe).norm_square();
110+
let mut derivative = derivative_probe.norm_square();
111+
112+
// Check to make sure that the derivative is greater than or equal to 1
113+
if derivative.to_float() < 1.0 {
114+
derivative.mantissa = 1.0;
115+
derivative.exponent = 0;
116+
}
117+
118+
// Check that the error over the derivative is less than the pixel spacing
119+
if relative_error / derivative > self.delta_pixel_square {
120+
self.z -= &self.c;
121+
self.z.sqrt_mut();
122+
return;
123+
}
124+
}
121125

122-
pub fn run(&mut self) {
123-
self.add_probe(ComplexExtended::new2(self.delta_top_left.mantissa.re, self.delta_top_left.mantissa.im, self.delta_top_left.exponent));
124-
self.add_probe(ComplexExtended::new2(self.delta_top_left.mantissa.re, -self.delta_top_left.mantissa.im, self.delta_top_left.exponent));
125-
self.add_probe(ComplexExtended::new2(-self.delta_top_left.mantissa.re, self.delta_top_left.mantissa.im, self.delta_top_left.exponent));
126-
self.add_probe(ComplexExtended::new2(-self.delta_top_left.mantissa.re, -self.delta_top_left.mantissa.im, self.delta_top_left.exponent));
126+
// If the approximation is valid, continue
127+
self.current_iteration += 1;
127128

128-
// Can be changed later into a better loop - this function could also return some more information
129-
while self.step() {
130-
continue;
129+
// Swap the two coefficients buffers
130+
swap(&mut self.coefficients, &mut self.next_coefficients);
131131
}
132132
}
133133

@@ -136,23 +136,23 @@ impl SeriesApproximation {
136136
self.original_probes.push(delta_probe);
137137
self.current_probes.push(delta_probe);
138138

139-
let mut delta_probe_n = delta_probe;
139+
let mut current_value = delta_probe;
140140

141-
let mut delta_probe_n_2 = Vec::with_capacity(self.order + 1);
142-
let mut delta_probe_n_derivative_2 = Vec::with_capacity(self.order + 1);
141+
let mut delta_n = Vec::with_capacity(self.order + 1);
142+
let mut delta_derivative_n = Vec::with_capacity(self.order + 1);
143143

144144
// The first element will be 1, in order for the derivative to be calculated
145-
delta_probe_n_2.push(delta_probe_n);
146-
delta_probe_n_derivative_2.push(ComplexExtended::new2(1.0, 0.0, 0));
145+
delta_n.push(current_value);
146+
delta_derivative_n.push(ComplexExtended::new2(1.0, 0.0, 0));
147147

148148
for i in 1..=self.order {
149-
delta_probe_n_derivative_2.push(delta_probe_n * (i + 1) as f64);
150-
delta_probe_n *= delta_probe;
151-
delta_probe_n_2.push(delta_probe_n);
149+
delta_derivative_n.push(current_value * (i + 1) as f64);
150+
current_value *= delta_probe;
151+
delta_n.push(current_value);
152152
}
153153

154-
self.approximation_probes.push(delta_probe_n_2);
155-
self.approximation_probes_derivative.push(delta_probe_n_derivative_2);
154+
self.approximation_probes.push(delta_n);
155+
self.approximation_probes_derivative.push(delta_derivative_n);
156156
}
157157

158158
// Get the current reference, and the current number of iterations done

src/renderer.rs

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ use crate::util::{image::Image, ComplexFixed, ComplexArbitrary, PixelData, compl
22
use crate::math::{SeriesApproximation, Perturbation};
33

44
use std::time::Instant;
5-
use std::cmp::max;
5+
use std::cmp::{min, max};
66
use std::f64::consts::LOG2_10;
77

88
use rand::seq::SliceRandom;
9-
use itertools::Itertools;
109
use rayon::prelude::*;
1110

1211
pub struct FractalRenderer {
@@ -46,14 +45,21 @@ impl FractalRenderer {
4645
precision as u32,
4746
ComplexArbitrary::parse("(".to_owned() + center_real + "," + center_imag + ")").expect("Location is not valid!"));
4847

48+
let auto_approximation = if approximation_order == 0 {
49+
let auto = (((image_width * image_height) as f64).log(1e6).powf(6.619) * 16.0f64) as usize;
50+
min(max(auto, 3), 64)
51+
} else {
52+
approximation_order
53+
};
54+
4955
FractalRenderer {
5056
image_width,
5157
image_height,
5258
aspect,
5359
zoom,
5460
center_location,
5561
maximum_iteration,
56-
approximation_order,
62+
approximation_order: auto_approximation,
5763
glitch_tolerance,
5864
image: Image::new(image_width, image_height, display_glitches)
5965
}
@@ -95,27 +101,27 @@ impl FractalRenderer {
95101

96102
let time = Instant::now();
97103

98-
let indices = (0..self.image_width).cartesian_product(0..self.image_height);
99-
100-
let mut pixel_data = indices.into_iter().par_bridge()
101-
.map(|(i, j)| {
102-
let element = ComplexFixed::new(i as f64 * delta_pixel + delta_top_left.re, j as f64 * delta_pixel + delta_top_left.im);
103-
let point_delta = ComplexExtended::new(element, -self.zoom.exponent);
104-
let new_delta = series_approximation.evaluate(point_delta);
105-
106-
PixelData {
107-
image_x: i,
108-
image_y: j,
109-
iteration: reference.start_iteration,
110-
delta_centre: point_delta,
111-
delta_reference: point_delta,
112-
delta_start: new_delta,
113-
delta_current: new_delta,
114-
derivative_current: ComplexFixed::new(1.0, 0.0),
115-
glitched: false,
116-
escaped: false
117-
}
118-
}).collect::<Vec<PixelData>>();
104+
let mut pixel_data = (0..(self.image_width * self.image_height)).into_par_iter()
105+
.map(|index| {
106+
let i = index % self.image_width;
107+
let j = index / self.image_width;
108+
let element = ComplexFixed::new(i as f64 * delta_pixel + delta_top_left.re, j as f64 * delta_pixel + delta_top_left.im);
109+
let point_delta = ComplexExtended::new(element, -self.zoom.exponent);
110+
let new_delta = series_approximation.evaluate(point_delta);
111+
112+
PixelData {
113+
image_x: i,
114+
image_y: j,
115+
iteration: reference.start_iteration,
116+
delta_centre: point_delta,
117+
delta_reference: point_delta,
118+
delta_start: new_delta,
119+
delta_current: new_delta,
120+
derivative_current: ComplexFixed::new(1.0, 0.0),
121+
glitched: false,
122+
escaped: false
123+
}
124+
}).collect::<Vec<PixelData>>();
119125

120126
println!("{:<14}{:>6} ms", "Packing", time.elapsed().as_millis());
121127

@@ -148,18 +154,15 @@ impl FractalRenderer {
148154

149155
// this can be made faster, without having to do the series approximation again
150156
// this is done by storing more data in pixeldata2
151-
pixel_data.chunks_mut(1)
152-
.for_each(|pixel_data| {
153-
for data in pixel_data {
154-
data.iteration = reference.start_iteration;
155-
data.glitched = false;
156-
data.escaped = false;
157-
data.delta_current = data.delta_start - delta_z;
158-
data.delta_reference = data.delta_centre - reference_wrt_sa;
159-
// might not need the evaluate here as if we store it separately, there is no need
160-
data.derivative_current = ComplexFixed::new(1.0, 0.0);
161-
}
162-
});
157+
pixel_data.par_iter_mut()
158+
.for_each(|pixel| {
159+
pixel.iteration = reference.start_iteration;
160+
pixel.glitched = false;
161+
pixel.delta_current = pixel.delta_start - delta_z;
162+
pixel.delta_reference = pixel.delta_centre - reference_wrt_sa;
163+
// might not need the evaluate here as if we store it separately, there is no need
164+
// data.derivative_current = ComplexFixed::new(1.0, 0.0);
165+
});
163166

164167
Perturbation::iterate(&mut pixel_data, &r, r.current_iteration);
165168

0 commit comments

Comments
 (0)