operation.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/tags/0.8.0/src/model/operation.cpp $
00003   version : $LastChangedRevision: 1203 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2010-03-17 09:02:56 +0100 (Wed, 17 Mar 2010) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007 by Johan De Taeye                                    *
00010  *                                                                         *
00011  * This library is free software; you can redistribute it and/or modify it *
00012  * under the terms of the GNU Lesser General Public License as published   *
00013  * by the Free Software Foundation; either version 2.1 of the License, or  *
00014  * (at your option) any later version.                                     *
00015  *                                                                         *
00016  * This library is distributed in the hope that it will be useful,         *
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
00019  * General Public License for more details.                                *
00020  *                                                                         *
00021  * You should have received a copy of the GNU Lesser General Public        *
00022  * License along with this library; if not, write to the Free Software     *
00023  * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 *
00024  * USA                                                                     *
00025  *                                                                         *
00026  ***************************************************************************/
00027 
00028 #define FREPPLE_CORE
00029 #include "frepple/model.h"
00030 
00031 namespace frepple
00032 {
00033 
00034 template<class Operation> DECLARE_EXPORT Tree utils::HasName<Operation>::st;
00035 DECLARE_EXPORT const MetaCategory* Operation::metadata;
00036 DECLARE_EXPORT const MetaClass* OperationFixedTime::metadata,
00037   *OperationTimePer::metadata,
00038   *OperationRouting::metadata,
00039   *OperationAlternate::metadata,
00040   *OperationSetup::metadata;
00041 DECLARE_EXPORT Operation::Operationlist Operation::nosubOperations;
00042 DECLARE_EXPORT const Operation* OperationSetup::setupoperation;
00043 
00044 
00045 int Operation::initialize()
00046 {
00047   // Initialize the metadata
00048   metadata = new MetaCategory("operation", "operations", reader, writer);
00049 
00050   // Initialize the Python class
00051   return FreppleCategory<Operation>::initialize();
00052 }
00053 
00054 
00055 int OperationFixedTime::initialize()
00056 {
00057   // Initialize the metadata
00058   metadata = new MetaClass("operation", "operation_fixed_time",
00059     Object::createString<OperationFixedTime>, true);
00060 
00061   // Initialize the Python class
00062   return FreppleClass<OperationFixedTime,Operation>::initialize();
00063 }
00064 
00065 
00066 int OperationTimePer::initialize()
00067 {
00068   // Initialize the metadata
00069   metadata = new MetaClass("operation", "operation_time_per",
00070     Object::createString<OperationTimePer>);
00071 
00072   // Initialize the Python class
00073   return FreppleClass<OperationTimePer,Operation>::initialize();
00074 }
00075 
00076 
00077 int OperationAlternate::initialize()
00078 {
00079   // Initialize the metadata
00080   metadata = new MetaClass("operation", "operation_alternate",
00081     Object::createString<OperationAlternate>);
00082 
00083   // Initialize the Python class
00084   FreppleClass<OperationAlternate,Operation>::getType().addMethod("addAlternate", OperationAlternate::addAlternate, METH_KEYWORDS, "add an alternate");
00085   return FreppleClass<OperationAlternate,Operation>::initialize();
00086 }
00087 
00088 
00089 int OperationRouting::initialize()
00090 {
00091   // Initialize the metadata
00092   metadata = new MetaClass("operation", "operation_routing",
00093     Object::createString<OperationRouting>);
00094 
00095   // Initialize the Python class
00096   FreppleClass<OperationRouting,Operation>::getType().addMethod("addStep", OperationRouting::addStep, METH_VARARGS , "add steps to the routing");
00097   return FreppleClass<OperationRouting,Operation>::initialize();
00098 }
00099 
00100 
00101 int OperationSetup::initialize()
00102 {
00103   // Initialize the metadata.
00104   // There is NO factory method
00105   metadata = new MetaClass("operation", "operation_setup");
00106 
00107   // Initialize the Python class
00108   int tmp = FreppleClass<OperationSetup,Operation>::initialize();
00109 
00110   // Create a generic setup operation.
00111   // This will be the only instance of this class.
00112   setupoperation = add(new OperationSetup("setup operation"));
00113 
00114   return tmp;
00115 }
00116 
00117 
00118 DECLARE_EXPORT Operation::~Operation()
00119 {
00120   // Delete all existing operationplans (even locked ones)
00121   deleteOperationPlans(true);
00122 
00123   // The Flow and Load objects are automatically deleted by the destructor
00124   // of the Association list class.
00125 
00126   // Remove the reference to this operation from all items
00127   for (Item::iterator k = Item::begin(); k != Item::end(); ++k)
00128     if (k->getOperation() == this) k->setOperation(NULL);
00129 
00130   // Remove the reference to this operation from all demands
00131   for (Demand::iterator l = Demand::begin(); l != Demand::end(); ++l)
00132     if (l->getOperation() == this) l->setOperation(NULL);
00133 
00134   // Remove the reference to this operation from all buffers
00135   for (Buffer::iterator m = Buffer::begin(); m != Buffer::end(); ++m)
00136     if (m->getProducingOperation() == this) m->setProducingOperation(NULL);
00137 
00138   // Remove the operation from its super-operations and sub-operations
00139   // Note that we are not using a for-loop since our function is actually
00140   // updating the list of super-operations at the same time as we move
00141   // through it.
00142   while (!getSuperOperations().empty())
00143     removeSuperOperation(*getSuperOperations().begin());
00144 }
00145 
00146 
00147 DECLARE_EXPORT OperationRouting::~OperationRouting()
00148 {
00149   // Note that we are not using a for-loop since our function is actually
00150   // updating the list of super-operations at the same time as we move
00151   // through it.
00152   while (!getSubOperations().empty())
00153     removeSubOperation(*getSubOperations().begin());
00154 }
00155 
00156 
00157 DECLARE_EXPORT OperationAlternate::~OperationAlternate()
00158 {
00159   // Note that we are not using a for-loop since our function is actually
00160   // updating the list of super-operations at the same time as we move
00161   // through it.
00162   while (!getSubOperations().empty())
00163     removeSubOperation(*getSubOperations().begin());
00164 }
00165 
00166 
00167 DECLARE_EXPORT OperationPlan* Operation::createOperationPlan (double q, Date s, Date e,
00168     Demand* l, OperationPlan* ow, unsigned long i,
00169     bool makeflowsloads) const
00170 {
00171   OperationPlan *opplan = new OperationPlan();
00172   initOperationPlan(opplan,q,s,e,l,ow,i,makeflowsloads);
00173   return opplan;
00174 }
00175 
00176 
00177 DECLARE_EXPORT DateRange Operation::calculateOperationTime
00178   (Date thedate, TimePeriod duration, bool forward,
00179     TimePeriod *actualduration) const
00180 {
00181   int calcount = 0;
00182   // Initial size of 10 should do for 99.99% of all cases
00183   vector<Calendar::EventIterator*> cals(10);
00184 
00185   // Default actual duration
00186   if (actualduration) *actualduration = duration;
00187 
00188   try
00189   {
00190     // Step 1: Create an iterator on each of the calendars
00191     // a) operation's location
00192     if (loc && loc->getAvailable())
00193       cals[calcount++] = new Calendar::EventIterator(loc->getAvailable(), thedate, forward);
00194     /* @todo multiple availability calendars are not implmented yet
00195       for (Operation::loadlist::const_iterator g=loaddata.begin();
00196         g!=loaddata.end(); ++g)
00197     {
00198       Resource* res = g->getResource();
00199       if (res->getMaximum())
00200         // b) resource size calendar
00201         cals[calcount++] = new Calendar::EventIterator(
00202           res->getMaximum(),
00203           thedate
00204           );
00205       if (res->getLocation() && res->getLocation()->getAvailable())
00206         // c) resource location
00207         cals[calcount++] = new Calendar::EventIterator(
00208           res->getLocation()->getAvailable(),
00209           thedate
00210           );
00211     }
00212     */
00213 
00214     // Special case: no calendars at all
00215     if (calcount == 0)
00216       return forward ?
00217         DateRange(thedate, thedate+duration) :
00218         DateRange(thedate-duration, thedate);
00219 
00220     // Step 2: Iterate over the calendar dates to find periods where all
00221     // calendars are simultaneously effective.
00222     DateRange result;
00223     Date curdate = thedate;
00224     bool status = false;
00225     TimePeriod curduration = duration;
00226     while (true)
00227     {
00228       // Check whether all calendars are available
00229       bool available = true;
00230       for (int c = 0; c < calcount && available; c++)
00231       {
00232         if (cals[c]->getBucket())
00233           available = cals[c]->getBucket()->getBool();
00234         else
00235           available = cals[c]->getCalendar()->getBool();
00236       }
00237       curdate = cals[0]->getDate();
00238 
00239       if (available && !status)
00240       {
00241         // Becoming available after unavailable period
00242         thedate = curdate;
00243         status = true;
00244         if (forward && result.getStart() == Date::infinitePast)
00245           // First available time - make operation start at this time
00246           result.setStart(curdate);
00247         else if (!forward && result.getEnd() == Date::infiniteFuture)
00248           // First available time - make operation end at this time
00249           result.setEnd(curdate);
00250       }
00251       else if (!available && status)
00252       {
00253         // Becoming unavailable after available period
00254         status = false;
00255         if (forward)
00256         {
00257           // Forward
00258           TimePeriod delta = curdate - thedate;
00259           if (delta >= curduration)
00260           {
00261             result.setEnd(thedate + curduration);
00262             break;
00263           }
00264           else
00265             curduration -= delta;
00266         }
00267         else
00268         {
00269           // Backward
00270           TimePeriod delta = thedate - curdate;
00271           if (delta >= curduration)
00272           {
00273             result.setStart(thedate - curduration);
00274             break;
00275           }
00276           else
00277             curduration -= delta;
00278         }
00279       }
00280       else if (forward && curdate == Date::infiniteFuture)
00281       {
00282         // End of forward iteration
00283         if (available)
00284         {
00285           TimePeriod delta = curdate - thedate;
00286           if (delta >= curduration)
00287             result.setEnd(thedate + curduration);
00288           else if (actualduration)
00289             *actualduration = duration - curduration;
00290         }
00291         else  if (actualduration)
00292           *actualduration = duration - curduration;
00293         break;
00294       }
00295       else if (!forward && curdate == Date::infinitePast)
00296       {
00297         // End of backward iteration
00298         if (available)
00299         {
00300           TimePeriod delta = thedate - curdate;
00301           if (delta >= curduration)
00302             result.setStart(thedate - curduration);
00303           else if (actualduration)
00304             *actualduration = duration - curduration;
00305         }
00306         else if (actualduration)
00307           *actualduration = duration - curduration;
00308         break;
00309       }
00310 
00311       // Advance to the next event
00312       if (forward) ++(*cals[0]);
00313       else --(*cals[0]);
00314     }
00315 
00316     // Step 3: Clean up
00317     while (calcount) delete cals[--calcount];
00318     return result;
00319   }
00320   catch (...)
00321   {
00322     // Clean up
00323     while (calcount) delete cals[calcount--];
00324     // Rethrow the exception
00325     throw;
00326   }
00327 }
00328 
00329 
00330 DECLARE_EXPORT DateRange Operation::calculateOperationTime
00331   (Date start, Date end, TimePeriod *actualduration) const
00332 {
00333   // Switch start and end if required
00334   if (end < start)
00335   {
00336     Date tmp = start;
00337     start = end;
00338     end = tmp;
00339   }
00340 
00341   int calcount = 0;
00342   // Initial size of 10 should do for 99.99% of all cases
00343   vector<Calendar::EventIterator*> cals(10);
00344 
00345   // Default actual duration
00346    if (actualduration) *actualduration = 0L;
00347 
00348   try
00349   {
00350     // Step 1: Create an iterator on each of the calendars
00351     // a) operation's location
00352     if (loc && loc->getAvailable())
00353       cals[calcount++] = new Calendar::EventIterator(loc->getAvailable(), start);
00354     /* @todo multiple availability calendars are not implmented yet
00355       for (Operation::loadlist::const_iterator g=loaddata.begin();
00356         g!=loaddata.end(); ++g)
00357     {
00358       Resource* res = g->getResource();
00359       if (res->getMaximum())
00360         // b) resource size calendar
00361         cals[calcount++] = new Calendar::EventIterator(
00362           res->getMaximum(),
00363           start
00364           );
00365       if (res->getLocation() && res->getLocation()->getAvailable())
00366         // c) resource location
00367         cals[calcount++] = new Calendar::EventIterator(
00368           res->getLocation()->getAvailable(),
00369           start
00370           );
00371     }
00372     */
00373 
00374     // Special case: no calendars at all
00375     if (calcount == 0)
00376     {
00377        if (actualduration) *actualduration = end - start;
00378       return DateRange(start, end);
00379     }
00380 
00381     // Step 2: Iterate over the calendar dates to find periods where all
00382     // calendars are simultaneously effective.
00383     DateRange result;
00384     Date curdate = start;
00385     bool status = false;
00386     while (true)
00387     {
00388       // Check whether all calendar are available
00389       bool available = true;
00390       for (int c = 0; c < calcount && available; c++)
00391       {
00392         if (cals[c]->getBucket())
00393           available = cals[c]->getBucket()->getBool();
00394         else
00395           available = cals[c]->getCalendar()->getBool();
00396       }
00397       curdate = cals[0]->getDate();
00398 
00399       if (available && !status)
00400       {
00401         // Becoming available after unavailable period
00402         if (curdate >= end)
00403         {
00404           // Leaving the desired date range
00405           result.setEnd(start);
00406           break;
00407         }
00408         start = curdate;
00409         status = true;
00410         if (result.getStart() == Date::infinitePast)
00411           // First available time - make operation start at this time
00412           result.setStart(curdate);
00413       }
00414       else if (!available && status)
00415       {
00416         // Becoming unavailable after available period
00417         if (curdate >= end)
00418         {
00419           // Leaving the desired date range
00420            if (actualduration) *actualduration += end - start;
00421           result.setEnd(end);
00422           break;
00423         }
00424         status = false;
00425          if (actualduration) *actualduration += curdate - start;
00426         start = curdate;
00427       }
00428       else if (curdate >= end)
00429       {
00430         // Leaving the desired date range
00431         if (available)
00432         {
00433            if (actualduration) *actualduration += end - start;
00434           result.setEnd(end);
00435           break;
00436         }
00437         else
00438           result.setEnd(start);
00439         break;
00440       }
00441 
00442       // Advance to the next event
00443       ++(*cals[0]);
00444     }
00445 
00446     // Step 3: Clean up
00447     while (calcount) delete cals[--calcount];
00448     return result;
00449   }
00450   catch (...)
00451   {
00452     // Clean up
00453     while (calcount) delete cals[calcount--];
00454     // Rethrow the exception
00455     throw;
00456   }
00457 }
00458 
00459 
00460 DECLARE_EXPORT void Operation::initOperationPlan (OperationPlan* opplan,
00461     double q, const Date& s, const Date& e, Demand* l, OperationPlan* ow,
00462     unsigned long i, bool makeflowsloads) const
00463 {
00464   opplan->oper = const_cast<Operation*>(this);
00465   opplan->setDemand(l);
00466   opplan->id = i;
00467 
00468   // Setting the owner first. Note that the order is important here!
00469   // For alternates & routings the quantity needs to be set through the owner.
00470   opplan->setOwner(ow);
00471 
00472   // Setting the dates and quantity
00473   setOperationPlanParameters(opplan,q,s,e);
00474 
00475   // Create the loadplans and flowplans, if allowed
00476   if (makeflowsloads) opplan->createFlowLoads();
00477 
00478   // Update flow and loadplans, and mark for problem detection
00479   opplan->update();
00480 }
00481 
00482 
00483 DECLARE_EXPORT void Operation::deleteOperationPlans(bool deleteLockedOpplans)
00484 {
00485   OperationPlan::deleteOperationPlans(this, deleteLockedOpplans);
00486 }
00487 
00488 
00489 DECLARE_EXPORT void Operation::writeElement(XMLOutput *o, const Keyword& tag, mode m) const
00490 {
00491   // Note that this class is abstract and never instantiated directly. There is
00492   // therefore no reason to ever write a header.
00493   assert(m == NOHEADER);
00494 
00495   // Write the fields
00496   HasDescription::writeElement(o, tag);
00497   Plannable::writeElement(o, tag);
00498   if (post_time)
00499     o->writeElement(Tags::tag_posttime, post_time);
00500   if (pre_time)
00501     o->writeElement(Tags::tag_pretime, pre_time);
00502   if (getCost() != 0.0)
00503     o->writeElement(Tags::tag_cost, getCost());
00504   if (fence)
00505     o->writeElement(Tags::tag_fence, fence);
00506   if (size_minimum != 1.0)
00507     o->writeElement(Tags::tag_size_minimum, size_minimum);
00508   if (size_multiple > 0.0)
00509     o->writeElement(Tags::tag_size_multiple, size_multiple);
00510   if (size_maximum < DBL_MAX)
00511     o->writeElement(Tags::tag_size_maximum, size_maximum);
00512   if (loc)
00513     o->writeElement(Tags::tag_location, loc);
00514 
00515   // Write extra plan information
00516   if ((o->getContentType() == XMLOutput::PLAN
00517       || o->getContentType() == XMLOutput::PLANDETAIL) && first_opplan)
00518   {
00519     o->BeginObject(Tags::tag_operationplans);
00520     for (OperationPlan::iterator i(this); i!=OperationPlan::end(); ++i)
00521       o->writeElement(Tags::tag_operationplan, *i, FULL);
00522     o->EndObject(Tags::tag_operationplans);
00523   }
00524 }
00525 
00526 
00527 DECLARE_EXPORT void Operation::beginElement(XMLInput& pIn, const Attribute& pAttr)
00528 {
00529   if (pAttr.isA(Tags::tag_flow)
00530       && pIn.getParentElement().first.isA(Tags::tag_flows))
00531   {
00532     Flow *f =
00533       dynamic_cast<Flow*>(MetaCategory::ControllerDefault(Flow::metadata,pIn.getAttributes()));
00534     if (f) f->setOperation(this);
00535     pIn.readto(f);
00536   }
00537   else if (pAttr.isA (Tags::tag_load)
00538       && pIn.getParentElement().first.isA(Tags::tag_loads))
00539   {
00540     Load* l = new Load();
00541     l->setOperation(this);
00542     pIn.readto(&*l);
00543   }
00544   else if (pAttr.isA (Tags::tag_operationplan))
00545     pIn.readto(OperationPlan::createOperationPlan(OperationPlan::metadata, pIn.getAttributes()));
00546   else if (pAttr.isA (Tags::tag_location))
00547     pIn.readto( Location::reader(Location::metadata,pIn.getAttributes()) );
00548 }
00549 
00550 
00551 DECLARE_EXPORT void Operation::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00552 {
00553   if (pAttr.isA (Tags::tag_fence))
00554     setFence(pElement.getTimeperiod());
00555   else if (pAttr.isA (Tags::tag_size_minimum))
00556     setSizeMinimum(pElement.getDouble());
00557   else if (pAttr.isA (Tags::tag_cost))
00558     setCost(pElement.getDouble());
00559   else if (pAttr.isA (Tags::tag_size_multiple))
00560     setSizeMultiple(pElement.getDouble());
00561   else if (pAttr.isA (Tags::tag_size_maximum))
00562     setSizeMaximum(pElement.getDouble());
00563   else if (pAttr.isA (Tags::tag_pretime))
00564     setPreTime(pElement.getTimeperiod());
00565   else if (pAttr.isA (Tags::tag_posttime))
00566     setPostTime(pElement.getTimeperiod());
00567   else if (pAttr.isA (Tags::tag_location))
00568   {
00569     Location *l = dynamic_cast<Location*>(pIn.getPreviousObject());
00570     if (l) setLocation(l);
00571     else throw LogicException("Incorrect object type during read operation");
00572   }
00573   else
00574   {
00575     Plannable::endElement(pIn, pAttr, pElement);
00576     HasDescription::endElement(pIn, pAttr, pElement);
00577   }
00578 }
00579 
00580 
00581 DECLARE_EXPORT OperationPlanState
00582 OperationFixedTime::setOperationPlanParameters
00583 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd, bool execute) const
00584 {
00585   // Invalid call to the function, or locked operationplan.
00586   if (!opplan || q<0)
00587     throw LogicException("Incorrect parameters for fixedtime operationplan");
00588   if (opplan->getLocked())
00589     return OperationPlanState(opplan);
00590 
00591   // All quantities are valid, as long as they are bigger than the minimum size
00592   if (q > 0 && q < getSizeMinimum()) q = getSizeMinimum();
00593   if (fabs(q - opplan->getQuantity()) > ROUNDING_ERROR)
00594     q = opplan->setQuantity(q, false, false, execute);
00595 
00596   // Set the start and end date.
00597   DateRange x;
00598   TimePeriod actualduration;
00599   if (e && s)
00600   {
00601     if (preferEnd) x = calculateOperationTime(e, duration, false, &actualduration);
00602     else x = calculateOperationTime(s, duration, true, &actualduration);
00603   }
00604   else if (s) x = calculateOperationTime(s, duration, true, &actualduration);
00605   else x = calculateOperationTime(e, duration, false, &actualduration);
00606   if (!execute)
00607     // Simulation only
00608     return OperationPlanState(x, actualduration == duration ? q : 0);
00609   else if (actualduration == duration)
00610     // Update succeeded
00611     opplan->setStartAndEnd(x.getStart(), x.getEnd());
00612   else
00613     // Update failed - Not enough available time
00614     opplan->setQuantity(0);
00615 
00616   // Return value
00617   return OperationPlanState(opplan);
00618 }
00619 
00620 
00621 DECLARE_EXPORT bool OperationFixedTime::extraInstantiate(OperationPlan* o)
00622 {
00623   // See if we can consolidate this operationplan with an existing one.
00624   // Merging is possible only when all the following conditions are met:
00625   //   - id of the new opplan is not set
00626   //   - id of the old opplan is set
00627   //   - it is a fixedtime operation
00628   //   - it doesn't load any resources
00629   //   - both operationplans aren't locked
00630   //   - both operationplans have no owner
00631   //   - start and end date of both operationplans are the same
00632   //   - demand of both operationplans are the same
00633   //   - maximum operation size is not exceeded
00634   //   - alternate flowplans need to be on the same alternate
00635   if (!o->getIdentifier() && !o->getLocked() && !o->getOwner() && getLoads().empty())
00636   {
00637     // Loop through candidates
00638     OperationPlan::iterator x(this);
00639     OperationPlan *y = NULL;
00640     for (; x != OperationPlan::end() && *x < *o; ++x)
00641       y = &*x;
00642     if (y && y->getDates() == o->getDates() && !y->getOwner()
00643       && y->getDemand() == o->getDemand() && !y->getLocked() && y->getIdentifier()
00644       && y->getQuantity() + o->getQuantity() < getSizeMaximum())  // @todo ignores multiple qty
00645     {
00646       // Check that the flowplans are on identical alternates
00647       OperationPlan::FlowPlanIterator fp1 = o->beginFlowPlans();
00648       OperationPlan::FlowPlanIterator fp2 = y->beginFlowPlans();
00649       while (fp1 != o->endFlowPlans())
00650       {
00651         if (fp1->getBuffer() != fp2->getBuffer())
00652           // Different alternates - no merge possible
00653           return true;
00654         ++fp1;
00655         ++fp2;
00656       }
00657       // Merging with the 'next' operationplan
00658       y->setQuantity(y->getQuantity() + o->getQuantity());
00659       return false;
00660     }
00661     if (x!= OperationPlan::end() && x->getDates() == o->getDates() && !x->getOwner()
00662       && x->getDemand() == o->getDemand() && !x->getLocked() && x->getIdentifier()
00663       && x->getQuantity() + o->getQuantity() < getSizeMaximum()) // @todo ignores multiple qty
00664     {
00665       // Check that the flowplans are on identical alternates
00666       OperationPlan::FlowPlanIterator fp1 = o->beginFlowPlans();
00667       OperationPlan::FlowPlanIterator fp2 = x->beginFlowPlans();
00668       while (fp1 != o->endFlowPlans())
00669       {
00670         if (fp1->getBuffer() != fp2->getBuffer())
00671           // Different alternates - no merge possible
00672           return true;
00673         ++fp1;
00674         ++fp2;
00675       }
00676       // Merging with the 'previous' operationplan
00677       x->setQuantity(x->getQuantity() + o->getQuantity());
00678       return false;
00679     }
00680   }
00681   return true;
00682 }
00683 
00684 
00685 DECLARE_EXPORT void OperationFixedTime::writeElement
00686 (XMLOutput *o, const Keyword& tag, mode m) const
00687 {
00688   // Writing a reference
00689   if (m == REFERENCE)
00690   {
00691     o->writeElement
00692       (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00693     return;
00694   }
00695 
00696   // Write the complete object
00697   if (m != NOHEADER) o->BeginObject
00698     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00699 
00700   // Write the fields
00701   Operation::writeElement(o, tag, NOHEADER);
00702   if (duration) o->writeElement (Tags::tag_duration, duration);
00703   o->EndObject (tag);
00704 }
00705 
00706 
00707 DECLARE_EXPORT void OperationFixedTime::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00708 {
00709   if (pAttr.isA (Tags::tag_duration))
00710     setDuration (pElement.getTimeperiod());
00711   else
00712     Operation::endElement (pIn, pAttr, pElement);
00713 }
00714 
00715 
00716 DECLARE_EXPORT OperationPlanState
00717 OperationTimePer::setOperationPlanParameters
00718 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd, bool execute) const
00719 {
00720   // Invalid call to the function.
00721   if (!opplan || q<0)
00722     throw LogicException("Incorrect parameters for timeper operationplan");
00723   if (opplan->getLocked())
00724     return OperationPlanState(opplan);
00725 
00726   // Respect minimum size
00727   if (q > 0 && q < getSizeMinimum()) q = getSizeMinimum();
00728 
00729   // The logic depends on which dates are being passed along
00730   DateRange x;
00731   TimePeriod actual;
00732   if (s && e)
00733   {
00734     // Case 1: Both the start and end date are specified: Compute the quantity.
00735     // Calculate the available time between those dates
00736     x = calculateOperationTime(s,e,&actual);
00737     if (actual < duration)
00738     {
00739       // Start and end aren't far enough from each other to fit the constant
00740       // part of the operation duration. This is infeasible.
00741       if (!execute) return OperationPlanState(x,0);
00742       opplan->setQuantity(0,true,false,execute);
00743       opplan->setEnd(e);  
00744     }
00745     else
00746     {
00747       // Calculate the quantity, respecting minimum, maximum and multiple size.
00748       if (duration_per)
00749       {
00750         if (q * duration_per < static_cast<double>(actual - duration) + 1)
00751           // Provided quantity is acceptable.
00752           // Note that we allow a margin of 1 second to accept.
00753           q = opplan->setQuantity(q, true, false, execute);
00754         else
00755           // Calculate the maximum operationplan that will fit in the window
00756           q = opplan->setQuantity(
00757             static_cast<double>(actual - duration) / duration_per, 
00758             true, false, execute);
00759       }
00760       else
00761         // No duration_per field given, so any quantity will go
00762         q = opplan->setQuantity(q, true, false, execute);
00763 
00764       // Updates the dates
00765       TimePeriod wanted(
00766         duration + static_cast<long>(duration_per * q)
00767         );
00768       if (preferEnd) x = calculateOperationTime(e, wanted, false, &actual);
00769       else x = calculateOperationTime(s, wanted, true, &actual);
00770       if (!execute) return OperationPlanState(x,q);
00771       opplan->setStartAndEnd(x.getStart(),x.getEnd());
00772     }
00773   }
00774   else if (e || !s)
00775   {
00776     // Case 2: Only an end date is specified. Respect the quantity and
00777     // compute the start date
00778     // Case 4: No date was given at all. Respect the quantity and the
00779     // existing end date of the operationplan.
00780     q = opplan->setQuantity(q,true,false,execute); // Round and size the quantity
00781     TimePeriod wanted(duration + static_cast<long>(duration_per * q));
00782     x = calculateOperationTime(e, wanted, false, &actual);
00783     if (actual == wanted)
00784     {
00785       // Size is as desired
00786       if (!execute) return OperationPlanState(x, q);
00787       opplan->setStartAndEnd(x.getStart(),x.getEnd());
00788     }
00789     else if (actual < duration)
00790     {
00791       // Not feasible
00792       if (!execute) return OperationPlanState(x, 0);
00793       opplan->setQuantity(0,true,false);
00794       opplan->setStartAndEnd(e,e);
00795     }
00796     else
00797     {
00798       // Resize the quantity to be feasible
00799       double max_q = duration_per ?
00800         static_cast<double>(actual-duration) / duration_per :
00801         q;
00802       q = opplan->setQuantity(q < max_q ? q : max_q, true, false, execute);
00803       wanted = duration + static_cast<long>(duration_per * q);
00804       x = calculateOperationTime(e, wanted, false, &actual);
00805       if (!execute) return OperationPlanState(x, q);
00806       opplan->setStartAndEnd(x.getStart(),x.getEnd());
00807     }
00808   }
00809   else
00810   {
00811     // Case 3: Only a start date is specified. Respect the quantity and
00812     // compute the end date
00813     q = opplan->setQuantity(q,true,false,execute); // Round and size the quantity
00814     TimePeriod wanted(
00815       duration + static_cast<long>(duration_per * q)
00816       );
00817     TimePeriod actual;
00818     x = calculateOperationTime(s, wanted, true, &actual);
00819     if (actual == wanted)
00820     {
00821       // Size is as desired
00822       if (!execute) return OperationPlanState(x, q);
00823       opplan->setStartAndEnd(x.getStart(),x.getEnd());
00824     }
00825     else if (actual < duration)
00826     {
00827       // Not feasible
00828       if (!execute) return OperationPlanState(x, 0);
00829       opplan->setQuantity(0,true,false);
00830       opplan->setStartAndEnd(s,s);
00831     }
00832     else
00833     {
00834       // Resize the quantity to be feasible
00835       double max_q = duration_per ?
00836         static_cast<double>(actual-duration) / duration_per :
00837         q;
00838       q = opplan->setQuantity(q < max_q ? q : max_q, true, false, execute);
00839       wanted = duration + static_cast<long>(duration_per * q);
00840       x = calculateOperationTime(e, wanted, false, &actual);
00841       if (!execute) return OperationPlanState(x, q);
00842       opplan->setStartAndEnd(x.getStart(),x.getEnd());
00843     }
00844   }
00845 
00846   // Return value
00847   return OperationPlanState(opplan);
00848 }
00849 
00850 
00851 DECLARE_EXPORT void OperationTimePer::writeElement
00852 (XMLOutput *o, const Keyword& tag, mode m) const
00853 {
00854   // Writing a reference
00855   if (m == REFERENCE)
00856   {
00857     o->writeElement
00858       (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00859     return;
00860   }
00861 
00862   // Write the complete object
00863   if (m != NOHEADER) o->BeginObject
00864     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00865 
00866   // Write the complete object
00867   Operation::writeElement(o, tag, NOHEADER);
00868   o->writeElement(Tags::tag_duration, duration);
00869   o->writeElement(Tags::tag_duration_per, duration_per);
00870   o->EndObject(tag);
00871 }
00872 
00873 
00874 DECLARE_EXPORT void OperationTimePer::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00875 {
00876   if (pAttr.isA (Tags::tag_duration))
00877     setDuration (pElement.getTimeperiod());
00878   else if (pAttr.isA (Tags::tag_duration_per))
00879     setDurationPer (pElement.getTimeperiod());
00880   else
00881     Operation::endElement (pIn, pAttr, pElement);
00882 }
00883 
00884 
00885 DECLARE_EXPORT void OperationRouting::writeElement
00886 (XMLOutput *o, const Keyword& tag, mode m) const
00887 {
00888   // Writing a reference
00889   if (m == REFERENCE)
00890   {
00891     o->writeElement
00892       (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00893     return;
00894   }
00895 
00896   // Write the complete object
00897   if (m != NOHEADER) o->BeginObject
00898     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00899 
00900   // Write the fields
00901   Operation::writeElement(o, tag, NOHEADER);
00902   if (steps.size())
00903   {
00904     o->BeginObject(Tags::tag_steps);
00905     for (Operationlist::const_iterator i = steps.begin(); i!=steps.end(); ++i)
00906       o->writeElement(Tags::tag_operation, *i, REFERENCE);
00907     o->EndObject(Tags::tag_steps);
00908   }
00909   o->EndObject(tag);
00910 }
00911 
00912 
00913 DECLARE_EXPORT void OperationRouting::beginElement(XMLInput& pIn, const Attribute& pAttr)
00914 {
00915   if (pAttr.isA (Tags::tag_operation))
00916     pIn.readto( Operation::reader(Operation::metadata,pIn.getAttributes()) );
00917   else
00918     Operation::beginElement(pIn, pAttr);
00919 }
00920 
00921 
00922 DECLARE_EXPORT void OperationRouting::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00923 {
00924   if (pAttr.isA (Tags::tag_operation)
00925       && pIn.getParentElement().first.isA(Tags::tag_steps))
00926   {
00927     Operation *oper = dynamic_cast<Operation*>(pIn.getPreviousObject());
00928     if (oper) addStepBack (oper);
00929     else throw LogicException("Incorrect object type during read operation");
00930   }
00931   Operation::endElement (pIn, pAttr, pElement);
00932 }
00933 
00934 
00935 DECLARE_EXPORT OperationPlanState OperationRouting::setOperationPlanParameters
00936 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd, bool execute) const
00937 {
00938   // Invalid call to the function
00939   if (!opplan || q<0)
00940     throw LogicException("Incorrect parameters for routing operationplan");
00941   if (opplan->getLocked())
00942     return OperationPlanState(opplan);
00943 
00944   if (!opplan->lastsubopplan || opplan->lastsubopplan->getOperation() == OperationSetup::setupoperation) // @todo replace with proper iterator
00945   {
00946     // No step operationplans to work with. Just apply the requested quantity
00947     // and dates.
00948     q = opplan->setQuantity(q,false,false,execute);
00949     if (!s && e) s = e;
00950     if (s && !e) e = s;
00951     if (!execute) return OperationPlanState(s, e, q);
00952     opplan->setStartAndEnd(s,e);
00953     return OperationPlanState(opplan);
00954   }
00955 
00956   // Behavior depends on the dates being passed.
00957   // Move all sub-operationplans in an orderly fashion, either starting from
00958   // the specified end date or the specified start date.
00959   OperationPlanState x;
00960   Date y;
00961   bool realfirst = true;
00962   if (e)
00963   {
00964     // Case 1: an end date is specified
00965     for (OperationPlan* i = opplan->lastsubopplan; i; i = i->prevsubopplan)
00966     {
00967       if (i->getOperation() == OperationSetup::setupoperation) continue;
00968       x = i->getOperation()->setOperationPlanParameters(i,q,Date::infinitePast,e,preferEnd,execute);
00969       e = x.start;
00970       if (realfirst) 
00971       {
00972         y = x.end;
00973         realfirst = false;
00974       }
00975     }
00976     return OperationPlanState(x.start, y, x.quantity);
00977   }
00978   else if (s)
00979   {
00980     // Case 2: a start date is specified
00981     for (OperationPlan *i = opplan->firstsubopplan; i; i = i->nextsubopplan)
00982     {
00983       if (i->getOperation() == OperationSetup::setupoperation) continue;
00984       x = i->getOperation()->setOperationPlanParameters(i,q,s,Date::infinitePast,preferEnd,execute);
00985       s = x.end;
00986       if (realfirst)
00987       {
00988         y = x.start;
00989         realfirst = false;
00990       }
00991     }
00992     return OperationPlanState(y, x.end, x.quantity);
00993   }
00994   else
00995     throw LogicException(
00996       "Updating a routing operationplan without start or end date argument"
00997     );
00998 }
00999 
01000 
01001 DECLARE_EXPORT bool OperationRouting::extraInstantiate(OperationPlan* o)
01002 {
01003   // Create step suboperationplans if they don't exist yet.
01004   if (!o->lastsubopplan || o->lastsubopplan->getOperation() == OperationSetup::setupoperation)
01005   {
01006     logger << "extra" << endl;
01007     Date d = o->getDates().getEnd();
01008     OperationPlan *p = NULL;
01009     // @todo not possible to initialize a routing oplan based on a start date
01010     if (d != Date::infiniteFuture)
01011     {
01012       // Using the end date
01013       for (Operation::Operationlist::const_reverse_iterator e =
01014             getSubOperations().rbegin(); e != getSubOperations().rend(); ++e)
01015       {
01016         p = (*e)->createOperationPlan(o->getQuantity(), Date::infinitePast,
01017             d, NULL, o, 0, true);
01018         d = p->getDates().getStart();
01019       }
01020     }
01021     else
01022     {
01023       // Using the start date when there is no end date
01024       d = o->getDates().getStart();
01025       // Using the current date when both the start and end date are missing
01026       if (!d) d = Plan::instance().getCurrent();
01027       for (Operation::Operationlist::const_iterator e =
01028             getSubOperations().begin(); e != getSubOperations().end(); ++e)
01029       {
01030         p = (*e)->createOperationPlan(o->getQuantity(), d,
01031             Date::infinitePast, NULL, o, 0, true);
01032         d = p->getDates().getEnd();
01033       }
01034     }
01035   }
01036   return true;
01037 }
01038 
01039 
01040 DECLARE_EXPORT SearchMode decodeSearchMode(const string& c)
01041 {
01042   if (c == "PRIORITY") return PRIORITY;
01043   if (c == "MINCOST") return MINCOST;
01044   if (c == "MINPENALTY") return MINPENALTY;
01045   if (c == "MINCOSTPENALTY") return MINCOSTPENALTY;
01046   throw DataException("Invalid search mode " + c);
01047 }
01048 
01049 
01050 DECLARE_EXPORT void OperationAlternate::addAlternate
01051   (Operation* o, int prio, DateRange eff)
01052 {
01053   if (!o) return;
01054   Operationlist::iterator altIter = alternates.begin();
01055   alternatePropertyList::iterator propIter = alternateProperties.begin();
01056   while (altIter!=alternates.end() && prio >= propIter->first)
01057   {
01058     ++propIter;
01059     ++altIter;
01060   }
01061   alternateProperties.insert(propIter,alternateProperty(prio,eff));
01062   alternates.insert(altIter,o);
01063   o->addSuperOperation(this);
01064 }
01065 
01066 
01067 DECLARE_EXPORT const OperationAlternate::alternateProperty&
01068   OperationAlternate::getProperties(Operation* o) const
01069 {
01070   if (!o)
01071     throw LogicException("Null pointer passed when searching for a \
01072         suboperation of alternate operation '" + getName() + "'");
01073   Operationlist::const_iterator altIter = alternates.begin();
01074   alternatePropertyList::const_iterator propIter = alternateProperties.begin();
01075   while (altIter!=alternates.end() && *altIter != o)
01076   {
01077     ++propIter;
01078     ++altIter;
01079   }
01080   if (*altIter == o) return *propIter;
01081   throw DataException("Operation '" + o->getName() +
01082       "' isn't a suboperation of alternate operation '" + getName() + "'");
01083 }
01084 
01085 
01086 DECLARE_EXPORT void OperationAlternate::setPriority(Operation* o, int f)
01087 {
01088   if (!o) return;
01089   Operationlist::const_iterator altIter = alternates.begin();
01090   alternatePropertyList::iterator propIter = alternateProperties.begin();
01091   while (altIter!=alternates.end() && *altIter != o)
01092   {
01093     ++propIter;
01094     ++altIter;
01095   }
01096   if (*altIter == o)
01097     propIter->first = f;
01098   else
01099     throw DataException("Operation '" + o->getName() +
01100         "' isn't a suboperation of alternate operation '" + getName() + "'");
01101 }
01102 
01103 
01104 DECLARE_EXPORT void OperationAlternate::setEffective(Operation* o, DateRange dr)
01105 {
01106   if (!o) return;
01107   Operationlist::const_iterator altIter = alternates.begin();
01108   alternatePropertyList::iterator propIter = alternateProperties.begin();
01109   while (altIter!=alternates.end() && *altIter != o)
01110   {
01111     ++propIter;
01112     ++altIter;
01113   }
01114   if (*altIter == o)
01115     propIter->second = dr;
01116   else
01117     throw DataException("Operation '" + o->getName() +
01118         "' isn't a suboperation of alternate operation '" + getName() + "'");
01119 }
01120 
01121 
01122 DECLARE_EXPORT void OperationAlternate::writeElement
01123 (XMLOutput *o, const Keyword& tag, mode m) const
01124 {
01125   // Writing a reference
01126   if (m == REFERENCE)
01127   {
01128     o->writeElement
01129       (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
01130     return;
01131   }
01132 
01133   // Write the complete object
01134   if (m != NOHEADER) o->BeginObject
01135     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
01136 
01137   // Write the standard fields
01138   Operation::writeElement(o, tag, NOHEADER);
01139   if (search != PRIORITY)
01140     o->writeElement(Tags::tag_search, search);
01141 
01142   // Write the extra fields
01143   o->BeginObject(Tags::tag_alternates);
01144   alternatePropertyList::const_iterator propIter = alternateProperties.begin();
01145   for (Operationlist::const_iterator i = alternates.begin();
01146       i != alternates.end(); ++i)
01147   {
01148     o->BeginObject(Tags::tag_alternate);
01149     o->writeElement(Tags::tag_operation, *i, REFERENCE);
01150     o->writeElement(Tags::tag_priority, propIter->first);
01151     if (propIter->second.getStart() != Date::infinitePast)
01152       o->writeElement(Tags::tag_effective_start, propIter->second.getStart());
01153     if (propIter->second.getEnd() != Date::infiniteFuture)
01154       o->writeElement(Tags::tag_effective_end, propIter->second.getEnd());
01155     o->EndObject (Tags::tag_alternate);
01156     ++propIter;
01157   }
01158   o->EndObject(Tags::tag_alternates);
01159 
01160   // Ending tag
01161   o->EndObject(tag);
01162 }
01163 
01164 
01165 DECLARE_EXPORT void OperationAlternate::beginElement(XMLInput& pIn, const Attribute& pAttr)
01166 {
01167   if (pAttr.isA(Tags::tag_operation))
01168     pIn.readto( Operation::reader(Operation::metadata,pIn.getAttributes()) );
01169   else
01170     Operation::beginElement(pIn, pAttr);
01171 }
01172 
01173 
01174 DECLARE_EXPORT void OperationAlternate::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
01175 {
01176   // Saving some typing...
01177   typedef pair<Operation*,alternateProperty> tempData;
01178 
01179   // Create a temporary object
01180   if (!pIn.getUserArea())
01181     pIn.setUserArea(new tempData(NULL,alternateProperty(1,DateRange())));
01182   tempData* tmp = static_cast<tempData*>(pIn.getUserArea());
01183 
01184   if (pAttr.isA(Tags::tag_alternate))
01185   {
01186     addAlternate(tmp->first, tmp->second.first, tmp->second.second);
01187     // Reset the defaults
01188     tmp->first = NULL;
01189     tmp->second.first = 1;
01190     tmp->second.second = DateRange();
01191   }
01192   else if (pAttr.isA(Tags::tag_priority))
01193     tmp->second.first = pElement.getInt();
01194   else if (pAttr.isA(Tags::tag_search))
01195     setSearch(pElement.getString());
01196   else if (pAttr.isA(Tags::tag_effective_start))
01197     tmp->second.second.setStart(pElement.getDate());
01198   else if (pAttr.isA(Tags::tag_effective_end))
01199     tmp->second.second.setEnd(pElement.getDate());
01200   else if (pAttr.isA(Tags::tag_operation)
01201       && pIn.getParentElement().first.isA(Tags::tag_alternate))
01202   {
01203     Operation * b = dynamic_cast<Operation*>(pIn.getPreviousObject());
01204     if (b) tmp->first = b;
01205     else throw LogicException("Incorrect object type during read operation");
01206   }
01207   Operation::endElement (pIn, pAttr, pElement);
01208 
01209   // Delete the temporary object
01210   if (pIn.isObjectEnd()) delete static_cast<tempData*>(pIn.getUserArea());
01211 }
01212 
01213 
01214 DECLARE_EXPORT OperationPlanState
01215 OperationAlternate::setOperationPlanParameters
01216   (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd,
01217   bool execute) const
01218 {
01219   // Invalid calls to this function
01220   if (!opplan || q<0)
01221     throw LogicException("Incorrect parameters for alternate operationplan");
01222   if (opplan->getLocked())
01223     return OperationPlanState(opplan);
01224 
01225   OperationPlan *x = opplan->lastsubopplan;
01226   while (x && x->getOperation() == OperationSetup::setupoperation) 
01227     x = x->prevsubopplan;
01228   if (!x)
01229   {
01230     // Blindly accept the parameters if there is no suboperationplan
01231     if (execute)
01232     {
01233       opplan->setQuantity(q,false,false);
01234       opplan->setStartAndEnd(s, e);
01235       return OperationPlanState(opplan);
01236     }
01237     else
01238       return OperationPlanState(s, e, opplan->setQuantity(q,false,false,false));
01239   }
01240   else
01241     // Pass the call to the sub-operation
01242     return x->getOperation()
01243       ->setOperationPlanParameters(x,q,s,e,preferEnd, execute);
01244 }
01245 
01246 
01247 DECLARE_EXPORT bool OperationAlternate::extraInstantiate(OperationPlan* o)
01248 {
01249   // Create a suboperationplan if one doesn't exist yet.
01250   // We use the first effective alternate by default.
01251   if (!o->lastsubopplan || o->lastsubopplan->getOperation() == OperationSetup::setupoperation)
01252   {
01253     // Find the right operation
01254     Operationlist::const_iterator altIter = getSubOperations().begin();
01255     for (; altIter != getSubOperations().end(); )
01256     {
01257       const OperationAlternate::alternateProperty& props = getProperties(*altIter);
01258       // Filter out alternates that are not suitable
01259       if (props.first != 0.0 && props.second.within(o->getDates().getEnd()))
01260         break;
01261     }
01262     if (altIter != getSubOperations().end())
01263       // Create an operationplan instance
01264       (*altIter)->createOperationPlan(
01265         o->getQuantity(), o->getDates().getStart(),
01266         o->getDates().getEnd(), NULL, o, 0, true);
01267   }
01268   return true;
01269 }
01270 
01271 
01272 DECLARE_EXPORT void OperationAlternate::removeSubOperation(Operation *o)
01273 {
01274   Operationlist::iterator altIter = alternates.begin();
01275   alternatePropertyList::iterator propIter = alternateProperties.begin();
01276   while (altIter!=alternates.end() && *altIter != o)
01277   {
01278     ++propIter;
01279     ++altIter;
01280   }
01281   if (*altIter == o)
01282   {
01283     alternates.erase(altIter);
01284     alternateProperties.erase(propIter);
01285     o->superoplist.remove(this);
01286     setChanged();
01287   }
01288   else
01289     logger << "Warning: operation '" << *o
01290     << "' isn't a suboperation of alternate operation '" << *this
01291     << "'" << endl;
01292 }
01293 
01294 
01295 DECLARE_EXPORT OperationPlanState OperationSetup::setOperationPlanParameters
01296 (OperationPlan* opplan, double q, Date s, Date e, bool preferEnd, bool execute) const
01297 {
01298   // Find or create a loadplan
01299   OperationPlan::LoadPlanIterator i = opplan->beginLoadPlans();
01300   LoadPlan *ldplan = NULL;
01301   if (i != opplan->endLoadPlans())
01302     // Already exists
01303     ldplan = &*i;
01304   else
01305   {
01306     // Create a new one
01307     if (!opplan->getOwner())
01308       throw LogicException("Setup operationplan always must have an owner");
01309     for (loadlist::const_iterator g=opplan->getOwner()->getOperation()->getLoads().begin(); 
01310       g!=opplan->getOwner()->getOperation()->getLoads().end(); ++g)
01311       if (g->getResource()->getSetupMatrix() && !g->getSetup().empty())
01312       {
01313         ldplan = new LoadPlan(opplan, &*g);
01314         break;
01315       }
01316     if (!ldplan)
01317       throw LogicException("Can't find a setup on operation '" 
01318         + opplan->getOwner()->getOperation()->getName() + "'");
01319   }
01320 
01321   // Find the setup of the resource at the start of the conversion
01322   const Load* lastld = NULL;
01323   Date boundary = s ? s : e;
01324   if (ldplan->getDate() < boundary)
01325   {
01326     for (TimeLine<LoadPlan>::const_iterator i = ldplan->getResource()->getLoadPlans().begin(ldplan);
01327       i != ldplan->getResource()->getLoadPlans().end() && i->getDate() <= boundary; ++i)
01328     {
01329       const LoadPlan *l = dynamic_cast<const LoadPlan*>(&*i);
01330       if (l && i->getQuantity() != 0.0 
01331         && l->getOperationPlan() != opplan
01332         && l->getOperationPlan() != opplan->getOwner()
01333         && !l->getLoad()->getSetup().empty())
01334           lastld = l->getLoad();
01335     }
01336   }
01337   if (!lastld)
01338   {
01339     for (TimeLine<LoadPlan>::const_iterator i = ldplan->getResource()->getLoadPlans().begin(ldplan);
01340       i != ldplan->getResource()->getLoadPlans().end(); --i)
01341     {
01342       const LoadPlan *l = dynamic_cast<const LoadPlan*>(&*i);
01343       if (l && i->getQuantity() != 0.0 
01344         && l->getOperationPlan() != opplan
01345         && l->getOperationPlan() != opplan->getOwner()
01346         && !l->getLoad()->getSetup().empty()
01347         && l->getDate() <= boundary)
01348         {
01349           lastld = l->getLoad();
01350           break;
01351         }
01352     }
01353   }
01354   string lastsetup = lastld ? lastld->getSetup() : ldplan->getResource()->getSetup();
01355 
01356   TimePeriod duration(0L);
01357   if (lastsetup != ldplan->getLoad()->getSetup())
01358   {
01359     // Calculate the setup time
01360     SetupMatrix::Rule *conversionrule = ldplan->getLoad()->getResource()->getSetupMatrix()
01361       ->calculateSetup(lastsetup, ldplan->getLoad()->getSetup());
01362     duration = conversionrule ? conversionrule->getDuration() : TimePeriod(365L*86400L);
01363   }
01364 
01365   // Set the start and end date.
01366   DateRange x;
01367   TimePeriod actualduration;
01368   if (e && s)
01369   {
01370     if (preferEnd) x = calculateOperationTime(e, duration, false, &actualduration);
01371     else x = calculateOperationTime(s, duration, true, &actualduration);
01372   }
01373   else if (s) x = calculateOperationTime(s, duration, true, &actualduration);
01374   else x = calculateOperationTime(e, duration, false, &actualduration);
01375   if (!execute)
01376     // Simulation only
01377     return OperationPlanState(x, actualduration == duration ? q : 0);
01378   else if (actualduration == duration)
01379   {
01380     // Update succeeded
01381     opplan->setStartAndEnd(x.getStart(), x.getEnd());
01382     if (opplan->getOwner()->getDates().getStart() != opplan->getDates().getEnd())
01383       opplan->getOwner()->setStart(opplan->getDates().getEnd());
01384   }
01385   else
01386     // Update failed - Not enough available time @todo setting the qty to 0 is not enough
01387     opplan->setQuantity(0);
01388 
01389   return OperationPlanState(opplan);
01390 }
01391 
01392 
01393 DECLARE_EXPORT PyObject* Operation::getattro(const Attribute& attr)
01394 {
01395   if (attr.isA(Tags::tag_name))
01396     return PythonObject(getName());
01397   if (attr.isA(Tags::tag_description))
01398     return PythonObject(getDescription());
01399   if (attr.isA(Tags::tag_category))
01400     return PythonObject(getCategory());
01401   if (attr.isA(Tags::tag_subcategory))
01402     return PythonObject(getSubCategory());
01403   if (attr.isA(Tags::tag_location))
01404     return PythonObject(getLocation());
01405   if (attr.isA(Tags::tag_fence))
01406     return PythonObject(getFence());
01407   if (attr.isA(Tags::tag_size_minimum))
01408     return PythonObject(getSizeMinimum());
01409   if (attr.isA(Tags::tag_size_multiple))
01410     return PythonObject(getSizeMultiple());
01411   if (attr.isA(Tags::tag_size_maximum))
01412     return PythonObject(getSizeMaximum());
01413   if (attr.isA(Tags::tag_cost))
01414     return PythonObject(getCost());
01415   if (attr.isA(Tags::tag_pretime))
01416     return PythonObject(getPreTime());
01417   if (attr.isA(Tags::tag_posttime))
01418     return PythonObject(getPostTime());
01419   if (attr.isA(Tags::tag_hidden))
01420     return PythonObject(getHidden());
01421   if (attr.isA(Tags::tag_loads))
01422     return new LoadIterator(this);
01423   if (attr.isA(Tags::tag_flows))
01424     return new FlowIterator(this);
01425   if (attr.isA(Tags::tag_operationplans))
01426     return new OperationPlanIterator(this);
01427   if (attr.isA(Tags::tag_level))
01428     return PythonObject(getLevel());
01429   if (attr.isA(Tags::tag_cluster))
01430     return PythonObject(getCluster());
01431   return NULL;
01432 }
01433 
01434 
01435 DECLARE_EXPORT int Operation::setattro(const Attribute& attr, const PythonObject& field)
01436 {
01437   if (attr.isA(Tags::tag_name))
01438     setName(field.getString());
01439   else if (attr.isA(Tags::tag_description))
01440     setDescription(field.getString());
01441   else if (attr.isA(Tags::tag_category))
01442     setCategory(field.getString());
01443   else if (attr.isA(Tags::tag_subcategory))
01444     setSubCategory(field.getString());
01445   else if (attr.isA(Tags::tag_location))
01446   {
01447     if (!field.check(Location::metadata))
01448     {
01449       PyErr_SetString(PythonDataException, "buffer location must be of type location");
01450       return -1;
01451     }
01452     Location* y = static_cast<Location*>(static_cast<PyObject*>(field));
01453     setLocation(y);
01454   }
01455   else if (attr.isA(Tags::tag_fence))
01456     setFence(field.getTimeperiod());
01457   else if (attr.isA(Tags::tag_size_minimum))
01458     setSizeMinimum(field.getDouble());
01459   else if (attr.isA(Tags::tag_size_multiple))
01460     setSizeMultiple(field.getDouble());
01461   else if (attr.isA(Tags::tag_size_maximum))
01462     setSizeMaximum(field.getDouble());
01463   else if (attr.isA(Tags::tag_cost))
01464     setCost(field.getDouble());
01465   else if (attr.isA(Tags::tag_pretime))
01466     setPreTime(field.getTimeperiod());
01467   else if (attr.isA(Tags::tag_posttime))
01468     setPostTime(field.getTimeperiod());
01469   else if (attr.isA(Tags::tag_hidden))
01470     setHidden(field.getBool());
01471   else
01472     return -1;  // Error
01473   return 0;  // OK
01474 }
01475 
01476 
01477 DECLARE_EXPORT PyObject* OperationFixedTime::getattro(const Attribute& attr)
01478 {
01479   if (attr.isA(Tags::tag_duration))
01480     return PythonObject(getDuration());
01481   return Operation::getattro(attr);
01482 }
01483 
01484 
01485 DECLARE_EXPORT int OperationFixedTime::setattro(const Attribute& attr, const PythonObject& field)
01486 {
01487   if (attr.isA(Tags::tag_duration))
01488     setDuration(field.getTimeperiod());
01489   else
01490     return Operation::setattro(attr, field);
01491   return 0;
01492 }
01493 
01494 
01495 DECLARE_EXPORT PyObject* OperationTimePer::getattro(const Attribute& attr)
01496 {
01497   if (attr.isA(Tags::tag_duration))
01498     return PythonObject(getDuration());
01499   if (attr.isA(Tags::tag_duration))
01500     return PythonObject(getDurationPer());
01501   return Operation::getattro(attr);
01502 }
01503 
01504 
01505 DECLARE_EXPORT int OperationTimePer::setattro(const Attribute& attr, const PythonObject& field)
01506 {
01507   if (attr.isA(Tags::tag_duration))
01508     setDuration(field.getTimeperiod());
01509   else if (attr.isA(Tags::tag_duration_per))
01510     setDurationPer(field.getTimeperiod());
01511   else
01512     return Operation::setattro(attr, field);
01513   return 0;
01514 }
01515 
01516 
01517 DECLARE_EXPORT PyObject* OperationAlternate::getattro(const Attribute& attr)
01518 {
01519   if (attr.isA(Tags::tag_alternates))
01520   {
01521     PyObject* result = PyTuple_New(getSubOperations().size());
01522     int count = 0;
01523     for (Operation::Operationlist::const_iterator i = getSubOperations().begin(); i != getSubOperations().end(); ++i)
01524       PyTuple_SetItem(result, count++, PythonObject(*i));
01525     return result;
01526   }
01527   if (attr.isA(Tags::tag_search))
01528   {
01529     ostringstream ch;
01530     ch << getSearch();
01531     return PythonObject(ch.str());
01532   }
01533   return Operation::getattro(attr);
01534 }
01535 
01536 
01537 DECLARE_EXPORT int OperationAlternate::setattro(const Attribute& attr, const PythonObject& field)
01538 {
01539   if (attr.isA(Tags::tag_search))
01540     setSearch(field.getString());
01541   else
01542     return Operation::setattro(attr, field);
01543   return 0;
01544 }
01545 
01546 
01547 DECLARE_EXPORT PyObject* OperationAlternate::addAlternate(PyObject* self, PyObject* args, PyObject* kwdict)
01548 {
01549   try
01550   {
01551     // Pick up the alternate operation
01552     OperationAlternate *altoper = static_cast<OperationAlternate*>(self);
01553     if (!altoper) throw LogicException("Can't add alternates to NULL alternate");
01554 
01555     // Parse the arguments
01556     PyObject *oper = NULL;
01557     int prio = 1;
01558     PyObject *eff_start = NULL;
01559     PyObject *eff_end = NULL;
01560     static const char *kwlist[] = {"operation", "priority", "effective_start", "effective_end", NULL};
01561     if (!PyArg_ParseTupleAndKeywords(args, kwdict,
01562       "O|iOO:addAlternate",
01563       const_cast<char**>(kwlist), &oper, &prio, &eff_start, &eff_end))
01564         return NULL;
01565     if (!PyObject_TypeCheck(oper, Operation::metadata->pythonClass))
01566       throw DataException("alternate operation must be of type operation");
01567     DateRange eff;
01568     if (eff_start)
01569     {
01570       PythonObject d(eff_start);
01571       eff.setStart(d.getDate());
01572     }
01573     if (eff_end)
01574     {
01575       PythonObject d(eff_end);
01576       eff.setEnd(d.getDate());
01577     }
01578 
01579     // Add the alternate
01580     altoper->addAlternate(static_cast<Operation*>(oper), prio, eff);
01581  }
01582   catch(...)
01583   {
01584     PythonType::evalException();
01585     return NULL;
01586   }
01587   return Py_BuildValue("");
01588 }
01589 
01590 
01591 DECLARE_EXPORT PyObject* OperationRouting::getattro(const Attribute& attr)
01592 {
01593   if (attr.isA(Tags::tag_steps))
01594   {
01595     PyObject* result = PyTuple_New(getSubOperations().size());
01596     int count = 0;
01597     for (Operation::Operationlist::const_iterator i = getSubOperations().begin(); i != getSubOperations().end(); ++i)
01598       PyTuple_SetItem(result, count++, PythonObject(*i));
01599     return result;
01600   }
01601   return Operation::getattro(attr);
01602 }
01603 
01604 
01605 PyObject *OperationRouting::addStep(PyObject *self, PyObject *args)
01606 {
01607   try
01608   {
01609     // Pick up the routing operation
01610     OperationRouting *oper = static_cast<OperationRouting*>(self);
01611     if (!oper) throw LogicException("Can't add steps to NULL routing");
01612 
01613     // Parse the arguments
01614     PyObject *steps[4];
01615     for (unsigned int i=0; i<4; ++i) steps[i] = NULL;
01616     if (PyArg_UnpackTuple(args, "addStep", 1, 4, &steps[0], &steps[1], &steps[2], &steps[3]))
01617       for (unsigned int i=0; i<4 && steps[i]; ++i)
01618       {
01619         if (!PyObject_TypeCheck(steps[i], Operation::metadata->pythonClass))
01620           throw DataException("routing steps must be of type operation");
01621         oper->addStepBack(static_cast<Operation*>(steps[i]));
01622       }
01623   }
01624   catch(...)
01625   {
01626     PythonType::evalException();
01627     return NULL;
01628   }
01629   return Py_BuildValue("");
01630 }
01631 
01632 } // end namespace