Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tikz export for performance profiles #104

Closed
wants to merge 20 commits into from
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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ LaTeXStrings = "^1.3"
NaNMath = "0.3, 1"
Requires = "1"
julia = "^1.6"
Tables = "1.11"
Tables = "1"

[extras]
PGFPlotsX = "8314cec4-20b6-5062-9cdb-752b83310925"
Expand Down
2 changes: 2 additions & 0 deletions src/BenchmarkProfiles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ using Requires
using Printf

export performance_ratios, performance_profile, performance_profile_data, export_performance_profile
export export_performance_profile_tikz
export data_ratios, data_profile
export bp_backends, PlotsBackend, UnicodePlotsBackend, PGFPlotsXBackend

Expand All @@ -30,6 +31,7 @@ end

include("performance_profiles.jl")
include("data_profiles.jl")
include("tikz_export.jl")

"""
Replace each number by 2^{number} in a string.
Expand Down
50 changes: 36 additions & 14 deletions src/performance_profiles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

using CSV, Tables

using CSV, Tables

"""Compute performance ratios used to produce a performance profile.

There is normally no need to call this function directly.
Expand Down Expand Up @@ -155,6 +157,23 @@ function performance_profile(
)
end

"""
function performance_profile_data_mat(T;kwargs...)

Retruns `performance_profile_data` output (vectors) as matrices. Matrices are padded with NaN if necessary.
"""
function performance_profile_data_mat(T::Matrix{Float64};kwargs...)
x_data, y_data, max_ratio = performance_profile_data(T;kwargs...)
max_elem = maximum(length.(x_data))
for i in eachindex(x_data)
append!(x_data[i],[NaN for i=1:max_elem-length(x_data[i])])
append!(y_data[i],[NaN for i=1:max_elem-length(y_data[i])])
end
x_mat = hcat(x_data...)
y_mat = hcat(y_data...)
return x_mat, y_mat
end

"""
export_performance_profile(T, filename; solver_names = [], header, kwargs...)

Expand All @@ -164,14 +183,14 @@ Export a performance profile plot data as .csv file. Profiles data are padded wi

* `T :: Matrix{Float64}`: each column of `T` defines the performance data for a solver (smaller is better).
Failures on a given problem are represented by a negative value, an infinite value, or `NaN`.
* `filename :: String` : path to the export file.
* `filename :: String` : path to the exported file.

## Keyword Arguments

* `solver_names :: Vector{S}` : names of the solvers
* `header::Vector{String}`: Contains .csv file column names. Note that `header` value does not change columns order in .csv exported files (see Output).
* `solver_names :: Vector{S}` : names of the solvers.
- `header::Vector{String}`: Contains .csv file column names. Note that `header` value does not change columns order in .csv exported files (see Output).

Other keyword arguments are passed `performance_profile_data`.
Other keyword arguments are passed to `performance_profile_data`.

Output:
File containing profile data in .csv format. Columns are solver1_x, solver1_y, solver2_x, ...
Expand All @@ -185,23 +204,26 @@ function export_performance_profile(
) where {S <: AbstractString}
nsolvers = size(T)[2]

x_data, y_data, max_ratio = performance_profile_data(T; kwargs...)
max_elem = maximum(length.(x_data))
for i in eachindex(x_data)
append!(x_data[i], [NaN for i = 1:(max_elem - length(x_data[i]))])
append!(y_data[i], [NaN for i = 1:(max_elem - length(y_data[i]))])
end
x_mat = hcat(x_data...)
y_mat = hcat(y_data...)

x_mat, y_mat = performance_profile_data_mat(T;kwargs...)
isempty(solver_names) && (solver_names = ["solver_$i" for i = 1:nsolvers])

if !isempty(header)
header_l = size(T)[2]*2
length(header) == header_l || error("Header should contain $(header_l) elements")
header = vcat([[sname*"_x",sname*"_y"] for sname in solver_names]...)
end
data = Matrix{Float64}(undef,size(x_mat,1),nsolvers*2)
for i =0:nsolvers-1
data[:,2*i+1] .= x_mat[:,i+1]
data[:,2*i+2] .= y_mat[:,i+1]
end

if !isempty(header)
header_l = size(T)[2] * 2
length(header) == header_l || error("Header should contain $(header_l) elements")
header = vcat([[sname * "_x", sname * "_y"] for sname in solver_names]...)
end
data = Matrix{Float64}(undef, max_elem, nsolvers * 2)
data = Matrix{Float64}(undef, size(x_mat,1), nsolvers * 2)
for i = 0:(nsolvers - 1)
data[:, 2 * i + 1] .= x_mat[:, i + 1]
data[:, 2 * i + 2] .= y_mat[:, i + 1]
Expand Down
153 changes: 153 additions & 0 deletions src/tikz_export.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
export export_performance_profile_tikz

"""
function export_performance_profile_tikz(T, filename; kwargs...)

