diff --git a/src/Numerical/analyze.jl b/src/Numerical/analyze.jl index d7f1545..1e97efd 100644 --- a/src/Numerical/analyze.jl +++ b/src/Numerical/analyze.jl @@ -17,17 +17,21 @@ function MathOptAnalyzer.analyze( threshold_dense_entries::Int = 1000, threshold_small::Float64 = 1e-5, threshold_large::Float64 = 1e+5, + threshold_dynamic_range_single::Float64 = 1e+6, + threshold_dynamic_range_matrix::Float64 = 1e+8, ) data = Data() data.threshold_dense_fill_in = threshold_dense_fill_in data.threshold_dense_entries = threshold_dense_entries data.threshold_small = threshold_small data.threshold_large = threshold_large + data.threshold_dynamic_range_single = threshold_dynamic_range_single + data.threshold_dynamic_range_matrix = threshold_dynamic_range_matrix # initialize simples data data.sense = MOI.get(model, MOI.ObjectiveSense()) data.number_of_variables = MOI.get(model, MOI.NumberOfVariables()) - sizehint!(data.variables_in_constraints, data.number_of_variables) + sizehint!(data._variables_in_constraints, data.number_of_variables) # objective pass objective_type = MOI.get(model, MOI.ObjectiveFunctionType()) @@ -56,13 +60,109 @@ function MathOptAnalyzer.analyze( # variable index constraints are not counted in the constraints pass list_of_variables = MOI.get(model, MOI.ListOfVariableIndices()) for var in list_of_variables - if !(var in data.variables_in_constraints) + if !(var in data._variables_in_constraints) push!( data.variables_not_in_constraints, VariableNotInConstraints(var), ) end end + # compute ranges and dynamic range violations + if !isempty(data._large_matrix_coefficient) && + !isempty(data._small_matrix_coefficient) + lower = data._small_matrix_coefficient[1][1] + upper = data._large_matrix_coefficient[1][1] + range = upper / lower + if range > data.threshold_dynamic_range_matrix + push!( + data.large_dynamic_range_matrix, + LargeDynamicRangeMatrix( + data._small_matrix_coefficient[1][2], + data._large_matrix_coefficient[1][2], + data._small_matrix_coefficient[1][3], + data._large_matrix_coefficient[1][3], + lower, + upper, + ), + ) + end + data.matrix_range = [lower, upper] + end + if !isempty(data._large_rhs_coefficient) && + !isempty(data._small_rhs_coefficient) + lower = data._small_rhs_coefficient[1][1] + upper = data._large_rhs_coefficient[1][1] + range = upper / lower + if range > data.threshold_dynamic_range_single + push!( + data.large_dynamic_range_rhs, + LargeDynamicRangeRHS( + data._small_rhs_coefficient[1][2], + data._large_rhs_coefficient[1][2], + lower, + upper, + ), + ) + end + data.rhs_range = [lower, upper] + end + if !isempty(data._large_bound_coefficient) && + !isempty(data._small_bound_coefficient) + lower = data._small_bound_coefficient[1][1] + upper = data._large_bound_coefficient[1][1] + range = upper / lower + if range > data.threshold_dynamic_range_single + push!( + data.large_dynamic_range_bounds, + LargeDynamicRangeBound( + data._small_bound_coefficient[1][2], + data._large_bound_coefficient[1][2], + lower, + upper, + ), + ) + end + data.bounds_range = [lower, upper] + end + if !isempty(data._large_objective_coefficient) && + !isempty(data._small_objective_coefficient) + lower = data._small_objective_coefficient[1][1] + upper = data._large_objective_coefficient[1][1] + range = upper / lower + if range > data.threshold_dynamic_range_single + push!( + data.large_dynamic_range_objective, + LargeDynamicRangeObjective( + data._small_objective_coefficient[1][2], + data._large_objective_coefficient[1][2], + lower, + upper, + ), + ) + end + data.objective_range = [lower, upper] + end + + for var in list_of_variables + if haskey(data._large_variable_coefficient, var) + lower = data._small_variable_coefficient[var][1] + upper = data._large_variable_coefficient[var][1] + range = upper / lower + if range > data.threshold_dynamic_range_single + push!( + data.large_dynamic_range_variables, + LargeDynamicRangeVariable( + var, + data._small_variable_coefficient[var][2], + data._large_variable_coefficient[var][2], + lower, + upper, + ), + ) + end + end + end + sort!(data.dense_rows, by = x -> x.nnz, rev = true) sort!(data.matrix_small, by = x -> abs(x.coefficient)) sort!(data.matrix_large, by = x -> abs(x.coefficient), rev = true) @@ -81,6 +181,16 @@ function MathOptAnalyzer.analyze( by = x -> abs(x.coefficient), rev = true, ) + sort!( + data.large_dynamic_range_constraints, + by = x -> (x.upper / x.lower), + rev = true, + ) + sort!( + data.large_dynamic_range_variables, + by = x -> (x.upper / x.lower), + rev = true, + ) return data end @@ -102,7 +212,7 @@ function _get_objective_data(data, func::MOI.ScalarAffineFunction) if iszero(coefficient) continue end - nnz += _update_range(data.objective_range, coefficient) + nnz += _update_objective_range(data, coefficient, variable) if abs(coefficient) < data.threshold_small push!( data.objective_small, @@ -118,6 +228,109 @@ function _get_objective_data(data, func::MOI.ScalarAffineFunction) return end +function _reset_function_range(data::Data) + empty!(data._large_constraint_coefficient) + empty!(data._small_constraint_coefficient) + sizehint!(data._large_constraint_coefficient, 1) + sizehint!(data._small_constraint_coefficient, 1) + return +end + +function _update_objective_range(data::Data, value, variable) + if iszero(value) + return 0 + end + value = abs(value) + if isempty(data._large_objective_coefficient) + push!(data._large_objective_coefficient, (value, variable)) + elseif value > data._large_objective_coefficient[1][1] + data._large_objective_coefficient[1] = (value, variable) + end + if isempty(data._small_objective_coefficient) + push!(data._small_objective_coefficient, (value, variable)) + elseif value < data._small_objective_coefficient[1][1] + data._small_objective_coefficient[1] = (value, variable) + end + return 1 +end + +function _update_bound_range(data::Data, value, variable) + if iszero(value) + return 0 + end + value = abs(value) + if isempty(data._large_bound_coefficient) + push!(data._large_bound_coefficient, (value, variable)) + elseif value > data._large_bound_coefficient[1][1] + data._large_bound_coefficient[1] = (value, variable) + end + if isempty(data._small_bound_coefficient) + push!(data._small_bound_coefficient, (value, variable)) + elseif value < data._small_bound_coefficient[1][1] + data._small_bound_coefficient[1] = (value, variable) + end + return 1 +end + +function _update_constraint_range(data::Data, value, variable, ref) + if iszero(value) + return 0 + end + value = abs(value) + # + if isempty(data._large_constraint_coefficient) + push!(data._large_constraint_coefficient, (value, variable)) + elseif value > data._large_constraint_coefficient[1][1] + data._large_constraint_coefficient[1] = (value, variable) + end + if isempty(data._small_constraint_coefficient) + push!(data._small_constraint_coefficient, (value, variable)) + elseif value < data._small_constraint_coefficient[1][1] + data._small_constraint_coefficient[1] = (value, variable) + end + # + if isempty(data._large_matrix_coefficient) + push!(data._large_matrix_coefficient, (value, ref, variable)) + elseif value > data._large_matrix_coefficient[1][1] + data._large_matrix_coefficient[1] = (value, ref, variable) + end + if isempty(data._small_matrix_coefficient) + push!(data._small_matrix_coefficient, (value, ref, variable)) + elseif value < data._small_matrix_coefficient[1][1] + data._small_matrix_coefficient[1] = (value, ref, variable) + end + # + if !haskey(data._large_variable_coefficient, variable) + data._large_variable_coefficient[variable] = (value, ref) + elseif value > data._large_variable_coefficient[variable][1] + data._large_variable_coefficient[variable] = (value, ref) + end + if !haskey(data._small_variable_coefficient, variable) + data._small_variable_coefficient[variable] = (value, ref) + elseif value < data._small_variable_coefficient[variable][1] + data._small_variable_coefficient[variable] = (value, ref) + end + return 1 +end + +function _update_rhs_range(data::Data, value, constraint) + if iszero(value) + return 0 + end + # + if isempty(data._large_rhs_coefficient) + push!(data._large_rhs_coefficient, (value, constraint)) + elseif value > data._large_rhs_coefficient[1][1] + data._large_rhs_coefficient[1] = (value, constraint) + end + if isempty(data._small_rhs_coefficient) + push!(data._small_rhs_coefficient, (value, constraint)) + elseif value < data._small_rhs_coefficient[1][1] + data._small_rhs_coefficient[1] = (value, constraint) + end + return 1 +end + function _get_objective_data( data, func::MOI.ScalarQuadraticFunction{T}, @@ -231,13 +444,14 @@ function _get_constraint_matrix_data( end end nnz = 0 + _reset_function_range(data) for term in func.terms variable = term.variable coefficient = term.coefficient if iszero(coefficient) continue end - nnz += _update_range(data.matrix_range, coefficient) + nnz += _update_constraint_range(data, coefficient, variable, ref) if abs(coefficient) < data.threshold_small push!( data.matrix_small, @@ -249,7 +463,7 @@ function _get_constraint_matrix_data( LargeMatrixCoefficient(ref, variable, coefficient), ) end - push!(data.variables_in_constraints, variable) + push!(data._variables_in_constraints, variable) end if nnz == 0 if !ignore_extras @@ -261,6 +475,25 @@ function _get_constraint_matrix_data( nnz > data.threshold_dense_entries push!(data.dense_rows, DenseConstraint(ref, nnz)) end + range = 1.0 + if !isempty(data._large_constraint_coefficient) + upper = data._large_constraint_coefficient[1][1] + lower = data._small_constraint_coefficient[1][1] + range = upper / lower + end + if range > data.threshold_dynamic_range_single + push!( + data.large_dynamic_range_constraints, + LargeDynamicRangeConstraint( + ref, + data._small_constraint_coefficient[1][2], + data._large_constraint_coefficient[1][2], + lower, + upper, + ), + ) + end + _reset_function_range(data) data.matrix_nnz += nnz return end @@ -290,8 +523,8 @@ function _get_constraint_matrix_data( LargeMatrixQuadraticCoefficient(ref, v1, v2, coefficient), ) end - push!(data.variables_in_constraints, v1) - push!(data.variables_in_constraints, v2) + push!(data._variables_in_constraints, v1) + push!(data._variables_in_constraints, v2) end data.has_quadratic_constraints = true _get_constraint_matrix_data( @@ -306,8 +539,11 @@ end function _get_constraint_matrix_data( data, ref::MOI.ConstraintIndex, - func::MOI.VectorAffineFunction{T}, + func::MOI.VectorAffineFunction{T}; + ignore_extras::Bool = false, ) where {T} + nnz = 0 + _reset_function_range(data) for term in func.terms variable = term.scalar_term.variable coefficient = term.scalar_term.coefficient @@ -315,7 +551,7 @@ function _get_constraint_matrix_data( if iszero(coefficient) continue end - _update_range(data.matrix_range, coefficient) + nnz += _update_constraint_range(data, coefficient, variable, ref) if abs(coefficient) < data.threshold_small push!( data.matrix_small, @@ -327,8 +563,40 @@ function _get_constraint_matrix_data( LargeMatrixCoefficient(ref, variable, coefficient), ) end - push!(data.variables_in_constraints, variable) + push!(data._variables_in_constraints, variable) + end + if nnz == 0 + if !ignore_extras + push!(data.empty_rows, EmptyConstraint(ref)) + end + return + end + # this computation for vector constraint can be more complicated + # as this might need to be per index + # if nnz / data.number_of_variables > data.threshold_dense_fill_in && + # nnz > data.threshold_dense_entries + # push!(data.dense_rows, DenseConstraint(ref, nnz)) + # end + range = 1.0 + if !isempty(data._large_constraint_coefficient) + upper = data._large_constraint_coefficient[1][1] + lower = data._small_constraint_coefficient[1][1] + range = upper / lower + end + if range > data.threshold_dynamic_range_single + push!( + data.large_dynamic_range_constraints, + LargeDynamicRangeConstraint( + ref, + data._small_constraint_coefficient[1][2], + data._large_constraint_coefficient[1][2], + lower, + upper, + ), + ) end + _reset_function_range(data) + data.matrix_nnz += nnz return end @@ -337,6 +605,7 @@ function _get_constraint_matrix_data( ref::MOI.ConstraintIndex, func::MOI.VectorQuadraticFunction{T}, ) where {T} + nnz = 0 for term in func.quadratic_terms v1 = term.scalar_term.variable_1 v2 = term.scalar_term.variable_2 @@ -344,7 +613,7 @@ function _get_constraint_matrix_data( if iszero(coefficient) continue end - _update_range(data.matrix_quadratic_range, coefficient) + nnz += _update_range(data.matrix_quadratic_range, coefficient) if abs(coefficient) < data.threshold_small push!( data.matrix_quadratic_small, @@ -356,14 +625,14 @@ function _get_constraint_matrix_data( LargeMatrixQuadraticCoefficient(ref, v1, v2, coefficient), ) end - push!(data.variables_in_constraints, v1) - push!(data.variables_in_constraints, v2) + push!(data._variables_in_constraints, v1) + push!(data._variables_in_constraints, v2) end _get_constraint_matrix_data( data, ref, MOI.VectorAffineFunction{T}(func.affine_terms, func.constants), - # ignore_extras = nnz > 0, + ignore_extras = nnz > 0, ) return end @@ -373,7 +642,7 @@ function _get_constraint_matrix_data( ref::MOI.ConstraintIndex, func::MOI.VariableIndex, ) - # push!(data.variables_in_constraints, func) + # push!(data._variables_in_constraints, func) return end @@ -386,7 +655,7 @@ function _get_constraint_matrix_data( return end for var in func.variables - push!(data.variables_in_constraints, var) + push!(data._variables_in_constraints, var) end return end @@ -401,7 +670,7 @@ function _get_constraint_data( if iszero(coefficient) return end - _update_range(data.rhs_range, coefficient) + _update_rhs_range(data, coefficient, ref) if abs(coefficient) < data.threshold_small push!(data.rhs_small, SmallRHSCoefficient(ref, coefficient)) elseif abs(coefficient) > data.threshold_large @@ -422,7 +691,7 @@ function _get_constraint_data( if iszero(coefficient) continue end - _update_range(data.rhs_range, coefficient) + _update_rhs_range(data, coefficient, ref) if abs(coefficient) < data.threshold_small push!(data.rhs_small, SmallRHSCoefficient(ref, coefficient)) elseif abs(coefficient) > data.threshold_large @@ -478,7 +747,7 @@ function _get_constraint_data( if iszero(coefficient) return end - _update_range(data.rhs_range, coefficient) + _update_rhs_range(data, coefficient, ref) if abs(coefficient) < data.threshold_small push!(data.rhs_small, SmallRHSCoefficient(ref, coefficient)) elseif abs(coefficient) > data.threshold_large @@ -533,7 +802,7 @@ function _get_constraint_data( if iszero(coefficient) return end - _update_range(data.rhs_range, coefficient) + _update_rhs_range(data, coefficient, ref) if abs(coefficient) < data.threshold_small push!(data.rhs_small, SmallRHSCoefficient(ref, coefficient)) elseif abs(coefficient) > data.threshold_large @@ -584,7 +853,7 @@ function _get_constraint_data( if iszero(coefficient) return end - _update_range(data.rhs_range, coefficient) + _update_rhs_range(data, coefficient, ref) if abs(coefficient) < data.threshold_small push!(data.rhs_small, SmallRHSCoefficient(ref, coefficient)) elseif abs(coefficient) > data.threshold_large @@ -601,7 +870,7 @@ function _get_constraint_data( ) coefficient = set.upper - func.constant if !(iszero(coefficient)) - _update_range(data.rhs_range, coefficient) + _update_rhs_range(data, coefficient, ref) if abs(coefficient) < data.threshold_small push!(data.rhs_small, SmallRHSCoefficient(ref, coefficient)) elseif abs(coefficient) > data.threshold_large @@ -612,7 +881,7 @@ function _get_constraint_data( if iszero(coefficient) return end - _update_range(data.rhs_range, coefficient) + _update_rhs_range(data, coefficient, ref) if abs(coefficient) < data.threshold_small push!(data.rhs_small, SmallRHSCoefficient(ref, coefficient)) elseif abs(coefficient) > data.threshold_large @@ -668,7 +937,7 @@ end function _get_variable_data(data, variable, coefficient::Number) if !(iszero(coefficient)) - _update_range(data.bounds_range, coefficient) + _update_bound_range(data, coefficient, variable) if abs(coefficient) < data.threshold_small push!( data.bounds_small, diff --git a/src/Numerical/structs.jl b/src/Numerical/structs.jl index 463465d..40f487c 100644 --- a/src/Numerical/structs.jl +++ b/src/Numerical/structs.jl @@ -19,6 +19,8 @@ julia> data = MathOptAnalyzer.analyze( threshold_dense_entries = 1000, threshold_small = 1e-5, threshold_large = 1e+5, + threshold_dynamic_range_single = 1e+6, + threshold_dynamic_range_matrix = 1e+8, ) ``` @@ -29,7 +31,11 @@ The additional parameters: constraint to be considered dense. - `threshold_small`: The threshold for small coefficients in the model. - `threshold_large`: The threshold for large coefficients in the model. - +- `threshold_dynamic_range_single`: The threshold for the range of coefficients + in the model with respect with individual columns, rows, objective + coefficients and rhs coefficients. +- `threshold_dynamic_range_matrix`: The threshold for the range of coefficients + in the model with respect to the matrix of coefficients. """ struct Analyzer <: MathOptAnalyzer.AbstractAnalyzer end @@ -428,8 +434,204 @@ julia> MathOptAnalyzer.summarize( struct NonconvexQuadraticConstraint <: AbstractNumericalIssue ref::MOI.ConstraintIndex end + MathOptAnalyzer.constraint(issue::NonconvexQuadraticConstraint) = issue.ref +""" + LargeDynamicRangeConstraint <: AbstractNumericalIssue + +The `LargeDynamicRangeConstraint` issue is identified when the dynamic range of +a constraint is larger than `threshold_dynamic_range_single`. The dynamic range +is defined as the ratio between the largest and smallest coefficients of the +constraint. + +For more information, run: +```julia +julia> MathOptAnalyzer.summarize( + MathOptAnalyzer.Numerical.LargeDynamicRangeConstraint +) +``` +""" +struct LargeDynamicRangeConstraint <: AbstractNumericalIssue + ref::MOI.ConstraintIndex + variable_lower::MOI.VariableIndex + variable_upper::MOI.VariableIndex + lower::Float64 + upper::Float64 +end + +function MathOptAnalyzer.constraint(issue::LargeDynamicRangeConstraint) + return issue.ref +end + +function MathOptAnalyzer.variables(issue::LargeDynamicRangeConstraint) + return [issue.variable_lower, issue.variable_upper] +end + +function MathOptAnalyzer.values(issue::LargeDynamicRangeConstraint) + return [issue.lower, issue.upper] +end + +""" + LargeDynamicRangeMatrix <: AbstractNumericalIssue + +The `LargeDynamicRangeMatrix` issue is identified when the dynamic range of the +matrix is larger than `threshold_dynamic_range_matrix`. The dynamic range is +defined as the ratio between the largest and smallest coefficients of the +matrix. + +For more information, run: +```julia +julia> MathOptAnalyzer.summarize( + MathOptAnalyzer.Numerical.LargeDynamicRangeMatrix +) +``` +""" +struct LargeDynamicRangeMatrix <: AbstractNumericalIssue + constraint_lower::MOI.ConstraintIndex + constraint_upper::MOI.ConstraintIndex + variable_lower::MOI.VariableIndex + variable_upper::MOI.VariableIndex + lower::Float64 + upper::Float64 +end + +function MathOptAnalyzer.constraints(issue::LargeDynamicRangeMatrix) + return [issue.constraint_lower, issue.constraint_upper] +end + +function MathOptAnalyzer.variables(issue::LargeDynamicRangeMatrix) + return [issue.variable_lower, issue.variable_upper] +end + +function MathOptAnalyzer.values(issue::LargeDynamicRangeMatrix) + return [issue.lower, issue.upper] +end + +""" + LargeDynamicRangeObjective <: AbstractNumericalIssue + +The `LargeDynamicRangeObjective` issue is identified when the dynamic range of +the objective function is larger than `threshold_dynamic_range_single`. The +dynamic range is defined as the ratio between the largest and smallest +coefficients of the objective function. + +For more information, run: +```julia +julia> MathOptAnalyzer.summarize( + MathOptAnalyzer.Numerical.LargeDynamicRangeObjective +) +``` +""" +struct LargeDynamicRangeObjective <: AbstractNumericalIssue + variable_lower::MOI.VariableIndex + variable_upper::MOI.VariableIndex + lower::Float64 + upper::Float64 +end + +function MathOptAnalyzer.variables(issue::LargeDynamicRangeObjective) + return [issue.variable_lower, issue.variable_upper] +end + +function MathOptAnalyzer.values(issue::LargeDynamicRangeObjective) + return [issue.lower, issue.upper] +end + +""" + LargeDynamicRangeRHS <: AbstractNumericalIssue + +The `LargeDynamicRangeRHS` issue is identified when the dynamic range of the +right-hand side (RHS) vector is larger than `threshold_dynamic_range_rhs`. The +dynamic range is defined as the ratio between the largest and smallest +coefficients of the RHS vector. + +For more information, run: +```julia +julia> MathOptAnalyzer.summarize( + MathOptAnalyzer.Numerical.LargeDynamicRangeRHS +) +``` +""" +struct LargeDynamicRangeRHS <: AbstractNumericalIssue + constraint_lower::MOI.ConstraintIndex + constraint_upper::MOI.ConstraintIndex + lower::Float64 + upper::Float64 +end + +function MathOptAnalyzer.constraints(issue::LargeDynamicRangeRHS) + return [issue.constraint_lower, issue.constraint_upper] +end + +function MathOptAnalyzer.values(issue::LargeDynamicRangeRHS) + return [issue.lower, issue.upper] +end + +""" + LargeDynamicRangeVariable <: AbstractNumericalIssue + +The `LargeDynamicRangeVariable` issue is identified when the dynamic range of a +variable is larger than `threshold_dynamic_range_single`. The dynamic range is +defined as the ratio between the largest and smallest coefficients of the +variable inall constraint it appears. + +For more information, run: +```julia +julia> MathOptAnalyzer.summarize( + MathOptAnalyzer.Numerical.LargeDynamicRangeVariable +) +``` +""" +struct LargeDynamicRangeVariable <: AbstractNumericalIssue + variable::MOI.VariableIndex + constraint_lower::MOI.ConstraintIndex + constraint_upper::MOI.ConstraintIndex + lower::Float64 + upper::Float64 +end + +function MathOptAnalyzer.variable(issue::LargeDynamicRangeVariable) + return issue.variable +end + +function MathOptAnalyzer.constraints(issue::LargeDynamicRangeVariable) + return [issue.constraint_lower, issue.constraint_upper] +end + +function MathOptAnalyzer.values(issue::LargeDynamicRangeVariable) + return [issue.lower, issue.upper] +end + +""" + LargeDynamicRangeBound <: AbstractNumericalIssue + +The `LargeDynamicRangeBound` issue is identified when the dynamic range of the +variables bounds is larger than `threshold_dynamic_range_single`. The dynamic +range is defined as the ratio between the largest and smallest bounds of all +variables. +For more information, run: +```julia +julia> MathOptAnalyzer.summarize( + MathOptAnalyzer.Numerical.LargeDynamicRangeBound +) +``` +""" +struct LargeDynamicRangeBound <: AbstractNumericalIssue + variable_lower::MOI.VariableIndex + variable_upper::MOI.VariableIndex + lower::Float64 + upper::Float64 +end + +function MathOptAnalyzer.variables(issue::LargeDynamicRangeBound) + return [issue.variable_lower, issue.variable_upper] +end + +function MathOptAnalyzer.values(issue::LargeDynamicRangeBound) + return [issue.lower, issue.upper] +end + """ Data @@ -443,6 +645,8 @@ Base.@kwdef mutable struct Data <: MathOptAnalyzer.AbstractData threshold_dense_entries::Int = 1000 threshold_small::Float64 = 1e-5 threshold_large::Float64 = 1e+5 + threshold_dynamic_range_single::Float64 = 1e+6 + threshold_dynamic_range_matrix::Float64 = 1e+8 # main numbers number_of_variables::Int = 0 number_of_constraints::Int = 0 @@ -450,13 +654,45 @@ Base.@kwdef mutable struct Data <: MathOptAnalyzer.AbstractData Tuple{DataType,DataType,Int}[] # objective_info::Any matrix_nnz::Int = 0 - # ranges + # ranges are filled after the caches for large and small coefficients matrix_range::Vector{Float64} = sizehint!(Float64[1.0, 1.0], 2) bounds_range::Vector{Float64} = sizehint!(Float64[1.0, 1.0], 2) rhs_range::Vector{Float64} = sizehint!(Float64[1.0, 1.0], 2) objective_range::Vector{Float64} = sizehint!(Float64[1.0, 1.0], 2) # cache data - variables_in_constraints::Set{MOI.VariableIndex} = Set{MOI.VariableIndex}() + _variables_in_constraints::Set{MOI.VariableIndex} = Set{MOI.VariableIndex}() + # for computing dynamic ranges + _large_matrix_coefficient::Vector{ + Tuple{Float64,MOI.ConstraintIndex,MOI.VariableIndex}, + } = sizehint!(Tuple{Float64,MOI.ConstraintIndex,MOI.VariableIndex}[], 1) + _small_matrix_coefficient::Vector{ + Tuple{Float64,MOI.ConstraintIndex,MOI.VariableIndex}, + } = sizehint!(Tuple{Float64,MOI.ConstraintIndex,MOI.VariableIndex}[], 1) + _large_variable_coefficient::Dict{ + MOI.VariableIndex, + Tuple{Float64,MOI.ConstraintIndex}, + } = Dict{MOI.VariableIndex,Tuple{Float64,MOI.ConstraintIndex}}() + _small_variable_coefficient::Dict{ + MOI.VariableIndex, + Tuple{Float64,MOI.ConstraintIndex}, + } = Dict{MOI.VariableIndex,Tuple{Float64,MOI.ConstraintIndex}}() + _large_rhs_coefficient::Vector{Tuple{Float64,MOI.ConstraintIndex}} = + sizehint!(Tuple{Float64,MOI.ConstraintIndex}[], 1) + _small_rhs_coefficient::Vector{Tuple{Float64,MOI.ConstraintIndex}} = + sizehint!(Tuple{Float64,MOI.ConstraintIndex}[], 1) + _large_objective_coefficient::Vector{Tuple{Float64,MOI.VariableIndex}} = + sizehint!(Tuple{Float64,MOI.VariableIndex}[], 1) + _small_objective_coefficient::Vector{Tuple{Float64,MOI.VariableIndex}} = + sizehint!(Tuple{Float64,MOI.VariableIndex}[], 1) + _large_bound_coefficient::Vector{Tuple{Float64,MOI.VariableIndex}} = + sizehint!(Tuple{Float64,MOI.VariableIndex}[], 1) + _small_bound_coefficient::Vector{Tuple{Float64,MOI.VariableIndex}} = + sizehint!(Tuple{Float64,MOI.VariableIndex}[], 1) + # the two below are used to compute dynamic ranges of constraint functions + _large_constraint_coefficient::Vector{Tuple{Float64,MOI.VariableIndex}} = + sizehint!(Tuple{Float64,MOI.VariableIndex}[], 1) + _small_constraint_coefficient::Vector{Tuple{Float64,MOI.VariableIndex}} = + sizehint!(Tuple{Float64,MOI.VariableIndex}[], 1) # variables analysis variables_not_in_constraints::Vector{VariableNotInConstraints} = VariableNotInConstraints[] @@ -470,6 +706,19 @@ Base.@kwdef mutable struct Data <: MathOptAnalyzer.AbstractData matrix_large::Vector{LargeMatrixCoefficient} = LargeMatrixCoefficient[] rhs_small::Vector{SmallRHSCoefficient} = SmallRHSCoefficient[] rhs_large::Vector{LargeRHSCoefficient} = LargeRHSCoefficient[] + # dynamic range analysis + large_dynamic_range_constraints::Vector{LargeDynamicRangeConstraint} = + LargeDynamicRangeConstraint[] + large_dynamic_range_matrix::Vector{LargeDynamicRangeMatrix} = + LargeDynamicRangeMatrix[] + large_dynamic_range_objective::Vector{LargeDynamicRangeObjective} = + LargeDynamicRangeObjective[] + large_dynamic_range_rhs::Vector{LargeDynamicRangeRHS} = + LargeDynamicRangeRHS[] + large_dynamic_range_variables::Vector{LargeDynamicRangeVariable} = + LargeDynamicRangeVariable[] + large_dynamic_range_bounds::Vector{LargeDynamicRangeBound} = + LargeDynamicRangeBound[] # quadratic constraints analysis has_quadratic_constraints::Bool = false nonconvex_rows::Vector{NonconvexQuadraticConstraint} = diff --git a/src/Numerical/summarize.jl b/src/Numerical/summarize.jl index 5c971a8..fe1edae 100644 --- a/src/Numerical/summarize.jl +++ b/src/Numerical/summarize.jl @@ -90,6 +90,30 @@ function MathOptAnalyzer._summarize( return print(io, "# NonconvexQuadraticConstraint") end +function MathOptAnalyzer._summarize(io::IO, ::Type{LargeDynamicRangeConstraint}) + return print(io, "# LargeDynamicRangeConstraint") +end + +function MathOptAnalyzer._summarize(io::IO, ::Type{LargeDynamicRangeMatrix}) + return print(io, "# LargeDynamicRangeMatrix") +end + +function MathOptAnalyzer._summarize(io::IO, ::Type{LargeDynamicRangeObjective}) + return print(io, "# LargeDynamicRangeObjective") +end + +function MathOptAnalyzer._summarize(io::IO, ::Type{LargeDynamicRangeRHS}) + return print(io, "# LargeDynamicRangeRHS") +end + +function MathOptAnalyzer._summarize(io::IO, ::Type{LargeDynamicRangeVariable}) + return print(io, "# LargeDynamicRangeVariable") +end + +function MathOptAnalyzer._summarize(io::IO, ::Type{LargeDynamicRangeBound}) + return print(io, "# LargeDynamicRangeBound") +end + function MathOptAnalyzer._verbose_summarize( io::IO, ::Type{VariableNotInConstraints}, @@ -676,6 +700,212 @@ function MathOptAnalyzer._verbose_summarize( ) end +function MathOptAnalyzer._verbose_summarize( + io::IO, + ::Type{LargeDynamicRangeConstraint}, +) + return print( + io, + """ + # `LargeDynamicRangeConstraint` + + ## What + + A `LargeDynamicRangeConstraint` issue is identified when a constraint + has a large dynamic range, that is, the ratio between the largest and + smallest coefficient is large. + + ## Why + + Large dynamic ranges can lead to numerical instability in the solution + process. + + ## How to fix + + Check if the constraint is correct. Check if the units of variables and + coefficients are correct. Check if the number makes is + reasonable given that solver have tolerances. Sometimes these + coefficients can be replaced by zeros. + + ## More information + + No extra information for this issue. + """, + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + ::Type{LargeDynamicRangeMatrix}, +) + return print( + io, + """ + # `LargeDynamicRangeMatrix` + + ## What + + A `LargeDynamicRangeMatrix` issue is identified when a matrix has a + large dynamic range, that is, the ratio between the largest and smallest + coefficient is large. + + ## Why + + Large dynamic ranges can lead to numerical instability in the solution + process. + + ## How to fix + + Check if the matrix is correct. Check if the units of variables and + coefficients are correct. Check if the number makes is + reasonable given that solver have tolerances. Sometimes these + coefficients can be replaced by zeros. + + ## More information + + No extra information for this issue. + """, + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + ::Type{LargeDynamicRangeObjective}, +) + return print( + io, + """ + # `LargeDynamicRangeObjective` + + ## What + + A `LargeDynamicRangeObjective` issue is identified when an objective + function has a large dynamic range, that is, the ratio between the + largest and smallest coefficient is large. + + ## Why + + Large dynamic ranges can lead to numerical instability in the solution + process. + + ## How to fix + + Check if the objective is correct. Check if the units of variables and + coefficients are correct. Check if the number makes is + reasonable given that solver have tolerances. Sometimes these + coefficients can be replaced by zeros. + + ## More information + + No extra information for this issue. + """, + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + ::Type{LargeDynamicRangeRHS}, +) + return print( + io, + """ + # `LargeDynamicRangeRHS` + + ## What + + A `LargeDynamicRangeRHS` issue is identified when the constraints + right-hand-side has a large dynamic range, that is, the ratio between + the largest and smallest rhs is large. + + ## Why + + Large dynamic ranges can lead to numerical instability in the solution + process. + + ## How to fix + + Check if the right-hand-side is correct. Check if the units of variables + and coefficients are correct. Check if the number makes is + reasonable given that solver have tolerances. Sometimes these + right-hand-sides can be replaced by zeros. + + ## More information + + No extra information for this issue. + """, + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + ::Type{LargeDynamicRangeVariable}, +) + return print( + io, + """ + # `LargeDynamicRangeVariable` + + ## What + + A `LargeDynamicRangeVariable` issue is identified when the dynamic range of a + variable is larger than `threshold_dynamic_range_single`. The dynamic range is + defined as the ratio between the largest and smallest coefficients of the + variable in all constraints it appears. + + ## Why + + Large dynamic ranges can lead to numerical instability in the solution + process. + + ## How to fix + + Check if the variable is correct. Check if the units of variables and + coefficients are correct. Check if the number makes is + reasonable given that solver have tolerances. Sometimes these + coefficients can be replaced by zeros. + + ## More information + + No extra information for this issue. + """, + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + ::Type{LargeDynamicRangeBound}, +) + return print( + io, + """ + # `LargeDynamicRangeBound` + + ## What + + A `LargeDynamicRangeBound` issue is identified when the dynamic range of + the bound is larger than `threshold_dynamic_range_single`. The dynamic range is + defined as the ratio between the largest and smallest bounds of all + variables + + ## Why + + Large dynamic ranges can lead to numerical instability in the solution + process. + + ## How to fix + + Check if the bound is correct. Check if the units of variables and + coefficients are correct. Check if the number makes is + reasonable given that solver have tolerances. Sometimes these + coefficients can be replaced by zeros. + + ## More information + + No extra information for this issue. + """, + ) +end + function MathOptAnalyzer._summarize( io::IO, issue::VariableNotInConstraints, @@ -872,6 +1102,115 @@ function MathOptAnalyzer._summarize( return print(io, MathOptAnalyzer._name(issue.ref, model)) end +function MathOptAnalyzer._summarize( + io::IO, + issue::LargeDynamicRangeConstraint, + model, +) + range = issue.upper / issue.lower + return print( + io, + MathOptAnalyzer._name(issue.ref, model), + " : ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._summarize( + io::IO, + issue::LargeDynamicRangeMatrix, + model, +) + range = issue.upper / issue.lower + return print( + io, + "Matrix dynamic range : ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._summarize( + io::IO, + issue::LargeDynamicRangeObjective, + model, +) + range = issue.upper / issue.lower + return print( + io, + "Objective dynamic range : ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._summarize(io::IO, issue::LargeDynamicRangeRHS, model) + range = issue.upper / issue.lower + return print( + io, + "RHS dynamic range : ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._summarize( + io::IO, + issue::LargeDynamicRangeVariable, + model, +) + range = issue.upper / issue.lower + return print( + io, + MathOptAnalyzer._name(issue.variable, model), + " : ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._summarize( + io::IO, + issue::LargeDynamicRangeBound, + model, +) + range = issue.upper / issue.lower + return print( + io, + MathOptAnalyzer._name(issue.variable_lower, model), + " -- ", + MathOptAnalyzer._name(issue.variable_upper, model), + " : ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + function MathOptAnalyzer._verbose_summarize( io::IO, issue::VariableNotInConstraints, @@ -1111,6 +1450,118 @@ function MathOptAnalyzer._verbose_summarize( return print(io, "Constraint: ", MathOptAnalyzer._name(issue.ref, model)) end +function MathOptAnalyzer._verbose_summarize( + io::IO, + issue::LargeDynamicRangeConstraint, + model, +) + range = issue.upper / issue.lower + return print( + io, + "Constraint: ", + MathOptAnalyzer._name(issue.ref, model), + " with dynamic range ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + issue::LargeDynamicRangeMatrix, + model, +) + range = issue.upper / issue.lower + return print( + io, + "Matrix dynamic range: ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + issue::LargeDynamicRangeObjective, + model, +) + range = issue.upper / issue.lower + return print( + io, + "Objective dynamic range: ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + issue::LargeDynamicRangeRHS, + model, +) + range = issue.upper / issue.lower + return print( + io, + "RHS dynamic range: ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + issue::LargeDynamicRangeVariable, + model, +) + range = issue.upper / issue.lower + return print( + io, + "Variable: ", + MathOptAnalyzer._name(issue.variable, model), + " with dynamic range ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + +function MathOptAnalyzer._verbose_summarize( + io::IO, + issue::LargeDynamicRangeBound, + model, +) + range = issue.upper / issue.lower + return print( + io, + "Bounds with dynamic range: ", + range, + " , [", + issue.lower, + ", ", + issue.upper, + "]", + ) +end + function MathOptAnalyzer.list_of_issues( data::Data, ::Type{VariableNotInConstraints}, @@ -1225,6 +1676,48 @@ function MathOptAnalyzer.list_of_issues( return data.nonconvex_rows end +function MathOptAnalyzer.list_of_issues( + data::Data, + ::Type{LargeDynamicRangeConstraint}, +) + return data.large_dynamic_range_constraints +end + +function MathOptAnalyzer.list_of_issues( + data::Data, + ::Type{LargeDynamicRangeMatrix}, +) + return data.large_dynamic_range_matrix +end + +function MathOptAnalyzer.list_of_issues( + data::Data, + ::Type{LargeDynamicRangeObjective}, +) + return data.large_dynamic_range_objective +end + +function MathOptAnalyzer.list_of_issues( + data::Data, + ::Type{LargeDynamicRangeRHS}, +) + return data.large_dynamic_range_rhs +end + +function MathOptAnalyzer.list_of_issues( + data::Data, + ::Type{LargeDynamicRangeVariable}, +) + return data.large_dynamic_range_variables +end + +function MathOptAnalyzer.list_of_issues( + data::Data, + ::Type{LargeDynamicRangeBound}, +) + return data.large_dynamic_range_bounds +end + function MathOptAnalyzer.list_of_issue_types(data::Data) ret = Type[] for type in ( @@ -1246,6 +1739,12 @@ function MathOptAnalyzer.list_of_issue_types(data::Data) LargeMatrixQuadraticCoefficient, NonconvexQuadraticConstraint, NonconvexQuadraticObjective, + LargeDynamicRangeConstraint, + LargeDynamicRangeMatrix, + LargeDynamicRangeObjective, + LargeDynamicRangeRHS, + LargeDynamicRangeVariable, + LargeDynamicRangeBound, ) if !isempty(MathOptAnalyzer.list_of_issues(data, type)) push!(ret, type) @@ -1260,6 +1759,18 @@ function summarize_configurations(io::IO, data::Data) print(io, " Dense entries threshold: ", data.threshold_dense_entries, "\n") print(io, " Small coefficient threshold: ", data.threshold_small, "\n") print(io, " Large coefficient threshold: ", data.threshold_large, "\n") + print( + io, + " Individual dynamic range threshold: ", + data.threshold_dynamic_range_single, + "\n", + ) + print( + io, + " Matrix dynamic range threshold: ", + data.threshold_dynamic_range_matrix, + "\n", + ) return end diff --git a/test/test_Numerical.jl b/test/test_Numerical.jl index d7b650e..0a74f4f 100644 --- a/test/test_Numerical.jl +++ b/test/test_Numerical.jl @@ -30,7 +30,7 @@ function test_variable_bounds() @variable(model, zg == 4e+11) data = MathOptAnalyzer.analyze(MathOptAnalyzer.Numerical.Analyzer(), model) list = MathOptAnalyzer.list_of_issue_types(data) - @test length(list) == 3 + @test length(list) == 4 ret = MathOptAnalyzer.list_of_issues( data, MathOptAnalyzer.Numerical.VariableNotInConstraints, @@ -46,6 +46,11 @@ function test_variable_bounds() MathOptAnalyzer.Numerical.LargeBoundCoefficient, ) @test length(ret) == 3 + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeBound, + ) + @test length(ret) == 1 buf = IOBuffer() MathOptAnalyzer.summarize( @@ -1152,7 +1157,7 @@ function test_vector_functions() @constraint(model, c6, [2 * x[1] * x[1]] in Zeros()) data = MathOptAnalyzer.analyze(MathOptAnalyzer.Numerical.Analyzer(), model) list = MathOptAnalyzer.list_of_issue_types(data) - @test length(list) == 6 + @test length(list) == 8 # ret = MathOptAnalyzer.list_of_issues( data, @@ -1196,6 +1201,23 @@ function test_vector_functions() ) @test length(ret) == 1 @test MathOptAnalyzer.variable(ret[], model) == x[3] + # + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeMatrix, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.variables(ret[], model) == [x[1], x[1]] + @test MathOptAnalyzer.values(ret[]) == [1e-9, 1e+9] + # + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeVariable, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.variable(ret[], model) == x[1] + @test MathOptAnalyzer.constraints(ret[], model) == [c1, c2] + @test MathOptAnalyzer.values(ret[]) == [1e-9, 1e+9] return end @@ -1205,7 +1227,7 @@ function test_variable_interval() @objective(model, Min, x) data = MathOptAnalyzer.analyze(MathOptAnalyzer.Numerical.Analyzer(), model) list = MathOptAnalyzer.list_of_issue_types(data) - @test length(list) == 3 + @test length(list) == 4 ret = MathOptAnalyzer.list_of_issues( data, MathOptAnalyzer.Numerical.SmallBoundCoefficient, @@ -1228,6 +1250,13 @@ function test_variable_interval() ) @test length(ret) == 1 @test MathOptAnalyzer.variable(ret[], model) == x + # + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeBound, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.variables(ret[], model) == [x, x] return end @@ -1357,6 +1386,279 @@ function test_more_than_max_issues() return end +function test_dyn_range_constraint_and_matrix() + model = Model() + @variable(model, x) + @variable(model, y) + @constraint(model, c, 1e-4 * x + 7e4 * y <= 4) + data = MathOptAnalyzer.analyze(MathOptAnalyzer.Numerical.Analyzer(), model) + list = MathOptAnalyzer.list_of_issue_types(data) + @test length(list) == 2 + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeConstraint, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.constraint(ret[], model) == c + @test MathOptAnalyzer.variables(ret[], model) == [x, y] + @test MathOptAnalyzer.values(ret[]) == [1e-4, 7e4] + # + buf = IOBuffer() + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeConstraint, + ) + str = String(take!(buf)) + @test startswith(str, "# `LargeDynamicRangeConstraint`") + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeConstraint, + verbose = false, + ) + str = String(take!(buf)) + @test str == "# LargeDynamicRangeConstraint" + # + MathOptAnalyzer.summarize(buf, ret[1], verbose = true) + str = String(take!(buf)) + @test startswith(str, "Constraint: ") + @test contains(str, " with dynamic range ") + MathOptAnalyzer.summarize(buf, ret[1], verbose = false) + str = String(take!(buf)) + @test contains(str, " : ") + @test contains(str, ", [") + # + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeMatrix, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.variables(ret[], model) == [x, y] + @test MathOptAnalyzer.values(ret[]) == [1e-4, 7e4] + # buf = IOBuffer() + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeMatrix, + ) + str = String(take!(buf)) + @test startswith(str, "# `LargeDynamicRangeMatrix`") + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeMatrix, + verbose = false, + ) + str = String(take!(buf)) + @test str == "# LargeDynamicRangeMatrix" + # + MathOptAnalyzer.summarize(buf, ret[1], verbose = true) + str = String(take!(buf)) + @test startswith(str, "Matrix dynamic range") + MathOptAnalyzer.summarize(buf, ret[1], verbose = false) + str = String(take!(buf)) + @test contains(str, "[") + return +end + +function test_dyn_range_variable_and_matrix() + model = Model() + @variable(model, x) + @variable(model, y) + @constraint(model, c1, 1e-4 * x + y <= 4) + @constraint(model, c2, 7e4 * x + y <= 4) + data = MathOptAnalyzer.analyze(MathOptAnalyzer.Numerical.Analyzer(), model) + list = MathOptAnalyzer.list_of_issue_types(data) + @test length(list) == 2 + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeVariable, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.variable(ret[], model) == x + @test MathOptAnalyzer.constraints(ret[], model) == [c1, c2] + @test MathOptAnalyzer.values(ret[]) == [1e-4, 7e4] + # + buf = IOBuffer() + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeVariable, + ) + str = String(take!(buf)) + @test startswith(str, "# `LargeDynamicRangeVariable`") + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeVariable, + verbose = false, + ) + str = String(take!(buf)) + @test str == "# LargeDynamicRangeVariable" + # + MathOptAnalyzer.summarize(buf, ret[1], verbose = true) + str = String(take!(buf)) + @test startswith(str, "Variable:") + @test contains(str, " with dynamic range ") + MathOptAnalyzer.summarize(buf, ret[1], verbose = false) + str = String(take!(buf)) + @test contains(str, " : ") + @test contains(str, ", [") + # + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeMatrix, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.variables(ret[], model) == [x, x] + @test MathOptAnalyzer.constraints(ret[], model) == [c1, c2] + @test MathOptAnalyzer.values(ret[]) == [1e-4, 7e4] + # buf = IOBuffer() + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeMatrix, + ) + str = String(take!(buf)) + @test startswith(str, "# `LargeDynamicRangeMatrix`") + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeMatrix, + verbose = false, + ) + str = String(take!(buf)) + @test str == "# LargeDynamicRangeMatrix" + # + MathOptAnalyzer.summarize(buf, ret[1], verbose = true) + str = String(take!(buf)) + @test startswith(str, "Matrix dynamic range") + MathOptAnalyzer.summarize(buf, ret[1], verbose = false) + str = String(take!(buf)) + @test contains(str, "[") + return +end + +function test_dyn_range_objective() + model = Model() + @variable(model, x) + @variable(model, y) + @constraint(model, c1, 1 * x + y <= 4) + @constraint(model, c2, 2 * x + y <= 4) + @objective(model, Min, 1e-4 * x + 7e4 * y) + data = MathOptAnalyzer.analyze(MathOptAnalyzer.Numerical.Analyzer(), model) + list = MathOptAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeObjective, + ) + @show ret + @test length(ret) == 1 + @test MathOptAnalyzer.variables(ret[], model) == [x, y] + @test MathOptAnalyzer.values(ret[]) == [1e-4, 7e4] + # + buf = IOBuffer() + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeObjective, + ) + str = String(take!(buf)) + @test startswith(str, "# `LargeDynamicRangeObjective`") + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeObjective, + verbose = false, + ) + str = String(take!(buf)) + @test str == "# LargeDynamicRangeObjective" + # + MathOptAnalyzer.summarize(buf, ret[1], verbose = true) + str = String(take!(buf)) + @test startswith(str, "Objective dynamic range") + MathOptAnalyzer.summarize(buf, ret[1], verbose = false) + str = String(take!(buf)) + @test contains(str, "[") + return +end + +function test_dyn_range_rhs() + model = Model() + @variable(model, x) + @variable(model, y) + @constraint(model, c1, x + y <= 1e-4) + @constraint(model, c2, x + y <= 7e4) + data = MathOptAnalyzer.analyze(MathOptAnalyzer.Numerical.Analyzer(), model) + list = MathOptAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeRHS, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.constraints(ret[], model) == [c1, c2] + @test MathOptAnalyzer.values(ret[]) == [1e-4, 7e4] + # + buf = IOBuffer() + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeRHS, + ) + str = String(take!(buf)) + @test startswith(str, "# `LargeDynamicRangeRHS`") + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeRHS, + verbose = false, + ) + str = String(take!(buf)) + @test str == "# LargeDynamicRangeRHS" + # + MathOptAnalyzer.summarize(buf, ret[1], verbose = true) + str = String(take!(buf)) + @test startswith(str, "RHS dynamic range:") + MathOptAnalyzer.summarize(buf, ret[1], verbose = false) + str = String(take!(buf)) + @test contains(str, " : ") + @test contains(str, ", [") + return +end + +function test_dyn_range_bounds() + model = Model() + @variable(model, x <= 1e-4) + @variable(model, y >= 7e4) + @constraint(model, c1, x + y <= 4) + @constraint(model, c2, x - y >= 3) + data = MathOptAnalyzer.analyze(MathOptAnalyzer.Numerical.Analyzer(), model) + list = MathOptAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = MathOptAnalyzer.list_of_issues( + data, + MathOptAnalyzer.Numerical.LargeDynamicRangeBound, + ) + @test length(ret) == 1 + @test MathOptAnalyzer.variables(ret[], model) == [x, y] + @test MathOptAnalyzer.values(ret[]) == [1e-4, 7e4] + # + buf = IOBuffer() + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeBound, + ) + str = String(take!(buf)) + @test startswith(str, "# `LargeDynamicRangeBound`") + MathOptAnalyzer.summarize( + buf, + MathOptAnalyzer.Numerical.LargeDynamicRangeBound, + verbose = false, + ) + str = String(take!(buf)) + @test str == "# LargeDynamicRangeBound" + # + MathOptAnalyzer.summarize(buf, ret[1], verbose = true) + str = String(take!(buf)) + @test startswith(str, "Bounds with dynamic range:") + MathOptAnalyzer.summarize(buf, ret[1], verbose = false) + str = String(take!(buf)) + @test contains(str, " : ") + @test contains(str, ", [") + return +end + end # module TestNumerical TestNumerical.runtests()