[Cbc] Segfault / memory corruption after "assignSolver" in "CbcModel"
John Forrest
john.forrest at fastercoin.com
Fri May 9 09:58:59 EDT 2014
Dirk,
Not exactly sure where the problem was - I rearranged code a bit and it
seems OK and without a leak. See if it fixes.
John Forrest
On 09/05/14 11:00, Dirk Eßer wrote:
> Hi John,
>
> thanks for the reply. I now have a minimal example (for some
> definition of minimal, which you can find attached), with which I can
> reproduce the access violation. Right now, it seems, that I have the
> choice between
>
> * getting a segfault when trying to clean-up all solvers
> instanciated during the program run, or
> * leaking a solver instance.
>
> I am not quite comfortable with option 2, since I don't know, how much
> memory is actually leaked in this case, in particular as the actual
> application will solve quite a few MIPs sequentially in the same
> process. If it were only a couple of bytes per solver instance, then I
> am almost willing accept that as "price to pay" for the preprocessing
> step. But I don't know the inner workings of the solvers involved
> enough to make a judgement here.
>
> Also, I am still not convinced, that the error isn't entirely in our
> own code; right now, though, I don't see anything inherently wrong.
> Any help appreciated.
>
>
> Cheers,
> Dirk Eßer
>
>
>
> Am 07.05.2014 19:36, schrieb John Forrest:
>> Dirk,
>>
>> The -cpp was a nice idea - but impossible to keep current - so
>> something may be broken.
>>
>> Try using code from the Cbc/examples driver3.cpp or driver4.cpp which
>> should give you the same flexibility.
>>
>> John Forrest
>> On 07/05/14 11:18, Dirk Eßer wrote:
>>> Hello,
>>>
>>> I have a little problem adding a preprocessing step to program doing
>>> MIP solving using the CBC library. The problem is, that the program
>>> fails with more or less random memory corruption (and in the better
>>> cases: with a segfault) some of the time.
>>>
>>> The CbcModel instance is created programmatically
>>>
>>> OsiClpSolverInterface iface;
>>> CoinPackedMatrix pmx(false, num_columns, num_rows, num_elements,
>>> matrix, columns, starts, lengths);
>>>
>>> iface.setObjSense(-1.0);
>>> iface.loadProblem(pmx, 0, 0, objective, senses, rights, 0);
>>> iface.passInMessageHandler(&printer);
>>>
>>> model_ptr LP(new CbcModel(iface));
>>> ... adding stuff via LP->addObjects(...) to declare integer
>>> constraints on variables and a few SOSes ...
>>>
>>> Things work fine, if we directly compute a solution without further
>>> preprocessing via
>>>
>>> LP->initialSolve();
>>> LP->branchAndBound();
>>>
>>> We now try to add a pre-processing step to the solution process. As
>>> a starting point, we used the driver code generated by the CBC
>>> command line tool ("-cpp 0", which doesn't compile).
>>>
>>> OsiSolverInterface* original_solver = LP->solver();
>>> OsiSolverInterface* cloned_solver = original_solver->clone();
>>> OsiSolverInterface* configured_solver = 0;
>>>
>>> cloned_solver->passInMessageHandler(LP->messageHandler());
>>> cloned_solver->setHintParam(OsiDoInBranchAndCut, true, OsiHintDo);
>>>
>>> CglPreProcess processor;
>>>
>>> {
>>> CglProbing prober;
>>> prober.setUsingObjective(1);
>>> prober.setMaxPass(3);
>>> prober.setMaxProbeRoot(cloned_solver->getNumCols());
>>> prober.setMaxElements(100);
>>> prober.setMaxLookRoot(50);
>>> prober.setRowCuts(3);
>>> processor.addCutGenerator(&prober);
>>> }
>>>
>>> configured_solver =
>>> processor.preProcessNonDefault(*cloned_solver, 2, 10);
>>>
>>> cloned_solver->setHintParam(OsiDoInBranchAndCut, false, OsiHintDo);
>>>
>>> if (!configured_solver)
>>> {
>>> delete cloned_solver;
>>> return status::infeasible;
>>> }
>>>
>>> configured_solver->setHintParam(OsiDoInBranchAndCut, false,
>>> OsiHintDo);
>>>
>>> OsiSolverInterface* configured_clone = configured_solver->clone();
>>>
>>> LP->assignSolver(configured_clone, true /* Kill previous solver
>>> */); /*-- XXX --*/
>>> LP->initialSolve();
>>> LP->branchAndBound();
>>>
>>> int n_cols = cloned_solver->getNumCols();
>>> processor.postProcess(*LP->solver());
>>>
>>> LP->assignSolver(cloned_solver, true /* Kill previous solver */);
>>> memcpy(LP->bestSolution(), LP->solver()->getColSolution(),
>>> n_cols * sizeof(double));
>>>
>>> It seems, that we are violating one or two unwritten laws of memory
>>> management with CBC, but I am at loss as what we are doing wrong
>>> here. Any help, hints, pointer to documentation, etc. is highly
>>> appreciated.
>>>
>>> Cheers,
>>> Dirk Eßer
>>>
>>>
>>> _______________________________________________
>>> Cbc mailing list
>>> Cbc at list.coin-or.org
>>> http://list.coin-or.org/mailman/listinfo/cbc
>>
>>
>>
>> _______________________________________________
>> Cbc mailing list
>> Cbc at list.coin-or.org
>> http://list.coin-or.org/mailman/listinfo/cbc
>
>
>
> _______________________________________________
> Cbc mailing list
> Cbc at list.coin-or.org
> http://list.coin-or.org/mailman/listinfo/cbc
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://list.coin-or.org/pipermail/cbc/attachments/20140509/501b2ffe/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: test.log
Type: text/x-log
Size: 6011 bytes
Desc: not available
URL: <http://list.coin-or.org/pipermail/cbc/attachments/20140509/501b2ffe/attachment-0001.bin>
-------------- next part --------------
#if defined(_MSC_VER)
// Turn off compiler warning about long names
# pragma warning(disable:4786)
#endif
#include <cassert>
#include <iomanip>
#include <iostream>
#include <algorithm>
#include "OsiClpSolverInterface.hpp"
#include "CbcModel.hpp"
#include "CbcCutGenerator.hpp"
#include "CbcStrategy.hpp"
#include "CglPreProcess.hpp"
#include "CoinTime.hpp"
#include "CglProbing.hpp"
#include "CglProbing.hpp"
#include "CglGomory.hpp"
#include "CglKnapsackCover.hpp"
#include "CglClique.hpp"
#include "CglMixedIntegerRounding2.hpp"
#include "CglFlowCover.hpp"
#include "CglTwomir.hpp"
#include "CbcHeuristicFPump.hpp"
#include "CbcHeuristic.hpp"
#include "CbcHeuristicGreedy.hpp"
#include "CbcHeuristicGreedy.hpp"
#include "CbcHeuristicDiveCoefficient.hpp"
#include "CbcHeuristicRINS.hpp"
#include "CbcHeuristicLocal.hpp"
#include "CbcCompareActual.hpp"
template<class T>
class Ptr
{
public:
Ptr(): m_ptr(0) {}
template<class U> Ptr(U* p): m_ptr(p) {}
~Ptr()
{
delete m_ptr;
// if (m_ptr) std::cout << "\nMemory leak" << std::endl;
}
T* operator-> () const { return m_ptr; }
T& operator* () const { return *m_ptr; }
bool operator == (const Ptr& p) const { return m_ptr == p.m_ptr; }
bool operator != (const Ptr& p) const { return m_ptr != p.m_ptr; }
T* get() const { return m_ptr; }
void clear() { delete m_ptr; m_ptr = 0; }
T* detach()
{
T* p = m_ptr; m_ptr = 0;
return p;
}
void swap(Ptr& o) throw() { std::swap(m_ptr, o.m_ptr); }
operator bool () const { return m_ptr != 0; }
template<class U> Ptr& operator= (U* p)
{
if (p != m_ptr)
{
Ptr<T> v(p);
swap(v);
}
return *this;
}
private:
Ptr(const Ptr&);
Ptr& operator= (const Ptr&);
private:
T* m_ptr;
};
static void
assign_solver(CbcModel& model, Ptr<OsiSolverInterface>& ptr)
{
OsiSolverInterface* hack = ptr.detach();
model.assignSolver(hack);
}
static void
configure_cut_generators(CbcModel& model)
{
int n_generators = 0;
CglProbing probing;
probing.setMaxPass(1);
probing.setMaxProbe(123);
probing.setMaxLook(10);
probing.setMaxElements(200);
probing.setMaxPassRoot(1);
probing.setMaxProbeRoot(123);
probing.setMaxLookRoot(20);
probing.setMaxElementsRoot(300);
probing.setRowCuts(3);
probing.setUsingObjective(1);
model.addCutGenerator(&probing, -98, "Probing", true, false, false, -100, -1, -1);
++n_generators;
// cbcModel->cutGenerator(n_generators - 1)->setTiming(true);
CglGomory gomory;
gomory.setLimitAtRoot(1000);
gomory.setAwayAtRoot(0.005);
model.addCutGenerator(&gomory,-98,"Gomory",true,false,false,-100,-1,-1);
++n_generators;
// cbcModel->cutGenerator(n_generators - 1)->setTiming(true);
CglKnapsackCover knapsackCover;
model.addCutGenerator(&knapsackCover,-98,"KnapsackCover",true,false,false,-100,-1,-1);
++n_generators;
// cbcModel->cutGenerator(n_generators - 1)->setTiming(true);
CglClique clique;
clique.setStarCliqueReport(false);
clique.setRowCliqueReport(false);
clique.setMinViolation(0.1);
model.addCutGenerator(&clique,-98,"Clique",true,false,false,-100,-1,-1);
++n_generators;
// cbcModel->cutGenerator(n_generators - 1)->setTiming(true);
CglFlowCover flowCover;
model.addCutGenerator(&flowCover,-98,"FlowCover",true,false,false,-100,-1,-1);
++n_generators;
// cbcModel->cutGenerator(n_generators - 1)->setTiming(true);
CglTwomir twomir;
twomir.setMaxElements(250);
model.addCutGenerator(&twomir,-98,"Twomir",true,false,false,-100,-1,-1);
++n_generators;
// cbcModel->cutGenerator(n_generators - 1)->setTiming(true);
}
static void
configure_heuristics(CbcModel& model)
{
#if 0
CbcHeuristicFPump heuristicFPump(model);
heuristicFPump.setWhen(13);
heuristicFPump.setFeasibilityPumpOptions(40);
heuristicFPump.setFractionSmall(0.5);
heuristicFPump.setHeuristicName("feasibility pump");
heuristicFPump.setMaximumPasses(30);
heuristicFPump.setMaximumRetries(6);
heuristicFPump.setAccumulate(1);
heuristicFPump.setDefaultRounding(0.5);
model.addHeuristic(&heuristicFPump);
CbcRounding rounding(model);
rounding.setHeuristicName("rounding");
model.addHeuristic(&rounding);
CbcHeuristicGreedyCover heuristicGreedyCover(model);
heuristicGreedyCover.setHeuristicName("greedy cover");
heuristicGreedyCover.setWhereFrom(1);
model.addHeuristic(&heuristicGreedyCover);
CbcHeuristicGreedyEquality heuristicGreedyEquality(model);
heuristicGreedyEquality.setHeuristicName("greedy equality");
heuristicGreedyEquality.setWhereFrom(1);
model.addHeuristic(&heuristicGreedyEquality);
#endif
CbcHeuristicDiveCoefficient heuristicDiveCoefficient(model);
heuristicDiveCoefficient.setWhen(3);
heuristicDiveCoefficient.setHeuristicName("DiveCoefficient");
heuristicDiveCoefficient.setDecayFactor(1);
heuristicDiveCoefficient.setWhereFrom(493);
model.addHeuristic(&heuristicDiveCoefficient);
#if 0
CbcHeuristicRINS heuristicRINS(model);
heuristicRINS.setFractionSmall(0.5);
heuristicRINS.setHeuristicName("RINS");
heuristicRINS.setDecayFactor(5);
heuristicRINS.setWhereFrom(65289);
heuristicRINS.setShallowDepth(0);
model.addHeuristic(&heuristicRINS);
CbcHeuristicLocal heuristicLocal(model);
heuristicLocal.setFractionSmall(0.5);
heuristicLocal.setHeuristicName("combine solutions");
heuristicLocal.setSwitches(16);
heuristicLocal.setSearchType(1);
model.addHeuristic(&heuristicLocal);
#endif
CbcCompareDefault compare;
model.setNodeComparison(compare);
}
static ClpSimplex&
clp_model_of(CbcModel& model)
{
return *(dynamic_cast<OsiClpSolverInterface*>(model.solver())->getModelPtr());
}
static void
configure_model_parameters(CbcModel& model)
{
OsiSolverInterface* osiModel = model.solver();
OsiClpSolverInterface* osiclpModel = dynamic_cast<OsiClpSolverInterface*>(osiModel);
ClpSimplex* clpModel = osiclpModel->getModelPtr();
model.setPrintFrequency(100);
model.setSpecialOptions(66050);
model.messageHandler()->setLogLevel(1);
model.setMaximumCutPassesAtRoot(-100);
model.setMaximumCutPasses(4);
model.setMinimumDrop(0.000103333);
clpModel->scaling(2);
clpModel->defaultFactorizationFrequency();
clpModel->setDualBound(1.0001e+08);
clpModel->setPerturbation(50);
osiclpModel->setSpecialOptions(1057);
osiclpModel->setIntParam(OsiMaxNumIterationHotStart,100);
osiclpModel->setHintParam(OsiDoReducePrint,true,OsiHintTry);
}
static bool
tighten_primal_bounds(CbcModel& model)
{
ClpSimplex& simplex = clp_model_of(model);
if (simplex.tightenPrimalBounds() != 0)
{
return false;
}
else
{
simplex.dual(); // clean up
return true;
}
}
int
main (int argc, const char *argv[])
{
OsiClpSolverInterface solver1;
int status = 1;
status = solver1.readMps("test.mps", "");
if (status)
{
std::cout<<"Bad readMps "<<argv[1]<<std::endl;
exit(1);
}
CbcModel model(solver1);
std::cout << "After reading, the problem has "
<< solver1.getNumRows() << " rows, and "
<< solver1.getNumCols() << " columns."
<< std::endl;
configure_cut_generators(model);
configure_heuristics(model);
configure_model_parameters(model);
model.initialSolve();
if (!tighten_primal_bounds(model))
{
std::cout << "\nInitial solve says, that the problem is infeasible." << std::endl;
}
else
{
// Hand coded preprocessing
CglPreProcess process;
Ptr<OsiSolverInterface> saveSolver(model.solver()->clone());
// Tell solver we are in Branch and Cut
saveSolver->setHintParam(OsiDoInBranchAndCut, true, OsiHintDo) ;
// Default set of cut generators
CglProbing generator1;
generator1.setUsingObjective(1);
generator1.setMaxPass(3);
generator1.setMaxProbeRoot(saveSolver->getNumCols());
generator1.setMaxElements(100);
generator1.setMaxLookRoot(50);
generator1.setRowCuts(3);
// Add in generators
process.addCutGenerator(&generator1);
process.messageHandler()->setLogLevel(model.logLevel());
std::cout << std::endl << "And now, for something completely different...\n"
<< std::endl;
OsiSolverInterface *solver2 = process.preProcessNonDefault(*saveSolver, 2, 10);
// Tell solver we are not in Branch and Cut
saveSolver->setHintParam(OsiDoInBranchAndCut,false,OsiHintDo);
if (solver2)
solver2->setHintParam(OsiDoInBranchAndCut,false,OsiHintDo);
if (!solver2)
{
std::cout << "Pre-processing says infeasible!" << std::endl;
exit(1);
}
else
{
std::cout << "processed model has " << solver2->getNumRows()
<< " rows, " << solver2->getNumCols()
<< " columns and " << solver2->getNumElements()
<< " elements" << solver2->getNumElements()<<std::endl;
}
// Comment was generated; not sure, why it applies here:
// "we have to keep solver2 so pass clone"
//
// The problem is: if we use "solver2" directly (in assign_solver below),
// then we get an access violation in the call to "process.postProcess()"
// below. If we clone "solver2" (uncomment the line below and use the
// appropriate call to assign_solver) then we get an access violation
// at the end of main, when the destructors are run and "solver2" tries
// to delete its pointer.
//
// Things work fine, if we use a plain pointer for "solver2", but this
// seems to leak the memory on exit, and I am not comfortable with that,
// given that the real application solves a lot of MIPs sequentially in
// the same process.
// Ptr<OsiSolverInterface> solver3(solver2->clone()); // Works until the
// assign_solver(model, solver3); // end of main, then segfaults
//assign_solver(model, solver2); // Segfaults in postProcess()
OsiSolverInterface * solver3 = solver2->clone();
model.assignSolver(solver3);
model.initialSolve();
model.branchAndBound();
// For best solution
int numberColumns = solver1.getNumCols();
if (model.getMinimizationObjValue() < 1.0e50)
{
process.postProcess(*model.solver());
assign_solver(model, saveSolver);
memcpy(model.bestSolution(), model.solver()->getColSolution(), numberColumns * sizeof(double));
solver1.setColSolution(model.bestSolution());
}
}
std::cout << "\nDone" << std::endl;
return 0;
}
More information about the Cbc
mailing list