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

Documentation generated for frePPLe by  doxygen