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