Export tikz figure of the performance profiles given by `T` in `filename`.

## Arguments

* `T :: Matrix{Float64}`: each column of `T` defines the performance data for a solver (smaller is better).
Failures on a given problem are represented by a negative value, an infinite value, or `NaN`.
* `filename :: String` : path to the tikz exported file.

## Keyword Arguments

* `solvernames :: Vector{String} = []` : names of the solvers, should have as many elements as the number of columns of `T`. If empty, use the labels returned by `performance_profile_axis_labels`.
* `xlim::AbstractFloat=10.` : size of the figure along the x axis. /!\\ the legend is added on the right hand side of the figure.
* `ylim::AbstractFloat=10.` : size of the figure along the y axis.
* `nxgrad::Int=5` : number of graduations on the x axis.
* `nygrad::Int=5` : number of graduations on the y axis.
* `grid::Bool=true` : display grid if true.
* `colours::Vector{String} = []` : colours of the plots, should have as many elements as the number of columns of `T`.
* `linestyles::Vector{String} = []` : line style (dashed, dotted, ...) of the plots, should have as many elements as the number of columns of `T`.
* `linewidth::AbstractFloat = 1.0` : line with of the plots.
* `xlabel::String = ""` : x axis label. If empty, uses the one returns by `performance_profile_axis_labels`.
* `ylabel::String = ""` : x axis label. If empty, uses the one returns by `performance_profile_axis_labels`.

Other keyword arguments are passed `performance_profile_data`.

