@@ -35,16 +35,10 @@ MOI.Utilities.@model(
3535 (),
3636 (MOI. SOS1, MOI. SOS2),
3737 (),
38- (MOI. ScalarAffineFunction, ),
38+ (MOI. ScalarQuadraticFunction, MOI . ScalarAffineFunction ),
3939 (MOI. VectorOfVariables,),
4040 ()
4141)
42- function MOI. supports (
43- :: Model{T} ,
44- :: MOI.ObjectiveFunction{<:MOI.ScalarQuadraticFunction{T}} ,
45- ) where {T}
46- return false
47- end
4842
4943struct Options
5044 maximum_length:: Int
@@ -93,7 +87,8 @@ function _write_function(
9387 io:: IO ,
9488 :: Model ,
9589 func:: MOI.VariableIndex ,
96- variable_names:: Dict{MOI.VariableIndex,String} ,
90+ variable_names:: Dict{MOI.VariableIndex,String} ;
91+ kwargs... ,
9792)
9893 print (io, variable_names[func])
9994 return
@@ -103,7 +98,8 @@ function _write_function(
10398 io:: IO ,
10499 :: Model ,
105100 func:: MOI.ScalarAffineFunction{Float64} ,
106- variable_names:: Dict{MOI.VariableIndex,String} ,
101+ variable_names:: Dict{MOI.VariableIndex,String} ;
102+ kwargs... ,
107103)
108104 is_first_item = true
109105 if ! (func. constant ≈ 0.0 )
@@ -125,6 +121,66 @@ function _write_function(
125121 return
126122end
127123
124+ function _write_function (
125+ io:: IO ,
126+ :: Model ,
127+ func:: MOI.ScalarQuadraticFunction{Float64} ,
128+ variable_names:: Dict{MOI.VariableIndex,String} ;
129+ print_half:: Bool = true ,
130+ kwargs... ,
131+ )
132+ is_first_item = true
133+ if ! (func. constant ≈ 0.0 )
134+ _print_shortest (io, func. constant)
135+ is_first_item = false
136+ end
137+ for term in func. affine_terms
138+ if ! (term. coefficient ≈ 0.0 )
139+ if is_first_item
140+ _print_shortest (io, term. coefficient)
141+ is_first_item = false
142+ else
143+ print (io, term. coefficient < 0 ? " - " : " + " )
144+ _print_shortest (io, abs (term. coefficient))
145+ end
146+ print (io, " " , variable_names[term. variable])
147+ end
148+ end
149+ if length (func. quadratic_terms) > 0
150+ if is_first_item
151+ print (io, " [ " )
152+ else
153+ print (io, " + [ " )
154+ end
155+ is_first_item = true
156+ for term in func. quadratic_terms
157+ coefficient = term. coefficient
158+ if ! print_half && term. variable_1 == term. variable_2
159+ coefficient /= 2
160+ end
161+ if is_first_item
162+ _print_shortest (io, coefficient)
163+ is_first_item = false
164+ else
165+ print (io, coefficient < 0 ? " - " : " + " )
166+ _print_shortest (io, abs (coefficient))
167+ end
168+ print (io, " " , variable_names[term. variable_1])
169+ if term. variable_1 == term. variable_2
170+ print (io, " ^ 2" )
171+ else
172+ print (io, " * " , variable_names[term. variable_2])
173+ end
174+ end
175+ if print_half
176+ print (io, " ]/2" )
177+ else
178+ print (io, " ]" )
179+ end
180+ end
181+ return
182+ end
183+
128184function _write_constraint_suffix (io:: IO , set:: MOI.LessThan )
129185 print (io, " <= " )
130186 _print_shortest (io, set. upper)
@@ -174,7 +230,7 @@ function _write_constraint(
174230 print (io, MOI. get (model, MOI. ConstraintName (), index), " : " )
175231 end
176232 _write_constraint_prefix (io, set)
177- _write_function (io, model, func, variable_names)
233+ _write_function (io, model, func, variable_names; print_half = false )
178234 _write_constraint_suffix (io, set)
179235 return
180236end
@@ -233,6 +289,10 @@ function _write_constraints(io, model, S, variable_names)
233289 for index in MOI. get (model, MOI. ListOfConstraintIndices {F,S} ())
234290 _write_constraint (io, model, index, variable_names; write_name = true )
235291 end
292+ F = MOI. ScalarQuadraticFunction{Float64}
293+ for index in MOI. get (model, MOI. ListOfConstraintIndices {F,S} ())
294+ _write_constraint (io, model, index, variable_names; write_name = true )
295+ end
236296 return
237297end
238298
@@ -382,15 +442,19 @@ const _KEYWORDS = Dict(
382442
383443mutable struct _ReadCache
384444 objective:: MOI.ScalarAffineFunction{Float64}
445+ quad_obj_terms:: Vector{MOI.ScalarQuadraticTerm{Float64}}
385446 constraint_function:: MOI.ScalarAffineFunction{Float64}
447+ quad_terms:: Vector{MOI.ScalarQuadraticTerm{Float64}}
386448 constraint_name:: String
387449 num_constraints:: Int
388450 name_to_variable:: Dict{String,MOI.VariableIndex}
389451 has_default_bound:: Set{MOI.VariableIndex}
390452 function _ReadCache ()
391453 return new (
392454 MOI. ScalarAffineFunction (MOI. ScalarAffineTerm{Float64}[], 0.0 ),
455+ MOI. ScalarQuadraticTerm{Float64}[],
393456 MOI. ScalarAffineFunction (MOI. ScalarAffineTerm{Float64}[], 0.0 ),
457+ MOI. ScalarQuadraticTerm{Float64}[],
394458 " " ,
395459 0 ,
396460 Dict {String,MOI.VariableIndex} (),
@@ -424,13 +488,30 @@ end
424488
425489_tokenize (line:: AbstractString ) = String .(split (line, " " ; keepempty = false ))
426490
427- @enum (_TokenType, _TOKEN_VARIABLE, _TOKEN_COEFFICIENT, _TOKEN_SIGN)
491+ @enum (
492+ _TokenType,
493+ _TOKEN_VARIABLE,
494+ _TOKEN_COEFFICIENT,
495+ _TOKEN_SIGN,
496+ _TOKEN_QUADRATIC_OPEN,
497+ _TOKEN_QUADRATIC_CLOSE,
498+ _TOKEN_QUADRATIC_DIAG,
499+ _TOKEN_QUADRATIC_OFF_DIAG,
500+ )
428501
429502function _parse_token (token:: String )
430503 if token == " +"
431504 return _TOKEN_SIGN, + 1.0
432505 elseif token == " -"
433506 return _TOKEN_SIGN, - 1.0
507+ elseif startswith (token, " [" )
508+ return _TOKEN_QUADRATIC_OPEN, + 1.0
509+ elseif startswith (token, " ]" )
510+ return _TOKEN_QUADRATIC_CLOSE, 0.5
511+ elseif token == " ^"
512+ return _TOKEN_QUADRATIC_DIAG, + 1.0
513+ elseif token == " *"
514+ return _TOKEN_QUADRATIC_OFF_DIAG, + 1.0
434515 end
435516 coef = tryparse (Float64, token)
436517 if coef === nothing
@@ -455,12 +536,30 @@ function _get_term(token_types, token_values, offset)
455536 if offset > length (token_types) || token_types[offset] == _TOKEN_SIGN
456537 return coef, offset # It's a standalone constant!
457538 end
539+ if token_types[offset] == _TOKEN_QUADRATIC_OPEN
540+ return _get_term (token_types, token_values, offset + 1 )
541+ end
458542 @assert token_types[offset] == _TOKEN_VARIABLE
459543 x = MOI. VariableIndex (Int64 (token_values[offset]))
460- return MOI. ScalarAffineTerm (coef, x), offset + 1
544+ offset += 1
545+ if offset > length (token_types) || token_types[offset] == _TOKEN_SIGN
546+ return MOI. ScalarAffineTerm (coef, x), offset
547+ end
548+ term = if token_types[offset] == _TOKEN_QUADRATIC_DIAG
549+ MOI. ScalarQuadraticTerm (coef, x, x)
550+ else
551+ @assert token_types[offset] == _TOKEN_QUADRATIC_OFF_DIAG
552+ y = MOI. VariableIndex (Int64 (token_values[offset+ 1 ]))
553+ MOI. ScalarQuadraticTerm (coef, x, y)
554+ end
555+ if get (token_types, offset + 2 , nothing ) == _TOKEN_QUADRATIC_CLOSE
556+ return term, offset + 3
557+ else
558+ return term, offset + 2
559+ end
461560end
462561
463- function _parse_affine_terms (
562+ function _parse_function (
464563 f:: MOI.ScalarAffineFunction{Float64} ,
465564 model:: Model ,
466565 cache:: _ReadCache ,
@@ -474,6 +573,10 @@ function _parse_affine_terms(
474573 token_types[i] = token_type
475574 if token_type in (_TOKEN_SIGN, _TOKEN_COEFFICIENT)
476575 token_values[i] = token:: Float64
576+ elseif token_type in (_TOKEN_QUADRATIC_OPEN, _TOKEN_QUADRATIC_CLOSE)
577+ token_values[i] = NaN
578+ elseif token_type in (_TOKEN_QUADRATIC_DIAG, _TOKEN_QUADRATIC_OFF_DIAG)
579+ token_values[i] = NaN
477580 else
478581 @assert token_type == _TOKEN_VARIABLE
479582 x = _get_variable_from_name (model, cache, token:: String )
@@ -486,6 +589,15 @@ function _parse_affine_terms(
486589 term, offset = _get_term (token_types, token_values, offset)
487590 if term isa MOI. ScalarAffineTerm{Float64}
488591 push! (f. terms, term:: MOI.ScalarAffineTerm{Float64} )
592+ elseif term isa MOI. ScalarQuadraticTerm{Float64}
593+ push! (cache. quad_terms, term:: MOI.ScalarQuadraticTerm{Float64} )
594+ if tokens[offset- 1 ] == " ]"
595+ for (i, term) in enumerate (cache. quad_terms)
596+ x, y = term. variable_1, term. variable_2
597+ scale = (x == y ? 2 : 1 ) * term. coefficient
598+ cache. quad_terms[i] = MOI. ScalarQuadraticTerm (scale, x, y)
599+ end
600+ end
489601 else
490602 f. constant += term:: Float64
491603 end
@@ -520,13 +632,21 @@ function _parse_section(
520632 if occursin (" :" , line) # Strip name of the objective
521633 line = String (match (r" (.*?)\: (.*)" , line)[2 ])
522634 end
635+ if occursin (" ^" , line)
636+ line = replace (line, " ^" => " ^ " )
637+ end
638+ if occursin (r" \] [\s /][\s /]+2" , line)
639+ line = replace (line, r" \] [\s /][\s /]+2" => " ]/2" )
640+ end
523641 tokens = _tokenize (line)
524642 if length (tokens) == 0
525643 # Can happen if the name of the objective is on one line and the
526644 # expression is on the next.
527645 return
528646 end
529- _parse_affine_terms (cache. objective, model, cache, tokens)
647+ _parse_function (cache. objective, model, cache, tokens)
648+ append! (cache. quad_obj_terms, cache. quad_terms)
649+ empty! (cache. quad_terms)
530650 return
531651end
532652
@@ -554,6 +674,15 @@ function _parse_section(
554674 cache. constraint_name = " R$(cache. num_constraints) "
555675 end
556676 end
677+ if occursin (" ^" , line)
678+ # Simplify parsing of constraints with ^2 terms by turning them into
679+ # explicit " ^ 2" terms. This avoids ambiguity when parsing names.
680+ line = replace (line, " ^" => " ^ " )
681+ end
682+ if occursin (r" \] [\s /][\s /]+2" , line)
683+ # Simplify parsing of ]/2 end blocks, which may contain whitespace.
684+ line = replace (line, r" \] [\s /][\s /]+2" => " ]/2" )
685+ end
557686 tokens = _tokenize (line)
558687 if length (tokens) == 0
559688 # Can happen if the name is on one line and the constraint on the next.
@@ -573,12 +702,22 @@ function _parse_section(
573702 MOI. EqualTo (rhs)
574703 end
575704 end
576- _parse_affine_terms (cache. constraint_function, model, cache, tokens)
705+ _parse_function (cache. constraint_function, model, cache, tokens)
577706 if constraint_set != = nothing
578- c = MOI. add_constraint (model, cache. constraint_function, constraint_set)
707+ f = if isempty (cache. quad_terms)
708+ cache. constraint_function
709+ else
710+ MOI. ScalarQuadraticFunction (
711+ cache. quad_terms,
712+ cache. constraint_function. terms,
713+ cache. constraint_function. constant,
714+ )
715+ end
716+ c = MOI. add_constraint (model, f, constraint_set)
579717 MOI. set (model, MOI. ConstraintName (), c, cache. constraint_name)
580718 cache. num_constraints += 1
581719 empty! (cache. constraint_function. terms)
720+ empty! (cache. quad_terms)
582721 cache. constraint_function. constant = 0.0
583722 cache. constraint_name = " "
584723 end
@@ -795,11 +934,16 @@ function Base.read!(io::IO, model::Model)
795934 end
796935 _parse_section (section, model, cache, line)
797936 end
798- MOI. set (
799- model,
800- MOI. ObjectiveFunction {MOI.ScalarAffineFunction{Float64}} (),
801- cache. objective,
802- )
937+ obj = if isempty (cache. quad_obj_terms)
938+ cache. objective
939+ else
940+ MOI. ScalarQuadraticFunction (
941+ cache. quad_obj_terms,
942+ cache. objective. terms,
943+ cache. objective. constant,
944+ )
945+ end
946+ MOI. set (model, MOI. ObjectiveFunction {typeof(obj)} (), obj)
803947 return
804948end
805949
0 commit comments