How to pass the domain of a variable to a function in a general manner?

I plan to implement some certain algorithm that is designed for a class of some type of optimization problems with one of whose decision variables x required to be in the domain F_x\subseteq \mathbb{R}_+^n.

I decide to write a function for this algorithm so that the users can pass the necessary parameters of any instance to it to obtain the optimal solution. Suppose the name of the function is MyAlgorithm(Fx, other_params).

Now my question is how should I pass the domain F_x to the function in a general way? For example, if in an instance of this type of problem we have F_x=\{0,1\}^m\times\mathbb{R}_+^n, what should I write at the position of Fx when invoking the function MyAlgorithm(Fx, other_params)? And furthermore, how can the function recognize and resolve it with the help of JuMP?

Many thanks!

Take a read of the tutorial: Design patterns for larger models · JuMP

You could do something like:

struct BinaryNonnegative
    m::Int
    n::Int
end

function create_variables(Fx::BinaryNonnegative, model)
    @variable(model, x[1:Fx.m], Bin)
    @variable(model, y[1:Fx.n] >= 0)
    return [x; y]
end

function MyAlgorithm(Fx, params)
    model = Model()
    x = create_variables(Fx, model)
    return model
end

Fx = BinaryNonnegative(3, 7)
params = nothing
model = MyAlgorithm(Fx, params)
1 Like

Yes, thanks! It does work, but what if F_x is other arbitrary domain such as F_x = \{0,1\}^m\times\mathbb{R}_+^n\times\mathbb{Z}_+^k\subseteq\mathbb{R}_+^{m+n+k}? I mean I don’t want the function MyAlgorithm to depend on another fixed function like create_variables.

but what if Fx is other arbitrary domain
I mean I don’t want the function MyAlgorithm to depend on another fixed function like create_variables .

JuMP doesn’t work by defining a single vector of decision variables that lives in a Cartesian product of domains. If you want something like that, you’ll need to code it yourself.

How about:

julia> using JuMP

julia> abstract type AbstractDomain end

julia> struct ZeroOne <: AbstractDomain; n::Int end

julia> add_variables(d::ZeroOne, model) = @variable(model, [1:d.n], Bin)
add_variables (generic function with 1 method)

julia> struct Rplus <: AbstractDomain; n::Int end

julia> add_variables(d::Rplus, model) = @variable(model, [1:d.n], lower_bound = 0)
add_variables (generic function with 2 methods)

julia> struct Zplus <: AbstractDomain; n::Int end

julia> add_variables(d::Zplus, model) = @variable(model, [1:d.n], lower_bound = 0, Int)
add_variables (generic function with 3 methods)

julia> function MyAlgorithm(Fx::Vector{<:AbstractDomain}, params)
           model = Model()
           x = reduce(vcat, add_variables.(Fx, model))
           return model, x
       end
MyAlgorithm (generic function with 1 method)

julia> model, x = MyAlgorithm([ZeroOne(3), Rplus(2), Zplus(3)], nothing)
(A JuMP Model
Feasibility problem with:
Variables: 8
`VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 5 constraints
`VariableRef`-in-`MathOptInterface.Integer`: 3 constraints
`VariableRef`-in-`MathOptInterface.ZeroOne`: 3 constraints
Model mode: AUTOMATIC
CachingOptimizer state: NO_OPTIMIZER
Solver name: No optimizer attached., VariableRef[_[1], _[2], _[3], _[4], _[5], _[6], _[7], _[8]])

julia> model
A JuMP Model
Feasibility problem with:
Variables: 8
`VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 5 constraints
`VariableRef`-in-`MathOptInterface.Integer`: 3 constraints
`VariableRef`-in-`MathOptInterface.ZeroOne`: 3 constraints
Model mode: AUTOMATIC
CachingOptimizer state: NO_OPTIMIZER
Solver name: No optimizer attached.

julia> x
8-element Vector{VariableRef}:
 _[1]
 _[2]
 _[3]
 _[4]
 _[5]
 _[6]
 _[7]
 _[8]

You could also get people to pass you a vector of lower bounds, upper bounds, and whether the variable is integer.

Perhaps:

julia> struct Domain
           lower::Float64
           upper::Float64
           discrete::Bool
       end

julia> function MyAlgorithm(Fx::Vector{Domain}, params)
           model = Model()
           @variable(
               model,
               Fx[i].lower <= x[i in 1:length(Fx)] <= Fx[i].upper,
               integer = Fx[i].discrete,
           )
           return model, x
       end
MyAlgorithm (generic function with 2 methods)

julia> Fx = vcat(
           fill(Domain(0.0, 1.0, true), 3),
           fill(Domain(0.0, Inf, false), 2),
           fill(Domain(0.0, Inf, true), 3),
       )
8-element Vector{Domain}:
 Domain(0.0, 1.0, true)
 Domain(0.0, 1.0, true)
 Domain(0.0, 1.0, true)
 Domain(0.0, Inf, false)
 Domain(0.0, Inf, false)
 Domain(0.0, Inf, true)
 Domain(0.0, Inf, true)
 Domain(0.0, Inf, true)

julia> model, x = MyAlgorithm(Fx, nothing)
(A JuMP Model
Feasibility problem with:
Variables: 8
`VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 8 constraints
`VariableRef`-in-`MathOptInterface.LessThan{Float64}`: 3 constraints
`VariableRef`-in-`MathOptInterface.Integer`: 6 constraints
Model mode: AUTOMATIC
CachingOptimizer state: NO_OPTIMIZER
Solver name: No optimizer attached.
Names registered in the model: x, VariableRef[x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]])

A key benefit of JuMP is that you are free to construct the most appropriate data structure for your problem. You do not need to use built-in constructs. A downside is that choosing the most appropriate data structure can be difficult.

1 Like

Thank you very much ! I’ll read your code carefully.

1 Like