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 PyObject* printModelSize(PyObject* self, PyObject* args) { // Free Python interpreter for other threads Py_BEGIN_ALLOW_THREADS // Execute and catch exceptions size_t count, memsize; try { // Intro logger << endl << "Size information of frePPLe " << PACKAGE_VERSION << " (" << __DATE__ << ")" << endl << endl; // Print current locale #if defined(HAVE_SETLOCALE) || defined(_MSC_VER) logger << "Locale: " << setlocale(LC_ALL,NULL) << endl << endl; #else logger << endl; #endif // Print loaded modules Environment::printModules(); // Print the number of clusters logger << "Clusters: " << HasLevel::getNumberOfClusters() << " (hanging: " << HasLevel::getNumberOfHangingClusters() << ")" << endl << endl; // Header for memory size logger << "Memory usage:" << endl; logger << "Model \tNumber\tMemory" << endl; logger << "----- \t------\t------" << endl; // Plan size_t total = Plan::instance().getSize(); logger << "Plan \t1\t"<< Plan::instance().getSize() << endl; // Locations memsize = 0; for (Location::iterator l = Location::begin(); l != Location::end(); ++l) memsize += l->getSize(); logger << "Location \t" << Location::size() << "\t" << memsize << endl; total += memsize; // Customers memsize = 0; for (Customer::iterator c = Customer::begin(); c != Customer::end(); ++c) memsize += c->getSize(); logger << "Customer \t" << Customer::size() << "\t" << memsize << endl; total += memsize; // Buffers memsize = 0; for (Buffer::iterator b = Buffer::begin(); b != Buffer::end(); ++b) memsize += b->getSize(); logger << "Buffer \t" << Buffer::size() << "\t" << memsize << endl; total += memsize; // Setup matrices memsize = 0; for (SetupMatrix::iterator s = SetupMatrix::begin(); s != SetupMatrix::end(); ++s) memsize += s->getSize(); logger << "Setup matrix \t" << SetupMatrix::size() << "\t" << memsize << endl; total += memsize; // Resources memsize = 0; for (Resource::iterator r = Resource::begin(); r != Resource::end(); ++r) memsize += r->getSize(); logger << "Resource \t" << Resource::size() << "\t" << memsize << endl; total += memsize; // Skills and resourceskills size_t countResourceSkills(0), memResourceSkills(0); memsize = 0; for (Skill::iterator sk = Skill::begin(); sk != Skill::end(); ++sk) { memsize += sk->getSize(); for (Skill::resourcelist::const_iterator rs = sk->getResources().begin(); rs != sk->getResources().end(); ++rs) { ++countResourceSkills; memResourceSkills += rs->getSize(); } } logger << "Skill \t" << Skill::size() << "\t" << memsize << endl; logger << "ResourceSkill \t" << countResourceSkills << "\t" << memResourceSkills << endl; total += memsize; // Operations, flows and loads size_t countFlows(0), memFlows(0), countLoads(0), memLoads(0); memsize = 0; for (Operation::iterator o = Operation::begin(); o != Operation::end(); ++o) { memsize += o->getSize(); for (Operation::flowlist::const_iterator fl = o->getFlows().begin(); fl != o->getFlows().end(); ++ fl) { ++countFlows; memFlows += fl->getSize(); } for (Operation::loadlist::const_iterator ld = o->getLoads().begin(); ld != o->getLoads().end(); ++ ld) { ++countLoads; memLoads += ld->getSize(); } } logger << "Operation \t" << Operation::size() << "\t" << memsize << endl; logger << "Flow \t" << countFlows << "\t" << memFlows << endl; logger << "Load \t" << countLoads << "\t" << memLoads << endl; total += memsize + memFlows + memLoads; // Calendars (which includes the buckets) memsize = 0; for (Calendar::iterator cl = Calendar::begin(); cl != Calendar::end(); ++cl) memsize += cl->getSize(); logger << "Calendar \t" << Calendar::size() << "\t" << memsize << endl; total += memsize; // Items memsize = 0; for (Item::iterator i = Item::begin(); i != Item::end(); ++i) memsize += i->getSize(); logger << "Item \t" << Item::size() << "\t" << memsize << endl; total += memsize; // Demands memsize = 0; size_t c_count = 0, c_memsize = 0; for (Demand::iterator dm = Demand::begin(); dm != Demand::end(); ++dm) { memsize += dm->getSize(); for (Problem::const_iterator cstrnt(dm->getConstraints().begin()); cstrnt != dm->getConstraints().end(); ++cstrnt) { ++c_count; c_memsize += cstrnt->getSize(); } } logger << "Demand \t" << Demand::size() << "\t" << memsize << endl; logger << "Constraints \t" << c_count << "\t" << c_memsize << endl; total += memsize + c_memsize; // Operationplans size_t countloadplans(0), countflowplans(0); memsize = count = 0; for (OperationPlan::iterator j = OperationPlan::begin(); j!=OperationPlan::end(); ++j) { ++count; memsize += sizeof(*j); countloadplans += j->sizeLoadPlans(); countflowplans += j->sizeFlowPlans(); } total += memsize; logger << "OperationPlan\t" << count << "\t" << memsize << endl; // Flowplans memsize = countflowplans * sizeof(FlowPlan); total += memsize; logger << "FlowPlan \t" << countflowplans << "\t" << memsize << endl; // Loadplans memsize = countloadplans * sizeof(LoadPlan); total += memsize; logger << "LoadPlan \t" << countloadplans << "\t" << memsize << endl; // Problems memsize = count = 0; for (Problem::const_iterator pr = Problem::begin(); pr!=Problem::end(); ++pr) { ++count; memsize += pr->getSize(); } total += memsize; logger << "Problem \t" << count << "\t" << memsize << endl; // TOTAL logger << "Total \t\t" << total << endl << endl; } catch (...) { 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; } }