| "A quick script for plotting a list of floats. |
| |
| Takes a path to a TOML file (Julia has builtin TOML support but not JSON) which |
| specifies a list of source files to plot. Plots are done with both a linear and |
| a log scale. |
| |
| Requires [Makie] (specifically CairoMakie) for plotting. |
| |
| [Makie]: https://docs.makie.org/stable/ |
| " |
| |
| using CairoMakie |
| using TOML |
| |
| function main()::Nothing |
| CairoMakie.activate!(px_per_unit = 10) |
| config_path = ARGS[1] |
| |
| cfg = Dict() |
| open(config_path, "r") do f |
| cfg = TOML.parse(f) |
| end |
| |
| out_dir = cfg["out_dir"] |
| for input in cfg["input"] |
| fn_name = input["function"] |
| gen_name = input["generator"] |
| input_file = input["input_file"] |
| |
| plot_one(input_file, out_dir, fn_name, gen_name) |
| end |
| end |
| |
| "Read inputs from a file, create both linear and log plots for one function" |
| function plot_one( |
| input_file::String, |
| out_dir::String, |
| fn_name::String, |
| gen_name::String, |
| )::Nothing |
| fig = Figure() |
| |
| lin_out_file = joinpath(out_dir, "plot-$fn_name-$gen_name.png") |
| log_out_file = joinpath(out_dir, "plot-$fn_name-$gen_name-log.png") |
| |
| # Map string function names to callable functions |
| if fn_name == "cos" |
| orig_func = cos |
| xlims = (-6.0, 6.0) |
| xlims_log = (-pi * 10, pi * 10) |
| elseif fn_name == "cbrt" |
| orig_func = cbrt |
| xlims = (-2.0, 2.0) |
| xlims_log = (-1000.0, 1000.0) |
| elseif fn_name == "sqrt" |
| orig_func = sqrt |
| xlims = (-1.1, 6.0) |
| xlims_log = (-1.1, 5000.0) |
| else |
| println("unrecognized function name `$fn_name`; update plot_file.jl") |
| exit(1) |
| end |
| |
| # Edge cases don't do much beyond +/-1, except for infinity. |
| if gen_name == "edge_cases" |
| xlims = (-1.1, 1.1) |
| xlims_log = (-1.1, 1.1) |
| end |
| |
| # Turn domain errors into NaN |
| func(x) = map_or(x, orig_func, NaN) |
| |
| # Parse a series of X values produced by the generator |
| inputs = readlines(input_file) |
| gen_x = map((v) -> parse(Float32, v), inputs) |
| |
| do_plot( |
| fig, |
| gen_x, |
| func, |
| xlims[1], |
| xlims[2], |
| "$fn_name $gen_name (linear scale)", |
| lin_out_file, |
| false, |
| ) |
| |
| do_plot( |
| fig, |
| gen_x, |
| func, |
| xlims_log[1], |
| xlims_log[2], |
| "$fn_name $gen_name (log scale)", |
| log_out_file, |
| true, |
| ) |
| end |
| |
| "Create a single plot" |
| function do_plot( |
| fig::Figure, |
| gen_x::Vector{F}, |
| func::Function, |
| xmin::AbstractFloat, |
| xmax::AbstractFloat, |
| title::String, |
| out_file::String, |
| logscale::Bool, |
| )::Nothing where {F<:AbstractFloat} |
| println("plotting $title") |
| |
| # `gen_x` is the values the generator produces. `actual_x` is for plotting a |
| # continuous function. |
| input_min = xmin - 1.0 |
| input_max = xmax + 1.0 |
| gen_x = filter((v) -> v >= input_min && v <= input_max, gen_x) |
| markersize = length(gen_x) < 10_000 ? 6.0 : 4.0 |
| |
| steps = 10_000 |
| if logscale |
| r = LinRange(symlog10(input_min), symlog10(input_max), steps) |
| actual_x = sympow10.(r) |
| xscale = Makie.pseudolog10 |
| else |
| actual_x = LinRange(input_min, input_max, steps) |
| xscale = identity |
| end |
| |
| gen_y = @. func(gen_x) |
| actual_y = @. func(actual_x) |
| |
| ax = Axis(fig[1, 1], xscale = xscale, title = title) |
| |
| lines!( |
| ax, |
| actual_x, |
| actual_y, |
| color = (:lightblue, 0.6), |
| linewidth = 6.0, |
| label = "true function", |
| ) |
| scatter!( |
| ax, |
| gen_x, |
| gen_y, |
| color = (:darkblue, 0.9), |
| markersize = markersize, |
| label = "checked inputs", |
| ) |
| axislegend(ax, position = :rb, framevisible = false) |
| |
| save(out_file, fig) |
| delete!(ax) |
| end |
| |
| "Apply a function, returning the default if there is a domain error" |
| function map_or(input::AbstractFloat, f::Function, default::Any)::Union{AbstractFloat,Any} |
| try |
| return f(input) |
| catch |
| return default |
| end |
| end |
| |
| # Operations for logarithms that are symmetric about 0 |
| C = 10 |
| symlog10(x::Number) = sign(x) * (log10(1 + abs(x) / (10^C))) |
| sympow10(x::Number) = (10^C) * (10^x - 1) |
| |
| main() |