Skip to content
49 changes: 49 additions & 0 deletions src/datatypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,52 @@ mutable struct GDPData{M <: JuMP.AbstractModel, V <: JuMP.AbstractVariableRef, C
)
end
end

################################################################################
# VARIABLE INFO
################################################################################
"""
VariableProperties{L, U, F, S, SET, T}

A type for storing variable properties and attributes that can be applied to JuMP variables.
This is used to capture and transfer variable information between models during reformulation.

**Fields**
- `info::JuMP.VariableInfo{L, U, F, S}`: JuMP's VariableInfo struct containing bounds, fixed values, start values, and binary/integer constraints.
- `name::String`: The variable name.
- `set::SET`: The constraint set the variable belongs to (if any), obtained via `JuMP.moi_set`.
- `variable_type::T`: The variable type information, critical for extensions.

**Type Parameters**
- `L, U, F, S`: Type parameters from JuMP.VariableInfo for lower bound, upper bound, fixed value, and start value types.
- `SET`: Type of the constraint set the variable belongs to.
- `T`: Type of the variable type information.

**Constructor**
`VariableProperties(vref::JuMP.GenericVariableRef{T})` creates a VariableProperties instance
from a JuMP variable reference, automatically extracting all relevant properties.
"""
mutable struct VariableProperties{L, U, F, S, SET, T}
info::JuMP.VariableInfo{L, U, F, S}
name::String
set::SET
variable_type::T # this is critical for extensions
Comment thread
dnguyen227 marked this conversation as resolved.
Outdated
end

function VariableProperties(vref::JuMP.GenericVariableRef{T}) where T
info = JuMP.VariableInfo(
JuMP.has_lower_bound(vref),
JuMP.has_lower_bound(vref) ? JuMP.lower_bound(vref) : zero(T),
JuMP.has_upper_bound(vref),
JuMP.has_upper_bound(vref) ? JuMP.upper_bound(vref) : zero(T),
JuMP.is_fixed(vref),
JuMP.is_fixed(vref) ? JuMP.fix_value(vref) : zero(T),
!isnothing(JuMP.start_value(vref)),
JuMP.start_value(vref),
JuMP.is_binary(vref),
JuMP.is_integer(vref)
)
name = JuMP.name(vref)
set = JuMP.is_variable_in_set(vref) ? JuMP.moi_set(JuMP.constraint_object(JuMP.VariableInSetRef(vref))) : nothing
return VariableProperties(info, name, set, nothing)
end
40 changes: 40 additions & 0 deletions src/variables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,43 @@ end
function _interrogate_variables(interrogator::Function, other)
error("Cannot extract variables from object of type $(typeof(other)).")
end

################################################################################
# VARIABLE CREATION #
################################################################################

"""
create_variable(model::JuMP.AbstractModel, props::VariableProperties)::JuMP.AbstractVariableRef

Creates and adds a JuMP variable to `model` using the properties specified in `props`.
This function applies all variable attributes from the `VariableProperties` object
including binary/integer constraints, bounds, fixed values, start values, and constraint sets.

The function first creates a variable object using `_make_variable_object`, optionally applies
any constraint sets if `props.set` is not nothing, and then adds the variable to the model
with the specified name.
"""
function create_variable(model::JuMP.AbstractModel, props::VariableProperties)
var = _make_variable_object(props)
if !isnothing(props.set)
var = JuMP.build_variable(error, var, props.set)
end
return JuMP.add_variable(model, var, props.name)
end

function _make_variable_object(
props::VariableProperties{L, U, F, S, SET, Nothing}
) where {L, U, F, S, SET}
return JuMP.build_variable(error, props.info)
end
function _make_variable_object(props::VariableProperties)
return JuMP.build_variable(error, props.info, props.variable_type)
end

function variable_copy(
model::JuMP.AbstractModel,
vref::JuMP.AbstractVariableRef
)
props = VariableProperties(vref)
return create_variable(model, props)
end
Comment thread
dnguyen227 marked this conversation as resolved.
Comment thread
dnguyen227 marked this conversation as resolved.
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ include("model.jl")
include("jump.jl")
include("variables/query.jl")
include("variables/logical.jl")
include("variables/creation.jl")
include("constraints/selector.jl")
include("constraints/proposition.jl")
include("constraints/disjunct.jl")
Expand Down
119 changes: 119 additions & 0 deletions test/variables/creation.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
function test_VariableProperties_constructor()
model = Model()

