/** * APPheuristics callback * * Called by DIP, we interface with Python */ int DippyDecompApp::APPheuristics(const double* xhat, const double* origCost, vector<DecompSolution*>& xhatIPFeas) { if (!m_pyHeuristics) { return 0; } PyObject* pSolution = pyTupleList_FromDoubleArray(xhat, m_colList); PyObject* pObjective = pyTupleList_FromDoubleArray(origCost, m_colList); PyObject* pSolList = PyObject_CallMethod(m_pProb, "solveHeuristics", "OO", pSolution, pObjective); if (pSolList == NULL) { throw UtilException("Error calling method prob.solveHeuristics()", "APPheuristics", "DippyDecompApp"); } // This should never happen, pyHeuristics should be set to false in dippy.py if (pSolList == Py_None) // method exists, but is not implemented, return 0 { return 0; } // APPheuristics returns dictionary of (variable, value) pairs const int len = PyObject_Length(pSolList); // loop through each solution for (int i = 0; i < len; i++) { pSolution = PyList_GetItem(pSolList, i); int* varInds = NULL; double* varVals = NULL; int numPairs = pyColDict_AsPackedArrays(pSolution, m_colIndices, &varInds, &varVals); assert(numPairs == PyObject_Length(pSolution)); double* sol = new double[m_numCols]; UtilFillN(sol, m_numCols, 0.0); for (int j = 0; j < numPairs; j++) { sol[varInds[j]] = varVals[j]; } xhatIPFeas.push_back(new DecompSolution(m_numCols, sol, origCost)); delete [] sol; delete [] varInds; delete [] varVals; } return len; }
/** * APPisUserFeasible callback * * Called by DIP, we interface with Python */ bool DippyDecompApp::APPisUserFeasible(const double* x, const int n_cols, const double tolZero) { assert(n_cols == m_modelCore.getModel()->getColNames().size()); PyObject* pSolutionList = pyTupleList_FromDoubleArray(x, m_colList); PyObject* pTolZero = PyFloat_FromDouble(tolZero); if (!m_pyIsUserFeasible) { return true; } PyObject* pResult = PyObject_CallMethod(m_pProb, "isUserFeasible", "Od", pSolutionList, pTolZero); if (pResult == NULL) { throw UtilException("Error calling method prob.isUserFeasible()", "APPisUserFeasible", "DippyDecompApp"); } // This should not happen as having isUserFeasible present but not returning a boolean is not good if (pResult == Py_None) { // method exists, but not implemented, return true return true; } return (bool)PyObject_IsTrue(pResult); }
/** * solveRelaxed callback * * This is called by DIP. This function interfaces with Python to * call the user defined function if it's present * * We're expected to populate varList (basically a vector) and * return the status of the subproblem solver. */ DecompSolverStatus DippyDecompApp::solveRelaxed(const int whichBlock, const double* redCostX, const double convexDual, DecompVarList& varList) { if (!m_pySolveRelaxed) { return DecompSolStatNoSolution; } PyObject* pRelaxKey = PyList_GetItem(m_relaxedKeys, whichBlock); PyObject* pRedCostList = pyTupleList_FromDoubleArray(redCostX, m_colList); PyObject* pConvexDual = PyFloat_FromDouble(convexDual); // call solveRelaxed on DipProblem PyObject* pStatandVarList = PyObject_CallMethod(m_pProb, "solveRelaxed", "OOd", pRelaxKey, pRedCostList, pConvexDual); Py_DECREF(pRedCostList); Py_DECREF(pConvexDual); if ( (pStatandVarList == NULL) || (pStatandVarList == Py_None) ){ throw UtilException("Error calling method prob.solveRelaxed()", "solveRelaxed", "DippyDecompApp"); } // [status, varList] = relaxed_solver(...) PyObject * pStatus = PyTuple_GetItem(pStatandVarList, 0); int cStatus = PyInt_AsLong(pStatus); DecompSolverStatus status = (DecompSolverStatus)cStatus; PyObject * pVarList = PyTuple_GetItem(pStatandVarList, 1); int nVars = PyObject_Length(pVarList); // In the new design, we need to allow the possibility that the user will solve // the problem exactly, but not find any solutions with reduced costs zero // The below is is commented out and left in the source for posterity // tkr 11/11/15 //if (nVars == 0) { // throw UtilException("Empty variable list", "solveRelaxed", "DippyDecompApp"); //} // solveRelaxed returns 3-tuples (cost, reduced cost, dictionary of (variable, value) pairs) // We can use these to construct a C++ DecompVar objects double cost, rc; PyObject *pDict, *pKeys, *pCol; string name; double value; for (int j = 0; j < nVars; j++) { PyObject* pTuple = PySequence_GetItem(pVarList, j); cost = PyFloat_AsDouble(PyTuple_GetItem(pTuple, 0)); rc = PyFloat_AsDouble(PyTuple_GetItem(pTuple, 1)); pDict = PyTuple_GetItem(pTuple, 2); pKeys = PyDict_Keys(pDict); vector<int> varInds; vector<double> varVals; for (int n = 0; n < PyObject_Length(pDict); n++) { pCol = PyList_GetItem(pKeys, n); value = PyFloat_AsDouble(PyDict_GetItem(pDict, pCol)); varInds.push_back(m_colIndices[pCol]); varVals.push_back(value); } Py_DECREF(pKeys); Py_DECREF(pTuple); DecompVar* var = new DecompVar(varInds, varVals, rc, cost); var->setBlockId(whichBlock); varList.push_back(var); } Py_DECREF(pStatandVarList); return status; }
/** * Perform branching * * This function should populate the (down|up)Branch(LB|UB) vectors with (indices, bound-value) pairs * which define the branch. */ bool DippyAlgoMixin::chooseBranchSet(DecompAlgo* algo, std::vector< std::pair<int, double> >& downBranchLB, std::vector< std::pair<int, double> >& downBranchUB, std::vector< std::pair<int, double> >& upBranchLB, std::vector< std::pair<int, double> >& upBranchUB) { bool ret_val; if (!m_utilParam->GetSetting("pyBranchMethod", true)) { return algo->DecompAlgo::chooseBranchSet(downBranchLB, downBranchUB, upBranchLB, upBranchUB); } DippyDecompApp* app = (DippyDecompApp*)algo->getDecompApp(); // copy the current solution into a Python list const double* xhat = algo->getXhat(); PyObject* pSolutionList = pyTupleList_FromDoubleArray(xhat, app->m_colList); // try to call chooseBranchSet on the DipProblem python object PyObject* pResult = PyObject_CallMethod(m_pProb, "chooseBranchSet", "O", pSolutionList); if (pResult == NULL) { //something's gone wrong with the function call, a Python exception has //been set throw UtilException("Error calling method prob.chooseBranchSet()", "chooseBranchSet", "DippyDecompAlgo"); } // need more error checking/assertion setting here if (pResult == Py_None) { // if chooseBranchSet returns None, do default branching for this algo ret_val = algo->DecompAlgo::chooseBranchSet(downBranchLB, downBranchUB, upBranchLB, upBranchUB); // Original comment: No branching set was returned. This shouldn't happen // tkr 11/11/15: Actually, it can happen if the solution is integral, but not feasible. // This happens sometimes when column generation is halted because of tailoff and // the solution to the relaxation is feasible. I'm leaving the commented code here for // posterity //assert(ret_val == true); //if (!ret_val){ // throw UtilException("No branch set found in prob.chooseBranchSet()", // "chooseBranchSet", "DippyDecompAlgo"); //} if (downBranchUB.size() > 0) { PyObject* downBranchVar, * upBranchVar; pDownLB = PyDict_New(); // Down branch LBs is an empty dictionary pDownUB = PyDict_New(); downBranchVar = PyList_GetItem(app->m_colList, downBranchUB[0].first); PyDict_SetItem(pDownUB, downBranchVar, PyInt_FromLong(static_cast<int>(round(downBranchUB[0].second)))); pUpLB = PyDict_New(); upBranchVar = PyList_GetItem(app->m_colList, upBranchLB[0].first); PyDict_SetItem(pUpLB, upBranchVar, PyInt_FromLong(static_cast<int>(round(upBranchLB[0].second)))); pUpUB = PyDict_New(); // Up branch UBs is an empty dictionary assert(downBranchVar == upBranchVar); }else{ //No branching set was returned. Zero out pointers to old branching //sets assert(ret_val == false); pDownLB = NULL; pDownUB = NULL; pUpLB = NULL; pUpUB = NULL; } return ret_val; } else { // or else, the function should have returned 4 lists of (name, value) tuples pDownLB = PyTuple_GetItem(pResult, 0); pDownUB = PyTuple_GetItem(pResult, 1); pUpLB = PyTuple_GetItem(pResult, 2); pUpUB = PyTuple_GetItem(pResult, 3); // copy the python dictionaries into the result vectors pyColDict_AsPairedVector(pDownLB, downBranchLB, app->m_colIndices); pyColDict_AsPairedVector(pDownUB, downBranchUB, app->m_colIndices); pyColDict_AsPairedVector(pUpLB, upBranchLB, app->m_colIndices); pyColDict_AsPairedVector(pUpUB, upBranchUB, app->m_colIndices); return true; } }