DECLARE_EXPORT PyObject* savePlan(PyObject* self, PyObject* args) { // Pick up arguments const char *filename = "plan.out"; int ok = PyArg_ParseTuple(args, "s:saveplan", &filename); if (!ok) return NULL; // Free Python interpreter for other threads Py_BEGIN_ALLOW_THREADS // Execute and catch exceptions ofstream textoutput; try { // Open the output file textoutput.open(filename, ios::out); // Write the buffer summary for (Buffer::iterator gbuf = Buffer::begin(); gbuf != Buffer::end(); ++gbuf) { if (!gbuf->getHidden()) for (Buffer::flowplanlist::const_iterator oo=gbuf->getFlowPlans().begin(); oo!=gbuf->getFlowPlans().end(); ++oo) if (oo->getType() == 1 && oo->getQuantity() != 0.0) { textoutput << "BUFFER\t" << *gbuf << '\t' << oo->getDate() << '\t' << oo->getQuantity() << '\t' << oo->getOnhand() << endl; } } // Write the demand summary for (Demand::iterator gdem = Demand::begin(); gdem != Demand::end(); ++gdem) { if (!gdem->getHidden()) { for (Demand::OperationPlan_list::const_iterator pp = gdem->getDelivery().begin(); pp != gdem->getDelivery().end(); ++pp) textoutput << "DEMAND\t" << (*gdem) << '\t' << (*pp)->getDates().getEnd() << '\t' << (*pp)->getQuantity() << endl; } } // Write the resource summary for (Resource::iterator gres = Resource::begin(); gres != Resource::end(); ++gres) { if (!gres->getHidden()) for (Resource::loadplanlist::const_iterator qq=gres->getLoadPlans().begin(); qq!=gres->getLoadPlans().end(); ++qq) if (qq->getType() == 1 && qq->getQuantity() != 0.0) { textoutput << "RESOURCE\t" << *gres << '\t' << qq->getDate() << '\t' << qq->getQuantity() << '\t' << qq->getOnhand() << endl; } } // Write the operationplan summary. for (OperationPlan::iterator rr = OperationPlan::begin(); rr != OperationPlan::end(); ++rr) { if (rr->getOperation()->getHidden()) continue; textoutput << "OPERATION\t" << rr->getOperation() << '\t' << rr->getDates().getStart() << '\t' << rr->getDates().getEnd() << '\t' << rr->getQuantity() << endl; } // Write the problem summary. for (Problem::const_iterator gprob = Problem::begin(); gprob != Problem::end(); ++gprob) { textoutput << "PROBLEM\t" << gprob->getType().type << '\t' << gprob->getDescription() << '\t' << gprob->getDates() << endl; } // Write the constraint summary for (Demand::iterator gdem = Demand::begin(); gdem != Demand::end(); ++gdem) { if (!gdem->getHidden()) { for (Problem::const_iterator i = gdem->getConstraints().begin(); i != gdem->getConstraints().end(); ++i) textoutput << "DEMAND CONSTRAINT\t" << (*gdem) << '\t' << i->getDescription() << '\t' << i->getDates() << '\t' << endl; } } // Close the output file textoutput.close(); } catch (...) { if (textoutput.is_open()) textoutput.close(); Py_BLOCK_THREADS; PythonType::evalException(); return NULL; } Py_END_ALLOW_THREADS // Reclaim Python interpreter return Py_BuildValue(""); }
DECLARE_EXPORT void SolverMRP::solve(const Demand* l, void* v) { // Set a bookmark at the current command SolverMRPdata* data = static_cast<SolverMRPdata*>(v); CommandManager::Bookmark* topcommand = data->setBookmark(); // Create a state stack State* mystate = data->state; data->push(); try { // Call the user exit if (userexit_demand) userexit_demand.call(l, PythonData(data->constrainedPlanning)); short loglevel = data->getSolver()->getLogLevel(); // Note: This solver method does not push/pop states on the stack. // We continue to work on the top element of the stack. // Message if (loglevel>0) { logger << "Planning demand '" << l->getName() << "' (" << l->getPriority() << ", " << l->getDue() << ", " << l->getQuantity() << ")"; if (!data->constrainedPlanning || !data->getSolver()->isConstrained()) logger << " in unconstrained mode"; logger << endl; } // Unattach previous delivery operationplans, if required. if (data->getSolver()->getErasePreviousFirst()) { // Locked operationplans will NOT be deleted, and a part of the demand can // still remain planned. const_cast<Demand*>(l)->deleteOperationPlans(false, data); // Empty constraint list const_cast<Demand*>(l)->getConstraints().clear(); } // Track constraints or not data->logConstraints = (getPlanType() == 1); // Determine the quantity to be planned and the date for the planning loop double plan_qty = l->getQuantity() - l->getPlannedQuantity(); Date plan_date = l->getDue(); if (plan_qty < ROUNDING_ERROR || plan_date == Date::infiniteFuture) { if (loglevel>0) logger << " Nothing to be planned." << endl; data->pop(); return; } if (plan_qty < l->getMinShipment()) plan_qty = l->getMinShipment(); // Temporary values to store the 'best-reply' so far double best_q_qty = 0.0, best_a_qty = 0.0; Date best_q_date; // Select delivery operation Operation* deliveryoper = l->getDeliveryOperation(); // Handle invalid or missing delivery operations { string problemtext = string("Demand '") + l->getName() + "' has no delivery operation"; Problem::const_iterator j = Problem::begin(const_cast<Demand*>(l), false); while (j!=Problem::end()) { if (&(j->getType()) == ProblemInvalidData::metadata && j->getDescription() == problemtext) break; ++j; } if (!deliveryoper) { // Create a problem if (j == Problem::end()) new ProblemInvalidData(const_cast<Demand*>(l), problemtext, "demand", l->getDue(), l->getDue(), l->getQuantity()); // Abort planning of this demand throw DataException("Demand '" + l->getName() + "' can't be planned"); } else // Remove problem that may already exist delete &*j; } // Planning loop do { // Message if (loglevel>0) logger << "Demand '" << l << "' asks: " << plan_qty << " " << plan_date << endl; // Store the last command in the list, in order to undo the following // commands if required. CommandManager::Bookmark* topcommand = data->setBookmark(); // Plan the demand by asking the delivery operation to plan double q_qty = plan_qty; data->state->curBuffer = NULL; data->state->q_qty = plan_qty; data->state->q_date = plan_date; data->planningDemand = const_cast<Demand*>(l); data->state->curDemand = const_cast<Demand*>(l); data->state->curOwnerOpplan = NULL; deliveryoper->solve(*this,v); Date next_date = data->state->a_date; if (data->state->a_qty < ROUNDING_ERROR && plan_qty > l->getMinShipment() && l->getMinShipment() > 0) { bool originalLogConstraints = data->logConstraints; data->logConstraints = false; try { // The full asked quantity is not possible. // Try with the minimum shipment quantity. if (loglevel>1) logger << "Demand '" << l << "' tries planning minimum quantity " << l->getMinShipment() << endl; data->rollback(topcommand); data->state->curBuffer = NULL; data->state->q_qty = l->getMinShipment(); data->state->q_date = plan_date; data->state->curDemand = const_cast<Demand*>(l); deliveryoper->solve(*this,v); if (data->state->a_date < next_date) next_date = data->state->a_date; if (data->state->a_qty > ROUNDING_ERROR) { // The minimum shipment quantity is feasible. // Now try iteratively different quantities to find the best we can do. double min_qty = l->getMinShipment(); double max_qty = plan_qty; double delta = fabs(max_qty - min_qty); while (delta > data->getSolver()->getIterationAccuracy() * l->getQuantity() && delta > data->getSolver()->getIterationThreshold()) { // Note: we're kind of assuming that the demand is an integer value here. double new_qty = floor((min_qty + max_qty) / 2); if (new_qty == min_qty) { // Required to avoid an infinite loop on the same value... new_qty += 1; if (new_qty > max_qty) break; } if (loglevel>0) logger << "Demand '" << l << "' tries planning a different quantity " << new_qty << endl; data->rollback(topcommand); data->state->curBuffer = NULL; data->state->q_qty = new_qty; data->state->q_date = plan_date; data->state->curDemand = const_cast<Demand*>(l); deliveryoper->solve(*this,v); if (data->state->a_date < next_date) next_date = data->state->a_date; if (data->state->a_qty > ROUNDING_ERROR) // Too small: new min min_qty = new_qty; else // Too big: new max max_qty = new_qty; delta = fabs(max_qty - min_qty); } q_qty = min_qty; // q_qty is the biggest Q quantity giving a positive reply if (data->state->a_qty <= ROUNDING_ERROR) { if (loglevel>0) logger << "Demand '" << l << "' restores plan for quantity " << min_qty << endl; // Restore the last feasible plan data->rollback(topcommand); data->state->curBuffer = NULL; data->state->q_qty = min_qty; data->state->q_date = plan_date; data->state->curDemand = const_cast<Demand*>(l); deliveryoper->solve(*this,v); } } } catch (...) { data->logConstraints = originalLogConstraints; throw; } data->logConstraints = originalLogConstraints; } // Message if (loglevel>0) logger << "Demand '" << l << "' gets answer: " << data->state->a_qty << " " << next_date << " " << data->state->a_cost << " " << data->state->a_penalty << endl; // Update the date to plan in the next loop Date copy_plan_date = plan_date; // Compare the planned quantity with the minimum allowed shipment quantity // We don't accept the answer in case: // 1) Nothing is planned // 2) The planned quantity is less than the minimum shipment quantity // 3) The remaining quantity after accepting this answer is less than // the minimum quantity. if (data->state->a_qty < ROUNDING_ERROR || data->state->a_qty + ROUNDING_ERROR < l->getMinShipment() || (plan_qty - data->state->a_qty < l->getMinShipment() && fabs(plan_qty - data->state->a_qty) > ROUNDING_ERROR)) { if (plan_qty - data->state->a_qty < l->getMinShipment() && data->state->a_qty + ROUNDING_ERROR >= l->getMinShipment() && data->state->a_qty > best_a_qty ) { // The remaining quantity after accepting this answer is less than // the minimum quantity. Therefore, we delay accepting it now, but // still keep track of this best offer. best_a_qty = data->state->a_qty; best_q_qty = q_qty; best_q_date = plan_date; } // Delete operationplans - Undo all changes data->rollback(topcommand); // Set the ask date for the next pass through the loop if (next_date <= copy_plan_date || (!data->getSolver()->getAllowSplits() && data->state->a_qty > ROUNDING_ERROR) || (data->state->a_qty > ROUNDING_ERROR && plan_qty - data->state->a_qty < l->getMinShipment() && plan_qty - data->state->a_qty > ROUNDING_ERROR)) { // Oops, we didn't get a proper answer we can use for the next loop. // Print a warning and simply try one day later. if (loglevel>0) logger << "Warning: Demand '" << l << "': Lazy retry" << endl; plan_date = copy_plan_date + data->getSolver()->getLazyDelay(); } else // Use the next-date answer from the solver plan_date = next_date; } else { // Accepting this answer if (data->state->a_qty + ROUNDING_ERROR < q_qty) { // The demand was only partially planned. We need to do a new // 'coordinated' planning run. // Delete operationplans created in the 'testing round' data->rollback(topcommand); // Create the correct operationplans if (loglevel>=2) logger << "Demand '" << l << "' plans coordination." << endl; data->getSolver()->setLogLevel(0); double tmpresult = 0; try { for(double remainder = data->state->a_qty; remainder > ROUNDING_ERROR; remainder -= data->state->a_qty) { data->state->q_qty = remainder; data->state->q_date = copy_plan_date; data->state->curDemand = const_cast<Demand*>(l); data->state->curBuffer = NULL; deliveryoper->solve(*this,v); if (data->state->a_qty < ROUNDING_ERROR) { logger << "Warning: Demand '" << l << "': Failing coordination" << endl; break; } tmpresult += data->state->a_qty; } } catch (...) { data->getSolver()->setLogLevel(loglevel); throw; } data->getSolver()->setLogLevel(loglevel); data->state->a_qty = tmpresult; if (tmpresult == 0) break; } // Register the new operationplans. We need to make sure that the // correct execute method is called! if (data->getSolver()->getAutocommit()) { data->getSolver()->scanExcess(data); data->CommandManager::commit(); } // Update the quantity to plan in the next loop plan_qty -= data->state->a_qty; best_a_qty = 0.0; // Reset 'best-answer' remember } } // Repeat while there is still a quantity left to plan and we aren't // exceeding the maximum delivery delay. while (plan_qty > ROUNDING_ERROR && ((data->getSolver()->getPlanType() != 2 && plan_date < l->getDue() + l->getMaxLateness()) || (data->getSolver()->getPlanType() == 2 && !data->constrainedPlanning && plan_date < l->getDue() + l->getMaxLateness()) || (data->getSolver()->getPlanType() == 2 && data->constrainedPlanning && plan_date == l->getDue()) )); // Accept the best possible answer. // We may have skipped it in the previous loop, awaiting a still better answer if (best_a_qty > 0.0 && data->constrainedPlanning) { if (loglevel>=2) logger << "Demand '" << l << "' accepts a best answer." << endl; data->getSolver()->setLogLevel(0); try { for (double remainder = best_q_qty; remainder > ROUNDING_ERROR && remainder > l->getMinShipment(); remainder -= data->state->a_qty) { data->state->q_qty = remainder; data->state->q_date = best_q_date; data->state->curDemand = const_cast<Demand*>(l); data->state->curBuffer = NULL; deliveryoper->solve(*this,v); if (data->state->a_qty < ROUNDING_ERROR) { logger << "Warning: Demand '" << l << "': Failing accepting best answer" << endl; break; } } if (data->getSolver()->getAutocommit()) { data->getSolver()->scanExcess(data); data->CommandManager::commit(); } } catch (...) { data->getSolver()->setLogLevel(loglevel); throw; } data->getSolver()->setLogLevel(loglevel); } // Reset the state stack to the position we found it at. while (data->state > mystate) data->pop(); } catch (...) { // Clean up if any exception happened during the planning of the demand while (data->state > mystate) data->pop(); data->rollback(topcommand); throw; } }