@@ -647,4 +647,229 @@ public void testMPSolver_setHintAndSolverGetters() {
647647
648648 assertFalse (solver .setNumThreads (4 ));
649649 }
650+
651+ private void runSolveWithHint (MPSolver .OptimizationProblemType problemType ) {
652+ if (!MPSolver .supportsProblemType (problemType )) {
653+ return ;
654+ }
655+ final MPSolver solver = new MPSolver ("testSolveWithHint" , problemType );
656+ assertNotNull (solver );
657+
658+ final double infinity = MPSolver .infinity ();
659+ final MPVariable x = solver .makeIntVar (0.0 , infinity , "x" );
660+ final MPVariable y = solver .makeIntVar (0.0 , infinity , "y" );
661+
662+ // Maximize x + 10 * y.
663+ final MPObjective objective = solver .objective ();
664+ objective .setCoefficient (x , 1 );
665+ objective .setCoefficient (y , 10 );
666+ objective .setMaximization ();
667+
668+ // x + 7 * y <= 17.5.
669+ final MPConstraint c0 = solver .makeConstraint (-infinity , 17.5 , "c0" );
670+ c0 .setCoefficient (x , 1 );
671+ c0 .setCoefficient (y , 7 );
672+
673+ // x <= 3.5.
674+ final MPConstraint c1 = solver .makeConstraint (-infinity , 3.5 , "c1" );
675+ c1 .setCoefficient (x , 1 );
676+
677+ // Provide a feasible hint to guide the solver.
678+ solver .setHint (new MPVariable [] {x , y }, new double [] {2.0 , 1.0 });
679+
680+ assertEquals (MPSolver .ResultStatus .OPTIMAL , solver .solve ());
681+ // Optimal: x = 3, y = 2, obj = 3 + 20 = 23.
682+ // With x <= 3.5 and integer, x max is 3. Then
683+ // x + 7*y <= 17.5 → 3 + 7*y <= 17.5 → 7*y <= 14.5 → y <= 2, so y = 2.
684+ assertThat (objective .value ()).isWithin (NUM_TOLERANCE ).of (23.0 );
685+ assertThat (x .solutionValue ()).isWithin (NUM_TOLERANCE ).of (3.0 );
686+ assertThat (y .solutionValue ()).isWithin (NUM_TOLERANCE ).of (2.0 );
687+ }
688+
689+ @ Test
690+ public void testMPSolver_solveWithHint () {
691+ runSolveWithHint (MPSolver .OptimizationProblemType .CBC_MIXED_INTEGER_PROGRAMMING );
692+ runSolveWithHint (MPSolver .OptimizationProblemType .SCIP_MIXED_INTEGER_PROGRAMMING );
693+ runSolveWithHint (MPSolver .OptimizationProblemType .SAT_INTEGER_PROGRAMMING );
694+ runSolveWithHint (MPSolver .OptimizationProblemType .GUROBI_MIXED_INTEGER_PROGRAMMING );
695+ }
696+
697+ private void runSolveWithAndWithoutHint (
698+ MPSolver .OptimizationProblemType problemType , boolean useHint ) {
699+ if (!MPSolver .supportsProblemType (problemType )) {
700+ return ;
701+ }
702+ final MPSolver solver =
703+ new MPSolver ("testSolveWithAndWithoutHint" , problemType );
704+ assertNotNull (solver );
705+
706+ final double infinity = MPSolver .infinity ();
707+ final MPVariable x = solver .makeIntVar (0.0 , infinity , "x" );
708+ final MPVariable y = solver .makeIntVar (0.0 , infinity , "y" );
709+ final MPVariable z = solver .makeIntVar (0.0 , infinity , "z" );
710+
711+ // Maximize 10*x + 6*y + 4*z.
712+ final MPObjective objective = solver .objective ();
713+ objective .setCoefficient (x , 10 );
714+ objective .setCoefficient (y , 6 );
715+ objective .setCoefficient (z , 4 );
716+ objective .setMaximization ();
717+
718+ // x + y + z <= 100.
719+ final MPConstraint c0 = solver .makeConstraint (-infinity , 100.0 );
720+ c0 .setCoefficient (x , 1 );
721+ c0 .setCoefficient (y , 1 );
722+ c0 .setCoefficient (z , 1 );
723+
724+ // 10*x + 4*y + 5*z <= 600.
725+ final MPConstraint c1 = solver .makeConstraint (-infinity , 600.0 );
726+ c1 .setCoefficient (x , 10 );
727+ c1 .setCoefficient (y , 4 );
728+ c1 .setCoefficient (z , 5 );
729+
730+ // 2*x + 2*y + 6*z <= 300.
731+ final MPConstraint c2 = solver .makeConstraint (-infinity , 300.0 );
732+ c2 .setCoefficient (x , 2 );
733+ c2 .setCoefficient (y , 2 );
734+ c2 .setCoefficient (z , 6 );
735+
736+ if (useHint ) {
737+ // Provide a feasible hint: (x=30, y=70, z=0) satisfies all constraints:
738+ // 30 + 70 + 0 = 100 <= 100
739+ // 10*30 + 4*70 + 5*0 = 300 + 280 = 580 <= 600
740+ // 2*30 + 2*70 + 6*0 = 60 + 140 = 200 <= 300
741+ solver .setHint (new MPVariable [] {x , y , z }, new double [] {30.0 , 70.0 , 0.0 });
742+ }
743+
744+ assertEquals (MPSolver .ResultStatus .OPTIMAL , solver .solve ());
745+ // Same problem as runLinearSolver with integer variables:
746+ // optimal: x=33, y=67, z=0, obj = 10*33 + 6*67 + 4*0 = 330 + 402 = 732.
747+ assertThat (objective .value ()).isWithin (NUM_TOLERANCE ).of (732.0 );
748+ assertThat (x .solutionValue ()).isWithin (NUM_TOLERANCE ).of (33.0 );
749+ assertThat (y .solutionValue ()).isWithin (NUM_TOLERANCE ).of (67.0 );
750+ assertThat (z .solutionValue ()).isWithin (NUM_TOLERANCE ).of (0.0 );
751+ }
752+
753+ @ Test
754+ public void testMPSolver_solveWithAndWithoutHint () {
755+ for (MPSolver .OptimizationProblemType solverType :
756+ new MPSolver .OptimizationProblemType [] {
757+ MPSolver .OptimizationProblemType .CBC_MIXED_INTEGER_PROGRAMMING ,
758+ MPSolver .OptimizationProblemType .SCIP_MIXED_INTEGER_PROGRAMMING ,
759+ MPSolver .OptimizationProblemType .SAT_INTEGER_PROGRAMMING ,
760+ MPSolver .OptimizationProblemType .GUROBI_MIXED_INTEGER_PROGRAMMING ,
761+ }) {
762+ // Solve without hint.
763+ runSolveWithAndWithoutHint (solverType , /*useHint=*/ false );
764+ // Solve with hint.
765+ runSolveWithAndWithoutHint (solverType , /*useHint=*/ true );
766+ }
767+ }
768+
769+ private void runSolveWithBadHint (MPSolver .OptimizationProblemType problemType ) {
770+ if (!MPSolver .supportsProblemType (problemType )) {
771+ return ;
772+ }
773+ final MPSolver solver =
774+ new MPSolver ("testSolveWithBadHint" , problemType );
775+ assertNotNull (solver );
776+
777+ final double infinity = MPSolver .infinity ();
778+ final MPVariable x = solver .makeIntVar (0.0 , infinity , "x" );
779+ final MPVariable y = solver .makeIntVar (0.0 , infinity , "y" );
780+
781+ // Maximize x + 10 * y.
782+ final MPObjective objective = solver .objective ();
783+ objective .setCoefficient (x , 1 );
784+ objective .setCoefficient (y , 10 );
785+ objective .setMaximization ();
786+
787+ // x + 7 * y <= 17.5.
788+ final MPConstraint c0 = solver .makeConstraint (-infinity , 17.5 , "c0" );
789+ c0 .setCoefficient (x , 1 );
790+ c0 .setCoefficient (y , 7 );
791+
792+ // x <= 3.5.
793+ final MPConstraint c1 = solver .makeConstraint (-infinity , 3.5 , "c1" );
794+ c1 .setCoefficient (x , 1 );
795+
796+ // Provide a very suboptimal but still feasible hint:
797+ // (x=0, y=0) yields obj = 0 + 10*0 = 0, but the true optimum is 23.
798+ // If the solver blindly accepted the hint and stopped early, it would
799+ // return obj=0. Verifying obj=23 proves the solver continued searching
800+ // past the hint and found the true optimum.
801+ solver .setHint (new MPVariable [] {x , y }, new double [] {0.0 , 0.0 });
802+
803+ assertEquals (MPSolver .ResultStatus .OPTIMAL , solver .solve ());
804+ // Optimal: x = 3, y = 2, obj = 3 + 20 = 23.
805+ assertThat (objective .value ()).isWithin (NUM_TOLERANCE ).of (23.0 );
806+ assertThat (x .solutionValue ()).isWithin (NUM_TOLERANCE ).of (3.0 );
807+ assertThat (y .solutionValue ()).isWithin (NUM_TOLERANCE ).of (2.0 );
808+ }
809+
810+ @ Test
811+ public void testMPSolver_solveWithBadHint () {
812+ runSolveWithBadHint (
813+ MPSolver .OptimizationProblemType .CBC_MIXED_INTEGER_PROGRAMMING );
814+ runSolveWithBadHint (
815+ MPSolver .OptimizationProblemType .SCIP_MIXED_INTEGER_PROGRAMMING );
816+ runSolveWithBadHint (
817+ MPSolver .OptimizationProblemType .SAT_INTEGER_PROGRAMMING );
818+ runSolveWithBadHint (
819+ MPSolver .OptimizationProblemType .GUROBI_MIXED_INTEGER_PROGRAMMING );
820+ }
821+
822+ private void runSolveWithInfeasibleHint (
823+ MPSolver .OptimizationProblemType problemType ) {
824+ if (!MPSolver .supportsProblemType (problemType )) {
825+ return ;
826+ }
827+ final MPSolver solver =
828+ new MPSolver ("testSolveWithInfeasibleHint" , problemType );
829+ assertNotNull (solver );
830+
831+ final double infinity = MPSolver .infinity ();
832+ final MPVariable x = solver .makeIntVar (0.0 , infinity , "x" );
833+ final MPVariable y = solver .makeIntVar (0.0 , infinity , "y" );
834+
835+ // Maximize x + 10 * y.
836+ final MPObjective objective = solver .objective ();
837+ objective .setCoefficient (x , 1 );
838+ objective .setCoefficient (y , 10 );
839+ objective .setMaximization ();
840+
841+ // x + 7 * y <= 17.5.
842+ final MPConstraint c0 = solver .makeConstraint (-infinity , 17.5 , "c0" );
843+ c0 .setCoefficient (x , 1 );
844+ c0 .setCoefficient (y , 7 );
845+
846+ // x <= 3.5.
847+ final MPConstraint c1 = solver .makeConstraint (-infinity , 3.5 , "c1" );
848+ c1 .setCoefficient (x , 1 );
849+
850+ // Provide an infeasible hint: (x=10, y=10) violates both constraints:
851+ // x + 7*y = 10 + 70 = 80 > 17.5
852+ // x = 10 > 3.5
853+ // The solver should detect infeasibility and ignore the hint, solving
854+ // the problem normally and finding the true optimum.
855+ solver .setHint (new MPVariable [] {x , y }, new double [] {10.0 , 10.0 });
856+
857+ assertEquals (MPSolver .ResultStatus .OPTIMAL , solver .solve ());
858+ // Optimal: x = 3, y = 2, obj = 3 + 20 = 23.
859+ assertThat (objective .value ()).isWithin (NUM_TOLERANCE ).of (23.0 );
860+ assertThat (x .solutionValue ()).isWithin (NUM_TOLERANCE ).of (3.0 );
861+ assertThat (y .solutionValue ()).isWithin (NUM_TOLERANCE ).of (2.0 );
862+ }
863+
864+ @ Test
865+ public void testMPSolver_solveWithInfeasibleHint () {
866+ runSolveWithInfeasibleHint (
867+ MPSolver .OptimizationProblemType .CBC_MIXED_INTEGER_PROGRAMMING );
868+ runSolveWithInfeasibleHint (
869+ MPSolver .OptimizationProblemType .SCIP_MIXED_INTEGER_PROGRAMMING );
870+ runSolveWithInfeasibleHint (
871+ MPSolver .OptimizationProblemType .SAT_INTEGER_PROGRAMMING );
872+ runSolveWithInfeasibleHint (
873+ MPSolver .OptimizationProblemType .GUROBI_MIXED_INTEGER_PROGRAMMING );
874+ }
650875}
0 commit comments