[Symphony] Problem adding custom cutting planes
Richard Russell
rar39 at cam.ac.uk
Tue Mar 3 08:45:14 EST 2009
Hi,
I'm having problems generating custom cutting planes. I am identifying
and attempting to add sensible cuts but they don't seem to be respected
by the LP solver, i.e. they are violated in subsequent LP solutions.
This has the result that the program just loops adding the same cut.
The problem I'm working on has a collection of constraints that are
represented explicitly in the constraint matrix. However there are some
constraints that are too numerous to exhaustively list in the constraint
matrix so I would like to add them dynamically as cuts.
I am working with the USER function stubs in SYMPHONY/Applications and I
have modified:
(1) user_find_cuts in the file 'user_cg.c'
(2) user_is_feasible in the file 'user_lp.c'
A description of what each function is doing:
(1) user_find_cuts first checks that the solution passed to it is
integer. If it isn't it lets SYMPHONY return it's default behaviour
without generating any cuts. If it is integer it then look for cycles in
a graph constructed from the solution. The graph has a node for each
variable that is set to 1.0 in the solution. These nodes are related in
the problem and have directed arcs between them. If a cycle exists in
the graph constructed from the solution then a cut needs to be added to
prevent this cycle. This is done for each cycle by adding a cut of the form:
var_1 + ... + var_n <= (n-1) for every cycle in the graph where var_1
to var_n are the nodes in the cycle.
So at least one of var_1 to var_n must be set to 0.0 in order for the
constraint to hold. This way the cycle is prevented.
(2) user_is_feasible checks that the solution is integer. If it isn't it
returns infeasible. If it is integer it checks that there are no cycles
in the graph constructed from the solution. If a cycle is found it
returns infeasible. If no cycles are found it returns feasible.
Here is most of the code, I have omitted some tedious initialisations of
arrays and such.
///////////////////////////////////////////////////////////////////
int user_find_cuts(void *user, int varnum, int iter_num, int level,
int index, double objval, int *indices, double *values,
double ub, double etol, int *num_cuts, int *alloc_cuts,
cut_data ***cuts)
{
user_problem *prob = (user_problem *) user;
int a,k,t,i;
int horizon;
int result;
SASPtr sasptr;
IPGraphPtr ipgraphptr;
List<IPNodePtr>** cyclelist;
int *nrcycles;
// Cut related variables
int nzcnt;
int *cut_indices;
double *cut_values;
double rhs;
bool integer = true;
// Debug information
printf("Entering cut generating routine.\n");
printf("Node details:\n");
printf("Index = %d\n", index);
printf("Level = %d\n", level);
printf("Iter = %d\n", iter_num);
// Initialise cycle data structures (Code omitted)
sasptr = prob->solverPtr->GetSASPtr();
horizon = prob->solverPtr->GetHorizon();
// Initialise vars[a][k][t] array (code omitted)
// Construct full 0-1 solution from non-zero encoding
for(t=0; t<horizon; t++) {
for(a=0; a<sasptr->GetNrActions(); a++) {
for(k=0; k<sasptr->GetNrActionProfiles(a); k++) {
vars[a][k][t] = 0.0;
for (i=0; i<varnum; i++) {
if (indices[i] == prob->solverPtr->mX[a][k][t].getID()) {
printf("%d = %e\n", indices[i], values[i]);
vars[a][k][t] = values[i];
if ( fabs(values[i]-1.0) > 0.001 ) {
integer = false;
}
break;
}
}
}
}
}
// If not an integer solution then don't generate cuts
if (!integer) {
printf("LP solution not integer, do not generate cuts.\n");
return (USER_DEFAULT);
}
// Count number of cycles for each time step
printf("Checking for cycles in cut generation...\n");
for(t=0; t<horizon; t++) {
ipgraphptr = prob->solverPtr->GetIPGraph(t, vars);
// Returns number of cycles and stores cycles in cyclelist[t] parameter
nrcycles[t] = ipgraphptr->GetIntegralCycles(cyclelist[t], MAXCYCLES);
printf("Time step = %d, Number of cycles = %d\n", t, nrcycles[t]);
}
int cut_count = 0;
for(t=0; t<horizon; t++) {
for(i=0; (i<MAXCYCLES) && (cyclelist[t][i].GetSize()>0); i++) {
nzcnt = 0;
cut_indices = new int[cyclelist[t][i].GetSize()];
cut_values = new double[cyclelist[t][i].GetSize()];
printf("Adding following cut:\n");
ListIterator<IPNode*> iter(cyclelist[t][i]);
for(iter.GotoBegin(); !iter.AtEnd(); iter.GotoNext()) {
a = IPNodePtr(iter.GetElement())->GetA();
k = IPNodePtr(iter.GetElement())->GetK();
t = IPNodePtr(iter.GetElement())->GetT();
cut_indices[nzcnt] = prob->solverPtr->mX[a][k][t].getID();
cut_values[nzcnt] = 1.0;
// Output debug information: the cut being generated
printf("%e*%d + ",cut_values[nzcnt], cut_indices[nzcnt]);
nzcnt++;
}
rhs = ((double)nzcnt) - 1.0;
// Output debug information: rhs
printf("<= %e\n", rhs);
result = cg_add_explicit_cut(nzcnt, cut_indices, cut_values, rhs,
0.0,
'L', TRUE, num_cuts, alloc_cuts, cuts);
if ( result != 1 ) {
printf("WARNING: Cuts not being generated.\n");
}
else {
cut_count++;
}
}
}
if (cut_count > 0)
return (USER_SUCCESS);
return (USER_DEFAULT);
}
//////////////////////////////////////////////////////////////////////////
int user_is_feasible(void *user, double lpetol, int varnum, int *indices,
double *values, int *feasible, double *objval,
char branching, double *heur_solution)
{
user_problem *prob = (user_problem *) user;
int a,k,t,i,j;
int size, sum;
int horizon;
SASPtr sasptr;
IPGraphPtr ipgraphptr;
List<IPNodePtr>** cyclelist;
int *nrcycles;
// Cut related variables
bool integer = true;
printf("Entering user_is_feasible function.\n");
// Initialise cycle data structures (code omitted)
sasptr = prob->solverPtr->GetSASPtr();
horizon = prob->solverPtr->GetHorizon();
// Initialise vars[a][k][t] array (Code omitted)
// Construct full 0-1 solution from non-zero encoding
for(t=0; t<horizon; t++) {
for(a=0; a<sasptr->GetNrActions(); a++) {
for(k=0; k<sasptr->GetNrActionProfiles(a); k++) {
vars[a][k][t] = 0.0;
for (i=0; i<varnum; i++) {
if (indices[i] == prob->solverPtr->mX[a][k][t].getID()) {
printf("%d = %e\n", indices[i], values[i]);
vars[a][k][t] = 1.0;
if ( fabs(values[i] - 1.0) > lpetol) {
integer = false;
}
break;
}
}
}
}
}
// First check that the solution is integer
if (!integer) {
printf("INFEASIBLE: Not integer.\n");
*feasible = IP_INFEASIBLE;
return (USER_SUCCESS);
}
printf("Checking for cycles...\n");
bool violated_cycles = false;
// Count the number of cycles for each time step
for(t=0; t<horizon; t++) {
ipgraphptr = prob->solverPtr->GetIPGraph(t, vars);
nrcycles[t] = ipgraphptr->GetIntegralCycles(cyclelist[t], MAXCYCLES);
if (nrcycles[t] > 0) {
violated_cycles = true;
}
// Output debug information
printf("Time step = %d, Number of cycles = %d\n", t, nrcycles[t]);
}
// If cycles have been found output the corresponding violated constraint
if( violated_cycles ) {
printf("INFEASIBLE: Violated cycle constraint.\n");
printf("Found the following violated cycles:\n");
for(t=0;t<horizon;t++) {
for (i=0; (i<MAXCYCLES) && (cyclelist[t][i].GetSize()>0); i++) {
sum = 0;
size = 0;
ListIterator<IPNode*> iter(cyclelist[t][i]);
for (iter.GotoBegin(); !iter.AtEnd(); iter.GotoNext()) {
a = IPNodePtr(iter.GetElement())->GetA();
k = IPNodePtr(iter.GetElement())->GetK();
t = IPNodePtr(iter.GetElement())->GetT();
printf("%e*%d +", vars[a][k][t],
prob->solverPtr->mX[a][k][t].getID());
size++;
}
printf("<= %d\n", size-1);
}
}
// Return infeasible solution
*feasible = IP_INFEASIBLE;
return (USER_SUCCESS);
}
else {
// Integer and no cycles so solution is feasible
*feasible = IP_FEASIBLE;
return (USER_SUCCESS);
}
}
Running the above code, here is a sample of the output I get:
Entering user_is_feasible function.
74 = 1.000000e+00
116 = 1.000000e+00
99 = 1.000000e+00
131 = 1.000000e+00
Checking for cycles...
Time step = 0, Number of cycles = 1
Time step = 1, Number of cycles = 0
INFEASIBLE: Violated cycle constraint.
Found the following violated cycles:
1.000000e+00*116 +1.000000e+00*74 +<= 1
Entering cut generating routine.
Node details:
Index = 2
Level = 1
Iter = 142
74 = 1.000000e+00
116 = 1.000000e+00
99 = 1.000000e+00
131 = 1.000000e+00
Checking for cycles in cut generation...
Time step = 0, Number of cycles = 1
Time step = 1, Number of cycles = 0
Adding following cut:
1.000000e+00*116 + 1.000000e+00*74 + <= 1.000000e+00
Entering user_is_feasible function.
74 = 1.000000e+00
116 = 1.000000e+00
99 = 1.000000e+00
131 = 1.000000e+00
Checking for cycles...
Time step = 0, Number of cycles = 1
Time step = 1, Number of cycles = 0
INFEASIBLE: Violated cycle constraint.
Found the following violated cycles:
1.000000e+00*116 +1.000000e+00*74 +<= 1
This just continues repeating with node index and level remaining
unchanged and the iteration number increasing.
Adding the cut should prohibit variable #74 and #116 being
simultaneously set to 1.0 but the cut doesn't seem to have any effect.
Does anybody notice anything I haven't done that I should or have any
suggestions for things I could try?
I would be very grateful for any help anyone could offer.
Kind regards,
Richard
More information about the Symphony
mailing list