Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 13f4c6c

Browse files
committed
Add a way to plot the output from generators
For visualization, add a simple script for generating scatter plots and a binary (via examples) to plot the inputs given various domains.
1 parent c84c3a7 commit 13f4c6c

File tree

3 files changed

+174
-0
lines changed

3 files changed

+174
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
Cargo.lock
88
musl/
99
**.tar.gz
10+
11+
# Files generated by binaries
12+
build/
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//! Program to write all inputs from a generator to a file, then invoke a Julia script
2+
//! to plot them. Requires Julia with the `CairoMakie` dependency.
3+
//!
4+
//! Note that running in release mode by default generates a _lot_ more datapoints, which
5+
//! causes plotting to be extremely slow (some simplification to be done in the script).
6+
7+
use std::io::{BufWriter, Write};
8+
use std::path::Path;
9+
use std::process::Command;
10+
use std::{env, fs};
11+
12+
use libm_test::domain::Domain;
13+
use libm_test::gen::domain_logspace;
14+
15+
const JL_PLOT: &str = "examples/plot_file.jl";
16+
17+
fn main() {
18+
let out_dir = Path::new("build");
19+
if !out_dir.exists() {
20+
fs::create_dir(out_dir).unwrap();
21+
}
22+
23+
let jl_script = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(JL_PLOT);
24+
let mut j_args = Vec::new();
25+
26+
// Plot a few domains with some functions that use them.
27+
plot_one(out_dir, "sqrt", Domain::SQRT, &mut j_args);
28+
plot_one(out_dir, "cos", Domain::TRIG, &mut j_args);
29+
plot_one(out_dir, "cbrt", Domain::UNBOUNDED, &mut j_args);
30+
31+
println!("launching script");
32+
let mut cmd = Command::new("julia");
33+
if !cfg!(debug_assertions) {
34+
cmd.arg("-O3");
35+
}
36+
cmd.arg(jl_script).args(j_args).status().unwrap();
37+
}
38+
39+
/// Plot a single domain.
40+
fn plot_one(out_dir: &Path, name: &str, domain: Domain<f32>, j_args: &mut Vec<String>) {
41+
let base_name = out_dir.join(format!("domain-inputs-{name}"));
42+
let text_file = base_name.with_extension("txt");
43+
44+
{
45+
// Scope for file and writer
46+
let f = fs::File::create(&text_file).unwrap();
47+
let mut w = BufWriter::new(f);
48+
49+
for input in domain_logspace::get_test_cases_inner::<f32>(domain) {
50+
writeln!(w, "{:e}", input.0).unwrap();
51+
}
52+
w.flush().unwrap();
53+
}
54+
55+
// The julia script expects `name1 path1 name2 path2...` args
56+
j_args.push(name.to_owned());
57+
j_args.push(base_name.to_str().unwrap().to_owned());
58+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"A quick script to for plotting a list of floats.
2+
3+
Takes a list of floats and a real function, plots both on a graph in both
4+
linear and log scale. Requires [Makie] (specifically CairoMakie) for plotting.
5+
6+
[Makie]: https://docs.makie.org/stable/
7+
"
8+
9+
using CairoMakie
10+
11+
CairoMakie.activate!(px_per_unit=10)
12+
13+
"Apply a function, returning the default if there is a domain error"
14+
function map_or_default(
15+
input::AbstractFloat,
16+
f::Function,
17+
default::AbstractFloat
18+
)::AbstractFloat
19+
try
20+
return f(input)
21+
catch
22+
return default
23+
end
24+
end
25+
26+
"Read inputs from a file, create both linear and log plots"
27+
function plot_one(
28+
fig::Figure,
29+
base_name::String,
30+
fn_name::String,
31+
f::Function;
32+
xlims::Union{Tuple{Any,Any},Nothing},
33+
xlims_log::Union{Tuple{Any,Any},Nothing},
34+
)::Nothing
35+
float_file = "$base_name.txt"
36+
lin_out_file = "$base_name.png"
37+
log_out_file = "$base_name-log.png"
38+
39+
if xlims === nothing
40+
xlims = (-6, 6)
41+
end
42+
if xlims_log === nothing
43+
xlims_log = (xlims[1] * 500, xlims[2] * 500)
44+
end
45+
46+
inputs = readlines(float_file)
47+
48+
# Parse floats
49+
x = map((v) -> parse(Float32, v), inputs)
50+
# Apply function to the test points
51+
y = map((v) -> map_or_default(v, f, 0.0), x)
52+
53+
# Plot the scatter plot of our checked values as well as the continuous function
54+
ax = Axis(fig[1, 1], limits=(xlims, nothing), title=fn_name)
55+
lines!(ax, xlims[1] .. xlims[2], f, color=(:blue, 0.4))
56+
scatter!(ax, x, y, color=(:green, 0.3))
57+
save(lin_out_file, fig)
58+
delete!(ax)
59+
60+
# Same as above on a log scale
61+
ax = Axis(
62+
fig[1, 1],
63+
limits=(xlims_log, nothing),
64+
xscale=Makie.pseudolog10,
65+
title="$fn_name (log scale)"
66+
)
67+
lines!(ax, xlims_log[1] .. xlims_log[2], f, color=(:blue, 0.4))
68+
scatter!(ax, x, y, color=(:green, 0.3))
69+
save(log_out_file, fig)
70+
delete!(ax)
71+
end
72+
73+
# Args alternate `name1 path1 name2 path2`
74+
fn_names = ARGS[1:2:end]
75+
base_names = ARGS[2:2:end]
76+
77+
for idx in eachindex(fn_names)
78+
fn_name = fn_names[idx]
79+
base_name = base_names[idx]
80+
81+
xlims = nothing
82+
xlims_log = nothing
83+
84+
fig = Figure()
85+
86+
# Map string function names to callable functions
87+
if fn_name == "cos"
88+
f = cos
89+
xlims_log = (-pi * 10, pi * 10)
90+
elseif fn_name == "cbrt"
91+
f = cbrt
92+
xlims = (-2.0, 2.0)
93+
elseif fn_name == "sqrt"
94+
f = sqrt
95+
xlims = (-1.1, 6.0)
96+
xlims_log = (-1.1, 5000.0)
97+
else
98+
println("unrecognized function name `$fn_name`; update plot_file.jl")
99+
end
100+
101+
println("plotting $fn_name")
102+
plot_one(
103+
fig,
104+
base_name,
105+
fn_name,
106+
f,
107+
xlims=xlims,
108+
xlims_log=xlims_log,
109+
)
110+
end
111+
112+
base_name = ARGS[1]
113+
fn_name = ARGS[2]

0 commit comments

Comments
 (0)