| "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() |