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