Skip to content

Commit

Permalink
Add a way to plot the output from generators
Browse files Browse the repository at this point in the history
For visualization, add a simple script for generating scatter plots and
a binary (via examples) to plot the inputs given various domains.
  • Loading branch information
tgross35 committed Dec 22, 2024
1 parent 445b19b commit f25c8e9
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
Cargo.lock
musl/
**.tar.gz

# Files generated by binaries
build/
58 changes: 58 additions & 0 deletions crates/libm-test/examples/plot_domains.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Program to write all inputs from a generator to a file, then invoke a Julia script
//! to plot them. Requires Julia with the `CairoMakie` dependency.
//!
//! Note that running in release mode by default generates a _lot_ more datapoints, which
//! causes plotting to be extremely slow (some simplification to be done in the script).
use std::io::{BufWriter, Write};
use std::path::Path;
use std::process::Command;
use std::{env, fs};

use libm_test::domain::Domain;
use libm_test::gen::domain_logspace;

const JL_PLOT: &str = "examples/plot_file.jl";

fn main() {
let out_dir = Path::new("build");
if !out_dir.exists() {
fs::create_dir(out_dir).unwrap();
}

let jl_script = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(JL_PLOT);
let mut j_args = Vec::new();

// Plot a few domains with some functions that use them.
plot_one(out_dir, "sqrt", Domain::SQRT, &mut j_args);
plot_one(out_dir, "cos", Domain::TRIG, &mut j_args);
plot_one(out_dir, "cbrt", Domain::UNBOUNDED, &mut j_args);

println!("launching script");
let mut cmd = Command::new("julia");
if !cfg!(debug_assertions) {
cmd.arg("-O3");
}
cmd.arg(jl_script).args(j_args).status().unwrap();
}

/// Plot a single domain.
fn plot_one(out_dir: &Path, name: &str, domain: Domain<f32>, j_args: &mut Vec<String>) {
let base_name = out_dir.join(format!("domain-inputs-{name}"));
let text_file = base_name.with_extension("txt");

{
// Scope for file and writer
let f = fs::File::create(&text_file).unwrap();
let mut w = BufWriter::new(f);

for input in domain_logspace::get_test_cases_inner::<f32>(domain) {
writeln!(w, "{:e}", input.0).unwrap();
}
w.flush().unwrap();
}

// The julia script expects `name1 path1 name2 path2...` args
j_args.push(name.to_owned());
j_args.push(base_name.to_str().unwrap().to_owned());
}
113 changes: 113 additions & 0 deletions crates/libm-test/examples/plot_file.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"A quick script to for plotting a list of floats.
Takes a list of floats and a real function, plots both on a graph in both
linear and log scale. Requires [Makie] (specifically CairoMakie) for plotting.
[Makie]: https://docs.makie.org/stable/
"

using CairoMakie

CairoMakie.activate!(px_per_unit=10)

"Apply a function, returning the default if there is a domain error"
function map_or_default(
input::AbstractFloat,
f::Function,
default::AbstractFloat
)::AbstractFloat
try
return f(input)
catch
return default
end
end

"Read inputs from a file, create both linear and log plots"
function plot_one(
fig::Figure,
base_name::String,
fn_name::String,
f::Function;
xlims::Union{Tuple{Any,Any},Nothing},
xlims_log::Union{Tuple{Any,Any},Nothing},
)::Nothing
float_file = "$base_name.txt"
lin_out_file = "$base_name.png"
log_out_file = "$base_name-log.png"

if xlims === nothing
xlims = (-6, 6)
end
if xlims_log === nothing
xlims_log = (xlims[1] * 500, xlims[2] * 500)
end

inputs = readlines(float_file)

# Parse floats
x = map((v) -> parse(Float32, v), inputs)
# Apply function to the test points
y = map((v) -> map_or_default(v, f, 0.0), x)

# Plot the scatter plot of our checked values as well as the continuous function
ax = Axis(fig[1, 1], limits=(xlims, nothing), title=fn_name)
lines!(ax, xlims[1] .. xlims[2], f, color=(:blue, 0.4))
scatter!(ax, x, y, color=(:green, 0.3))
save(lin_out_file, fig)
delete!(ax)

# Same as above on a log scale
ax = Axis(
fig[1, 1],
limits=(xlims_log, nothing),
xscale=Makie.pseudolog10,
title="$fn_name (log scale)"
)
lines!(ax, xlims_log[1] .. xlims_log[2], f, color=(:blue, 0.4))
scatter!(ax, x, y, color=(:green, 0.3))
save(log_out_file, fig)
delete!(ax)
end

# Args alternate `name1 path1 name2 path2`
fn_names = ARGS[1:2:end]
base_names = ARGS[2:2:end]

for idx in eachindex(fn_names)
fn_name = fn_names[idx]
base_name = base_names[idx]

xlims = nothing
xlims_log = nothing

fig = Figure()

# Map string function names to callable functions
if fn_name == "cos"
f = cos
xlims_log = (-pi * 10, pi * 10)
elseif fn_name == "cbrt"
f = cbrt
xlims = (-2.0, 2.0)
elseif fn_name == "sqrt"
f = sqrt
xlims = (-1.1, 6.0)
xlims_log = (-1.1, 5000.0)
else
println("unrecognized function name `$fn_name`; update plot_file.jl")
end

println("plotting $fn_name")
plot_one(
fig,
base_name,
fn_name,
f,
xlims=xlims,
xlims_log=xlims_log,
)
end

base_name = ARGS[1]
fn_name = ARGS[2]

0 comments on commit f25c8e9

Please sign in to comment.