Skip to content

Commit 166b1df

Browse files
Add functionality and test for external hyperplane generation
1 parent 1b634a2 commit 166b1df

File tree

4 files changed

+239
-2
lines changed

4 files changed

+239
-2
lines changed

src/CallbackData.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,41 @@ struct ExternalPrimalSolutionCallbackData
137137
}
138138
};
139139

140+
/**
141+
* @brief Data structure for external hyperplane selection callback events
142+
*
143+
* Provides context information for callbacks that provide external hyperplanes.
144+
*/
145+
struct ExternalHyperplaneSelectionCallbackData
146+
{
147+
bool isMinimization; ///< True if minimization problem, false if maximization problem
148+
int iterationNumber; ///< Current iteration number
149+
double currentDualBound; ///< Current dual bound value
150+
double currentPrimalBound; ///< Current primal bound value
151+
double relativeGap; ///< Current relative gap
152+
double absoluteGap; ///< Current absolute gap
153+
std::vector<SolutionPoint> solutionPoints; ///< Solution points from current iteration for hyperplane generation
154+
ProblemPtr originalProblem; ///< Pointer to the original problem
155+
ProblemPtr reformulatedProblem; ///< Pointer to the reformulated problem
156+
bool isObjectiveNonlinear; ///< True if the objective function of the reformulated problem is nonlinear
157+
SolutionStatistics solutionStatistics; ///< Statistics about the current solution state
158+
159+
ExternalHyperplaneSelectionCallbackData(bool minimize, int iteration, double dualBound, double primalBound,
160+
double relGap, double absGap, const std::vector<SolutionPoint>& points, ProblemPtr origProblem,
161+
ProblemPtr reformProblem, bool objNonlinear, SolutionStatistics stats)
162+
: isMinimization(minimize)
163+
, iterationNumber(iteration)
164+
, currentDualBound(dualBound)
165+
, currentPrimalBound(primalBound)
166+
, relativeGap(relGap)
167+
, absoluteGap(absGap)
168+
, solutionPoints(points)
169+
, originalProblem(origProblem)
170+
, reformulatedProblem(reformProblem)
171+
, isObjectiveNonlinear(objNonlinear)
172+
, solutionStatistics(stats)
173+
{
174+
}
175+
};
176+
140177
} // namespace SHOT

src/Tasks/TaskSelectHyperplanesExternal.cpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
#include "TaskSelectHyperplanesExternal.h"
1111

12+
#include "../CallbackData.h"
1213
#include "../DualSolver.h"
1314
#include "../EventHandler.h"
1415
#include "../MIPSolver/IMIPSolver.h"
@@ -37,11 +38,49 @@ void TaskSelectHyperplanesExternal::run() { this->run(env->results->getPreviousI
3738

3839
void TaskSelectHyperplanesExternal::run(std::vector<SolutionPoint> solutionPoints)
3940
{
41+
// Only execute if there are external hyperplane data providers registered
42+
if(!env->events->hasDataProvider(E_EventType::ExternalHyperplaneSelection))
43+
return;
44+
4045
env->timing->startTimer("CallbackExternalHyperplaneGeneration");
4146

4247
env->output->outputDebug(" Selecting cutting planes using external callback functionality:");
4348

44-
env->events->notify(E_EventType::ExternalHyperplaneSelection, solutionPoints);
49+
// Gather current state information for the callback
50+
bool isMinimization = (env->reformulatedProblem->objectiveFunction->properties.isMinimize);
51+
int iterationNumber = env->results->getCurrentIteration()->iterationNumber;
52+
double currentDualBound = env->results->getCurrentDualBound();
53+
double currentPrimalBound = env->results->getPrimalBound();
54+
double relativeGap = env->results->getRelativeCurrentObjectiveGap();
55+
double absoluteGap = env->results->getAbsoluteCurrentObjectiveGap();
56+
57+
// Check if the objective function is nonlinear
58+
bool isObjectiveNonlinear = (env->reformulatedProblem->objectiveFunction->properties.classification
59+
>= E_ObjectiveFunctionClassification::Quadratic);
60+
61+
// Create callback data with comprehensive context information
62+
ExternalHyperplaneSelectionCallbackData callbackData(isMinimization, iterationNumber, currentDualBound,
63+
currentPrimalBound, relativeGap, absoluteGap, solutionPoints, env->problem, env->reformulatedProblem,
64+
isObjectiveNonlinear, env->solutionStatistics);
65+
66+
// Request external hyperplanes from registered data provider callbacks
67+
auto externalHyperplanes = env->events->requestData<std::vector<ExternalHyperplane>>(
68+
E_EventType::ExternalHyperplaneSelection, callbackData);
69+
70+
if(externalHyperplanes.has_value() && !externalHyperplanes->empty())
71+
{
72+
env->output->outputDebug(fmt::format(" Received {} external hyperplanes from callback at iteration {}",
73+
externalHyperplanes->size(), iterationNumber));
74+
75+
// Add each received hyperplane to the dual solver
76+
for(const auto& HP : *externalHyperplanes)
77+
{
78+
env->dualSolver->addHyperplane(std::make_shared<ExternalHyperplane>(HP));
79+
}
80+
81+
env->output->outputDebug(fmt::format(
82+
" Successfully added {} external hyperplanes to dual solver", externalHyperplanes->size()));
83+
}
4584

4685
env->timing->stopTimer("CallbackExternalHyperplaneGeneration");
4786
}

test/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ set(Solver_parts
5858
4
5959
5
6060
6
61-
7)
61+
7
62+
8)
6263
set(cpptests ${cpptests} Solver)
6364

6465
if(HAS_IPOPT)

test/SolverTest.cpp

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 << "\nSolution 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+
722877
int 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

Comments
 (0)