Skip to content

Commit

Permalink
Merge pull request #17 from ericphanson/catch-warnings
Browse files Browse the repository at this point in the history
Catch warnings, change params argument
  • Loading branch information
ericphanson authored Sep 17, 2019
2 parents 121f7ff + 0d2362d commit cb68fd5
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 151 deletions.
10 changes: 5 additions & 5 deletions docs/src/issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,21 @@ end
test_problem = Convex.ProblemDepot.PROBLEMS["lp"]["lp_dotsort_atom"];
TEST = true; atol = 1e-3; rtol = 0.0;
test_problem(Val(TEST), atol, rtol, Float64) do problem
solve!(problem, SDPAFamily.Optimizer{Float64}(variant=:sdpa_dd, silent=true, params_path = "-pt 1"))
solve!(problem, SDPAFamily.Optimizer{Float64}(variant=:sdpa_dd, silent=true, params = UNSTABLE_BUT_FAST))
end
```

## Summary of problematic problems

Due to the above reasons, we have excluded the following tests from `Convex.jl`'s `Problem Depot'.
Due to the above reasons, we have modified the default settings for the following tests from `Convex.jl`'s `Problem Depot'.

| Solver | Underflow | Need to use `params_path = "-pt 1"` | Presolve disabled due to long runtime |
| Solver | Underflow | Need to use `params = UNSTABLE_BUT_FAST` | Presolve disabled due to long runtime |
| :---------- | :------------------------------------------------ | :----------------------------------------------------------- | :----------------------------------------------------- |
| `:sdpa_dd` | `affine_Partial_transpose` | `affine_Partial_transpose` `lp_pos_atom` `lp_neg_atom` `sdp_matrix_frac_atom` `lp_dotsort_atom` | `affine_Partial_transpose` `lp_min_atom` `lp_max_atom` |
| `:sdpa_qd` | `affine_Partial_transpose` `affine_Diagonal_atom` | `affine_Partial_transpose` `affine_Diagonal_atom` | `affine_Partial_transpose` `lp_min_atom` `lp_max_atom` |
| `:sdpa_gmp` | `affine_Partial_transpose` | `affine_Partial_transpose` | `affine_Partial_transpose` `lp_min_atom` `lp_max_atom` |

Note that here all underflowing test cases will pass when using `params_path = "-pt 1"`. In addition, we have excluded `lp_dotsort_atom` and `lp_pos_atom` when testing `:sdpa` due to imprecise solutions using default parameters.
In addition, we have excluded `lp_dotsort_atom` and `lp_pos_atom` when testing `:sdpa` due to imprecise solutions using default parameters. We have also excluded all second-order cone problems when using `BigFloat` or `Double64` numeric types, due to MathOptInterface.jl#876, as well as the `sdp_lambda_max_atom` problem due to GenericLinearAlgebra#47.

## Troubleshooting

