Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

ERROR: type Symbol has no field head #223

Closed
freemin7 opened this issue Oct 20, 2022 · 10 comments
Closed

ERROR: type Symbol has no field head #223

freemin7 opened this issue Oct 20, 2022 · 10 comments
Labels

Comments

@freemin7
Copy link

ERROR: type Symbol has no field head
Stacktrace:
  [1] getproperty
    @ ./Base.jl:42 [inlined]
  [2] traverse_expr_linear_to_affine(expr::Symbol, lhscoeffs::Vector{Any}, lhsvars::Vector{Any}, rhs::Float64, bufferVal::Nothing, bufferVar::Nothing, sign::Float64, coef::Float64, level::Int64)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:351
  [3] traverse_expr_linear_to_affine(expr::Expr, lhscoeffs::Vector{Any}, lhsvars::Vector{Any}, rhs::Float64, bufferVal::Nothing, bufferVar::Nothing, sign::Float64, coef::Float64, level::Int64) (repeats 3 times)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:369
  [4] traverse_expr_linear_to_affine(expr::Expr)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:327
  [5] expr_linear_to_affine(expr::Expr)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:282
  [6] expr_conversion(m::Alpine.Optimizer)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:97
  [7] process_expr
    @ ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:10 [inlined]
  [8] load!(m::Alpine.Optimizer)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/main_algorithm.jl:110
  [9] optimize!(m::Alpine.Optimizer)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/main_algorithm.jl:151
 [10] optimize!
    @ ~/.julia/packages/MathOptInterface/Ohzb2/src/Bridges/bridge_optimizer.jl:376 [inlined]
 [11] optimize!
    @ ~/.julia/packages/MathOptInterface/Ohzb2/src/MathOptInterface.jl:87 [inlined]
 [12] optimize!(m::MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.Bridges.LazyBridgeOptimizer{Alpine.Optimizer}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}})
    @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/Ohzb2/src/Utilities/cachingoptimizer.jl:316
 [13] optimize!(model::Model; ignore_optimize_hook::Bool, _differentiation_backend::MathOptInterface.Nonlinear.SparseReverseMode, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ JuMP ~/.julia/packages/JuMP/gVq7V/src/optimizer_interface.jl:185
 [14] optimize!(model::Model)
    @ JuMP ~/.julia/packages/JuMP/gVq7V/src/optimizer_interface.jl:163
 [15] top-level scope
    @ REPL[560]:1

Offending code:

nn = 4
np = 5
n0 = 1

Qmax = 500
Pmin = 5
Pmax = 45


Aqp = sparse([1,2,2,3,3,4,4,5,5],[1,1,2,1,3,2,4,3,4],[1.,1.,-1.,-1.,1.,1.,-1.,1.,-1.],5,4)
Aq0 = sparse([1],[1],[-1.],5,1)

Demands = fill(25.,(4))
Elevation = [90.,90.,88.,88.]
WaterHeight = [100.;]

nv = 4

## MINLP

using Ipopt, Alpine, LinearAlgebra, Juniper

mip = optimizer_with_attributes(HiGHS.Optimizer, 
                                         MOI.Silent() => true,
                                         "presolve"   => "on") 

# NLP optimizer
ipopt = optimizer_with_attributes(Ipopt.Optimizer, 
                                        MOI.Silent() => true, 
                                        "sb" => "yes", 
                                        "max_iter"   => 9999)
                                        
# Local MINLP feasibility pump
juniper = optimizer_with_attributes(
        Juniper.Optimizer,
       # MOI.Silent() => true,
        "mip_solver" => mip,
        "nl_solver" => ipopt,
    )


# Global optimizer
alpine = optimizer_with_attributes(Alpine.Optimizer, 
                                         "nlp_solver" => ipopt,
                                         "mip_solver" => mip,
                                         "minlp_solver" => juniper)
mnl = Model(juniper)
@variable(mnl, Pmin <= p[1:nn] <= Pmax)
@variable(mnl, 0 <= q[1:(2*np)] <= Qmax)
@variable(mnl, z[1:(2*np)], Bin)
@variable(mnl, hfq[1:(2*np)])

@constraint(mnl, transpose(Aqp)*(q[1:np] - q[(np+1):(2np)]) - Demands .== 0)


@variable(mnl, b[1:(2*np)])
@constraint(mnl, b[1:np] .== -Aqp*p -Aqp*Demands -Aq0*WaterHeight -hfq[1:np] )
@constraint(mnl, b[(np+1):(2np)] .== +Aqp*p +Aqp*Demands +Aq0*WaterHeight -hfq[(np+1):(2np)])


for i in 1:(2*np)
  @NLconstraint(mnl, 0 <=    q[i]*b[i] )
end

@constraint(mnl, -Aqp*p -Aqp*Demands -Aq0*WaterHeight -hfq[1:np] -100*z[1:np] .<= 0)
@constraint(mnl, +Aqp*p +Aqp*Demands +Aq0*WaterHeight -hfq[(np+1):(2np)] -100*z[(1+np):(2*np)] .<= 0)

@constraint(mnl, z[1:np] + z[(np+1):(2np)] .<= 1)
@constraint(mnl, sum(z) == nv)

for i in 1:(2*np)
  @NLconstraint(mnl, hfq[i] == q[i]^1.852)
end


@objective(mnl,Min,sum(p))

optimize!(mnl)

I hope i got the context right.

(model) pkg> status
      Status `~/privatepath/model/Project.toml`
  [07493b3f] Alpine v0.5.1
  [87dc4568] HiGHS v1.1.4
  [b99e6be6] Hypatia v0.7.0
  [b6b21f68] Ipopt v1.1.0
  [4076af6c] JuMP v1.3.1
  [2ddba703] Juniper v0.9.1
  [2f354839] Pajarito v0.8.0
  [2f01184e] SparseArrays
@odow
Copy link
Collaborator

odow commented Oct 20, 2022

I can't reproduce. I get

julia> optimize!(mnl)
nl_solver   : MathOptInterface.OptimizerWithAttributes(Ipopt.Optimizer, Pair{MathOptInterface.AbstractOptimizerAttribute, Any}[MathOptInterface.Silent() => true, MathOptInterface.RawOptimizerAttribute("sb") => "yes", MathOptInterface.RawOptimizerAttribute("max_iter") => 9999])
mip_solver  : MathOptInterface.OptimizerWithAttributes(HiGHS.Optimizer, Pair{MathOptInterface.AbstractOptimizerAttribute, Any}[MathOptInterface.Silent() => true, MathOptInterface.RawOptimizerAttribute("presolve") => "on"])
log_levels  : [:Options, :Table, :Info]

#Variables: 44
#IntBinVar: 10
Obj Sense: Min

Start values are not feasible.
Status of relaxation: NUMERICAL_ERROR

@odow
Copy link
Collaborator

odow commented Oct 20, 2022

What is

pkg> st -m MathOptInterface
      Status `/private/tmp/Manifest.toml`
  [b8f27783] MathOptInterface v1.8.2

@freemin7
Copy link
Author

wait, run it with alpine not juniper. Copied the wrong version.

@odow
Copy link
Collaborator

odow commented Oct 20, 2022

Can reproduce. Will take a look.

@odow
Copy link
Collaborator

odow commented Oct 20, 2022

Just as an FYI for future reports, it helps immensely if you can isolate a minimal reproducible example. In this case:

julia> using JuMP

julia> import Alpine

julia> model = Model(Alpine.Optimizer)
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: AUTOMATIC
CachingOptimizer state: EMPTY_OPTIMIZER
Solver name: Alpine

julia> @variable(model, x)
x

julia> @NLconstraint(model, x^1.852 <= 1)
x ^ 1.852 - 1.0  0

julia> optimize!(model)
ERROR: type Symbol has no field head
Stacktrace:
  [1] getproperty(x::Symbol, f::Symbol)
    @ Base ./Base.jl:33
  [2] traverse_expr_linear_to_affine(expr::Symbol, lhscoeffs::Vector{Any}, lhsvars::Vector{Any}, rhs::Float64, bufferVal::Nothing, bufferVar::Nothing, sign::Float64, coef::Float64, level::Int64)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:351
  [3] traverse_expr_linear_to_affine(expr::Expr, lhscoeffs::Vector{Any}, lhsvars::Vector{Any}, rhs::Float64, bufferVal::Nothing, bufferVar::Nothing, sign::Float64, coef::Float64, level::Int64) (repeats 2 times)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:369
  [4] traverse_expr_linear_to_affine(expr::Expr)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:327
  [5] expr_linear_to_affine(expr::Expr)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:282
  [6] expr_conversion(m::Alpine.Optimizer)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:97
  [7] process_expr
    @ ~/.julia/packages/Alpine/fkUe3/src/nlexpr.jl:10 [inlined]
  [8] load!(m::Alpine.Optimizer)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/main_algorithm.jl:110
  [9] optimize!(m::Alpine.Optimizer)
    @ Alpine ~/.julia/packages/Alpine/fkUe3/src/main_algorithm.jl:151
 [10] optimize!
    @ ~/.julia/packages/MathOptInterface/Ohzb2/src/Bridges/bridge_optimizer.jl:376 [inlined]
 [11] optimize!
    @ ~/.julia/packages/MathOptInterface/Ohzb2/src/MathOptInterface.jl:87 [inlined]
 [12] optimize!(m::MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.Bridges.LazyBridgeOptimizer{Alpine.Optimizer}, MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}})
    @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/Ohzb2/src/Utilities/cachingoptimizer.jl:316
 [13] optimize!(model::Model; ignore_optimize_hook::Bool, _differentiation_backend::MathOptInterface.Nonlinear.SparseReverseMode, kwargs::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ JuMP ~/.julia/packages/JuMP/gVq7V/src/optimizer_interface.jl:185
 [14] optimize!(model::Model)
    @ JuMP ~/.julia/packages/JuMP/gVq7V/src/optimizer_interface.jl:163
 [15] top-level scope
    @ REPL[572]:1

@odow
Copy link
Collaborator

odow commented Oct 20, 2022

This might be one for @harshangrjn. Does Alpine support x^y?

The underling problem is here. The constraint coming in is x^1.852 - 1 <= 0, but is_strucural is false:

Alpine.jl/src/nlexpr.jl

Lines 64 to 70 in c6da8b9

for i in 1:m.num_constr_orig
is_strucural = Alp.expr_constr_parsing(m.bounding_constr_expr_mip[i], m, i)
if !is_strucural
m.bounding_constr_expr_mip[i] =
Alp.expr_term_parsing(m.bounding_constr_expr_mip[i], i, m)
m.constr_structure[i] = :generic_linear
end

Alpine.jl/src/nlexpr.jl

Lines 201 to 224 in c6da8b9

"""
Alp.expr_constr_parsing(expr, m::Optimizer)
Recognize structural constraints.
"""
function expr_constr_parsing(expr, m::Optimizer, idx::Int = 0)
# First process user-defined structures in-cases of over-ride
# for i in 1:length(Alp.get_option(m, :constr_patterns))
# is_strucural = eval(Alp.get_option(m, :constr_patterns)[i])(expr, m, idx)
# return
# end
isa(expr, Number) && return false
# Recognize built-in special structural pattern
if Alp.get_option(m, :recognize_convex)
is_convex = Alp.resolve_convex_constr(expr, m, idx)
is_convex && return true
end
# More patterns goes here
return false
end

And so the constraint gets classified as linear. Then later, when the linear constraint tries to get parsed, it encounters :^ and errors.

expr is :^, so none of these if statements are true, we reach the last and error:

Alpine.jl/src/nlexpr.jl

Lines 336 to 351 in c6da8b9

if isa(expr, Number) # Capture any coefficients or right hand side
(bufferVal !== nothing) ? bufferVal *= expr : bufferVal = expr * coef
return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar
elseif expr in [:+, :-] # TODO: what is this condition?
if bufferVal !== nothing && bufferVar !== nothing
push!(lhscoeffs, bufferVal)
push!(lhsvars, bufferVar)
bufferVal = 0.0
bufferVar = nothing
end
return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar
elseif expr in [:*]
return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar
elseif expr in [:(<=), :(==), :(>=)]
return lhscoeffs, lhsvars, rhs, bufferVal, bufferVar
elseif expr.head == :ref

@harshangrjn
Copy link
Collaborator

@odow non-integral (and non-positive) exponents are not supported by Alpine at this point. But the error it is reporting certainly needs to be fixed, and a more meaningful message saying fractional exponents aren't supported in Alpine will be useful.

@freemin7
Copy link
Author

freemin7 commented Oct 20, 2022

I am a bit surprised positive fractional exponents are not handled. f(y) == x^(a/b) should be equivalent to f(y) == z, z^b == z^a, z>=0 sound like something a bridge could handle. I will rewrite the constraint with that in mind. Maybe suggesting such a thing in in error message might help, although that can lead to giant exponents. A more interesting approach would be to bound a/b with nicer fractions from above and below and refine them once they become an uncertainty and less sensitive intervals are left.

@harshangrjn
Copy link
Collaborator

@freemin7 The idea for leaving behind these exponents is that we didn't want to handle exponents which aren't supported by Gurobi, after applying relaxations at this point. But having fractional exponents shouldn't be too hard to include with a simple outer approximation.

@harshangrjn
Copy link
Collaborator

harshangrjn commented Nov 2, 2022

@odow The issue is not with is_strucural but is with the lack of check for fractional exponents in the constraints similar to the objective expression here:

Alp.expr_isfracexp(m.bounding_obj_expr_mip)

generic_linear classification is happening since the constraint is convex, although the naming is quite confusing. It needs some re-naming for sure. Anyway this should be fixed now where Alpine throws a more meaningful error message.

@freemin7 closing this for now as this should be addressed in v0.5.2. Plz feel free to re-open if you see any issue.

harshangrjn added a commit that referenced this issue Nov 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants