forecast.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/trunk/modules/forecast/forecast.cpp $
00003   version : $LastChangedRevision: 1315 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2010-07-17 18:08:53 +0200 (Sat, 17 Jul 2010) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007-2010 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 #include "forecast.h"
00029 
00030 namespace module_forecast
00031 {
00032 
00033 const Keyword Forecast::tag_total("total");
00034 const Keyword Forecast::tag_net("net");
00035 const Keyword Forecast::tag_consumed("consumed");
00036 const MetaClass *Forecast::metadata;
00037 const MetaClass *ForecastBucket::metadata;
00038 bool ForecastBucket::DueAtEndOfBucket = false;
00039 
00040 
00041 int Forecast::initialize()
00042 {
00043   // Initialize the metadata
00044   metadata = new MetaClass("demand", "demand_forecast",
00045     Object::createString<Forecast>);
00046 
00047   // Get notified when a calendar is deleted
00048   FunctorStatic<Calendar,Forecast>::connect(SIG_REMOVE);
00049 
00050   // Initialize the Python class
00051   FreppleClass<Forecast,Demand>::getType().addMethod("timeseries", Forecast::timeseries, METH_VARARGS,
00052      "Set the future based on the timeseries of historical data");
00053   return FreppleClass<Forecast,Demand>::initialize();
00054 }
00055 
00056 
00057 int ForecastBucket::initialize()
00058 {
00059   // Initialize the metadata
00060   // No factory method for this class
00061   metadata = new MetaClass("demand", "demand_forecastbucket");
00062 
00063   // Initialize the Python class
00064   // No support for creation
00065   PythonType& x = FreppleClass<ForecastBucket,Demand>::getType();
00066   x.setName("demand_forecastbucket");
00067   x.setDoc("frePPLe forecastbucket");
00068   x.supportgetattro();
00069   x.supportsetattro();
00070   x.supportstr();
00071   x.supportcompare();
00072   x.setBase(Demand::metadata->pythonClass);
00073   x.addMethod("toXML", toXML, METH_VARARGS, "return a XML representation");
00074   const_cast<MetaClass*>(metadata)->pythonClass = x.type_object();
00075   return x.typeReady();
00076 }
00077 
00078 
00079 bool Forecast::callback(Calendar* l, const Signal a)
00080 {
00081   // This function is called when a calendar is about to be deleted.
00082   // If that calendar is being used for a forecast we reset the calendar
00083   // pointer to null.
00084   for (MapOfForecasts::iterator x = ForecastDictionary.begin();
00085     x != ForecastDictionary.end(); ++x)
00086     if (x->second->calptr == l)
00087       // Calendar in use for this forecast
00088       x->second->calptr = NULL;
00089   return true;
00090 }
00091 
00092 
00093 Forecast::~Forecast()
00094 {
00095   // Update the dictionary
00096   for (MapOfForecasts::iterator x=
00097     ForecastDictionary.lower_bound(make_pair(&*getItem(),&*getCustomer()));
00098     x != ForecastDictionary.end(); ++x)
00099     if (x->second == this)
00100     {
00101       ForecastDictionary.erase(x);
00102       break;
00103     }
00104 
00105   // Delete all children demands
00106   for(memberIterator i = beginMember(); i != endMember(); i = beginMember())
00107     delete &*i;
00108 }
00109 
00110 
00111 void Forecast::instantiate()
00112 {
00113   if (!calptr) throw DataException("Missing forecast calendar");
00114 
00115   // Create a demand for every bucket. The weight value depends on the
00116   // calendar type: double, integer, bool or other
00117   const CalendarDouble* c = dynamic_cast<const CalendarDouble*>(calptr);
00118   ForecastBucket* prev = NULL;
00119   Date prevDate;
00120   double prevValue(0.0);
00121   if (c)
00122     // Double calendar
00123     for (CalendarDouble::EventIterator i(c); i.getDate()<=Date::infiniteFuture; ++i)
00124     {
00125       if ((prevDate || i.getDate() == Date::infiniteFuture) && prevValue > 0.0)
00126       {
00127         prev = new ForecastBucket
00128           (this, prevDate, i.getDate(), prevValue, prev);
00129         Demand::add(prev);
00130       }
00131       if (i.getDate() == Date::infiniteFuture) break;
00132       prevDate = i.getDate();
00133       prevValue = i.getValue();
00134     }
00135   else
00136   {
00137     const CalendarInt* c = dynamic_cast<const CalendarInt*>(calptr);
00138     if (c)
00139       // Integer calendar
00140       for (CalendarInt::EventIterator i(c); i.getDate()<=Date::infiniteFuture; ++i)
00141       {
00142         if ((prevDate || i.getDate() == Date::infiniteFuture) && prevValue > 0)
00143         {
00144           prev = new ForecastBucket
00145             (this, prevDate, i.getDate(), prevValue, prev);
00146           Demand::add(prev);
00147         }
00148         if (i.getDate() == Date::infiniteFuture) break;
00149         prevDate = i.getDate();
00150         prevValue = static_cast<double>(i.getValue());
00151       }
00152     else
00153     {
00154       const CalendarBool* c = dynamic_cast<const CalendarBool*>(calptr);
00155       bool prevValueBool = false;
00156       if (c)
00157         // Boolean calendar
00158         for (CalendarBool::EventIterator i(c); true; ++i)
00159         {
00160           if ((prevDate || i.getDate() == Date::infiniteFuture) && prevValueBool)
00161           {
00162             prev = new ForecastBucket
00163                 (this, prevDate, i.getDate(), 1.0, prev);
00164             Demand::add(prev);
00165             }
00166           if (i.getDate() == Date::infiniteFuture) break;
00167           prevDate = i.getDate();
00168           prevValueBool = i.getValue();
00169         }
00170       else
00171       {
00172         // Other calendar
00173         for (Calendar::EventIterator i(calptr); true; ++i)
00174         {
00175           if (prevDate || i.getDate() == Date::infiniteFuture)
00176           {
00177             prev = new ForecastBucket(this, prevDate, i.getDate(), 1.0, prev);
00178             Demand::add(prev);
00179             if (i.getDate() == Date::infiniteFuture) break;
00180           }
00181           prevDate = i.getDate();
00182         }
00183       }
00184     }
00185   }
00186 }
00187 
00188 
00189 void Forecast::setDiscrete(const bool b)
00190 {
00191   // Update the flag
00192   discrete = b;
00193 
00194   // Round down any forecast demands that may already exist.
00195   if (discrete)
00196     for (memberIterator m = beginMember(); m!=endMember(); ++m)
00197       m->setQuantity(floor(m->getQuantity()));
00198 }
00199 
00200 
00201 void Forecast::setTotalQuantity(const DateRange& d, double f)
00202 {
00203   // Initialize, if not done yet
00204   if (!isGroup()) instantiate();
00205 
00206   // Find all forecast demands, and sum their weights
00207   double weights = 0.0;
00208   for (memberIterator m = beginMember(); m!=endMember(); ++m)
00209   {
00210     ForecastBucket* x = dynamic_cast<ForecastBucket*>(&*m);
00211     if (!x)
00212       throw DataException("Invalid subdemand of forecast '" + getName() +"'");
00213     if (d.intersect(x->getDueRange()))
00214     {
00215       // Bucket intersects with daterange
00216       if (!d.getDuration())
00217       {
00218         // Single date provided. Update that one bucket.
00219         x->setTotal(f);
00220         return;
00221       }
00222       weights += x->getWeight() * static_cast<long>(x->getDueRange().overlap(d));
00223     }
00224   }
00225 
00226   // Expect to find at least one non-zero weight...
00227   if (!weights)
00228     throw DataException("No valid forecast date in range "
00229       + string(d) + " of forecast '" + getName() +"'");
00230 
00231   // Update the forecast quantity, respecting the weights
00232   f /= weights;
00233   double carryover = 0.0;
00234   for (memberIterator m = beginMember(); m!=endMember(); ++m)
00235   {
00236     ForecastBucket* x = dynamic_cast<ForecastBucket*>(&*m);
00237     if (d.intersect(x->getDueRange()))
00238     {
00239       // Bucket intersects with daterange
00240       TimePeriod o = x->getDueRange().overlap(d);
00241       double percent = x->getWeight() * static_cast<long>(o);
00242       if (getDiscrete())
00243       {
00244         // Rounding to discrete numbers
00245         carryover += f * percent;
00246         int intdelta = static_cast<int>(ceil(carryover - 0.5));
00247         carryover -= intdelta;
00248         if (o < x->getDueRange().getDuration())
00249           // The bucket is only partially updated
00250           x->incTotal(static_cast<double>(intdelta));
00251         else
00252           // The bucket is completely updated
00253           x->setTotal(static_cast<double>(intdelta));
00254       }
00255       else
00256       {
00257         // No rounding
00258         if (o < x->getDueRange().getDuration())
00259           // The bucket is only partially updated
00260           x->incTotal(f * percent);
00261         else
00262           // The bucket is completely updated
00263           x->setTotal(f * percent);
00264       }
00265     }
00266   }
00267 }
00268 
00269 
00270 void Forecast::writeElement(XMLOutput *o, const Keyword &tag, mode m) const
00271 {
00272   // Writing a reference
00273   if (m == REFERENCE)
00274   {
00275     o->writeElement
00276     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00277     return;
00278   }
00279 
00280   // Write the complete object
00281   if (m != NOHEADER) o->BeginObject
00282     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00283 
00284   o->writeElement(Tags::tag_item, &*getItem());
00285   o->writeElement(Tags::tag_operation, &*getOperation());
00286   if (getPriority()) o->writeElement(Tags::tag_priority, getPriority());
00287   o->writeElement(Tags::tag_calendar, calptr);
00288   if (!getDiscrete()) o->writeElement(Tags::tag_discrete, getDiscrete());
00289 
00290   // Write all entries
00291   o->BeginObject (Tags::tag_buckets);
00292   for (memberIterator i = beginMember(); i != endMember(); ++i)
00293   {
00294     ForecastBucket* f = dynamic_cast<ForecastBucket*>(&*i);
00295     o->BeginObject(Tags::tag_bucket, Tags::tag_start, string(f->getDue()));
00296     o->writeElement(tag_total, f->getTotal());
00297     o->writeElement(Tags::tag_quantity, f->getQuantity());
00298     o->writeElement(tag_consumed, f->getConsumed());
00299     o->EndObject(Tags::tag_bucket);
00300   }
00301   o->EndObject(Tags::tag_buckets);
00302 
00303   o->EndObject(tag);
00304 }
00305 
00306 
00307 void Forecast::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00308 {
00309   // While reading forecast buckets, we use the userarea field on the input
00310   // to cache the data. The temporary object is deleted when the bucket
00311   // tag is closed.
00312   if (pAttr.isA(Tags::tag_calendar))
00313   {
00314     Calendar *b = dynamic_cast<Calendar*>(pIn.getPreviousObject());
00315     if (b) setCalendar(b);
00316     else throw LogicException("Incorrect object type during read operation");
00317   }
00318   else if (pAttr.isA(Tags::tag_discrete))
00319     setDiscrete(pElement.getBool());
00320   else if (pAttr.isA(Tags::tag_bucket))
00321   {
00322     pair<DateRange,double> *d =
00323       static_cast< pair<DateRange,double>* >(pIn.getUserArea());
00324     if (d)
00325     {
00326       // Update the forecast quantities
00327       setTotalQuantity(d->first, d->second);
00328       // Clear the read buffer
00329       d->first.setStart(Date());
00330       d->first.setEnd(Date());
00331       d->second = 0;
00332     }
00333   }
00334   else if (pIn.getParentElement().first.isA(Tags::tag_bucket))
00335   {
00336     pair<DateRange,double> *d =
00337       static_cast< pair<DateRange,double>* >(pIn.getUserArea());
00338     if (pAttr.isA(tag_total))
00339     {
00340       if (d) d->second = pElement.getDouble();
00341       else pIn.setUserArea(
00342         new pair<DateRange,double>(DateRange(),pElement.getDouble())
00343         );
00344     }
00345     else if (pAttr.isA(Tags::tag_start))
00346     {
00347       Date x = pElement.getDate();
00348       if (d)
00349       {
00350         if (!d->first.getStart()) d->first.setStartAndEnd(x,x);
00351         else d->first.setStart(x);
00352       }
00353       else pIn.setUserArea(new pair<DateRange,double>(DateRange(x,x),0));
00354     }
00355     else if (pAttr.isA(Tags::tag_end))
00356     {
00357       Date x = pElement.getDate();
00358       if (d)
00359       {
00360         if (!d->first.getStart()) d->first.setStartAndEnd(x,x);
00361         else d->first.setEnd(x);
00362       }
00363       else pIn.setUserArea(new pair<DateRange,double>(DateRange(x,x),0));
00364     }
00365   }
00366   else
00367     Demand::endElement(pIn, pAttr, pElement);
00368 
00369   if (pIn.isObjectEnd())
00370   {
00371     // Delete dynamically allocated temporary read object
00372     if (pIn.getUserArea())
00373       delete static_cast< pair<DateRange,double>* >(pIn.getUserArea());
00374   }
00375 }
00376 
00377 
00378 void Forecast::beginElement(XMLInput& pIn, const Attribute& pAttr)
00379 {
00380   if (pAttr.isA(Tags::tag_calendar))
00381     pIn.readto( Calendar::reader(Calendar::metadata, pIn.getAttributes()) );
00382   else
00383     Demand::beginElement(pIn, pAttr);
00384 }
00385 
00386 
00387 void Forecast::setCalendar(Calendar* c)
00388 {
00389   if (isGroup())
00390     throw DataException(
00391       "Changing the calendar of an initialized forecast isn't allowed");
00392   calptr = c;
00393 }
00394 
00395 
00396 void Forecast::setItem(Item* i)
00397 {
00398   // No change
00399   if (getItem() == i) return;
00400 
00401   // Update the dictionary
00402   for (MapOfForecasts::iterator x =
00403     ForecastDictionary.lower_bound(make_pair(
00404       &*getItem(),&*getCustomer()
00405       ));
00406     x != ForecastDictionary.end(); ++x)
00407     if (x->second == this)
00408     {
00409       ForecastDictionary.erase(x);
00410       break;
00411     }
00412   ForecastDictionary.insert(make_pair(make_pair(i,&*getCustomer()),this));
00413 
00414   // Update data field
00415   Demand::setItem(i);
00416 
00417   // Update the item for all buckets/subdemands
00418   for (memberIterator m = beginMember(); m!=endMember(); ++m)
00419     m->setItem(i);
00420 }
00421 
00422 
00423 void Forecast::setCustomer(Customer* i)
00424 {
00425   // No change
00426   if (getCustomer() == i) return;
00427 
00428   // Update the dictionary
00429   for (MapOfForecasts::iterator x =
00430     ForecastDictionary.lower_bound(make_pair(
00431       getItem(), getCustomer()
00432       ));
00433     x != ForecastDictionary.end(); ++x)
00434     if (x->second == this)
00435     {
00436       ForecastDictionary.erase(x);
00437       break;
00438     }
00439   ForecastDictionary.insert(make_pair(make_pair(&*getItem(),i),this));
00440 
00441   // Update data field
00442   Demand::setCustomer(i);
00443 
00444   // Update the customer for all buckets/subdemands
00445   for (memberIterator m = beginMember(); m!=endMember(); ++m)
00446     m->setCustomer(i);
00447 }
00448 
00449 
00450 void Forecast::setMaxLateness(TimePeriod i)
00451 {
00452   Demand::setMaxLateness(i);
00453   // Update the maximum lateness for all buckets/subdemands
00454   for (memberIterator m = beginMember(); m!=endMember(); ++m)
00455     m->setMaxLateness(i);
00456 }
00457 
00458 
00459 void Forecast::setMinShipment(double i)
00460 {
00461   Demand::setMinShipment(i);
00462   // Update the minimum shipment for all buckets/subdemands
00463   for (memberIterator m = beginMember(); m!=endMember(); ++m)
00464     m->setMinShipment(i);
00465 }
00466 
00467 
00468 void Forecast::setPriority(int i)
00469 {
00470   Demand::setPriority(i);
00471   // Update the priority for all buckets/subdemands
00472   for (memberIterator m = beginMember(); m!=endMember(); ++m)
00473     m->setPriority(i);
00474 }
00475 
00476 
00477 void Forecast::setOperation(Operation *o)
00478 {
00479   Demand::setOperation(o);
00480   // Update the priority for all buckets/subdemands
00481   for (memberIterator m = beginMember(); m!=endMember(); ++m)
00482     m->setOperation(o);
00483 }
00484 
00485 }       // end namespace

Documentation generated for frePPLe by  doxygen