DECLARE_EXPORT void Flow::validate(Action action) { // Catch null operation and buffer pointers Operation* oper = getOperation(); Buffer* buf = getBuffer(); if (!oper || !buf) { // This flow is not a valid one since it misses essential information if (!oper && !buf) throw DataException("Missing operation and buffer on a flow"); else if (!oper) throw DataException("Missing operation on a flow with buffer '" + buf->getName() + "'"); else throw DataException("Missing buffer on a flow with operation '" + oper->getName() + "'"); } // Check if a flow with 1) identical buffer, 2) identical operation and // 3) overlapping effectivity dates already exists, and 4) same // flow type. Operation::flowlist::const_iterator i = oper->getFlows().begin(); for (; i != oper->getFlows().end(); ++i) if (i->getBuffer() == buf && i->getEffective().overlap(getEffective()) && i->getType() == getType() && &*i != this) break; // Apply the appropriate action switch (action) { case ADD: if (i != oper->getFlows().end()) throw DataException("Flow of '" + oper->getName() + "' and '" + buf->getName() + "' already exists"); break; case CHANGE: throw DataException("Can't update a flow"); case ADD_CHANGE: // ADD is handled in the code after the switch statement if (i == oper->getFlows().end()) break; throw DataException("Can't update a flow between '" + oper->getName() + "' and '" + buf->getName() + "')"); case REMOVE: // Delete the temporary flow object delete this; // Nothing to delete if (i == oper->getFlows().end()) throw DataException("Can't remove nonexistent flow of '" + oper->getName() + "' and '" + buf->getName() + "'"); // Delete delete &*i; } // Set a flag to make sure the level computation is triggered again HasLevel::triggerLazyRecomputation(); }
Object* Flow::finder(const DataValueDict& d) { // Check operation const DataValue* tmp = d.get(Tags::operation); if (!tmp) return nullptr; Operation* oper = static_cast<Operation*>(tmp->getObject()); // Check buffer field tmp = d.get(Tags::buffer); if (!tmp) return nullptr; Buffer* buf = static_cast<Buffer*>(tmp->getObject()); // Walk over all flows of the operation, and return // the first one with matching const DataValue* hasEffectiveStart = d.get(Tags::effective_start); Date effective_start; if (hasEffectiveStart) effective_start = hasEffectiveStart->getDate(); const DataValue* hasEffectiveEnd = d.get(Tags::effective_end); Date effective_end; if (hasEffectiveEnd) effective_end = hasEffectiveEnd->getDate(); const DataValue* hasPriority = d.get(Tags::priority); int priority = 1; if (hasPriority) priority = hasPriority->getInt(); const DataValue* hasName = d.get(Tags::name); string name; if (hasName) name = hasName->getString(); for (Operation::flowlist::const_iterator fl = oper->getFlows().begin(); fl != oper->getFlows().end(); ++fl) { if (fl->getBuffer() != buf) continue; if (hasEffectiveStart && fl->getEffectiveStart() != effective_start) continue; if (hasEffectiveEnd && fl->getEffectiveEnd() != effective_end) continue; if (hasPriority && fl->getPriority() != priority) continue; if (hasName && fl->getName() != name) continue; return const_cast<Flow*>(&*fl); } return nullptr; }
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 HasLevel::computeLevels() { computationBusy = true; // Get exclusive access to this function in a multi-threaded environment. static Mutex levelcomputationbusy; ScopeMutexLock l(levelcomputationbusy); // Another thread may already have computed the levels while this thread was // waiting for the lock. In that case the while loop will be skipped. while (recomputeLevels) { // Reset the recomputation flag. Note that during the computation the flag // could be switched on again by some model change in a different thread. // In that case, the while loop will be rerun. recomputeLevels = false; // Force creation of all delivery operations f for (Demand::iterator gdem = Demand::begin(); gdem != Demand::end(); ++gdem) gdem->getDeliveryOperation(); // Reset current levels on buffers, resources and operations. // Also force the creation of all producing operations on the buffers. size_t numbufs = Buffer::size(); // Creating the producing operations of the buffers can cause new buffers // to be created. We repeat this loop until no new buffers are being added. // This isn't the most efficient loop, but it remains cheap and fast... while (true) { for (Buffer::iterator gbuf = Buffer::begin(); gbuf != Buffer::end(); ++gbuf) { gbuf->cluster = 0; gbuf->lvl = -1; gbuf->getProducingOperation(); } size_t numbufs_after = Buffer::size(); if (numbufs == numbufs_after) break; else numbufs = numbufs_after; } for (Resource::iterator gres = Resource::begin(); gres != Resource::end(); ++gres) { gres->cluster = 0; gres->lvl = -1; } for (Operation::iterator gop = Operation::begin(); gop != Operation::end(); ++gop) { gop->cluster = 0; gop->lvl = -1; } // Loop through all operations stack< pair<Operation*,int> > stack; Operation* cur_oper; int cur_level; Buffer *cur_buf; const Flow* cur_Flow; bool search_level; int cur_cluster; numberOfLevels = 0; numberOfClusters = 0; map<Operation*,short> visited; for (Operation::iterator g = Operation::begin(); g != Operation::end(); ++g) { // Select a new cluster number if (g->cluster) cur_cluster = g->cluster; else { // Detect hanging operations if (g->getFlows().empty() && g->getLoads().empty() && g->getSuperOperations().empty() && g->getSubOperations().empty() ) { // Cluster 0 keeps all dangling operations g->lvl = 0; continue; } cur_cluster = ++numberOfClusters; if (numberOfClusters >= UINT_MAX) throw LogicException("Too many clusters"); } #ifdef CLUSTERDEBUG logger << "Investigating operation '" << &*g << "' - current cluster " << g->cluster << endl; #endif // Do we need to activate the level search? // Criterion are: // - Not used in a super operation // - Have a producing flow on the operation itself // or on any of its sub operations search_level = false; if (g->getSuperOperations().empty()) { search_level = true; // Does the operation itself have producing flows? for (Operation::flowlist::const_iterator fl = g->getFlows().begin(); fl != g->getFlows().end() && search_level; ++fl) if (fl->isProducer()) search_level = false; if (search_level) { // Do suboperations have a producing flow? for (Operation::Operationlist::const_reverse_iterator i = g->getSubOperations().rbegin(); i != g->getSubOperations().rend() && search_level; ++i) for (Operation::flowlist::const_iterator fl = (*i)->getOperation()->getFlows().begin(); fl != (*i)->getOperation()->getFlows().end() && search_level; ++fl) if (fl->isProducer()) search_level = false; } } // If both the level and the cluster are de-activated, then we can move on if (!search_level && g->cluster) continue; // Start recursing // Note that as soon as push an operation on the stack we set its // cluster and/or level. This is avoid that operations are needlessly // pushed a second time on the stack. stack.push(make_pair(&*g, search_level ? 0 : -1)); visited.clear(); g->cluster = cur_cluster; if (search_level) g->lvl = 0; while (!stack.empty()) { // Take the top of the stack cur_oper = stack.top().first; cur_level = stack.top().second; stack.pop(); // Keep track of the maximum number of levels if (cur_level > numberOfLevels) numberOfLevels = cur_level; #ifdef CLUSTERDEBUG logger << " Recursing in Operation '" << *(cur_oper) << "' - current level " << cur_level << endl; #endif // Detect loops in the supply chain map<Operation*,short>::iterator detectloop = visited.find(cur_oper); if (detectloop == visited.end()) // Keep track of operations already visited visited.insert(make_pair(cur_oper,0)); else if (++(detectloop->second) > 1) // Already visited this operation enough times - don't repeat continue; // Push sub operations on the stack for (Operation::Operationlist::const_reverse_iterator i = cur_oper->getSubOperations().rbegin(); i != cur_oper->getSubOperations().rend(); ++i) { if ((*i)->getOperation()->lvl < cur_level) { // Search level and cluster stack.push(make_pair((*i)->getOperation(),cur_level)); (*i)->getOperation()->lvl = cur_level; (*i)->getOperation()->cluster = cur_cluster; } else if (!(*i)->getOperation()->cluster) { // Search for clusters information only stack.push(make_pair((*i)->getOperation(),-1)); (*i)->getOperation()->cluster = cur_cluster; } // else: no search required } // Push super operations on the stack for (list<Operation*>::const_reverse_iterator j = cur_oper->getSuperOperations().rbegin(); j != cur_oper->getSuperOperations().rend(); ++j) { if ((*j)->lvl < cur_level) { // Search level and cluster stack.push(make_pair(*j,cur_level)); (*j)->lvl = cur_level; (*j)->cluster = cur_cluster; } else if (!(*j)->cluster) { // Search for clusters information only stack.push(make_pair(*j,-1)); (*j)->cluster = cur_cluster; } // else: no search required } // Update level of resources linked to current operation for (Operation::loadlist::const_iterator gres = cur_oper->getLoads().begin(); gres != cur_oper->getLoads().end(); ++gres) { Resource *resptr = gres->getResource(); // Update the level of the resource if (resptr->lvl < cur_level) resptr->lvl = cur_level; // Update the cluster of the resource and operations using it if (!resptr->cluster) { resptr->cluster = cur_cluster; // Find more operations connected to this cluster by the resource for (Resource::loadlist::const_iterator resops = resptr->getLoads().begin(); resops != resptr->getLoads().end(); ++resops) if (!resops->getOperation()->cluster) { stack.push(make_pair(resops->getOperation(),-1)); resops->getOperation()->cluster = cur_cluster; } } } // Now loop through all flows of the operation for (Operation::flowlist::const_iterator gflow = cur_oper->getFlows().begin(); gflow != cur_oper->getFlows().end(); ++gflow) { cur_Flow = &*gflow; cur_buf = cur_Flow->getBuffer(); // Check whether the level search needs to continue search_level = cur_level!=-1 && cur_buf->lvl<cur_level+1; // Check if the buffer needs processing if (search_level || !cur_buf->cluster) { // Update the cluster of the current buffer cur_buf->cluster = cur_cluster; // Loop through all flows of the buffer for (Buffer::flowlist::const_iterator buffl = cur_buf->getFlows().begin(); buffl != cur_buf->getFlows().end(); ++buffl) { // Check level recursion if (cur_Flow->isConsumer() && search_level) { if (buffl->getOperation()->lvl < cur_level+1 && &*buffl != cur_Flow && buffl->isProducer()) { stack.push(make_pair(buffl->getOperation(),cur_level+1)); buffl->getOperation()->lvl = cur_level+1; buffl->getOperation()->cluster = cur_cluster; } else if (!buffl->getOperation()->cluster) { stack.push(make_pair(buffl->getOperation(),-1)); buffl->getOperation()->cluster = cur_cluster; } if (cur_level+1 > numberOfLevels) numberOfLevels = cur_level+1; cur_buf->lvl = cur_level+1; } // Check cluster recursion else if (!buffl->getOperation()->cluster) { stack.push(make_pair(buffl->getOperation(),-1)); buffl->getOperation()->cluster = cur_cluster; } } } // End of needs-procssing if statement } // End of flow loop } // End while stack not empty } // End of Operation loop // The above loop will visit ALL operations and recurse through the // buffers and resources connected to them. // Missing from the loop are buffers and resources that have no flows or // loads at all. We catch those poor lonely fellows now... for (Buffer::iterator gbuf2 = Buffer::begin(); gbuf2 != Buffer::end(); ++gbuf2) if (gbuf2->getFlows().empty()) gbuf2->cluster = 0; for (Resource::iterator gres2 = Resource::begin(); gres2 != Resource::end(); ++gres2) if (gres2->getLoads().empty()) gres2->cluster = 0; } // End of while recomputeLevels. The loop will be repeated as long as model // changes are done during the recomputation. // Unlock the exclusive access to this function computationBusy = false; }
void OperationItemSupplier::trimExcess(bool zero_or_minimum) const { // This method can only trim operations not loading a resource if (getLoads().begin() != getLoads().end()) return; for (Operation::flowlist::const_iterator fliter = getFlows().begin(); fliter != getFlows().end(); ++fliter) { if (fliter->getQuantity() <= 0) // Strange, shouldn't really happen continue; FlowPlan* candidate = nullptr; double curmin = 0; double oh = 0; double excess_min = DBL_MAX; for (Buffer::flowplanlist::const_iterator flplniter = fliter->getBuffer()->getFlowPlans().begin(); flplniter != fliter->getBuffer()->getFlowPlans().end(); ++flplniter) { // For any operationplan we get the onhand when its successor // replenishment arrives. If that onhand is higher than the minimum // onhand value we can resize it. // This is only valid in unconstrained plans and when there are // no upstream activities. if (flplniter->getEventType() == 3 && zero_or_minimum) curmin = flplniter->getMin(); else if (flplniter->getEventType() == 1) { const FlowPlan* flpln = static_cast<const FlowPlan*>(&*flplniter); if (oh - curmin < excess_min) { excess_min = oh - curmin; if (excess_min < 0) excess_min = 0; } if (flpln->getQuantity() > 0 && !flpln->getOperationPlan()->getLocked() && (!candidate || candidate->getDate() != flpln->getDate())) { if (candidate && excess_min > ROUNDING_ERROR && candidate->getQuantity() > excess_min + ROUNDING_ERROR && candidate->getQuantity() > getSizeMinimum() + ROUNDING_ERROR ) { // This candidate can now be resized candidate->setQuantity(candidate->getQuantity() - excess_min, false); candidate = nullptr; } else if (flpln->getOperation() == this) candidate = const_cast<FlowPlan*>(flpln); else candidate = nullptr; excess_min = DBL_MAX; } } oh = flplniter->getOnhand(); } if (candidate && excess_min > ROUNDING_ERROR && candidate->getQuantity() > excess_min + ROUNDING_ERROR && candidate->getQuantity() > getSizeMinimum() + ROUNDING_ERROR ) // Resize the last candidate at the end of the horizon candidate->setQuantity(candidate->getQuantity() - excess_min, false); } }
DECLARE_EXPORT void SolverMRP::solve(const Flow* fl, void* v) // @todo implement search mode { // Note: This method is only called for consuming flows and for the leading // flow of an alternate group. See SolverMRP::checkOperation SolverMRPdata* data = static_cast<SolverMRPdata*>(v); if (fl->hasAlternates()) { // CASE I: It is an alternate flow. // We ask each alternate flow in order of priority till we find a flow // that has a non-zero reply. // 1) collect a list of alternates list<const Flow*> thealternates; const Flow *x = fl->hasAlternates() ? fl : fl->getAlternate(); for (Operation::flowlist::const_iterator i = fl->getOperation()->getFlows().begin(); i != fl->getOperation()->getFlows().end(); ++i) if ((i->getAlternate() == x || &*i == x) && i->getEffective().within(data->state->q_flowplan->getDate())) thealternates.push_front(&*i); // 2) Sort the list thealternates.sort(sortFlow); // 3) Control the planning mode bool originalPlanningMode = data->constrainedPlanning; data->constrainedPlanning = true; const Flow *firstAlternate = NULL; double firstQuantity = 0.0; // Remember the top constraint bool originalLogConstraints = data->logConstraints; //Problem* topConstraint = data->planningDemand->getConstraints().top(); // 4) Loop through the alternates till we find a non-zero reply Date min_next_date(Date::infiniteFuture); double ask_qty; FlowPlan *flplan = data->state->q_flowplan; for (list<const Flow*>::const_iterator i = thealternates.begin(); i != thealternates.end();) { const Flow *curflow = *i; data->state->q_flowplan = flplan; // because q_flowplan can change // 4a) Switch to this flow if (data->state->q_flowplan->getFlow() != curflow) data->state->q_flowplan->setFlow(curflow); // 4b) Call the Python user exit if there is one if (userexit_flow) { PythonObject result = userexit_flow.call(data->state->q_flowplan, PythonObject(data->constrainedPlanning)); if (!result.getBool()) { // Return value is false, alternate rejected if (data->getSolver()->getLogLevel()>1) logger << indent(curflow->getOperation()->getLevel()) << " User exit disallows consumption from '" << (*i)->getBuffer()->getName() << "'" << endl; // Move to the next alternate if (++i != thealternates.end() && data->getSolver()->getLogLevel()>1) logger << indent(curflow->getOperation()->getLevel()) << " Alternate flow switches from '" << curflow->getBuffer()->getName() << "' to '" << (*i)->getBuffer()->getName() << "'" << endl; continue; } } // Remember the first alternate if (!firstAlternate) { firstAlternate = *i; firstQuantity = data->state->q_flowplan->getQuantity(); } // Constraint tracking if (*i != firstAlternate) // Only enabled on first alternate data->logConstraints = false; else // Keep track of constraints, if enabled data->logConstraints = originalLogConstraints; // 4c) Ask the buffer data->state->q_qty = ask_qty = - data->state->q_flowplan->getQuantity(); data->state->q_date = data->state->q_flowplan->getDate(); CommandManager::Bookmark* topcommand = data->setBookmark(); curflow->getBuffer()->solve(*this,data); // 4d) A positive reply: exit the loop if (data->state->a_qty > ROUNDING_ERROR) { // Update the opplan, which is required to (1) update the flowplans // and to (2) take care of lot sizing constraints of this operation. if (data->state->a_qty < ask_qty - ROUNDING_ERROR) { flplan->setQuantity(-data->state->a_qty, true); data->state->a_qty = -flplan->getQuantity(); } if (data->state->a_qty > ROUNDING_ERROR) { data->constrainedPlanning = originalPlanningMode; data->logConstraints = originalLogConstraints; return; } } // 4e) Undo the plan on the alternate data->rollback(topcommand); // 4f) Prepare for the next alternate if (data->state->a_date < min_next_date) min_next_date = data->state->a_date; if (++i != thealternates.end() && data->getSolver()->getLogLevel()>1) logger << indent(curflow->getOperation()->getLevel()) << " Alternate flow switches from '" << curflow->getBuffer()->getName() << "' to '" << (*i)->getBuffer()->getName() << "'" << endl; } // 5) No reply found, all alternates are infeasible if (!originalPlanningMode) { assert(firstAlternate); // Unconstrained plan: Plan on the primary alternate // Switch to this flow if (flplan->getFlow() != firstAlternate) flplan->setFlow(firstAlternate); // Message if (data->getSolver()->getLogLevel()>1) logger << indent(fl->getOperation()->getLevel()) << " Alternate flow plans unconstrained on alternate '" << firstAlternate->getBuffer()->getName() << "'" << endl; // Plan unconstrained data->constrainedPlanning = false; data->state->q_flowplan = flplan; // because q_flowplan can change flplan->setQuantity(firstQuantity, true); data->state->q_qty = ask_qty = - flplan->getQuantity(); data->state->q_date = flplan->getDate(); firstAlternate->getBuffer()->solve(*this,data); data->state->a_qty = -flplan->getQuantity(); // Restore original planning mode data->constrainedPlanning = originalPlanningMode; } else { // Constrained plan: Return 0 data->state->a_date = min_next_date; data->state->a_qty = 0; if (data->getSolver()->getLogLevel()>1) logger << indent(fl->getOperation()->getLevel()) << " Alternate flow doesn't find supply on any alternate : " << data->state->a_qty << " " << data->state->a_date << endl; } } else { // CASE II: Not an alternate flow. // In this case, this method is passing control on to the buffer. data->state->q_qty = - data->state->q_flowplan->getQuantity(); data->state->q_date = data->state->q_flowplan->getDate(); if (data->state->q_qty != 0.0) { fl->getBuffer()->solve(*this,data); if (data->state->a_date > fl->getEffective().getEnd()) { // The reply date must be less than the effectivity end date: after // that date the flow in question won't consume any material any more. if (data->getSolver()->getLogLevel()>1 && data->state->a_qty < ROUNDING_ERROR) logger << indent(fl->getBuffer()->getLevel()) << " Buffer '" << fl->getBuffer()->getName() << "' answer date is adjusted to " << fl->getEffective().getEnd() << " because of a date effective flow" << endl; data->state->a_date = fl->getEffective().getEnd(); } } else { // It's a zero quantity flowplan. // E.g. because it is not effective. data->state->a_date = data->state->q_date; data->state->a_qty = 0.0; } } }
extern "C" PyObject* OperationItemDistribution::createOrder( PyObject *self, PyObject *args, PyObject *kwdict ) { // Parse the Python arguments PyObject* pydest = NULL; unsigned long id = 0; const char* ref = NULL; PyObject* pyitem = NULL; PyObject* pyorigin = NULL; double qty = 0; PyObject* pystart = NULL; PyObject* pyend = NULL; int consume = 1; const char* status = NULL; const char* source = NULL; static const char *kwlist[] = { "destination", "id", "reference", "item", "origin", "quantity", "start", "end", "consume_material", "status", "source", NULL }; int ok = PyArg_ParseTupleAndKeywords( args, kwdict, "|OkzOOdOOpzz:createOrder", const_cast<char**>(kwlist), &pydest, &id, &ref, &pyitem, &pyorigin, &qty, &pystart, &pyend, &consume, &status, &source ); if (!ok) return NULL; Date start = pystart ? PythonData(pystart).getDate() : Date::infinitePast; Date end = pyend ? PythonData(pyend).getDate() : Date::infinitePast; // Validate all arguments if (!pydest || !pyitem) { PyErr_SetString(PythonDataException, "item and destination arguments are mandatory"); return NULL; } PythonData dest_tmp(pydest); if (!dest_tmp.check(Location::metadata)) { PyErr_SetString(PythonDataException, "destination argument must be of type location"); return NULL; } PythonData item_tmp(pyitem); if (!item_tmp.check(Item::metadata)) { PyErr_SetString(PythonDataException, "item argument must be of type item"); return NULL; } PythonData origin_tmp(pyorigin); if (pyorigin && !origin_tmp.check(Location::metadata)) { PyErr_SetString(PythonDataException, "origin argument must be of type location"); return NULL; } Item *item = static_cast<Item*>(item_tmp.getObject()); Location *dest = static_cast<Location*>(dest_tmp.getObject()); Location *origin = pyorigin ? static_cast<Location*>(origin_tmp.getObject()) : NULL; // Find or create the destination buffer. Buffer* destbuffer = NULL; for (Buffer::iterator bufiter = Buffer::begin(); bufiter != Buffer::end(); ++bufiter) { if (bufiter->getLocation() == dest && bufiter->getItem() == item) { if (destbuffer) { stringstream o; o << "Multiple buffers found for item '" << item << "'' and location'" << dest << "'"; throw DataException(o.str()); } destbuffer = &*bufiter; } } if (!destbuffer) { // Create the destination buffer destbuffer = new BufferDefault(); stringstream o; o << item << " @ " << dest; destbuffer->setName(o.str()); destbuffer->setItem(item); destbuffer->setLocation(dest); } // Build the producing operation for this buffer. destbuffer->getProducingOperation(); // Look for a matching operation replenishing this buffer. Operation *oper = NULL; for (Buffer::flowlist::const_iterator flowiter = destbuffer->getFlows().begin(); flowiter != destbuffer->getFlows().end() && !oper; ++flowiter) { if (flowiter->getOperation()->getType() != *OperationItemDistribution::metadata || flowiter->getQuantity() <= 0) continue; OperationItemDistribution* opitemdist = static_cast<OperationItemDistribution*>(flowiter->getOperation()); if (origin) { // Origin must match as well for (Operation::flowlist::const_iterator fl = opitemdist->getFlows().begin(); fl != opitemdist->getFlows().end(); ++ fl) { if (fl->getQuantity() < 0 && fl->getBuffer()->getLocation()->isMemberOf(origin)) oper = opitemdist; } } else oper = opitemdist; } // No matching operation is found. if (!oper) { // We'll create one now, but that requires that we have an origin defined. if (!origin) throw DataException("Origin location is needed on this distribution order"); Buffer* originbuffer = NULL; for (Buffer::iterator bufiter = Buffer::begin(); bufiter != Buffer::end(); ++bufiter) { if (bufiter->getLocation() == origin && bufiter->getItem() == item) { if (originbuffer) { stringstream o; o << "Multiple buffers found for item '" << item << "'' and location'" << dest << "'"; throw DataException(o.str()); } originbuffer = &*bufiter; } } if (!originbuffer) { // Create the origin buffer originbuffer = new BufferDefault(); stringstream o; o << item << " @ " << origin; originbuffer->setName(o.str()); originbuffer->setItem(item); originbuffer->setLocation(origin); } // Note: We know that we need to create a new one. An existing one would // have created an operation on the buffer already. ItemDistribution *itemdist = new ItemDistribution(); itemdist->setOrigin(origin); itemdist->setItem(item); itemdist->setDestination(dest); oper = new OperationItemDistribution(itemdist, originbuffer, destbuffer); new ProblemInvalidData(oper, "Distribution orders on unauthorized lanes", "operation", Date::infinitePast, Date::infiniteFuture, 1); } // Finally, create the operationplan OperationPlan *opplan = oper->createOperationPlan(qty, start, end, NULL, NULL, 0, false); if (id) opplan->setIdentifier(id); if (status) opplan->setStatus(status); if (ref) opplan->setReference(ref); if (!consume) opplan->setConsumeMaterial(false); opplan->activate(); // Return result Py_INCREF(opplan); return opplan; }