From d8136f09aa36cbbb75a8edb9c5942ffd664874fc Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 30 Oct 2025 08:05:45 +0530 Subject: [PATCH] add support for passing separate kwargs closes #143 --- src/profiles.jl | 51 ++++++++++++++++++--------- test/profiles_kwargs.jl | 76 +++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 3 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 test/profiles_kwargs.jl diff --git a/src/profiles.jl b/src/profiles.jl index 257271f..71ca624 100644 --- a/src/profiles.jl +++ b/src/profiles.jl @@ -4,17 +4,20 @@ using BenchmarkProfiles, Plots export performance_profile, profile_solvers """ - performance_profile(stats, cost, args...; b = PlotsBackend(), kwargs...) + performance_profile(stats, cost; b = PlotsBackend(), kwargs...) Produce a performance profile comparing solvers in `stats` using the `cost` function. Inputs: - `stats::AbstractDict{Symbol,DataFrame}`: pairs of `:solver => df`; -- `cost::Function`: cost function applyed to each `df`. Should return a vector with the cost of solving the problem at each row; +- `cost::Function`: cost function applied to each `df`. Should return a vector with the cost of solving the problem at each row; - 0 cost is not allowed; - - If the solver did not solve the problem, return Inf or a negative number. + - If the solver did not solve the problem, return `Inf` or a negative number. - `b::BenchmarkProfiles.AbstractBackend` : backend used for the plot. +Keyword arguments: +- `kwargs...` : keyword arguments forwarded to `BenchmarkProfiles.performance_profile` (backend-specific options). + Example: `logscale = false` to disable log-scaling when supported by the backend. If several profiles will be produced with variants of the same solvers, `stats` may be an `OrderedDict`, as defined in the OrderedCollections.jl package. @@ -24,21 +27,20 @@ Examples of cost functions: """ function performance_profile( stats::AbstractDict{Symbol, DataFrame}, - cost::Function, - args...; + cost::Function; b::BenchmarkProfiles.AbstractBackend = PlotsBackend(), kwargs..., ) solvers = keys(stats) dfs = (stats[s] for s in solvers) P = hcat([cost(df) for df in dfs]...) - performance_profile(b, P, string.(solvers), args...; kwargs...) + BenchmarkProfiles.performance_profile(b, P, string.(solvers); kwargs...) end """ p = profile_solvers(stats, costs, costnames; width = 400, height = 400, - b = PlotsBackend(), kwargs...) + b = PlotsBackend(), bp_kwargs = Dict(), kwargs...) Produce performance profiles comparing `solvers` based on the data in `stats`. @@ -52,11 +54,9 @@ Keyword inputs: - `width::Int`: Width of each individual plot (Default: 400) - `height::Int`: Height of each individual plot (Default: 400) - `b::BenchmarkProfiles.AbstractBackend` : backend used for the plot. +- `bp_kwargs::Dict` : a `Dict` of keyword arguments forwarded to the `performance_profile` backend calls. -Additional `kwargs` are passed to the `plot` call. - -Output: -A Plots.jl plot representing a set of performance profiles comparing the solvers. +Additional `kwargs` are passed to the final `plot` call that assembles the profiles. The set contains performance profiles comparing all the solvers together on the measures given in `costs`. If there are more than two solvers, additional profiles are produced comparing the @@ -69,6 +69,7 @@ function profile_solvers( width::Int = 400, height::Int = 400, b::BenchmarkProfiles.AbstractBackend = PlotsBackend(), + bp_kwargs::Dict = Dict(), kwargs..., ) solvers = collect(keys(stats)) @@ -83,24 +84,26 @@ function profile_solvers( # profiles with all solvers ps = [ - performance_profile( + BenchmarkProfiles.performance_profile( b, Ps[1], - string.(solvers), + string.(solvers); palette = colors, title = costnames[1], legend = :bottomright, + bp_kwargs..., ), ] nsolvers > 2 && xlabel!(ps[1], "") for k = 2:ncosts - p = performance_profile( + p = BenchmarkProfiles.performance_profile( b, Ps[k], - string.(solvers), + string.(solvers); palette = colors, title = costnames[k], legend = false, + bp_kwargs..., ) nsolvers > 2 && xlabel!(p, "") ylabel!(p, "") @@ -118,11 +121,25 @@ function profile_solvers( Ps = [hcat([Float64.(cost(df)) for df in dfs]...) for cost in costs] clrs = [colors[i], colors[j]] - p = performance_profile(b, Ps[1], string.(pair), palette = clrs, legend = :bottomright) + p = BenchmarkProfiles.performance_profile( + b, + Ps[1], + string.(pair); + palette = clrs, + legend = :bottomright, + bp_kwargs..., + ) ipairs < npairs && xlabel!(p, "") push!(ps, p) for k = 2:ncosts - p = performance_profile(b, Ps[k], string.(pair), palette = clrs, legend = false) + p = BenchmarkProfiles.performance_profile( + b, + Ps[k], + string.(pair); + palette = clrs, + legend = false, + bp_kwargs..., + ) ipairs < npairs && xlabel!(p, "") ylabel!(p, "") push!(ps, p) diff --git a/test/profiles_kwargs.jl b/test/profiles_kwargs.jl new file mode 100644 index 0000000..0ee83ae --- /dev/null +++ b/test/profiles_kwargs.jl @@ -0,0 +1,76 @@ +using Test +using DataFrames +using SolverBenchmark +using BenchmarkProfiles +using Plots + +struct CaptureBackend <: BenchmarkProfiles.AbstractBackend end + +struct CapturedPlot + labels::Vector{String} + kwargs::Any +end + +# Extend BenchmarkProfiles.performance_profile for our CaptureBackend +function BenchmarkProfiles.performance_profile( + ::CaptureBackend, + P::Matrix{<:Number}, + labels::Vector{<:AbstractString}; + kwargs..., +) + labs = string.(labels) + CapturedPlot(labs, kwargs) +end + +function Plots.plot(ps::CapturedPlot...; kwargs...) + return (plots = ps, plot_kwargs = kwargs) +end + +@testset "profiles: bp_kwargs and kwargs forwarding" begin + df1 = DataFrame(a = [1.0, 2.0]) + df2 = DataFrame(a = [2.0, 3.0]) + stats = Dict(:s1 => df1, :s2 => df2) + + costs = [df -> df.a] + costnames = ["a"] + + result = profile_solvers( + stats, + costs, + costnames; + b = CaptureBackend(), + bp_kwargs = Dict(:logscale => false), + ) + @test isa(result, NamedTuple) + # The inner performance_profile returns CapturedPlot objects stored in result.plots + plots = result[:plots] + @test length(plots) >= 1 + first_plot = plots[1] + @test (:logscale in keys(first_plot.kwargs)) && first_plot.kwargs[:logscale] == false + + result2 = profile_solvers( + stats, + costs, + costnames; + b = CaptureBackend(), + bp_kwargs = Dict(:logscale => true), + title = "T", + legend = false, + ) + @test isa(result2, NamedTuple) + @test (:title in keys(result2[:plot_kwargs])) && result2[:plot_kwargs][:title] == "T" + @test (:legend in keys(result2[:plot_kwargs])) && result2[:plot_kwargs][:legend] == false + + result3 = profile_solvers( + stats, + costs, + costnames; + b = CaptureBackend(), + bp_kwargs = Dict(:foo => 1), + bar = 2, + extra = 3, + ) + @test (:foo in keys(result3[:plots][1].kwargs)) && result3[:plots][1].kwargs[:foo] == 1 + @test (:bar in keys(result3[:plot_kwargs])) && result3[:plot_kwargs][:bar] == 2 + @test (:extra in keys(result3[:plot_kwargs])) && result3[:plot_kwargs][:extra] == 3 +end diff --git a/test/runtests.jl b/test/runtests.jl index 8357b50..aa6432e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -24,3 +24,4 @@ include("test-tables.jl") include("profiles.jl") include("pkgbmark.jl") include("test_bmark.jl") +include("profiles_kwargs.jl")