@variable(model, x, lower_bound = 2.5)
x_props = DP.VariableProperties(x)
@test x_props.info.has_lb == true
@test x_props.info.lower_bound == 2.5
@test x_props.info.has_ub == false
@test x_props.info.upper_bound == 0.0
@test x_props.info.has_fix == false
@test x_props.info.fixed_value == 0.0
@test x_props.info.has_start == false
@test x_props.name == "x"
@test x_props.set === nothing
@test x_props.variable_type === nothing

@variable(model, y, start = 3.14)
y_props = DP.VariableProperties(y)
@test y_props.info.has_start == true
@test y_props.info.start == 3.14
@test y_props.info.has_lb == false
@test y_props.info.lower_bound == 0.0

@variable(model, z == 7.5)
z_props = DP.VariableProperties(z)
@test z_props.info.has_fix == true
@test z_props.info.fixed_value == 7.5
@test z_props.info.has_lb == false
@test z_props.info.lower_bound == 0.0
end

function test_make_variable_object()
model = Model()

@variable(model, x, lower_bound = 1.0, upper_bound = 5.0)
props = DP.VariableProperties(x)


# props.info.upper_bound = 10.0 <= can not set because Variable.Info is immutable

modified_info = JuMP.VariableInfo(
props.info.has_lb,
props.info.lower_bound,
props.info.has_ub,
10.0, # Modified upper bound
props.info.has_fix,
props.info.fixed_value,
props.info.has_start,
props.info.start,
props.info.binary,
props.info.integer
)

# Create new VariableProperties with modified info
modified_props = DP.VariableProperties(
modified_info,
props.name,
props.set,
props.variable_type
)

Comment thread
dnguyen227 marked this conversation as resolved.
Outdated
var_obj = DP._make_variable_object(modified_props)
@test var_obj.info.upper_bound == 10.0
end

function test_create_variable()
model1 = Model()
model2 = Model()

@variable(model1, x, lower_bound = 1.0, upper_bound = 5.0, start = 2.0)
props_no_set = DP.VariableProperties(x)

@test props_no_set.set === nothing

var_no_set = DP.create_variable(model2, props_no_set)

@test var_no_set !== nothing
@test JuMP.name(var_no_set) == "x"
@test JuMP.has_lower_bound(var_no_set) == true
@test JuMP.lower_bound(var_no_set) == 1.0
@test JuMP.has_upper_bound(var_no_set) == true
@test JuMP.upper_bound(var_no_set) == 5.0
@test JuMP.has_start_value(var_no_set) == true
@test JuMP.start_value(var_no_set) == 2.0

@test var_no_set in JuMP.all_variables(model2)
@test !(var_no_set in JuMP.all_variables(model1))
end

function test_complete_workflow()
model1 = Model()
model2 = Model()

@variable(model1, original, lower_bound = 1, upper_bound = 5, start = 3.0)

props = DP.VariableProperties(original)

recreated = DP.create_variable(model2, props)

@test JuMP.has_lower_bound(recreated) == JuMP.has_lower_bound(original)
@test JuMP.lower_bound(recreated) == JuMP.lower_bound(original)
@test JuMP.has_upper_bound(recreated) == JuMP.has_upper_bound(original)
@test JuMP.upper_bound(recreated) == JuMP.upper_bound(original)
@test JuMP.has_start_value(recreated) == JuMP.has_start_value(original)
@test JuMP.start_value(recreated) == JuMP.start_value(original)
@test JuMP.name(recreated) == JuMP.name(original)

@test original in JuMP.all_variables(model1)
@test recreated in JuMP.all_variables(model2)
@test !(original in JuMP.all_variables(model2))
@test !(recreated in JuMP.all_variables(model1))
end

@testset "Variable Creation" begin
test_VariableProperties_constructor()
test_make_variable_object()
test_create_variable()
test_complete_workflow()
end
Loading