MOI: Re-evaluate custom bridge & `direct_model(...)` bridging

Thanks for the hint, besides all the digging into MOI, I somehow missed that JuMP has the add_bridge too… :slight_smile:

MWE below, short preface: I’ve got a quadratic constraint, that I want to reformulate in a way that HiGHS can handle it; the MWE just deletes the constraint.

The bridge looks like (based on your answer here):

struct MyCustomBridge{T} <: MOI.Bridges.Constraint.AbstractBridge end

function MOI.Bridges.Constraint.concrete_bridge_type(
    ::Type{<:MyCustomBridge}, 
    F::Type{<:MOI.ScalarQuadraticFunction},
    S::Type{<:MOI.AbstractScalarSet},
)
    return MyCustomBridge{Float64}
end

function MOI.supports_constraint(
    ::Type{<:MyCustomBridge},
	::Type{<:MOI.ScalarQuadraticFunction},
	::Type{<:MOI.AbstractScalarSet},
)
	return true
end

function MOI.Bridges.added_constrained_variable_types(::Type{<:MyCustomBridge}) 
	return Tuple{DataType}[]
end

function MOI.Bridges.added_constraint_types(::Type{<:MyCustomBridge})
	return []
end

function MOI.Bridges.Constraint.bridge_constraint(
    ::Type{<:MyCustomBridge}, 
    m::MOI.ModelLike, 
    f::MOI.ScalarQuadraticFunction, 
    s::MOI.AbstractScalarSet,
)
    @info "Bridging now"
    return MyCustomBridge{Float64}()
end

The model building looks like:

model = JuMP.Model(HiGHS.Optimizer)
JuMP.add_bridge(model, MyCustomBridge)

x = JuMP.@variable(model, [1:3], lower_bound=0)
c = JuMP.@constraint(model, x[1] + x[2]^2 - 3*x[3] >= 0)

JuMP.@objective(model, Min, x[1] + 0)

JuMP.optimize!(model)           # calling the bridge here

JuMP.fix(x[2], 5; force=true)   # fixing this variable would make the constraint viable for HiGHS

JuMP.optimize!(model)           # not calling the bride here - constraint is still deleted

JuMP.set_normalized_rhs(c, JuMP.normalized_rhs(c))

JuMP.optimize!(model)           # calling the bridge here

The important part is JuMP.fix(x[2], 5; force=true), where the user may change the constraint (by fixing the variable that complicates the constraint into a quadratic one) - essentially reducing the constraint to an affine one. Now the next optimize!(...) does not call the bridge - as far as I understand because it actually does not “know of the change” (since fix() just creates an additional MOI Equal).

If however I force an “update” of the respective constraint, e.g. using JuMP.set_normalized_rhs(c, JuMP.normalized_rhs(c)), then the following optimize!(...) will invoke the bridge - allowing me to react to the change that the user made.

While it makes sense (at least to me :sweat_smile:), that this happens, I’m looking for a “less hacky” way to trigger that update.