forecast.cpp

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