@@ -719,6 +719,161 @@ bool TestCallbackUserTermination()
719719 return passed;
720720}
721721
722+ bool TestCallbackExternalHyperplane ()
723+ {
724+ bool passed = true ;
725+
726+ auto solver = std::make_unique<SHOT::Solver>();
727+
728+ // Contains the environment variable unique to the created solver instance
729+ auto env = solver->getEnvironment ();
730+ solver->updateSetting (" Console.LogLevel" , " Output" , static_cast <int >(E_LogLevel::Off));
731+ solver->updateSetting (" Convexity.AssumeConvex" , " Model" , true );
732+ solver->updateSetting (" Debug.Enable" , " Output" , true );
733+ solver->updateSetting (" Reformulation.Constraint.PartitionQuadraticTerms" , " Model" , 2 );
734+ solver->updateSetting (" Relaxation.Use" , " Dual" , false );
735+ solver->updateSetting (" CutStrategy" , " Dual" , 1 );
736+
737+ // Initializing a SHOT problem class
738+ auto problem = std::make_shared<SHOT::Problem>(env);
739+ problem->name = " ex1223b" ;
740+
741+ // Creating the variables
742+ auto x1 = std::make_shared<Variable>(" x1" , 0 , E_VariableType::Integer, 0.0 , 3.0 );
743+ auto x2 = std::make_shared<Variable>(" x2" , 1 , E_VariableType::Integer, 1.0 , 3.0 );
744+
745+ // All variables are nonlinear, so need to add expression variables as well
746+ auto nl_x1 = std::make_shared<ExpressionVariable>(x1);
747+ auto nl_x2 = std::make_shared<ExpressionVariable>(x2);
748+
749+ // Adding the variables to the problem
750+ problem->add ({ x1, x2 });
751+
752+ // Creating the objective function
753+ // minimize -x1 -2x2
754+
755+ auto objective = std::make_shared<LinearObjectiveFunction>(E_ObjectiveFunctionDirection::Minimize);
756+ problem->add (objective);
757+
758+ objective->add (std::make_shared<LinearTerm>(-1.0 , x1));
759+ objective->add (std::make_shared<LinearTerm>(-2.0 , x2));
760+
761+ // Creating the constraint e1: 0.1 e^x2 + x1^2 + x2 <= 10;
762+ auto e1 = std::make_shared<NonlinearConstraint>(0 , " e1" , SHOT_DBL_MIN, 10.0 );
763+ e1 ->add (std::make_shared<QuadraticTerm>(1.0 , x1, x1));
764+ e1 ->add (std::make_shared<LinearTerm>(1.0 , x2));
765+
766+ e1 ->add (std::make_shared<ExpressionProduct>(
767+ std::make_shared<ExpressionConstant>(0.1 ), std::make_shared<ExpressionExp>(nl_x2)));
768+ problem->add (e1 );
769+
770+ // Creating the constraint e2: e^x1 / x2 <= 3;
771+ auto e2 = std::make_shared<NonlinearConstraint>(1 , " e2" , SHOT_DBL_MIN, 3.0 );
772+
773+ e2 ->add (std::make_shared<ExpressionDivide>(std::make_shared<ExpressionExp>(nl_x1), nl_x2));
774+ problem->add (e2 );
775+
776+ NonlinearConstraints constraints = { e1 , e2 };
777+
778+ // Add constraints to a vector
779+
780+ // Finalize the problem object (after this no changes should be made)
781+ problem->updateProperties ();
782+ problem->finalize ();
783+ solver->setProblem (problem, problem);
784+
785+ // Writing the problem to console
786+ std::cout << ' \n ' ;
787+ std::cout << " Problem created:\n\n " ;
788+ std::cout << env->problem << ' \n ' ;
789+
790+ // Writing the reformulated problem to console
791+ std::cout << ' \n ' ;
792+ std::cout << " Reformulated problem created:\n\n " ;
793+ std::cout << env->reformulatedProblem << ' \n ' ;
794+
795+ // Register external hyperplane callback
796+ solver->registerCallback (E_EventType::ExternalHyperplaneSelection, [&env, &constraints](std::any args) -> std::any {
797+ auto data = std::any_cast<ExternalHyperplaneSelectionCallbackData>(args);
798+
799+ std::cout << " External hyperplane callback called at iteration " << data.iterationNumber << std::endl;
800+ std::cout << " Current dual bound: " << data.currentDualBound << std::endl;
801+ std::cout << " Current primal bound: " << data.currentPrimalBound << std::endl;
802+ std::cout << " Number of solution points: " << data.solutionPoints .size () << std::endl;
803+
804+ std::vector<ExternalHyperplane> hyperplanes;
805+
806+ // Example: Add a simple cutting plane if we have solution points
807+ if (!data.solutionPoints .empty () && data.iterationNumber > 0 )
808+ {
809+ for (const auto & solPoint : data.solutionPoints )
810+ {
811+ std::cout << " \n Solution point: \n " ;
812+ Utilities::displayVector (solPoint.point );
813+
814+ // Constraint with largest error
815+ auto constraint = constraints.at (solPoint.maxDeviation .index );
816+
817+ double funcValue = constraint->calculateFunctionValue (solPoint.point ) - constraint->valueRHS ;
818+ auto gradient = constraint->calculateGradient (solPoint.point , false );
819+
820+ // This is just an example - in practice you'd generate meaningful hyperplanes
821+ ExternalHyperplane hyperplane;
822+
823+ double constant = funcValue;
824+ constant += (-gradient[env->reformulatedProblem ->getVariable (0 )]) * solPoint.point .at (0 );
825+ constant += (-gradient[env->reformulatedProblem ->getVariable (1 )]) * solPoint.point .at (1 );
826+
827+ // Set hyperplane properties
828+ hyperplane.variableIndexes = { 0 , 1 }; // x1, x2
829+ hyperplane.variableCoefficients .emplace_back () = gradient[env->reformulatedProblem ->getVariable (0 )];
830+ hyperplane.variableCoefficients .emplace_back () = gradient[env->reformulatedProblem ->getVariable (1 )];
831+ hyperplane.rhsValue = -constant; // RHS
832+ hyperplane.isGlobal = true ;
833+
834+ hyperplanes.push_back (hyperplane);
835+
836+ std::cout << " Generetaed hyperplane variable coefficients: \n " ;
837+ Utilities::displayVector (hyperplane.variableCoefficients );
838+ std::cout << " RHS value: " << hyperplane.rhsValue << std::endl;
839+
840+ break ; // Only add one hyperplane per iterations
841+ }
842+ }
843+
844+ std::cout << " Returning " << hyperplanes.size () << " external hyperplanes" << std::endl;
845+ return std::make_any<std::vector<ExternalHyperplane>>(hyperplanes);
846+ });
847+
848+ solver->solveProblem ();
849+
850+ if (solver->getPrimalSolutions ().size () > 0 )
851+ {
852+ std::cout << " Solution found: \n " ;
853+ std::cout << std::endl << " Objective value: " << solver->getPrimalSolution ().objValue << std::endl;
854+ std::cout << std::endl << " Solution point: \n " ;
855+ Utilities::displayVector (solver->getPrimalSolution ().point );
856+
857+ if (solver->getPrimalSolution ().objValue == -8 && solver->getPrimalSolution ().point .size () == 2
858+ && solver->getPrimalSolution ().point [0 ] == 2 && solver->getPrimalSolution ().point [1 ] == 3 )
859+ {
860+ std::cout << " Ok, solution is correct!" << std::endl;
861+ passed = true ;
862+ }
863+ else
864+ {
865+ std::cout << " Error: solution is not correct!" << std::endl;
866+ passed = false ;
867+ }
868+ }
869+ else
870+ {
871+ passed = false ;
872+ }
873+
874+ return passed;
875+ }
876+
722877int SolverTest (int argc, char * argv[])
723878{
724879 int defaultchoice = 1 ;
@@ -773,6 +928,11 @@ int SolverTest(int argc, char* argv[])
773928 passed = TestCallbackUserTermination ();
774929 std::cout << " Finished test for callback system - user termination check." << std::endl;
775930 break ;
931+ case 8 :
932+ std::cout << " Starting test for callback system - external hyperplanes" << std::endl;
933+ passed = TestCallbackExternalHyperplane ();
934+ std::cout << " Finished test for callback system - external hyperplanes." << std::endl;
935+ break ;
776936 default :
777937 passed = false ;
778938 std::cout << " Test #" << choice << " does not exist!\n " ;
0 commit comments