Example #1
0
DECLARE_EXPORT Skill::~Skill()
{
  // The ResourceSkill objects are automatically deleted by the destructor
  // of the Association list class.

  // Clean up the references on the load models
  for (Operation::iterator o = Operation::begin(); o != Operation::end(); ++o)
    for(Operation::loadlist::const_iterator l = o->getLoads().begin();
      l != o->getLoads().end(); ++l)
      if (l->getSkill() == this)
        const_cast<Load&>(*l).setSkill(NULL);
}
Example #2
0
DECLARE_EXPORT Object* Load::finder(const DataValueDict& d)
{
  // Check operation
  const DataValue* tmp = d.get(Tags::operation);
  if (!tmp)
    return NULL;
  Operation* oper = static_cast<Operation*>(tmp->getObject());

  // Check resource field
  tmp = d.get(Tags::resource);
  if (!tmp)
    return NULL;
  Resource* res = static_cast<Resource*>(tmp->getObject());

  // Walk over all loads 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;
  if (hasPriority)
    priority = hasPriority->getInt();
  const DataValue* hasName = d.get(Tags::name);
  string name;
  if (hasName)
    name = hasName->getString();
  for (Operation::loadlist::const_iterator fl = oper->getLoads().begin();
    fl != oper->getLoads().end(); ++fl)
  {
    if (fl->getResource() != res)
      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<Load*>(&*fl);
  }
  return NULL;
}
Example #3
0
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("");
}
Example #4
0
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;
}
Example #5
0
DECLARE_EXPORT void Load::validate(Action action)
{
  // Catch null operation and resource pointers
  Operation *oper = getOperation();
  Resource *res = getResource();
  if (!oper || !res)
  {
    // Invalid load model
    if (!oper && !res)
      throw DataException("Missing operation and resource on a load");
    else if (!oper)
      throw DataException("Missing operation on a load on resource '"
          + res->getName() + "'");
    else if (!res)
      throw DataException("Missing resource on a load on operation '"
          + oper->getName() + "'");
  }

  // Check if a load with 1) identical resource, 2) identical operation and
  // 3) overlapping effectivity dates already exists
  Operation::loadlist::const_iterator i = oper->getLoads().begin();
  for (; i != oper->getLoads().end(); ++i)
    if (i->getResource() == res
        && i->getEffective().overlap(getEffective())
        && &*i != this)
      break;

  // Apply the appropriate action
  switch (action)
  {
    case ADD:
      if (i != oper->getLoads().end())
      {
        throw DataException("Load of '" + oper->getName() + "' and '"
            + res->getName() + "' already exists");
      }
      break;
    case CHANGE:
      throw DataException("Can't update a load");
    case ADD_CHANGE:
      // ADD is handled in the code after the switch statement
      if (i == oper->getLoads().end()) break;
      throw DataException("Can't update a load");
    case REMOVE:
      // This load was only used temporarily during the reading process
      delete this;
      if (i == oper->getLoads().end())
        // Nothing to delete
        throw DataException("Can't remove nonexistent load of '"
            + oper->getName() + "' and '" + res->getName() + "'");
      delete &*i;
      // Set a flag to make sure the level computation is triggered again
      HasLevel::triggerLazyRecomputation();
      return;
  }

  // The statements below should be executed only when a new load is created.

  // Set a flag to make sure the level computation is triggered again
  HasLevel::triggerLazyRecomputation();
}
Example #6
0
void SolverMRP::solve(const Load* l, void* v)
{
  // Note: This method is only called for decrease loadplans and for the leading
  // load of an alternate group. See SolverMRP::checkOperation
  SolverMRPdata* data = static_cast<SolverMRPdata*>(v);
  unsigned int loglevel = data->getSolver()->getLogLevel();

  if (data->state->q_qty >= 0.0)
  {
    // The loadplan is an increase in size, and the resource solver only needs
    // the decreases.
    data->state->a_qty = data->state->q_qty;
    data->state->a_date = data->state->q_date;
    return;
  }

  if (!l->hasAlternates() && !l->getAlternate())
  {
    // CASE I: It is not an alternate load.
    // Delegate the answer immediately to the resource
    chooseResource(l, data);
    return;
  }

  // CASE II: It is an alternate load.
  // We ask each alternate load in order of priority till we find a load
  // that has a non-zero reply.

  // 1) collect a list of alternates
  list<const Load*> thealternates;
  const Load *x = l->hasAlternates() ? l : l->getAlternate();
  SearchMode search = l->getSearch();
  for (Operation::loadlist::const_iterator i = l->getOperation()->getLoads().begin();
      i != l->getOperation()->getLoads().end(); ++i)
    if ((i->getAlternate() == x || &*i == x)
        && i->getEffective().within(data->state->q_loadplan->getDate()))
      thealternates.push_back(&*i);

  // 2) Sort the list
  thealternates.sort(sortLoad);  // @todo cpu-intensive - better is to maintain the list in the correct order

  // 3) Control the planning mode
  bool originalPlanningMode = data->constrainedPlanning;
  data->constrainedPlanning = true;

  // Don't keep track of the constraints right now
  bool originalLogConstraints = data->logConstraints;
  data->logConstraints = false;

  // 4) Loop through all alternates or till we find a non-zero
  // reply (priority search)
  Date min_next_date(Date::infiniteFuture);
  LoadPlan *lplan = data->state->q_loadplan;
  double bestAlternateValue = DBL_MAX;
  double bestAlternateQuantity = DBL_MIN;
  const Load* bestAlternateSelection = NULL;
  double beforeCost = data->state->a_cost;
  double beforePenalty = data->state->a_penalty;
  OperationPlanState originalOpplan(lplan->getOperationPlan());
  double originalLoadplanQuantity = data->state->q_loadplan->getQuantity();
  for (list<const Load*>::const_iterator i = thealternates.begin();
      i != thealternates.end();)
  {
    const Load *curload = *i;
    data->state->q_loadplan = lplan; // because q_loadplan can change!

    // 4a) Switch to this load
    if (lplan->getLoad() != curload) lplan->setLoad(curload);
    lplan->getOperationPlan()->restore(originalOpplan);
    data->state->q_qty = lplan->getQuantity();
    data->state->q_date = lplan->getDate();

    // 4b) Ask the resource
    // TODO XXX Need to insert another loop here! It goes over all resources qualified for the required skill.
    // The qualified resources need to be sorted based on their cost. If the cost is the same we should use a decent tie breaker, eg number of skills or number of loads.
    // The first resource with the qualified skill that is available will be used.
    CommandManager::Bookmark* topcommand = data->setBookmark();
    if (search == PRIORITY)
      curload->getResource()->solve(*this,data);
    else
    {
      data->getSolver()->setLogLevel(0);
      try {curload->getResource()->solve(*this,data);}
      catch (...)
      {
        data->getSolver()->setLogLevel(loglevel);
        // Restore the planning mode
        data->constrainedPlanning = originalPlanningMode;
        data->logConstraints = originalLogConstraints;
        throw;
      }
      data->getSolver()->setLogLevel(loglevel);
    }

    // 4c) Evaluate the result
    if (data->state->a_qty > ROUNDING_ERROR
        && lplan->getOperationPlan()->getQuantity() > 0)
    {
      if (search == PRIORITY)
      {
        // Priority search: accept any non-zero reply
        // Restore the planning mode
        data->constrainedPlanning = originalPlanningMode;
        data->logConstraints = originalLogConstraints;
        return;
      }
      else
      {
        // Other search modes: evaluate all
        double deltaCost = data->state->a_cost - beforeCost;
        double deltaPenalty = data->state->a_penalty - beforePenalty;
        // Message
        if (loglevel>1 && search != PRIORITY)
          logger << indent(l->getOperation()->getLevel()) << "   Operation '"
              << l->getOperation()->getName() << "' evaluates alternate '"
              << curload->getResource() << "': cost " << deltaCost
              << ", penalty " << deltaPenalty << endl;
        if (deltaCost < ROUNDING_ERROR && deltaPenalty < ROUNDING_ERROR)
        {
          // Zero cost and zero penalty on this alternate. It won't get any better
          // than this, so we accept this alternate.
          if (loglevel>1)
            logger << indent(l->getOperation()->getLevel()) << "   Operation '"
                << l->getOperation()->getName() << "' chooses alternate '"
                << curload->getResource() << "' " << search << endl;
          // Restore the planning mode
          data->constrainedPlanning = originalPlanningMode;
          data->logConstraints = originalLogConstraints;
          return;
        }
        data->state->a_cost = beforeCost;
        data->state->a_penalty = beforePenalty;
        double val = 0.0;
        switch (search)
        {
          case MINCOST:
            val = deltaCost / lplan->getOperationPlan()->getQuantity();
            break;
          case MINPENALTY:
            val = deltaPenalty / lplan->getOperationPlan()->getQuantity();
            break;
          case MINCOSTPENALTY:
            val = (deltaCost + deltaPenalty) / lplan->getOperationPlan()->getQuantity();
            break;
          default:
            LogicException("Unsupported search mode for alternate load");
        }
        if (val + ROUNDING_ERROR < bestAlternateValue
            || (fabs(val - bestAlternateValue) < ROUNDING_ERROR
                && lplan->getOperationPlan()->getQuantity() > bestAlternateQuantity))
        {
          // Found a better alternate
          bestAlternateValue = val;
          bestAlternateSelection = curload;
          bestAlternateQuantity = lplan->getOperationPlan()->getQuantity();
        }
      }
    }
    else if (loglevel>1 && search != PRIORITY)
      logger << indent(l->getOperation()->getLevel()) << "   Operation '"
          << l->getOperation()->getName() << "' evaluates alternate '"
          << curload->getResource() << "': not available before "
          << data->state->a_date << endl;

    // 4d) Undo the plan on the alternate
    data->rollback(topcommand);

    // 4e) Prepare for the next alternate
    if (data->state->a_date < min_next_date)
      min_next_date = data->state->a_date;
    if (++i != thealternates.end() && loglevel>1 && search == PRIORITY)
      logger << indent(curload->getOperation()->getLevel()) << "   Alternate load switches from '"
          << curload->getResource()->getName() << "' to '"
          << (*i)->getResource()->getName() << "'" << endl;
  }

  // 5) Unconstrained plan: plan on the first alternate
  if (!originalPlanningMode && !(search != PRIORITY && bestAlternateSelection))
  {
    // Switch to unconstrained planning
    data->constrainedPlanning = false;
    bestAlternateSelection = *(thealternates.begin());
  }

  // 6) Finally replan on the best alternate
  if (!originalPlanningMode || (search != PRIORITY && bestAlternateSelection))
  {
    // Message
    if (loglevel>1)
      logger << indent(l->getOperation()->getLevel()) << "   Operation '"
          << l->getOperation()->getName() << "' chooses alternate '"
          << bestAlternateSelection->getResource() << "' " << search << endl;

    // Switch back
    data->state->q_loadplan = lplan; // because q_loadplan can change!
    data->state->a_cost = beforeCost;
    data->state->a_penalty = beforePenalty;
    if (lplan->getLoad() != bestAlternateSelection)
      lplan->setLoad(bestAlternateSelection);
    lplan->getOperationPlan()->restore(originalOpplan);
    // TODO XXX need to restore also the selected resource with the right skill!
    data->state->q_qty = lplan->getQuantity();
    data->state->q_date = lplan->getDate();
    bestAlternateSelection->getResource()->solve(*this,data);

    // Restore the planning mode
    data->constrainedPlanning = originalPlanningMode;
    data->logConstraints = originalLogConstraints;
    return;
  }

  // 7) No alternate gave a good result
  data->state->a_date = min_next_date;
  data->state->a_qty = 0;

  // Restore the planning mode
  data->constrainedPlanning = originalPlanningMode;

  // Maintain the constraint list
  if (originalLogConstraints)
  {
    const Load *primary = *(thealternates.begin());
    data->planningDemand->getConstraints().push(
      ProblemCapacityOverload::metadata,
      primary->getResource(), originalOpplan.start, originalOpplan.end,
      -originalLoadplanQuantity);
  }
  data->logConstraints = originalLogConstraints;

  if (loglevel>1)
    logger << indent(lplan->getOperationPlan()->getOperation()->getLevel()) <<
        "   Alternate load doesn't find supply on any alternate : "
        << data->state->a_qty << "  " << data->state->a_date << endl;
}