Expand All @@ -75,4 +75,4 @@ When the solvers fail to return a solution, we recommend trying out the followin
1. Set `silent=false` and look for warnings and error messages. If necessary, check the output file. Its path is printed by the solver output and can also be retrieved via `Optimizer.tempdir`.
2. Set `presolve=true` to remove redundant constraints. Typically, redundant constraints are indicated by a premature `cholesky miss` error as shown above.
3. Use `BigFloat` (the default) or `Double64` (from the [DoubleFloats](https://github.com/JuliaMath/DoubleFloats.jl) package) precision instead of `Float64` (e.g. `SDPAFamily.Optimizer{Double64}(...)`). This will reduce the chance of having underflow errors when reading back the results.
4. Change the parameters by passing a custom parameter file (i.e. `SDPAFamily.Optimizer(params_path=...)`). [SDPA users manual](https://sourceforge.net/projects/sdpa/files/sdpa/sdpa.7.1.1.manual.20080618.pdf) contains two other sets of parameters, `UNSTABLE_BUT_FAST` and `STABLE_BUT_SLOW`. It might also be helpful to use a tighter `epsilonDash` and `epsilonStar` tolerance.
4. Change the parameters by passing a custom parameter file (i.e. `SDPAFamily.Optimizer(params=...)`). [SDPA users manual](https://sourceforge.net/projects/sdpa/files/sdpa/sdpa.7.1.1.manual.20080618.pdf) contains two other sets of parameters, `UNSTABLE_BUT_FAST` and `STABLE_BUT_SLOW`, which can be set by the `params` argument. It might also be helpful to use a tighter `epsilonDash` and `epsilonStar` tolerance in a custom params file.
7 changes: 4 additions & 3 deletions docs/src/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
The main object of interest supplied by this package is `SDPAFamily.Optimizer{T}()`. Here, `T` is a numeric type which defaults to `BigFloat`. Keyword arguments may also be passed to `SDPAFamily.Optimizer()`:

* `variant`: either `:sdpa_gmp`, `:sdpa_qd`, or `:sdpa_dd`, to use SDPA-GMP, SDPA-QD, or SDPA-DD, respectively. Defaults to `:sdpa_gmp`.
* `silent`: a boolean to indicate whether or not to print output. Defaults to `true`.
* `silent`: a boolean to indicate whether or not to print output. Defaults to `false`.
* `verbose`: accepts either `SDPAFamily.SILENT` (which is equivalent to `silent=true`), `SDPAFamily.WARN` (which is the default), or `SDPAFamily.VERBOSE`, which prints all output from the binary as well as additional warnings.
* `presolve`: whether or not to run a presolve routine to remove linearly dependent constraints. See below for more details. Defaults to `false`. Note, `presolve=true` is required to pass many of the tests for this package; linearly independent constraints are an assumption of the SDPA-family, but constraints generated from high level modelling languages often do have linear dependence between them.
* `binary_path`: a string representing a path to the SDPA-GMP binary to use. The default is chosen at `build` time. To change the default binaries, see [Custom binary](@ref).
* `params_path`: a string representing a path to a parameter file named `param.sdpa` in the same folder as the binary if present. The default parameters used by `SDPAFamily.jl` are listed [here](https://github.com/ericphanson/SDPAFamily.jl/blob/master/deps/). There are two other sets of parameters, `UNSTABLE_BUT_FAST` and `STABLE_BUT_SLOW`, called by `params_path = "-pt 1"` and `params_path = "-pt 2"` respectively. For details please refer to the [SDPA users manual](https://sourceforge.net/projects/sdpa/files/sdpa/sdpa.7.1.1.manual.20080618.pdf).
* `params`: either `SDPAFamily.DEFAULT` (the default option), `SDPAFamily.UNSTABLE_BUT_FAST`, `SDPAFamily.STABLE_BUT_SLOW`, or a string representing a path to a parameter file. The default parameters used by `SDPAFamily.jl` are listed [here](https://github.com/ericphanson/SDPAFamily.jl/blob/master/deps/). There are two other choices of parameters, `UNSTABLE_BUT_FAST` and `STABLE_BUT_SLOW` are documented in the [SDPA users manual](https://sourceforge.net/projects/sdpa/files/sdpa/sdpa.7.1.1.manual.20080618.pdf).

`SDPAFamily.Optimizer()` also accepts `variant = :sdpa` to use the non-high-precision SDPA binary. For general usage of the SDPA solver, use [SDPA.jl](https://github.com/JuliaOpt/SDPA.jl) which uses the C++ library to interface directly with the SDPA binary.

Expand Down Expand Up @@ -36,7 +37,7 @@ solve!(p, SDPAFamily.Optimizer())
```
Applying presolve helps by removing 8 redundant constraints from the final input file.
```@repl convexquantum
opt.presolve = true; opt.silent = true;
opt.presolve = true; opt.verbosity = SDPAFamily.SILENT;
solve!(p, opt)
@test p.optval ≈ (6 - sqrt(big"2.0"))/4 atol=1e-30
SDPAFamily.presolve(opt)
Expand Down
35 changes: 20 additions & 15 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer
varmap::Vector{Tuple{Int, Int, Int}} # Variable Index vi -> blk, i, j
b::Vector{T}
solve_time::Float64
silent::Bool
verbosity::Verbosity
options::Dict{Symbol, Any}
y::Vector{T}
X::PrimalSolution{T}
Expand All @@ -70,30 +70,30 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer
elemdata::Vector{Any}
presolve::Bool
binary_path::String
params_path::String
params::Union{ParamsSetting, String}
no_solve::Bool
use_WSL::Bool
variant::Symbol
function Optimizer{T}(; variant = :sdpa_gmp, presolve::Bool = false, silent::Bool = false,
function Optimizer{T}(; variant = :sdpa_gmp, presolve::Bool = false,
silent::Bool = false,
verbose::Verbosity = silent ? SILENT : WARN,
binary_path = BB_PATHS[variant],
use_WSL = HAS_WSL[variant],
params_path = use_WSL ? WSLize_path(default_params_path[variant]) : default_params_path[variant]
params::Union{ParamsSetting, String} = DEFAULT
) where T

optimizer = new(
zero(T), 1, Int[], Tuple{Int, Int, Int}[], T[],
NaN, silent, Dict{Symbol, Any}(), T[], PrimalSolution{T}(Matrix{T}[]), VarDualSolution{T}(Matrix{T}[]), zero(T), zero(T), :noINFO, mktempdir(), [], presolve, binary_path, params_path, false, use_WSL, variant)
NaN, verbose, Dict{Symbol, Any}(), T[], PrimalSolution{T}(Matrix{T}[]), VarDualSolution{T}(Matrix{T}[]), zero(T), zero(T), :noINFO, mktempdir(), [], presolve, binary_path, params, false, use_WSL, variant)

if T != BigFloat && !optimizer.silent
if silent && verbose != SILENT
throw(ArgumentError("Cannot set both `silent=true` and `verbose != SILENT`."))
end

if T != BigFloat && optimizer.verbosity == VERBOSE
@warn "Not using BigFloat entries may cause underflow errors."
end

if params_path == (use_WSL ? WSLize_path(default_params_path[variant]) : default_params_path[variant]) && optimizer.variant == :sdpa_gmp && T != BigFloat
optimizer.params_path = use_WSL ? WSLize_path(default_params_path[:sdpa_gmp_float64]) : default_params_path[:sdpa_gmp_float64]
if optimizer.silent == false
@info "Precision reduced to 80 bits on problems with Float64 entries."
end
end

return optimizer
end
Expand All @@ -119,11 +119,16 @@ end

MOI.supports(::Optimizer, ::MOI.Silent) = true
function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool)
optimizer.silent = value
if value
optimizer.verbosity = SILENT
else
optimizer.verbosity = WARN
end
end
MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.silent

MOI.get(::Optimizer, ::MOI.SolverName) = "SDPA-GMP"
MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.verbosity == SILENT

MOI.get(::Optimizer, ::MOI.SolverName) = "SDPAFamily"

# See https://www.researchgate.net/publication/247456489_SDPA_SemiDefinite_Programming_Algorithm_User's_Manual_-_Version_600
# "SDPA (SemiDefinite Programming Algorithm) User's Manual — Version 6.00" Section 6.2
Expand Down
16 changes: 16 additions & 0 deletions src/SDPAFamily.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@ const MOI = MathOptInterface
const MOIB = MOI.Bridges
using BinaryProvider

"""
Possible verbosity levels of an `SDPAFamily.Optimizer`.
Options are `SILENT`, `WARN`, or `VERBOSE`.
"""
@enum Verbosity SILENT WARN VERBOSE

"""
Possible the parameter settings of an `SDPAFamily.Optimizer`.
One can also pass a path to the `params` keyword argument to use
a custom parameter file.
Options are `DEFAULT`, `UNSTABLE_BUT_FAST`, or `STABLE_BUT_SLOW`.
"""
@enum ParamsSetting DEFAULT UNSTABLE_BUT_FAST STABLE_BUT_SLOW


# The `deps.jl` file defines `HAS_WSL::Bool` and `sdpa_gmp::String`.
#
Expand Down
113 changes: 105 additions & 8 deletions src/binary_call.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,122 @@ function sdpa_gmp_binary_solve!(m::Optimizer, full_input_path::String, full_outp
if m.use_WSL
full_input_path = WSLize_path(full_input_path)
full_output_path = WSLize_path(full_output_path)
if !m.silent
if !m.verbosity != SILENT
@info "Redirecting to WSL environment."
end
end
if startswith(m.params_path, "-pt ") && length(m.params_path) == 5
arg = `-ds $full_input_path -o $full_output_path $(split(m.params_path))`
if m.params == UNSTABLE_BUT_FAST
arg = `-ds $full_input_path -o $full_output_path -pt 1`
elseif m.params == STABLE_BUT_SLOW
arg = `-ds $full_input_path -o $full_output_path -pt 2`
else
arg = `-ds $full_input_path -o $full_output_path -p $(m.params_path)`
params_path = get_params_path(m)
arg = `-ds $full_input_path -o $full_output_path -p $(params_path)`
end
if m.use_WSL
wsl_binary_path = dirname(normpath(m.binary_path))
cd(wsl_binary_path) do
error_messages, miss = cd(wsl_binary_path) do
var = string(m.variant)
run(pipeline(`wsl ./$var $arg`, stdout = m.silent ? devnull : stdout))
run_binary(`wsl ./$var $arg`, m.verbosity)
end
else
withenv([prefix]) do
run(pipeline(`$(m.binary_path) $arg`, stdout = m.silent ? devnull : stdout))
error_messages, miss = withenv([prefix]) do
run_binary(`$(m.binary_path) $arg`, m.verbosity)
end
end

error_log_path = joinpath(m.tempdir, "errors.log")
open(error_log_path, "w") do io
print(io, error_messages)
end

if m.verbosity != SILENT
if m.verbosity == VERBOSE && error_messages != ""
println("error log: $error_log_path")
end

if miss
@warn("'cholesky miss condition' warning detected; results may be unreliable. Try `presolve=true`, or see troubleshooting guide.")
end
end

read_results!(m, read_path, redundant_entries);
end


function get_params_path(optimizer::Optimizer{T}) where {T}
@assert optimizer.params == DEFAULT || optimizer.params isa String

# use custom params
if optimizer.params isa String
return optimizer.params
end

if optimizer.variant == :sdpa_gmp && T == Float64
if optimizer.use_WSL
return WSLize_path(default_params_path[:sdpa_gmp_float64])
else
return default_params_path[:sdpa_gmp_float64]
end

if optimizer.verbosity == VERBOSE
@info "Precision reduced to 80 bits on problems with Float64 entries."
end
else
if optimizer.use_WSL
return WSLize_path(default_params_path[optimizer.variant])
else
return default_params_path[optimizer.variant]
end
end
end

function run_binary(cmd::Cmd, verbosity)
if verbosity == SILENT
error_messages, miss = run_and_parse_output(devnull, devnull, cmd)
elseif verbosity == WARN
error_messages, miss = run_and_parse_output(stdout, stderr, cmd)
else
error_messages, miss = run_and_parse_output(stdout, stderr, cmd; echo = true)
end

return error_messages, miss
end


function run_and_parse_output(out_io, err_io, cmd; echo = false)
buffer = IOBuffer()
miss = false
function print_stream(out)
for line in eachline(out)
if occursin(" :: ", line)
if occursin("cholesky miss condition", line)
miss=true
end
println(out_io, "Warning: $line")
println(buffer, "Warning: $line")
elseif echo
println(out_io, line)
end
end
end

function error_stream(out)
for line in eachline(out)
println(err_io, "error: $line")
write(buffer, "error: $line")
end
end

out = Pipe()
err = Pipe()
process = run(pipeline(cmd, stdout=out, stderr=err), wait=false)
close(out.in)
close(err.in)


s1 = @async print_stream(out)
s2 = @async error_stream(err)
wait.((process, s1, s2))
return String(take!(buffer)), miss
end
2 changes: 1 addition & 1 deletion src/file_io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function read_results!(
line = getnextline(io)
end
end
if norm(optimizer.b, Inf) < eps(norm(xMatvec, Inf)) || norm(optimizer.b, Inf) < eps(norm(yMatvec, Inf))
if optimizer.verbosity != SILENT && (norm(optimizer.b, Inf) < eps(norm(xMatvec, Inf)) || norm(optimizer.b, Inf) < eps(norm(yMatvec, Inf)))
@warn "Potential underflow detected. Check the results and use `BigFloat` entries if necessary."
end
xVecstring = remove_brackets!(xVecstring)
Expand Down
6 changes: 4 additions & 2 deletions src/presolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ function presolve(optimizer::SDPAFamily.Optimizer{T}) where T
if aug_mat[i, end] != 0
# println(aug_mat[i, end])
abort = 1
@warn "Inconsistency at constraint index $i. Problem is dual infeasible."
if optimizer.verbosity != SILENT
@warn "Inconsistency at constraint index $i. Problem is dual infeasible."
end
end
end
if abort == 1
Expand All @@ -48,7 +50,7 @@ function presolve(optimizer::SDPAFamily.Optimizer{T}) where T
end
finish = time() - start
n = length(redundant_F)
if !optimizer.silent
if optimizer.verbosity == VERBOSE
@info "Presolve finished in $finish seconds. $n constraint(s) eliminated."
end
return sort!(redundant_F)
Expand Down
Loading

0 comments on commit cb68fd5

Please sign in to comment.