From 71c4f9e9af24255b3e570e9783782aef0c00d24d Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 16 Nov 2023 15:09:26 +1300 Subject: [PATCH 01/15] WIP: remove unecessary parts of C wrapper --- src/C_wrapper.jl | 497 ++------------------------------------------ src/MOI_wrapper.jl | 135 ++++++++---- src/callbacks.jl | 168 ++++++--------- test/MOI_wrapper.jl | 4 +- 4 files changed, 179 insertions(+), 625 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index c11dc5d..d928d6a 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -12,42 +12,6 @@ macro kn_ccall(func, args...) end end -macro kn_get_values(function_name, type) - name_singular = "KN_" * string(function_name) - name_plural = "KN_" * string(function_name) * "s" - fname = Symbol(name_plural) - fnameshort = Symbol(name_singular) - # Names of C function in knitro.h - c_fname = Symbol(name_singular) - c_fnames = Symbol(name_plural) - c_fnames_all = Symbol(name_plural * "_all") - - n = if occursin("var", name_singular) - :(KN_get_number_vars(kc)) - elseif occursin("con", name_singular) - :(KN_get_number_cons(kc)) - end - - quote - function $(esc(fname))(kc::Model) - result = zeros($type, $n) - $c_fnames_all(kc, result) - return result - end - function $(esc(fname))(kc::Model, index::Vector{Cint}) - result = zeros($type, length(index)) - $c_fnames(kc, length(index), index, result) - return result - end - function $(esc(fname))(kc::Model, index::Integer) - result = zeros($type, 1) - $c_fname(kc, index, result) - return result[1] - end - $(esc(fnameshort))(kc::Model, index::Integer) = $(esc(fname))(kc, index) - end -end - macro kn_get_attribute(function_name, type) fname = Symbol("KN_" * string(function_name)) quote @@ -297,220 +261,6 @@ function KN_release_license(lm::LMcontext) return end -#= - VARIABLES -=# - -function KN_add_var(m::Model) - index = Ref{Cint}(0) - KN_add_var(m, index) - return index[] -end - -function KN_add_vars(m::Model, nvars::Int) - indices = zeros(Cint, nvars) - KN_add_vars(m, nvars, indices) - return indices -end - -@kn_get_values get_var_lobnd Cdouble -@kn_get_values get_var_upbnd Cdouble -@kn_get_values get_var_eqbnd Cdouble -@kn_get_values get_var_fxbnd Cdouble -@kn_get_values get_var_primal_value Cdouble -@kn_get_values get_var_dual_value Cdouble -@kn_get_values get_var_type Cint - -#= - OBJECTIVE -=# - -function KN_add_obj_linear_struct( - m::Model, - objIndices::Vector{Cint}, - objCoefs::Vector{Cdouble}, -) - nnz = length(objIndices) - @assert nnz == length(objCoefs) - return KN_add_obj_linear_struct(m, nnz, objIndices, objCoefs) -end - -function KN_add_obj_linear_struct(m::Model, objindex::Int, objCoefs::Cdouble) - return KN_add_obj_linear_struct(m, Cint[objindex], [objCoefs]) -end - -function KN_add_obj_quadratic_struct( - m::Model, - indexVars1::Vector{Cint}, - indexVars2::Vector{Cint}, - coefs::Vector{Cdouble}, -) - nnz = length(indexVars1) - @assert nnz == length(indexVars2) == length(coefs) - return KN_add_obj_quadratic_struct(m, nnz, indexVars1, indexVars2, coefs) -end - -#= - CONSTRAINTS -=# - -function KN_add_cons(m::Model, ncons::Integer) - indices = zeros(Cint, ncons) - KN_add_cons(m, ncons, indices) - return indices -end - -function KN_add_con(m::Model) - index = Ref{Cint}(0) - KN_add_con(m, index) - return index[] -end - -@kn_get_values get_con_lobnd Cdouble -@kn_get_values get_con_upbnd Cdouble -@kn_get_values get_con_eqbnd Cdouble -@kn_get_values get_con_dual_value Cdouble -@kn_get_values get_con_value Cdouble - -function KN_add_con_linear_struct( - m::Model, - index_cons::Vector{Cint}, - index_vars::Vector{Cint}, - coefs::Vector{Cdouble}, -) - @assert length(index_cons) == length(index_vars) == length(coefs) - return KN_add_con_linear_struct(m, length(index_cons), index_cons, index_vars, coefs) -end - -function KN_add_con_linear_struct( - m::Model, - index_con::Integer, - index_vars::Vector{Cint}, - coefs::Vector{Cdouble}, -) - @assert length(index_vars) == length(coefs) - return KN_add_con_linear_struct_one(m, length(index_vars), index_con, index_vars, coefs) -end - -function KN_add_con_linear_struct( - m::Model, - index_con::Integer, - index_var::Integer, - coef::Cdouble, -) - return KN_add_con_linear_struct_one(m, 1, index_con, [index_var], [coef]) -end - -function KN_add_con_quadratic_struct( - m::Model, - index_cons::Vector{Cint}, - index_vars1::Vector{Cint}, - index_vars2::Vector{Cint}, - coefs::Vector{Cdouble}, -) - @assert length(index_cons) == - length(index_vars1) == - length(index_vars2) == - length(coefs) - return KN_add_con_quadratic_struct( - m, - length(index_cons), - index_cons, - index_vars1, - index_vars2, - coefs, - ) -end - -function KN_add_con_quadratic_struct( - m::Model, - index_con::Integer, - index_vars1::Vector{Cint}, - index_vars2::Vector{Cint}, - coefs::Vector{Cdouble}, -) - @assert length(index_vars1) == length(index_vars2) == length(coefs) - return KN_add_con_quadratic_struct_one( - m, - length(index_vars1), - index_con, - index_vars1, - index_vars2, - coefs, - ) -end - -function KN_add_con_quadratic_struct( - m::Model, - index_con::Integer, - index_var1::Integer, - index_var2::Integer, - coef::Cdouble, -) - return KN_add_con_quadratic_struct_one( - m, - 1, - index_con, - Cint[index_var1], - Cint[index_var2], - [coef], - ) -end - -function KN_set_compcons( - m::Model, - ccTypes::Vector{Cint}, - indexComps1::Vector{Cint}, - indexComps2::Vector{Cint}, -) - # get number of constraints - nnc = length(ccTypes) - @assert nnc == length(indexComps1) == length(indexComps2) - return KN_set_compcons(m, nnc, ccTypes, indexComps1, indexComps2) -end - -#= - RESIDUALS -=# - -function KN_add_rsds(m::Model, ncons::Integer) - indices = zeros(Cint, ncons) - KN_add_rsds(m, ncons, indices) - return indices -end - -function KN_add_rsd(m::Model) - index = Cint[0] - KN_add_rsd(m, index) - return index -end - -function KN_add_rsd_linear_struct( - m::Model, - indexRsds::Vector{Cint}, - indexVars::Vector{Cint}, - coefs::Vector{Cdouble}, -) - nnz = length(indexRsds) - @assert nnz == length(indexVars) == length(coefs) - return KN_add_rsd_linear_struct(m, nnz, indexRsds, indexVars, coefs) -end - -function KN_add_rsd_linear_struct( - m::Model, - indexRsd::Integer, - indexVar::Vector{Cint}, - coefs::Vector{Cdouble}, -) - nnz = length(indexVar) - @assert nnz == length(coefs) - return KN_add_rsd_linear_struct_one(m, nnz, indexRsd, indexVar, coefs) -end - -#= - SOLVE -=# - function KN_solve(m::Model) # Check sanity. If model has Julia callbacks, we need to ensure # that Knitro is not multithreaded. Otherwise, the code will segfault @@ -518,12 +268,12 @@ function KN_solve(m::Model) # code. See issue #93 on https://github.com/jump-dev/KNITRO.jl. if has_callbacks(m) if KNITRO_VERSION >= v"13.0" - KN_set_param(m, KN_PARAM_MS_NUMTHREADS, 1) - KN_set_param(m, KN_PARAM_NUMTHREADS, 1) - KN_set_param(m, KN_PARAM_MIP_NUMTHREADS, 1) + KN_set_int_param(m, KN_PARAM_MS_NUMTHREADS, 1) + KN_set_int_param(m, KN_PARAM_NUMTHREADS, 1) + KN_set_int_param(m, KN_PARAM_MIP_NUMTHREADS, 1) else - KN_set_param(m, "par_numthreads", 1) - KN_set_param(m, "par_msnumthreads", 1) + KN_set_int_param_by_name(m, "par_numthreads", 1) + KN_set_int_param_by_name(m, "par_msnumthreads", 1) end end # For KN_solve, we do not return an error if ret is different of 0. @@ -538,8 +288,11 @@ end function KN_get_solution(m::Model) # we first check that the model is well defined to avoid segfault @assert m.env != C_NULL - nx = KN_get_number_vars(m) - nc = KN_get_number_cons(m) + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] x = zeros(Cdouble, nx) lambda = zeros(Cdouble, nx + nc) @@ -584,7 +337,9 @@ function get_solution(m::Model) if !isempty(m.x) return m.x end - nx = KN_get_number_vars(m) + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] x = zeros(Cdouble, nx) status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) KN_get_solution(m, status, obj, x, C_NULL) @@ -600,8 +355,11 @@ function get_dual(m::Model) if !isempty(m.mult) return m.mult end - nx = KN_get_number_vars(m) - nc = KN_get_number_cons(m) + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] lambda = zeros(Cdouble, nx + nc) status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) KN_get_solution(m, status, obj, C_NULL, lambda) @@ -611,222 +369,3 @@ function get_dual(m::Model) end get_dual(m::Model, ix::Int) = isempty(m.mult) ? get_dual(m)[ix] : m.mult[ix] - -function KN_get_objgrad_values(m::Model) - nnz = KN_get_objgrad_nnz(m) - indexVars = zeros(Cint, nnz) - objGrad = zeros(Cdouble, nnz) - KN_get_objgrad_values(m, indexVars, objGrad) - return indexVars, objGrad -end - -function KN_get_jacobian_values(m::Model) - nnz = KN_get_jacobian_nnz(m) - jacvars = zeros(Cint, nnz) - jaccons = zeros(Cint, nnz) - jaccoef = zeros(Cdouble, nnz) - KN_get_jacobian_values(m, jacvars, jaccons, jaccoef) - return jacvars, jaccons, jaccoef -end - -function KN_get_rsd_jacobian_values(m::Model) - nnz = KN_get_rsd_jacobian_nnz(m) - jacvars = zeros(Cint, nnz) - jaccons = zeros(Cint, nnz) - jaccoef = zeros(Cdouble, nnz) - KN_get_rsd_jacobian_values(m, jacvars, jaccons, jaccoef) - return jacvars, jaccons, jaccoef -end - -function KN_get_hessian_values(m::Model) - nnz = KN_get_hessian_nnz(m) - indexVars1 = zeros(Cint, nnz) - indexVars2 = zeros(Cint, nnz) - hess = zeros(Cdouble, nnz) - KN_get_hessian_values(m, indexVars1, indexVars2, hess) - return indexVars1, indexVars2, hess -end - -function KN_get_var_viols(kc::Model, index::Vector{Cint}) - bndInfeas = zeros(Cint, length(index)) - intInfeas = zeros(Cint, length(index)) - viols = zeros(Cdouble, length(index)) - KN_get_var_viols(kc, length(index), index, bndInfeas, intInfeas, viols) - return bndInfeas, intInfeas, viols -end - -function KN_get_con_viols(kc::Model, index::Vector{Cint}) - infeas = zeros(Cint, length(index)) - viols = zeros(Cdouble, length(index)) - KN_get_con_viols(kc, length(index), index, infeas, viols) - return infeas, viols -end - -function KN_get_presolve_error(m::Model) - @assert m.env != C_NULL - component, index, error = Ref{Cint}(0), Ref{Cint}(0), Ref{Cint}(0) - viol = Ref{Cdouble}(0.0) - KN_get_presolve_error(m, component, index, error, viol) - return Bool(component[]), Int64(index[]), Int64(error[]), viol[] -end - -@kn_get_attribute get_number_vars Cint -@kn_get_attribute get_number_cons Cint -@kn_get_attribute get_obj_value Cdouble -@kn_get_attribute get_obj_type Cint - -@kn_get_attribute get_number_iters Cint -@kn_get_attribute get_number_cg_iters Cint -@kn_get_attribute get_abs_feas_error Cdouble -@kn_get_attribute get_rel_feas_error Cdouble -@kn_get_attribute get_abs_opt_error Cdouble -@kn_get_attribute get_rel_opt_error Cdouble -@kn_get_attribute get_objgrad_nnz Cint -@kn_get_attribute get_jacobian_nnz KNLONG -@kn_get_attribute get_rsd_jacobian_nnz KNLONG -@kn_get_attribute get_hessian_nnz KNLONG -@kn_get_attribute get_solve_time_cpu Cdouble -@kn_get_attribute get_solve_time_real Cdouble - -@kn_get_attribute get_mip_number_nodes Cint -@kn_get_attribute get_mip_number_solves Cint -@kn_get_attribute get_mip_abs_gap Cdouble -@kn_get_attribute get_mip_rel_gap Cdouble -@kn_get_attribute get_mip_incumbent_obj Cdouble -@kn_get_attribute get_mip_relaxation_bnd Cdouble -@kn_get_attribute get_mip_lastnode_obj Cdouble - -#= -PARAMS -=# - -function KN_set_param(m::Model, id::Integer, value::Integer) - return KN_set_int_param(m, id, value) -end - -function KN_set_param(m::Model, param::AbstractString, value::Integer) - return KN_set_int_param_by_name(m, param, value) -end - -function KN_set_param(m::Model, id::Integer, value::Cdouble) - return KN_set_double_param(m, id, value) -end - -function KN_set_param(m::Model, param::AbstractString, value::Cdouble) - return KN_set_double_param_by_name(m, param, value) -end - -function KN_set_param(m::Model, id::Integer, value::AbstractString) - return KN_set_char_param(m, id, value) -end - -function KN_set_param(m::Model, param::AbstractString, value::AbstractString) - return KN_set_char_param_by_name(m, param, value) -end - -function KN_get_int_param(m::Model, id::Integer) - res = Ref{Cint}(0) - KN_get_int_param(m, id, res) - return res[] -end -function KN_get_int_param(m::Model, param::AbstractString) - res = Ref{Cint}(0) - KN_get_int_param_by_name(m, param, res) - return res[] -end - -function KN_get_double_param(m::Model, id::Integer) - res = Ref{Cdouble}(0.0) - KN_get_double_param(m, id, res) - return res[] -end -function KN_get_double_param(m::Model, param::AbstractString) - res = Ref{Cdouble}(0.0) - KN_get_double_param_by_name(m, param, res) - return res[] -end - -function KN_get_param_name(m::Model, id::Integer) - output_size = 128 - res = Vector{Cchar}(undef, output_size) - KN_get_param_name(m, id, res, output_size) - GC.@preserve res begin - return unsafe_string(pointer(res)) - end -end - -function KN_get_param_doc(m::Model, id::Integer) - output_size = 128 - res = Vector{Cchar}(undef, output_size) - KN_get_param_doc(m, id, res, output_size) - GC.@preserve res begin - return unsafe_string(pointer(res)) - end -end - -function KN_get_param_type(m::Model, id::Integer) - res = Ref{Cint}(0) - KN_get_param_type(m, id, res) - return res[] -end - -function KN_get_num_param_values(m::Model, id::Integer) - res = Ref{Cint}(0) - KN_get_num_param_values(m, id, res) - return res[] -end - -function KN_get_param_value_doc(m::Model, id::Integer, value_id::Integer) - output_size = 128 - res = Vector{Cchar}(undef, output_size) - KN_get_param_value_doc(m, id, value_id, res, output_size) - GC.@preserve res begin - return unsafe_string(pointer(res)) - end -end - -function KN_get_param_id(m::Model, name::AbstractString) - res = Ref{Cint}(0) - KN_get_param_id(m, name, res) - return res[] -end - -#= - NAMES -=# - -function KN_get_var_names(m::Model, max_length=1024) - return String[ - KN_get_var_names(m, Cint(id - 1), max_length) for id in 1:KN_get_number_vars(m) - ] -end - -function KN_get_var_names(m::Model, index::Vector{Cint}, max_length=1024) - return String[KN_get_var_names(m, id, max_length) for id in index] -end - -function KN_get_var_names(m::Model, index::Cint, max_length=1024) - rawname = Vector{Cchar}(undef, max_length) - KN_get_var_name(m, index, max_length, rawname) - GC.@preserve rawname begin - return unsafe_string(pointer(rawname)) - end -end - -function KN_get_con_names(m::Model, max_length=1024) - return String[ - KN_get_con_names(m, Cint(id - 1), max_length) for id in 1:KN_get_number_cons(m) - ] -end - -function KN_get_con_names(m::Model, index::Vector{Cint}, max_length=1024) - return String[KN_get_con_names(m, id, max_length) for id in index] -end - -function KN_get_con_names(m::Model, index::Cint, max_length=1024) - rawname = Vector{Cchar}(undef, max_length) - KN_get_con_name(m, index, max_length, rawname) - GC.@preserve rawname begin - return unsafe_string(pointer(rawname)) - end -end diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 626beed..c71860c 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -31,13 +31,13 @@ function _canonical_quadratic_reduction(f::MOI.ScalarQuadraticFunction) I, J, V = SparseArrays.findnz(SparseArrays.sparse(I, J, V)) I .-= Cint(1) J .-= Cint(1) - return I, J, V + return return length(I), I, J, V end function _canonical_linear_reduction(terms::Vector{<:MOI.ScalarAffineTerm}) columns = Cint[_c_column(term.variable) for term in terms] coefficients = Cdouble[term.coefficient for term in terms] - return columns, coefficients + return length(terms), columns, coefficients end function _canonical_linear_reduction(f::MOI.ScalarQuadraticFunction) @@ -253,7 +253,11 @@ function _throw_if_solved(model::Optimizer, ::Type{MOI.VariableIndex}) return end -_number_constraints(model::Optimizer) = KN_get_number_cons(model.inner) +function _number_constraints(model::Optimizer) + p = Ref{Cint}(0) + KN_get_number_cons(model.inner, p) + return p[] +end # MOI.SolverName @@ -268,14 +272,16 @@ MOI.get(::Optimizer, ::MOI.SolverVersion) = string(KNITRO_VERSION) MOI.supports(model::Optimizer, ::MOI.Silent) = true function MOI.get(model::Optimizer, ::MOI.Silent) - return KN_get_int_param(model.inner, "outlev") == 0 + p = Ref{Cint}(-1) + KN_get_int_param_by_name(model.inner, "outlev", p) + return p[] == 0 end function MOI.set(model::Optimizer, ::MOI.Silent, value) # Default outlev is KN_OUTLEV_ITER_10. outlev = value ? KN_OUTLEV_NONE : KN_OUTLEV_ITER_10 model.options["outlev"] = outlev - KN_set_param(model.inner, KN_PARAM_OUTLEV, outlev) + KN_set_int_param(model.inner, KN_PARAM_OUTLEV, outlev) return end @@ -284,13 +290,14 @@ end MOI.supports(model::Optimizer, ::MOI.TimeLimitSec) = true function MOI.get(model::Optimizer, ::MOI.TimeLimitSec) - ret = KN_get_double_param(model.inner, KN_PARAM_MAXTIMECPU) - return ret == 1e8 ? nothing : ret + p = Ref{Cdouble}(0.0) + KN_get_double_param(model.inner, KN_PARAM_MAXTIMECPU, p) + return p[] == 1e8 ? nothing : p[] end function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, value) # By default, maxtime is set to 1e8 in Knitro. - KN_set_param(model.inner, KN_PARAM_MAXTIMECPU, something(value, 1e8)) + KN_set_double_param(model.inner, KN_PARAM_MAXTIMECPU, something(value, 1e8)) return end @@ -300,7 +307,9 @@ function MOI.supports(model::Optimizer, attr::MOI.RawOptimizerAttribute) if attr.name == "free" return true end - return KN_get_param_id(model.inner, attr.name) > 0 + p = Ref{Cint}(0) + KN_get_param_id(model.inner, attr.name, p) + return p[] > 0 end function MOI.get(model::Optimizer, attr::MOI.RawOptimizerAttribute) @@ -321,8 +330,12 @@ function MOI.set(model::Optimizer, attr::MOI.RawOptimizerAttribute, value) free(model) elseif !MOI.supports(model, attr) throw(MOI.UnsupportedAttribute(attr)) - else - KN_set_param(model.inner, attr.name, value) + elseif value isa Integer + KN_set_int_param_by_name(model.inner, attr.name, value) + elseif value isa Cdouble + KN_set_double_param_by_name(model.inner, attr.name, value) + elseif value isa AbstractString + KN_set_char_param_by_name(model.inner, attr.name, value) end model.options[attr.name] = value return @@ -337,7 +350,8 @@ end function MOI.add_variable(model::Optimizer) _throw_if_solved(model, MOI.VariableIndex) push!(model.variable_info, _VariableInfo()) - KN_add_var(model.inner) + pindex = Ref{Cint}(0) + KN_add_var(model.inner, pindex) return MOI.VariableIndex(length(model.variable_info)) end @@ -625,11 +639,14 @@ function MOI.add_constraint(model::Optimizer, x::MOI.VariableIndex, ::MOI.ZeroOn MOI.throw_if_not_valid(model, x) model.number_zeroone_constraints += 1 lb, ub = nothing, nothing + p = Ref{Cdouble}(NaN) if model.variable_info[x.value].has_lower_bound - lb = max(0.0, KN_get_var_lobnd(model.inner, _c_column(x))) + KN_get_var_lobnd(model.inner, _c_column(x), p) + lb = max(0.0, p[]) end if model.variable_info[x.value].has_upper_bound - ub = min(1.0, KN_get_var_upbnd(model.inner, _c_column(x))) + KN_get_var_upbnd(model.inner, _c_column(x), p) + ub = min(1.0, p[]) end KN_set_var_type(model.inner, _c_column(x), KN_VARTYPE_BINARY) # Calling `set_var_type` resets variable bounds in KNITRO. To fix, we need @@ -672,7 +689,9 @@ function MOI.add_constraint( _throw_if_solved(model, func, set) _throw_if_not_valid(model, func) # Add a single constraint in KNITRO. - num_cons = KN_add_con(model.inner) + p = Ref{Cint}(0) + KN_add_con(model.inner, p) + num_cons = p[] # Add bound to constraint. if isa(set, MOI.LessThan{Float64}) val = _check_value(set.upper) @@ -690,8 +709,8 @@ function MOI.add_constraint( KN_set_con_lobnd(model.inner, num_cons, lb - func.constant) KN_set_con_upbnd(model.inner, num_cons, ub - func.constant) end - columns, coefficients = _canonical_linear_reduction(func) - KN_add_con_linear_struct(model.inner, num_cons, columns, coefficients) + nnz, columns, coefficients = _canonical_linear_reduction(func) + KN_add_con_linear_struct_one(model.inner, nnz, num_cons, columns, coefficients) ci = MOI.ConstraintIndex{typeof(func),typeof(set)}(num_cons) model.constraint_mapping[ci] = num_cons return ci @@ -749,7 +768,9 @@ function MOI.add_constraint( _throw_if_solved(model, func, set) _throw_if_not_valid(model, func) # We add a constraint in KNITRO. - num_cons = KN_add_con(model.inner) + p = Ref{Cint}(0) + KN_add_con(model.inner, p) + num_cons = p[] # Add upper bound. if isa(set, MOI.LessThan{Float64}) val = _check_value(set.upper) @@ -766,10 +787,10 @@ function MOI.add_constraint( KN_set_con_lobnd(model.inner, num_cons, lb - func.constant) KN_set_con_upbnd(model.inner, num_cons, ub - func.constant) end - columns, coefficients = _canonical_linear_reduction(func) - KN_add_con_linear_struct(model.inner, num_cons, columns, coefficients) - I, J, V = _canonical_quadratic_reduction(func) - KN_add_con_quadratic_struct(model.inner, num_cons, I, J, V) + nnz, columns, coefficients = _canonical_linear_reduction(func) + KN_add_con_linear_struct_one(model.inner, nnz, num_cons, columns, coefficients) + nnz, I, J, V = _canonical_quadratic_reduction(func) + KN_add_con_quadratic_struct_one(model.inner, nnz, num_cons, I, J, V) ci = MOI.ConstraintIndex{typeof(func),typeof(set)}(num_cons) model.constraint_mapping[ci] = num_cons return ci @@ -828,7 +849,9 @@ function MOI.add_constraint( set::MOI.SecondOrderCone, ) _throw_if_solved(model, func, set) - index_con = KN_add_con(model.inner) + p = Ref{Cint}(0) + KN_add_con(model.inner, p) + index_con = p[] rows, columns, coefficients = _canonical_vector_affine_reduction(func) # Distinct two parts of secondordercone. # First row corresponds to linear part of SOC. @@ -836,8 +859,9 @@ function MOI.add_constraint( cone_indices = .!(linear_row_indices) ## i) linear part KN_set_con_upbnd(model.inner, index_con, func.constants[1]) - KN_add_con_linear_struct( + KN_add_con_linear_struct_one( model.inner, + sum(linear_row_indices), index_con, columns[linear_row_indices], -coefficients[linear_row_indices], @@ -881,10 +905,12 @@ function MOI.add_constraint( ) _throw_if_solved(model, func, set) # Add constraints inside KNITRO. - index_con = KN_add_con(model.inner) + p = Ref{Cint}(0) + KN_add_con(model.inner, p) + index_con = p[] indv = _c_column.(func.variables) KN_set_con_upbnd(model.inner, index_con, 0.0) - KN_add_con_linear_struct(model.inner, index_con, indv[1], -1.0) + KN_add_con_linear_struct_one(model.inner, 1, index_con, [indv[1]], [-1.0]) indexVars = indv[2:end] nnz = length(indexVars) indexCoords = Cint[i for i in 0:(nnz-1)] @@ -1023,10 +1049,9 @@ function MOI.supports( end function _add_objective(model::Optimizer, f::MOI.ScalarQuadraticFunction) - I, J, V = _canonical_quadratic_reduction(f) - KN_add_obj_quadratic_struct(model.inner, I, J, V) - columns, coefficients = _canonical_linear_reduction(f) - nnz = length(columns) + nnz, I, J, V = _canonical_quadratic_reduction(f) + KN_add_obj_quadratic_struct(model.inner, nnz, I, J, V) + nnz, columns, coefficients = _canonical_linear_reduction(f) KN_add_obj_linear_struct(model.inner, nnz, columns, coefficients) KN_add_obj_constant(model.inner, f.constant) model.objective = nothing @@ -1034,8 +1059,7 @@ function _add_objective(model::Optimizer, f::MOI.ScalarQuadraticFunction) end function _add_objective(model::Optimizer, f::MOI.ScalarAffineFunction) - columns, coefficients = _canonical_linear_reduction(f) - nnz = length(columns) + nnz, columns, coefficients = _canonical_linear_reduction(f) KN_add_obj_linear_struct(model.inner, nnz, columns, coefficients) KN_add_obj_constant(model.inner, f.constant) model.objective = nothing @@ -1093,6 +1117,7 @@ end function _load_complementarity_constraint(model::Optimizer, cache::_ComplementarityCache) return KN_set_compcons( model.inner, + length(cache.cc_types), cache.cc_types, cache.index_comps_1, cache.index_comps_2, @@ -1106,7 +1131,8 @@ function _load_nlp_constraints(model::Optimizer) if num_nlp_constraints == 0 return end - num_cons = KN_add_cons(model.inner, num_nlp_constraints) + num_cons = zeros(Cint, num_nlp_constraints) + KN_add_cons(model.inner, num_nlp_constraints, num_cons) for (ib, pair) in enumerate(model.nlp_data.constraint_bounds) if pair.upper == pair.lower KN_set_con_eqbnd(model.inner, num_cons[ib], pair.upper) @@ -1120,8 +1146,8 @@ function _load_nlp_constraints(model::Optimizer) end function MOI.optimize!(model::Optimizer) - KN_set_param(model.inner, "datacheck", 0) - KN_set_param(model.inner, "hessian_no_f", 1) + KN_set_int_param_by_name(model.inner, "datacheck", 0) + KN_set_int_param_by_name(model.inner, "hessian_no_f", 1) if _has_complementarity(model.complementarity_cache) _load_complementarity_constraint(model, model.complementarity_cache) end @@ -1153,7 +1179,7 @@ function MOI.optimize!(model::Optimizer) # Load NLP structure inside Knitro. offset = _number_constraints(model) _load_nlp_constraints(model) - num_cons = KN_get_number_cons(model.inner) + num_cons = _number_constraints(model) # 1/ Definition of the callbacks # Objective callback (used both for objective and constraint evaluation). function eval_f_cb(kc, cb, evalRequest, evalResult, userParams) @@ -1280,7 +1306,7 @@ function MOI.optimize!(model::Optimizer) # (no need to specify sparsity pattern for Hessian-vector product). KN_set_cb_hess(model.inner, cb, 0, eval_hv_cb) # Specify to Knitro that we are using Hessian-vector product. - KN_set_param(model.inner, KN_PARAM_HESSOPT, KN_HESSOPT_PRODUCT) + KN_set_int_param(model.inner, KN_PARAM_HESSOPT, KN_HESSOPT_PRODUCT) end model.nlp_loaded = true elseif !isa(model.objective, Nothing) && @@ -1466,8 +1492,10 @@ function MOI.get( }, } _check_cons(model, ci, cp) - g = KN_get_con_values(model.inner) - return g[model.constraint_mapping[ci].+1] + indexCon = model.constraint_mapping[ci] + p = Ref{Cdouble}(NaN) + KN_get_con_value(model.inner, indexCon, p) + return p[] end # function MOI.get( @@ -1606,16 +1634,33 @@ function MOI.get(model::Optimizer, ::MOI.NLPBlockDual) end function MOI.get(model::Optimizer, ::MOI.SolveTimeSec) + p = Ref{Cdouble}(NaN) if KNITRO_VERSION >= v"12.0" - return KN_get_solve_time_cpu(model.inner) + KN_get_solve_time_cpu(model.inner, p) end - return NaN + return p[] end -MOI.get(model::Optimizer, ::MOI.NodeCount) = KN_get_mip_number_nodes(model.inner) +function MOI.get(model::Optimizer, ::MOI.NodeCount) + p = Ref{Cint}(0) + KN_get_mip_number_nodes(model.inner, p) + return p[] +end -MOI.get(model::Optimizer, ::MOI.BarrierIterations) = KN_get_number_iters(model.inner) +function MOI.get(model::Optimizer, ::MOI.BarrierIterations) + p = Ref{Cint}(0) + KN_get_number_iters(model.inner, p) + return p[] +end -MOI.get(model::Optimizer, ::MOI.RelativeGap) = KN_get_mip_rel_gap(model.inner) +function MOI.get(model::Optimizer, ::MOI.RelativeGap) + p = Ref{Cdouble}(NaN) + KN_get_mip_rel_gap(model.inner, p) + return p[] +end -MOI.get(model::Optimizer, ::MOI.ObjectiveBound) = KN_get_mip_relaxation_bnd(model.inner) +function MOI.get(model::Optimizer, ::MOI.ObjectiveBound) + p = Ref{Cdouble}(NaN) + KN_get_mip_relaxation_bnd(model.inner, p) + return p[] +end diff --git a/src/callbacks.jl b/src/callbacks.jl index b15f6d2..a3cbe86 100644 --- a/src/callbacks.jl +++ b/src/callbacks.jl @@ -14,8 +14,12 @@ # the arrays of primal variable x and dual variable \lambda have fixed # sizes. function link!(cb::CallbackContext, model::Model) - cb.n = KN_get_number_vars(model) - return cb.m = KN_get_number_cons(model) + p = Ref{Cint}(0) + KN_get_number_vars(model, p) + cb.n = p[] + KN_get_number_cons(model, p) + cb.m = p[] + return end function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing) @@ -25,7 +29,7 @@ function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing # Link current callback context with Knitro model link!(cb, m) # Store callback context inside KNITRO user data. - @kn_ccall(set_cb_user_params, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Any), m.env, cb, cb) + KN_set_cb_user_params(m.env, cb, cb) return nothing end @@ -37,34 +41,33 @@ then a gradient evaluation callback must be set by `KN_set_cb_grad()` """ function KN_set_cb_gradopt(m::Model, cb::CallbackContext, gradopt::Integer) - @kn_ccall(set_cb_gradopt, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Cint), m.env, cb, gradopt) + KN_set_cb_gradopt(m.env, cb, gradopt) return nothing end -macro callback_getter(function_name, return_type) - fname = Symbol("KN_" * string(function_name)) - quote - function $(esc(fname))(kc::Ptr{Cvoid}, cb::Ptr{Cvoid}) - result = zeros($return_type, 1) - @kn_ccall( - $function_name, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cint}), - kc, - cb, - result - ) - return result[1] - end - end -end - -@callback_getter get_cb_number_cons Cint -@callback_getter get_cb_objgrad_nnz Cint -@callback_getter get_cb_jacobian_nnz KNLONG -@callback_getter get_cb_hessian_nnz KNLONG -@callback_getter get_cb_number_rsds Cint -@callback_getter get_cb_rsd_jacobian_nnz KNLONG +# macro callback_getter(function_name, return_type) +# fname = Symbol("KN_" * string(function_name)) +# quote +# function $(esc(fname))(kc::Ptr{Cvoid}, cb::Ptr{Cvoid}) +# result = zeros($return_type, 1) +# @kn_ccall( +# $function_name, +# Cint, +# (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cint}), +# kc, +# cb, +# result +# ) +# return result[1] +# end +# end +# end + +# @callback_getter get_cb_objgrad_nnz Cint +# @callback_getter get_cb_jacobian_nnz KNLONG +# @callback_getter get_cb_hessian_nnz KNLONG +# @callback_getter get_cb_number_rsds Cint +# @callback_getter get_cb_rsd_jacobian_nnz KNLONG # High level EvalRequest structure. mutable struct EvalRequest @@ -227,14 +230,7 @@ function KN_add_eval_callback_all(m::Model, funccallback::Function) rfptr = Ref{Ptr{Cvoid}}() # Add callback to context. - @kn_ccall( - add_eval_callback_all, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), - m.env, - c_f, - rfptr - ) + KN_add_eval_callback_all(m.env, c_f, rfptr) cb = CallbackContext(rfptr[]) register_callback(m, cb) @@ -260,15 +256,7 @@ function KN_add_objective_callback(m::Model, objcallback::Function) rfptr = Ref{Ptr{Cvoid}}() # add callback to context - @kn_ccall( - add_eval_callback_one, - Cint, - (Ptr{Cvoid}, Cint, Ptr{Cvoid}, Ptr{Cvoid}), - m.env, - Cint(-1), - c_f, - rfptr - ) + KN_add_eval_callback_one(m.env, Cint(-1), c_f, rfptr) cb = CallbackContext(rfptr[]) register_callback(m, cb) @@ -300,17 +288,7 @@ function KN_add_eval_callback( rfptr = Ref{Ptr{Cvoid}}() # Add callback to context. - @kn_ccall( - add_eval_callback, - Cint, - (Ptr{Cvoid}, KNBOOL, Cint, Ptr{Cint}, Ptr{Cvoid}, Ptr{Cvoid}), - m.env, - KNBOOL(evalObj), - nC, - indexCons, - c_f, - rfptr - ) + KN_add_eval_callback(m.env, KNBOOL(evalObj), nC, indexCons, c_f, rfptr) cb = CallbackContext(rfptr[]) register_callback(m, cb) @@ -337,13 +315,16 @@ function KN_set_cb_grad( cb::CallbackContext, gradcallback; nV::Integer=KN_DENSE, - nnzJ::Integer=( - iszero(KNITRO.KN_get_number_cons(m)) ? KNLONG(0) : KNITRO.KN_DENSE_COLMAJOR - ), + nnzJ::Union{Nothing,Integer}=nothing, objGradIndexVars=C_NULL, jacIndexCons=C_NULL, jacIndexVars=C_NULL, ) + if nnzJ === nothing + p = Ref{Cint}(0) + KN_get_number_cons(m, p) + nnzJ = iszero(p[]) ? KNLONG(0) : KNITRO.KN_DENSE_COLMAJOR + end # Check consistency of arguments. if (nV == 0 || nV == KN_DENSE) (objGradIndexVars != C_NULL) && @@ -371,10 +352,7 @@ function KN_set_cb_grad( c_grad_g = C_NULL end - @kn_ccall( - set_cb_grad, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Cint, Ptr{Cint}, KNLONG, Ptr{Cint}, Ptr{Cint}, Ptr{Cvoid}), + KN_set_cb_grad( m.env, cb, nV, @@ -426,10 +404,7 @@ function KN_set_cb_hess( (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) - @kn_ccall( - set_cb_hess, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, KNLONG, Ptr{Cint}, Ptr{Cint}, Ptr{Cvoid}), + KN_set_cb_hess( m.env, cb, nnzH, @@ -485,14 +460,7 @@ function KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) rfptr = Ref{Ptr{Cvoid}}() # Add callback to context. - @kn_ccall( - add_lsq_eval_callback_all, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), - m.env, - c_f, - rfptr - ) + KN_add_lsq_eval_callback_all(m.env, c_f, rfptr) cb = CallbackContext(rfptr[]) register_callback(m, cb) @@ -528,10 +496,7 @@ function KN_set_cb_rsd_jac( Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) - @kn_ccall( - set_cb_rsd_jac, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, KNLONG, Ptr{Cint}, Ptr{Cint}, Ptr{Cvoid}), + KN_set_cb_rsd_jac( m.env, cb, nnzJ, @@ -554,8 +519,11 @@ function newpt_wrapper( # Load KNITRO's Julia Model. try m = unsafe_pointer_to_objref(userdata_)::Model - nx = KN_get_number_vars(m) - nc = KN_get_number_cons(m) + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] x = unsafe_wrap(Array, ptr_x, nx) lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) ret = m.newpt_callback(m, x, lambda, m.newpoint_user) @@ -606,7 +574,7 @@ function KN_set_newpt_callback(m::Model, callback::Function, userparams=nothing) (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - @kn_ccall(set_newpt_callback, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Any), m.env, c_func, m) + KN_set_newpt_callback(m.env, c_func, m) return nothing end @@ -620,8 +588,11 @@ function ms_process_wrapper( # Load KNITRO's Julia Model. try m = unsafe_pointer_to_objref(userdata_)::Model - nx = KN_get_number_vars(m) - nc = KN_get_number_cons(m) + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] x = unsafe_wrap(Array, ptr_x, nx) lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) res = m.ms_process(m, x, lambda, m.multistart_user) @@ -668,14 +639,7 @@ function KN_set_ms_process_callback(m::Model, callback::Function, userparams=not (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - @kn_ccall( - set_ms_process_callback, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Any), - m.env, - c_func, - m - ) + KN_set_ms_process_callback(m.env, c_func, m) return nothing end @@ -688,8 +652,11 @@ function mip_node_callback_wrapper( # Load KNITRO's Julia Model. try m = unsafe_pointer_to_objref(userdata_)::Model - nx = KN_get_number_vars(m) - nc = KN_get_number_cons(m) + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] x = unsafe_wrap(Array, ptr_x, nx) lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) res = m.mip_callback(m, x, lambda, m.mip_user) @@ -736,7 +703,7 @@ function KN_set_mip_node_callback(m::Model, callback::Function, userparams=nothi (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - @kn_ccall(set_mip_node_callback, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Any), m.env, c_func, m) + KN_set_mip_node_callback(m.env, c_func, m) return nothing end @@ -750,8 +717,11 @@ function ms_initpt_wrapper( # Load KNITRO's Julia Model. m = unsafe_pointer_to_objref(userdata_)::Model - nx = KN_get_number_vars(m) - nc = KN_get_number_cons(m) + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] x = unsafe_wrap(Array, ptr_x, nx) lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) @@ -790,7 +760,7 @@ function KN_set_ms_initpt_callback(m::Model, callback::Function, userparams=noth (Ptr{Cvoid}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - @kn_ccall(set_ms_initpt_callback, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Any), m.env, c_func, m) + KN_set_ms_initpt_callback(m.env, c_func, m) return nothing end @@ -838,6 +808,6 @@ function KN_set_puts_callback(m::Model, callback::Function, userparams=nothing) # Wrap user callback wrapper as C function. c_func = @cfunction(puts_callback_wrapper, Cint, (Ptr{Cchar}, Ptr{Cvoid})) - @kn_ccall(set_puts_callback, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Any), m.env, c_func, m) + KN_set_puts_callback(m.env, c_func, m) return nothing end diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 25e4818..47f3e81 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -13,9 +13,9 @@ import MathOptInterface as MOI function runtests() for name in names(@__MODULE__; all=true) if startswith("$(name)", "test_") - @testset "$(name)" begin + # @testset "$(name)" begin getfield(@__MODULE__, name)() - end + # end end end return From 76f82d2d807d40134c63dadd1e7bcb20f543a9af Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 20 Nov 2023 09:57:24 +1300 Subject: [PATCH 02/15] Updates --- src/C_wrapper.jl | 64 ++++++++++---------------------- src/callbacks.jl | 97 ++++++++++++++++++++++++++++-------------------- test/runtests.jl | 48 ++++++++++++------------ 3 files changed, 100 insertions(+), 109 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index d928d6a..831d9e8 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -3,26 +3,6 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. -"A macro to make calling KNITRO's KN_* C API a little cleaner" -macro kn_ccall(func, args...) - f = Base.Meta.quot(Symbol("KN_$(func)")) - args = [esc(a) for a in args] - quote - ccall(($f, libknitro), $(args...)) - end -end - -macro kn_get_attribute(function_name, type) - fname = Symbol("KN_" * string(function_name)) - quote - function $(esc(fname))(m::Model) - val = zeros($type, 1) - ret = $fname(m, val) - return val[1] - end - end -end - "Return the current KNITRO version." function get_release() len = 15 @@ -168,33 +148,27 @@ has_callbacks(m::Model) = !isempty(m.callbacks) register_callback(model::Model, cb::CallbackContext) = push!(model.callbacks, cb) function Base.show(io::IO, m::Model) - if is_valid(m) - println(io, "$(get_release())") - println(io, "-----------------------") - println(io, "Problem Characteristics") - println(io, "-----------------------") - println(io, "Objective goal: Minimize") - println(io, "Objective type: $(KN_get_obj_type(m))") - println( - io, - "Number of variables: $(KN_get_number_vars(m))", - ) - println( - io, - "Number of constraints: $(KN_get_number_cons(m))", - ) - println( - io, - "Number of nonzeros in Jacobian: $(KN_get_jacobian_nnz(m))", - ) - println( - io, - "Number of nonzeros in Hessian: $(KN_get_hessian_nnz(m))", - ) - - else + if !is_valid(m) println(io, "KNITRO Problem: NULL") + return end + println(io, "$(get_release())") + println(io, "-----------------------") + println(io, "Problem Characteristics") + println(io, "-----------------------") + println(io, "Objective goal: Minimize") + p = Ref{Cint}() + KN_get_obj_type(m, p) + println(io, "Objective type: $(p[])") + KN_get_number_vars(m, p) + println(io, "Number of variables: $(p[])") + KN_get_number_cons(m, p) + println(io, "Number of constraints: $(p[])") + q = Ref{KNLONG}() + KN_get_jacobian_nnz(m, q) + println(io, "Number of nonzeros in Jacobian: $(q[])") + KN_get_hessian_nnz(m, q) + println(io, "Number of nonzeros in Hessian: $(q[])") return end diff --git a/src/callbacks.jl b/src/callbacks.jl index a3cbe86..31b93b5 100644 --- a/src/callbacks.jl +++ b/src/callbacks.jl @@ -29,7 +29,13 @@ function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing # Link current callback context with Knitro model link!(cb, m) # Store callback context inside KNITRO user data. - KN_set_cb_user_params(m.env, cb, cb) + # KN_set_cb_user_params(m.env, cb, cb) + ccall( + (:KN_set_cb_user_params, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), + m.env, cb, cb, + ) return nothing end @@ -41,34 +47,16 @@ then a gradient evaluation callback must be set by `KN_set_cb_grad()` """ function KN_set_cb_gradopt(m::Model, cb::CallbackContext, gradopt::Integer) - KN_set_cb_gradopt(m.env, cb, gradopt) + # KN_set_cb_gradopt(m.env, cb, gradopt) + ccall( + (:KN_set_cb_gradopt, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), + m.env, cb, gradopt, + ) return nothing end -# macro callback_getter(function_name, return_type) -# fname = Symbol("KN_" * string(function_name)) -# quote -# function $(esc(fname))(kc::Ptr{Cvoid}, cb::Ptr{Cvoid}) -# result = zeros($return_type, 1) -# @kn_ccall( -# $function_name, -# Cint, -# (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cint}), -# kc, -# cb, -# result -# ) -# return result[1] -# end -# end -# end - -# @callback_getter get_cb_objgrad_nnz Cint -# @callback_getter get_cb_jacobian_nnz KNLONG -# @callback_getter get_cb_hessian_nnz KNLONG -# @callback_getter get_cb_number_rsds Cint -# @callback_getter get_cb_rsd_jacobian_nnz KNLONG - # High level EvalRequest structure. mutable struct EvalRequest evalRequestCode::Cint @@ -114,15 +102,25 @@ function EvalResult( n::Int, m::Int, ) + objgrad_nnz = Ref{Cint}() + jacobian_nnz = Ref{KNLONG}() + hessian_nnz = Ref{KNLONG}() + num_rsds = Ref{Cint}() + rsd_jacobian_nnz = Ref{KNLONG}() + KN_get_cb_objgrad_nnz(kc, cb, objgrad_nnz) + KN_get_cb_jacobian_nnz(kc, cb, jacobian_nnz) + KN_get_cb_hessian_nnz(kc, cb, hessian_nnz) + KN_get_cb_number_rsds(kc, cb, num_rsds) + KN_get_cb_rsd_jacobian_nnz(kc, cb, rsd_jacobian_nnz) return EvalResult( unsafe_wrap(Array, evalResult_.obj, 1), unsafe_wrap(Array, evalResult_.c, m), - unsafe_wrap(Array, evalResult_.objGrad, KN_get_cb_objgrad_nnz(kc, cb)), - unsafe_wrap(Array, evalResult_.jac, KN_get_cb_jacobian_nnz(kc, cb)), - unsafe_wrap(Array, evalResult_.hess, KN_get_cb_hessian_nnz(kc, cb)), + unsafe_wrap(Array, evalResult_.objGrad, objgrad_nnz[]), + unsafe_wrap(Array, evalResult_.jac, jacobian_nnz[]), + unsafe_wrap(Array, evalResult_.hess, hessian_nnz[]), unsafe_wrap(Array, evalResult_.hessVec, n), - unsafe_wrap(Array, evalResult_.rsd, KN_get_cb_number_rsds(kc, cb)), - unsafe_wrap(Array, evalResult_.rsdJac, KN_get_cb_rsd_jacobian_nnz(kc, cb)), + unsafe_wrap(Array, evalResult_.rsd, num_rsds[]), + unsafe_wrap(Array, evalResult_.rsdJac, rsd_jacobian_nnz[]), ) end @@ -416,11 +414,6 @@ function KN_set_cb_hess( return nothing end -@kn_get_attribute get_number_FC_evals Cint -@kn_get_attribute get_number_GA_evals Cint -@kn_get_attribute get_number_H_evals Cint -@kn_get_attribute get_number_HV_evals Cint - #= RESIDUALS =# @@ -639,7 +632,13 @@ function KN_set_ms_process_callback(m::Model, callback::Function, userparams=not (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - KN_set_ms_process_callback(m.env, c_func, m) + # KN_set_ms_process_callback(m.env, c_func, m) + ccall( + (:KN_set_ms_process_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), + m.env, c_func, m, + ) return nothing end @@ -703,7 +702,13 @@ function KN_set_mip_node_callback(m::Model, callback::Function, userparams=nothi (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - KN_set_mip_node_callback(m.env, c_func, m) + # KN_set_mip_node_callback(m.env, c_func, m) + ccall( + (:KN_set_mip_node_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), + m.env, c_func, m, + ) return nothing end @@ -760,7 +765,13 @@ function KN_set_ms_initpt_callback(m::Model, callback::Function, userparams=noth (Ptr{Cvoid}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - KN_set_ms_initpt_callback(m.env, c_func, m) + # KN_set_ms_initpt_callback(m.env, c_func, m) + ccall( + (:KN_set_ms_initpt_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), + m.env, c_func, m, + ) return nothing end @@ -808,6 +819,12 @@ function KN_set_puts_callback(m::Model, callback::Function, userparams=nothing) # Wrap user callback wrapper as C function. c_func = @cfunction(puts_callback_wrapper, Cint, (Ptr{Cchar}, Ptr{Cvoid})) - KN_set_puts_callback(m.env, c_func, m) + # KN_set_puts_callback(m.env, c_func, m) + ccall( + (:KN_set_puts_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), + m.env, c_func, m, + ) return nothing end diff --git a/test/runtests.jl b/test/runtests.jl index 5fa3036..7fe58e3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,33 +3,33 @@ using Test const KN_VERBOSE = false -@testset "Test C API" begin - include("C_wrapper.jl") -end +# @testset "Test C API" begin +# include("C_wrapper.jl") +# end -@testset "Test examples" begin - examples_dir = joinpath(dirname(@__FILE__), "..", "examples") - for file in filter(f -> endswith(f, ".jl"), readdir(examples_dir)) - if !occursin("mps_reader", file) - include(joinpath(examples_dir, file)) - end - end -end +# @testset "Test examples" begin +# examples_dir = joinpath(dirname(@__FILE__), "..", "examples") +# for file in filter(f -> endswith(f, ".jl"), readdir(examples_dir)) +# if !occursin("mps_reader", file) +# include(joinpath(examples_dir, file)) +# end +# end +# end @testset "Test MathOptInterface" begin include("MOI_wrapper.jl") end -try - @testset "Test C API License" begin - include("knitroapi_licman.jl") - end -catch e - @warn( - "License tests failed, but this might be due to License Manager" * - " not being supported by your license." - ) - println("The error catched was:\n") - println("$e\n") - println("See table above for more details.") -end +# try +# @testset "Test C API License" begin +# include("knitroapi_licman.jl") +# end +# catch e +# @warn( +# "License tests failed, but this might be due to License Manager" * +# " not being supported by your license." +# ) +# println("The error catched was:\n") +# println("$e\n") +# println("See table above for more details.") +# end From 9539a34fb0bbdeb5c9a9985d3af14dd396d14279 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 20 Nov 2023 10:35:36 +1300 Subject: [PATCH 03/15] Update --- src/C_wrapper.jl | 782 ++++++++++++++++++++++++++++++++++++++++- src/KNITRO.jl | 1 - src/callbacks.jl | 830 -------------------------------------------- test/MOI_wrapper.jl | 4 +- 4 files changed, 778 insertions(+), 839 deletions(-) delete mode 100644 src/callbacks.jl diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index 831d9e8..f9e6585 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -55,7 +55,6 @@ mutable struct CallbackContext m::Int # Add a dictionnary to store user params. userparams::Any - # Oracle's callbacks are context dependent, so store # them inside dedicated CallbackContext. eval_f::Function @@ -267,7 +266,6 @@ function KN_get_solution(m::Model) nx = p[] KN_get_number_cons(m, p) nc = p[] - x = zeros(Cdouble, nx) lambda = zeros(Cdouble, nx + nc) status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) @@ -280,7 +278,6 @@ function KN_get_solution(m::Model) return status[], obj[], x, lambda end -# some wrapper functions for MOI function get_status(m::Model) @assert m.env != C_NULL if m.status != 1 @@ -288,7 +285,6 @@ function get_status(m::Model) end status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) KN_get_solution(m, status, obj, C_NULL, C_NULL) - # Keep status in cache. m.status = status[] return status[] end @@ -300,7 +296,6 @@ function get_objective(m::Model) end status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) KN_get_solution(m, status, obj, C_NULL, C_NULL) - # Keep objective value in cache. m.obj_val = obj[] return obj[] end @@ -317,10 +312,10 @@ function get_solution(m::Model) x = zeros(Cdouble, nx) status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) KN_get_solution(m, status, obj, x, C_NULL) - # Keep solution in cache. m.x = x return x end + get_solution(m::Model, ix::Int) = isempty(m.x) ? get_solution(m)[ix] : m.x[ix] function get_dual(m::Model) @@ -343,3 +338,778 @@ function get_dual(m::Model) end get_dual(m::Model, ix::Int) = isempty(m.mult) ? get_dual(m)[ix] : m.mult[ix] + +# Callbacks utilities. + +# Note: we store here the number of constraints and variables defined +# in the original Knitro model. We cannot retrieve these numbers during +# callbacks invokation, as sometimes the number of variables and constraints +# change internally in Knitro (e.g. when cuts are added when resolving +# Branch&Bound). We prefer to use the number of variables and constraints +# of the original model so that user's callbacks could consider that +# the arrays of primal variable x and dual variable \lambda have fixed +# sizes. +function link!(cb::CallbackContext, model::Model) + p = Ref{Cint}(0) + KN_get_number_vars(model, p) + cb.n = p[] + KN_get_number_cons(model, p) + cb.m = p[] + return +end + +function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing) + if userParams != nothing + cb.userparams = userParams + end + link!(cb, m) + # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} + ccall( + (:KN_set_cb_user_params, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, Any), + m.env, + cb, + cb, + ) + return +end + +""" +Specify which gradient option `gradopt` will be used to evaluate +the first derivatives of the callback functions. If `gradopt=KN_GRADOPT_EXACT` +then a gradient evaluation callback must be set by `KN_set_cb_grad()` +(or `KN_set_cb_rsd_jac()` for least squares). + +""" +function KN_set_cb_gradopt(m::Model, cb::CallbackContext, gradopt::Integer) + KN_set_cb_gradopt(m.env, cb, gradopt) + return +end + +# High level EvalRequest structure. +mutable struct EvalRequest + evalRequestCode::Cint + threadID::Cint + x::Array{Float64} + lambda::Array{Float64} + sigma::Float64 + vec::Array{Float64} +end + +# Import low level request to Julia object. +function EvalRequest(ptr_model::Ptr{Cvoid}, evalRequest_::KN_eval_request, n::Int, m::Int) + # Import objective's scaling. + sigma = + (evalRequest_.sigma != C_NULL) ? unsafe_wrap(Array, evalRequest_.sigma, 1)[1] : 1.0 + # Wrap directly C arrays to avoid unnecessary copy. + return EvalRequest( + evalRequest_.type, + evalRequest_.threadID, + unsafe_wrap(Array, evalRequest_.x, n), + unsafe_wrap(Array, evalRequest_.lambda, n + m), + sigma, + unsafe_wrap(Array, evalRequest_.vec, n), + ) +end + +# High level EvalResult structure. +mutable struct EvalResult + obj::Array{Float64} + c::Array{Float64} + objGrad::Array{Float64} + jac::Array{Float64} + hess::Array{Float64} + hessVec::Array{Float64} + rsd::Array{Float64} + rsdJac::Array{Float64} +end + +function EvalResult( + kc::Ptr{Cvoid}, + cb::Ptr{Cvoid}, + evalResult_::KN_eval_result, + n::Int, + m::Int, +) + objgrad_nnz = Ref{Cint}() + jacobian_nnz = Ref{KNLONG}() + hessian_nnz = Ref{KNLONG}() + num_rsds = Ref{Cint}() + rsd_jacobian_nnz = Ref{KNLONG}() + KN_get_cb_objgrad_nnz(kc, cb, objgrad_nnz) + KN_get_cb_jacobian_nnz(kc, cb, jacobian_nnz) + KN_get_cb_hessian_nnz(kc, cb, hessian_nnz) + KN_get_cb_number_rsds(kc, cb, num_rsds) + KN_get_cb_rsd_jacobian_nnz(kc, cb, rsd_jacobian_nnz) + return EvalResult( + unsafe_wrap(Array, evalResult_.obj, 1), + unsafe_wrap(Array, evalResult_.c, m), + unsafe_wrap(Array, evalResult_.objGrad, objgrad_nnz[]), + unsafe_wrap(Array, evalResult_.jac, jacobian_nnz[]), + unsafe_wrap(Array, evalResult_.hess, hessian_nnz[]), + unsafe_wrap(Array, evalResult_.hessVec, n), + unsafe_wrap(Array, evalResult_.rsd, num_rsds[]), + unsafe_wrap(Array, evalResult_.rsdJac, rsd_jacobian_nnz[]), + ) +end + +macro wrap_function(wrap_name, name) + quote + function $(esc(wrap_name))( + ptr_model::Ptr{Cvoid}, + ptr_cb::Ptr{Cvoid}, + evalRequest_::Ptr{Cvoid}, + evalResults_::Ptr{Cvoid}, + userdata_::Ptr{Cvoid}, + ) + try + # Load evalRequest object. + ptr_request = Ptr{KN_eval_request}(evalRequest_) + evalRequest = unsafe_load(ptr_request)::KN_eval_request + # Load evalResult object. + ptr_result = Ptr{KN_eval_result}(evalResults_) + evalResult = unsafe_load(ptr_result)::KN_eval_result + # Eventually, load callback context. + cb = unsafe_pointer_to_objref(userdata_) + # Ensure that cb is a CallbackContext. + # Otherwise, we tell KNITRO that a problem occurs by returning a + # non-zero status. + if !isa(cb, CallbackContext) + return Cint(KN_RC_CALLBACK_ERR) + end + request = EvalRequest(ptr_model, evalRequest, cb.n, cb.m) + result = EvalResult(ptr_model, ptr_cb, evalResult, cb.n, cb.m) + res = cb.$name(ptr_model, ptr_cb, request, result, cb.userparams) + return Cint(res) + catch ex + if isa(ex, InterruptException) + return Cint(KN_RC_USER_TERMINATION) + end + if isa(ex, DomainError) + return Cint(KN_RC_EVAL_ERR) + else + @warn("Knitro encounters an exception in evaluation callback: $ex") + return Cint(KN_RC_CALLBACK_ERR) + end + end + end + end +end + +@wrap_function eval_fc_wrapper eval_f +@wrap_function eval_ga_wrapper eval_g +@wrap_function eval_hess_wrapper eval_h + +""" + KN_add_eval_callback(m::Model, funccallback::Function) + KN_add_eval_callback(m::Model, evalObj::Bool, indexCons::Vector{Cint}, + funccallback::Function) + +This is the routine for adding a callback for (nonlinear) evaluations +of objective and constraint functions. This routine can be called +multiple times to add more than one callback structure (e.g. to create +different callback structures to handle different blocks of constraints). +This routine specifies the minimal information needed for a callback, and +creates the callback structure `cb`, which can then be passed to other +callback functions to set additional information for that callback. + +# Parameters +* `evalObj`: boolean indicating whether or not any part of the objective + function is evaluated in the callback +* `indexCons`: (length nC) index of constraints evaluated in the callback + (set to NULL if nC=0) +* `funcCallback`: a function that evaluates the objective parts + (if evalObj=KNTRUE) and any constraint parts (specified by + nC and indexCons) involved in this callback; when + eval_fcga=KN_EVAL_FCGA_YES, this callback should also evaluate + the relevant first derivatives/gradients + +# Returns +* `cb`: the callback structure that gets created by + calling this function; all the memory for this structure is + handled by Knitro + +After a callback is created by `KN_add_eval_callback()`, the user can then specify +gradient information and structure through `KN_set_cb_grad()` and Hessian +information and structure through `KN_set_cb_hess()`. If not set, Knitro will +approximate these. However, it is highly recommended to provide a callback routine +to specify the gradients if at all possible as this will greatly improve the +performance of Knitro. Even if a gradient callback is not provided, it is still +helpful to provide the sparse Jacobian structure through `KN_set_cb_grad()` to +improve the efficiency of the finite-difference gradient approximations. +Other optional information can also be set via `KN_set_cb_*()` functions as +detailed below. + +""" +function KN_add_eval_callback_all(m::Model, funccallback::Function) + # wrap eval_callback_wrapper as C function + # Wrap eval_callback_wrapper as C function. + c_f = @cfunction( + eval_fc_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) + ) + + # Define callback context. + rfptr = Ref{Ptr{Cvoid}}() + + # Add callback to context. + KN_add_eval_callback_all(m.env, c_f, rfptr) + cb = CallbackContext(rfptr[]) + register_callback(m, cb) + + # Store function in callback environment: + cb.eval_f = funccallback + + # Store model in user params to access callback in C + KN_set_cb_user_params(m, cb) + + return cb +end + +# Evaluate only the objective +function KN_add_objective_callback(m::Model, objcallback::Function) + # wrap eval_callback_wrapper as C function + c_f = @cfunction( + eval_fc_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) + ) + + # define callback context + rfptr = Ref{Ptr{Cvoid}}() + + # add callback to context + KN_add_eval_callback_one(m.env, Cint(-1), c_f, rfptr) + cb = CallbackContext(rfptr[]) + register_callback(m, cb) + + # store function in callback environment: + cb.eval_f = objcallback + + # store model in user params to access callback in C + KN_set_cb_user_params(m, cb) + + return cb +end + +function KN_add_eval_callback( + m::Model, + evalObj::Bool, # switch on obj eval + indexCons::Vector{Cint}, # index of constaints + funccallback::Function, +) # callback + nC = length(indexCons) + + # Wrap eval_callback_wrapper as C function. + c_f = @cfunction( + eval_fc_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) + ) + + # Define callback context. + rfptr = Ref{Ptr{Cvoid}}() + + # Add callback to context. + KN_add_eval_callback(m.env, KNBOOL(evalObj), nC, indexCons, c_f, rfptr) + cb = CallbackContext(rfptr[]) + register_callback(m, cb) + + # Store function in callback environment. + cb.eval_f = funccallback + + KN_set_cb_user_params(m, cb) + + return cb +end + +""" + KN_set_cb_grad(m::Model, cb::CallbackContext, gradcallback; + nV::Integer=KN_DENSE, objGradIndexVars=C_NULL, + jacIndexCons=C_NULL, jacIndexVars=C_NULL) + +This API function is used to set the objective gradient and constraint Jacobian +structure and also (optionally) a callback function to evaluate the objective +gradient and constraint Jacobian provided through this callback. + +""" +function KN_set_cb_grad( + m::Model, + cb::CallbackContext, + gradcallback; + nV::Integer=KN_DENSE, + nnzJ::Union{Nothing,Integer}=nothing, + objGradIndexVars=C_NULL, + jacIndexCons=C_NULL, + jacIndexVars=C_NULL, +) + if nnzJ === nothing + p = Ref{Cint}(0) + KN_get_number_cons(m, p) + nnzJ = iszero(p[]) ? KNLONG(0) : KNITRO.KN_DENSE_COLMAJOR + end + # Check consistency of arguments. + if (nV == 0 || nV == KN_DENSE) + (objGradIndexVars != C_NULL) && + error("objGradIndexVars must be set to C_NULL when nV = $nV") + else + @assert (objGradIndexVars != C_NULL) && (length(objGradIndexVars) == nV) + end + + if jacIndexCons != C_NULL && jacIndexVars != C_NULL + @assert length(jacIndexCons) == length(jacIndexVars) + nnzJ = KNLONG(length(jacIndexCons)) + end + + if gradcallback != nothing + # Store grad function inside model. + cb.eval_g = gradcallback + + # Wrap gradient wrapper as C function. + c_grad_g = @cfunction( + eval_ga_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) + ) + else + c_grad_g = C_NULL + end + + KN_set_cb_grad( + m.env, + cb, + nV, + objGradIndexVars, + KNLONG(nnzJ), + jacIndexCons, + jacIndexVars, + c_grad_g, + ) + return +end + +""" + KN_set_cb_hess(m::Model, cb::CallbackContext, nnzH::Integer, hesscallback::Function; + hessIndexVars1=C_NULL, hessIndexVars2=C_NULL) + +This API function is used to set the structure and a callback function to +evaluate the components of the Hessian of the Lagrangian provided through this +callback. KN_set_cb_hess() should only be used when defining a user-supplied +Hessian callback function (via the `hessopt=KN_HESSOPT_EXACT` user option). +When Knitro is approximating the Hessian, it cannot make use of the Hessian +sparsity structure. + +""" +function KN_set_cb_hess( + m::Model, + cb::CallbackContext, + nnzH::Integer, + hesscallback::Function; + hessIndexVars1=C_NULL, + hessIndexVars2=C_NULL, +) + + # If Hessian is dense, ensure that sparsity pattern is empty + if nnzH == KN_DENSE_ROWMAJOR || nnzH == KN_DENSE_COLMAJOR + @assert hessIndexVars1 == hessIndexVars2 == C_NULL + # Otherwise, check validity of sparsity pattern + elseif nnzH > 0 + @assert hessIndexVars1 != C_NULL && hessIndexVars2 != C_NULL + @assert length(hessIndexVars1) == length(hessIndexVars2) == nnzH + end + # Store hessian function inside model. + cb.eval_h = hesscallback + + # Wrap gradient wrapper as C function. + c_hess = @cfunction( + eval_hess_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) + ) + KN_set_cb_hess(m.env, cb, nnzH, hessIndexVars1, hessIndexVars2, c_hess) + return +end + +#= + RESIDUALS +=# + +@wrap_function eval_rj_wrapper eval_jac_rsd +@wrap_function eval_rsd_wrapper eval_rsd + +# TODO: add _one and _* support +""" + KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) + +Add an evaluation callback for a least-squares models. Similar to KN_add_eval_callback() +above, but for least-squares models. + +* `m`: current KNITRO model +* `rsdCallback`: a function that evaluates any residual parts + +After a callback is created by `KN_add_lsq_eval_callback()`, the user can then +specify residual Jacobian information and structure through `KN_set_cb_rsd_jac()`. +If not set, Knitro will approximate the residual Jacobian. However, it is highly +recommended to provide a callback routine to specify the residual Jacobian if at all +possible as this will greatly improve the performance of Knitro. Even if a callback +for the residual Jacobian is not provided, it is still helpful to provide the sparse +Jacobian structure for the residuals through `KN_set_cb_rsd_jac()` to improve the +efficiency of the finite-difference Jacobian approximation. + +""" +function KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) + c_f = @cfunction( + eval_rsd_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) + ) + rfptr = Ref{Ptr{Cvoid}}() + KN_add_lsq_eval_callback_all(m.env, c_f, rfptr) + cb = CallbackContext(rfptr[]) + register_callback(m, cb) + cb.eval_rsd = rsdCallBack + KN_set_cb_user_params(m, cb) + return cb +end + +function KN_set_cb_rsd_jac( + m::Model, + cb::CallbackContext, + nnzJ::Integer, + evalRJ::Function; + jacIndexRsds=C_NULL, + jacIndexVars=C_NULL, +) + if nnzJ == KN_DENSE_ROWMAJOR || nnzJ == KN_DENSE_COLMAJOR || nnzJ == 0 + @assert jacIndexRsds == jacIndexVars == C_NULL + else + @assert jacIndexRsds != C_NULL && jacIndexVars != C_NULL + @assert length(jacIndexRsds) == length(jacIndexVars) == nnzJ + end + cb.eval_jac_rsd = evalRJ + c_eval_rj = @cfunction( + eval_rj_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) + ) + KN_set_cb_rsd_jac(m.env, cb, nnzJ, jacIndexRsds, jacIndexVars, c_eval_rj) + return cb +end + +#= + USER CALLBACKS +=# + +function newpt_wrapper( + ptr_model::Ptr{Cvoid}, + ptr_x::Ptr{Cdouble}, + ptr_lambda::Ptr{Cdouble}, + userdata_::Ptr{Cvoid}, +) + try + m = unsafe_pointer_to_objref(userdata_)::Model + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] + x = unsafe_wrap(Array, ptr_x, nx) + lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) + ret = m.newpt_callback(m, x, lambda, m.newpoint_user) + return Cint(ret) + catch ex + if isa(ex, InterruptException) + return Cint(KN_RC_USER_TERMINATION) + else + @warn("Knitro encounters an exception in newpoint callback: $ex") + return Cint(KN_RC_CALLBACK_ERR) + end + end +end + +""" + KN_set_newpt_callback(m::Model, callback::Function) + +Set the callback function that is invoked after Knitro computes a +new estimate of the solution point (i.e., after every iteration). +The function should not modify any Knitro arguments. + +Callback is a function with signature: + + callback(kc, x, lambda, userdata) + +Argument `kc` passed to the callback from inside Knitro is the +context pointer for the current problem being solved inside Knitro +(either the main single-solve problem, or a subproblem when using +multi-start, Tuner, etc.). +Arguments `x` and `lambda` contain the latest solution estimates. +Other values (such as objective, constraint, jacobian, etc.) can be +queried using the corresonding KN_get_XXX_values methods. + +Note: Currently only active for continuous models. + +""" +function KN_set_newpt_callback(m::Model, callback::Function, userparams=nothing) + m.newpt_callback = callback + if userparams != nothing + m.newpoint_user = userparams + end + c_func = @cfunction( + newpt_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) + ) + # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} + ccall( + (:KN_set_newpt_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, Any), + m.env, + c_func, + m, + ) + return +end + +function ms_process_wrapper( + ptr_model::Ptr{Cvoid}, + ptr_x::Ptr{Cdouble}, + ptr_lambda::Ptr{Cdouble}, + userdata_::Ptr{Cvoid}, +) + try + m = unsafe_pointer_to_objref(userdata_)::Model + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] + x = unsafe_wrap(Array, ptr_x, nx) + lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) + res = m.ms_process(m, x, lambda, m.multistart_user) + return Cint(res) + catch ex + if isa(ex, InterruptException) + return Cint(KN_RC_USER_TERMINATION) + else + @warn("Knitro encounters an exception in multistart callback: $ex") + return Cint(KN_RC_CALLBACK_ERR) + end + end +end + +""" + KN_set_ms_process_callback(m::Model, callback::Function) + +This callback function is for multistart (MS) problems only. +Set the callback function that is invoked after Knitro finishes +processing a multistart solve. + +Callback is a function with signature: + + callback(kc, x, lambda, userdata) + +Argument `kc` passed to the callback +from inside Knitro is the context pointer for the last multistart +subproblem solved inside Knitro. The function should not modify any +Knitro arguments. Arguments `x` and `lambda` contain the solution from +the last solve. + +""" +function KN_set_ms_process_callback(m::Model, callback::Function, userparams=nothing) + m.ms_process = callback + if userparams != nothing + m.multistart_user = userparams + end + c_func = @cfunction( + ms_process_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) + ) + # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} + ccall( + (:KN_set_ms_process_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, Any), + m.env, + c_func, + m, + ) + return +end + +function mip_node_callback_wrapper( + ptr_model::Ptr{Cvoid}, + ptr_x::Ptr{Cdouble}, + ptr_lambda::Ptr{Cdouble}, + userdata_::Ptr{Cvoid}, +) + try + m = unsafe_pointer_to_objref(userdata_)::Model + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] + x = unsafe_wrap(Array, ptr_x, nx) + lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) + res = m.mip_callback(m, x, lambda, m.mip_user) + return Cint(res) + catch ex + if isa(ex, InterruptException) + return Cint(KN_RC_USER_TERMINATION) + else + @warn("Knitro encounters an exception in MIP callback: $ex") + return Cint(KN_RC_CALLBACK_ERR) + end + end +end + +""" + KN_set_mip_node_callback(m::Model, callback::Function) + +This callback function is for mixed integer (MIP) problems only. +Set the callback function that is invoked after Knitro finishes +processing a node on the branch-and-bound tree (i.e., after a relaxed +subproblem solve in the branch-and-bound procedure). + +Callback is a function with signature: + + callback(kc, x, lambda, userdata) + +Argument `kc` passed to the callback from inside Knitro is the +context pointer for the last node subproblem solved inside Knitro. +The function should not modify any Knitro arguments. +Arguments `x` and `lambda` contain the solution from the node solve. + +""" +function KN_set_mip_node_callback(m::Model, callback::Function, userparams=nothing) + m.mip_callback = callback + if userparams != nothing + m.mip_user = userparams + end + c_func = @cfunction( + mip_node_callback_wrapper, + Cint, + (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) + ) + # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} + ccall( + (:KN_set_mip_node_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, Any), + m.env, + c_func, + m, + ) + return +end + +function ms_initpt_wrapper( + ptr_model::Ptr{Cvoid}, + nSolveNumber::Cint, + ptr_x::Ptr{Cdouble}, + ptr_lambda::Ptr{Cdouble}, + userdata_::Ptr{Cvoid}, +) + m = unsafe_pointer_to_objref(userdata_)::Model + p = Ref{Cint}(0) + KN_get_number_vars(m, p) + nx = p[] + KN_get_number_cons(m, p) + nc = p[] + x = unsafe_wrap(Array, ptr_x, nx) + lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) + res = m.ms_initpt_callback(m, nSolveNumber, x, lambda, m.multistart_user) + return Cint(res) +end + +""" + KN_set_ms_initpt_callback(m::Model, callback::Function) +Type declaration for the callback that allows applications to +specify an initial point before each local solve in the multistart +procedure. + +Callback is a function with signature: + + callback(kc, x, lambda, userdata) + +On input, arguments `x` and `lambda` are the randomly +generated initial points determined by Knitro, which can be overwritten +by the user. The argument `nSolveNumber` is the number of the +multistart solve. + +""" +function KN_set_ms_initpt_callback(m::Model, callback::Function, userparams=nothing) + m.ms_initpt_callback = callback + if userparams != nothing + m.multistart_user = userparams + end + c_func = @cfunction( + ms_initpt_wrapper, + Cint, + (Ptr{Cvoid}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) + ) + # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} + ccall( + (:KN_set_ms_initpt_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, Any), + m.env, + c_func, + m, + ) + return +end + +function puts_callback_wrapper(str::Ptr{Cchar}, userdata_::Ptr{Cvoid}) + try + m = unsafe_pointer_to_objref(userdata_)::Model + res = m.puts_callback(unsafe_string(str), m.puts_user) + return Cint(res) + catch ex + if isa(ex, InterruptException) + return Cint(KN_RC_USER_TERMINATION) + else + @warn("Knitro encounters an exception in puts callback: $ex") + return Cint(KN_RC_CALLBACK_ERR) + end + end +end + +""" + KN_set_puts_callback(m::Model, callback::Function) + +Set the callback that allows applications to handle +output. Applications can set a `put string` callback function to handle +output generated by the Knitro solver. By default Knitro prints to +stdout or a file named `knitro.log`, as determined by KN_PARAM_OUTMODE. + +Callback is a function with signature: + + callback(str::String, userdata) + +The KN_puts callback function takes a `userParams` argument which is a pointer +passed directly from KN_solve. The function should return the number of +characters that were printed. + +""" +function KN_set_puts_callback(m::Model, callback::Function, userparams=nothing) + m.puts_callback = callback + if userparams != nothing + m.puts_user = userparams + end + c_func = @cfunction(puts_callback_wrapper, Cint, (Ptr{Cchar}, Ptr{Cvoid})) + # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} + ccall( + (:KN_set_puts_callback, libknitro), + Cint, + (KN_context_ptr, Ptr{Cvoid}, Any), + m.env, + c_func, + m, + ) + return +end diff --git a/src/KNITRO.jl b/src/KNITRO.jl index 61c08fe..d7d19bd 100644 --- a/src/KNITRO.jl +++ b/src/KNITRO.jl @@ -46,7 +46,6 @@ knitro_version() = KNITRO_VERSION include("libknitro.jl") include("C_wrapper.jl") -include("callbacks.jl") include("MOI_wrapper.jl") end diff --git a/src/callbacks.jl b/src/callbacks.jl deleted file mode 100644 index 31b93b5..0000000 --- a/src/callbacks.jl +++ /dev/null @@ -1,830 +0,0 @@ -# Copyright (c) 2016: Ng Yee Sian, Miles Lubin, other contributors -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -# Callbacks utilities. - -# Note: we store here the number of constraints and variables defined -# in the original Knitro model. We cannot retrieve these numbers during -# callbacks invokation, as sometimes the number of variables and constraints -# change internally in Knitro (e.g. when cuts are added when resolving -# Branch&Bound). We prefer to use the number of variables and constraints -# of the original model so that user's callbacks could consider that -# the arrays of primal variable x and dual variable \lambda have fixed -# sizes. -function link!(cb::CallbackContext, model::Model) - p = Ref{Cint}(0) - KN_get_number_vars(model, p) - cb.n = p[] - KN_get_number_cons(model, p) - cb.m = p[] - return -end - -function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing) - if userParams != nothing - cb.userparams = userParams - end - # Link current callback context with Knitro model - link!(cb, m) - # Store callback context inside KNITRO user data. - # KN_set_cb_user_params(m.env, cb, cb) - ccall( - (:KN_set_cb_user_params, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), - m.env, cb, cb, - ) - return nothing -end - -""" -Specify which gradient option `gradopt` will be used to evaluate -the first derivatives of the callback functions. If `gradopt=KN_GRADOPT_EXACT` -then a gradient evaluation callback must be set by `KN_set_cb_grad()` -(or `KN_set_cb_rsd_jac()` for least squares). - -""" -function KN_set_cb_gradopt(m::Model, cb::CallbackContext, gradopt::Integer) - # KN_set_cb_gradopt(m.env, cb, gradopt) - ccall( - (:KN_set_cb_gradopt, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), - m.env, cb, gradopt, - ) - return nothing -end - -# High level EvalRequest structure. -mutable struct EvalRequest - evalRequestCode::Cint - threadID::Cint - x::Array{Float64} - lambda::Array{Float64} - sigma::Float64 - vec::Array{Float64} -end - -# Import low level request to Julia object. -function EvalRequest(ptr_model::Ptr{Cvoid}, evalRequest_::KN_eval_request, n::Int, m::Int) - # Import objective's scaling. - sigma = - (evalRequest_.sigma != C_NULL) ? unsafe_wrap(Array, evalRequest_.sigma, 1)[1] : 1.0 - # Wrap directly C arrays to avoid unnecessary copy. - return EvalRequest( - evalRequest_.type, - evalRequest_.threadID, - unsafe_wrap(Array, evalRequest_.x, n), - unsafe_wrap(Array, evalRequest_.lambda, n + m), - sigma, - unsafe_wrap(Array, evalRequest_.vec, n), - ) -end - -# High level EvalResult structure. -mutable struct EvalResult - obj::Array{Float64} - c::Array{Float64} - objGrad::Array{Float64} - jac::Array{Float64} - hess::Array{Float64} - hessVec::Array{Float64} - rsd::Array{Float64} - rsdJac::Array{Float64} -end - -function EvalResult( - kc::Ptr{Cvoid}, - cb::Ptr{Cvoid}, - evalResult_::KN_eval_result, - n::Int, - m::Int, -) - objgrad_nnz = Ref{Cint}() - jacobian_nnz = Ref{KNLONG}() - hessian_nnz = Ref{KNLONG}() - num_rsds = Ref{Cint}() - rsd_jacobian_nnz = Ref{KNLONG}() - KN_get_cb_objgrad_nnz(kc, cb, objgrad_nnz) - KN_get_cb_jacobian_nnz(kc, cb, jacobian_nnz) - KN_get_cb_hessian_nnz(kc, cb, hessian_nnz) - KN_get_cb_number_rsds(kc, cb, num_rsds) - KN_get_cb_rsd_jacobian_nnz(kc, cb, rsd_jacobian_nnz) - return EvalResult( - unsafe_wrap(Array, evalResult_.obj, 1), - unsafe_wrap(Array, evalResult_.c, m), - unsafe_wrap(Array, evalResult_.objGrad, objgrad_nnz[]), - unsafe_wrap(Array, evalResult_.jac, jacobian_nnz[]), - unsafe_wrap(Array, evalResult_.hess, hessian_nnz[]), - unsafe_wrap(Array, evalResult_.hessVec, n), - unsafe_wrap(Array, evalResult_.rsd, num_rsds[]), - unsafe_wrap(Array, evalResult_.rsdJac, rsd_jacobian_nnz[]), - ) -end - -macro wrap_function(wrap_name, name) - quote - function $(esc(wrap_name))( - ptr_model::Ptr{Cvoid}, - ptr_cb::Ptr{Cvoid}, - evalRequest_::Ptr{Cvoid}, - evalResults_::Ptr{Cvoid}, - userdata_::Ptr{Cvoid}, - ) - try - # Load evalRequest object. - ptr_request = Ptr{KN_eval_request}(evalRequest_) - evalRequest = unsafe_load(ptr_request)::KN_eval_request - # Load evalResult object. - ptr_result = Ptr{KN_eval_result}(evalResults_) - evalResult = unsafe_load(ptr_result)::KN_eval_result - # Eventually, load callback context. - cb = unsafe_pointer_to_objref(userdata_) - # Ensure that cb is a CallbackContext. - # Otherwise, we tell KNITRO that a problem occurs by returning a - # non-zero status. - if !isa(cb, CallbackContext) - return Cint(KN_RC_CALLBACK_ERR) - end - request = EvalRequest(ptr_model, evalRequest, cb.n, cb.m) - result = EvalResult(ptr_model, ptr_cb, evalResult, cb.n, cb.m) - res = cb.$name(ptr_model, ptr_cb, request, result, cb.userparams) - return Cint(res) - catch ex - if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) - end - if isa(ex, DomainError) - return Cint(KN_RC_EVAL_ERR) - else - @warn("Knitro encounters an exception in evaluation callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) - end - end - end - end -end - -@wrap_function eval_fc_wrapper eval_f -@wrap_function eval_ga_wrapper eval_g -@wrap_function eval_hess_wrapper eval_h - -# Eval callbacks should be of the form: -# callback(kc, cb, evalrequest, evalresult, usrparams)::Int - -""" - KN_add_eval_callback(m::Model, funccallback::Function) - KN_add_eval_callback(m::Model, evalObj::Bool, indexCons::Vector{Cint}, - funccallback::Function) - -This is the routine for adding a callback for (nonlinear) evaluations -of objective and constraint functions. This routine can be called -multiple times to add more than one callback structure (e.g. to create -different callback structures to handle different blocks of constraints). -This routine specifies the minimal information needed for a callback, and -creates the callback structure `cb`, which can then be passed to other -callback functions to set additional information for that callback. - -# Parameters -* `evalObj`: boolean indicating whether or not any part of the objective - function is evaluated in the callback -* `indexCons`: (length nC) index of constraints evaluated in the callback - (set to NULL if nC=0) -* `funcCallback`: a function that evaluates the objective parts - (if evalObj=KNTRUE) and any constraint parts (specified by - nC and indexCons) involved in this callback; when - eval_fcga=KN_EVAL_FCGA_YES, this callback should also evaluate - the relevant first derivatives/gradients - -# Returns -* `cb`: the callback structure that gets created by - calling this function; all the memory for this structure is - handled by Knitro - -After a callback is created by `KN_add_eval_callback()`, the user can then specify -gradient information and structure through `KN_set_cb_grad()` and Hessian -information and structure through `KN_set_cb_hess()`. If not set, Knitro will -approximate these. However, it is highly recommended to provide a callback routine -to specify the gradients if at all possible as this will greatly improve the -performance of Knitro. Even if a gradient callback is not provided, it is still -helpful to provide the sparse Jacobian structure through `KN_set_cb_grad()` to -improve the efficiency of the finite-difference gradient approximations. -Other optional information can also be set via `KN_set_cb_*()` functions as -detailed below. - -""" -function KN_add_eval_callback_all(m::Model, funccallback::Function) - # wrap eval_callback_wrapper as C function - # Wrap eval_callback_wrapper as C function. - c_f = @cfunction( - eval_fc_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) - ) - - # Define callback context. - rfptr = Ref{Ptr{Cvoid}}() - - # Add callback to context. - KN_add_eval_callback_all(m.env, c_f, rfptr) - cb = CallbackContext(rfptr[]) - register_callback(m, cb) - - # Store function in callback environment: - cb.eval_f = funccallback - - # Store model in user params to access callback in C - KN_set_cb_user_params(m, cb) - - return cb -end - -# Evaluate only the objective -function KN_add_objective_callback(m::Model, objcallback::Function) - # wrap eval_callback_wrapper as C function - c_f = @cfunction( - eval_fc_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) - ) - - # define callback context - rfptr = Ref{Ptr{Cvoid}}() - - # add callback to context - KN_add_eval_callback_one(m.env, Cint(-1), c_f, rfptr) - cb = CallbackContext(rfptr[]) - register_callback(m, cb) - - # store function in callback environment: - cb.eval_f = objcallback - - # store model in user params to access callback in C - KN_set_cb_user_params(m, cb) - - return cb -end - -function KN_add_eval_callback( - m::Model, - evalObj::Bool, # switch on obj eval - indexCons::Vector{Cint}, # index of constaints - funccallback::Function, -) # callback - nC = length(indexCons) - - # Wrap eval_callback_wrapper as C function. - c_f = @cfunction( - eval_fc_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) - ) - - # Define callback context. - rfptr = Ref{Ptr{Cvoid}}() - - # Add callback to context. - KN_add_eval_callback(m.env, KNBOOL(evalObj), nC, indexCons, c_f, rfptr) - cb = CallbackContext(rfptr[]) - register_callback(m, cb) - - # Store function in callback environment. - cb.eval_f = funccallback - - KN_set_cb_user_params(m, cb) - - return cb -end - -""" - KN_set_cb_grad(m::Model, cb::CallbackContext, gradcallback; - nV::Integer=KN_DENSE, objGradIndexVars=C_NULL, - jacIndexCons=C_NULL, jacIndexVars=C_NULL) - -This API function is used to set the objective gradient and constraint Jacobian -structure and also (optionally) a callback function to evaluate the objective -gradient and constraint Jacobian provided through this callback. - -""" -function KN_set_cb_grad( - m::Model, - cb::CallbackContext, - gradcallback; - nV::Integer=KN_DENSE, - nnzJ::Union{Nothing,Integer}=nothing, - objGradIndexVars=C_NULL, - jacIndexCons=C_NULL, - jacIndexVars=C_NULL, -) - if nnzJ === nothing - p = Ref{Cint}(0) - KN_get_number_cons(m, p) - nnzJ = iszero(p[]) ? KNLONG(0) : KNITRO.KN_DENSE_COLMAJOR - end - # Check consistency of arguments. - if (nV == 0 || nV == KN_DENSE) - (objGradIndexVars != C_NULL) && - error("objGradIndexVars must be set to C_NULL when nV = $nV") - else - @assert (objGradIndexVars != C_NULL) && (length(objGradIndexVars) == nV) - end - - if jacIndexCons != C_NULL && jacIndexVars != C_NULL - @assert length(jacIndexCons) == length(jacIndexVars) - nnzJ = KNLONG(length(jacIndexCons)) - end - - if gradcallback != nothing - # Store grad function inside model. - cb.eval_g = gradcallback - - # Wrap gradient wrapper as C function. - c_grad_g = @cfunction( - eval_ga_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) - ) - else - c_grad_g = C_NULL - end - - KN_set_cb_grad( - m.env, - cb, - nV, - objGradIndexVars, - KNLONG(nnzJ), - jacIndexCons, - jacIndexVars, - c_grad_g - ) - return nothing -end - -""" - KN_set_cb_hess(m::Model, cb::CallbackContext, nnzH::Integer, hesscallback::Function; - hessIndexVars1=C_NULL, hessIndexVars2=C_NULL) - -This API function is used to set the structure and a callback function to -evaluate the components of the Hessian of the Lagrangian provided through this -callback. KN_set_cb_hess() should only be used when defining a user-supplied -Hessian callback function (via the `hessopt=KN_HESSOPT_EXACT` user option). -When Knitro is approximating the Hessian, it cannot make use of the Hessian -sparsity structure. - -""" -function KN_set_cb_hess( - m::Model, - cb::CallbackContext, - nnzH::Integer, - hesscallback::Function; - hessIndexVars1=C_NULL, - hessIndexVars2=C_NULL, -) - - # If Hessian is dense, ensure that sparsity pattern is empty - if nnzH == KN_DENSE_ROWMAJOR || nnzH == KN_DENSE_COLMAJOR - @assert hessIndexVars1 == hessIndexVars2 == C_NULL - # Otherwise, check validity of sparsity pattern - elseif nnzH > 0 - @assert hessIndexVars1 != C_NULL && hessIndexVars2 != C_NULL - @assert length(hessIndexVars1) == length(hessIndexVars2) == nnzH - end - # Store hessian function inside model. - cb.eval_h = hesscallback - - # Wrap gradient wrapper as C function. - c_hess = @cfunction( - eval_hess_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) - ) - - KN_set_cb_hess( - m.env, - cb, - nnzH, - hessIndexVars1, - hessIndexVars2, - c_hess - ) - - return nothing -end - -#= - RESIDUALS -=# -@wrap_function eval_rj_wrapper eval_jac_rsd -@wrap_function eval_rsd_wrapper eval_rsd - -# TODO: add _one and _* support -""" - KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) - -Add an evaluation callback for a least-squares models. Similar to KN_add_eval_callback() -above, but for least-squares models. - -* `m`: current KNITRO model -* `rsdCallback`: a function that evaluates any residual parts - -After a callback is created by `KN_add_lsq_eval_callback()`, the user can then -specify residual Jacobian information and structure through `KN_set_cb_rsd_jac()`. -If not set, Knitro will approximate the residual Jacobian. However, it is highly -recommended to provide a callback routine to specify the residual Jacobian if at all -possible as this will greatly improve the performance of Knitro. Even if a callback -for the residual Jacobian is not provided, it is still helpful to provide the sparse -Jacobian structure for the residuals through `KN_set_cb_rsd_jac()` to improve the -efficiency of the finite-difference Jacobian approximation. - -""" -function KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) - - # Wrap eval_callback_wrapper as C function. - c_f = @cfunction( - eval_rsd_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) - ) - - # Define callback context. - rfptr = Ref{Ptr{Cvoid}}() - - # Add callback to context. - KN_add_lsq_eval_callback_all(m.env, c_f, rfptr) - cb = CallbackContext(rfptr[]) - register_callback(m, cb) - - # Store function inside model. - cb.eval_rsd = rsdCallBack - - KN_set_cb_user_params(m, cb) - - return cb -end - -function KN_set_cb_rsd_jac( - m::Model, - cb::CallbackContext, - nnzJ::Integer, - evalRJ::Function; - jacIndexRsds=C_NULL, - jacIndexVars=C_NULL, -) - # Check consistency of arguments. - if nnzJ == KN_DENSE_ROWMAJOR || nnzJ == KN_DENSE_COLMAJOR || nnzJ == 0 - @assert jacIndexRsds == jacIndexVars == C_NULL - else - @assert jacIndexRsds != C_NULL && jacIndexVars != C_NULL - @assert length(jacIndexRsds) == length(jacIndexVars) == nnzJ - end - - # Store function inside model. - cb.eval_jac_rsd = evalRJ - # Wrap as C function. - c_eval_rj = @cfunction( - eval_rj_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) - ) - KN_set_cb_rsd_jac( - m.env, - cb, - nnzJ, - jacIndexRsds, - jacIndexVars, - c_eval_rj - ) - return cb -end - -#= - USER CALLBACKS -=# -function newpt_wrapper( - ptr_model::Ptr{Cvoid}, - ptr_x::Ptr{Cdouble}, - ptr_lambda::Ptr{Cdouble}, - userdata_::Ptr{Cvoid}, -) - # Load KNITRO's Julia Model. - try - m = unsafe_pointer_to_objref(userdata_)::Model - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - x = unsafe_wrap(Array, ptr_x, nx) - lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) - ret = m.newpt_callback(m, x, lambda, m.newpoint_user) - return Cint(ret) - catch ex - if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) - else - @warn("Knitro encounters an exception in newpoint callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) - end - end -end - -""" - KN_set_newpt_callback(m::Model, callback::Function) - -Set the callback function that is invoked after Knitro computes a -new estimate of the solution point (i.e., after every iteration). -The function should not modify any Knitro arguments. - -Callback is a function with signature: - - callback(kc, x, lambda, userdata) - -Argument `kc` passed to the callback from inside Knitro is the -context pointer for the current problem being solved inside Knitro -(either the main single-solve problem, or a subproblem when using -multi-start, Tuner, etc.). -Arguments `x` and `lambda` contain the latest solution estimates. -Other values (such as objective, constraint, jacobian, etc.) can be -queried using the corresonding KN_get_XXX_values methods. - -Note: Currently only active for continuous models. - -""" -function KN_set_newpt_callback(m::Model, callback::Function, userparams=nothing) - # Store callback function inside model. - m.newpt_callback = callback - if userparams != nothing - m.newpoint_user = userparams - end - - # Wrap user callback wrapper as C function. - c_func = @cfunction( - newpt_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) - ) - - KN_set_newpt_callback(m.env, c_func, m) - return nothing -end - -function ms_process_wrapper( - ptr_model::Ptr{Cvoid}, - ptr_x::Ptr{Cdouble}, - ptr_lambda::Ptr{Cdouble}, - userdata_::Ptr{Cvoid}, -) - - # Load KNITRO's Julia Model. - try - m = unsafe_pointer_to_objref(userdata_)::Model - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - x = unsafe_wrap(Array, ptr_x, nx) - lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) - res = m.ms_process(m, x, lambda, m.multistart_user) - return Cint(res) - catch ex - if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) - else - @warn("Knitro encounters an exception in multistart callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) - end - end -end - -""" - KN_set_ms_process_callback(m::Model, callback::Function) - -This callback function is for multistart (MS) problems only. -Set the callback function that is invoked after Knitro finishes -processing a multistart solve. - -Callback is a function with signature: - - callback(kc, x, lambda, userdata) - -Argument `kc` passed to the callback -from inside Knitro is the context pointer for the last multistart -subproblem solved inside Knitro. The function should not modify any -Knitro arguments. Arguments `x` and `lambda` contain the solution from -the last solve. - -""" -function KN_set_ms_process_callback(m::Model, callback::Function, userparams=nothing) - # Store callback function inside model. - m.ms_process = callback - if userparams != nothing - m.multistart_user = userparams - end - - # Wrap user callback wrapper as C function. - c_func = @cfunction( - ms_process_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) - ) - - # KN_set_ms_process_callback(m.env, c_func, m) - ccall( - (:KN_set_ms_process_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), - m.env, c_func, m, - ) - return nothing -end - -function mip_node_callback_wrapper( - ptr_model::Ptr{Cvoid}, - ptr_x::Ptr{Cdouble}, - ptr_lambda::Ptr{Cdouble}, - userdata_::Ptr{Cvoid}, -) - # Load KNITRO's Julia Model. - try - m = unsafe_pointer_to_objref(userdata_)::Model - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - x = unsafe_wrap(Array, ptr_x, nx) - lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) - res = m.mip_callback(m, x, lambda, m.mip_user) - return Cint(res) - catch ex - if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) - else - @warn("Knitro encounters an exception in MIP callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) - end - end -end - -""" - KN_set_mip_node_callback(m::Model, callback::Function) - -This callback function is for mixed integer (MIP) problems only. -Set the callback function that is invoked after Knitro finishes -processing a node on the branch-and-bound tree (i.e., after a relaxed -subproblem solve in the branch-and-bound procedure). - -Callback is a function with signature: - - callback(kc, x, lambda, userdata) - -Argument `kc` passed to the callback from inside Knitro is the -context pointer for the last node subproblem solved inside Knitro. -The function should not modify any Knitro arguments. -Arguments `x` and `lambda` contain the solution from the node solve. - -""" -function KN_set_mip_node_callback(m::Model, callback::Function, userparams=nothing) - # Store callback function inside model. - m.mip_callback = callback - if userparams != nothing - m.mip_user = userparams - end - - # Wrap user callback wrapper as C function. - c_func = @cfunction( - mip_node_callback_wrapper, - Cint, - (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) - ) - - # KN_set_mip_node_callback(m.env, c_func, m) - ccall( - (:KN_set_mip_node_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), - m.env, c_func, m, - ) - return nothing -end - -function ms_initpt_wrapper( - ptr_model::Ptr{Cvoid}, - nSolveNumber::Cint, - ptr_x::Ptr{Cdouble}, - ptr_lambda::Ptr{Cdouble}, - userdata_::Ptr{Cvoid}, -) - - # Load KNITRO's Julia Model. - m = unsafe_pointer_to_objref(userdata_)::Model - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - - x = unsafe_wrap(Array, ptr_x, nx) - lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) - res = m.ms_initpt_callback(m, nSolveNumber, x, lambda, m.multistart_user) - - return Cint(res) -end - -""" - KN_set_ms_initpt_callback(m::Model, callback::Function) -Type declaration for the callback that allows applications to -specify an initial point before each local solve in the multistart -procedure. - -Callback is a function with signature: - - callback(kc, x, lambda, userdata) - -On input, arguments `x` and `lambda` are the randomly -generated initial points determined by Knitro, which can be overwritten -by the user. The argument `nSolveNumber` is the number of the -multistart solve. - -""" -function KN_set_ms_initpt_callback(m::Model, callback::Function, userparams=nothing) - # Store callback function inside model. - m.ms_initpt_callback = callback - if userparams != nothing - m.multistart_user = userparams - end - - # Wrap user callback wrapper as C function. - c_func = @cfunction( - ms_initpt_wrapper, - Cint, - (Ptr{Cvoid}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) - ) - - # KN_set_ms_initpt_callback(m.env, c_func, m) - ccall( - (:KN_set_ms_initpt_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), - m.env, c_func, m, - ) - return nothing -end - -function puts_callback_wrapper(str::Ptr{Cchar}, userdata_::Ptr{Cvoid}) - - # Load KNITRO's Julia Model. - try - m = unsafe_pointer_to_objref(userdata_)::Model - res = m.puts_callback(unsafe_string(str), m.puts_user) - return Cint(res) - catch ex - if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) - else - @warn("Knitro encounters an exception in puts callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) - end - end -end - -""" - KN_set_puts_callback(m::Model, callback::Function) - -Set the callback that allows applications to handle -output. Applications can set a `put string` callback function to handle -output generated by the Knitro solver. By default Knitro prints to -stdout or a file named `knitro.log`, as determined by KN_PARAM_OUTMODE. - -Callback is a function with signature: - - callback(str::String, userdata) - -The KN_puts callback function takes a `userParams` argument which is a pointer -passed directly from KN_solve. The function should return the number of -characters that were printed. - -""" -function KN_set_puts_callback(m::Model, callback::Function, userparams=nothing) - # Store callback function inside model. - m.puts_callback = callback - if userparams != nothing - m.puts_user = userparams - end - - # Wrap user callback wrapper as C function. - c_func = @cfunction(puts_callback_wrapper, Cint, (Ptr{Cchar}, Ptr{Cvoid})) - - # KN_set_puts_callback(m.env, c_func, m) - ccall( - (:KN_set_puts_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, #=CHANGED=# Any), - m.env, c_func, m, - ) - return nothing -end diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 47f3e81..25e4818 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -13,9 +13,9 @@ import MathOptInterface as MOI function runtests() for name in names(@__MODULE__; all=true) if startswith("$(name)", "test_") - # @testset "$(name)" begin + @testset "$(name)" begin getfield(@__MODULE__, name)() - # end + end end end return From 71f8e52229e9edc9a4f2f7786202427ed3c89c14 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 20 Nov 2023 16:55:43 +1300 Subject: [PATCH 04/15] Update --- src/C_wrapper.jl | 278 +++++++++++---------------------------- src/MOI_wrapper.jl | 94 +++++++------ test/C_wrapper.jl | 10 +- test/knitroapi_licman.jl | 7 +- 4 files changed, 138 insertions(+), 251 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index f9e6585..dcb2cd5 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -259,7 +259,6 @@ end =# function KN_get_solution(m::Model) - # we first check that the model is well defined to avoid segfault @assert m.env != C_NULL p = Ref{Cint}(0) KN_get_number_vars(m, p) @@ -270,7 +269,6 @@ function KN_get_solution(m::Model) lambda = zeros(Cdouble, nx + nc) status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) KN_get_solution(m, status, obj, x, lambda) - # Keep solution in cache. m.status = status[] m.x = x m.mult = lambda @@ -278,67 +276,6 @@ function KN_get_solution(m::Model) return status[], obj[], x, lambda end -function get_status(m::Model) - @assert m.env != C_NULL - if m.status != 1 - return m.status - end - status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) - KN_get_solution(m, status, obj, C_NULL, C_NULL) - m.status = status[] - return status[] -end - -function get_objective(m::Model) - @assert m.env != C_NULL - if isfinite(m.obj_val) - return m.obj_val - end - status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) - KN_get_solution(m, status, obj, C_NULL, C_NULL) - m.obj_val = obj[] - return obj[] -end - -function get_solution(m::Model) - # We first check that the model is well defined to avoid segfault. - @assert m.env != C_NULL - if !isempty(m.x) - return m.x - end - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - x = zeros(Cdouble, nx) - status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) - KN_get_solution(m, status, obj, x, C_NULL) - m.x = x - return x -end - -get_solution(m::Model, ix::Int) = isempty(m.x) ? get_solution(m)[ix] : m.x[ix] - -function get_dual(m::Model) - # we first check that the model is well defined to avoid segfault - @assert m.env != C_NULL - if !isempty(m.mult) - return m.mult - end - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - lambda = zeros(Cdouble, nx + nc) - status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) - KN_get_solution(m, status, obj, C_NULL, lambda) - # Keep multipliers in cache. - m.mult = lambda - return lambda -end - -get_dual(m::Model, ix::Int) = isempty(m.mult) ? get_dual(m)[ix] : m.mult[ix] - # Callbacks utilities. # Note: we store here the number of constraints and variables defined @@ -380,14 +317,12 @@ Specify which gradient option `gradopt` will be used to evaluate the first derivatives of the callback functions. If `gradopt=KN_GRADOPT_EXACT` then a gradient evaluation callback must be set by `KN_set_cb_grad()` (or `KN_set_cb_rsd_jac()` for least squares). - """ function KN_set_cb_gradopt(m::Model, cb::CallbackContext, gradopt::Integer) KN_set_cb_gradopt(m.env, cb, gradopt) return end -# High level EvalRequest structure. mutable struct EvalRequest evalRequestCode::Cint threadID::Cint @@ -397,12 +332,9 @@ mutable struct EvalRequest vec::Array{Float64} end -# Import low level request to Julia object. function EvalRequest(ptr_model::Ptr{Cvoid}, evalRequest_::KN_eval_request, n::Int, m::Int) - # Import objective's scaling. sigma = (evalRequest_.sigma != C_NULL) ? unsafe_wrap(Array, evalRequest_.sigma, 1)[1] : 1.0 - # Wrap directly C arrays to avoid unnecessary copy. return EvalRequest( evalRequest_.type, evalRequest_.threadID, @@ -413,7 +345,6 @@ function EvalRequest(ptr_model::Ptr{Cvoid}, evalRequest_::KN_eval_request, n::In ) end -# High level EvalResult structure. mutable struct EvalResult obj::Array{Float64} c::Array{Float64} @@ -454,53 +385,47 @@ function EvalResult( ) end -macro wrap_function(wrap_name, name) - quote - function $(esc(wrap_name))( +for (wrap_name, name) in [ + :eval_fc_wrapper => :eval_f, + :eval_ga_wrapper => :eval_g, + :eval_hess_wrapper => :eval_h, + :eval_rj_wrapper => :eval_jac_rsd, + :eval_rsd_wrapper => :eval_rsd, +] + @eval begin + function $wrap_name( ptr_model::Ptr{Cvoid}, ptr_cb::Ptr{Cvoid}, evalRequest_::Ptr{Cvoid}, evalResults_::Ptr{Cvoid}, userdata_::Ptr{Cvoid}, - ) + )::Cint try - # Load evalRequest object. ptr_request = Ptr{KN_eval_request}(evalRequest_) evalRequest = unsafe_load(ptr_request)::KN_eval_request - # Load evalResult object. ptr_result = Ptr{KN_eval_result}(evalResults_) evalResult = unsafe_load(ptr_result)::KN_eval_result - # Eventually, load callback context. cb = unsafe_pointer_to_objref(userdata_) - # Ensure that cb is a CallbackContext. - # Otherwise, we tell KNITRO that a problem occurs by returning a - # non-zero status. if !isa(cb, CallbackContext) - return Cint(KN_RC_CALLBACK_ERR) + return KN_RC_CALLBACK_ERR end request = EvalRequest(ptr_model, evalRequest, cb.n, cb.m) result = EvalResult(ptr_model, ptr_cb, evalResult, cb.n, cb.m) - res = cb.$name(ptr_model, ptr_cb, request, result, cb.userparams) - return Cint(res) + return cb.$name(ptr_model, ptr_cb, request, result, cb.userparams) catch ex if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) - end - if isa(ex, DomainError) - return Cint(KN_RC_EVAL_ERR) + return KN_RC_USER_TERMINATION + elseif isa(ex, DomainError) + return KN_RC_EVAL_ERR else @warn("Knitro encounters an exception in evaluation callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) + return KN_RC_CALLBACK_ERR end end end end end -@wrap_function eval_fc_wrapper eval_f -@wrap_function eval_ga_wrapper eval_g -@wrap_function eval_hess_wrapper eval_h - """ KN_add_eval_callback(m::Model, funccallback::Function) KN_add_eval_callback(m::Model, evalObj::Bool, indexCons::Vector{Cint}, @@ -540,88 +465,55 @@ helpful to provide the sparse Jacobian structure through `KN_set_cb_grad()` to improve the efficiency of the finite-difference gradient approximations. Other optional information can also be set via `KN_set_cb_*()` functions as detailed below. - """ function KN_add_eval_callback_all(m::Model, funccallback::Function) - # wrap eval_callback_wrapper as C function - # Wrap eval_callback_wrapper as C function. c_f = @cfunction( eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) - - # Define callback context. rfptr = Ref{Ptr{Cvoid}}() - - # Add callback to context. KN_add_eval_callback_all(m.env, c_f, rfptr) cb = CallbackContext(rfptr[]) register_callback(m, cb) - - # Store function in callback environment: cb.eval_f = funccallback - - # Store model in user params to access callback in C KN_set_cb_user_params(m, cb) - return cb end -# Evaluate only the objective function KN_add_objective_callback(m::Model, objcallback::Function) - # wrap eval_callback_wrapper as C function c_f = @cfunction( eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) - - # define callback context rfptr = Ref{Ptr{Cvoid}}() - - # add callback to context KN_add_eval_callback_one(m.env, Cint(-1), c_f, rfptr) cb = CallbackContext(rfptr[]) register_callback(m, cb) - - # store function in callback environment: cb.eval_f = objcallback - - # store model in user params to access callback in C KN_set_cb_user_params(m, cb) - return cb end function KN_add_eval_callback( m::Model, - evalObj::Bool, # switch on obj eval - indexCons::Vector{Cint}, # index of constaints + evalObj::Bool, + indexCons::Vector{Cint}, funccallback::Function, -) # callback +) nC = length(indexCons) - - # Wrap eval_callback_wrapper as C function. - c_f = @cfunction( + c_f = @cfunction( eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) - - # Define callback context. rfptr = Ref{Ptr{Cvoid}}() - - # Add callback to context. KN_add_eval_callback(m.env, KNBOOL(evalObj), nC, indexCons, c_f, rfptr) cb = CallbackContext(rfptr[]) register_callback(m, cb) - - # Store function in callback environment. cb.eval_f = funccallback - KN_set_cb_user_params(m, cb) - return cb end @@ -633,7 +525,6 @@ end This API function is used to set the objective gradient and constraint Jacobian structure and also (optionally) a callback function to evaluate the objective gradient and constraint Jacobian provided through this callback. - """ function KN_set_cb_grad( m::Model, @@ -650,24 +541,18 @@ function KN_set_cb_grad( KN_get_number_cons(m, p) nnzJ = iszero(p[]) ? KNLONG(0) : KNITRO.KN_DENSE_COLMAJOR end - # Check consistency of arguments. if (nV == 0 || nV == KN_DENSE) (objGradIndexVars != C_NULL) && error("objGradIndexVars must be set to C_NULL when nV = $nV") else @assert (objGradIndexVars != C_NULL) && (length(objGradIndexVars) == nV) end - if jacIndexCons != C_NULL && jacIndexVars != C_NULL @assert length(jacIndexCons) == length(jacIndexVars) nnzJ = KNLONG(length(jacIndexCons)) end - if gradcallback != nothing - # Store grad function inside model. cb.eval_g = gradcallback - - # Wrap gradient wrapper as C function. c_grad_g = @cfunction( eval_ga_wrapper, Cint, @@ -676,7 +561,6 @@ function KN_set_cb_grad( else c_grad_g = C_NULL end - KN_set_cb_grad( m.env, cb, @@ -710,19 +594,13 @@ function KN_set_cb_hess( hessIndexVars1=C_NULL, hessIndexVars2=C_NULL, ) - - # If Hessian is dense, ensure that sparsity pattern is empty if nnzH == KN_DENSE_ROWMAJOR || nnzH == KN_DENSE_COLMAJOR @assert hessIndexVars1 == hessIndexVars2 == C_NULL - # Otherwise, check validity of sparsity pattern elseif nnzH > 0 @assert hessIndexVars1 != C_NULL && hessIndexVars2 != C_NULL @assert length(hessIndexVars1) == length(hessIndexVars2) == nnzH end - # Store hessian function inside model. cb.eval_h = hesscallback - - # Wrap gradient wrapper as C function. c_hess = @cfunction( eval_hess_wrapper, Cint, @@ -732,14 +610,6 @@ function KN_set_cb_hess( return end -#= - RESIDUALS -=# - -@wrap_function eval_rj_wrapper eval_jac_rsd -@wrap_function eval_rsd_wrapper eval_rsd - -# TODO: add _one and _* support """ KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) @@ -757,7 +627,6 @@ possible as this will greatly improve the performance of Knitro. Even if a call for the residual Jacobian is not provided, it is still helpful to provide the sparse Jacobian structure for the residuals through `KN_set_cb_rsd_jac()` to improve the efficiency of the finite-difference Jacobian approximation. - """ function KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) c_f = @cfunction( @@ -771,7 +640,7 @@ function KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) register_callback(m, cb) cb.eval_rsd = rsdCallBack KN_set_cb_user_params(m, cb) - return cb + return end function KN_set_cb_rsd_jac( @@ -795,14 +664,12 @@ function KN_set_cb_rsd_jac( (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) KN_set_cb_rsd_jac(m.env, cb, nnzJ, jacIndexRsds, jacIndexVars, c_eval_rj) - return cb + return end -#= - USER CALLBACKS -=# +# KN_set_newpt_callback -function newpt_wrapper( +function _newpt_wrapper( ptr_model::Ptr{Cvoid}, ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, @@ -857,7 +724,7 @@ function KN_set_newpt_callback(m::Model, callback::Function, userparams=nothing) m.newpoint_user = userparams end c_func = @cfunction( - newpt_wrapper, + _newpt_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) @@ -873,7 +740,9 @@ function KN_set_newpt_callback(m::Model, callback::Function, userparams=nothing) return end -function ms_process_wrapper( +# KN_set_ms_process_callback + +function _ms_process_wrapper( ptr_model::Ptr{Cvoid}, ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, @@ -924,7 +793,7 @@ function KN_set_ms_process_callback(m::Model, callback::Function, userparams=not m.multistart_user = userparams end c_func = @cfunction( - ms_process_wrapper, + _ms_process_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) @@ -940,21 +809,21 @@ function KN_set_ms_process_callback(m::Model, callback::Function, userparams=not return end -function mip_node_callback_wrapper( +# KN_set_mip_node_callback + +function _mip_node_callback_wrapper( ptr_model::Ptr{Cvoid}, ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, - userdata_::Ptr{Cvoid}, + user_data::Ptr{Cvoid}, ) try - m = unsafe_pointer_to_objref(userdata_)::Model - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - x = unsafe_wrap(Array, ptr_x, nx) - lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) + m = unsafe_pointer_to_objref(user_data)::Model + nx, nc = Ref{Cint}(0), Ref{Cint}(0) + KN_get_number_vars(m, nx) + KN_get_number_cons(m, nc) + x = unsafe_wrap(Array, ptr_x, nx[]) + lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) res = m.mip_callback(m, x, lambda, m.mip_user) return Cint(res) catch ex @@ -991,7 +860,7 @@ function KN_set_mip_node_callback(m::Model, callback::Function, userparams=nothi m.mip_user = userparams end c_func = @cfunction( - mip_node_callback_wrapper, + _mip_node_callback_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) @@ -1007,40 +876,40 @@ function KN_set_mip_node_callback(m::Model, callback::Function, userparams=nothi return end -function ms_initpt_wrapper( +# KN_set_ms_initpt_callback + +function _ms_initpt_wrapper( ptr_model::Ptr{Cvoid}, nSolveNumber::Cint, ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, - userdata_::Ptr{Cvoid}, + user_data::Ptr{Cvoid}, ) - m = unsafe_pointer_to_objref(userdata_)::Model - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - x = unsafe_wrap(Array, ptr_x, nx) - lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) - res = m.ms_initpt_callback(m, nSolveNumber, x, lambda, m.multistart_user) + model = unsafe_pointer_to_objref(user_data)::Model + nx, nc = Ref{Cint}(0), Ref{Cint}(0) + KN_get_number_vars(model, nx) + KN_get_number_cons(model, nc) + x = unsafe_wrap(Array, ptr_x, nx[]) + lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) + res = model.ms_initpt_callback(model, nSolveNumber, x, lambda, m.multistart_user) return Cint(res) end """ KN_set_ms_initpt_callback(m::Model, callback::Function) -Type declaration for the callback that allows applications to -specify an initial point before each local solve in the multistart -procedure. -Callback is a function with signature: +Set a callback that allows applications to specify an initial point before each local solve +in the multistart procedure. - callback(kc, x, lambda, userdata) +Callback is a function with signature: -On input, arguments `x` and `lambda` are the randomly -generated initial points determined by Knitro, which can be overwritten -by the user. The argument `nSolveNumber` is the number of the -multistart solve. +```julia +callback(kc, x, lambda, userdata) +``` +On input, arguments `x` and `lambda` are the randomly generated initial points determined by +Knitro, which can be overwritten by the user. The argument `nSolveNumber` is the number of +the multistart solve. """ function KN_set_ms_initpt_callback(m::Model, callback::Function, userparams=nothing) m.ms_initpt_callback = callback @@ -1048,7 +917,7 @@ function KN_set_ms_initpt_callback(m::Model, callback::Function, userparams=noth m.multistart_user = userparams end c_func = @cfunction( - ms_initpt_wrapper, + _ms_initpt_wrapper, Cint, (Ptr{Cvoid}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) @@ -1064,10 +933,12 @@ function KN_set_ms_initpt_callback(m::Model, callback::Function, userparams=noth return end -function puts_callback_wrapper(str::Ptr{Cchar}, userdata_::Ptr{Cvoid}) +# KN_set_puts_callback + +function _puts_callback_wrapper(str::Ptr{Cchar}, user_data::Ptr{Cvoid}) try - m = unsafe_pointer_to_objref(userdata_)::Model - res = m.puts_callback(unsafe_string(str), m.puts_user) + model = unsafe_pointer_to_objref(user_data)::Model + res = model.puts_callback(unsafe_string(str), model.puts_user) return Cint(res) catch ex if isa(ex, InterruptException) @@ -1082,26 +953,27 @@ end """ KN_set_puts_callback(m::Model, callback::Function) -Set the callback that allows applications to handle -output. Applications can set a `put string` callback function to handle -output generated by the Knitro solver. By default Knitro prints to -stdout or a file named `knitro.log`, as determined by KN_PARAM_OUTMODE. +Set the callback that allows applications to handle output. -Callback is a function with signature: +Applications can set a `put string` callback function to handle output generated by the +Knitro solver. By default Knitro prints to stdout or a file named `knitro.log`, as +determined by KN_PARAM_OUTMODE. - callback(str::String, userdata) +The `callback` is a function with signature: +```julia +callback(str::String, user_data) -> Cint +``` The KN_puts callback function takes a `userParams` argument which is a pointer passed directly from KN_solve. The function should return the number of characters that were printed. - """ function KN_set_puts_callback(m::Model, callback::Function, userparams=nothing) m.puts_callback = callback if userparams != nothing m.puts_user = userparams end - c_func = @cfunction(puts_callback_wrapper, Cint, (Ptr{Cchar}, Ptr{Cvoid})) + c_func = @cfunction(_puts_callback_wrapper, Cint, (Ptr{Cchar}, Ptr{Cvoid})) # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} ccall( (:KN_set_puts_callback, libknitro), diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c71860c..d61c46a 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1318,7 +1318,11 @@ function MOI.optimize!(model::Optimizer) return end -MOI.get(model::Optimizer, ::MOI.RawStatusString) = string(get_status(model.inner)) +function MOI.get(model::Optimizer, ::MOI.RawStatusString) + statusP, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) + KN_get_solution(model.inner, statusP, obj, C_NULL, C_NULL) + return string(statusP[]) +end # Refer to KNITRO manual for solver status: # https://www.artelys.com/tools/knitro_doc/3_referenceManual/returnCodes.html#returncodes @@ -1391,19 +1395,22 @@ function MOI.get(model::Optimizer, ::MOI.TerminationStatus) if model.number_solved == 0 return MOI.OPTIMIZE_NOT_CALLED end - status = get_status(model.inner) - return get(_KN_TO_MOI_RETURN_STATUS, status, MOI.OTHER_ERROR) + status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) + KN_get_solution(model.inner, status, obj, C_NULL, C_NULL) + return get(_KN_TO_MOI_RETURN_STATUS, status[], MOI.OTHER_ERROR) end function MOI.get(model::Optimizer, ::MOI.ResultCount) return model.number_solved >= 1 ? 1 : 0 end -function MOI.get(model::Optimizer, status::MOI.PrimalStatus) - if model.number_solved == 0 || status.result_index != 1 +function MOI.get(model::Optimizer, attr::MOI.PrimalStatus) + if model.number_solved == 0 || attr.result_index != 1 return MOI.NO_SOLUTION end - status = get_status(model.inner) + statusP, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) + KN_get_solution(model.inner, statusP, obj, C_NULL, C_NULL) + status = statusP[] if status == 0 return MOI.FEASIBLE_POINT elseif -109 <= status <= -100 @@ -1424,11 +1431,13 @@ function MOI.get(model::Optimizer, status::MOI.PrimalStatus) end end -function MOI.get(model::Optimizer, status::MOI.DualStatus) - if model.number_solved == 0 || status.result_index != 1 +function MOI.get(model::Optimizer, attr::MOI.DualStatus) + if model.number_solved == 0 || attr.result_index != 1 return MOI.NO_SOLUTION end - status = get_status(model.inner) + statusP, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) + KN_get_solution(model.inner, statusP, obj, C_NULL, C_NULL) + status = statusP[] if status == 0 return MOI.FEASIBLE_POINT elseif -109 <= status <= -100 @@ -1454,7 +1463,34 @@ function MOI.get(model::Optimizer, obj::MOI.ObjectiveValue) elseif obj.result_index != 1 throw(MOI.ResultIndexBoundsError{MOI.ObjectiveValue}(obj, 1)) end - return get_objective(model.inner) + status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) + KN_get_solution(model.inner, status, obj, C_NULL, C_NULL) + return obj[] +end + +function _get_solution(model::Model, index::Integer) + @assert model.env != C_NULL + if isempty(model.x) + p = Ref{Cint}(0) + KN_get_number_vars(model, p) + model.x = zeros(Cdouble, p[]) + status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) + KN_get_solution(model, status, obj, model.x, C_NULL) + end + return model.x[index] +end + +function _get_dual(model::Model, index::Integer) + @assert model.env != C_NULL + if isempty(model.mult) + nx, nc = Ref{Cint}(0), Ref{Cint}(0) + KN_get_number_vars(model, nx) + KN_get_number_cons(model, nc) + model.mult = zeros(Cdouble, nx[] + nc[]) + status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) + KN_get_solution(model, status, obj, C_NULL, model.mult) + end + return model.mult[index] end function MOI.get(model::Optimizer, v::MOI.VariablePrimal, x::MOI.VariableIndex) @@ -1464,7 +1500,7 @@ function MOI.get(model::Optimizer, v::MOI.VariablePrimal, x::MOI.VariableIndex) throw(MOI.ResultIndexBoundsError{MOI.VariablePrimal}(v, 1)) end MOI.throw_if_not_valid(model, x) - return get_solution(model.inner, x.value) + return _get_solution(model.inner, x.value) end function _check_cons(model, ci, cp) @@ -1521,26 +1557,7 @@ function MOI.get( end x = MOI.VariableIndex(ci.value) MOI.throw_if_not_valid(model, x) - return get_solution(model.inner, x.value) -end - -function MOI.get( - model::Optimizer, - cp::MOI.ConstraintPrimal, - ci::Vector{ - MOI.ConstraintIndex{ - MOI.VariableIndex, - <:Union{MOI.EqualTo{Float64},MOI.GreaterThan{Float64},MOI.LessThan{Float64}}, - }, - }, -) - if model.number_solved == 0 - error("ConstraintPrimal not available.") - elseif cp.result_index > 1 - throw(MOI.ResultIndexBoundsError{MOI.ConstraintPrimal}(cp, 1)) - end - x = get_solution(model.inner) - return [x[c.value] for c in ci] + return _get_solution(model.inner, x.value) end # KNITRO's dual sign depends on optimization sense. @@ -1561,8 +1578,7 @@ function MOI.get( } _check_cons(model, ci, cd) index = model.constraint_mapping[ci] + 1 - lambda = get_dual(model.inner) - return _sense_dual(model) * lambda[index] + return _sense_dual(model) * _get_dual(model.inner, index) end function MOI.get( @@ -1570,8 +1586,7 @@ function MOI.get( ::MOI.ConstraintDual, ci::MOI.ConstraintIndex{MOI.ScalarNonlinearFunction}, ) - lambda = get_dual(model.inner) - return _sense_dual(model) * lambda[ci.value] + return _sense_dual(model) * _get_dual(model.inner, ci.value) end # function MOI.get( @@ -1595,7 +1610,7 @@ function _reduced_cost( x = MOI.VariableIndex(ci.value) MOI.throw_if_not_valid(model, x) offset = _number_constraints(model) - return _sense_dual(model) * get_dual(model.inner, x.value + offset) + return _sense_dual(model) * _get_dual(model.inner, x.value + offset) end function MOI.get( @@ -1626,11 +1641,8 @@ function MOI.get(model::Optimizer, ::MOI.NLPBlockDual) if model.number_solved == 0 error("NLPBlockDual not available.") end - # Get first index corresponding to a non-linear constraint: - lambda = get_dual(model.inner) - # FIXME: Assume that lambda has same sense as for linear - # and quadratic constraint, but this is not tested inside MOI. - return _sense_dual(model) .* [lambda[i+1] for i in model.nlp_index_cons] + sign = _sense_dual(model) + return [sign * _get_dual(model.inner, i + 1) for i in model.nlp_index_cons] end function MOI.get(model::Optimizer, ::MOI.SolveTimeSec) diff --git a/test/C_wrapper.jl b/test/C_wrapper.jl index bc7e3fd..429015b 100644 --- a/test/C_wrapper.jl +++ b/test/C_wrapper.jl @@ -143,20 +143,22 @@ if KNITRO.KNITRO_VERSION >= v"12.1" KNITRO.KN_set_param(kc, "outlev", 0) KNITRO.KN_write_mps_file(kc, mps_name_out) status = KNITRO.KN_solve(kc) - obj = KNITRO.get_objective(kc) + obj = Ref{Cdouble}(0.0) + KNITRO.KN_get_solution(kc, Ref{Cint}(), obj, C_NULL, C_NULL) KNITRO.KN_free(kc) @test status == 0 - @test obj ≈ 250.0 / 3.0 + @test obj[] ≈ 250.0 / 3.0 # Resolve with dumped MPS file kc = KNITRO.KN_new() KNITRO.KN_load_mps_file(kc, mps_name_out) KNITRO.KN_set_param(kc, "outlev", 0) status = KNITRO.KN_solve(kc) - obj = KNITRO.get_objective(kc) + obj = Ref{Cdouble}(0.0) + KNITRO.KN_get_solution(kc, Ref{Cint}(), obj, C_NULL, C_NULL) KNITRO.KN_free(kc) @test status == 0 - @test obj ≈ 250.0 / 3.0 + @test obj[] ≈ 250.0 / 3.0 end end diff --git a/test/knitroapi_licman.jl b/test/knitroapi_licman.jl index 13bf2b8..e631cb6 100644 --- a/test/knitroapi_licman.jl +++ b/test/knitroapi_licman.jl @@ -37,14 +37,15 @@ end # setting a string userparam in Knitro. for userparam in [nothing, "myuserparam"] kc = KNITRO.KN_new_lm(lm) - KNITRO.KN_set_param(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) # At first, `newpoint_user` is nothing @test isnothing(kc.newpoint_user) - KNITRO.KN_add_vars(kc, 1) + p = Ref{Cint}() + KNITRO.KN_add_var(kc, p) KNITRO.KN_set_var_lobnd(kc, Cint(0), 2.0) KNITRO.KN_set_var_primal_init_values_all(kc, [0.0]) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) - KNITRO.KN_add_obj_quadratic_struct(kc, Cint[0], Cint[0], [1.0]) + KNITRO.KN_add_obj_quadratic_struct(kc, 1, Cint[0], Cint[0], [1.0]) KNITRO.KN_set_newpt_callback(kc, callbackNewPoint, userparam) # Test that userparam is correctly set @test kc.newpoint_user == userparam From cbb1c9cb481065b52a24b443c436ece636da57d2 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 11:11:23 +1300 Subject: [PATCH 05/15] Updates --- src/C_wrapper.jl | 268 ++++++++++++++++++++--------------------------- 1 file changed, 111 insertions(+), 157 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index dcb2cd5..fe34652 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -11,38 +11,24 @@ function get_release() return String(strip(String(convert(Vector{UInt8}, out)), '\0')) end -"Wrapper for KNITRO KN_context." mutable struct Env ptr_env::Ptr{Cvoid} +end - function Env() - ptrptr_env = Ref{Ptr{Cvoid}}() - res = KN_new(ptrptr_env) - if res != 0 - error("Fail to retrieve a valid KNITRO KN_context. Error $res") - end - return new(ptrptr_env[]) +function Env() + ptrptr_env = Ref{Ptr{Cvoid}}() + res = KN_new(ptrptr_env) + if res != 0 + error("Fail to retrieve a valid KNITRO KN_context. Error $res") end - - Env(ptr::Ptr{Cvoid}) = new(ptr) + return Env(ptrptr_env[]) end +Base.cconvert(::Type{Ptr{Cvoid}}, env::Env) = env Base.unsafe_convert(::Type{Ptr{Cvoid}}, env::Env) = env.ptr_env::Ptr{Cvoid} is_valid(env::Env) = env.ptr_env != C_NULL -""" -Free all memory and release any Knitro license acquired by calling KN_new. -""" -function free_env(env::Env) - if env.ptr_env != C_NULL - ptrptr_env = Ref{Ptr{Cvoid}}(env.ptr_env) - KN_free(ptrptr_env) - env.ptr_env = C_NULL - end - return -end - """ Structure specifying the callback context. @@ -68,6 +54,7 @@ mutable struct CallbackContext end end +Base.cconvert(::Type{Ptr{Cvoid}}, cb::CallbackContext) = cb Base.unsafe_convert(::Type{Ptr{Cvoid}}, cb::CallbackContext) = cb.context::Ptr{Cvoid} mutable struct Model @@ -97,25 +84,6 @@ mutable struct Model ms_initpt_callback::Function puts_callback::Function - # Constructor. - function Model() - model = new( - Env(), - CallbackContext[], - nothing, - nothing, - nothing, - nothing, - 1, - Inf, - Cdouble[], - Cdouble[], - ) - # Add a destructor to properly delete model. - finalizer(KN_free, model) - return model - end - # Instantiate a new Knitro instance in current environment `env`. function Model(env::Env) return new( env, @@ -132,10 +100,23 @@ mutable struct Model end end +function Model() + model = Model(Env()) + finalizer(KN_free, model) + return model +end + +Base.cconvert(::Type{Ptr{Cvoid}}, model::Model) = model Base.unsafe_convert(::Type{Ptr{Cvoid}}, kn::Model) = kn.env.ptr_env::Ptr{Cvoid} "Free solver object." -KN_free(m::Model) = free_env(m.env) +function KN_free(m::Model) + if m.env.ptr_env != C_NULL + KN_free(Ref(m.env.ptr_env)) + m.env.ptr_env = C_NULL + end + return +end "Create solver object." KN_new() = Model() @@ -144,8 +125,6 @@ is_valid(m::Model) = is_valid(m.env) has_callbacks(m::Model) = !isempty(m.callbacks) -register_callback(model::Model, cb::CallbackContext) = push!(model.callbacks, cb) - function Base.show(io::IO, m::Model) if !is_valid(m) println(io, "KNITRO Problem: NULL") @@ -197,6 +176,7 @@ mutable struct LMcontext end end +Base.cconvert(::Type{Ptr{Cvoid}}, lm::LMcontext) = lm Base.unsafe_convert(::Type{Ptr{Cvoid}}, lm::LMcontext) = lm.ptr_lmcontext::Ptr{Cvoid} function Env(lm::LMcontext) @@ -208,15 +188,9 @@ function Env(lm::LMcontext) return Env(ptrptr_env[]) end -function attach!(lm::LMcontext, model::Model) - push!(lm.linked_models, model) - return -end - -# create Model with license manager function Model(lm::LMcontext) model = Model(Env(lm)) - attach!(lm, model) + push!(lm.linked_models, model) return model end @@ -254,10 +228,6 @@ function KN_solve(m::Model) return m.status end -#= - GETTERS -=# - function KN_get_solution(m::Model) @assert m.env != C_NULL p = Ref{Cint}(0) @@ -278,28 +248,23 @@ end # Callbacks utilities. -# Note: we store here the number of constraints and variables defined -# in the original Knitro model. We cannot retrieve these numbers during -# callbacks invokation, as sometimes the number of variables and constraints -# change internally in Knitro (e.g. when cuts are added when resolving -# Branch&Bound). We prefer to use the number of variables and constraints -# of the original model so that user's callbacks could consider that -# the arrays of primal variable x and dual variable \lambda have fixed -# sizes. -function link!(cb::CallbackContext, model::Model) +function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing) + if userParams != nothing + cb.userparams = userParams + end + # Note: we store here the number of constraints and variables defined + # in the original Knitro model. We cannot retrieve these numbers during + # callbacks invokation, as sometimes the number of variables and constraints + # change internally in Knitro (e.g. when cuts are added when resolving + # Branch&Bound). We prefer to use the number of variables and constraints + # of the original model so that user's callbacks could consider that + # the arrays of primal variable x and dual variable \lambda have fixed + # sizes. p = Ref{Cint}(0) KN_get_number_vars(model, p) cb.n = p[] KN_get_number_cons(model, p) cb.m = p[] - return -end - -function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing) - if userParams != nothing - cb.userparams = userParams - end - link!(cb, m) # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} ccall( (:KN_set_cb_user_params, libknitro), @@ -330,19 +295,17 @@ mutable struct EvalRequest lambda::Array{Float64} sigma::Float64 vec::Array{Float64} -end -function EvalRequest(ptr_model::Ptr{Cvoid}, evalRequest_::KN_eval_request, n::Int, m::Int) - sigma = - (evalRequest_.sigma != C_NULL) ? unsafe_wrap(Array, evalRequest_.sigma, 1)[1] : 1.0 - return EvalRequest( - evalRequest_.type, - evalRequest_.threadID, - unsafe_wrap(Array, evalRequest_.x, n), - unsafe_wrap(Array, evalRequest_.lambda, n + m), - sigma, - unsafe_wrap(Array, evalRequest_.vec, n), - ) + function EvalRequest(::Ptr{Cvoid}, request::KN_eval_request, n::Int, m::Int) + return new( + request.type, + request.threadID, + unsafe_wrap(Array, request.x, n), + unsafe_wrap(Array, request.lambda, n + m), + request.sigma == C_NULL ? 1.0 : unsafe_wrap(Array, request.sigma, 1)[1], + unsafe_wrap(Array, request.vec, n), + ) + end end mutable struct EvalResult @@ -354,43 +317,43 @@ mutable struct EvalResult hessVec::Array{Float64} rsd::Array{Float64} rsdJac::Array{Float64} -end -function EvalResult( - kc::Ptr{Cvoid}, - cb::Ptr{Cvoid}, - evalResult_::KN_eval_result, - n::Int, - m::Int, -) - objgrad_nnz = Ref{Cint}() - jacobian_nnz = Ref{KNLONG}() - hessian_nnz = Ref{KNLONG}() - num_rsds = Ref{Cint}() - rsd_jacobian_nnz = Ref{KNLONG}() - KN_get_cb_objgrad_nnz(kc, cb, objgrad_nnz) - KN_get_cb_jacobian_nnz(kc, cb, jacobian_nnz) - KN_get_cb_hessian_nnz(kc, cb, hessian_nnz) - KN_get_cb_number_rsds(kc, cb, num_rsds) - KN_get_cb_rsd_jacobian_nnz(kc, cb, rsd_jacobian_nnz) - return EvalResult( - unsafe_wrap(Array, evalResult_.obj, 1), - unsafe_wrap(Array, evalResult_.c, m), - unsafe_wrap(Array, evalResult_.objGrad, objgrad_nnz[]), - unsafe_wrap(Array, evalResult_.jac, jacobian_nnz[]), - unsafe_wrap(Array, evalResult_.hess, hessian_nnz[]), - unsafe_wrap(Array, evalResult_.hessVec, n), - unsafe_wrap(Array, evalResult_.rsd, num_rsds[]), - unsafe_wrap(Array, evalResult_.rsdJac, rsd_jacobian_nnz[]), + function EvalResult( + kc::Ptr{Cvoid}, + cb::Ptr{Cvoid}, + result::KN_eval_result, + n::Int, + m::Int, ) + objgrad_nnz = Ref{Cint}() + jacobian_nnz = Ref{KNLONG}() + hessian_nnz = Ref{KNLONG}() + num_rsds = Ref{Cint}() + rsd_jacobian_nnz = Ref{KNLONG}() + KN_get_cb_objgrad_nnz(kc, cb, objgrad_nnz) + KN_get_cb_jacobian_nnz(kc, cb, jacobian_nnz) + KN_get_cb_hessian_nnz(kc, cb, hessian_nnz) + KN_get_cb_number_rsds(kc, cb, num_rsds) + KN_get_cb_rsd_jacobian_nnz(kc, cb, rsd_jacobian_nnz) + return new( + unsafe_wrap(Array, result.obj, 1), + unsafe_wrap(Array, result.c, m), + unsafe_wrap(Array, result.objGrad, objgrad_nnz[]), + unsafe_wrap(Array, result.jac, jacobian_nnz[]), + unsafe_wrap(Array, result.hess, hessian_nnz[]), + unsafe_wrap(Array, result.hessVec, n), + unsafe_wrap(Array, result.rsd, num_rsds[]), + unsafe_wrap(Array, result.rsdJac, rsd_jacobian_nnz[]), + ) + end end for (wrap_name, name) in [ - :eval_fc_wrapper => :eval_f, - :eval_ga_wrapper => :eval_g, - :eval_hess_wrapper => :eval_h, - :eval_rj_wrapper => :eval_jac_rsd, - :eval_rsd_wrapper => :eval_rsd, + :_eval_fc_wrapper => :eval_f, + :_eval_ga_wrapper => :eval_g, + :_eval_hess_wrapper => :eval_h, + :_eval_rj_wrapper => :eval_jac_rsd, + :_eval_rsd_wrapper => :eval_rsd, ] @eval begin function $wrap_name( @@ -405,10 +368,7 @@ for (wrap_name, name) in [ evalRequest = unsafe_load(ptr_request)::KN_eval_request ptr_result = Ptr{KN_eval_result}(evalResults_) evalResult = unsafe_load(ptr_result)::KN_eval_result - cb = unsafe_pointer_to_objref(userdata_) - if !isa(cb, CallbackContext) - return KN_RC_CALLBACK_ERR - end + cb = unsafe_pointer_to_objref(userdata_)::CallbackContext request = EvalRequest(ptr_model, evalRequest, cb.n, cb.m) result = EvalResult(ptr_model, ptr_cb, evalResult, cb.n, cb.m) return cb.$name(ptr_model, ptr_cb, request, result, cb.userparams) @@ -468,14 +428,14 @@ detailed below. """ function KN_add_eval_callback_all(m::Model, funccallback::Function) c_f = @cfunction( - eval_fc_wrapper, + _eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) rfptr = Ref{Ptr{Cvoid}}() KN_add_eval_callback_all(m.env, c_f, rfptr) cb = CallbackContext(rfptr[]) - register_callback(m, cb) + push!(m.callbacks, cb) cb.eval_f = funccallback KN_set_cb_user_params(m, cb) return cb @@ -483,14 +443,14 @@ end function KN_add_objective_callback(m::Model, objcallback::Function) c_f = @cfunction( - eval_fc_wrapper, + _eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) rfptr = Ref{Ptr{Cvoid}}() KN_add_eval_callback_one(m.env, Cint(-1), c_f, rfptr) cb = CallbackContext(rfptr[]) - register_callback(m, cb) + push!(m.callbacks, cb) cb.eval_f = objcallback KN_set_cb_user_params(m, cb) return cb @@ -503,15 +463,15 @@ function KN_add_eval_callback( funccallback::Function, ) nC = length(indexCons) - c_f = @cfunction( - eval_fc_wrapper, + c_f = @cfunction( + _eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) rfptr = Ref{Ptr{Cvoid}}() KN_add_eval_callback(m.env, KNBOOL(evalObj), nC, indexCons, c_f, rfptr) cb = CallbackContext(rfptr[]) - register_callback(m, cb) + push!(m.callbacks, cb) cb.eval_f = funccallback KN_set_cb_user_params(m, cb) return cb @@ -637,7 +597,7 @@ function KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) rfptr = Ref{Ptr{Cvoid}}() KN_add_lsq_eval_callback_all(m.env, c_f, rfptr) cb = CallbackContext(rfptr[]) - register_callback(m, cb) + push!(m.callbacks, cb) cb.eval_rsd = rsdCallBack KN_set_cb_user_params(m, cb) return @@ -747,24 +707,21 @@ function _ms_process_wrapper( ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, userdata_::Ptr{Cvoid}, -) +)::Cint try - m = unsafe_pointer_to_objref(userdata_)::Model - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - x = unsafe_wrap(Array, ptr_x, nx) - lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) - res = m.ms_process(m, x, lambda, m.multistart_user) - return Cint(res) + model = unsafe_pointer_to_objref(userdata_)::Model + nx, nc = Ref{Cint}(0), Ref{Cint}(0) + KN_get_number_vars(model, nx) + KN_get_number_cons(model, nc) + x = unsafe_wrap(Array, ptr_x, nx[]) + lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) + return model.ms_process(model, x, lambda, m.multistart_user) catch ex if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) + return KN_RC_USER_TERMINATION else @warn("Knitro encounters an exception in multistart callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) + return KN_RC_CALLBACK_ERR end end end @@ -816,22 +773,21 @@ function _mip_node_callback_wrapper( ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, user_data::Ptr{Cvoid}, -) +)::Cint try - m = unsafe_pointer_to_objref(user_data)::Model + model = unsafe_pointer_to_objref(user_data)::Model nx, nc = Ref{Cint}(0), Ref{Cint}(0) - KN_get_number_vars(m, nx) - KN_get_number_cons(m, nc) + KN_get_number_vars(model, nx) + KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - res = m.mip_callback(m, x, lambda, m.mip_user) - return Cint(res) + return model.mip_callback(model, x, lambda, m.mip_user) catch ex if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) + return KN_RC_USER_TERMINATION else @warn("Knitro encounters an exception in MIP callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) + return KN_RC_CALLBACK_ERR end end end @@ -884,15 +840,14 @@ function _ms_initpt_wrapper( ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, user_data::Ptr{Cvoid}, -) +)::Cint model = unsafe_pointer_to_objref(user_data)::Model nx, nc = Ref{Cint}(0), Ref{Cint}(0) KN_get_number_vars(model, nx) KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - res = model.ms_initpt_callback(model, nSolveNumber, x, lambda, m.multistart_user) - return Cint(res) + return model.ms_initpt_callback(model, nSolveNumber, x, lambda, m.multistart_user) end """ @@ -935,17 +890,16 @@ end # KN_set_puts_callback -function _puts_callback_wrapper(str::Ptr{Cchar}, user_data::Ptr{Cvoid}) +function _puts_callback_wrapper(str::Ptr{Cchar}, user_data::Ptr{Cvoid})::Cint try model = unsafe_pointer_to_objref(user_data)::Model - res = model.puts_callback(unsafe_string(str), model.puts_user) - return Cint(res) + return model.puts_callback(unsafe_string(str), model.puts_user) catch ex if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) + return KN_RC_USER_TERMINATION else @warn("Knitro encounters an exception in puts callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) + return KN_RC_CALLBACK_ERR end end end From f2f8ceb4ba436d0163c95629a9faafebf2994489 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 11:12:50 +1300 Subject: [PATCH 06/15] Update --- src/C_wrapper.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index fe34652..58b90e0 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -514,7 +514,7 @@ function KN_set_cb_grad( if gradcallback != nothing cb.eval_g = gradcallback c_grad_g = @cfunction( - eval_ga_wrapper, + _eval_ga_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) @@ -562,7 +562,7 @@ function KN_set_cb_hess( end cb.eval_h = hesscallback c_hess = @cfunction( - eval_hess_wrapper, + _eval_hess_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) @@ -590,7 +590,7 @@ efficiency of the finite-difference Jacobian approximation. """ function KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) c_f = @cfunction( - eval_rsd_wrapper, + _eval_rsd_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) @@ -619,7 +619,7 @@ function KN_set_cb_rsd_jac( end cb.eval_jac_rsd = evalRJ c_eval_rj = @cfunction( - eval_rj_wrapper, + _eval_rj_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) From c578085ec9468cfc960d3e32f835f5b5b274b670 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 12:16:35 +1300 Subject: [PATCH 07/15] Update --- src/C_wrapper.jl | 429 ++++++++++++++++++----------------------------- 1 file changed, 163 insertions(+), 266 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index 58b90e0..4149a74 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -40,7 +40,7 @@ mutable struct CallbackContext n::Int m::Int # Add a dictionnary to store user params. - userparams::Any + user_data::Any # Oracle's callbacks are context dependent, so store # them inside dedicated CallbackContext. eval_f::Function @@ -62,7 +62,7 @@ mutable struct Model env::Env # Keep reference to callbacks for garbage collector. callbacks::Vector{CallbackContext} - # Some structures for userParams + # Some structures for user_data puts_user::Any multistart_user::Any mip_user::Any @@ -110,10 +110,10 @@ Base.cconvert(::Type{Ptr{Cvoid}}, model::Model) = model Base.unsafe_convert(::Type{Ptr{Cvoid}}, kn::Model) = kn.env.ptr_env::Ptr{Cvoid} "Free solver object." -function KN_free(m::Model) - if m.env.ptr_env != C_NULL - KN_free(Ref(m.env.ptr_env)) - m.env.ptr_env = C_NULL +function KN_free(model::Model) + if model.env.ptr_env != C_NULL + KN_free(Ref(model.env.ptr_env)) + model.env.ptr_env = C_NULL end return end @@ -121,12 +121,12 @@ end "Create solver object." KN_new() = Model() -is_valid(m::Model) = is_valid(m.env) +is_valid(model::Model) = is_valid(model.env) -has_callbacks(m::Model) = !isempty(m.callbacks) +has_callbacks(model::Model) = !isempty(model.callbacks) -function Base.show(io::IO, m::Model) - if !is_valid(m) +function Base.show(io::IO, model::Model) + if !is_valid(model) println(io, "KNITRO Problem: NULL") return end @@ -136,16 +136,16 @@ function Base.show(io::IO, m::Model) println(io, "-----------------------") println(io, "Objective goal: Minimize") p = Ref{Cint}() - KN_get_obj_type(m, p) + KN_get_obj_type(model, p) println(io, "Objective type: $(p[])") - KN_get_number_vars(m, p) + KN_get_number_vars(model, p) println(io, "Number of variables: $(p[])") - KN_get_number_cons(m, p) + KN_get_number_cons(model, p) println(io, "Number of constraints: $(p[])") q = Ref{KNLONG}() - KN_get_jacobian_nnz(m, q) + KN_get_jacobian_nnz(model, q) println(io, "Number of nonzeros in Jacobian: $(q[])") - KN_get_hessian_nnz(m, q) + KN_get_hessian_nnz(model, q) println(io, "Number of nonzeros in Hessian: $(q[])") return end @@ -208,50 +208,44 @@ function KN_release_license(lm::LMcontext) return end -function KN_solve(m::Model) +function KN_solve(model::Model) # Check sanity. If model has Julia callbacks, we need to ensure # that Knitro is not multithreaded. Otherwise, the code will segfault # as we have trouble calling Julia code from multithreaded C # code. See issue #93 on https://github.com/jump-dev/KNITRO.jl. - if has_callbacks(m) + if has_callbacks(model) if KNITRO_VERSION >= v"13.0" - KN_set_int_param(m, KN_PARAM_MS_NUMTHREADS, 1) - KN_set_int_param(m, KN_PARAM_NUMTHREADS, 1) - KN_set_int_param(m, KN_PARAM_MIP_NUMTHREADS, 1) + KN_set_int_param(model, KN_PARAM_MS_NUMTHREADS, 1) + KN_set_int_param(model, KN_PARAM_NUMTHREADS, 1) + KN_set_int_param(model, KN_PARAM_MIP_NUMTHREADS, 1) else - KN_set_int_param_by_name(m, "par_numthreads", 1) - KN_set_int_param_by_name(m, "par_msnumthreads", 1) + KN_set_int_param_by_name(model, "par_numthreads", 1) + KN_set_int_param_by_name(model, "par_msnumthreads", 1) end end # For KN_solve, we do not return an error if ret is different of 0. - m.status = KN_solve(m.env) - return m.status + model.status = KN_solve(model.env) + return model.status end -function KN_get_solution(m::Model) - @assert m.env != C_NULL - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - x = zeros(Cdouble, nx) - lambda = zeros(Cdouble, nx + nc) +function KN_get_solution(model::Model) + @assert model.env != C_NULL + nx, nc = Ref{Cint}(0), Ref{Cint}(0) + KN_get_number_vars(model, nx) + KN_get_number_cons(model, nc) + model.x = zeros(Cdouble, nx[]) + model.mult = zeros(Cdouble, nx[] + nc[]) status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) - KN_get_solution(m, status, obj, x, lambda) - m.status = status[] - m.x = x - m.mult = lambda - m.obj_val = obj[] - return status[], obj[], x, lambda + KN_get_solution(model, status, obj, model.x, model.mult) + model.status = status[] + model.obj_val = obj[] + return status[], obj[], model.x, model.mult end # Callbacks utilities. -function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing) - if userParams != nothing - cb.userparams = userParams - end +function KN_set_cb_user_params(model::Model, cb::CallbackContext, user_data=nothing) + cb.user_data = user_data # Note: we store here the number of constraints and variables defined # in the original Knitro model. We cannot retrieve these numbers during # callbacks invokation, as sometimes the number of variables and constraints @@ -265,26 +259,7 @@ function KN_set_cb_user_params(m::Model, cb::CallbackContext, userParams=nothing cb.n = p[] KN_get_number_cons(model, p) cb.m = p[] - # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} - ccall( - (:KN_set_cb_user_params, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, Any), - m.env, - cb, - cb, - ) - return -end - -""" -Specify which gradient option `gradopt` will be used to evaluate -the first derivatives of the callback functions. If `gradopt=KN_GRADOPT_EXACT` -then a gradient evaluation callback must be set by `KN_set_cb_grad()` -(or `KN_set_cb_rsd_jac()` for least squares). -""" -function KN_set_cb_gradopt(m::Model, cb::CallbackContext, gradopt::Integer) - KN_set_cb_gradopt(m.env, cb, gradopt) + KN_set_cb_user_params(model.env, cb, pointer_from_objref(cb)) return end @@ -348,6 +323,21 @@ mutable struct EvalResult end end +function _try_catch_handler(f::F) where {F<:Function} + try + return f() + catch ex + if ex isa InterruptException + return KN_RC_USER_TERMINATION + elseif ex isa DomainError + return KN_RC_EVAL_ERR + else + @warn("Knitro encounters an exception in puts callback: $ex") + return KN_RC_CALLBACK_ERR + end + end +end + for (wrap_name, name) in [ :_eval_fc_wrapper => :eval_f, :_eval_ga_wrapper => :eval_g, @@ -359,36 +349,25 @@ for (wrap_name, name) in [ function $wrap_name( ptr_model::Ptr{Cvoid}, ptr_cb::Ptr{Cvoid}, - evalRequest_::Ptr{Cvoid}, - evalResults_::Ptr{Cvoid}, - userdata_::Ptr{Cvoid}, + ptr_eval_request::Ptr{Cvoid}, + ptr_eval_results::Ptr{Cvoid}, + user_data::Ptr{Cvoid}, )::Cint - try - ptr_request = Ptr{KN_eval_request}(evalRequest_) - evalRequest = unsafe_load(ptr_request)::KN_eval_request - ptr_result = Ptr{KN_eval_result}(evalResults_) - evalResult = unsafe_load(ptr_result)::KN_eval_result - cb = unsafe_pointer_to_objref(userdata_)::CallbackContext - request = EvalRequest(ptr_model, evalRequest, cb.n, cb.m) - result = EvalResult(ptr_model, ptr_cb, evalResult, cb.n, cb.m) - return cb.$name(ptr_model, ptr_cb, request, result, cb.userparams) - catch ex - if isa(ex, InterruptException) - return KN_RC_USER_TERMINATION - elseif isa(ex, DomainError) - return KN_RC_EVAL_ERR - else - @warn("Knitro encounters an exception in evaluation callback: $ex") - return KN_RC_CALLBACK_ERR - end + return _try_catch_handler() do + eval_request = unsafe_load(Ptr{KN_eval_request}(ptr_eval_request)) + eval_result = unsafe_load(Ptr{KN_eval_result}(ptr_eval_results)) + cb = unsafe_pointer_to_objref(user_data)::CallbackContext + request = EvalRequest(ptr_model, eval_request, cb.n, cb.m) + result = EvalResult(ptr_model, ptr_cb, eval_result, cb.n, cb.m) + return cb.$name(ptr_model, ptr_cb, request, result, cb.user_data) end end end end """ - KN_add_eval_callback(m::Model, funccallback::Function) - KN_add_eval_callback(m::Model, evalObj::Bool, indexCons::Vector{Cint}, + KN_add_eval_callback(model::Model, funccallback::Function) + KN_add_eval_callback(model::Model, evalObj::Bool, indexCons::Vector{Cint}, funccallback::Function) This is the routine for adding a callback for (nonlinear) evaluations @@ -426,59 +405,58 @@ improve the efficiency of the finite-difference gradient approximations. Other optional information can also be set via `KN_set_cb_*()` functions as detailed below. """ -function KN_add_eval_callback_all(m::Model, funccallback::Function) - c_f = @cfunction( +function KN_add_eval_callback_all(model::Model, callback::Function) + c_func = @cfunction( _eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) rfptr = Ref{Ptr{Cvoid}}() - KN_add_eval_callback_all(m.env, c_f, rfptr) + KN_add_eval_callback_all(model, c_f, rfptr) cb = CallbackContext(rfptr[]) - push!(m.callbacks, cb) - cb.eval_f = funccallback - KN_set_cb_user_params(m, cb) + push!(model.callbacks, cb) + cb.eval_f = callback + KN_set_cb_user_params(model, cb) return cb end -function KN_add_objective_callback(m::Model, objcallback::Function) - c_f = @cfunction( +function KN_add_objective_callback(model::Model, callback::Function) + c_func = @cfunction( _eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) rfptr = Ref{Ptr{Cvoid}}() - KN_add_eval_callback_one(m.env, Cint(-1), c_f, rfptr) + KN_add_eval_callback_one(model, Cint(-1), c_func, rfptr) cb = CallbackContext(rfptr[]) - push!(m.callbacks, cb) - cb.eval_f = objcallback - KN_set_cb_user_params(m, cb) + push!(model.callbacks, cb) + cb.eval_f = callback + KN_set_cb_user_params(model, cb) return cb end function KN_add_eval_callback( - m::Model, + model::Model, evalObj::Bool, indexCons::Vector{Cint}, - funccallback::Function, + callback::Function, ) - nC = length(indexCons) - c_f = @cfunction( + c_func = @cfunction( _eval_fc_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) rfptr = Ref{Ptr{Cvoid}}() - KN_add_eval_callback(m.env, KNBOOL(evalObj), nC, indexCons, c_f, rfptr) + KN_add_eval_callback(model, evalObj, length(indexCons), indexCons, c_func, rfptr) cb = CallbackContext(rfptr[]) - push!(m.callbacks, cb) - cb.eval_f = funccallback - KN_set_cb_user_params(m, cb) + push!(model.callbacks, cb) + cb.eval_f = callback + KN_set_cb_user_params(model, cb) return cb end """ - KN_set_cb_grad(m::Model, cb::CallbackContext, gradcallback; + KN_set_cb_grad(model::Model, cb::CallbackContext, gradcallback; nV::Integer=KN_DENSE, objGradIndexVars=C_NULL, jacIndexCons=C_NULL, jacIndexVars=C_NULL) @@ -487,9 +465,9 @@ structure and also (optionally) a callback function to evaluate the objective gradient and constraint Jacobian provided through this callback. """ function KN_set_cb_grad( - m::Model, + model::Model, cb::CallbackContext, - gradcallback; + callback; nV::Integer=KN_DENSE, nnzJ::Union{Nothing,Integer}=nothing, objGradIndexVars=C_NULL, @@ -498,12 +476,13 @@ function KN_set_cb_grad( ) if nnzJ === nothing p = Ref{Cint}(0) - KN_get_number_cons(m, p) + KN_get_number_cons(model, p) nnzJ = iszero(p[]) ? KNLONG(0) : KNITRO.KN_DENSE_COLMAJOR end if (nV == 0 || nV == KN_DENSE) - (objGradIndexVars != C_NULL) && + if objGradIndexVars != C_NULL error("objGradIndexVars must be set to C_NULL when nV = $nV") + end else @assert (objGradIndexVars != C_NULL) && (length(objGradIndexVars) == nV) end @@ -511,32 +490,37 @@ function KN_set_cb_grad( @assert length(jacIndexCons) == length(jacIndexVars) nnzJ = KNLONG(length(jacIndexCons)) end - if gradcallback != nothing - cb.eval_g = gradcallback - c_grad_g = @cfunction( + c_func = C_NULL + if callback !== nothing + cb.eval_g = callback + c_func = @cfunction( _eval_ga_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) - else - c_grad_g = C_NULL end KN_set_cb_grad( - m.env, + model, cb, nV, objGradIndexVars, - KNLONG(nnzJ), + nnzJ, jacIndexCons, jacIndexVars, - c_grad_g, + c_func, ) return end """ - KN_set_cb_hess(m::Model, cb::CallbackContext, nnzH::Integer, hesscallback::Function; - hessIndexVars1=C_NULL, hessIndexVars2=C_NULL) + KN_set_cb_hess( + model::Model, + cb::CallbackContext, + nnzH::Integer, + callback::Function; + hessIndexVars1=C_NULL, + hessIndexVars2=C_NULL, + ) This API function is used to set the structure and a callback function to evaluate the components of the Hessian of the Lagrangian provided through this @@ -544,13 +528,12 @@ callback. KN_set_cb_hess() should only be used when defining a user-supplied Hessian callback function (via the `hessopt=KN_HESSOPT_EXACT` user option). When Knitro is approximating the Hessian, it cannot make use of the Hessian sparsity structure. - """ function KN_set_cb_hess( - m::Model, + model::Model, cb::CallbackContext, nnzH::Integer, - hesscallback::Function; + callback::Function; hessIndexVars1=C_NULL, hessIndexVars2=C_NULL, ) @@ -560,24 +543,24 @@ function KN_set_cb_hess( @assert hessIndexVars1 != C_NULL && hessIndexVars2 != C_NULL @assert length(hessIndexVars1) == length(hessIndexVars2) == nnzH end - cb.eval_h = hesscallback - c_hess = @cfunction( + cb.eval_h = callback + c_func = @cfunction( _eval_hess_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) - KN_set_cb_hess(m.env, cb, nnzH, hessIndexVars1, hessIndexVars2, c_hess) + KN_set_cb_hess(model, cb, nnzH, hessIndexVars1, hessIndexVars2, c_func) return end """ - KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) + KN_add_lsq_eval_callback(model::Model, callback::Function) Add an evaluation callback for a least-squares models. Similar to KN_add_eval_callback() above, but for least-squares models. -* `m`: current KNITRO model -* `rsdCallback`: a function that evaluates any residual parts +* `model`: current KNITRO model +* `callback`: a function that evaluates any residual parts After a callback is created by `KN_add_lsq_eval_callback()`, the user can then specify residual Jacobian information and structure through `KN_set_cb_rsd_jac()`. @@ -588,26 +571,26 @@ for the residual Jacobian is not provided, it is still helpful to provide the sp Jacobian structure for the residuals through `KN_set_cb_rsd_jac()` to improve the efficiency of the finite-difference Jacobian approximation. """ -function KN_add_lsq_eval_callback(m::Model, rsdCallBack::Function) - c_f = @cfunction( +function KN_add_lsq_eval_callback(model::Model, callback::Function) + c_func = @cfunction( _eval_rsd_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) rfptr = Ref{Ptr{Cvoid}}() - KN_add_lsq_eval_callback_all(m.env, c_f, rfptr) + KN_add_lsq_eval_callback_all(model, c_func, rfptr) cb = CallbackContext(rfptr[]) - push!(m.callbacks, cb) - cb.eval_rsd = rsdCallBack - KN_set_cb_user_params(m, cb) + push!(model.callbacks, cb) + cb.eval_rsd = callback + KN_set_cb_user_params(model, cb) return end function KN_set_cb_rsd_jac( - m::Model, + model::Model, cb::CallbackContext, nnzJ::Integer, - evalRJ::Function; + callback::Function; jacIndexRsds=C_NULL, jacIndexVars=C_NULL, ) @@ -617,13 +600,13 @@ function KN_set_cb_rsd_jac( @assert jacIndexRsds != C_NULL && jacIndexVars != C_NULL @assert length(jacIndexRsds) == length(jacIndexVars) == nnzJ end - cb.eval_jac_rsd = evalRJ - c_eval_rj = @cfunction( + cb.eval_jac_rsd = callback + c_func = @cfunction( _eval_rj_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) - KN_set_cb_rsd_jac(m.env, cb, nnzJ, jacIndexRsds, jacIndexVars, c_eval_rj) + KN_set_cb_rsd_jac(model, cb, nnzJ, jacIndexRsds, jacIndexVars, c_func) return end @@ -633,31 +616,21 @@ function _newpt_wrapper( ptr_model::Ptr{Cvoid}, ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, - userdata_::Ptr{Cvoid}, -) - try - m = unsafe_pointer_to_objref(userdata_)::Model - p = Ref{Cint}(0) - KN_get_number_vars(m, p) - nx = p[] - KN_get_number_cons(m, p) - nc = p[] - x = unsafe_wrap(Array, ptr_x, nx) - lambda = unsafe_wrap(Array, ptr_lambda, nx + nc) - ret = m.newpt_callback(m, x, lambda, m.newpoint_user) - return Cint(ret) - catch ex - if isa(ex, InterruptException) - return Cint(KN_RC_USER_TERMINATION) - else - @warn("Knitro encounters an exception in newpoint callback: $ex") - return Cint(KN_RC_CALLBACK_ERR) - end + user_data::Ptr{Cvoid}, +)::Cint + return _try_catch_handler() do + model = unsafe_pointer_to_objref(user_data)::Model + nx, nc = Ref{Cint}(0), Ref{Cint}(0) + KN_get_number_vars(model, nx) + KN_get_number_cons(model, nc) + x = unsafe_wrap(Array, ptr_x, nx[]) + lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) + return model.newpt_callback(model, x, lambda, model.newpoint_user) end end """ - KN_set_newpt_callback(m::Model, callback::Function) + KN_set_newpt_callback(model::Model, callback::Function) Set the callback function that is invoked after Knitro computes a new estimate of the solution point (i.e., after every iteration). @@ -678,25 +651,14 @@ queried using the corresonding KN_get_XXX_values methods. Note: Currently only active for continuous models. """ -function KN_set_newpt_callback(m::Model, callback::Function, userparams=nothing) - m.newpt_callback = callback - if userparams != nothing - m.newpoint_user = userparams - end +function KN_set_newpt_callback(model::Model, callback::Function, user_data=nothing) + model.newpt_callback, model.newpoint_user = callback, user_data c_func = @cfunction( _newpt_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} - ccall( - (:KN_set_newpt_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, Any), - m.env, - c_func, - m, - ) + KN_set_newpt_callback(model, c_func, pointer_from_objref(model)) return end @@ -706,28 +668,21 @@ function _ms_process_wrapper( ptr_model::Ptr{Cvoid}, ptr_x::Ptr{Cdouble}, ptr_lambda::Ptr{Cdouble}, - userdata_::Ptr{Cvoid}, + user_data::Ptr{Cvoid}, )::Cint - try - model = unsafe_pointer_to_objref(userdata_)::Model + return _try_catch_handler() do + model = unsafe_pointer_to_objref(user_data)::Model nx, nc = Ref{Cint}(0), Ref{Cint}(0) KN_get_number_vars(model, nx) KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - return model.ms_process(model, x, lambda, m.multistart_user) - catch ex - if isa(ex, InterruptException) - return KN_RC_USER_TERMINATION - else - @warn("Knitro encounters an exception in multistart callback: $ex") - return KN_RC_CALLBACK_ERR - end + return model.ms_process(model, x, lambda, model.multistart_user) end end """ - KN_set_ms_process_callback(m::Model, callback::Function) + KN_set_ms_process_callback(model::Model, callback::Function) This callback function is for multistart (MS) problems only. Set the callback function that is invoked after Knitro finishes @@ -744,25 +699,14 @@ Knitro arguments. Arguments `x` and `lambda` contain the solution from the last solve. """ -function KN_set_ms_process_callback(m::Model, callback::Function, userparams=nothing) - m.ms_process = callback - if userparams != nothing - m.multistart_user = userparams - end +function KN_set_ms_process_callback(model::Model, callback::Function, user_data=nothing) + model.ms_process, model.multistart_user = callback, user_data c_func = @cfunction( _ms_process_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} - ccall( - (:KN_set_ms_process_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, Any), - m.env, - c_func, - m, - ) + KN_set_ms_process_callback(model, c_func, pointer_from_objref(model)) return end @@ -774,26 +718,19 @@ function _mip_node_callback_wrapper( ptr_lambda::Ptr{Cdouble}, user_data::Ptr{Cvoid}, )::Cint - try + return _try_catch_handler() do model = unsafe_pointer_to_objref(user_data)::Model nx, nc = Ref{Cint}(0), Ref{Cint}(0) KN_get_number_vars(model, nx) KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - return model.mip_callback(model, x, lambda, m.mip_user) - catch ex - if isa(ex, InterruptException) - return KN_RC_USER_TERMINATION - else - @warn("Knitro encounters an exception in MIP callback: $ex") - return KN_RC_CALLBACK_ERR - end + return model.mip_callback(model, x, lambda, model.mip_user) end end """ - KN_set_mip_node_callback(m::Model, callback::Function) + KN_set_mip_node_callback(model::Model, callback::Function) This callback function is for mixed integer (MIP) problems only. Set the callback function that is invoked after Knitro finishes @@ -810,25 +747,14 @@ The function should not modify any Knitro arguments. Arguments `x` and `lambda` contain the solution from the node solve. """ -function KN_set_mip_node_callback(m::Model, callback::Function, userparams=nothing) - m.mip_callback = callback - if userparams != nothing - m.mip_user = userparams - end +function KN_set_mip_node_callback(model::Model, callback::Function, user_data=nothing) + model.mip_callback, model.mip_user = callback, user_data c_func = @cfunction( _mip_node_callback_wrapper, Cint, (Ptr{Cvoid}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} - ccall( - (:KN_set_mip_node_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, Any), - m.env, - c_func, - m, - ) + KN_set_mip_node_callback(model, c_func, pointer_from_objref(model)) return end @@ -847,11 +773,11 @@ function _ms_initpt_wrapper( KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - return model.ms_initpt_callback(model, nSolveNumber, x, lambda, m.multistart_user) + return model.ms_initpt_callback(model, nSolveNumber, x, lambda, model.multistart_user) end """ - KN_set_ms_initpt_callback(m::Model, callback::Function) + KN_set_ms_initpt_callback(model::Model, callback::Function) Set a callback that allows applications to specify an initial point before each local solve in the multistart procedure. @@ -866,46 +792,28 @@ On input, arguments `x` and `lambda` are the randomly generated initial points d Knitro, which can be overwritten by the user. The argument `nSolveNumber` is the number of the multistart solve. """ -function KN_set_ms_initpt_callback(m::Model, callback::Function, userparams=nothing) - m.ms_initpt_callback = callback - if userparams != nothing - m.multistart_user = userparams - end +function KN_set_ms_initpt_callback(model::Model, callback::Function, user_data=nothing) + model.ms_initpt_callback, model.multistart_user = callback, user_data c_func = @cfunction( _ms_initpt_wrapper, Cint, (Ptr{Cvoid}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cvoid}) ) - # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} - ccall( - (:KN_set_ms_initpt_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, Any), - m.env, - c_func, - m, - ) + KN_set_ms_initpt_callback(model, c_func, pointer_from_objref(model)) return end # KN_set_puts_callback function _puts_callback_wrapper(str::Ptr{Cchar}, user_data::Ptr{Cvoid})::Cint - try + return _try_catch_handler() do model = unsafe_pointer_to_objref(user_data)::Model return model.puts_callback(unsafe_string(str), model.puts_user) - catch ex - if isa(ex, InterruptException) - return KN_RC_USER_TERMINATION - else - @warn("Knitro encounters an exception in puts callback: $ex") - return KN_RC_CALLBACK_ERR - end end end """ - KN_set_puts_callback(m::Model, callback::Function) + KN_set_puts_callback(model::Model, callback::Function) Set the callback that allows applications to handle output. @@ -918,24 +826,13 @@ The `callback` is a function with signature: callback(str::String, user_data) -> Cint ``` -The KN_puts callback function takes a `userParams` argument which is a pointer +The KN_puts callback function takes a `user_data` argument which is a pointer passed directly from KN_solve. The function should return the number of characters that were printed. """ -function KN_set_puts_callback(m::Model, callback::Function, userparams=nothing) - m.puts_callback = callback - if userparams != nothing - m.puts_user = userparams - end +function KN_set_puts_callback(model::Model, callback::Function, user_data=nothing) + model.puts_callback, model.puts_user = callback, user_data c_func = @cfunction(_puts_callback_wrapper, Cint, (Ptr{Cchar}, Ptr{Cvoid})) - # TODO: use a wrapper for the model so it isn't cconverted on Ptr{Cvoid} - ccall( - (:KN_set_puts_callback, libknitro), - Cint, - (KN_context_ptr, Ptr{Cvoid}, Any), - m.env, - c_func, - m, - ) + KN_set_puts_callback(model, c_func, pointer_from_objref(model)) return end From 19b0112510a6ddce596b4890d38ec33d2cda64ee Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 13:07:57 +1300 Subject: [PATCH 08/15] Update --- src/C_wrapper.jl | 4 +- test/C_wrapper.jl | 344 +++++++++++++++++++++++----------------------- test/runtests.jl | 53 +++---- 3 files changed, 201 insertions(+), 200 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index 4149a74..a7860ee 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -412,7 +412,7 @@ function KN_add_eval_callback_all(model::Model, callback::Function) (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}) ) rfptr = Ref{Ptr{Cvoid}}() - KN_add_eval_callback_all(model, c_f, rfptr) + KN_add_eval_callback_all(model, c_func, rfptr) cb = CallbackContext(rfptr[]) push!(model.callbacks, cb) cb.eval_f = callback @@ -583,7 +583,7 @@ function KN_add_lsq_eval_callback(model::Model, callback::Function) push!(model.callbacks, cb) cb.eval_rsd = callback KN_set_cb_user_params(model, cb) - return + return cb end function KN_set_cb_rsd_jac( diff --git a/test/C_wrapper.jl b/test/C_wrapper.jl index 429015b..cc2c15b 100644 --- a/test/C_wrapper.jl +++ b/test/C_wrapper.jl @@ -1,3 +1,7 @@ +# Copyright (c) 2016: Ng Yee Sian, Miles Lubin, other contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. using KNITRO using Test @@ -35,18 +39,15 @@ const MPS_PROBLEM = """ """ @testset "Instantiation Knitro C interface" begin - # get KNITRO.KNITRO release version rel = KNITRO.get_release() @test isa(rel, String) - - @testset "Definition of model" begin - m = KNITRO.Model() - options = joinpath(dirname(@__FILE__), "..", "examples", "knitro.opt") - KNITRO.KN_reset_params_to_defaults(m) - - KNITRO.KN_free(m) - @test m.env.ptr_env == C_NULL - end +end +@testset "Definition of model" begin + m = KNITRO.Model() + options = joinpath(dirname(@__FILE__), "..", "examples", "knitro.opt") + KNITRO.KN_reset_params_to_defaults(m) + KNITRO.KN_free(m) + @test m.env.ptr_env == C_NULL end # add generic callbacks for future tests @@ -54,7 +55,6 @@ end function evalAll(kc, cb, evalRequest, evalResult, userParams) x = evalRequest.x evalRequestCode = evalRequest.evalRequestCode - if evalRequestCode == KNITRO.KN_RC_EVALFC # Evaluate nonlinear objective evalResult.obj[1] = x[1]^2 * x[3] + x[2]^3 * x[3]^2 @@ -74,20 +74,17 @@ function evalAll(kc, cb, evalRequest, evalResult, userParams) evalResult.hessVec[2] = (6 * x[2] * x[3]^2) * vec[2] + (6 * x[2]^2 * x[3]) * vec[3] evalResult.hessVec[3] = (2 * x[1]) * vec[1] + (6 * x[2]^2 * x[3]) * vec[2] + (2 * x[2]^3) * vec[3] - elseif evalRequestCode == KNITRO.KN_RC_EVALH_NO_F evalResult.hess[1] = 0 evalResult.hess[2] = 0 evalResult.hess[3] = 0 evalResult.hess[4] = 0 evalResult.hess[5] = 0 - elseif evalRequestCode == KNITRO.KN_RC_EVALHV_NO_F vec = evalRequest.vec evalResult.hessVec[1] = 0 evalResult.hessVec[2] = lambda_[4] * vec[3] evalResult.hessVec[3] = lambda_[4] * vec[2] - else return KNITRO.KN_RC_CALLBACK_ERR end @@ -102,35 +99,6 @@ function callback(name) return callbackFn end -if KNITRO.KNITRO_VERSION >= v"12.0" - @testset "Names getters" begin - kc = KNITRO.KN_new() - KNITRO.KN_add_vars(kc, 3) - KNITRO.KN_add_cons(kc, 3) - xnames = ["x1", "x2", "x3"] - cnames = ["c1", "c2", "c3"] - - KNITRO.KN_set_var_names_all(kc, xnames) - KNITRO.KN_set_con_names_all(kc, cnames) - index = Cint(1) - - name = KNITRO.KN_get_var_names(kc, index) - @test name == xnames[2] - outnames = KNITRO.KN_get_var_names(kc, [index]) - @test outnames[1] == xnames[2] - outnames = KNITRO.KN_get_var_names(kc) - @test xnames == outnames - - name = KNITRO.KN_get_con_names(kc, index) - @test name == cnames[2] - outnames = KNITRO.KN_get_con_names(kc, [index]) - @test outnames[1] == cnames[2] - outnames = KNITRO.KN_get_con_names(kc) - @test cnames == outnames - - KNITRO.KN_free(kc) - end -end if KNITRO.KNITRO_VERSION >= v"12.1" @testset "MPS reader/writer" begin mps_name = joinpath(dirname(@__FILE__), "lp.mps") @@ -140,7 +108,7 @@ if KNITRO.KNITRO_VERSION >= v"12.1" end kc = KNITRO.KN_new() KNITRO.KN_load_mps_file(kc, mps_name) - KNITRO.KN_set_param(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) KNITRO.KN_write_mps_file(kc, mps_name_out) status = KNITRO.KN_solve(kc) obj = Ref{Cdouble}(0.0) @@ -152,7 +120,7 @@ if KNITRO.KNITRO_VERSION >= v"12.1" # Resolve with dumped MPS file kc = KNITRO.KN_new() KNITRO.KN_load_mps_file(kc, mps_name_out) - KNITRO.KN_set_param(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) status = KNITRO.KN_solve(kc) obj = Ref{Cdouble}(0.0) KNITRO.KN_get_solution(kc, Ref{Cint}(), obj, C_NULL, C_NULL) @@ -175,40 +143,52 @@ end options = joinpath(dirname(@__FILE__), "..", "examples", "test_knitro.opt") tuner1 = joinpath(dirname(@__FILE__), "..", "examples", "tuner-fixed.opt") tuner2 = joinpath(dirname(@__FILE__), "..", "examples", "tuner-explore.opt") - KNITRO.KN_set_param(kc, "algorithm", 0) - KNITRO.KN_set_param(kc, "cplexlibname", ".") - KNITRO.KN_set_param(kc, "xtol", 1e-15) - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_ALG, KNITRO.KN_ALG_BAR_DIRECT) - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_CPLEXLIB, ".") - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_XTOL, 1e-15) - - @test KNITRO.KN_get_int_param(kc, "algorithm") == KNITRO.KN_ALG_BAR_DIRECT - @test KNITRO.KN_get_double_param(kc, "xtol") == 1e-15 - @test KNITRO.KN_get_int_param(kc, KNITRO.KN_PARAM_ALG) == KNITRO.KN_ALG_BAR_DIRECT - @test KNITRO.KN_get_double_param(kc, KNITRO.KN_PARAM_XTOL) == 1e-15 - @test KNITRO.KN_get_param_name(kc, KNITRO.KN_PARAM_XTOL) == "xtol" - - @test KNITRO.KN_get_param_doc(kc, KNITRO.KN_PARAM_XTOL) == + KNITRO.KN_set_int_param_by_name(kc, "algorithm", 0) + KNITRO.KN_set_char_param_by_name(kc, "cplexlibname", ".") + KNITRO.KN_set_double_param_by_name(kc, "xtol", 1e-15) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_ALG, KNITRO.KN_ALG_BAR_DIRECT) + KNITRO.KN_set_char_param(kc, KNITRO.KN_PARAM_CPLEXLIB, ".") + KNITRO.KN_set_double_param(kc, KNITRO.KN_PARAM_XTOL, 1e-15) + + pCint = Ref{Cint}() + KNITRO.KN_get_int_param_by_name(kc, "algorithm", pCint) + @test pCint[] == KNITRO.KN_ALG_BAR_DIRECT + pCdouble = Ref{Cdouble}() + KNITRO.KN_get_double_param_by_name(kc, "xtol", pCdouble) + @test pCdouble[] == 1e-15 + KNITRO.KN_get_int_param(kc, KNITRO.KN_PARAM_ALG, pCint) + @test pCint[] == KNITRO.KN_ALG_BAR_DIRECT + KNITRO.KN_get_double_param(kc, KNITRO.KN_PARAM_XTOL, pCdouble) + @test pCdouble[] == 1e-15 + tmp = Vector{Cchar}(undef, 1024) + KNITRO.KN_get_param_name(kc, KNITRO.KN_PARAM_XTOL, tmp, 1024) + _to_string(x) = GC.@preserve(x, unsafe_string(pointer(x))) + @test _to_string(tmp) == "xtol" + KNITRO.KN_get_param_doc(kc, KNITRO.KN_PARAM_XTOL, tmp, 1024) + @test _to_string(tmp) == "# Step size tolerance used for terminating the optimization.\n" - @test KNITRO.KN_get_param_type(kc, KNITRO.KN_PARAM_XTOL) == KNITRO.KN_PARAMTYPE_FLOAT - @test KNITRO.KN_get_num_param_values(kc, KNITRO.KN_PARAM_XTOL) == 0 - - @test KNITRO.KN_get_param_value_doc(kc, KNITRO.KN_PARAM_GRADOPT, 1) == "exact" - - @test KNITRO.KN_get_param_id(kc, "xtol") == KNITRO.KN_PARAM_XTOL + KNITRO.KN_get_param_type(kc, KNITRO.KN_PARAM_XTOL, pCint) + @test pCint[] == KNITRO.KN_PARAMTYPE_FLOAT + KNITRO.KN_get_num_param_values(kc, KNITRO.KN_PARAM_XTOL, pCint) + @test pCint[] == 0 + KNITRO.KN_get_param_value_doc(kc, KNITRO.KN_PARAM_GRADOPT, 1, tmp, 1024) + @test _to_string(tmp) == "exact" + KNITRO.KN_get_param_id(kc, "xtol", pCint) + @test pCint[] == KNITRO.KN_PARAM_XTOL # START: Some specific parameter settings - KNITRO.KN_set_param(kc, "hessopt", 1) - KNITRO.KN_set_param(kc, "presolve", 0) - KNITRO.KN_set_param(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "hessopt", 1) + KNITRO.KN_set_int_param_by_name(kc, "presolve", 0) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) # END: Some specific parameter settings # Perform a derivative check. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_DERIVCHECK, KNITRO.KN_DERIVCHECK_ALL) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_DERIVCHECK, KNITRO.KN_DERIVCHECK_ALL) function newpt_callback(kc, x, lambda_, user_data) - a = KNITRO.KN_get_rel_feas_error(kc) - b = KNITRO.KN_get_rel_opt_error(kc) + a = Ref{Cdouble}() + KNITRO.KN_get_rel_feas_error(kc, a) + KNITRO.KN_get_rel_opt_error(kc, a) return 0 end @@ -218,7 +198,7 @@ end # Add the variables and set their bounds. nV = 3 - KNITRO.KN_add_vars(kc, nV) + KNITRO.KN_add_vars(kc, nV, zeros(Cint, nV)) KNITRO.KN_set_var_lobnds_all(kc, [0, 0.1, 0]) KNITRO.KN_set_var_upbnds_all(kc, [0.0, 2, 2]) # Define an initial point. @@ -227,23 +207,29 @@ end # Add the constraints and set their bounds. nC = 1 - KNITRO.KN_add_cons(kc, nC) + KNITRO.KN_add_cons(kc, nC, zeros(Cint, nC)) KNITRO.KN_set_con_lobnds_all(kc, [0.1]) KNITRO.KN_set_con_upbnds_all(kc, [2 * 2 * 0.99]) # Test getters. if KNITRO.KNITRO_VERSION >= v"12.0" xindex = Cint[0, 1, 2] - @test KNITRO.KN_get_var_lobnds(kc, xindex) == [0, 0.1, 0] - @test KNITRO.KN_get_var_upbnds(kc, xindex) == [0.0, 2, 2] + ret = zeros(Cdouble, 3) + KNITRO.KN_get_var_lobnds(kc, 3, xindex, ret) + @test ret == [0, 0.1, 0] + KNITRO.KN_get_var_upbnds(kc, 3, xindex, ret) + @test ret == [0.0, 2, 2] cindex = Cint[0] - @test KNITRO.KN_get_con_lobnds(kc, cindex) == [0.1] - @test KNITRO.KN_get_con_upbnds(kc, cindex) == [2 * 2 * 0.99] + ret = zeros(Cdouble, 1) + KNITRO.KN_get_con_lobnds(kc, 1, cindex, ret) + @test ret == [0.1] + KNITRO.KN_get_con_upbnds(kc, 1, cindex, ret) + @test ret == [2 * 2 * 0.99] end # Load quadratic structure x1*x2 for the constraint. - KNITRO.KN_add_con_quadratic_struct(kc, 0, 1, 2, 1.0) + KNITRO.KN_add_con_quadratic_struct(kc, 1, Cint[0], Cint[1], Cint[2], [1.0]) # Define callback functions. cb = KNITRO.KN_add_objective_callback(kc, evalAll) @@ -261,7 +247,7 @@ end KNITRO.KN_set_newpt_callback(kc, newpt_callback) # Add complementarity constraints. - KNITRO.KN_set_compcons(kc, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) + KNITRO.KN_set_compcons(kc, 1, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) # Solve the problem. status = KNITRO.KN_solve(kc) @@ -281,18 +267,30 @@ end @test status == 0 # Retrieve relevant solve information - @test KNITRO.KN_get_number_FC_evals(kc) >= 1 - @test KNITRO.KN_get_number_GA_evals(kc) >= 1 - @test KNITRO.KN_get_number_H_evals(kc) >= 1 - @test KNITRO.KN_get_number_HV_evals(kc) == 0 - @test KNITRO.KN_get_number_iters(kc) >= 1 - @test KNITRO.KN_get_number_cg_iters(kc) >= 0 - @test KNITRO.KN_get_abs_feas_error(kc) < 1e-10 - @test KNITRO.KN_get_rel_feas_error(kc) < 1e-10 - @test KNITRO.KN_get_abs_opt_error(kc) < 1e-7 - @test KNITRO.KN_get_rel_opt_error(kc) < 1e-8 - @test KNITRO.KN_get_con_values(kc)[1] ≈ 3.96 - + pCint = Ref{Cint}(0) + KNITRO.KN_get_number_FC_evals(kc, pCint) + @test pCint[] >= 1 + KNITRO.KN_get_number_GA_evals(kc, pCint) + @test pCint[] >= 1 + KNITRO.KN_get_number_H_evals(kc, pCint) + @test pCint[] >= 1 + KNITRO.KN_get_number_HV_evals(kc, pCint) + @test pCint[] == 0 + KNITRO.KN_get_number_iters(kc, pCint) + @test pCint[] >= 1 + KNITRO.KN_get_number_cg_iters(kc, pCint) + @test pCint[] >= 0 + pCdouble = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, pCdouble) + @test pCdouble[] < 1e-10 + KNITRO.KN_get_rel_feas_error(kc, pCdouble) + @test pCdouble[] < 1e-10 + KNITRO.KN_get_abs_opt_error(kc, pCdouble) + @test pCdouble[] < 1e-7 + KNITRO.KN_get_rel_opt_error(kc, pCdouble) + @test pCdouble[] < 1e-8 + KNITRO.KN_get_con_value(kc, 0, pCdouble) + @test pCdouble[] ≈ 3.96 nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) @test nStatus == 0 @test x ≈ [0.0, 2.0, 1.98] @@ -301,11 +299,14 @@ end # Test getters for primal and dual variables if KNITRO.KNITRO_VERSION >= v"12.0" - xopt = KNITRO.KN_get_var_primal_values(kc, Cint[0, 1, 2]) + xopt = zeros(Cdouble, 3) + KNITRO.KN_get_var_primal_values(kc, 3, Cint[0, 1, 2], xopt) @test xopt == x - rc = KNITRO.KN_get_var_dual_values(kc, Cint[0, 1, 2]) + rc = zeros(Cdouble, 3) + KNITRO.KN_get_var_dual_values(kc, 3, Cint[0, 1, 2], rc) @test rc == lambda_[2:4] - dual = KNITRO.KN_get_con_dual_values(kc, Cint[0]) + dual = zeros(Cdouble, 1) + KNITRO.KN_get_con_dual_values(kc, 1, Cint[0], dual) @test dual == [lambda_[1]] end @@ -324,12 +325,12 @@ end KNITRO.KN_set_puts_callback(kc, prettyPrinting) # START: Some specific parameter settings - KNITRO.KN_set_param(kc, "outlev", 0) - KNITRO.KN_set_param(kc, "presolve", 0) - KNITRO.KN_set_param(kc, "ms_enable", 1) - KNITRO.KN_set_param(kc, "ms_maxsolves", 5) - KNITRO.KN_set_param(kc, "hessian_no_f", 1) - KNITRO.KN_set_param(kc, "hessopt", KNITRO.KN_HESSOPT_PRODUCT) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "presolve", 0) + KNITRO.KN_set_int_param_by_name(kc, "ms_enable", 1) + KNITRO.KN_set_int_param_by_name(kc, "ms_maxsolves", 5) + KNITRO.KN_set_int_param_by_name(kc, "hessian_no_f", 1) + KNITRO.KN_set_int_param_by_name(kc, "hessopt", KNITRO.KN_HESSOPT_PRODUCT) # END: Some specific parameter settings # Define objective goal @@ -338,7 +339,7 @@ end # Add the variables and set their bounds. nV = 3 - KNITRO.KN_add_vars(kc, nV) + KNITRO.KN_add_vars(kc, nV, zeros(Cint, nV)) KNITRO.KN_set_var_lobnds_all(kc, [0, 0.1, 0]) KNITRO.KN_set_var_upbnds_all(kc, [0.0, 2, 2]) @@ -348,12 +349,12 @@ end # Add the constraints and set their lower bounds. nC = 1 - KNITRO.KN_add_cons(kc, nC) + KNITRO.KN_add_cons(kc, nC, zeros(Cint, nC)) KNITRO.KN_set_con_lobnds_all(kc, [0.1]) KNITRO.KN_set_con_upbnds_all(kc, [2 * 2 * 0.99]) # Load quadratic structure x1*x2 for the constraint. - KNITRO.KN_add_con_quadratic_struct(kc, 0, 1, 2, 1.0) + KNITRO.KN_add_con_quadratic_struct(kc, 1, Cint[0], Cint[1], Cint[2], [1.0]) # Define callback functions. cb = KNITRO.KN_add_objective_callback(kc, evalAll) @@ -373,7 +374,7 @@ end KNITRO.KN_set_ms_initpt_callback(kc, ms_initpt_callbackFn) # Add complementarity constraints. - KNITRO.KN_set_compcons(kc, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) + KNITRO.KN_set_compcons(kc, 1, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) # Solve the problem. status = KNITRO.KN_solve(kc) @@ -391,8 +392,8 @@ end @testset "Third problem test" begin kc = KNITRO.KN_new() - KNITRO.KN_set_param(kc, "outlev", 0) - KNITRO.KN_set_param(kc, "presolve", KNITRO.KN_PRESOLVEDBG_NONE) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "presolve", KNITRO.KN_PRESOLVEDBG_NONE) # Define objective goal objGoal = KNITRO.KN_OBJGOAL_MAXIMIZE @@ -400,7 +401,7 @@ end # Add the variables and set their bounds. nV = 3 - KNITRO.KN_add_vars(kc, nV) + KNITRO.KN_add_vars(kc, nV, zeros(Cint, nV)) KNITRO.KN_set_var_lobnds_all(kc, [0, 0.1, 0]) KNITRO.KN_set_var_upbnds_all(kc, [0.0, 2, 2]) @@ -410,19 +411,19 @@ end # Add the constraints and set their lower bounds. nC = 1 - KNITRO.KN_add_cons(kc, nC) + KNITRO.KN_add_cons(kc, nC, zeros(Cint, nC)) KNITRO.KN_set_con_lobnds_all(kc, [0.1]) KNITRO.KN_set_con_upbnds_all(kc, [2 * 2 * 0.99]) # Load quadratic structure x1*x2 for the constraint. - KNITRO.KN_add_con_quadratic_struct(kc, 0, 1, 2, 1.0) + KNITRO.KN_add_con_quadratic_struct(kc, 1, Cint[0], Cint[1], Cint[2], [1.0]) # Define callback functions. cb = KNITRO.KN_add_objective_callback(kc, evalAll) KNITRO.KN_set_cb_grad(kc, cb, evalAll) KNITRO.KN_set_cb_hess(kc, cb, KNITRO.KN_DENSE_ROWMAJOR, evalAll) - KNITRO.KN_set_compcons(kc, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) + KNITRO.KN_set_compcons(kc, 1, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) KNITRO.KN_set_var_honorbnds_all( kc, @@ -433,31 +434,23 @@ end KNITRO.KN_set_con_scalings_all(kc, [0.5]) KNITRO.KN_set_compcon_scalings_all(kc, [2.0]) KNITRO.KN_set_obj_scaling(kc, 10.0) - status = KNITRO.KN_solve(kc) @test status == 0 - - # Retrieve derivatives values - objGrad = KNITRO.KN_get_objgrad_values(kc) - jac = KNITRO.KN_get_jacobian_values(kc) - hess = KNITRO.KN_get_hessian_values(kc) - nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) @test nStatus == 0 @test x ≈ [0.0, 2.0, 1.98] @test objSol ≈ 31.363199 atol = 1e-5 - KNITRO.KN_free(kc) end @testset "Fourth problem test" begin kc = KNITRO.KN_new() # START: Some specific parameter settings - KNITRO.KN_set_param(kc, "presolve", 0) - KNITRO.KN_set_param(kc, "outlev", 0) - KNITRO.KN_set_param(kc, "gradopt", 2) - KNITRO.KN_set_param(kc, "hessopt", 2) - KNITRO.KN_set_param(kc, "mip_numthreads", 1) + KNITRO.KN_set_int_param_by_name(kc, "presolve", 0) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "gradopt", 2) + KNITRO.KN_set_int_param_by_name(kc, "hessopt", 2) + KNITRO.KN_set_int_param_by_name(kc, "mip_numthreads", 1) # END: Some specific parameter settings function evalF_evalGA(kc, cb, evalRequest, evalResult, userParams) x = evalRequest.x @@ -479,7 +472,7 @@ end KNITRO.KN_set_obj_goal(kc, objGoal) # Add the variables and set their bounds. nV = 3 - KNITRO.KN_add_vars(kc, nV) + KNITRO.KN_add_vars(kc, nV, zeros(Cint, nV)) KNITRO.KN_set_var_lobnds_all(kc, [0.0, 0.1, 0.0]) KNITRO.KN_set_var_upbnds_all(kc, [0.0, 2.0, 2.0]) KNITRO.KN_set_var_types_all( @@ -495,16 +488,16 @@ end KNITRO.KN_set_var_dual_init_values_all(kc, [1.0, 1.0, 1.0, 1.0]) # Add the constraints and set their lower bounds. nC = 1 - KNITRO.KN_add_cons(kc, nC) + KNITRO.KN_add_cons(kc, nC, zeros(Cint, nC)) KNITRO.KN_set_con_lobnds_all(kc, [0.1]) KNITRO.KN_set_con_upbnds_all(kc, [2 * 2 * 0.99]) # Load quadratic structure x1*x2 for the constraint. - KNITRO.KN_add_con_quadratic_struct(kc, 0, 1, 2, 1.0) + KNITRO.KN_add_con_quadratic_struct(kc, 1, Cint[0], Cint[1], Cint[2], [1.0]) # Define callback functions. cb = KNITRO.KN_add_objective_callback(kc, evalF_evalGA) KNITRO.KN_set_cb_grad(kc, cb, evalF_evalGA) # Define complementarity constraints - KNITRO.KN_set_compcons(kc, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) + KNITRO.KN_set_compcons(kc, 1, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) # Set MIP parameters KNITRO.KN_set_mip_branching_priorities_all(kc, Int32[0, 1, 2]) # not compatible with MPEC constraint as a variable cannot be involved in @@ -526,15 +519,14 @@ end # Test for return codes 0 for optimality, and KN_RC_MIP_EXH_FEAS for all # nodes explored, assumed optimal @test status == 0 || status == KNITRO.KN_RC_MIP_EXH_FEAS - @test KNITRO.KN_get_mip_number_nodes(kc) >= 1 - @test KNITRO.KN_get_mip_number_solves(kc) >= 1 - @test KNITRO.KN_get_mip_relaxation_bnd(kc) isa Float64 - @test KNITRO.KN_get_mip_lastnode_obj(kc) isa Float64 - @test 0.1 - 1e-4 <= KNITRO.KN_get_con_values(kc)[1] <= 2 * 2 * 0.99 + 1e-4 + pCdouble = Ref{Cdouble}() + KNITRO.KN_get_con_value(kc, 0, pCdouble) + @test 0.1 - 1e-4 <= pCdouble[] <= 2 * 2 * 0.99 + 1e-4 x_val = zeros(3) KNITRO.KN_get_mip_incumbent_x(kc, x_val) obj_val = x_val[1]^2 * x_val[3] + x_val[2]^3 * x_val[3]^2 - @test KNITRO.KN_get_mip_incumbent_obj(kc) ≈ obj_val + KNITRO.KN_get_mip_incumbent_obj(kc, pCdouble) + @test pCdouble[] ≈ obj_val KNITRO.KN_free(kc) end @@ -543,8 +535,8 @@ end myParams = "stringUserParam" kc = KNITRO.KN_new() - KNITRO.KN_set_param(kc, "outlev", 0) - KNITRO.KN_set_param(kc, "gradopt", 1) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "gradopt", 1) function evalR(kc, cb, evalRequest, evalResult, userParams) x = evalRequest.x @@ -582,13 +574,13 @@ end # Add the variables and set their bounds. nV = 2 - KNITRO.KN_add_vars(kc, nV) + KNITRO.KN_add_vars(kc, nV, zeros(Cint, nV)) KNITRO.KN_set_var_lobnds_all(kc, [-1.0, -1.0]) KNITRO.KN_set_var_upbnds_all(kc, [1.0, 1.0]) KNITRO.KN_set_var_primal_init_values_all(kc, [1.0, 5.0]) # Add the residuals - KNITRO.KN_add_rsds(kc, 6) + KNITRO.KN_add_rsds(kc, 6, zeros(Cint, 6)) # Define callbacks cb = KNITRO.KN_add_lsq_eval_callback(kc, evalR) @@ -597,38 +589,31 @@ end kc, cb, nnzJ, - evalJ, + evalJ; jacIndexRsds=Int32[0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5], jacIndexVars=Int32[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], ) KNITRO.KN_set_cb_user_params(kc, cb, myParams) - - # Solve the problem. status = KNITRO.KN_solve(kc) @test status == 0 - - jac = KNITRO.KN_get_rsd_jacobian_values(kc) - nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) @test nStatus == 0 - @test objSol ≈ 21.5848 atol = 1e-3 @test x ≈ [1.0, 1.0] atol = 1e-5 - KNITRO.KN_free(kc) end @testset "User callback test (issue #110)" begin kc = KNITRO.KN_new() - KNITRO.KN_set_param(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) # Define objective goal objGoal = KNITRO.KN_OBJGOAL_MAXIMIZE KNITRO.KN_set_obj_goal(kc, objGoal) # Add the variables and set their bounds. nV = 3 - KNITRO.KN_add_vars(kc, nV) + KNITRO.KN_add_vars(kc, nV, zeros(Cint, nV)) KNITRO.KN_set_var_lobnds_all(kc, [0, 0.1, 0]) KNITRO.KN_set_var_upbnds_all(kc, [0.0, 2, 2]) @@ -638,22 +623,24 @@ end # Add the constraints and set their lower bounds. nC = 1 - KNITRO.KN_add_cons(kc, nC) + KNITRO.KN_add_cons(kc, nC, zeros(Cint, nC)) KNITRO.KN_set_con_lobnds_all(kc, [0.1]) KNITRO.KN_set_con_upbnds_all(kc, [2 * 2 * 0.99]) # Load quadratic structure x1*x2 for the constraint. - KNITRO.KN_add_con_quadratic_struct(kc, 0, 1, 2, 1.0) + KNITRO.KN_add_con_quadratic_struct(kc, 1, Cint[0], Cint[1], Cint[2], [1.0]) # Define callback functions. cb = KNITRO.KN_add_eval_callback_all(kc, evalAll) KNITRO.KN_set_cb_grad(kc, cb, evalAll) KNITRO.KN_set_cb_hess(kc, cb, KNITRO.KN_DENSE_ROWMAJOR, evalAll) - KNITRO.KN_set_compcons(kc, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) + KNITRO.KN_set_compcons(kc, 1, Int32[KNITRO.KN_CCTYPE_VARVAR], Int32[0], Int32[1]) function newpt_callback(kc, x, lambda_, user_data) - if KNITRO.KN_get_number_iters(kc) > 1 + pCint = Ref{Cint}() + KNITRO.KN_get_number_iters(kc, pCint) + if pCint[] > 1 return KNITRO.KN_RC_USER_TERMINATION end return 0 @@ -666,7 +653,9 @@ end nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) @test nStatus == KNITRO.KN_RC_USER_TERMINATION - @test KNITRO.KN_get_number_iters(kc) == 2 + pCint = Ref{Cint}() + KNITRO.KN_get_number_iters(kc, pCint) + @test pCint[] == 2 KNITRO.KN_free(kc) end @@ -679,8 +668,8 @@ end end kc = KNITRO.KN_new() - KNITRO.KN_set_param(kc, "outlev", 0) - KNITRO.KN_add_vars(kc, 1) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) + KNITRO.KN_add_vars(kc, 1, zeros(Cint, 1)) KNITRO.KN_set_var_primal_init_values_all(kc, [0.0]) cb = KNITRO.KN_add_objective_callback(kc, eval_kn) nstatus = KNITRO.KN_solve(kc) @@ -696,8 +685,8 @@ end end kc = KNITRO.KN_new() - KNITRO.KN_set_param(kc, "outlev", 0) - KNITRO.KN_add_vars(kc, 1) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) + KNITRO.KN_add_vars(kc, 1, zeros(Cint, 1)) # Start from a non-evaluable point KNITRO.KN_set_var_primal_init_values_all(kc, [-1.0]) cb = KNITRO.KN_add_objective_callback(kc, eval_kn) @@ -735,21 +724,22 @@ end # Create a new Knitro solver instance. kc = KNITRO.KN_new() - KNITRO.KN_set_param(kc, "outlev", 0) - xIndices = KNITRO.KN_add_vars(kc, 4) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) + xIndices = zeros(Cint, 4) + KNITRO.KN_add_vars(kc, 4, xIndices) for x in xIndices KNITRO.KN_set_var_primal_init_value(kc, x, 0.8) end # Add the constraints and set the rhs and coefficients - KNITRO.KN_add_cons(kc, 3) + KNITRO.KN_add_cons(kc, 3, zeros(Cint, 3)) KNITRO.KN_set_con_eqbnds_all(kc, [1.0, 0.0, 0.0]) # Coefficients for 2 linear terms lconIndexCons = Int32[1, 2] lconIndexVars = Int32[2, 1] lconCoefs = [-1.0, -1.0] - KNITRO.KN_add_con_linear_struct(kc, lconIndexCons, lconIndexVars, lconCoefs) + KNITRO.KN_add_con_linear_struct(kc, 2, lconIndexCons, lconIndexVars, lconCoefs) # Coefficients for 2 quadratic terms @@ -762,6 +752,7 @@ end KNITRO.KN_add_con_quadratic_struct( kc, + 2, qconIndexCons, qconIndexVars1, qconIndexVars2, @@ -784,18 +775,19 @@ end nStatus = KNITRO.KN_solve(kc) # An example of obtaining solution information. nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) - varbndInfeas, varintInfeas, varviols = KNITRO.KN_get_var_viols(kc, Cint[0, 1, 2, 3]) - - coninfeas, conviols = KNITRO.KN_get_con_viols(kc, Cint[0, 1, 2]) - - KNITRO.KN_get_presolve_error(kc) + varbndInfeas, varintInfeas, varviols = zeros(Cint, 4), zeros(Cint, 4), zeros(Cdouble, 4) + KNITRO.KN_get_var_viols(kc, 4, Cint[0, 1, 2, 3], varbndInfeas, varintInfeas, varviols) + coninfeas, conviols = zeros(Cint, 3), zeros(Cdouble, 3) + KNITRO.KN_get_con_viols(kc, 3, Cint[0, 1, 2], coninfeas, conviols) @testset "Example HS40 nlp1noderivs" begin @test varbndInfeas == [0, 0, 0, 0] @test varintInfeas == [0, 0, 0, 0] @test varviols ≈ [0.0, 0.0, 0.0, 0.0] atol = 1e-6 @test coninfeas == [0, 0, 0] @test conviols ≈ [0.0, 0.0, 0.0] atol = 1e-6 - @test KNITRO.KN_get_abs_feas_error(kc) == max(conviols...) + pCdouble = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, pCdouble) + @test pCdouble[] == max(conviols...) end # Delete the Knitro solver instance. @@ -886,7 +878,7 @@ end # Create a new Knitro solver instance. kc = KNITRO.KN_new() - KNITRO.KN_set_param(kc, "outlev", 0) + KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) # Initialize Knitro with the problem definition. @@ -894,20 +886,21 @@ end # Note: any unset lower bounds are assumed to be # unbounded below and any unset upper bounds are # assumed to be unbounded above. - xIndices = KNITRO.KN_add_vars(kc, 4) + xIndices = zeros(Cint, 4) + KNITRO.KN_add_vars(kc, 4, xIndices) for x in xIndices KNITRO.KN_set_var_primal_init_value(kc, x, 0.8) end # Add the constraints and set the rhs and coefficients - KNITRO.KN_add_cons(kc, 3) + KNITRO.KN_add_cons(kc, 3, zeros(Cint, 3)) KNITRO.KN_set_con_eqbnds_all(kc, [1.0, 0.0, 0.0]) # Coefficients for 2 linear terms lconIndexCons = Int32[1, 2] lconIndexVars = Int32[2, 1] lconCoefs = [-1.0, -1.0] - KNITRO.KN_add_con_linear_struct(kc, lconIndexCons, lconIndexVars, lconCoefs) + KNITRO.KN_add_con_linear_struct(kc, 2, lconIndexCons, lconIndexVars, lconCoefs) # Coefficients for 2 quadratic terms @@ -920,6 +913,7 @@ end KNITRO.KN_add_con_quadratic_struct( kc, + 2, qconIndexCons, qconIndexVars1, qconIndexVars2, @@ -978,13 +972,15 @@ end # =============== MODIFY PROBLEM AND RE-SOLVE =========== # Add 0.5x3 linear term to c2 - KNITRO.KN_add_con_linear_struct(kc, 2, 3, 0.5) + KNITRO.KN_add_con_linear_struct(kc, 1, Cint[2], Cint[3], [0.5]) # Change -x2 to 5x2 in c1 KNITRO.KN_chg_con_linear_term(kc, 1, 2, 5.0) # Now add a new linear constraint x1 + 2x2 + x3 <= 2.5 (c3) and re-solve - c3 = KNITRO.KN_add_con(kc) + pc3 = Ref{Cint}() + KNITRO.KN_add_con(kc, pc3) + c3 = pc3[] KNITRO.KN_set_con_upbnd(kc, c3, 2.5) - KNITRO.KN_add_con_linear_struct(kc, c3, Int32[1, 2, 3], [1.0, 2.0, 1.0]) + KNITRO.KN_add_con_linear_struct_one(kc, 3, c3, Int32[1, 2, 3], [1.0, 2.0, 1.0]) # Add a constant to the objective KNITRO.KN_add_obj_constant(kc, 100.0) @@ -992,7 +988,7 @@ end # Tell Knitro to try a "warm-start" since it is starting from the solution # of the previous solve, which may be a good initial point for the solution # of the slightly modified problem. - KNITRO.KN_set_param( + KNITRO.KN_set_int_param( kc, KNITRO.KN_PARAM_STRAT_WARM_START, KNITRO.KN_STRAT_WARM_START_YES, diff --git a/test/runtests.jl b/test/runtests.jl index 7fe58e3..40b02ce 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,35 +1,40 @@ +# Copyright (c) 2016: Ng Yee Sian, Miles Lubin, other contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + using KNITRO using Test const KN_VERBOSE = false -# @testset "Test C API" begin -# include("C_wrapper.jl") -# end +@testset "Test C API" begin + include("C_wrapper.jl") +end -# @testset "Test examples" begin -# examples_dir = joinpath(dirname(@__FILE__), "..", "examples") -# for file in filter(f -> endswith(f, ".jl"), readdir(examples_dir)) -# if !occursin("mps_reader", file) -# include(joinpath(examples_dir, file)) -# end -# end -# end +@testset "Test examples" begin + examples_dir = joinpath(dirname(@__FILE__), "..", "examples") + for file in filter(f -> endswith(f, ".jl"), readdir(examples_dir)) + if !occursin("mps_reader", file) + # include(joinpath(examples_dir, file)) + end + end +end @testset "Test MathOptInterface" begin include("MOI_wrapper.jl") end -# try -# @testset "Test C API License" begin -# include("knitroapi_licman.jl") -# end -# catch e -# @warn( -# "License tests failed, but this might be due to License Manager" * -# " not being supported by your license." -# ) -# println("The error catched was:\n") -# println("$e\n") -# println("See table above for more details.") -# end +try + @testset "Test C API License" begin + include("knitroapi_licman.jl") + end +catch e + @warn( + "License tests failed, but this might be due to License Manager" * + " not being supported by your license." + ) + println("The error catched was:\n") + println("$e\n") + println("See table above for more details.") +end From b641d6b4e4a62590645174a7934abbadf4780e1d Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 13:40:16 +1300 Subject: [PATCH 09/15] Update --- src/C_wrapper.jl | 56 ++++++++++++---------------------------- src/MOI_wrapper.jl | 17 +++++------- test/C_wrapper.jl | 12 +++------ test/knitroapi_licman.jl | 2 +- 4 files changed, 26 insertions(+), 61 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index a7860ee..8a7c333 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -3,14 +3,6 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. -"Return the current KNITRO version." -function get_release() - len = 15 - out = zeros(Cchar, len) - KN_get_release(len, out) - return String(strip(String(convert(Vector{UInt8}, out)), '\0')) -end - mutable struct Env ptr_env::Ptr{Cvoid} end @@ -27,8 +19,6 @@ end Base.cconvert(::Type{Ptr{Cvoid}}, env::Env) = env Base.unsafe_convert(::Type{Ptr{Cvoid}}, env::Env) = env.ptr_env::Ptr{Cvoid} -is_valid(env::Env) = env.ptr_env != C_NULL - """ Structure specifying the callback context. @@ -100,12 +90,6 @@ mutable struct Model end end -function Model() - model = Model(Env()) - finalizer(KN_free, model) - return model -end - Base.cconvert(::Type{Ptr{Cvoid}}, model::Model) = model Base.unsafe_convert(::Type{Ptr{Cvoid}}, kn::Model) = kn.env.ptr_env::Ptr{Cvoid} @@ -119,18 +103,21 @@ function KN_free(model::Model) end "Create solver object." -KN_new() = Model() - -is_valid(model::Model) = is_valid(model.env) - -has_callbacks(model::Model) = !isempty(model.callbacks) +function KN_new() + model = Model(Env()) + finalizer(KN_free, model) + return model +end function Base.show(io::IO, model::Model) - if !is_valid(model) + if model.env.ptr_env !== C_NULL println(io, "KNITRO Problem: NULL") return end - println(io, "$(get_release())") + out = zeros(Cchar, 15) + KN_get_release(15, out) + release = GC.@preserve(len, unsafe_string(pointer(out))) + println(io, "$get_release") println(io, "-----------------------") println(io, "Problem Characteristics") println(io, "-----------------------") @@ -150,10 +137,6 @@ function Base.show(io::IO, model::Model) return end -#= - LM license manager -=# - """ Type declaration for the Artelys License Manager context object. Applications must not modify any part of the context. @@ -179,26 +162,21 @@ end Base.cconvert(::Type{Ptr{Cvoid}}, lm::LMcontext) = lm Base.unsafe_convert(::Type{Ptr{Cvoid}}, lm::LMcontext) = lm.ptr_lmcontext::Ptr{Cvoid} -function Env(lm::LMcontext) +function KN_new_lm(lm::LMcontext) ptrptr_env = Ref{Ptr{Cvoid}}() res = KN_new_lm(lm, ptrptr_env) if res != 0 error("Fail to retrieve a valid KNITRO KN_context. Error $res") end - return Env(ptrptr_env[]) -end - -function Model(lm::LMcontext) - model = Model(Env(lm)) + env = Env(ptrptr_env[]) + model = Model(env) push!(lm.linked_models, model) return model end -KN_new_lm(lm::LMcontext) = Model(lm) - function KN_release_license(lm::LMcontext) - # First, ensure that all linked models are properly freed - # before releasing license manager! + # First, ensure that all linked models are properly freed before releasing + # license manager! KN_free.(lm.linked_models) if lm.ptr_lmcontext != C_NULL refptr = Ref{Ptr{Cvoid}}(lm.ptr_lmcontext) @@ -213,7 +191,7 @@ function KN_solve(model::Model) # that Knitro is not multithreaded. Otherwise, the code will segfault # as we have trouble calling Julia code from multithreaded C # code. See issue #93 on https://github.com/jump-dev/KNITRO.jl. - if has_callbacks(model) + if !isempty(model.callbacks) if KNITRO_VERSION >= v"13.0" KN_set_int_param(model, KN_PARAM_MS_NUMTHREADS, 1) KN_set_int_param(model, KN_PARAM_NUMTHREADS, 1) @@ -242,8 +220,6 @@ function KN_get_solution(model::Model) return status[], obj[], model.x, model.mult end -# Callbacks utilities. - function KN_set_cb_user_params(model::Model, cb::CallbackContext, user_data=nothing) cb.user_data = user_data # Note: we store here the number of constraints and variables defined diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index d61c46a..2a7eca9 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -150,9 +150,9 @@ end function Optimizer(; license_manager=nothing, options...) # Create KNITRO context. kc = if isa(license_manager, LMcontext) - Model(license_manager) + KN_new_lm(license_manager) else - Model() + KN_new() end model = Optimizer( kc, @@ -187,17 +187,12 @@ function MOI.copy_to(model::Optimizer, src::MOI.ModelLike) return MOI.Utilities.default_copy_to(model, src) end -function free(model::Optimizer) - KN_free(model.inner) - return -end - function MOI.empty!(model::Optimizer) - free(model) + KN_free(model.inner) model.inner = if isa(model.license_manager, LMcontext) - Model(model.license_manager) + KN_new_lm(model.license_manager) else - Model() + KN_new() end empty!(model.variable_info) model.number_solved = 0 @@ -327,7 +322,7 @@ function MOI.set(model::Optimizer, attr::MOI.RawOptimizerAttribute, value) elseif attr.name == "tuner_file" KN_load_tuner_file(model.inner, value) elseif attr.name == "free" - free(model) + KN_free(model.inner) elseif !MOI.supports(model, attr) throw(MOI.UnsupportedAttribute(attr)) elseif value isa Integer diff --git a/test/C_wrapper.jl b/test/C_wrapper.jl index cc2c15b..42fe557 100644 --- a/test/C_wrapper.jl +++ b/test/C_wrapper.jl @@ -38,12 +38,8 @@ const MPS_PROBLEM = """ ENDATA """ -@testset "Instantiation Knitro C interface" begin - rel = KNITRO.get_release() - @test isa(rel, String) -end @testset "Definition of model" begin - m = KNITRO.Model() + m = KNITRO.KN_new() options = joinpath(dirname(@__FILE__), "..", "examples", "knitro.opt") KNITRO.KN_reset_params_to_defaults(m) KNITRO.KN_free(m) @@ -134,9 +130,7 @@ end kc = KNITRO.KN_new() @test isa(kc, KNITRO.Model) # By default, kc does not have any callback - @test !KNITRO.has_callbacks(kc) - - release = KNITRO.get_release() + @test isempty(kc.callbacks) KNITRO.KN_reset_params_to_defaults(kc) @@ -233,7 +227,7 @@ end # Define callback functions. cb = KNITRO.KN_add_objective_callback(kc, evalAll) - @test KNITRO.has_callbacks(kc) + @test !isempty(kc.callbacks) KNITRO.KN_set_cb_grad(kc, cb, evalAll) KNITRO.KN_set_cb_hess( kc, diff --git a/test/knitroapi_licman.jl b/test/knitroapi_licman.jl index e631cb6..e861a52 100644 --- a/test/knitroapi_licman.jl +++ b/test/knitroapi_licman.jl @@ -3,7 +3,7 @@ using KNITRO using Test @testset "License manager test" begin - m = KNITRO.Model() + m = KNITRO.KN_new() KNITRO.KN_free(m) # create license manager context @show lm = KNITRO.LMcontext() From 2876b87e47318acd18c36720823ad7908ab99ae0 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 13:43:13 +1300 Subject: [PATCH 10/15] More updates --- src/C_wrapper.jl | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index 8a7c333..e18b01b 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -7,15 +7,6 @@ mutable struct Env ptr_env::Ptr{Cvoid} end -function Env() - ptrptr_env = Ref{Ptr{Cvoid}}() - res = KN_new(ptrptr_env) - if res != 0 - error("Fail to retrieve a valid KNITRO KN_context. Error $res") - end - return Env(ptrptr_env[]) -end - Base.cconvert(::Type{Ptr{Cvoid}}, env::Env) = env Base.unsafe_convert(::Type{Ptr{Cvoid}}, env::Env) = env.ptr_env::Ptr{Cvoid} @@ -104,7 +95,12 @@ end "Create solver object." function KN_new() - model = Model(Env()) + ptrptr_env = Ref{Ptr{Cvoid}}() + res = KN_new(ptrptr_env) + if res != 0 + error("Fail to retrieve a valid KNITRO KN_context. Error $res") + end + model = Model(Env(ptrptr_env[])) finalizer(KN_free, model) return model end @@ -168,8 +164,7 @@ function KN_new_lm(lm::LMcontext) if res != 0 error("Fail to retrieve a valid KNITRO KN_context. Error $res") end - env = Env(ptrptr_env[]) - model = Model(env) + model = Model(Env(ptrptr_env[])) push!(lm.linked_models, model) return model end From 41a58deda4e2ffa90e04bd2a7cb7829d85d76d3a Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 16:26:55 +1300 Subject: [PATCH 11/15] Update C examples --- .../advanced/space_shuttle/space_shuttle.jl | 10 ++-- examples/conic.jl | 43 ++++++------- examples/fcga.jl | 22 ++++--- examples/licensemanager/nlp1.jl | 10 ++-- examples/lp1.jl | 20 ++++--- examples/lsq1.jl | 8 +-- examples/lsq2.jl | 6 +- examples/minlp1.jl | 60 +++++++++++-------- examples/mpec1.jl | 21 ++++--- examples/multipleCB.jl | 21 ++++--- examples/multistart.jl | 44 ++++++++------ examples/nlp1.jl | 27 +++++---- examples/nlp1_hessian_product.jl | 33 +++++----- examples/nlp1noderivs.jl | 25 ++++---- examples/nlp2.jl | 34 +++++++---- examples/nlp2noderivs.jl | 27 +++++---- examples/nlp2resolve.jl | 44 +++++++++----- examples/qcqp1.jl | 14 +++-- examples/qp1.jl | 24 ++++---- examples/restart.jl | 50 +++++++++------- examples/tuner.jl | 31 ++++++---- test/runtests.jl | 3 +- 22 files changed, 349 insertions(+), 228 deletions(-) diff --git a/examples/advanced/space_shuttle/space_shuttle.jl b/examples/advanced/space_shuttle/space_shuttle.jl index d5d624a..d4ee44b 100644 --- a/examples/advanced/space_shuttle/space_shuttle.jl +++ b/examples/advanced/space_shuttle/space_shuttle.jl @@ -301,12 +301,12 @@ objIndex, objCoef = ind_θ[end], 1.0 KNITRO.KN_add_obj_linear_struct(kc, objIndex - 1, objCoef) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MAXIMIZE) -KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_ALG, KNITRO.KN_ALG_BAR_DIRECT) -KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_BAR_MURULE, KNITRO.KN_BAR_MURULE_QUALITY) -KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_LINSOLVER, KNITRO.KN_LINSOLVER_MA27) +KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_ALG, KNITRO.KN_ALG_BAR_DIRECT) +KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_BAR_MURULE, KNITRO.KN_BAR_MURULE_QUALITY) +KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_LINSOLVER, KNITRO.KN_LINSOLVER_MA27) -KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSOPT, KNITRO.KN_HESSOPT_LBFGS) -KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_LMSIZE, 5) # limited-memory pairs stored +KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSOPT, KNITRO.KN_HESSOPT_LBFGS) +KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_LMSIZE, 5) # limited-memory pairs stored KNITRO.KN_solve(kc) nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) diff --git a/examples/conic.jl b/examples/conic.jl index fc8e7fa..0d234be 100644 --- a/examples/conic.jl +++ b/examples/conic.jl @@ -34,7 +34,7 @@ function example_conic(; verbose=true) ####* unbounded below and any unset upper bounds are ####* assumed to be unbounded above. */ n = 4 - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) xLoBnds = [-KNITRO.KN_INFINITY, 1.0, -KNITRO.KN_INFINITY, 2.0] xUpBnds = [KNITRO.KN_INFINITY, KNITRO.KN_INFINITY, 1.0, KNITRO.KN_INFINITY] @@ -43,7 +43,7 @@ function example_conic(; verbose=true) #** Add the constraints and set the RHS and coefficients */ m = 3 - KNITRO.KN_add_cons(kc, m) + KNITRO.KN_add_cons(kc, m, C_NULL) KNITRO.KN_set_con_upbnd(kc, 0, 0.0) KNITRO.KN_set_con_upbnd(kc, 1, 100.0) KNITRO.KN_set_con_upbnd(kc, 2, 100.0) @@ -51,23 +51,23 @@ function example_conic(; verbose=true) #** coefficients for linear terms in constraint c2 */ indexVars1 = Cint[1, 2] coefs1 = [2.0, 3.0] - KNITRO.KN_add_con_linear_struct(kc, 2, indexVars1, coefs1) + KNITRO.KN_add_con_linear_struct_one(kc, 2, 2, indexVars1, coefs1) #** coefficient for linear term in constraint c1 */ indexVars2 = Cint[0] coefs2 = [5.0] - KNITRO.KN_add_con_linear_struct(kc, 1, indexVars2, coefs2) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 1, indexVars2, coefs2) #** coefficient for linear term in constraint c0 */ indexVars3 = Cint[1] coefs3 = [-10.0] - KNITRO.KN_add_con_linear_struct(kc, 0, indexVars3, coefs3) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 0, indexVars3, coefs3) #** coefficient for quadratic term in constraint c1 */ - qconIndexVar1 = 3 - qconIndexVar2 = 3 - qconCoef = 1.0 - KNITRO.KN_add_con_quadratic_struct(kc, 1, qconIndexVar1, qconIndexVar2, qconCoef) + qconIndexVar1 = Cint[3] + qconIndexVar2 = Cint[3] + qconCoef = [1.0] + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 1, qconIndexVar1, qconIndexVar2, qconCoef) #** Coefficients for L2-norm constraint components in c0. #* Assume the form ||Ax+b|| (here with b = 0) @@ -90,27 +90,27 @@ function example_conic(; verbose=true) qobjIndexVars1 = Cint[0, 2, 3, 2, 1] qobjIndexVars2 = Cint[0, 2, 3, 3, 1] qobjCoefs = [1.0, 1.0, 1.0, 2.0, 1.0] - KNITRO.KN_add_obj_quadratic_struct(kc, qobjIndexVars1, qobjIndexVars2, qobjCoefs) + KNITRO.KN_add_obj_quadratic_struct(kc, 5, qobjIndexVars1, qobjIndexVars2, qobjCoefs) #** Add linear objective term. */ lobjIndexVar = Cint[2] lobjCoef = [1.0] - KNITRO.KN_add_obj_linear_struct(kc, lobjIndexVar, lobjCoef) + KNITRO.KN_add_obj_linear_struct(kc, 1, lobjIndexVar, lobjCoef) #** Interior/Direct algorithm is required for models with #* L2 norm structure. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_ALGORITHM, KNITRO.KN_ALG_BAR_DIRECT) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_ALGORITHM, KNITRO.KN_ALG_BAR_DIRECT) #** Enable the special barrier tools for second order cone(SOC) constraints. */ - KNITRO.KN_set_param( + KNITRO.KN_set_int_param( kc, KNITRO.KN_PARAM_BAR_CONIC_ENABLE, KNITRO.KN_BAR_CONIC_ENABLE_SOC, ) #** Specify maximum output */ outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, outlev) #** Specify special barrier update rule */ - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_BAR_MURULE, KNITRO.KN_BAR_MURULE_FULLMPC) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_BAR_MURULE, KNITRO.KN_BAR_MURULE_FULLMPC) #** Solve the problem. ####* @@ -118,20 +118,23 @@ function example_conic(; verbose=true) ####* in the Knitro manual. */ nStatus = KNITRO.KN_solve(kc) nStatus, objSol, x, _ = KNITRO.KN_get_solution(kc) - feasError = KNITRO.KN_get_abs_feas_error(kc) - optError = KNITRO.KN_get_abs_opt_error(kc) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) #** An example of obtaining solution information. */ if verbose println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", feasError) - println(" KKT optimality violation = ", optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end #** Delete the Knitro solver instance. */ - return KNITRO.KN_free(kc) + KNITRO.KN_free(kc) + return end example_conic(; verbose=isdefined(Main, :KN_VERBOSE) ? KN_VERBOSE : true) diff --git a/examples/fcga.jl b/examples/fcga.jl index f81431f..b5a3d7b 100644 --- a/examples/fcga.jl +++ b/examples/fcga.jl @@ -108,20 +108,21 @@ function example_fcga(; verbose=true) # Note: any unset lower bounds are assumed to be # unbounded below and any unset upper bounds are # assumed to be unbounded above. - vars = KNITRO.KN_add_vars(kc, 4) + vars = zeros(Cint, 4) + KNITRO.KN_add_vars(kc, 4, vars) for x in vars KNITRO.KN_set_var_primal_init_value(kc, x, 0.8) end # Add the constraints and set the rhs and coefficients - KNITRO.KN_add_cons(kc, 3) + KNITRO.KN_add_cons(kc, 3, C_NULL) KNITRO.KN_set_con_eqbnds_all(kc, [1.0, 0.0, 0.0]) # Coefficients for 2 linear terms lconIndexCons = Int32[1, 2] lconIndexVars = Int32[2, 1] lconCoefs = [-1.0, -1.0] - KNITRO.KN_add_con_linear_struct(kc, lconIndexCons, lconIndexVars, lconCoefs) + KNITRO.KN_add_con_linear_struct(kc, 2, lconIndexCons, lconIndexVars, lconCoefs) # Coefficients for 2 quadratic terms @@ -132,6 +133,7 @@ function example_fcga(; verbose=true) KNITRO.KN_add_con_quadratic_struct( kc, + 2, qconIndexCons, qconIndexVars1, qconIndexVars2, @@ -185,11 +187,11 @@ function example_fcga(; verbose=true) # Set option to print output after every iteration. kn_outlev = verbose ? KNITRO.KN_OUTLEV_ITER : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Set option to tell Knitro that the gradients are being provided # with the functions in one callback. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_EVAL_FCGA, KNITRO.KN_EVAL_FCGA_YES) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_EVAL_FCGA, KNITRO.KN_EVAL_FCGA_YES) # Solve the problem. # @@ -197,14 +199,17 @@ function example_fcga(; verbose=true) # in the Knitro manual. nStatus = KNITRO.KN_solve(kc) nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) - # An example of obtaining solution information. if verbose + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. @@ -215,6 +220,7 @@ function example_fcga(; verbose=true) @test objSol ≈ 0.25 @test x ≈ [0.793701, 0.707107, 0.529732, 0.840896] atol = 1e-5 end + return end example_fcga(; verbose=isdefined(Main, :KN_VERBOSE) ? KN_VERBOSE : true) diff --git a/examples/licensemanager/nlp1.jl b/examples/licensemanager/nlp1.jl index 1c27783..c768c5c 100644 --- a/examples/licensemanager/nlp1.jl +++ b/examples/licensemanager/nlp1.jl @@ -113,7 +113,7 @@ KNITRO.KN_set_con_lobnds(kc, [1.0, 0.0]) # structure for these constraints. # First load quadratic structure x0*x1 for the first constraint -KNITRO.KN_add_con_quadratic_struct(kc, 0, 0, 1, 1.0) +KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 0, Cint[0], Cint[1], [1.0]) # Load structure for the second constraint. below we add the linear # structure and the quadratic structure separately, though it @@ -122,10 +122,10 @@ KNITRO.KN_add_con_quadratic_struct(kc, 0, 0, 1, 1.0) # supports adding linear terms. # Add linear term x0 in the second constraint -KNITRO.KN_add_con_linear_struct(kc, 1, 0, 1.0) +KNITRO.KN_add_con_linear_struct_one(kc, 1, 1, Cint[0], [1.0]) # Add quadratic term x1^2 in the second constraint -KNITRO.KN_add_con_quadratic_struct(kc, 1, 1, 1, 1.0) +KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 1, Cint[1], Cint[1], [1.0]) # Add a callback function "callbackEvalF" to evaluate the nonlinear #(non-quadratic) objective. Note that the linear and @@ -160,13 +160,13 @@ KNITRO.KN_set_cb_hess(kc, cb, KNITRO.KN_DENSE_ROWMAJOR, callbackEvalH!) # Specify that the user is able to provide evaluations # of the hessian matrix without the objective component. # turned off by default but should be enabled if possible. -KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) +KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) # Set minimize or maximize(if not set, assumed minimize) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) # Perform a derivative check. -KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_DERIVCHECK, KNITRO.KN_DERIVCHECK_ALL) +KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_DERIVCHECK, KNITRO.KN_DERIVCHECK_ALL) # Solve the problem. # diff --git a/examples/lp1.jl b/examples/lp1.jl index a82186d..2d5a4fb 100644 --- a/examples/lp1.jl +++ b/examples/lp1.jl @@ -36,13 +36,15 @@ function example_lp1(; verbose=true) # Add the variables and set their bounds. # Note: unset bounds assumed to be infinite. - xIndices = KNITRO.KN_add_vars(kc, 4) + xIndices = zeros(Cint, 4) + KNITRO.KN_add_vars(kc, 4, xIndices) for x in xIndices KNITRO.KN_set_var_lobnd(kc, x, 0.0) end # Add the constraints and set the rhs and coefficients. - cons = KNITRO.KN_add_cons(kc, 2) + cons = zeros(Cint, 2) + KNITRO.KN_add_cons(kc, 2, cons) KNITRO.KN_set_con_eqbnds_all(kc, [5.0, 8.0]) # Add Jacobian structure and coefficients. # First constraint @@ -53,8 +55,8 @@ function example_lp1(; verbose=true) jacIndexCons = [jacIndexCons; Int32[1, 1, 1]] jacIndexVars = [jacIndexVars; Int32[0, 1, 3]] jacCoefs = [jacCoefs; [2.0, 0.5, 1.0]] - KNITRO.KN_add_con_linear_struct(kc, 0, Int32[0, 1, 2], [1.0, 1.0, 1.0]) - KNITRO.KN_add_con_linear_struct(kc, 1, Int32[0, 1, 3], [2.0, 0.5, 1.0]) + KNITRO.KN_add_con_linear_struct_one(kc, 3, 0, Int32[0, 1, 2], [1.0, 1.0, 1.0]) + KNITRO.KN_add_con_linear_struct_one(kc, 3, 1, Int32[0, 1, 3], [2.0, 0.5, 1.0]) # Set minimize or maximize (if not set, assumed minimize). KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) @@ -65,7 +67,7 @@ function example_lp1(; verbose=true) KNITRO.KN_add_obj_linear_struct(kc, 2, objIndices, objCoefs) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the problem. # @@ -75,11 +77,15 @@ function example_lp1(; verbose=true) nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) if verbose + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/examples/lsq1.jl b/examples/lsq1.jl index 9537954..ea45e5a 100644 --- a/examples/lsq1.jl +++ b/examples/lsq1.jl @@ -42,11 +42,11 @@ function example_lsq1(; verbose=true) # unbounded below and any unset upper bounds are # assumed to be unbounded above. n = 3 # # of variables/parameters - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) # Add the residuals. m = 5 # # of residuals - KNITRO.KN_add_rsds(kc, m) + KNITRO.KN_add_rsds(kc, m, C_NULL) # Set the array of constants, y, in the residuals KNITRO.KN_add_rsd_constants_all(kc, [1.0, 0.5, 0.0, 0.5, 2.0]) @@ -79,10 +79,10 @@ function example_lsq1(; verbose=true) coefs = [coefs; [-1.0, -1.0, -1.0]] # Pass in the linear coefficients - KNITRO.KN_add_rsd_linear_struct(kc, indexRsds, indexVars, coefs) + KNITRO.KN_add_rsd_linear_struct(kc, length(indexRsds), indexRsds, indexVars, coefs) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the problem. # diff --git a/examples/lsq2.jl b/examples/lsq2.jl index 65286e3..ebe7ff0 100644 --- a/examples/lsq2.jl +++ b/examples/lsq2.jl @@ -98,7 +98,7 @@ function example_lsq2(; verbose=true) # unbounded below and any unset upper bounds are # assumed to be unbounded above. n = 2 # # of variables/parameters - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) # In order to prevent the possiblity of numerical # overflow from very large numbers, we set a @@ -110,7 +110,7 @@ function example_lsq2(; verbose=true) # Add the residuals. m = 6 # # of residuals - KNITRO.KN_add_rsds(kc, m) + KNITRO.KN_add_rsds(kc, m, C_NULL) # Set the array of constants in the residuals KNITRO.KN_add_rsd_constants_all(kc, [-2.138, -3.421, -3.597, -4.34, -4.882, -5.66]) @@ -132,7 +132,7 @@ function example_lsq2(; verbose=true) KNITRO.KN_set_cb_rsd_jac(kc, cb, KNITRO.KN_DENSE_ROWMAJOR, callbackEvalRJ) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the problem. # diff --git a/examples/minlp1.jl b/examples/minlp1.jl index 34c4444..2b43d43 100644 --- a/examples/minlp1.jl +++ b/examples/minlp1.jl @@ -121,23 +121,29 @@ function example_minlp1(; verbose=true) # The Knitro context pointer was passed in through "userParams". # Print info about the status of the MIP solution. - numNodes = KNITRO.KN_get_mip_number_nodes(kc) - relaxBound = KNITRO.KN_get_mip_relaxation_bnd(kc) + numNodes = Ref{Cint}() + KNITRO.KN_get_mip_number_nodes(kc, numNodes) + relaxBound = Ref{Cdouble}() + KNITRO.KN_get_mip_relaxation_bnd(kc, relaxBound) # Note: To retrieve solution information about the node subproblem # we need to pass in "kcSub" here. - nodeObj = KNITRO.KN_get_obj_value(kc) - mip_io = KNITRO.KN_get_mip_incumbent_obj(kc) - mip_ag = KNITRO.KN_get_mip_abs_gap(kc) - mip_rg = KNITRO.KN_get_mip_rel_gap(kc) + nodeObj = Ref{Cdouble}() + KNITRO.KN_get_obj_value(kc, nodeObj) + mip_io = Ref{Cdouble}() + KNITRO.KN_get_mip_incumbent_obj(kc, mip_io) + mip_ag = Ref{Cdouble}() + KNITRO.KN_get_mip_abs_gap(kc, mip_ag) + mip_rg = Ref{Cdouble}() + KNITRO.KN_get_mip_rel_gap(kc, mip_rg) if verbose println("callbackProcessNode:") - println(" Node number = ", numNodes) - println(" Node objective = ", nodeObj) - println(" Current relaxation bound = ", relaxBound) - println(" Current incumbent bound = ", mip_io) - println(" Absolute integrality gap = ", mip_ag) - println(" Relative integrality gap = ", mip_rg) + println(" Node number = ", numNodes[]) + println(" Node objective = ", nodeObj[]) + println(" Current relaxation bound = ", relaxBound[]) + println(" Current incumbent bound = ", mip_io[]) + println(" Absolute integrality gap = ", mip_ag[]) + println(" Relative integrality gap = ", mip_rg[]) end # User defined termination example. @@ -156,12 +162,12 @@ function example_minlp1(; verbose=true) kc = KNITRO.KN_new() # Illustrate how to override default options. - KNITRO.KN_set_param(kc, "mip_method", KNITRO.KN_MIP_METHOD_BB) - KNITRO.KN_set_param(kc, "algorithm", KNITRO.KN_ALG_ACT_CG) - KNITRO.KN_set_param(kc, "outmode", KNITRO.KN_OUTMODE_SCREEN) - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, KNITRO.KN_OUTLEV_ALL) - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_MIP_OUTINTERVAL, 1) - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_MIP_MAXNODES, 10000) + KNITRO.KN_set_int_param_by_name(kc, "mip_method", KNITRO.KN_MIP_METHOD_BB) + KNITRO.KN_set_int_param_by_name(kc, "algorithm", KNITRO.KN_ALG_ACT_CG) + KNITRO.KN_set_int_param_by_name(kc, "outmode", KNITRO.KN_OUTMODE_SCREEN) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, KNITRO.KN_OUTLEV_ALL) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_MIP_OUTINTERVAL, 1) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_MIP_MAXNODES, 10000) # Initialize Knitro with the problem definition. @@ -170,7 +176,7 @@ function example_minlp1(; verbose=true) # unbounded below and any unset upper bounds are # assumed to be unbounded above. n = 6 - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, zeros(Float64, n)) KNITRO.KN_set_var_upbnds_all(kc, [2.0, 2.0, 1.0, 1.0, 1.0, 1.0]) ret = KNITRO.KN_set_var_types_all( @@ -193,7 +199,7 @@ function example_minlp1(; verbose=true) end # Add the constraints and set their bounds - KNITRO.KN_add_cons(kc, 6) + KNITRO.KN_add_cons(kc, 6, C_NULL) KNITRO.KN_set_con_lobnds_all( kc, [ @@ -210,7 +216,7 @@ function example_minlp1(; verbose=true) # Add the linear structure in the objective function. objGradIndexVars = Int32[3, 4, 5, 0, 2] objGradCoefs = [5.0, 6.0, 8.0, 10.0, -7.0] - KNITRO.KN_add_obj_linear_struct(kc, objGradIndexVars, objGradCoefs) + KNITRO.KN_add_obj_linear_struct(kc, 5, objGradIndexVars, objGradCoefs) # Add the constant in the objective function. KNITRO.KN_add_obj_constant(kc, 10.0) @@ -219,7 +225,13 @@ function example_minlp1(; verbose=true) jacIndexCons = Int32[0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5] jacIndexVars = Int32[2, 2, 5, 1, 0, 1, 3, 0, 1, 4, 3, 4] jacCoefs = [-0.8, -1.0, -2.0, 1.0, -1.0, 1.0, -2.0, 1.0, -1.0, -2.0, 1.0, 1.0] - KNITRO.KN_add_con_linear_struct(kc, jacIndexCons, jacIndexVars, jacCoefs) + KNITRO.KN_add_con_linear_struct( + kc, + length(jacIndexCons), + jacIndexCons, + jacIndexVars, + jacCoefs, + ) # Add a callback function "callbackEvalFC" to evaluate the nonlinear # structure in the objective and first two constraints. Note that @@ -271,7 +283,7 @@ function example_minlp1(; verbose=true) # Specify that the user is able to provide evaluations # of the Hessian matrix without the objective component. # turned off by default but should be enabled if possible. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) # Set minimize or maximize(if not set, assumed minimize) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) @@ -281,7 +293,7 @@ function example_minlp1(; verbose=true) KNITRO.KN_set_mip_node_callback(kc, callbackProcessNode) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the problem. # diff --git a/examples/mpec1.jl b/examples/mpec1.jl index 3a8d3b2..70adf51 100644 --- a/examples/mpec1.jl +++ b/examples/mpec1.jl @@ -47,12 +47,12 @@ function example_mpec1(; verbose=true) # Add the variables and set their bounds and initial values. # Note: unset bounds assumed to be infinite. - KNITRO.KN_add_vars(kc, 8) + KNITRO.KN_add_vars(kc, 8, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, zeros(Float64, 8)) KNITRO.KN_set_var_primal_init_values_all(kc, zeros(Float64, 8)) # Add the constraints and set their bounds. - KNITRO.KN_add_cons(kc, 4) + KNITRO.KN_add_cons(kc, 4, C_NULL) KNITRO.KN_set_con_eqbnds_all(kc, Float64[2, 3, -4, -7]) # Add coefficients for all linear constraints at once. @@ -77,7 +77,8 @@ function example_mpec1(; verbose=true) lconIndexVars = [lconIndexVars; Int32[0, 1, 7]] lconCoefs = [lconCoefs; [-1.0, -1.0, -1.0]] - KNITRO.KN_add_con_linear_struct(kc, lconIndexCons, lconIndexVars, lconCoefs) + nnz = length(lconIndexCons) + KNITRO.KN_add_con_linear_struct(kc, nnz, lconIndexCons, lconIndexVars, lconCoefs) # Note that the objective(x0 - 5)^2 +(2 x1 + 1)^2 when # expanded becomes: @@ -87,12 +88,12 @@ function example_mpec1(; verbose=true) qobjIndexVars1 = Int32[0, 1] qobjIndexVars2 = Int32[0, 1] qobjCoefs = [1.0, 4.0] - KNITRO.KN_add_obj_quadratic_struct(kc, qobjIndexVars1, qobjIndexVars2, qobjCoefs) + KNITRO.KN_add_obj_quadratic_struct(kc, 2, qobjIndexVars1, qobjIndexVars2, qobjCoefs) # Add linear coefficients for the objective lobjIndexVars = Int32[0, 1] lobjCoefs = [-10.0, 4.0] - KNITRO.KN_add_obj_linear_struct(kc, lobjIndexVars, lobjCoefs) + KNITRO.KN_add_obj_linear_struct(kc, 2, lobjIndexVars, lobjCoefs) # Add constant to the objective KNITRO.KN_add_obj_constant(kc, 26.0) @@ -107,7 +108,7 @@ function example_mpec1(; verbose=true) KNITRO.KN_set_compcons(kc, 3, ccTypes, indexComps1, indexComps2) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the problem. # @@ -119,6 +120,10 @@ function example_mpec1(; verbose=true) nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) if verbose + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x0=", x[1]) @@ -126,8 +131,8 @@ function example_mpec1(; verbose=true) println(" x2=", (x[3], x[6])) println(" x3=", (x[4], x[7])) println(" x4=", (x[5], x[8])) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/examples/multipleCB.jl b/examples/multipleCB.jl index 872b4f0..2578b73 100644 --- a/examples/multipleCB.jl +++ b/examples/multipleCB.jl @@ -143,13 +143,15 @@ function example_multiple_cb(; verbose=true) # Note: any unset lower bounds are assumed to be # unbounded below and any unset upper bounds are # assumed to be unbounded above. - xIndices = KNITRO.KN_add_vars(kc, 4) + xIndices = zeros(Cint, 4) + KNITRO.KN_add_vars(kc, 4, xIndices) for x in xIndices KNITRO.KN_set_var_primal_init_value(kc, x, 0.8) end # Add the constraints and set the rhs and coefficients - cIndices = KNITRO.KN_add_cons(kc, 3) + cIndices = zeros(Cint, 3) + KNITRO.KN_add_cons(kc, 3, cIndices) KNITRO.KN_set_con_eqbnd(kc, cIndices[1], 1.0) KNITRO.KN_set_con_eqbnd(kc, cIndices[2], 0.0) KNITRO.KN_set_con_eqbnd(kc, cIndices[3], 0.0) @@ -158,7 +160,7 @@ function example_multiple_cb(; verbose=true) lconIndexCons = Int32[1, 2] lconIndexVars = Int32[2, 1] lconCoefs = [-1.0, -1.0] - KNITRO.KN_add_con_linear_struct(kc, lconIndexCons, lconIndexVars, lconCoefs) + KNITRO.KN_add_con_linear_struct(kc, 2, lconIndexCons, lconIndexVars, lconCoefs) # Coefficients for 2 quadratic terms @@ -171,6 +173,7 @@ function example_multiple_cb(; verbose=true) KNITRO.KN_add_con_quadratic_struct( kc, + 2, qconIndexCons, qconIndexVars1, qconIndexVars2, @@ -226,10 +229,10 @@ function example_multiple_cb(; verbose=true) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MAXIMIZE) # Approximate hessian using BFGS - KNITRO.KN_set_param(kc, "hessopt", KNITRO.KN_HESSOPT_BFGS) + KNITRO.KN_set_int_param_by_name(kc, "hessopt", KNITRO.KN_HESSOPT_BFGS) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the problem. # @@ -240,11 +243,15 @@ function example_multiple_cb(; verbose=true) # An example of obtaining solution information. if verbose + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/examples/multistart.jl b/examples/multistart.jl index 4dd040b..c9e209c 100644 --- a/examples/multistart.jl +++ b/examples/multistart.jl @@ -93,11 +93,14 @@ function example_multistart(; verbose=true) function callbackMSProcess(kcSub, x, lambda_, userParams) # Print solution of the just completed multi-start solve. - n = KNITRO.KN_get_number_vars(kc) + pCint = Ref{Cint}() + KNITRO.KN_get_number_vars(kc, pCint) if verbose println("callbackMSProcess: ") - println(" Last solution: obj= ", KNITRO.KN_get_obj_value(kc)) - for i in 1:n + pCdouble = Ref{Cdouble}() + KNITRO.KN_get_obj_value(kc, pCdouble) + println(" Last solution: obj= ", pCdouble[]) + for i in 1:pCint[] println(" x[$i]= ", x[i]) end end @@ -118,7 +121,7 @@ function example_multistart(; verbose=true) # unbounded below and any unset upper bounds are # assumed to be unbounded above. n = 2 - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, [-KNITRO.KN_INFINITY, -KNITRO.KN_INFINITY]) # not necessary since infinite KNITRO.KN_set_var_upbnds_all(kc, [0.5, KNITRO.KN_INFINITY]) # Define an initial point. If not set, Knitro will generate one. @@ -126,14 +129,14 @@ function example_multistart(; verbose=true) # Add the constraints and set their lower bounds m = 2 - KNITRO.KN_add_cons(kc, m) + KNITRO.KN_add_cons(kc, m, C_NULL) KNITRO.KN_set_con_lobnds_all(kc, [1.0, 0.0]) # Both constraints are quadratic so we can directly load all the # structure for these constraints. # First load quadratic structure x0*x1 for the first constraint - KNITRO.KN_add_con_quadratic_struct(kc, 0, 0, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 0, Cint[0], Cint[1], [1.0]) # Load structure for the second constraint. below we add the linear # structure and the quadratic structure separately, though it @@ -142,10 +145,10 @@ function example_multistart(; verbose=true) # supports adding linear terms. # Add linear term x0 in the second constraint - KNITRO.KN_add_con_linear_struct(kc, 1, 0, 1.0) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 1, Cint[0], Cint[1.0]) # Add quadratic term x1^2 in the second constraint - KNITRO.KN_add_con_quadratic_struct(kc, 1, 1, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 1, Cint[1], Cint[1], [1.0]) # Add a callback function "callbackEvalF" to evaluate the nonlinear # (non-quadratic) objective. Note that the linear and @@ -179,7 +182,7 @@ function example_multistart(; verbose=true) # Specify that the user is able to provide evaluations # of the hessian matrix without the objective component. # turned off by default but should be enabled if possible. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) # Set minimize or maximize(if not set, assumed minimze) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) @@ -189,13 +192,13 @@ function example_multistart(; verbose=true) KNITRO.KN_set_ms_process_callback(kc, callbackMSProcess) # # Disable automatic scaling. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_SCALE, KNITRO.KN_SCALE_NO) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_SCALE, KNITRO.KN_SCALE_NO) # Enable multi-start - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_MULTISTART, KNITRO.KN_MULTISTART_YES) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_MULTISTART, KNITRO.KN_MULTISTART_YES) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Perform multistart in parallel using max number of available threads nThreads = Sys.CPU_THREADS @@ -203,7 +206,7 @@ function example_multistart(; verbose=true) println( "Force Knitro multistart to run in parallel with 1 threads (instead of $nThreads).", ) - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_MS_NUMTHREADS, 1) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_MS_NUMTHREADS, 1) end # Solve the problem. @@ -223,12 +226,17 @@ function example_multistart(; verbose=true) println(" x[$i] = ", x[i], "(lambda = ", lambda_[m+i], ")") end println("Optimal constraint values(with corresponding multiplier)") - c = KNITRO.KN_get_con_values(kc) + c = zeros(Cdouble, m) + KNITRO.KN_get_con_values_all(kc, c) for j in 1:m println(" c[$j] = ", c[j], "(lambda = ", lambda_[j], ")") end - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. @@ -236,8 +244,8 @@ function example_multistart(; verbose=true) @testset "Example HS15 multistart" begin @test nStatus == 0 - @test objSol ≈ 306.5 - @test x ≈ [0.5, 2] + @test isapprox(objSol, 306.5; atol=1e-3) + @test isapprox(x, [0.5, 2]; atol=1e-3) end end diff --git a/examples/nlp1.jl b/examples/nlp1.jl index 12f42ca..c57a296 100644 --- a/examples/nlp1.jl +++ b/examples/nlp1.jl @@ -93,7 +93,7 @@ function example_nlp1(; verbose=true) KNITRO.KN_load_param_file(kc, options) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Initialize Knitro with the problem definition. @@ -102,7 +102,7 @@ function example_nlp1(; verbose=true) # unbounded below and any unset upper bounds are # assumed to be unbounded above. n = 2 - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, [-KNITRO.KN_INFINITY, -KNITRO.KN_INFINITY]) # not necessary since infinite KNITRO.KN_set_var_upbnds_all(kc, [0.5, KNITRO.KN_INFINITY]) # Define an initial point. If not set, Knitro will generate one. @@ -110,14 +110,14 @@ function example_nlp1(; verbose=true) # Add the constraints and set their lower bounds m = 2 - KNITRO.KN_add_cons(kc, m) + KNITRO.KN_add_cons(kc, m, C_NULL) KNITRO.KN_set_con_lobnds_all(kc, [1.0, 0.0]) # Both constraints are quadratic so we can directly load all the # structure for these constraints. # First load quadratic structure x0*x1 for the first constraint - KNITRO.KN_add_con_quadratic_struct(kc, 0, 0, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 0, Cint[0], Cint[1], [1.0]) # Load structure for the second constraint. below we add the linear # structure and the quadratic structure separately, though it @@ -126,10 +126,10 @@ function example_nlp1(; verbose=true) # supports adding linear terms. # Add linear term x0 in the second constraint - KNITRO.KN_add_con_linear_struct(kc, 1, 0, 1.0) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 1, Cint[0], [1.0]) # Add quadratic term x1^2 in the second constraint - KNITRO.KN_add_con_quadratic_struct(kc, 1, 1, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 1, Cint[1], Cint[1], [1.0]) # Add a callback function "callbackEvalF" to evaluate the nonlinear #(non-quadratic) objective. Note that the linear and @@ -163,13 +163,13 @@ function example_nlp1(; verbose=true) # Specify that the user is able to provide evaluations # of the hessian matrix without the objective component. # turned off by default but should be enabled if possible. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) # Set minimize or maximize(if not set, assumed minimize) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) # Perform a derivative check. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_DERIVCHECK, KNITRO.KN_DERIVCHECK_ALL) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_DERIVCHECK, KNITRO.KN_DERIVCHECK_ALL) # Solve the problem. # @@ -187,12 +187,17 @@ function example_nlp1(; verbose=true) println(" x[$i] = ", x[i], "(lambda = ", lambda_[m+i], ")") end println("Optimal constraint values(with corresponding multiplier)") - c = KNITRO.KN_get_con_values(kc) + c = zeros(Cdouble, m) + KNITRO.KN_get_con_values_all(kc, c) for j in 1:m println(" c[$j] = ", c[j], "(lambda = ", lambda_[j], ")") end - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/examples/nlp1_hessian_product.jl b/examples/nlp1_hessian_product.jl index 2f50187..aa20c6f 100644 --- a/examples/nlp1_hessian_product.jl +++ b/examples/nlp1_hessian_product.jl @@ -95,7 +95,7 @@ function example_nlp1_hessian_product(; verbose=true) options = joinpath(dirname(@__FILE__), "..", "examples", "knitro.opt") KNITRO.KN_load_param_file(kc, options) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Initialize Knitro with the problem definition. @@ -104,7 +104,7 @@ function example_nlp1_hessian_product(; verbose=true) # unbounded below and any unset upper bounds are # assumed to be unbounded above. n = 2 - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, [-KNITRO.KN_INFINITY, -KNITRO.KN_INFINITY]) # not necessary since infinite KNITRO.KN_set_var_upbnds_all(kc, [0.5, KNITRO.KN_INFINITY]) # Define an initial point. If not set, Knitro will generate one. @@ -112,14 +112,14 @@ function example_nlp1_hessian_product(; verbose=true) # Add the constraints and set their lower bounds m = 2 - KNITRO.KN_add_cons(kc, m) + KNITRO.KN_add_cons(kc, m, C_NULL) KNITRO.KN_set_con_lobnds_all(kc, [1.0, 0.0]) # Both constraints are quadratic so we can directly load all the # structure for these constraints. # First load quadratic structure x0*x1 for the first constraint - KNITRO.KN_add_con_quadratic_struct(kc, 0, 0, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 0, Cint[0], Cint[1], [1.0]) # Load structure for the second constraint. below we add the linear # structure and the quadratic structure separately, though it @@ -128,9 +128,9 @@ function example_nlp1_hessian_product(; verbose=true) # supports adding linear terms. # Add linear term x0 in the second constraint - KNITRO.KN_add_con_linear_struct(kc, 1, 0, 1.0) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 1, Cint[0], [1.0]) # Add quadratic term x1^2 in the second constraint - KNITRO.KN_add_con_quadratic_struct(kc, 1, 1, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 1, Cint[1], Cint[1], [1.0]) # Add a callback function "callbackEvalF" to evaluate the nonlinear #(non-quadratic) objective. Note that the linear and @@ -158,24 +158,24 @@ function example_nlp1_hessian_product(; verbose=true) # in Knitro. KNITRO.KN_set_cb_hess(kc, cb, 0, callbackEvalHv!) # We should specify to knitro to use Hessian vector product here. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSOPT, KNITRO.KN_HESSOPT_PRODUCT) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSOPT, KNITRO.KN_HESSOPT_PRODUCT) # Sometimes, you may want to add a preconditionner to reduce the # number of CG iterations in Knitro. Switch to 1 to activate. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_CG_PRECOND, 0) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_CG_PRECOND, 0) # Specify that the user is able to provide evaluations # of the hessian matrix without the objective component. # turned off by default but should be enabled if possible. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) # Set minimize or maximize(if not set, assumed minimize) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) # Perform a derivative check. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_DERIVCHECK, KNITRO.KN_DERIVCHECK_ALL) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_DERIVCHECK, KNITRO.KN_DERIVCHECK_ALL) # Increase tolerance on optimality conditions - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OPTTOL, 1e-10) + KNITRO.KN_set_double_param(kc, KNITRO.KN_PARAM_OPTTOL, 1e-10) # Solve the problem. # @@ -185,7 +185,8 @@ function example_nlp1_hessian_product(; verbose=true) # An example of obtaining solution information. nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) - c = KNITRO.KN_get_con_values(kc) + c = zeros(Cdouble, m) + KNITRO.KN_get_con_values_all(kc, c) if verbose println("Optimal objective value = ", objSol) @@ -197,8 +198,12 @@ function example_nlp1_hessian_product(; verbose=true) for j in 1:m println(" c[$j] = ", c[j], "(lambda = ", lambda_[j], ")") end - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/examples/nlp1noderivs.jl b/examples/nlp1noderivs.jl index c57a93e..c638e07 100644 --- a/examples/nlp1noderivs.jl +++ b/examples/nlp1noderivs.jl @@ -56,7 +56,7 @@ function example_nlp1noderivs(; verbose=true) options = joinpath(dirname(@__FILE__), "..", "examples", "knitro.opt") KNITRO.KN_load_param_file(kc, options) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ALL : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Initialize Knitro with the problem definition. @@ -65,7 +65,7 @@ function example_nlp1noderivs(; verbose=true) # unbounded below and any unset upper bounds are # assumed to be unbounded above. n = 2 - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, [-KNITRO.KN_INFINITY, -KNITRO.KN_INFINITY]) # not necessary since infinite KNITRO.KN_set_var_upbnds_all(kc, [0.5, KNITRO.KN_INFINITY]) # Define an initial point. If not set, Knitro will generate one. @@ -73,14 +73,14 @@ function example_nlp1noderivs(; verbose=true) # Add the constraints and set their lower bounds m = 2 - KNITRO.KN_add_cons(kc, m) + KNITRO.KN_add_cons(kc, m, C_NULL) KNITRO.KN_set_con_lobnds_all(kc, [1.0, 0.0]) # Both constraints are quadratic so we can directly load all the # structure for these constraints. # First load quadratic structure x0*x1 for the first constraint - KNITRO.KN_add_con_quadratic_struct(kc, 0, 0, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 0, Cint[0], Cint[1], [1.0]) # Load structure for the second constraint. below we add the linear # structure and the quadratic structure separately, though it @@ -91,13 +91,13 @@ function example_nlp1noderivs(; verbose=true) # Add linear term x0 in the second constraint indexVar1 = 0 coef = 1.0 - KNITRO.KN_add_con_linear_struct(kc, 1, 0, 1.0) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 1, Cint[0], [1.0]) # Add quadratic term x1^2 in the second constraint indexVar1 = 1 indexVar2 = 1 coef = 1.0 - KNITRO.KN_add_con_quadratic_struct(kc, 1, 1, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 1, Cint[1], Cint[1], [1.0]) # Add a callback function "callbackEvalF" to evaluate the nonlinear #(non-quadratic) objective. Note that the linear and @@ -115,7 +115,7 @@ function example_nlp1noderivs(; verbose=true) # active-set algorithm("KNITRO.KN_ALG_ACT_CG") may be preferable for # derivative-free optimization models with expensive function # evaluations. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_ALGORITHM, KNITRO.KN_ALG_ACT_SQP) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_ALGORITHM, KNITRO.KN_ALG_ACT_SQP) # Solve the problem. # @@ -132,12 +132,17 @@ function example_nlp1noderivs(; verbose=true) println(" x[$i] = ", x[i], "(lambda = ", lambda_[m+i], ")") end println("Optimal constraint values(with corresponding multiplier)") - c = KNITRO.KN_get_con_values(kc) + c = zeros(Cdouble, m) + KNITRO.KN_get_con_values_all(kc, c) for j in 1:m println(" c[$j] = ", c[j], "(lambda = ", lambda_[j], ")") end - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/examples/nlp2.jl b/examples/nlp2.jl index 92faea6..53d4da3 100644 --- a/examples/nlp2.jl +++ b/examples/nlp2.jl @@ -111,20 +111,26 @@ function example_nlp2(; verbose=true) function callbackNewPoint(kc, x, lambda_, userParams) # Get the number of variables in the model - n = KNITRO.KN_get_number_vars(kc) + n = Ref{Cint}() + KNITRO.KN_get_number_vars(kc, n) # Query information about the current problem. - dFeasError = KNITRO.KN_get_abs_feas_error(kc) + dFeasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, dFeasError) if verbose println(">> New point computed by Knitro:(", x, ")") - println("Number FC evals= ", KNITRO.KN_get_number_FC_evals(kc)) - println("Current feasError= ", dFeasError) + pCint = Ref{Cint}() + KNITRO.KN_get_number_FC_evals(kc, pCint) + println("Number FC evals= ", pCint[]) + println("Current feasError= ", dFeasError[]) end # Demonstrate user-defined termination #(Uncomment to activate) - if KNITRO.KN_get_obj_value(kc) > 0.2 && dFeasError <= 1.0e-4 + pObj = Ref{Cdouble}() + KNITRO.KN_get_obj_value(kc, pObj) + if pObj[] > 0.2 && dFeasError[] <= 1.0e-4 return KNITRO.KN_RC_USER_TERMINATION end @@ -144,20 +150,21 @@ function example_nlp2(; verbose=true) # Note: any unset lower bounds are assumed to be # unbounded below and any unset upper bounds are # assumed to be unbounded above. - xIndices = KNITRO.KN_add_vars(kc, 4) + xIndices = zeros(Cint, 4) + KNITRO.KN_add_vars(kc, 4, xIndices) for x in xIndices KNITRO.KN_set_var_primal_init_value(kc, x, 0.8) end # Add the constraints and set the rhs and coefficients - KNITRO.KN_add_cons(kc, 3) + KNITRO.KN_add_cons(kc, 3, C_NULL) KNITRO.KN_set_con_eqbnds_all(kc, [1.0, 0.0, 0.0]) # Coefficients for 2 linear terms lconIndexCons = Int32[1, 2] lconIndexVars = Int32[2, 1] lconCoefs = [-1.0, -1.0] - KNITRO.KN_add_con_linear_struct(kc, lconIndexCons, lconIndexVars, lconCoefs) + KNITRO.KN_add_con_linear_struct(kc, 2, lconIndexCons, lconIndexVars, lconCoefs) # Coefficients for 2 quadratic terms @@ -170,6 +177,7 @@ function example_nlp2(; verbose=true) KNITRO.KN_add_con_quadratic_struct( kc, + 2, qconIndexCons, qconIndexVars1, qconIndexVars2, @@ -224,7 +232,7 @@ function example_nlp2(; verbose=true) # Set option to println output after every iteration. kn_outlev = verbose ? KNITRO.KN_OUTLEV_ITER : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the problem. # @@ -239,8 +247,12 @@ function example_nlp2(; verbose=true) println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/examples/nlp2noderivs.jl b/examples/nlp2noderivs.jl index 4aec3a9..f29b5c7 100644 --- a/examples/nlp2noderivs.jl +++ b/examples/nlp2noderivs.jl @@ -50,20 +50,21 @@ function example_nlp2noderivs(; verbose=true) # Note: any unset lower bounds are assumed to be # unbounded below and any unset upper bounds are # assumed to be unbounded above. - xIndices = KNITRO.KN_add_vars(kc, 4) + xIndices = zeros(Cint, 4) + KNITRO.KN_add_vars(kc, 4, xIndices) for x in xIndices KNITRO.KN_set_var_primal_init_value(kc, x, 0.8) end # Add the constraints and set the rhs and coefficients - KNITRO.KN_add_cons(kc, 3) + KNITRO.KN_add_cons(kc, 3, C_NULL) KNITRO.KN_set_con_eqbnds_all(kc, [1.0, 0.0, 0.0]) # Coefficients for 2 linear terms lconIndexCons = Int32[1, 2] lconIndexVars = Int32[2, 1] lconCoefs = [-1.0, -1.0] - KNITRO.KN_add_con_linear_struct(kc, lconIndexCons, lconIndexVars, lconCoefs) + KNITRO.KN_add_con_linear_struct(kc, 2, lconIndexCons, lconIndexVars, lconCoefs) # Coefficients for 2 quadratic terms @@ -76,6 +77,7 @@ function example_nlp2noderivs(; verbose=true) KNITRO.KN_add_con_quadratic_struct( kc, + 2, qconIndexCons, qconIndexVars1, qconIndexVars2, @@ -93,7 +95,7 @@ function example_nlp2noderivs(; verbose=true) # Set option to println output after every iteration. kn_outlev = verbose ? KNITRO.KN_OUTLEV_ITER : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the problem. # @@ -101,18 +103,23 @@ function example_nlp2noderivs(; verbose=true) # in the Knitro manual. nStatus = KNITRO.KN_solve(kc) nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) - varbndInfeas, varintInfeas, varviols = KNITRO.KN_get_var_viols(kc, Cint[0, 1, 2, 3]) - coninfeas, conviols = KNITRO.KN_get_con_viols(kc, Cint[0, 1, 2]) - err = KNITRO.KN_get_presolve_error(kc) + varbndInfeas, varintInfeas, varviols = zeros(Cint, 4), zeros(Cint, 4), zeros(Cdouble, 4) + KNITRO.KN_get_var_viols(kc, 4, Cint[0, 1, 2, 3], varbndInfeas, varintInfeas, varviols) + coninfeas, conviols = zeros(Cint, 3), zeros(Cdouble, 3) + KNITRO.KN_get_con_viols(kc, 3, Cint[0, 1, 2], coninfeas, conviols) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) if verbose println() println("Knitro converged with final status = ", nStatus) # An example of obtaining solution information. println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) println("Variables bound violations = ", varbndInfeas) println("Variables integrality violations = ", varintInfeas) println("Variables violation values = ", varviols) @@ -126,7 +133,7 @@ function example_nlp2noderivs(; verbose=true) @test varviols ≈ [0.0, 0.0, 0.0, 0.0] atol = 1e-6 @test coninfeas == [0, 0, 0] @test conviols ≈ [0.0, 0.0, 0.0] atol = 1e-6 - @test KNITRO.KN_get_abs_feas_error(kc) == max(conviols...) + @test feasError[] == max(conviols...) end # Delete the Knitro solver instance. diff --git a/examples/nlp2resolve.jl b/examples/nlp2resolve.jl index 388dbe6..ada8a15 100644 --- a/examples/nlp2resolve.jl +++ b/examples/nlp2resolve.jl @@ -124,20 +124,21 @@ function example_nlp2resolve(; verbose=true) # Note: any unset lower bounds are assumed to be # unbounded below and any unset upper bounds are # assumed to be unbounded above. - xIndices = KNITRO.KN_add_vars(kc, 4) + xIndices = zeros(Cint, 4) + KNITRO.KN_add_vars(kc, 4, xIndices) for x in xIndices KNITRO.KN_set_var_primal_init_value(kc, x, 0.8) end # Add the constraints and set the rhs and coefficients - KNITRO.KN_add_cons(kc, 3) + KNITRO.KN_add_cons(kc, 3, C_NULL) KNITRO.KN_set_con_eqbnds_all(kc, [1.0, 0.0, 0.0]) # Coefficients for 2 linear terms lconIndexCons = Int32[1, 2] lconIndexVars = Int32[2, 1] lconCoefs = [-1.0, -1.0] - KNITRO.KN_add_con_linear_struct(kc, lconIndexCons, lconIndexVars, lconCoefs) + KNITRO.KN_add_con_linear_struct(kc, 2, lconIndexCons, lconIndexVars, lconCoefs) # Coefficients for 2 quadratic terms @@ -150,6 +151,7 @@ function example_nlp2resolve(; verbose=true) KNITRO.KN_add_con_quadratic_struct( kc, + 2, qconIndexCons, qconIndexVars1, qconIndexVars2, @@ -199,7 +201,7 @@ function example_nlp2resolve(; verbose=true) # Set option to println output after every iteration. kn_outlev = verbose ? KNITRO.KN_OUTLEV_ITER : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Solve the initial problem. # @@ -214,22 +216,28 @@ function example_nlp2resolve(; verbose=true) nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # =============== MODIFY PROBLEM AND RE-SOLVE =========== # Add 0.5x3 linear term to c2 - KNITRO.KN_add_con_linear_struct(kc, 2, 3, 0.5) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 2, Cint[3], [0.5]) # Now add a new linear constraint x1 + 2x2 + x3 <= 2.5 (c3) and re-solve - cIndNew = KNITRO.KN_add_con(kc) + pcIndNew = Ref{Cint}() + KNITRO.KN_add_con(kc, pcIndNew) + cIndNew = pcIndNew[] KNITRO.KN_set_con_upbnd(kc, cIndNew, 2.5) - KNITRO.KN_add_con_linear_struct(kc, cIndNew, Int32[1, 2, 3], [1.0, 2.0, 1.0]) + KNITRO.KN_add_con_linear_struct_one(kc, 3, cIndNew, Int32[1, 2, 3], [1.0, 2.0, 1.0]) # Tell Knitro to try a "warm-start" since it is starting from the solution # of the previous solve, which may be a good initial point for the solution # of the slightly modified problem. - KNITRO.KN_set_param( + KNITRO.KN_set_int_param( kc, KNITRO.KN_PARAM_STRAT_WARM_START, KNITRO.KN_STRAT_WARM_START_YES, @@ -251,8 +259,12 @@ function example_nlp2resolve(; verbose=true) nStatus, objSol, x, lambda_ = KNITRO.KN_get_solution(kc) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # =============== MODIFY PROBLEM AND RE-SOLVE AGAIN =========== @@ -274,8 +286,12 @@ function example_nlp2resolve(; verbose=true) println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end @testset "Example HS40 nlp2resolve" begin diff --git a/examples/qcqp1.jl b/examples/qcqp1.jl index 87cfc2b..ec96cfa 100644 --- a/examples/qcqp1.jl +++ b/examples/qcqp1.jl @@ -30,18 +30,18 @@ function example_qcqp1(; verbose=true) options = joinpath(dirname(@__FILE__), "..", "examples", "knitro.opt") KNITRO.KN_load_param_file(kc, options) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ITER : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Initialize Knitro with the problem definition. # Add the variables and set their bounds and initial values. # Note: unset bounds assumed to be infinite. - KNITRO.KN_add_vars(kc, 3) + KNITRO.KN_add_vars(kc, 3, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, [0.0, 0.0, 0.0]) KNITRO.KN_set_var_primal_init_values_all(kc, [2.0, 2.0, 2.0]) # Add the constraints and set their bounds. - KNITRO.KN_add_cons(kc, 2) + KNITRO.KN_add_cons(kc, 2, C_NULL) KNITRO.KN_set_con_eqbnd(kc, 0, 56.0) KNITRO.KN_set_con_lobnd(kc, 1, 25.0) @@ -85,11 +85,15 @@ function example_qcqp1(; verbose=true) # An example of obtaining solution information. if verbose + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/examples/qp1.jl b/examples/qp1.jl index 1e76397..442bc58 100644 --- a/examples/qp1.jl +++ b/examples/qp1.jl @@ -37,20 +37,20 @@ function example_qp1(; verbose=true) options = joinpath(dirname(@__FILE__), "..", "examples", "knitro.opt") KNITRO.KN_load_param_file(kc, options) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ITER : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Initialize Knitro with the problem definition. # Add the variables and set their bounds. # Note: unset bounds assumed to be infinite. - KNITRO.KN_add_vars(kc, 3) + KNITRO.KN_add_vars(kc, 3, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, [0.0, 0.0, -3.0]) KNITRO.KN_set_var_upbnd(kc, 2, 2.0) # Add the constraint and set the bound and coefficient. - KNITRO.KN_add_cons(kc, 1) + KNITRO.KN_add_cons(kc, 1, C_NULL) KNITRO.KN_set_con_upbnd(kc, 0, 5.0) - KNITRO.KN_add_con_linear_struct(kc, 0, 2, -6.0) + KNITRO.KN_add_con_linear_struct(kc, 1, Cint[0], Cint[2], [-6.0]) # Set the coefficients for the objective - # can either set linear and quadratic objective structure @@ -62,12 +62,12 @@ function example_qp1(; verbose=true) # First set linear objective structure. lobjIndexVars = Int32[0, 2] lobjCoefs = [11.0, 1.0] - KNITRO.KN_add_obj_linear_struct(kc, lobjIndexVars, lobjCoefs) + KNITRO.KN_add_obj_linear_struct(kc, 2, lobjIndexVars, lobjCoefs) # Now set quadratic objective structure. qobjIndexVars1 = Int32[0, 1, 2] qobjIndexVars2 = Int32[0, 1, 2] qobjCoefs = [0.5, 0.5, 0.5] - KNITRO.KN_add_obj_quadratic_struct(kc, qobjIndexVars1, qobjIndexVars2, qobjCoefs) + KNITRO.KN_add_obj_quadratic_struct(kc, 3, qobjIndexVars1, qobjIndexVars2, qobjCoefs) else # Example of how to set linear and quadratic objective # structure at once. Setting the 2nd variable index in a @@ -75,7 +75,7 @@ function example_qp1(; verbose=true) indexVars1 = Int32[0, 1, 2, 0, 2] indexVars2 = Int32[0, 1, 2, -1, -1] # -1 for linear coefficients objCoefs = [0.5, 0.5, 0.5, 11.0, 1.0] - KNITRO.KN_add_obj_quadratic_struct(kc, indexVars1, indexVars2, objCoefs) + KNITRO.KN_add_obj_quadratic_struct(kc, 5, indexVars1, indexVars2, objCoefs) end # Set minimize or maximize (if not set, assumed minimize) @@ -83,7 +83,7 @@ function example_qp1(; verbose=true) # Enable iteration output and crossover procedure to try to # get more solution precision - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_BAR_MAXCROSSIT, 5) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_BAR_MAXCROSSIT, 5) # Solve the problem. # @@ -94,11 +94,15 @@ function example_qp1(; verbose=true) # An example of obtaining solution information. if verbose + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) println("Knitro converged with final status = ", nStatus) println(" optimal objective value = ", objSol) println(" optimal primal values x = ", x) - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. KNITRO.KN_free(kc) diff --git a/examples/restart.jl b/examples/restart.jl index 913e73c..0785951 100644 --- a/examples/restart.jl +++ b/examples/restart.jl @@ -95,7 +95,7 @@ function example_restart(; verbose=true) options = joinpath(dirname(@__FILE__), "..", "examples", "knitro.opt") KNITRO.KN_load_param_file(kc, options) kn_outlev = verbose ? KNITRO.KN_OUTLEV_ITER : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Initialize knitro with the problem definition. @@ -103,19 +103,19 @@ function example_restart(; verbose=true) # Note: any unset lower bounds are assumed to be # unbounded below and any unset upper bounds are # assumed to be unbounded above. - KNITRO.KN_add_vars(kc, 2) + KNITRO.KN_add_vars(kc, 2, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, [-KNITRO.KN_INFINITY, -KNITRO.KN_INFINITY]) # not necessary since infinite KNITRO.KN_set_var_upbnds_all(kc, [0.5, KNITRO.KN_INFINITY]) # Add the constraints and set their lower bounds - KNITRO.KN_add_cons(kc, 2) + KNITRO.KN_add_cons(kc, 2, C_NULL) KNITRO.KN_set_con_lobnds_all(kc, [1.0, 0.0]) # Both constraints are quadratic so we can directly load all the # structure for these constraints. # First load quadratic structure x0*x1 for the first constraint - KNITRO.KN_add_con_quadratic_struct(kc, 0, 0, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 0, Cint[0], Cint[1], [1.0]) # Load structure for the second constraint. below we add the linear # structure and the quadratic structure separately, though it @@ -124,10 +124,10 @@ function example_restart(; verbose=true) # supports adding linear terms. # Add linear term x0 in the second constraint - KNITRO.KN_add_con_linear_struct(kc, 1, 0, 1.0) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 1, Cint[0], [1.0]) # Add quadratic term x1^2 in the second constraint - KNITRO.KN_add_con_quadratic_struct(kc, 1, 1, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 1, Cint[1], Cint[1], [1.0]) # Add a callback function "callbackEvalF" to evaluate the nonlinear #(non-quadratic) objective. Note that the linear and @@ -161,14 +161,14 @@ function example_restart(; verbose=true) # specify that the user is able to provide evaluations # of the hessian matrix without the objective component. # turned off by default but should be enabled if possible. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) # Set minimize or maximize(if not set, assumed minimize) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) # Turn output off and use Interior/Direct algorithm - KNITRO.KN_set_param(kc, "outlev", KNITRO.KN_OUTLEV_NONE) - KNITRO.KN_set_param(kc, "algorithm", KNITRO.KN_ALG_BAR_DIRECT) + KNITRO.KN_set_int_param_by_name(kc, "outlev", KNITRO.KN_OUTLEV_NONE) + KNITRO.KN_set_int_param_by_name(kc, "algorithm", KNITRO.KN_ALG_BAR_DIRECT) # Solve the problem. # @@ -180,7 +180,7 @@ function example_restart(; verbose=true) # iteration in the barrier/interior-point solver. verbose && println("Changing a user option and re-solving...") for i in 1:6 - KNITRO.KN_set_param(kc, "bar_murule", i) + KNITRO.KN_set_int_param_by_name(kc, "bar_murule", i) # Reset original initial point KNITRO.KN_set_var_primal_init_values_all(kc, [-2.0, 1.0]) nStatus = KNITRO.KN_solve(kc) @@ -188,12 +188,16 @@ function example_restart(; verbose=true) if nStatus != 0 println(" bar_murule=$i - Knitro failed to solve, status = $nStatus") else + pIters, pEvals, pObj = Ref{Cint}(), Ref{Cint}(), Ref{Cdouble}() + KNITRO.KN_get_number_iters(kc, pIters) + KNITRO.KN_get_number_FC_evals(kc, pEvals) + KNITRO.KN_get_obj_value(kc, pObj) @printf( "\n bar_murule=%d - solved in %2d iters, %2d function evaluations, objective=%e", i, - KNITRO.KN_get_number_iters(kc), - KNITRO.KN_get_number_FC_evals(kc), - KNITRO.KN_get_obj_value(kc) + pIters[], + pEvals[], + pObj[], ) end end @@ -206,7 +210,7 @@ function example_restart(; verbose=true) # Change to the active-set algorithm and do not reset the # initial point, so the re-solves are "warm-started". verbose && println("\nChanging a variable bound and re-solving...") - KNITRO.KN_set_param(kc, "algorithm", KNITRO.KN_ALG_ACT_CG) + KNITRO.KN_set_int_param_by_name(kc, "algorithm", KNITRO.KN_ALG_ACT_CG) i = 0 for i in 1:20 @@ -224,10 +228,12 @@ function example_restart(; verbose=true) nStatus ) else + pIters = Ref{Cint}() + KNITRO.KN_get_number_iters(kc, pIters) @printf( "\n x0 upper bound=%e - solved in %2d iters, x0=%e, objective=%e", tmpbound, - KNITRO.KN_get_number_iters(kc), + pIters[], x[1], objSol ) @@ -251,7 +257,8 @@ function example_restart(; verbose=true) tmpbound = 1.0 - 0.1 * i KNITRO.KN_set_con_lobnd(kc, 0, tmpbound) nStatus = KNITRO.KN_solve(kc) - c0 = KNITRO.KN_get_con_values(kc, 0) + c0 = Ref{Cdouble}() + KNITRO.KN_get_con_value(kc, 0, c0) if verbose if nStatus != 0 @printf( @@ -260,16 +267,19 @@ function example_restart(; verbose=true) nStatus ) else + pIter, pObj = Ref{Cint}(), Ref{Cdouble}() + KNITRO.KN_get_number_iters(kc, pIter) + KNITRO.KN_get_obj_value(kc, pObj) @printf( "\n c0 lower bound=%e - solved in %2d iters, c0=%e, objective=%e", tmpbound, - KNITRO.KN_get_number_iters(kc), - c0, - KNITRO.KN_get_obj_value(kc) + pIter[], + c0[], + pObj[], ) end end - if nStatus != 0 || c0 > tmpbound + 1e-4 + if nStatus != 0 || c0[] > tmpbound + 1e-4 break end end diff --git a/examples/tuner.jl b/examples/tuner.jl index 1f47218..b57ca46 100644 --- a/examples/tuner.jl +++ b/examples/tuner.jl @@ -88,7 +88,7 @@ function example_tuner(; verbose=true) kc = KNITRO.KN_new() kn_outlev = verbose ? KNITRO.KN_OUTLEV_ITER : KNITRO.KN_OUTLEV_NONE - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_OUTLEV, kn_outlev) # Initialize Knitro with the problem definition. @@ -97,7 +97,7 @@ function example_tuner(; verbose=true) # unbounded below and any unset upper bounds are # assumed to be unbounded above. n = 2 - KNITRO.KN_add_vars(kc, n) + KNITRO.KN_add_vars(kc, n, C_NULL) KNITRO.KN_set_var_lobnds_all(kc, [-KNITRO.KN_INFINITY, -KNITRO.KN_INFINITY]) # not necessary since infinite KNITRO.KN_set_var_upbnds_all(kc, [0.5, KNITRO.KN_INFINITY]) # Define an initial point. If not set, Knitro will generate one. @@ -105,14 +105,14 @@ function example_tuner(; verbose=true) # Add the constraints and set their lower bounds m = 2 - KNITRO.KN_add_cons(kc, m) + KNITRO.KN_add_cons(kc, m, C_NULL) KNITRO.KN_set_con_lobnds_all(kc, [1.0, 0.0]) # Both constraints are quadratic so we can directly load all the # structure for these constraints. # First load quadratic structure x0*x1 for the first constraint - KNITRO.KN_add_con_quadratic_struct(kc, 0, 0, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 0, Cint[0], Cint[1], [1.0]) # Load structure for the second constraint. below we add the linear # structure and the quadratic structure separately, though it @@ -121,10 +121,10 @@ function example_tuner(; verbose=true) # supports adding linear terms. # Add linear term x0 in the second constraint - KNITRO.KN_add_con_linear_struct(kc, 1, 0, 1.0) + KNITRO.KN_add_con_linear_struct_one(kc, 1, 1, Cint[0], [1.0]) # Add quadratic term x1^2 in the second constraint - KNITRO.KN_add_con_quadratic_struct(kc, 1, 1, 1, 1.0) + KNITRO.KN_add_con_quadratic_struct_one(kc, 1, 1, Cint[1], Cint[1], [1.0]) # Add a callback function "callbackEvalF" to evaluate the nonlinear #(non-quadratic) objective. Note that the linear and @@ -158,7 +158,7 @@ function example_tuner(; verbose=true) # Specify that the user is able to provide evaluations # of the hessian matrix without the objective component. # turned off by default but should be enabled if possible. - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_HESSIAN_NO_F, KNITRO.KN_HESSIAN_NO_F_ALLOW) # Set minimize or maximize(if not set, assumed minimize) KNITRO.KN_set_obj_goal(kc, KNITRO.KN_OBJGOAL_MINIMIZE) @@ -170,7 +170,7 @@ function example_tuner(; verbose=true) KNITRO.KN_load_param_file(kc, options) # Turn on the Knitro-Tuner - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_TUNER, KNITRO.KN_TUNER_ON) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_TUNER, KNITRO.KN_TUNER_ON) # Use KNITRO.KN_load_tuner_file to specify the options and option # values that should be tuned by the Knitro-Tuner. The @@ -190,9 +190,9 @@ function example_tuner(; verbose=true) "Force Knitro multistart to run in parallel with 1 threads (instead of $nThreads).", ) if KNITRO.KNITRO_VERSION >= v"13.0" - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_NUMTHREADS, 1) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_NUMTHREADS, 1) else - KNITRO.KN_set_param(kc, KNITRO.KN_PARAM_PAR_NUMTHREADS, 1) + KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_PAR_NUMTHREADS, 1) end end @@ -213,12 +213,17 @@ function example_tuner(; verbose=true) println(" x[$i] = ", x[i], "(lambda = ", lambda_[m+i], ")") end println("Optimal constraint values(with corresponding multiplier)") - c = KNITRO.KN_get_con_values(kc) + c = zeros(Cdouble, m) + KNITRO.KN_get_con_values_all(kc, c) for j in 1:m println(" c[$j] = ", c[j], "(lambda = ", lambda_[m+j], ")") end - println(" feasibility violation = ", KNITRO.KN_get_abs_feas_error(kc)) - println(" KKT optimality violation = ", KNITRO.KN_get_abs_opt_error(kc)) + feasError = Ref{Cdouble}() + KNITRO.KN_get_abs_feas_error(kc, feasError) + optError = Ref{Cdouble}() + KNITRO.KN_get_abs_opt_error(kc, optError) + println(" feasibility violation = ", feasError[]) + println(" KKT optimality violation = ", optError[]) end # Delete the Knitro solver instance. diff --git a/test/runtests.jl b/test/runtests.jl index 40b02ce..47dc256 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,7 +16,8 @@ end examples_dir = joinpath(dirname(@__FILE__), "..", "examples") for file in filter(f -> endswith(f, ".jl"), readdir(examples_dir)) if !occursin("mps_reader", file) - # include(joinpath(examples_dir, file)) + @info "Executing $file" + include(joinpath(examples_dir, file)) end end end From fca0c2fd3c88afe8f9954e573fc1331d2b177afd Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 17:38:39 +1300 Subject: [PATCH 12/15] Update --- examples/nlp2noderivs.jl | 2 +- examples/nlp2resolve.jl | 2 +- examples/tuner.jl | 2 +- src/C_wrapper.jl | 9 +++------ src/KNITRO.jl | 23 ++++++++++------------- src/MOI_wrapper.jl | 4 ++-- test/C_wrapper.jl | 28 +++++++++++++++++----------- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/examples/nlp2noderivs.jl b/examples/nlp2noderivs.jl index f29b5c7..84867d9 100644 --- a/examples/nlp2noderivs.jl +++ b/examples/nlp2noderivs.jl @@ -140,7 +140,7 @@ function example_nlp2noderivs(; verbose=true) return KNITRO.KN_free(kc) end -if KNITRO.KNITRO_VERSION >= v"12.4" +if KNITRO.knitro_version() >= v"12.4" example_nlp2noderivs(; verbose=isdefined(Main, :KN_VERBOSE) ? KN_VERBOSE : true) else println("Example `nlp2noderivs.jl` is only available with Knitro >= 12.4") diff --git a/examples/nlp2resolve.jl b/examples/nlp2resolve.jl index ada8a15..026bd14 100644 --- a/examples/nlp2resolve.jl +++ b/examples/nlp2resolve.jl @@ -304,7 +304,7 @@ function example_nlp2resolve(; verbose=true) return KNITRO.KN_free(kc) end -if KNITRO.KNITRO_VERSION >= v"12.4" +if KNITRO.knitro_version() >= v"12.4" example_nlp2resolve(; verbose=isdefined(Main, :KN_VERBOSE) ? KN_VERBOSE : true) else println("Example `nlp2resolve.jl` is only available with Knitro >= 12.4") diff --git a/examples/tuner.jl b/examples/tuner.jl index b57ca46..0994045 100644 --- a/examples/tuner.jl +++ b/examples/tuner.jl @@ -189,7 +189,7 @@ function example_tuner(; verbose=true) println( "Force Knitro multistart to run in parallel with 1 threads (instead of $nThreads).", ) - if KNITRO.KNITRO_VERSION >= v"13.0" + if KNITRO.knitro_version() >= v"13.0" KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_NUMTHREADS, 1) else KNITRO.KN_set_int_param(kc, KNITRO.KN_PARAM_PAR_NUMTHREADS, 1) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index e18b01b..2899f5a 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -106,14 +106,11 @@ function KN_new() end function Base.show(io::IO, model::Model) - if model.env.ptr_env !== C_NULL + if model.env.ptr_env === C_NULL println(io, "KNITRO Problem: NULL") return end - out = zeros(Cchar, 15) - KN_get_release(15, out) - release = GC.@preserve(len, unsafe_string(pointer(out))) - println(io, "$get_release") + println(io, "$(knitro_version())") println(io, "-----------------------") println(io, "Problem Characteristics") println(io, "-----------------------") @@ -187,7 +184,7 @@ function KN_solve(model::Model) # as we have trouble calling Julia code from multithreaded C # code. See issue #93 on https://github.com/jump-dev/KNITRO.jl. if !isempty(model.callbacks) - if KNITRO_VERSION >= v"13.0" + if knitro_version() >= v"13.0" KN_set_int_param(model, KN_PARAM_MS_NUMTHREADS, 1) KN_set_int_param(model, KN_PARAM_NUMTHREADS, 1) KN_set_int_param(model, KN_PARAM_MIP_NUMTHREADS, 1) diff --git a/src/KNITRO.jl b/src/KNITRO.jl index d7d19bd..c39bfcd 100644 --- a/src/KNITRO.jl +++ b/src/KNITRO.jl @@ -16,24 +16,17 @@ else error("KNITRO.jl not properly installed. Please run `] build KNITRO`") end -const IS_KNITRO_LOADED = endswith(libknitro, Libdl.dlext) -KNITRO_VERSION = VersionNumber(0, 0, 0) # Fake a version for AutoMerge +has_knitro() = endswith(libknitro, Libdl.dlext) function __init__() libiomp5 = replace(libknitro, "libknitro" => "libiomp5") if isfile(libiomp5) Libdl.dlopen(libiomp5) end - if IS_KNITRO_LOADED - len = 15 - out = zeros(Cchar, len) - ccall((:KTR_get_release, libknitro), Any, (Cint, Ptr{Cchar}), len, out) - res = String(strip(String(convert(Vector{UInt8}, out)), '\0')) - global KNITRO_VERSION = VersionNumber(split(res, " ")[2]) - end - if KNITRO_VERSION != v"0.0.0" && KNITRO_VERSION < v"11.0" + version = has_knitro() ? knitro_version() : v"0.0.0" + if version != v"0.0.0" && version < v"11.0" error( - "You have installed version $KNITRO_VERSION of Artelys " * + "You have installed version $version of Artelys " * "Knitro, which is not supported by KNITRO.jl. We require a " * "Knitro version greater than 11.0.", ) @@ -41,8 +34,12 @@ function __init__() return end -has_knitro() = IS_KNITRO_LOADED -knitro_version() = KNITRO_VERSION +function knitro_version() + buffer = zeros(Cchar, 15) + ccall((:KTR_get_release, libknitro), Cint, (Cint, Ptr{Cchar}), 15, buffer) + version_string = GC.@preserve(buffer, unsafe_string(pointer(buffer))) + return VersionNumber(split(version_string, " ")[2]) +end include("libknitro.jl") include("C_wrapper.jl") diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 2a7eca9..a0d23cb 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -260,7 +260,7 @@ MOI.get(model::Optimizer, ::MOI.SolverName) = "Knitro" # MOI.SolverVersion -MOI.get(::Optimizer, ::MOI.SolverVersion) = string(KNITRO_VERSION) +MOI.get(::Optimizer, ::MOI.SolverVersion) = string(knitro_version()) # MOI.Silent @@ -1642,7 +1642,7 @@ end function MOI.get(model::Optimizer, ::MOI.SolveTimeSec) p = Ref{Cdouble}(NaN) - if KNITRO_VERSION >= v"12.0" + if knitro_version() >= v"12.0" KN_get_solve_time_cpu(model.inner, p) end return p[] diff --git a/test/C_wrapper.jl b/test/C_wrapper.jl index 42fe557..3d2419b 100644 --- a/test/C_wrapper.jl +++ b/test/C_wrapper.jl @@ -95,7 +95,7 @@ function callback(name) return callbackFn end -if KNITRO.KNITRO_VERSION >= v"12.1" +if KNITRO.knitro_version() >= v"12.1" @testset "MPS reader/writer" begin mps_name = joinpath(dirname(@__FILE__), "lp.mps") mps_name_out = joinpath(dirname(@__FILE__), "lp2.mps") @@ -206,7 +206,7 @@ end KNITRO.KN_set_con_upbnds_all(kc, [2 * 2 * 0.99]) # Test getters. - if KNITRO.KNITRO_VERSION >= v"12.0" + if KNITRO.knitro_version() >= v"12.0" xindex = Cint[0, 1, 2] ret = zeros(Cdouble, 3) KNITRO.KN_get_var_lobnds(kc, 3, xindex, ret) @@ -292,7 +292,7 @@ end @test objSol ≈ 31.363199 atol = 1e-5 # Test getters for primal and dual variables - if KNITRO.KNITRO_VERSION >= v"12.0" + if KNITRO.knitro_version() >= v"12.0" xopt = zeros(Cdouble, 3) KNITRO.KN_get_var_primal_values(kc, 3, Cint[0, 1, 2], xopt) @test xopt == x @@ -310,16 +310,15 @@ end @testset "Second problem test" begin kc = KNITRO.KN_new() - function prettyPrinting(str, userParams) - s = "KNITRO-Julia: " * str * "\n" - println(s) - return length(s) + function pretty_printer(contents::String, ::Any) + print("[KNITRO.jl] $contents") + return 12 + length(contents) end - KNITRO.KN_set_puts_callback(kc, prettyPrinting) + KNITRO.KN_set_puts_callback(kc, pretty_printer) # START: Some specific parameter settings - KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) + # KNITRO.KN_set_int_param_by_name(kc, "outlev", 0) KNITRO.KN_set_int_param_by_name(kc, "presolve", 0) KNITRO.KN_set_int_param_by_name(kc, "ms_enable", 1) KNITRO.KN_set_int_param_by_name(kc, "ms_maxsolves", 5) @@ -691,7 +690,7 @@ end end @testset "Knitro violation information" begin - if KNITRO.KNITRO_VERSION < v"12.4" + if KNITRO.knitro_version() < v"12.4" return 0 end #*------------------------------------------------------------------* @@ -789,7 +788,7 @@ end end @testset "Knitro structural manipulation" begin - if KNITRO.KNITRO_VERSION < v"12.4" + if KNITRO.knitro_version() < v"12.4" return 0 end #*------------------------------------------------------------------* @@ -1015,3 +1014,10 @@ end # Delete the Knitro solver instance. KNITRO.KN_free(kc) end + +@testset "show" begin + model = KNITRO.KN_new() + @test occursin("Problem Characteristics", sprint(show, model)) + KNITRO.KN_free(model) + @test sprint(show, model) == "KNITRO Problem: NULL\n" +end From 7cf43418e67b0c1cb029fbbf692be025cacc52a3 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 21 Nov 2023 19:22:57 +1300 Subject: [PATCH 13/15] Create .codecov.yml --- .codecov.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..9591d6e --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,2 @@ +ignore: + - 'src/libknitro.jl' From 8d1919683b91968d77bad51216680bc25c543eb3 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 21 Nov 2023 19:36:55 +1300 Subject: [PATCH 14/15] Update --- src/C_wrapper.jl | 68 ++++++++++++++++++---------------------------- src/MOI_wrapper.jl | 8 +++--- 2 files changed, 30 insertions(+), 46 deletions(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index 2899f5a..90b5e40 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -20,48 +20,34 @@ mutable struct CallbackContext context::Ptr{Cvoid} n::Int m::Int - # Add a dictionnary to store user params. user_data::Any - # Oracle's callbacks are context dependent, so store - # them inside dedicated CallbackContext. eval_f::Function eval_g::Function eval_h::Function eval_rsd::Function eval_jac_rsd::Function - function CallbackContext(ptr_cb::Ptr{Cvoid}) - return new(ptr_cb, 0, 0, nothing) - end + CallbackContext(ptr_cb::Ptr{Cvoid}) = new(ptr_cb, 0, 0, nothing) end Base.cconvert(::Type{Ptr{Cvoid}}, cb::CallbackContext) = cb Base.unsafe_convert(::Type{Ptr{Cvoid}}, cb::CallbackContext) = cb.context::Ptr{Cvoid} mutable struct Model - # KNITRO context environment. env::Env - # Keep reference to callbacks for garbage collector. callbacks::Vector{CallbackContext} - # Some structures for user_data - puts_user::Any - multistart_user::Any - mip_user::Any - newpoint_user::Any - - # Solution values. - # Optimization status. Equal to 1 if problem is unsolved. status::Cint obj_val::Cdouble x::Vector{Cdouble} - mult::Vector{Cdouble} - - # Special callbacks (undefined by default). - # (this functions do not depend on callback environments) - ms_process::Function + lambda::Vector{Cdouble} + newpt_user_data::Any + ms_process_user_data::Any + mip_node_user_data::Any + ms_initpt_user_data::Any + puts_user_data::Any newpt_callback::Function - mip_callback::Function - user_callback::Function + ms_process_callback::Function + mip_node_callback::Function ms_initpt_callback::Function puts_callback::Function @@ -69,14 +55,14 @@ mutable struct Model return new( env, CallbackContext[], + 1, + NaN, + Cdouble[], + Cdouble[], nothing, nothing, nothing, nothing, - 1, - Inf, - Cdouble[], - Cdouble[], ) end end @@ -136,8 +122,6 @@ Applications must not modify any part of the context. """ mutable struct LMcontext ptr_lmcontext::Ptr{Cvoid} - # Keep a pointer to instantiated models in order to free - # memory properly. linked_models::Vector{Model} function LMcontext() @@ -204,12 +188,12 @@ function KN_get_solution(model::Model) KN_get_number_vars(model, nx) KN_get_number_cons(model, nc) model.x = zeros(Cdouble, nx[]) - model.mult = zeros(Cdouble, nx[] + nc[]) + model.lambda = zeros(Cdouble, nx[] + nc[]) status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) - KN_get_solution(model, status, obj, model.x, model.mult) + KN_get_solution(model, status, obj, model.x, model.lambda) model.status = status[] model.obj_val = obj[] - return status[], obj[], model.x, model.mult + return status[], obj[], model.x, model.lambda end function KN_set_cb_user_params(model::Model, cb::CallbackContext, user_data=nothing) @@ -593,7 +577,7 @@ function _newpt_wrapper( KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - return model.newpt_callback(model, x, lambda, model.newpoint_user) + return model.newpt_callback(model, x, lambda, model.newpt_user_data) end end @@ -620,7 +604,7 @@ Note: Currently only active for continuous models. """ function KN_set_newpt_callback(model::Model, callback::Function, user_data=nothing) - model.newpt_callback, model.newpoint_user = callback, user_data + model.newpt_callback, model.newpt_user_data = callback, user_data c_func = @cfunction( _newpt_wrapper, Cint, @@ -645,7 +629,7 @@ function _ms_process_wrapper( KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - return model.ms_process(model, x, lambda, model.multistart_user) + return model.ms_process_callback(model, x, lambda, model.ms_process_user_data) end end @@ -668,7 +652,7 @@ the last solve. """ function KN_set_ms_process_callback(model::Model, callback::Function, user_data=nothing) - model.ms_process, model.multistart_user = callback, user_data + model.ms_process_callback, model.ms_process_user_data = callback, user_data c_func = @cfunction( _ms_process_wrapper, Cint, @@ -693,7 +677,7 @@ function _mip_node_callback_wrapper( KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - return model.mip_callback(model, x, lambda, model.mip_user) + return model.mip_node_callback(model, x, lambda, model.mip_node_user_data) end end @@ -716,7 +700,7 @@ Arguments `x` and `lambda` contain the solution from the node solve. """ function KN_set_mip_node_callback(model::Model, callback::Function, user_data=nothing) - model.mip_callback, model.mip_user = callback, user_data + model.mip_node_callback, model.mip_node_user_data = callback, user_data c_func = @cfunction( _mip_node_callback_wrapper, Cint, @@ -741,7 +725,7 @@ function _ms_initpt_wrapper( KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - return model.ms_initpt_callback(model, nSolveNumber, x, lambda, model.multistart_user) + return model.ms_initpt_callback(model, nSolveNumber, x, lambda, model.ms_initpt_user_data) end """ @@ -761,7 +745,7 @@ Knitro, which can be overwritten by the user. The argument `nSolveNumber` is th the multistart solve. """ function KN_set_ms_initpt_callback(model::Model, callback::Function, user_data=nothing) - model.ms_initpt_callback, model.multistart_user = callback, user_data + model.ms_initpt_callback, model.ms_initpt_user_data = callback, user_data c_func = @cfunction( _ms_initpt_wrapper, Cint, @@ -776,7 +760,7 @@ end function _puts_callback_wrapper(str::Ptr{Cchar}, user_data::Ptr{Cvoid})::Cint return _try_catch_handler() do model = unsafe_pointer_to_objref(user_data)::Model - return model.puts_callback(unsafe_string(str), model.puts_user) + return model.puts_callback(unsafe_string(str), model.puts_user_data) end end @@ -799,7 +783,7 @@ passed directly from KN_solve. The function should return the number of characters that were printed. """ function KN_set_puts_callback(model::Model, callback::Function, user_data=nothing) - model.puts_callback, model.puts_user = callback, user_data + model.puts_callback, model.puts_user_data = callback, user_data c_func = @cfunction(_puts_callback_wrapper, Cint, (Ptr{Cchar}, Ptr{Cvoid})) KN_set_puts_callback(model, c_func, pointer_from_objref(model)) return diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index a0d23cb..171b825 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1477,15 +1477,15 @@ end function _get_dual(model::Model, index::Integer) @assert model.env != C_NULL - if isempty(model.mult) + if isempty(model.lambda) nx, nc = Ref{Cint}(0), Ref{Cint}(0) KN_get_number_vars(model, nx) KN_get_number_cons(model, nc) - model.mult = zeros(Cdouble, nx[] + nc[]) + model.lambda = zeros(Cdouble, nx[] + nc[]) status, obj = Ref{Cint}(0), Ref{Cdouble}(0.0) - KN_get_solution(model, status, obj, C_NULL, model.mult) + KN_get_solution(model, status, obj, C_NULL, model.lambda) end - return model.mult[index] + return model.lambda[index] end function MOI.get(model::Optimizer, v::MOI.VariablePrimal, x::MOI.VariableIndex) From 1be5603cb87e3c73b418acca100b0ef239e51556 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 22 Nov 2023 11:10:55 +1300 Subject: [PATCH 15/15] Update src/C_wrapper.jl --- src/C_wrapper.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/C_wrapper.jl b/src/C_wrapper.jl index 90b5e40..b4eb92b 100644 --- a/src/C_wrapper.jl +++ b/src/C_wrapper.jl @@ -725,7 +725,13 @@ function _ms_initpt_wrapper( KN_get_number_cons(model, nc) x = unsafe_wrap(Array, ptr_x, nx[]) lambda = unsafe_wrap(Array, ptr_lambda, nx[] + nc[]) - return model.ms_initpt_callback(model, nSolveNumber, x, lambda, model.ms_initpt_user_data) + return model.ms_initpt_callback( + model, + nSolveNumber, + x, + lambda, + model.ms_initpt_user_data, + ) end """