"""
function export_performance_profile_tikz(
T::Matrix{Float64},
filename::String;
solvernames::Vector{String}=String[],
xlim::AbstractFloat=10.,
ylim::AbstractFloat=10.,
nxgrad::Int=5,
nygrad::Int=5,
grid::Bool=true,
# markers::Vector{S} = String[],
colours::Vector{String} = String[],
linestyles::Vector{String} = String[],
linewidth::AbstractFloat = 1.0,
xlabel::String = "",
ylabel::String = "",
kwargs...)



logscale = true
if haskey(kwargs,:logscale)
logscale = kwargs[:logscale]

Check warning on line 52 in src/tikz_export.jl

View check run for this annotation

Codecov / codecov/patch

src/tikz_export.jl#L52

Added line #L52 was not covered by tests
end
xlabel_def, ylabel_def, solvernames = performance_profile_axis_labels(solvernames, size(T, 2), logscale; kwargs...)
isempty(xlabel) && (xlabel=xlabel_def)
isempty(ylabel) && (ylabel=ylabel_def)

# some offsets
axis_tik_l = 0.2 # tick length
lgd_offset = 0.5 # space between figure and legend box
lgd_box_length = 3. # legend box length
lgd_plot_length = 0.7 # legend plot length
lgd_v_offset = 0.7 # vertical space between legend items

label_val = [0.2,0.25,0.5,1] # possible graduation labels along axes are multiples of label_val elements times 10^n
ymax = 1.0
y_grad = collect(0.:ymax/(nygrad-1):ymax)

isempty(colours) && (colours = ["black" for _ =1:size(T,2)])
isempty(linestyles) && (linestyles = ["solid" for _ =1:size(T,2)])

x_mat, y_mat = BenchmarkProfiles.performance_profile_data_mat(T;kwargs...)

# get nice looking graduation on x axis
xmax , _ = findmax(x_mat[.!isnan.(x_mat)])
dist = xmax/(nxgrad-1)
n=log.(10,dist./label_val)
_, ind = findmin(abs.(n .- round.(n)))
xgrad_dist = label_val[ind]*10^round(n[ind])
x_grad = [0. , [xgrad_dist*i for i =1 : nxgrad-1]...]
#x_grad[end] <= xmax || (pop!(x_grad))
xmax=max(x_grad[end],xmax)
to_int(x) = isinteger(x) ? Int(x) : x

xratio = xlim/xmax
yratio = ylim/ymax
open(filename, "w") do io
println(io, "\\begin{tikzpicture}")
# axes
println(io, "\\draw[line width=$linewidth] (0,0) -- ($xlim,0);")
println(io, "\\node at ($(xlim/2), -1) {$xlabel};")
println(io, "\\draw[line width=$linewidth] (0,0) -- (0,$ylim);")
println(io, "\\node at (-1,$(ylim/2)) [rotate = 90] {$ylabel};")
# axes graduations and labels,
if logscale
for i in eachindex(x_grad)
println(io, "\\draw[line width=$linewidth] ($(x_grad[i]*xratio),0) -- ($(x_grad[i]*xratio),$axis_tik_l) node [pos=0, below] {\$2^{$(to_int(x_grad[i]))}\$};")
end
else
for i in eachindex(x_grad)
println(io, "\\draw[line width=$linewidth] ($(x_grad[i]*xratio),0) -- ($(x_grad[i]*xratio),$axis_tik_l) node [pos=0, below] {$(to_int(x_grad[i]))};")
end

Check warning on line 102 in src/tikz_export.jl

View check run for this annotation

Codecov / codecov/patch

src/tikz_export.jl#L100-L102

Added lines #L100 - L102 were not covered by tests
end
for i in eachindex(y_grad)
println(io, "\\draw[line width=$linewidth] (0,$(y_grad[i]*yratio)) -- ($axis_tik_l,$(y_grad[i]*yratio)) node [pos=0, left] {$(to_int(y_grad[i]))};")
end
# grid
if grid
for i in eachindex(x_grad)
println(io, "\\draw[gray] ($(x_grad[i]*xratio),0) -- ($(x_grad[i]*xratio),$ylim);")
end
for i in eachindex(y_grad)
println(io, "\\draw[gray] (0,$(y_grad[i]*yratio)) -- ($xlim,$(y_grad[i]*yratio)) node [pos=0, left] {$(to_int(y_grad[i]))};")
end
end

# profiles
for j in eachindex(solvernames)
drawcmd = "\\draw[line width=$linewidth, $(colours[j]), $(linestyles[j]), line width = $linewidth] "
drawcmd *= "($(x_mat[1,j]*xratio),$(y_mat[1,j]*yratio))"
for k in 2:size(x_mat,1)
if isnan(x_mat[k,j])
break
end
if y_mat[k,j] > 1 # for some reasons last point of profile is set with y=1.1 by data function...
drawcmd *= " -- ($(xmax*xratio),$(y_mat[k-1,j]*yratio)) -- ($(xmax*xratio),$(y_mat[k-1,j]*yratio))"
else
# if !isempty(markers)
# drawcmd *= " -- ($(x_mat[k,j]*xratio),$(y_mat[k-1,j]*yratio)) node[$(colours[j]),draw,$(markers[j]),solid] {} -- ($(x_mat[k,j]*xratio),$(y_mat[k,j]*yratio))"
# else
drawcmd *= " -- ($(x_mat[k,j]*xratio),$(y_mat[k-1,j]*yratio)) -- ($(x_mat[k,j]*xratio),$(y_mat[k,j]*yratio))"
# end
end
end
drawcmd *= ";"
println(io,drawcmd)
end
# legend
for j in eachindex(solvernames)
legcmd = "\\draw[$(colours[j]), $(linestyles[j]), line width = $linewidth] "
legcmd *= "($(xlim+lgd_offset),$(ylim-j*lgd_v_offset)) -- ($(xlim+lgd_offset+lgd_plot_length),$(ylim-j*lgd_v_offset)) node [black,pos=1,right] {$(String(solvernames[j]))}"
# if !isempty(markers)
# legcmd *= " node [midway,draw,$(markers[j]),solid] {}"
# end
legcmd *= ";"

println(io,legcmd)
end
# legend box
println(io,"\\draw[line width=$linewidth] ($(xlim+lgd_offset-0.1),$ylim) -- ($(xlim+lgd_offset+lgd_box_length),$ylim) -- ($(xlim+lgd_offset+lgd_box_length),$(ylim-lgd_v_offset*(length(solvernames)+1))) -- ($(xlim+lgd_offset-0.1),$(ylim-lgd_v_offset*(length(solvernames)+1))) -- cycle;")
println(io,"\\end{tikzpicture}")
end
end
10 changes: 10 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,15 @@ if !Sys.isfreebsd() # GR_jll not available, so Plots won't install
export_performance_profile(T, filename, header = ["" for _ = 1:(size(T, 2) * 2)])
@test isfile(filename)
rm(filename)
export_performance_profile(T,filename,header = ["" for _=1:size(T,2)*2])
@test isfile(filename)
rm(filename)
end
@testset "tikz export" begin
T = 10 * rand(25, 3)
filename = "tikz_fig.tex"
export_performance_profile_tikz(T,filename)
@test isfile(filename)
rm(filename)
end
end
Loading