@@ -25,18 +25,60 @@ function print_shortest(io::IO, x::Real)
2525 return
2626end
2727
28+ const IndicatorLessThanTrue{T} =
29+ MOI. Indicator{MOI. ACTIVATE_ON_ONE,MOI. LessThan{T}}
30+
31+ const IndicatorGreaterThanTrue{T} =
32+ MOI. Indicator{MOI. ACTIVATE_ON_ONE,MOI. GreaterThan{T}}
33+
34+ const IndicatorLessThanFalse{T} =
35+ MOI. Indicator{MOI. ACTIVATE_ON_ZERO,MOI. LessThan{T}}
36+
37+ const IndicatorGreaterThanFalse{T} =
38+ MOI. Indicator{MOI. ACTIVATE_ON_ZERO,MOI. GreaterThan{T}}
39+
2840MOI. Utilities. @model (
2941 Model,
3042 (MOI. ZeroOne, MOI. Integer),
3143 (MOI. EqualTo, MOI. GreaterThan, MOI. LessThan, MOI. Interval),
3244 (),
33- (MOI. SOS1, MOI. SOS2),
45+ (
46+ MOI. SOS1,
47+ MOI. SOS2,
48+ IndicatorLessThanTrue,
49+ IndicatorLessThanFalse,
50+ IndicatorGreaterThanTrue,
51+ IndicatorGreaterThanFalse,
52+ ),
3453 (),
3554 (MOI. ScalarAffineFunction, MOI. ScalarQuadraticFunction),
3655 (MOI. VectorOfVariables,),
37- ()
56+ (MOI . VectorAffineFunction, )
3857)
3958
59+ function MOI. supports_constraint (
60+ :: Model{T} ,
61+ :: Type{MOI.VectorAffineFunction{T}} ,
62+ :: Type{<:Union{MOI.SOS1{T},MOI.SOS2{T}}} ,
63+ ) where {T}
64+ return false
65+ end
66+
67+ function MOI. supports_constraint (
68+ :: Model{T} ,
69+ :: Type{MOI.VectorOfVariables} ,
70+ :: Type {
71+ <: Union {
72+ IndicatorLessThanTrue{T},
73+ IndicatorLessThanFalse{T},
74+ IndicatorGreaterThanTrue{T},
75+ IndicatorGreaterThanFalse{T},
76+ },
77+ },
78+ ) where {T}
79+ return false
80+ end
81+
4082@enum (
4183 QuadraticFormat,
4284 kQuadraticFormatCPLEX,
@@ -216,7 +258,8 @@ function Base.write(io::IO, model::Model)
216258 flip_obj = MOI. get (model, MOI. ObjectiveSense ()) == MOI. MAX_SENSE
217259 end
218260 write_rows (io, model)
219- obj_const = write_columns (io, model, flip_obj, ordered_names, names)
261+ obj_const, indicators =
262+ write_columns (io, model, flip_obj, ordered_names, names)
220263 write_rhs (io, model, obj_const)
221264 write_ranges (io, model)
222265 write_bounds (io, model, ordered_names, names)
@@ -230,6 +273,7 @@ function Base.write(io::IO, model::Model)
230273 # CPLEX needs qcons _after_ SOS.
231274 write_quadcons (io, model, ordered_names, var_to_column)
232275 end
276+ write_indicators (io, indicators)
233277 println (io, " ENDATA" )
234278 return
235279end
@@ -294,6 +338,11 @@ function write_rows(io::IO, model::Model)
294338 _write_rows (io, model, F, set_type, sense_char)
295339 end
296340 end
341+ F = MOI. VectorAffineFunction{Float64}
342+ _write_rows (io, model, F, IndicatorLessThanTrue{Float64}, " L" )
343+ _write_rows (io, model, F, IndicatorLessThanFalse{Float64}, " L" )
344+ _write_rows (io, model, F, IndicatorGreaterThanTrue{Float64}, " G" )
345+ _write_rows (io, model, F, IndicatorGreaterThanFalse{Float64}, " G" )
297346 return
298347end
299348
@@ -363,6 +412,25 @@ function _collect_coefficients(
363412 return
364413end
365414
415+ _activation_condition (:: Type{<:MOI.Indicator{A}} ) where {A} = A
416+
417+ function _collect_indicator (model, S, names, coefficients, indicators)
418+ F = MOI. VectorAffineFunction{Float64}
419+ for index in MOI. get (model, MOI. ListOfConstraintIndices {F,S} ())
420+ row_name = MOI. get (model, MOI. ConstraintName (), index)
421+ func = MOI. get (model, MOI. ConstraintFunction (), index)
422+ funcs = MOI. Utilities. eachscalar (func)
423+ z = convert (MOI. VariableIndex, funcs[1 ])
424+ _extract_terms (names, coefficients, row_name, funcs[2 ])
425+ condition = _activation_condition (S)
426+ push! (
427+ indicators,
428+ (row_name, MOI. get (model, MOI. VariableName (), z), condition),
429+ )
430+ end
431+ return
432+ end
433+
366434function _get_objective (model)
367435 F = MOI. get (model, MOI. ObjectiveFunctionType ())
368436 f = MOI. get (model, MOI. ObjectiveFunction {F} ())
@@ -373,6 +441,7 @@ function _get_objective(model)
373441end
374442
375443function write_columns (io:: IO , model:: Model , flip_obj, ordered_names, names)
444+ indicators = Tuple{String,String,MOI. ActivationCondition}[]
376445 coefficients = Dict {String,Vector{Tuple{String,Float64}}} (
377446 n => Tuple{String,Float64}[] for n in ordered_names
378447 )
@@ -382,6 +451,14 @@ function write_columns(io::IO, model::Model, flip_obj, ordered_names, names)
382451 _collect_coefficients (model, F, S, names, coefficients)
383452 end
384453 end
454+ for S in (
455+ IndicatorLessThanTrue{Float64},
456+ IndicatorLessThanFalse{Float64},
457+ IndicatorGreaterThanTrue{Float64},
458+ IndicatorGreaterThanFalse{Float64},
459+ )
460+ _collect_indicator (model, S, names, coefficients, indicators)
461+ end
385462 # Build objective
386463 obj_func = _get_objective (model)
387464 _extract_terms (names, coefficients, " OBJ" , obj_func, flip_obj)
@@ -413,7 +490,7 @@ function write_columns(io::IO, model::Model, flip_obj, ordered_names, names)
413490 )
414491 end
415492 end
416- return obj_func. constant
493+ return obj_func. constant, indicators
417494end
418495
419496# ==============================================================================
423500_value (set:: MOI.LessThan ) = set. upper
424501_value (set:: MOI.GreaterThan ) = set. lower
425502_value (set:: MOI.EqualTo ) = set. value
503+ _value (set:: MOI.Indicator ) = _value (set. set)
426504
427505function _write_rhs (io, model, F, S)
428506 for index in MOI. get (model, MOI. ListOfConstraintIndices {F,S} ())
@@ -467,6 +545,11 @@ function write_rhs(io::IO, model::Model, obj_const)
467545 _write_rhs (io, model, F, set_type)
468546 end
469547 end
548+ F = MOI. VectorAffineFunction{Float64}
549+ _write_rhs (io, model, F, IndicatorLessThanTrue{Float64})
550+ _write_rhs (io, model, F, IndicatorLessThanFalse{Float64})
551+ _write_rhs (io, model, F, IndicatorGreaterThanTrue{Float64})
552+ _write_rhs (io, model, F, IndicatorGreaterThanFalse{Float64})
470553 # Objective constants are added to the RHS as a negative offset.
471554 # https://www.ibm.com/docs/en/icos/20.1.0?topic=standard-records-in-mps-format
472555 if ! iszero (obj_const)
@@ -786,6 +869,25 @@ function write_sos(io::IO, model::Model, names)
786869 return
787870end
788871
872+ # ==============================================================================
873+ # INDICATORS
874+ # ==============================================================================
875+
876+ function write_indicators (io:: IO , indicators)
877+ if isempty (indicators)
878+ return
879+ end
880+ println (io, " INDICATORS" )
881+ for (row, var, condition) in indicators
882+ if condition == MOI. ACTIVATE_ON_ONE
883+ println (io, Card (f1 = " IF" , f2 = row, f3 = var, f4 = " 1" ))
884+ else
885+ println (io, Card (f1 = " IF" , f2 = row, f3 = var, f4 = " 0" ))
886+ end
887+ end
888+ return
889+ end
890+
789891# ==============================================================================
790892#
791893# Base.read!
@@ -861,6 +963,7 @@ mutable struct TempMPSModel
861963 quad_obj:: Vector{Tuple{String,String,Float64}}
862964 qc_matrix:: Dict{String,Vector{Tuple{String,String,Float64}}}
863965 current_qc_matrix:: String
966+ indicators:: Dict{String,Tuple{String,MOI.ActivationCondition}}
864967end
865968
866969function TempMPSModel ()
@@ -886,6 +989,7 @@ function TempMPSModel()
886989 Tuple{String,String,Float64}[],
887990 Dict {String,Vector{Tuple{String,String,Float64}}} (),
888991 " " ,
992+ Dict {String,Tuple{String,MOI.ActivationCondition}} (),
889993 )
890994end
891995
9051009 HEADER_QMATRIX,
9061010 HEADER_QCMATRIX,
9071011 HEADER_QSECTION,
1012+ HEADER_INDICATORS,
9081013)
9091014
9101015# Headers(s) gets called _alot_ (on every line), so we try very hard to be
@@ -941,6 +1046,10 @@ function Headers(s::AbstractString)
9411046 return HEADER_QMATRIX
9421047 end
9431048 end
1049+ elseif N == 10
1050+ if (x == ' I' || x == ' i' ) && uppercase (s) == " INDICATORS"
1051+ return HEADER_INDICATORS
1052+ end
9441053 elseif N == 12
9451054 if (x == ' O' || x == ' o' ) && startswith (uppercase (s), " OBJSENSE" )
9461055 return HEADER_OBJSENSE
@@ -1028,6 +1137,8 @@ function Base.read!(io::IO, model::Model)
10281137 parse_qcmatrix_line (data, items)
10291138 elseif header == HEADER_QSECTION
10301139 parse_qsection_line (data, items)
1140+ elseif header == HEADER_INDICATORS
1141+ parse_indicators_line (data, items)
10311142 else
10321143 @assert header == HEADER_ENDATA
10331144 break
@@ -1119,12 +1230,30 @@ end
11191230function _add_constraint (model, data, variable_map, j, c_name, set)
11201231 if haskey (data. qc_matrix, c_name)
11211232 _add_quad_constraint (model, data, variable_map, j, c_name, set)
1233+ elseif haskey (data. indicators, c_name)
1234+ _add_indicator_constraint (model, data, variable_map, j, c_name, set)
11221235 else
11231236 _add_linear_constraint (model, data, variable_map, j, c_name, set)
11241237 end
11251238 return
11261239end
11271240
1241+ function _add_indicator_constraint (model, data, variable_map, j, c_name, set)
1242+ z, activate = data. indicators[c_name]
1243+ terms = MOI. VectorAffineTerm{Float64}[MOI. VectorAffineTerm (
1244+ 1 ,
1245+ MOI. ScalarAffineTerm (1.0 , variable_map[z]),
1246+ ),]
1247+ for (i, coef) in data. A[j]
1248+ scalar = MOI. ScalarAffineTerm (coef, variable_map[data. col_to_name[i]])
1249+ push! (terms, MOI. VectorAffineTerm (2 , scalar))
1250+ end
1251+ f = MOI. VectorAffineFunction (terms, [0.0 , 0.0 ])
1252+ c = MOI. add_constraint (model, f, MOI. Indicator {activate} (set))
1253+ MOI. set (model, MOI. ConstraintName (), c, c_name)
1254+ return
1255+ end
1256+
11281257function _add_linear_constraint (model, data, variable_map, j, c_name, set)
11291258 terms = MOI. ScalarAffineTerm{Float64}[
11301259 MOI. ScalarAffineTerm (coef, variable_map[data. col_to_name[i]]) for
@@ -1579,4 +1708,22 @@ function parse_qsection_line(data, items)
15791708 return
15801709end
15811710
1711+ # ==============================================================================
1712+ # INDICATORS
1713+ # ==============================================================================
1714+
1715+ function parse_indicators_line (data, items)
1716+ if length (items) != 4
1717+ error (" Malformed INDICATORS line: $(join (items, " " )) " )
1718+ end
1719+ condition = if items[4 ] == " 0"
1720+ MOI. ACTIVATE_ON_ZERO
1721+ else
1722+ @assert items[4 ] == " 1"
1723+ MOI. ACTIVATE_ON_ONE
1724+ end
1725+ data. indicators[items[2 ]] = (items[3 ], condition)
1726+ return
1727+ end
1728+
15821729end
0 commit comments