Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions src/profiles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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`.

Expand All @@ -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
Expand All @@ -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))
Expand All @@ -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, "")
Expand All @@ -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)
Expand Down
76 changes: 76 additions & 0 deletions test/profiles_kwargs.jl
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ include("test-tables.jl")
include("profiles.jl")
include("pkgbmark.jl")
include("test_bmark.jl")
include("profiles_kwargs.jl")