use num_bigint::BigUint;
use num_format::{Locale, ToFormattedString};
use plotters::coord::types::RangedCoordf64;
use plotters::prelude::*;
use rand::{Rng, SeedableRng, rngs::StdRng};
use std::sync::Arc;
use std::time::Instant;
use zeck::*;
const AXIS_FONT_SIZE: u32 = 100;
const AXIS_TICK_FONT_SIZE: u32 = 64;
const CAPTION_FONT_SIZE: u32 = 160;
const LEGEND_FONT_SIZE: u32 = 80;
const POINT_LABEL_FONT_SIZE: u32 = 40;
const CHART_MARGIN: u32 = 120;
const PLOT_WIDTH: u32 = 3840;
const PLOT_HEIGHT: u32 = 2160;
const LEGEND_MARGIN: u32 = 50;
const SERIES_LINE_STROKE_WIDTH: u32 = 3;
const SERIES_LINE_DOT_SIZE: u32 = 5;
const LEGEND_PATH_LEFT_OFFSET: i32 = 30;
const LEGEND_PATH_RIGHT_OFFSET: i32 = 10;
fn main() {
let start_time = Instant::now();
std::fs::create_dir_all("plots").expect("Failed to create plots directory");
plot_fibonacci_numbers("plots/fibonacci_plot_0_to_30.png", 0..31)
.expect("Failed to plot Fibonacci numbers");
plot_fibonacci_binary_all_ones("plots/fibonacci_binary_all_ones_0_to_30.png", 0..31)
.expect("Failed to plot Fibonacci, binary, and all-ones Zeckendorf numbers");
plot_fibonacci_binary_all_ones_power3(
"plots/fibonacci_binary_all_ones_power3_0_to_30.png",
0..31,
)
.expect("Failed to plot Fibonacci, binary, all-ones Zeckendorf, and 3^n numbers");
plot_fibonacci_binary_all_ones_power3_phi_phi_squared(
"plots/fibonacci_binary_all_ones_power3_phi_phi_squared_0_to_30.png",
0..31,
)
.expect("Failed to plot Fibonacci, binary, all-ones Zeckendorf, 3^n, φⁿ, and φ²ⁿ numbers");
_plot_all_square_root_error_convergences();
let end_time = Instant::now();
println!("Time taken: {:?}", end_time.duration_since(start_time));
}
fn _plot_all_square_root_error_convergences() {
plot_fibonacci_square_root_error_convergence(
"plots/fibonacci_square_root_error_convergence_start2_step2_log_y.png",
true,
NValueStrategy::Start2Step2,
)
.expect("Failed to plot Fibonacci square root error convergence");
plot_fibonacci_square_root_error_convergence(
"plots/fibonacci_square_root_error_convergence_start2_step2_linear_y.png",
false,
NValueStrategy::Start2Step2,
)
.expect("Failed to plot Fibonacci square root error convergence");
plot_fibonacci_square_root_error_convergence(
"plots/fibonacci_square_root_error_convergence_start1_step2_linear_y.png",
false,
NValueStrategy::Start1Step2,
)
.expect("Failed to plot Fibonacci square root error convergence");
plot_fibonacci_square_root_error_convergence(
"plots/fibonacci_square_root_error_convergence_start1_step1_linear_y.png",
false,
NValueStrategy::Start1Step1,
)
.expect("Failed to plot Fibonacci square root error convergence");
}
fn _plot_all_compression_ratios() {
_plot_compression_ratios("plots/compression_ratios_0_to_100.png", 0..100)
.expect("Failed to plot compression ratios");
_plot_compression_ratios("plots/compression_ratios_0_to_257.png", 0..257)
.expect("Failed to plot compression ratios");
_plot_compression_ratios("plots/compression_ratios_0_to_1_000.png", 0..1_000)
.expect("Failed to plot compression ratios");
_plot_compression_ratios("plots/compression_ratios_0_to_10_000.png", 0..10_000)
.expect("Failed to plot compression ratios");
_plot_compression_ratios("plots/compression_ratios_0_to_100_000.png", 0..100_000)
.expect("Failed to plot compression ratios");
_plot_compression_ratios("plots/compression_ratios_0_to_1_000_000.png", 0..1_000_000)
.expect("Failed to plot compression ratios");
_plot_compression_ratios(
"plots/compression_ratios_0_to_10_000_000.png",
0..10_000_000,
)
.expect("Failed to plot compression ratios");
_plot_compression_ratios(
"plots/compression_ratios_0_to_100_000_000.png",
0..100_000_000,
)
.expect("Failed to plot compression ratios");
}
fn _plot_all_histograms() {
for i in 3..=6 {
let samples = 10_u64.pow(i as u32);
_plot_compressed_bits_histogram(
&format!("plots/compressed_bits_histogram_{samples}_random_u64s.png"),
samples as usize,
)
.expect("Failed to plot compressed bits histogram");
}
}
fn plot_fibonacci_numbers(
filename: &str,
range: std::ops::Range<u64>,
) -> Result<(), Box<dyn std::error::Error>> {
let start_time = Instant::now();
println!("Plotting Fibonacci numbers for range {:?}", range);
let root = BitMapBackend::new(filename, (PLOT_WIDTH, PLOT_HEIGHT)).into_drawing_area();
root.fill(&WHITE)?;
let max_fib = range
.clone()
.map(|i| {
let fib = memoized_fast_doubling_fibonacci_biguint(i);
biguint_to_u64(&fib)
})
.max()
.unwrap_or(1) as f64;
let mut chart = ChartBuilder::on(&root)
.caption(
"Fibonacci Numbers (Log Scale)",
("sans-serif", CAPTION_FONT_SIZE).into_font(),
)
.margin(CHART_MARGIN)
.x_label_area_size(200)
.y_label_area_size(300)
.build_cartesian_2d(
range.start as f64..range.end as f64,
(1f64..max_fib).log_scale(),
)?;
let axis_label_style =
TextStyle::from(("sans-serif", AXIS_FONT_SIZE).into_font()).color(&BLACK);
let axis_tick_style =
TextStyle::from(("sans-serif", AXIS_TICK_FONT_SIZE).into_font()).color(&BLACK);
chart
.configure_mesh()
.x_desc("Fibonacci Index")
.y_desc("Fibonacci Number")
.label_style(axis_tick_style)
.axis_desc_style(axis_label_style)
.draw()?;
let data: Vec<(f64, f64)> = range
.clone()
.map(|i| {
let fib = memoized_fast_doubling_fibonacci_biguint(i);
let fib_u64 = biguint_to_u64(&fib);
(i as f64, fib_u64 as f64)
})
.filter(|(_, y)| *y > 0.0)
.collect();
chart
.draw_series(LineSeries::new(
data.iter().copied(),
RED.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Fibonacci Numbers")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
RED.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart.draw_series(
data.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, RED.filled())),
)?;
for (x, y) in &data {
let label = format!("({:.0}, {:.0})", x, y);
let text_x = *x + 0.3;
let text_y = *y * 1.0;
chart.draw_series(std::iter::once(Text::new(
label,
(text_x, text_y),
("sans-serif", POINT_LABEL_FONT_SIZE).into_font(),
)))?;
}
chart
.configure_series_labels()
.margin(LEGEND_MARGIN)
.label_font(("sans-serif", LEGEND_FONT_SIZE).into_font())
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()?;
root.present()?;
println!("Fibonacci plot saved to {}", filename);
let end_time = Instant::now();
println!(
"Time taken to plot Fibonacci numbers for range {:?}: {:?}",
range,
end_time.duration_since(start_time)
);
Ok(())
}
fn plot_fibonacci_binary_all_ones(
filename: &str,
range: std::ops::Range<u64>,
) -> Result<(), Box<dyn std::error::Error>> {
let start_time = Instant::now();
println!(
"Plotting Fibonacci, binary, and all-ones Zeckendorf numbers for range {:?}",
range
);
let fibonacci_data: Vec<(f64, f64)> = range
.clone()
.filter_map(|i| {
let fib = memoized_fast_doubling_fibonacci_biguint(i);
let fib_f64 = biguint_to_approximate_f64(&fib);
if fib_f64 > 0.0 && fib_f64.is_finite() {
Some((i as f64, fib_f64))
} else {
None
}
})
.collect();
let binary_data: Vec<(f64, f64)> = range
.clone()
.map(|i| {
let binary_value = 2_f64.powi(i as i32);
(i as f64, binary_value)
})
.filter(|(_, y)| *y > 0.0 && y.is_finite())
.collect();
let all_ones_data: Vec<(f64, f64)> = range
.clone()
.filter_map(|i| {
if i == 0 {
return None; }
let all_ones_biguint = all_ones_zeckendorf_to_biguint(i as usize);
let all_ones_f64 = biguint_to_approximate_f64(&all_ones_biguint);
if all_ones_f64 > 0.0 && all_ones_f64.is_finite() {
Some((i as f64, all_ones_f64))
} else {
None
}
})
.collect();
let max_value = fibonacci_data
.iter()
.chain(binary_data.iter())
.chain(all_ones_data.iter())
.map(|(_, y)| *y)
.fold(1.0f64, |acc, y| acc.max(y));
let root = BitMapBackend::new(filename, (PLOT_WIDTH, PLOT_HEIGHT)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption(
"Fibonacci, Binary, and All-Ones Zeckendorf Numbers (Log Scale)",
("sans-serif", CAPTION_FONT_SIZE).into_font(),
)
.margin(CHART_MARGIN)
.x_label_area_size(260)
.y_label_area_size(300)
.build_cartesian_2d(
range.start as f64..range.end as f64,
(1f64..max_value).log_scale(),
)?;
let axis_label_style =
TextStyle::from(("sans-serif", AXIS_FONT_SIZE).into_font()).color(&BLACK);
let axis_tick_style =
TextStyle::from(("sans-serif", AXIS_TICK_FONT_SIZE).into_font()).color(&BLACK);
let y_label_formatter = |y: &f64| {
if *y == 0.0 {
"0".to_string()
} else {
let exponent = y.log10().floor() as i32;
let mantissa = y / 10_f64.powi(exponent);
let rounded_mantissa = mantissa.round();
if (mantissa - rounded_mantissa).abs() < 1e-10 {
format!("{}e{}", rounded_mantissa as i64, exponent)
} else {
format!("{:.1}e{}", mantissa, exponent)
}
}
};
chart
.configure_mesh()
.x_desc("Input n")
.y_desc("Number Value (Log Scale)")
.y_label_formatter(&y_label_formatter)
.label_style(axis_tick_style)
.axis_desc_style(axis_label_style)
.draw()?;
chart
.draw_series(LineSeries::new(
fibonacci_data.iter().copied(),
RED.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Fibonacci Numbers F(n)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
RED.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
binary_data.iter().copied(),
BLUE.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Binary Numbers 2^n")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
BLUE.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
all_ones_data.iter().copied(),
GREEN.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("All-Ones Zeckendorf (n ones)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
GREEN.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart.draw_series(
fibonacci_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, RED.filled())),
)?;
chart.draw_series(
binary_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, BLUE.filled())),
)?;
chart.draw_series(
all_ones_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, GREEN.filled())),
)?;
chart
.configure_series_labels()
.position(SeriesLabelPosition::LowerRight)
.margin(LEGEND_MARGIN)
.label_font(("sans-serif", LEGEND_FONT_SIZE).into_font())
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()?;
root.present()?;
println!(
"Fibonacci, binary, and all-ones Zeckendorf plot saved to {}",
filename
);
let end_time = Instant::now();
println!(
"Time taken to plot for range {:?}: {:?}",
range,
end_time.duration_since(start_time)
);
Ok(())
}
fn plot_fibonacci_binary_all_ones_power3(
filename: &str,
range: std::ops::Range<u64>,
) -> Result<(), Box<dyn std::error::Error>> {
let start_time = Instant::now();
println!(
"Plotting Fibonacci, binary, all-ones Zeckendorf, and 3^n numbers for range {:?}",
range
);
let fibonacci_data: Vec<(f64, f64)> = range
.clone()
.filter_map(|i| {
let fib = memoized_fast_doubling_fibonacci_biguint(i);
let fib_f64 = biguint_to_approximate_f64(&fib);
if fib_f64 > 0.0 && fib_f64.is_finite() {
Some((i as f64, fib_f64))
} else {
None
}
})
.collect();
let binary_data: Vec<(f64, f64)> = range
.clone()
.map(|i| {
let binary_value = 2_f64.powi(i as i32);
(i as f64, binary_value)
})
.filter(|(_, y)| *y > 0.0 && y.is_finite())
.collect();
let all_ones_data: Vec<(f64, f64)> = range
.clone()
.filter_map(|i| {
if i == 0 {
return None; }
let all_ones_biguint = all_ones_zeckendorf_to_biguint(i as usize);
let all_ones_f64 = biguint_to_approximate_f64(&all_ones_biguint);
if all_ones_f64 > 0.0 && all_ones_f64.is_finite() {
Some((i as f64, all_ones_f64))
} else {
None
}
})
.collect();
let power3_data: Vec<(f64, f64)> = range
.clone()
.map(|i| {
let power3_value = 3_f64.powi(i as i32);
(i as f64, power3_value)
})
.filter(|(_, y)| *y > 0.0 && y.is_finite())
.collect();
let max_value = fibonacci_data
.iter()
.chain(binary_data.iter())
.chain(all_ones_data.iter())
.chain(power3_data.iter())
.map(|(_, y)| *y)
.fold(1.0f64, |acc, y| acc.max(y));
let root = BitMapBackend::new(filename, (PLOT_WIDTH, PLOT_HEIGHT)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption(
"Fibonacci, Binary, All-Ones Zeckendorf, and 3^n Numbers (Log Scale)",
("sans-serif", CAPTION_FONT_SIZE).into_font(),
)
.margin(CHART_MARGIN)
.x_label_area_size(260)
.y_label_area_size(300)
.build_cartesian_2d(
range.start as f64..range.end as f64,
(1f64..max_value).log_scale(),
)?;
let axis_label_style =
TextStyle::from(("sans-serif", AXIS_FONT_SIZE).into_font()).color(&BLACK);
let axis_tick_style =
TextStyle::from(("sans-serif", AXIS_TICK_FONT_SIZE).into_font()).color(&BLACK);
let y_label_formatter = |y: &f64| {
if *y == 0.0 {
"0".to_string()
} else {
let exponent = y.log10().floor() as i32;
let mantissa = y / 10_f64.powi(exponent);
let rounded_mantissa = mantissa.round();
if (mantissa - rounded_mantissa).abs() < 1e-10 {
format!("{}e{}", rounded_mantissa as i64, exponent)
} else {
format!("{:.1}e{}", mantissa, exponent)
}
}
};
chart
.configure_mesh()
.x_desc("Input n")
.y_desc("Number Value (Log Scale)")
.y_label_formatter(&y_label_formatter)
.label_style(axis_tick_style)
.axis_desc_style(axis_label_style)
.draw()?;
chart
.draw_series(LineSeries::new(
fibonacci_data.iter().copied(),
RED.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Fibonacci Numbers F(n)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
RED.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
binary_data.iter().copied(),
BLUE.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Binary Numbers 2^n")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
BLUE.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
all_ones_data.iter().copied(),
GREEN.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("All-Ones Zeckendorf (n ones)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
GREEN.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
power3_data.iter().copied(),
MAGENTA.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Powers of 3 (3^n)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
MAGENTA.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart.draw_series(
fibonacci_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, RED.filled())),
)?;
chart.draw_series(
binary_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, BLUE.filled())),
)?;
chart.draw_series(
all_ones_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, GREEN.filled())),
)?;
chart.draw_series(
power3_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, MAGENTA.filled())),
)?;
chart
.configure_series_labels()
.position(SeriesLabelPosition::LowerRight)
.margin(LEGEND_MARGIN)
.label_font(("sans-serif", LEGEND_FONT_SIZE).into_font())
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()?;
root.present()?;
println!(
"Fibonacci, binary, all-ones Zeckendorf, and 3^n plot saved to {}",
filename
);
let end_time = Instant::now();
println!(
"Time taken to plot for range {:?}: {:?}",
range,
end_time.duration_since(start_time)
);
Ok(())
}
fn plot_fibonacci_binary_all_ones_power3_phi_phi_squared(
filename: &str,
range: std::ops::Range<u64>,
) -> Result<(), Box<dyn std::error::Error>> {
let start_time = Instant::now();
println!(
"Plotting Fibonacci, binary, all-ones Zeckendorf, 3^n, φⁿ, and φ²ⁿ numbers for range {:?}",
range
);
let fibonacci_data: Vec<(f64, f64)> = range
.clone()
.filter_map(|i| {
let fib = memoized_fast_doubling_fibonacci_biguint(i);
let fib_f64 = biguint_to_approximate_f64(&fib);
if fib_f64 > 0.0 && fib_f64.is_finite() {
Some((i as f64, fib_f64))
} else {
None
}
})
.collect();
let binary_data: Vec<(f64, f64)> = range
.clone()
.map(|i| {
let binary_value = 2_f64.powi(i as i32);
(i as f64, binary_value)
})
.filter(|(_, y)| *y > 0.0 && y.is_finite())
.collect();
let all_ones_data: Vec<(f64, f64)> = range
.clone()
.filter_map(|i| {
if i == 0 {
return None; }
let all_ones_biguint = all_ones_zeckendorf_to_biguint(i as usize);
let all_ones_f64 = biguint_to_approximate_f64(&all_ones_biguint);
if all_ones_f64 > 0.0 && all_ones_f64.is_finite() {
Some((i as f64, all_ones_f64))
} else {
None
}
})
.collect();
let power3_data: Vec<(f64, f64)> = range
.clone()
.map(|i| {
let power3_value = 3_f64.powi(i as i32);
(i as f64, power3_value)
})
.filter(|(_, y)| *y > 0.0 && y.is_finite())
.collect();
let phi_data: Vec<(f64, f64)> = range
.clone()
.map(|i| {
let phi_value = PHI.powi(i as i32);
(i as f64, phi_value)
})
.filter(|(_, y)| *y > 0.0 && y.is_finite())
.collect();
let phi_squared_data: Vec<(f64, f64)> = range
.clone()
.map(|i| {
let phi_squared_value = PHI_SQUARED.powi(i as i32);
(i as f64, phi_squared_value)
})
.filter(|(_, y)| *y > 0.0 && y.is_finite())
.collect();
let max_value = fibonacci_data
.iter()
.chain(binary_data.iter())
.chain(all_ones_data.iter())
.chain(power3_data.iter())
.chain(phi_data.iter())
.chain(phi_squared_data.iter())
.map(|(_, y)| *y)
.fold(1.0f64, |acc, y| acc.max(y));
let root = BitMapBackend::new(filename, (PLOT_WIDTH, PLOT_HEIGHT)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption(
"Various Number Sequences (Log Scale)",
("sans-serif", CAPTION_FONT_SIZE).into_font(),
)
.margin(CHART_MARGIN)
.x_label_area_size(260)
.y_label_area_size(300)
.build_cartesian_2d(
range.start as f64..range.end as f64,
(1f64..max_value).log_scale(),
)?;
let axis_label_style =
TextStyle::from(("sans-serif", AXIS_FONT_SIZE).into_font()).color(&BLACK);
let axis_tick_style =
TextStyle::from(("sans-serif", AXIS_TICK_FONT_SIZE).into_font()).color(&BLACK);
let y_label_formatter = |y: &f64| {
if *y == 0.0 {
"0".to_string()
} else {
let exponent = y.log10().floor() as i32;
let mantissa = y / 10_f64.powi(exponent);
let rounded_mantissa = mantissa.round();
if (mantissa - rounded_mantissa).abs() < 1e-10 {
format!("{}e{}", rounded_mantissa as i64, exponent)
} else {
format!("{:.1}e{}", mantissa, exponent)
}
}
};
chart
.configure_mesh()
.x_desc("Input n")
.y_desc("Number Value (Log Scale)")
.y_label_formatter(&y_label_formatter)
.label_style(axis_tick_style)
.axis_desc_style(axis_label_style)
.draw()?;
chart
.draw_series(LineSeries::new(
power3_data.iter().copied(),
MAGENTA.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Powers of 3 (3^n)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
MAGENTA.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
phi_squared_data.iter().copied(),
CYAN.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Phi Squared to the n (φ²ⁿ)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
CYAN.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
all_ones_data.iter().copied(),
GREEN.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("All-Ones Zeckendorf (n ones)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
GREEN.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
binary_data.iter().copied(),
BLUE.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Binary Numbers 2^n")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
BLUE.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
phi_data.iter().copied(),
BLACK.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Phi to the n (φⁿ)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
BLACK.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart
.draw_series(LineSeries::new(
fibonacci_data.iter().copied(),
RED.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Fibonacci Numbers F(n)")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
RED.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
chart.draw_series(
fibonacci_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, RED.filled())),
)?;
chart.draw_series(
binary_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, BLUE.filled())),
)?;
chart.draw_series(
all_ones_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, GREEN.filled())),
)?;
chart.draw_series(
power3_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, MAGENTA.filled())),
)?;
chart.draw_series(
phi_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, BLACK.filled())),
)?;
chart.draw_series(
phi_squared_data
.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, CYAN.filled())),
)?;
chart
.configure_series_labels()
.position(SeriesLabelPosition::UpperLeft)
.margin(LEGEND_MARGIN)
.label_font(("sans-serif", LEGEND_FONT_SIZE).into_font())
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()?;
root.present()?;
println!(
"Fibonacci, binary, all-ones Zeckendorf, 3^n, φⁿ, and φ²ⁿ plot saved to {}",
filename
);
let end_time = Instant::now();
println!(
"Time taken to plot for range {:?}: {:?}",
range,
end_time.duration_since(start_time)
);
Ok(())
}
fn _plot_compression_ratios(
filename: &str,
range: std::ops::Range<u64>,
) -> Result<(), Box<dyn std::error::Error>> {
use num_format::{Locale, ToFormattedString};
let start_time = Instant::now();
println!("Plotting compression ratios for range {:?}", range);
let root = BitMapBackend::new(filename, (PLOT_WIDTH, PLOT_HEIGHT)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption(
format!(
"Zeckendorf Compression Ratios from {} to {}",
range.start,
range.end.to_formatted_string(&Locale::en)
),
("sans-serif", CAPTION_FONT_SIZE).into_font(),
)
.margin(CHART_MARGIN)
.x_label_area_size(260)
.y_label_area_size(260)
.build_cartesian_2d(range.start as f64..range.end as f64, 0.0f64..2.0f64)?;
let axis_label_style =
TextStyle::from(("sans-serif", AXIS_FONT_SIZE).into_font()).color(&BLACK);
let axis_tick_style =
TextStyle::from(("sans-serif", AXIS_TICK_FONT_SIZE).into_font()).color(&BLACK);
let x_label_formatter = |x: &f64| {
if *x >= 1000.0 {
let exponent = x.log10().floor() as i32;
let mantissa = x / 10_f64.powi(exponent);
let rounded_mantissa = mantissa.round();
if (mantissa - rounded_mantissa).abs() < 1e-10 {
format!("{}e{}", rounded_mantissa as i64, exponent)
} else {
format!("{:.1}e{}", mantissa, exponent)
}
} else {
format!("{:.0}", x)
}
};
chart
.configure_mesh()
.x_desc("Input Value")
.y_desc("Compression Ratio (Compressed / Original)")
.x_label_formatter(&x_label_formatter)
.label_style(axis_tick_style)
.axis_desc_style(axis_label_style)
.draw()?;
let data: Vec<(f64, f64)> = range
.clone()
.filter_map(|i| {
let original_number = BigUint::from(i);
let original_bit_size = original_number.bits() as f64;
let data_bytes = original_number.to_bytes_be();
let compressed_as_zeckendorf_data =
padless_zeckendorf_compress_be_dangerous(&data_bytes);
let compressed_as_bigint = BigUint::from_bytes_le(&compressed_as_zeckendorf_data);
let compressed_bit_size = compressed_as_bigint.bits() as f64;
if original_bit_size > 0.0 {
Some((i as f64, compressed_bit_size / original_bit_size))
} else {
None
}
})
.collect();
const THINNER_SERIES_LINE_STROKE_WIDTH: u32 = 1;
chart
.draw_series(LineSeries::new(
data,
BLUE.stroke_width(THINNER_SERIES_LINE_STROKE_WIDTH),
))?
.label("Compression Ratio")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
BLUE.stroke_width(THINNER_SERIES_LINE_STROKE_WIDTH),
)
});
chart.draw_series(LineSeries::new(
vec![(range.start as f64, 1.0), (range.end as f64, 1.0)],
GREEN.mix(0.5).stroke_width(SERIES_LINE_STROKE_WIDTH),
))?;
chart
.configure_series_labels()
.position(SeriesLabelPosition::LowerRight)
.margin(LEGEND_MARGIN)
.label_font(("sans-serif", LEGEND_FONT_SIZE).into_font())
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()?;
root.present()?;
println!("Compression ratio plot saved to {}", filename);
let end_time = Instant::now();
println!(
"Time taken to plot compression ratios for range {:?}: {:?}",
range,
end_time.duration_since(start_time)
);
Ok(())
}
fn biguint_to_u64(value: &Arc<BigUint>) -> u64 {
let digits = value.to_u64_digits();
if digits.len() == 1 {
digits[0]
} else if digits.is_empty() {
0
} else {
panic!("Fibonacci value too large to fit in u64");
}
}
fn biguint_to_approximate_f64(value: &BigUint) -> f64 {
let digits = value.to_u64_digits();
if digits.len() == 1 {
digits[0] as f64
} else if digits.is_empty() {
0.0
} else {
let bits = value.bits() as f64;
let capped_bits = bits.min(1023.0);
2_f64.powf(capped_bits)
}
}
fn _calculate_mean(values: &[u64]) -> f64 {
if values.is_empty() {
return 0.0;
}
values.iter().sum::<u64>() as f64 / values.len() as f64
}
fn _calculate_median(values: &[u64]) -> f64 {
if values.is_empty() {
return 0.0;
}
let mut sorted_values = values.to_vec();
sorted_values.sort();
let mid = sorted_values.len() / 2;
if sorted_values.len().is_multiple_of(2) {
(sorted_values[mid - 1] + sorted_values[mid]) as f64 / 2.0
} else {
sorted_values[mid] as f64
}
}
fn _calculate_standard_deviation(values: &[u64], mean: f64) -> f64 {
if values.is_empty() || values.len() == 1 {
return 0.0;
}
let variance = values
.iter()
.map(|&value| {
let diff = value as f64 - mean;
diff * diff
})
.sum::<f64>()
/ (values.len() - 1) as f64;
variance.sqrt()
}
#[allow(clippy::too_many_arguments)]
fn _draw_text_box<'a>(
chart: &mut ChartContext<'a, BitMapBackend<'a>, Cartesian2d<RangedCoordf64, RangedCoordf64>>,
lines: &[String],
box_top_right: (f64, f64),
x_range: f64,
y_max_val: f64,
box_width_fraction: f64,
margin_right_fraction: f64,
margin_top_fraction: f64,
padding_x_fraction: f64,
padding_y_fraction: f64,
line_height_fraction: f64,
font_size: u32,
) -> Result<(), Box<dyn std::error::Error>> {
if lines.is_empty() {
return Ok(());
}
let (x_max, _) = box_top_right;
let num_lines = lines.len() as f64;
let box_width = x_range * box_width_fraction;
let box_left = x_max - box_width - (x_range * margin_right_fraction);
let box_right = x_max - (x_range * margin_right_fraction);
let box_top = y_max_val - (y_max_val * margin_top_fraction);
let text_x = box_left + (x_range * padding_x_fraction);
let line_height = y_max_val * line_height_fraction;
let text_y_start = box_top - (y_max_val * padding_y_fraction);
let box_height = (y_max_val * padding_y_fraction * 2.0) + (line_height * num_lines);
let box_bottom = box_top - box_height;
chart.draw_series(std::iter::once(Rectangle::new(
[(box_left, box_bottom), (box_right, box_top)],
WHITE.mix(0.95).filled(),
)))?;
chart.draw_series(std::iter::once(PathElement::new(
vec![
(box_left, box_bottom),
(box_right, box_bottom),
(box_right, box_top),
(box_left, box_top),
(box_left, box_bottom),
],
BLACK.stroke_width(2),
)))?;
for (i, line) in lines.iter().enumerate() {
let y_pos = text_y_start - (line_height * (i as f64));
chart.draw_series(std::iter::once(Text::new(
line.clone(),
(text_x, y_pos),
(FontFamily::Monospace, font_size).into_font(),
)))?;
}
Ok(())
}
fn _plot_compressed_bits_histogram(
filename: &str,
count: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let start_time = Instant::now();
println!(
"Plotting compressed bits histogram from {} random 64-bit integers",
count
);
let seed = [42u8; 32]; let mut rng = StdRng::from_seed(seed);
let random_numbers: Vec<u64> = (0..count).map(|_| rng.random::<u64>()).collect();
let mut bit_counts: Vec<u64> = Vec::with_capacity(count);
for number in &random_numbers {
let biguint = BigUint::from(*number);
let data_bytes = biguint.to_bytes_le();
let compressed_data = padless_zeckendorf_compress_le_dangerous(&data_bytes);
let compressed_biguint = BigUint::from_bytes_le(&compressed_data);
let bits = compressed_biguint.bits();
bit_counts.push(bits);
}
let min_bits = *bit_counts.iter().min().unwrap_or(&0);
let max_bits = *bit_counts.iter().max().unwrap_or(&0);
let bucket_count = (max_bits - min_bits + 1) as usize;
let mut histogram: Vec<u64> = vec![0; bucket_count];
for &bits in &bit_counts {
let bucket_index = (bits - min_bits) as usize;
histogram[bucket_index] += 1;
}
let max_frequency = *histogram.iter().max().unwrap_or(&1) as f64;
let mean = _calculate_mean(&bit_counts);
let median = _calculate_median(&bit_counts);
let std_dev = _calculate_standard_deviation(&bit_counts, mean);
let root = BitMapBackend::new(filename, (PLOT_WIDTH, PLOT_HEIGHT)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption(
format!(
"Histogram of Compressed Bit Counts ({count} Random u64's)",
count = count.to_formatted_string(&Locale::en)
),
("sans-serif", 110.0).into_font(),
)
.margin(CHART_MARGIN)
.x_label_area_size(260)
.y_label_area_size(300)
.build_cartesian_2d(
(min_bits as f64 - 0.5)..(max_bits as f64 + 0.5),
0.0..(max_frequency * 1.1),
)?;
let axis_label_style =
TextStyle::from(("sans-serif", AXIS_FONT_SIZE).into_font()).color(&BLACK);
let axis_tick_style =
TextStyle::from(("sans-serif", AXIS_TICK_FONT_SIZE).into_font()).color(&BLACK);
let axis_label_formatter = |value: &f64| {
let rounded = value.round() as u64;
rounded.to_formatted_string(&Locale::en)
};
chart
.configure_mesh()
.x_desc("Compressed Bit Count")
.y_desc("Frequency")
.x_label_formatter(&axis_label_formatter)
.y_label_formatter(&axis_label_formatter)
.label_style(axis_tick_style)
.axis_desc_style(axis_label_style)
.draw()?;
const BAR_WIDTH: f64 = 0.8; const BAR_GAP: f64 = (1.0 - BAR_WIDTH) / 2.0;
for (bucket_index, &frequency) in histogram.iter().enumerate() {
if frequency > 0 {
let bits_value = min_bits + bucket_index as u64;
let bar_left = bits_value as f64 - 0.5 + BAR_GAP;
let bar_right = bits_value as f64 - 0.5 + BAR_GAP + BAR_WIDTH;
let bar_height = frequency as f64;
chart.draw_series(std::iter::once(Rectangle::new(
[(bar_left, 0.0), (bar_right, bar_height)],
BLUE.filled(),
)))?;
}
}
let x_max = max_bits as f64 + 0.5;
let y_max = max_frequency * 1.1;
let x_range = x_max - (min_bits as f64 - 10.0);
let stats_lines = vec![
format!("Mean: {:.2}", mean),
format!("Median: {:.2}", median),
format!("Std: {:.2}", std_dev),
format!("Min: {:.0}", min_bits),
format!("Max: {:.0}", max_bits),
];
_draw_text_box(
&mut chart,
&stats_lines,
(x_max, y_max),
x_range,
y_max,
0.15, 0.025, 0.015, 0.015, 0.015, 0.04, LEGEND_FONT_SIZE,
)?;
root.present()?;
println!("Compressed bits histogram saved to {}", filename);
let end_time = Instant::now();
println!(
"Time taken to plot compressed bits histogram: {:?}",
end_time.duration_since(start_time)
);
Ok(())
}
#[derive(Clone, Copy, Debug)]
enum NValueStrategy {
Start2Step2,
Start1Step2,
Start1Step1,
}
impl NValueStrategy {
fn generate_n_values(&self, count: u64) -> Vec<u64> {
match self {
NValueStrategy::Start2Step2 => (1..=count).map(|i| i * 2).collect(),
NValueStrategy::Start1Step2 => (0..count).map(|i| i * 2 + 1).collect(),
NValueStrategy::Start1Step1 => (1..=count).collect(),
}
}
}
fn plot_fibonacci_square_root_error_convergence(
filename: &str,
log_y: bool,
n_strategy: NValueStrategy,
) -> Result<(), Box<dyn std::error::Error>> {
let start_time = Instant::now();
println!(
"Plotting Fibonacci square root error convergence to {}",
filename
);
let n_values: Vec<u64> = match n_strategy {
NValueStrategy::Start2Step2 => n_strategy.generate_n_values(15),
NValueStrategy::Start1Step2 => n_strategy.generate_n_values(15),
NValueStrategy::Start1Step1 => n_strategy.generate_n_values(30),
};
let data: Vec<(f64, f64)> = n_values
.iter()
.filter_map(|&n| {
let fib_n = memoized_fast_doubling_fibonacci_biguint(n);
let fib_n_plus_2 = memoized_fast_doubling_fibonacci_biguint(n + 2);
let fib_n_plus_4 = memoized_fast_doubling_fibonacci_biguint(n + 4);
let fib_n_f64 = biguint_to_approximate_f64(&fib_n);
let fib_n_plus_2_f64 = biguint_to_approximate_f64(&fib_n_plus_2);
let fib_n_plus_4_f64 = biguint_to_approximate_f64(&fib_n_plus_4);
if fib_n_f64 <= 0.0
|| fib_n_plus_2_f64 <= 0.0
|| fib_n_plus_4_f64 <= 0.0
|| !fib_n_f64.is_finite()
|| !fib_n_plus_2_f64.is_finite()
|| !fib_n_plus_4_f64.is_finite()
{
return None;
}
let sqrt_fib_n = fib_n_f64.sqrt();
let sqrt_fib_n_plus_2 = fib_n_plus_2_f64.sqrt();
let sqrt_fib_n_plus_4 = fib_n_plus_4_f64.sqrt();
if sqrt_fib_n == 0.0 {
return None;
}
let ratio = sqrt_fib_n_plus_4 - sqrt_fib_n_plus_2 - sqrt_fib_n;
if ratio.is_finite() {
Some((n as f64, ratio))
} else {
None
}
})
.collect();
if data.is_empty() {
return Err("No valid data points calculated".into());
}
let min_y = data
.iter()
.map(|(_, y)| *y)
.fold(f64::INFINITY, |acc, y| acc.min(y));
println!("Min y: {}", min_y);
let max_y = data
.iter()
.map(|(_, y)| *y)
.fold(f64::NEG_INFINITY, |acc, y| acc.max(y));
println!("Max y: {}", max_y);
let y_padding = (max_y - min_y) * 0.1;
let y_min = min_y - y_padding;
println!("Calculated y min: {}", y_min);
let y_max = max_y + y_padding;
println!("Calculated y max: {}", y_max);
let root = BitMapBackend::new(filename, (PLOT_WIDTH, PLOT_HEIGHT)).into_drawing_area();
root.fill(&WHITE)?;
let axis_label_style =
TextStyle::from(("sans-serif", AXIS_FONT_SIZE).into_font()).color(&BLACK);
let axis_tick_style =
TextStyle::from(("sans-serif", AXIS_TICK_FONT_SIZE).into_font()).color(&BLACK);
let y_label_formatter = |y: &f64| {
if *y == 0.0 {
return "0".to_string();
}
let abs_y = y.abs();
if abs_y < 1e-12 {
return "0".to_string();
}
if abs_y < 1e-3 {
let sign = if *y < 0.0 { "-" } else { "" };
let exponent = abs_y.log10().floor() as i32;
let mantissa = abs_y / 10_f64.powi(exponent);
let rounded_mantissa = mantissa.round();
if (mantissa - rounded_mantissa).abs() < 1e-10 {
return format!("{}{}e{}", sign, rounded_mantissa as i64, exponent);
}
return format!("{}{:.1}e{}", sign, mantissa, exponent);
}
let formatted = format!("{:.3}", y);
formatted
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
};
macro_rules! draw_series_content {
($chart:ident) => {
$chart
.configure_mesh()
.x_desc("n")
.y_desc("Sqrt(F(n+4)) - Sqrt(F(n+2)) - Sqrt(F(n))")
.y_label_formatter(&y_label_formatter)
.label_style(axis_tick_style)
.axis_desc_style(axis_label_style)
.draw()?;
$chart
.draw_series(LineSeries::new(
data.iter().copied(),
BLUE.stroke_width(SERIES_LINE_STROKE_WIDTH),
))?
.label("Ratio")
.legend(|(x, y)| {
PathElement::new(
vec![
(x - LEGEND_PATH_LEFT_OFFSET, y),
(x + LEGEND_PATH_RIGHT_OFFSET, y),
],
BLUE.stroke_width(SERIES_LINE_STROKE_WIDTH),
)
});
$chart.draw_series(
data.iter()
.map(|point| Circle::new(*point, SERIES_LINE_DOT_SIZE, BLUE.filled())),
)?;
$chart
.configure_series_labels()
.position(SeriesLabelPosition::UpperRight)
.margin(LEGEND_MARGIN)
.label_font(("sans-serif", LEGEND_FONT_SIZE).into_font())
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()?;
};
}
let n_strategy_str = match n_strategy {
NValueStrategy::Start2Step2 => "Start at 2, Step by 2",
NValueStrategy::Start1Step2 => "Start at 1, Step by 2",
NValueStrategy::Start1Step1 => "Start at 1, Step by 1",
};
if log_y {
let log_y_range = (y_min.max(1e-10)..y_max).log_scale();
let mut chart = ChartBuilder::on(&root)
.caption(
format!("Fibonacci Square Root Error Convergence ({n_strategy_str}) (Log Y-Axis)"),
("sans-serif", 100).into_font(),
)
.margin(CHART_MARGIN)
.x_label_area_size(260)
.y_label_area_size(300)
.build_cartesian_2d(0.0f64..32.0f64, log_y_range)?;
draw_series_content!(chart);
} else {
let mut chart = ChartBuilder::on(&root)
.caption(
format!("Fibonacci Square Root Error Convergence ({n_strategy_str})"),
("sans-serif", 100).into_font(),
)
.margin(CHART_MARGIN)
.x_label_area_size(260)
.y_label_area_size(260)
.build_cartesian_2d(0.0f64..32.0f64, y_min..y_max)?;
draw_series_content!(chart);
}
root.present()?;
let end_time = Instant::now();
println!(
"Time taken to plot Fibonacci square root error convergence to {}: {:?}",
filename,
end_time.duration_since(start_time)
);
Ok(())
}