@@ -356,9 +356,242 @@ function knapsacktest(model::MOI.ModelLike, config::TestConfig)
356356 end
357357end
358358
359+ function indicator1_test (model:: MOI.ModelLike , config:: TestConfig )
360+ atol = config. atol
361+ rtol = config. rtol
362+ # linear problem with indicator constraint
363+ # max 2x1 + 3x2
364+ # s.t. x1 + x2 <= 10
365+ # z1 ==> x2 <= 8
366+ # z2 ==> x2 + x1/5 <= 9
367+ # z1 + z2 >= 1
368+
369+ MOI. empty! (model)
370+ @test MOI. is_empty (model)
371+
372+ @test MOI. supports (model, MOI. ObjectiveFunction {MOI.ScalarAffineFunction{Float64}} ())
373+ @test MOI. supports (model, MOI. ObjectiveSense ())
374+ @test MOI. supports_constraint (model, MOI. SingleVariable, MOI. ZeroOne)
375+ @test MOI. supports_constraint (model, MOI. SingleVariable, MOI. Interval{Float64})
376+ @test MOI. supports_constraint (model, MOI. ScalarAffineFunction{Float64}, MOI. Interval{Float64})
377+ @test MOI. supports_constraint (model, MOI. VectorAffineFunction{Float64}, MOI. IndicatorSet{MOI. ACTIVATE_ON_ONE, MOI. LessThan{Float64}})
378+ x1 = MOI. add_variable (model)
379+ x2 = MOI. add_variable (model)
380+ z1 = MOI. add_variable (model)
381+ z2 = MOI. add_variable (model)
382+ MOI. add_constraint (model, z1, MOI. ZeroOne ())
383+ MOI. add_constraint (model, z2, MOI. ZeroOne ())
384+ f1 = MOI. VectorAffineFunction (
385+ [MOI. VectorAffineTerm (1 , MOI. ScalarAffineTerm (1.0 , z1)),
386+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (1.0 , x2)),
387+ ],
388+ [0.0 , 0.0 ]
389+ )
390+ iset1 = MOI. IndicatorSet {MOI.ACTIVATE_ON_ONE} (MOI. LessThan (8.0 ))
391+ MOI. add_constraint (model, f1, iset1)
392+
393+ f2 = MOI. VectorAffineFunction (
394+ [MOI. VectorAffineTerm (1 , MOI. ScalarAffineTerm (1.0 , z2)),
395+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (0.2 , x1)),
396+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (1.0 , x2)),
397+ ],
398+ [0.0 , 0.0 ],
399+ )
400+ iset2 = MOI. IndicatorSet {MOI.ACTIVATE_ON_ONE} (MOI. LessThan (9.0 ))
401+
402+ MOI. add_constraint (model, f2, iset2)
403+
404+ # Additional regular constraint.
405+ MOI. add_constraint (model,
406+ MOI. ScalarAffineFunction ([MOI. ScalarAffineTerm (1.0 , x1), MOI. ScalarAffineTerm (1.0 , x2)], 0.0 ),
407+ MOI. LessThan (10.0 ),
408+ )
409+
410+ # Disjunction z1 ⋁ z2
411+ MOI. add_constraint (model,
412+ MOI. ScalarAffineFunction ([MOI. ScalarAffineTerm (1.0 , z1), MOI. ScalarAffineTerm (1.0 , z2)], 0.0 ),
413+ MOI. GreaterThan (1.0 ),
414+ )
415+
416+ MOI. set (model, MOI. ObjectiveFunction {MOI.ScalarAffineFunction{Float64}} (),
417+ MOI. ScalarAffineFunction (MOI. ScalarAffineTerm .([2.0 , 3.0 ], [x1, x2]), 0.0 )
418+ )
419+ MOI. set (model, MOI. ObjectiveSense (), MOI. MAX_SENSE)
420+
421+ if config. solve
422+ @test MOI. get (model, MOI. TerminationStatus ()) == MOI. OPTIMIZE_NOT_CALLED
423+
424+ MOI. optimize! (model)
425+
426+ @test MOI. get (model, MOI. TerminationStatus ()) == MOI. OPTIMAL
427+ @test MOI. get (model, MOI. PrimalStatus ()) == MOI. FEASIBLE_POINT
428+ @test MOI. get (model, MOI. ObjectiveValue ()) ≈ 28.75 atol= atol rtol= rtol
429+ @test MOI. get (model, MOI. VariablePrimal (), x1) ≈ 1.25 atol= atol rtol= rtol
430+ @test MOI. get (model, MOI. VariablePrimal (), x2) ≈ 8.75 atol= atol rtol= rtol
431+ @test MOI. get (model, MOI. VariablePrimal (), z1) ≈ 0.0 atol= atol rtol= rtol
432+ @test MOI. get (model, MOI. VariablePrimal (), z2) ≈ 1.0 atol= atol rtol= rtol
433+ end
434+ end
435+
436+ function indicator2_test (model:: MOI.ModelLike , config:: TestConfig )
437+ atol = config. atol
438+ rtol = config. rtol
439+ # linear problem with indicator constraint
440+ # max 2x1 + 3x2 - 30 z2
441+ # s.t. x1 + x2 <= 10
442+ # z1 ==> x2 <= 8
443+ # z2 ==> x2 + x1/5 <= 9
444+ # z1 + z2 >= 1
445+
446+ MOI. empty! (model)
447+ @test MOI. is_empty (model)
448+
449+ # This is the same model as indicator_test1, except that the penalty on z2 forces z1 to be 1.
450+
451+ x1 = MOI. add_variable (model)
452+ x2 = MOI. add_variable (model)
453+ z1 = MOI. add_variable (model)
454+ z2 = MOI. add_variable (model)
455+ MOI. add_constraint (model, z1, MOI. ZeroOne ())
456+ MOI. add_constraint (model, z2, MOI. ZeroOne ())
457+ f1 = MOI. VectorAffineFunction (
458+ [MOI. VectorAffineTerm (1 , MOI. ScalarAffineTerm (1.0 , z1)),
459+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (1.0 , x2)),
460+ ],
461+ [0.0 , 0.0 ]
462+ )
463+ iset1 = MOI. IndicatorSet {MOI.ACTIVATE_ON_ONE} (MOI. LessThan (8.0 ))
464+ MOI. add_constraint (model, f1, iset1)
465+
466+ f2 = MOI. VectorAffineFunction (
467+ [MOI. VectorAffineTerm (1 , MOI. ScalarAffineTerm (1.0 , z2)),
468+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (0.2 , x1)),
469+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (1.0 , x2)),
470+ ],
471+ [0.0 , 0.0 ],
472+ )
473+ iset2 = MOI. IndicatorSet {MOI.ACTIVATE_ON_ONE} (MOI. LessThan (9.0 ))
474+
475+ MOI. add_constraint (model, f2, iset2)
476+
477+ # additional regular constraint
478+ MOI. add_constraint (model,
479+ MOI. ScalarAffineFunction ([MOI. ScalarAffineTerm (1.0 , x1), MOI. ScalarAffineTerm (1.0 , x2)], 0.0 ),
480+ MOI. LessThan (10.0 ),
481+ )
482+
483+ # disjunction z1 ⋁ z2
484+ MOI. add_constraint (model,
485+ MOI. ScalarAffineFunction ([MOI. ScalarAffineTerm (1.0 , z1), MOI. ScalarAffineTerm (1.0 , z2)], 0.0 ),
486+ MOI. GreaterThan (1.0 ),
487+ )
488+
489+ # objective penalized on z2
490+ MOI. set (model, MOI. ObjectiveFunction {MOI.ScalarAffineFunction{Float64}} (),
491+ MOI. ScalarAffineFunction (MOI. ScalarAffineTerm .([2.0 , 3.0 , - 30.0 ], [x1, x2, z2]), 0.0 )
492+ )
493+ MOI. set (model, MOI. ObjectiveSense (), MOI. MAX_SENSE)
494+
495+ if config. solve
496+ @test MOI. get (model, MOI. TerminationStatus ()) == MOI. OPTIMIZE_NOT_CALLED
497+
498+ MOI. optimize! (model)
499+
500+ @test MOI. get (model, MOI. TerminationStatus ()) == MOI. OPTIMAL
501+ @test MOI. get (model, MOI. PrimalStatus ()) == MOI. FEASIBLE_POINT
502+ @test MOI. get (model, MOI. ObjectiveValue ()) ≈ 28.0 atol= atol rtol= rtol
503+ @test MOI. get (model, MOI. VariablePrimal (), x1) ≈ 2.0 atol= atol rtol= rtol
504+ @test MOI. get (model, MOI. VariablePrimal (), x2) ≈ 8.0 atol= atol rtol= rtol
505+ @test MOI. get (model, MOI. VariablePrimal (), z1) ≈ 1.0 atol= atol rtol= rtol
506+ @test MOI. get (model, MOI. VariablePrimal (), z2) ≈ 0.0 atol= atol rtol= rtol
507+ end
508+ end
509+
510+ function indicator3_test (model:: MOI.ModelLike , config:: TestConfig )
511+ atol = config. atol
512+ rtol = config. rtol
513+ # linear problem with indicator constraint
514+ # similar to indicator1_test with reversed z1
515+ # max 2x1 + 3x2
516+ # s.t. x1 + x2 <= 10
517+ # z1 == 0 ==> x2 <= 8
518+ # z2 == 1 ==> x2 + x1/5 <= 9
519+ # (1-z1) + z2 >= 1 <=> z2 - z1 >= 0
520+
521+ MOI. empty! (model)
522+ @test MOI. is_empty (model)
523+
524+ @test MOI. supports (model, MOI. ObjectiveFunction {MOI.ScalarAffineFunction{Float64}} ())
525+ @test MOI. supports (model, MOI. ObjectiveSense ())
526+ @test MOI. supports_constraint (model, MOI. SingleVariable, MOI. ZeroOne)
527+ @test MOI. supports_constraint (model, MOI. SingleVariable, MOI. Interval{Float64})
528+ @test MOI. supports_constraint (model, MOI. ScalarAffineFunction{Float64}, MOI. Interval{Float64})
529+ @test MOI. supports_constraint (model, MOI. VectorAffineFunction{Float64}, MOI. IndicatorSet{MOI. ACTIVATE_ON_ONE, MOI. LessThan{Float64}})
530+ x1 = MOI. add_variable (model)
531+ x2 = MOI. add_variable (model)
532+ z1 = MOI. add_variable (model)
533+ z2 = MOI. add_variable (model)
534+ MOI. add_constraint (model, z1, MOI. ZeroOne ())
535+ MOI. add_constraint (model, z2, MOI. ZeroOne ())
536+ f1 = MOI. VectorAffineFunction (
537+ [MOI. VectorAffineTerm (1 , MOI. ScalarAffineTerm (1.0 , z1)),
538+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (1.0 , x2)),
539+ ],
540+ [0.0 , 0.0 ]
541+ )
542+ iset1 = MOI. IndicatorSet {MOI.ACTIVATE_ON_ZERO} (MOI. LessThan (8.0 ))
543+ MOI. add_constraint (model, f1, iset1)
544+
545+ f2 = MOI. VectorAffineFunction (
546+ [MOI. VectorAffineTerm (1 , MOI. ScalarAffineTerm (1.0 , z2)),
547+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (0.2 , x1)),
548+ MOI. VectorAffineTerm (2 , MOI. ScalarAffineTerm (1.0 , x2)),
549+ ],
550+ [0.0 , 0.0 ],
551+ )
552+ iset2 = MOI. IndicatorSet {MOI.ACTIVATE_ON_ONE} (MOI. LessThan (9.0 ))
553+
554+ MOI. add_constraint (model, f2, iset2)
555+
556+ # Additional regular constraint.
557+ MOI. add_constraint (model,
558+ MOI. ScalarAffineFunction ([MOI. ScalarAffineTerm (1.0 , x1), MOI. ScalarAffineTerm (1.0 , x2)], 0.0 ),
559+ MOI. LessThan (10.0 ),
560+ )
561+
562+ # Disjunction (1-z1) ⋁ z2
563+ MOI. add_constraint (model,
564+ MOI. ScalarAffineFunction ([MOI. ScalarAffineTerm (- 1.0 , z1), MOI. ScalarAffineTerm (1.0 , z2)], 0.0 ),
565+ MOI. GreaterThan (0.0 ),
566+ )
567+
568+ MOI. set (model, MOI. ObjectiveFunction {MOI.ScalarAffineFunction{Float64}} (),
569+ MOI. ScalarAffineFunction (MOI. ScalarAffineTerm .([2.0 , 3.0 ], [x1, x2]), 0.0 )
570+ )
571+ MOI. set (model, MOI. ObjectiveSense (), MOI. MAX_SENSE)
572+
573+ if config. solve
574+ @test MOI. get (model, MOI. TerminationStatus ()) == MOI. OPTIMIZE_NOT_CALLED
575+
576+ MOI. optimize! (model)
577+
578+ @test MOI. get (model, MOI. TerminationStatus ()) == MOI. OPTIMAL
579+ @test MOI. get (model, MOI. PrimalStatus ()) == MOI. FEASIBLE_POINT
580+ @test MOI. get (model, MOI. ObjectiveValue ()) ≈ 28.75 atol= atol rtol= rtol
581+ @test MOI. get (model, MOI. VariablePrimal (), x1) ≈ 1.25 atol= atol rtol= rtol
582+ @test MOI. get (model, MOI. VariablePrimal (), x2) ≈ 8.75 atol= atol rtol= rtol
583+ @test MOI. get (model, MOI. VariablePrimal (), z1) ≈ 1.0 atol= atol rtol= rtol
584+ @test MOI. get (model, MOI. VariablePrimal (), z2) ≈ 1.0 atol= atol rtol= rtol
585+ end
586+ end
587+
359588const intlineartests = Dict (" knapsack" => knapsacktest,
360589 " int1" => int1test,
361590 " int2" => int2test,
362- " int3" => int3test)
591+ " int3" => int3test,
592+ " indicator1" => indicator1_test,
593+ " indicator2" => indicator2_test,
594+ " indicator3" => indicator3_test,
595+ )
363596
364597@moitestset intlinear
0 commit comments