@@ -811,39 +811,40 @@ function write_quadobj(io::IO, model::Model, flip_obj::Bool, var_to_column)
811811 return
812812 end
813813 options = get_options (model)
814- if options. quadratic_format == kQuadraticFormatGurobi
815- println (io, " QUADOBJ" )
816- elseif options. quadratic_format == kQuadraticFormatCPLEX
817- println (io, " QMATRIX" )
818- else
819- @assert options. quadratic_format == kQuadraticFormatMosek
820- println (io, " QSECTION OBJ" )
821- end
814+ # Here we always write out QUADOBJ sections for the quadratic objective. All
815+ # solvers can read these, even if CPLEX writes QMATRIX by default and Mosek
816+ # writes QSECTION OBJ.
817+ println (io, " QUADOBJ" )
822818 _write_q_matrix (
823819 io,
824820 model,
825- flip_obj,
826821 f,
827822 var_to_column;
828- duplicate_off_diagonal = options. quadratic_format ==
829- kQuadraticFormatCPLEX,
823+ flip_coef = flip_obj,
824+ generic_names = options. generic_names,
825+ # In QUADOBJ, we need only to specific the ij term:
826+ include_ij_and_ji = false ,
827+ # And all solvers interpret QUADOBJ to include /2:
828+ include_div_2 = true ,
830829 )
831830 return
832831end
833832
834833function _write_q_matrix (
835834 io:: IO ,
836835 model:: Model ,
837- flip_obj:: Bool ,
838- f,
836+ f:: MOI.ScalarQuadraticFunction ,
839837 var_to_column;
840- duplicate_off_diagonal:: Bool ,
838+ flip_coef:: Bool ,
839+ generic_names:: Bool ,
840+ include_ij_and_ji:: Bool ,
841+ include_div_2:: Bool ,
841842)
842- options = get_options (model)
843- # Convert the quadratic terms into matrix form. We don't need to scale
844- # because MOI uses the same Q/2 format as Gurobi, but we do need to ensure
845- # we collate off-diagonal terms in the lower-triangular.
846843 terms = Dict {Tuple{MOI.VariableIndex,MOI.VariableIndex},Float64} ()
844+ scale = flip_coef ? - 1.0 : 1.0
845+ if ! include_div_2
846+ scale /= 2
847+ end
847848 for term in f. quadratic_terms
848849 x = term. variable_1
849850 y = term. variable_2
@@ -861,14 +862,11 @@ function _write_q_matrix(
861862 collect (keys (terms)),
862863 by = ((x, y),) -> (var_to_column[x], var_to_column[y]),
863864 )
864- x_name = _var_name (model, x, var_to_column[x], options. generic_names)
865- y_name = _var_name (model, y, var_to_column[y], options. generic_names)
866- coef = terms[(x, y)]
867- if flip_obj
868- coef *= - 1
869- end
865+ x_name = _var_name (model, x, var_to_column[x], generic_names)
866+ y_name = _var_name (model, y, var_to_column[y], generic_names)
867+ coef = scale * terms[(x, y)]
870868 println (io, Card (f2 = x_name, f3 = y_name, f4 = _to_string (coef)))
871- if x != y && duplicate_off_diagonal
869+ if x != y && include_ij_and_ji
872870 println (io, Card (f2 = y_name, f3 = x_name, f4 = _to_string (coef)))
873871 end
874872 end
@@ -890,20 +888,23 @@ function write_quadcons(io::IO, model::Model, var_to_column)
890888 )
891889 for ci in MOI. get (model, MOI. ListOfConstraintIndices {F,S} ())
892890 name = MOI. get (model, MOI. ConstraintName (), ci)
893- if options. quadratic_format == kQuadraticFormatMosek
894- println (io, " QSECTION $name " )
895- else
896- println (io, " QCMATRIX $name " )
897- end
891+ println (io, " QCMATRIX $name " )
898892 f = MOI. get (model, MOI. ConstraintFunction (), ci)
899893 _write_q_matrix (
900894 io,
901895 model,
902- false , # flip_obj
903896 f,
904897 var_to_column;
905- duplicate_off_diagonal = options. quadratic_format !=
906- kQuadraticFormatMosek,
898+ generic_names = options. generic_names,
899+ # flip_coef is needed only for maximization objectives
900+ flip_coef = false ,
901+ # All solvers interpret QCMATRIX to require both (i,j) and (j,i)
902+ # terms.
903+ include_ij_and_ji = true ,
904+ # In Gurobi's QCMATRIX there is no factor of /2. This is
905+ # different to both CPLEX and Mosek.
906+ include_div_2 = options. quadratic_format !=
907+ kQuadraticFormatGurobi,
907908 )
908909 end
909910 end
@@ -1372,11 +1373,21 @@ function _add_quad_constraint(model, data, variable_map, j, c_name, set)
13721373 (i, coef) in data. A[j]
13731374 ]
13741375 quad_terms = MOI. ScalarQuadraticTerm{Float64}[]
1375- for (x, y, q) in data. qc_matrix[c_name]
1376- push! (
1377- quad_terms,
1378- MOI. ScalarQuadraticTerm (q, variable_map[x], variable_map[y]),
1376+ options = get_options (model)
1377+ scale = if options. quadratic_format == kQuadraticFormatGurobi
1378+ # Gurobi does NOT have a /2 as part of the quadratic matrix! Why oh why
1379+ # would you break precedent with all other formats.
1380+ 2.0
1381+ else
1382+ @assert in (
1383+ options. quadratic_format,
1384+ (kQuadraticFormatCPLEX, kQuadraticFormatMosek),
13791385 )
1386+ 1.0
1387+ end
1388+ for (x_name, y_name, q) in data. qc_matrix[c_name]
1389+ x, y = variable_map[x_name], variable_map[y_name]
1390+ push! (quad_terms, MOI. ScalarQuadraticTerm (scale * q, x, y))
13801391 end
13811392 f = MOI. ScalarQuadraticFunction (quad_terms, aff_terms, 0.0 )
13821393 c = MOI. add_constraint (model, f, set)
0 commit comments