00001 /*************************************************************************** 00002 file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/trunk/modules/forecast/forecast.h $ 00003 version : $LastChangedRevision: 963 $ $LastChangedBy: jdetaeye $ 00004 date : $LastChangedDate: 2009-05-24 14:31:17 +0200 (Sun, 24 May 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA * 00024 * * 00025 ***************************************************************************/ 00026 00027 /** @file forecast.h 00028 * @brief Header file for the module forecast. 00029 * 00030 * @namespace module_forecast 00031 * @brief Module for representing forecast. 00032 * 00033 * The forecast module provides the following functionality: 00034 * 00035 * - A <b>new demand type</b> to model forecasts.<br> 00036 * A forecast demand is bucketized. A demand is automatically 00037 * created for each time bucket.<br> 00038 * A calendar is used to define the time buckets to be used. 00039 * 00040 * - Functionality for <b>distributing / profiling</b> forecast numbers 00041 * into time buckets used for planning.<br> 00042 * This functionality is typically used to translate between the time 00043 * granularity of the sales department (which creates a sales forecast 00044 * per e.g. calendar month) and the manufacturing department (which 00045 * creates manufacturing and procurement plans in weekly or daily buckets 00046 * ).<br> 00047 * Another usage is to model a delivery date profile of the customers. 00048 * Each bucket has a weight that is used to model situations where the 00049 * demand is not evenly spread across buckets: e.g. when more orders are 00050 * expected due on a monday than on a friday, or when a peak of orders is 00051 * expected for delivery near the end of a month. 00052 * 00053 * - A solver for <b>netting orders from the forecast</b>.<br> 00054 * As customer orders are being received they need to be deducted from 00055 * the forecast to avoid double-counting demand.<br> 00056 * The netting solver will for each order search for a matching forecast 00057 * and reduce the remaining net quantity of the forecast. 00058 * 00059 * - A forecasting algorithm to <b>extrapolate historical demand data to 00060 * the future</b>.<br> 00061 * The following classical forecasting methods are implemented: 00062 * - single exponential smoothing, which is applicable for 00063 * constant demands . 00064 * - double exponential smoothing, which is applicable for trended 00065 * demands. 00066 * - moving average, which is applicable when there is little demand 00067 * history to rely on. 00068 * The forecast method giving the smallest mean absolute deviation (aka 00069 * "mad"-error) will be automatically picked to produce the forecast.<br> 00070 * The algorithm will automatically tune the parameters for the 00071 * forecasting methods (i.e. alfa for the single exponential smoothing, 00072 * or alfa and gamma for the double exponential smoothing) to their 00073 * optimal value. The user can specify minimum and maximum boundaries 00074 * for the parameters and the maximum allowed number of iterations 00075 * for the algorithm. 00076 * 00077 * The XML schema extension enabled by this module is (see mod_forecast.xsd): 00078 * <PRE> 00079 * <!-- Define the forecast type --> 00080 * <xsd:complexType name="demand_forecast"> 00081 * <xsd:complexContent> 00082 * <xsd:extension base="demand"> 00083 * <xsd:choice minOccurs="0" maxOccurs="unbounded"> 00084 * <xsd:element name="calendar" type="calendar" /> 00085 * <xsd:element name="discrete" type="xsd:boolean" /> 00086 * <xsd:element name="buckets"> 00087 * <xsd:complexType> 00088 * <xsd:choice minOccurs="0" maxOccurs="unbounded"> 00089 * <xsd:element name="bucket"> 00090 * <xsd:complexType> 00091 * <xsd:all> 00092 * <xsd:element name="total" type="positiveDouble" 00093 * minOccurs="0" /> 00094 * <xsd:element name="net" type="positiveDouble" 00095 * minOccurs="0" /> 00096 * <xsd:element name="consumed" type="positiveDouble" 00097 * minOccurs="0" /> 00098 * <xsd:element name="start" type="xsd:dateTime" 00099 * minOccurs="0"/> 00100 * <xsd:element name="end" type="xsd:dateTime" 00101 * minOccurs="0"/> 00102 * </xsd:all> 00103 * <xsd:attribute name="total" type="positiveDouble" /> 00104 * <xsd:attribute name="net" type="positiveDouble" /> 00105 * <xsd:attribute name="consumed" type="positiveDouble" /> 00106 * <xsd:attribute name="start" type="xsd:dateTime" /> 00107 * <xsd:attribute name="end" type="xsd:dateTime" /> 00108 * </xsd:complexType> 00109 * </xsd:element> 00110 * </xsd:choice> 00111 * </xsd:complexType> 00112 * </xsd:element> 00113 * </xsd:choice> 00114 * <xsd:attribute name="discrete" type="xsd:boolean" /> 00115 * </xsd:extension> 00116 * </xsd:complexContent> 00117 * </xsd:complexType> 00118 * 00119 * <!-- Define the netting solver. --> 00120 * <xsd:complexType name="solver_forecast"> 00121 * <xsd:complexContent> 00122 * <xsd:extension base="solver"> 00123 * <xsd:choice minOccurs="0" maxOccurs="unbounded"> 00124 * <xsd:element name="loglevel" type="loglevel" /> 00125 * </xsd:choice> 00126 * </xsd:extension> 00127 * </xsd:complexContent> 00128 * </xsd:complexType> 00129 * </PRE> 00130 * 00131 * The module support the following configuration parameters: 00132 * 00133 * - Net_CustomerThenItemHierarchy:<br> 00134 * As part of the forecast netting a demand is assiociated with a certain 00135 * forecast. When no matching forecast is found for the customer and item 00136 * of the demand, frePPLe looks for forecast at higher level customers 00137 * and items.<br> 00138 * This flag allows us to control whether we first search the customer 00139 * hierarchy and then the item hierarchy, or the other way around.<br> 00140 * The default value is true, ie search higher customer levels before 00141 * searching higher levels of the item. 00142 * 00143 * - Net_MatchUsingDeliveryOperation:<br> 00144 * Specifies whether or not a demand and a forecast require to have the 00145 * same delivery operation to be a match.<br> 00146 * The default value is true. 00147 * 00148 * - Net_NetEarly:<br> 00149 * Defines how much time before the due date of an order we are allowed 00150 * to search for a forecast bucket to net from.<br> 00151 * The default value is 0, meaning that we can net only from the bucket 00152 * where the demand is due. 00153 * 00154 * - Net_NetLate:<br> 00155 * Defines how much time after the due date of an order we are allowed 00156 * to search for a forecast bucket to net from.<br> 00157 * The default value is 0, meaning that we can net only from the bucket 00158 * where the demand is due. 00159 * 00160 * - Forecast_Iterations:<br> 00161 * Specifies the maximum number of iterations allowed for a forecast 00162 * method to tune its parameters.<br> 00163 * Only positive values are allowed and the default value is 10.<br> 00164 * Set the parameter to 1 to disable the tuning and generate a forecast 00165 * based on the user-supplied parameters. 00166 * 00167 * - Forecast_madAlfa:<br> 00168 * Specifies how the MAD forecast error is weighted for different time 00169 * buckets. The MAD value in the most recent bucket is 1.0, and the 00170 * weight decreases exponentially for earlier buckets.<br> 00171 * Acceptable values are in the interval 0.5 and 1.0, and the default 00172 * is 0.95. 00173 * 00174 * - Forecast_Skip:<br> 00175 * Specifies the number of time series values used to initialize the 00176 * forecasting method. The forecast error in these bucket isn't counted. 00177 * 00178 * - Forecast_MovingAverage.buckets<br> 00179 * This parameter controls the number of buckets to be averaged by the 00180 * moving average forecast method. 00181 * 00182 * - Forecast_SingleExponential.initialAlfa,<br> 00183 * Forecast_SingleExponential.minAlfa,<br> 00184 * Forecast_SingleExponential.maxAlfa:<br> 00185 * Specifies the initial value and the allowed range of the smoothing 00186 * parameter in the single exponential forecasting method.<br> 00187 * The allowed range is between 0 and 1. Values lower than about 0.05 00188 * are not advisible. 00189 * 00190 * - Forecast_DoubleExponential.initialAlfa,<br> 00191 * Forecast_DoubleExponential.minAlfa,<br> 00192 * Forecast_DoubleExponential.maxAlfa:<br> 00193 * Specifies the initial value and the allowed range of the smoothing 00194 * parameter in the double exponential forecasting method.<br> 00195 * The allowed range is between 0 and 1. Values lower than about 0.05 00196 * are not advisible. 00197 * 00198 * - Forecast_DoubleExponential.initialGamma,<br> 00199 * Forecast_DoubleExponential.minGamma,<br> 00200 * Forecast_DoubleExponential.maxGamma:<br> 00201 * Specifies the initial value and the allowed range of the trend 00202 * smoothing parameter in the double exponential forecasting method.<br> 00203 * The allowed range is between 0 and 1. 00204 */ 00205 00206 #ifndef FORECAST_H 00207 #define FORECAST_H 00208 00209 #include "frepple.h" 00210 using namespace frepple; 00211 00212 namespace module_forecast 00213 { 00214 00215 00216 /** Initialization routine for the library. */ 00217 MODULE_EXPORT const char* initialize(const CommandLoadLibrary::ParameterList&); 00218 00219 /** @brief This class represents a bucketized demand signal. 00220 * 00221 * The forecast object defines the item and priority of the demands.<br> 00222 * A calendar (of type void, double, integer or boolean) divides the time horizon 00223 * in individual time buckets. The calendar value is used to assign priorities 00224 * to the time buckets.<br> 00225 * The class basically works as an interface for a hierarchy of demands, where the 00226 * lower level demands represent forecasting time buckets. 00227 */ 00228 class Forecast : public Demand 00229 { 00230 friend class ForecastSolver; 00231 public: 00232 00233 static const Keyword tag_total; 00234 static const Keyword tag_net; 00235 static const Keyword tag_consumed; 00236 00237 /** @brief Abstract base class for all forecasting methods. */ 00238 class ForecastMethod 00239 { 00240 public: 00241 /** Forecast evaluation. */ 00242 virtual double generateForecast 00243 (Forecast*, const double[], unsigned int, const double[], bool) = 0; 00244 00245 /** This method is called when this forecast method has generated the 00246 * lowest forecast error and now needs to set the forecast values. 00247 */ 00248 virtual void applyForecast 00249 (Forecast*, const Date[], unsigned int, bool) = 0; 00250 00251 /** The name of the method. */ 00252 virtual string getName() = 0; 00253 }; 00254 00255 00256 /** @brief A class to calculate a forecast based on a moving average. */ 00257 class MovingAverage : public ForecastMethod 00258 { 00259 private: 00260 /** Number of smoothed buckets. */ 00261 static unsigned int defaultbuckets; 00262 00263 /** Number of buckets to average. */ 00264 unsigned int buckets; 00265 00266 /** Calculated average.<br> 00267 * Used to carry results between the evaluation and applying of the forecast. 00268 */ 00269 double avg; 00270 00271 public: 00272 /** Constructor. */ 00273 MovingAverage(int i = defaultbuckets) : buckets(i), avg(0) 00274 { 00275 if (i < 1) 00276 throw DataException("Moving average needs to smooth over at least 1 bucket"); 00277 } 00278 00279 /** Forecast evaluation. */ 00280 double generateForecast(Forecast* fcst, const double history[], 00281 unsigned int count, const double madWeight[], bool debug); 00282 00283 /** Forecast value updating. */ 00284 void applyForecast(Forecast*, const Date[], unsigned int, bool); 00285 00286 /** Update the initial value for the alfa parameter. */ 00287 static void setDefaultBuckets(int x) 00288 { 00289 if (x < 1) 00290 throw DataException("Parameter MovingAverage.buckets needs to smooth over at least 1 bucket"); 00291 defaultbuckets = x; 00292 } 00293 00294 string getName() {return "moving average";} 00295 }; 00296 00297 /** @brief A class to perform single exponential smoothing on a time series. */ 00298 class SingleExponential : public ForecastMethod 00299 { 00300 private: 00301 /** Smoothing constant. */ 00302 double alfa; 00303 00304 /** Default initial alfa value.<br> 00305 * The default value is 0.2. 00306 */ 00307 static double initial_alfa; 00308 00309 /** Lower limit on the alfa parameter.<br> 00310 * The default value is 0. 00311 **/ 00312 static double min_alfa; 00313 00314 /** Upper limit on the alfa parameter.<br> 00315 * The default value is 1. 00316 **/ 00317 static double max_alfa; 00318 00319 /** Smoothed result.<br> 00320 * Used to carry results between the evaluation and applying of the forecast. 00321 */ 00322 double f_i; 00323 00324 public: 00325 /** Constructor. */ 00326 SingleExponential(double a = initial_alfa) : alfa(a), f_i(0) 00327 { 00328 if (alfa < min_alfa) alfa = min_alfa; 00329 if (alfa > max_alfa) alfa = max_alfa; 00330 } 00331 00332 /** Forecast evaluation. */ 00333 double generateForecast(Forecast* fcst, const double history[], 00334 unsigned int count, const double madWeight[], bool debug); 00335 00336 /** Forecast value updating. */ 00337 void applyForecast(Forecast*, const Date[], unsigned int, bool); 00338 00339 /** Update the initial value for the alfa parameter. */ 00340 static void setInitialAlfa(double x) 00341 { 00342 if (x<0 || x>1.0) throw DataException( 00343 "Parameter SingleExponential.initialAlfa must be between 0 and 1"); 00344 initial_alfa = x; 00345 } 00346 00347 /** Update the minimum value for the alfa parameter. */ 00348 static void setMinAlfa(double x) 00349 { 00350 if (x<0 || x>1.0) throw DataException( 00351 "Parameter SingleExponential.minAlfa must be between 0 and 1"); 00352 min_alfa = x; 00353 } 00354 00355 /** Update the maximum value for the alfa parameter. */ 00356 static void setMaxAlfa(double x) 00357 { 00358 if (x<0 || x>1.0) throw DataException( 00359 "Parameter SingleExponential.maxAlfa must be between 0 and 1"); 00360 max_alfa = x; 00361 } 00362 00363 string getName() {return "single exponential";} 00364 }; 00365 00366 /** @brief A class to perform double exponential smoothing on a time 00367 * series. 00368 */ 00369 class DoubleExponential : public ForecastMethod 00370 { 00371 private: 00372 /** Smoothing constant. */ 00373 double alfa; 00374 00375 /** Default initial alfa value.<br> 00376 * The default value is 0.2. 00377 */ 00378 static double initial_alfa; 00379 00380 /** Lower limit on the alfa parameter.<br> 00381 * The default value is 0. 00382 **/ 00383 static double min_alfa; 00384 00385 /** Upper limit on the alfa parameter.<br> 00386 * The default value is 1. 00387 **/ 00388 static double max_alfa; 00389 00390 /** Trend smoothing constant. */ 00391 double gamma; 00392 00393 /** Default initial gamma value.<br> 00394 * The default value is 0.05. 00395 */ 00396 static double initial_gamma; 00397 00398 /** Lower limit on the gamma parameter.<br> 00399 * The default value is 0.05. 00400 **/ 00401 static double min_gamma; 00402 00403 /** Upper limit on the gamma parameter.<br> 00404 * The default value is 1. 00405 **/ 00406 static double max_gamma; 00407 00408 /** Smoothed result.<br> 00409 * Used to carry results between the evaluation and applying of the forecast. 00410 */ 00411 double trend_i; 00412 00413 /** Smoothed result.<br> 00414 * Used to carry results between the evaluation and applying of the forecast. 00415 */ 00416 double constant_i; 00417 00418 public: 00419 /** Constructor. */ 00420 DoubleExponential(double a = initial_alfa, double g = initial_gamma) 00421 : alfa(a), gamma(g), trend_i(0), constant_i(0) {} 00422 00423 /** Forecast evaluation. */ 00424 double generateForecast(Forecast* fcst, const double history[], 00425 unsigned int count, const double madWeight[], bool debug); 00426 00427 /** Forecast value updating. */ 00428 void applyForecast(Forecast*, const Date[], unsigned int, bool); 00429 00430 /** Update the initial value for the alfa parameter. */ 00431 static void setInitialAlfa(double x) 00432 { 00433 if (x<0 || x>1.0) throw DataException( 00434 "Parameter DoubleExponential.initialAlfa must be between 0 and 1"); 00435 initial_alfa = x; 00436 } 00437 00438 /** Update the minimum value for the alfa parameter. */ 00439 static void setMinAlfa(double x) 00440 { 00441 if (x<0 || x>1.0) throw DataException( 00442 "Parameter DoubleExponential.minAlfa must be between 0 and 1"); 00443 min_alfa = x; 00444 } 00445 00446 /** Update the maximum value for the alfa parameter. */ 00447 static void setMaxAlfa(double x) 00448 { 00449 if (x<0 || x>1.0) throw DataException( 00450 "Parameter DoubleExponential.maxAlfa must be between 0 and 1"); 00451 max_alfa = x; 00452 } 00453 00454 /** Update the initial value for the alfa parameter.<br> 00455 * The default value is 0.05. <br> 00456 * Setting this parameter to too low a value can create false 00457 * positives: the double exponential method is selected for a time 00458 * series without a real trend. A single exponential is better for 00459 * such cases. 00460 */ 00461 static void setInitialGamma(double x) 00462 { 00463 if (x<0 || x>1.0) throw DataException( 00464 "Parameter DoubleExponential.initialGamma must be between 0 and 1"); 00465 initial_gamma = x; 00466 } 00467 00468 /** Update the minimum value for the alfa parameter. */ 00469 static void setMinGamma(double x) 00470 { 00471 if (x<0 || x>1.0) throw DataException( 00472 "Parameter DoubleExponential.minGamma must be between 0 and 1"); 00473 min_gamma = x; 00474 } 00475 00476 /** Update the maximum value for the alfa parameter. */ 00477 static void setMaxGamma(double x) 00478 { 00479 if (x<0 || x>1.0) throw DataException( 00480 "Parameter DoubleExponential.maxGamma must be between 0 and 1"); 00481 max_gamma = x; 00482 } 00483 00484 string getName() {return "double exponential";} 00485 }; 00486 00487 public: 00488 /** Constructor. */ 00489 explicit Forecast(const string& nm) 00490 : Demand(nm), calptr(NULL), discrete(true) {} 00491 00492 /** Destructor. */ 00493 ~Forecast(); 00494 00495 /** Updates the quantity of the forecast. This method is empty. */ 00496 virtual void setQuantity(double f) 00497 {throw DataException("Can't set quantity of a forecast");} 00498 00499 /** Update the forecast quantity.<br> 00500 * The forecast quantity will be distributed equally among the buckets 00501 * available between the two dates, taking into account also the bucket 00502 * weights.<br> 00503 * The logic applied is briefly summarized as follows: 00504 * - If the daterange has its start and end dates equal, we find the 00505 * matching forecast bucket and update the quantity. 00506 * - Otherwise the quantity is distributed among all intersecting 00507 * forecast buckets. This distribution is considering the weigth of 00508 * the bucket and the time duration of the bucket.<br> 00509 * The bucket weight is the value specified on the calendar.<br> 00510 * If a forecast bucket only partially overlaps with the daterange 00511 * only the overlapping time is used as the duration. 00512 * - If only buckets with zero weigth are found in the daterange a 00513 * dataexception is thrown. It indicates a situation where forecast 00514 * is specified for a date where no values are allowed. 00515 */ 00516 virtual void setTotalQuantity(const DateRange& , double); 00517 00518 void writeElement(XMLOutput*, const Keyword&, mode=DEFAULT) const; 00519 void endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement); 00520 void beginElement(XMLInput& pIn, const Attribute& pAttr); 00521 00522 /** Returns whether fractional forecasts are allowed or not.<br> 00523 * The default is true. 00524 */ 00525 bool getDiscrete() const {return discrete;} 00526 00527 /** Updates forecast discreteness flag. */ 00528 void setDiscrete(const bool b); 00529 00530 /** Update the item to be planned. */ 00531 virtual void setItem(Item*); 00532 00533 /** Update the customer. */ 00534 virtual void setCustomer(Customer*); 00535 00536 /* Update the maximum allowed lateness for planning. */ 00537 void setMaxLateness(TimePeriod); 00538 00539 /* Update the minumum allowed shipment quantity for planning. */ 00540 void setMinShipment(double); 00541 00542 /** Specify a bucket calendar for the forecast. Once forecasted 00543 * quantities have been entered for the forecast, the calendar 00544 * can't be updated any more. */ 00545 virtual void setCalendar(Calendar*); 00546 00547 /** Returns a reference to the calendar used for this forecast. */ 00548 Calendar* getCalendar() const {return calptr;} 00549 00550 /** Generate a forecast value based on historical demand data.<br> 00551 * This method will call the different forecasting methods and select the 00552 * method with the lowest mad-error.<br> 00553 * It then asks the selected forecast method to generate a value for 00554 * each of the time buckets passed. 00555 */ 00556 void generateFutureValues 00557 (const double[], unsigned int, const Date[], unsigned int, bool=false); 00558 00559 /** Updates the due date of the demand. Lower numbers indicate a 00560 * higher priority level. The method also updates the priority 00561 * in all buckets. 00562 */ 00563 virtual void setPriority(int); 00564 00565 /** Updates the operation being used to plan the demands. */ 00566 virtual void setOperation(Operation *); 00567 00568 /** Updates the due date of the demand. */ 00569 virtual void setDue(const Date& d) 00570 {throw DataException("Can't set due date of a forecast");} 00571 00572 virtual const MetaClass& getType() const {return *metadata;} 00573 static const MetaClass *metadata; 00574 virtual size_t getSize() const 00575 { 00576 return sizeof(Forecast) + Demand::extrasize() 00577 + 6 * sizeof(void*); // Approx. size of an entry in forecast dictionary 00578 } 00579 00580 /** Updates the value of the Customer_Then_Item_Hierarchy module 00581 * parameter. */ 00582 static void setCustomerThenItemHierarchy(bool b) 00583 {Customer_Then_Item_Hierarchy = b;} 00584 00585 /** Returns the value of the Customer_Then_Item_Hierarchy module 00586 * parameter. */ 00587 static bool getCustomerThenItemHierarchy() 00588 {return Customer_Then_Item_Hierarchy;} 00589 00590 /** Updates the value of the Match_Using_Delivery_Operation module 00591 * parameter. */ 00592 static void setMatchUsingDeliveryOperation(bool b) 00593 {Match_Using_Delivery_Operation = b;} 00594 00595 /** Returns the value of the Match_Using_Delivery_Operation module 00596 * parameter. */ 00597 static bool getMatchUsingDeliveryOperation() 00598 {return Match_Using_Delivery_Operation;} 00599 00600 /** Updates the value of the Net_Early module parameter. */ 00601 static void setNetEarly(TimePeriod t) {Net_Early = t;} 00602 00603 /** Returns the value of the Net_Early module parameter. */ 00604 static TimePeriod getNetEarly() {return Net_Early;} 00605 00606 /** Updates the value of the Net_Late module parameter. */ 00607 static void setNetLate(TimePeriod t) {Net_Late = t;} 00608 00609 /** Returns the value of the Net_Late module parameter. */ 00610 static TimePeriod getNetLate() {return Net_Late;} 00611 00612 /** Updates the value of the Forecast.madAlfa module parameter. */ 00613 static void setForecastMadAlfa(double t) 00614 { 00615 if (t<=0.5 || t>1.0) throw DataException( 00616 "Parameter Forecast.madAlfa must be between 0.5 and 1.0" 00617 ); 00618 Forecast_MadAlfa = t; 00619 } 00620 00621 /** Returns the value of the Forecast_Iterations module parameter. */ 00622 static double getForecastMadAlfa() {return Forecast_MadAlfa;} 00623 00624 /** Updates the value of the Forecast_Iterations module parameter. */ 00625 static void setForecastIterations(unsigned long t) 00626 { 00627 if (t<=0) throw DataException( 00628 "Parameter Forecast.Iterations must be bigger than 0" 00629 ); 00630 Forecast_Iterations = t; 00631 } 00632 00633 /** Returns the value of the Forecast_Iterations module parameter. */ 00634 static unsigned long getForecastIterations() {return Forecast_Iterations;} 00635 00636 /** Updates the value of the Forecast_Skip module parameter. */ 00637 static void setForecastSkip(unsigned int t) 00638 { 00639 if (t<0) throw DataException( 00640 "Parameter Forecast.Skip must be bigger than or equal to 0" 00641 ); 00642 Forecast_Skip = t; 00643 } 00644 00645 /** Return the number of timeseries values used to initialize the 00646 * algorithm. The forecast error is not counted for these buckets. 00647 */ 00648 static unsigned int getForecastSkip() { return Forecast_Skip; } 00649 00650 /** A data type to maintain a dictionary of all forecasts. */ 00651 typedef multimap < pair<const Item*, const Customer*>, Forecast* > MapOfForecasts; 00652 00653 /** Callback function, used for prevent a calendar from being deleted when it 00654 * is used for an uninitialized forecast. */ 00655 static bool callback(Calendar*, const Signal); 00656 00657 /** Return a reference to a dictionary with all forecast objects. */ 00658 static const MapOfForecasts& getForecasts() {return ForecastDictionary;} 00659 00660 private: 00661 /** Initializion of a forecast.<br> 00662 * It creates demands for each bucket of the calendar. 00663 */ 00664 void initialize(); 00665 00666 /** A void calendar to define the time buckets. */ 00667 Calendar* calptr; 00668 00669 /** Flags whether fractional forecasts are allowed. */ 00670 bool discrete; 00671 00672 /** A dictionary of all forecasts. */ 00673 static MapOfForecasts ForecastDictionary; 00674 00675 /** Controls how we search the customer and item levels when looking for a 00676 * matching forecast for a demand. 00677 */ 00678 static bool Customer_Then_Item_Hierarchy; 00679 00680 /** Controls whether or not a matching delivery operation is required 00681 * between a matching order and its forecast. 00682 */ 00683 static bool Match_Using_Delivery_Operation; 00684 00685 /** Store the maximum time difference between an order due date and a 00686 * forecast bucket to net from.<br> 00687 * The default value is 0, meaning that only netting from the due 00688 * bucket is allowed. 00689 */ 00690 static TimePeriod Net_Late; 00691 00692 /** Store the maximum time difference between an order due date and a 00693 * forecast bucket to net from.<br> 00694 * The default value is 0, meaning that only netting from the due 00695 * bucket is allowed. 00696 */ 00697 static TimePeriod Net_Early; 00698 00699 /** Specifies the maximum number of iterations allowed for a forecast 00700 * method to tune its parameters.<br> 00701 * Only positive values are allowed and the default value is 10.<br> 00702 * Set the parameter to 1 to disable the tuning and generate a 00703 * forecast based on the user-supplied parameters. 00704 */ 00705 static unsigned long Forecast_Iterations; 00706 00707 /** Specifies how the MAD forecast error is weighted for different time 00708 * buckets. The MAD value in the most recent bucket is 1.0, and the 00709 * weight decreases exponentially for earlier buckets.<br> 00710 * Acceptable values are in the interval 0.5 and 1.0, and the default 00711 * is 0.95. 00712 */ 00713 static double Forecast_MadAlfa; 00714 00715 /** Number of warmup periods.<br> 00716 * These periods are used for the initialization of the algorithm 00717 * and don't count towards measuring the forecast error.<br> 00718 * The default value is 5. 00719 */ 00720 static unsigned long Forecast_Skip; 00721 }; 00722 00723 00724 /** @brief This class represents a forecast value in a time bucket. 00725 * 00726 * A forecast bucket is never manipulated or created directly. Instead, 00727 * the owning forecast manages the buckets. 00728 */ 00729 class ForecastBucket : public Demand 00730 { 00731 public: 00732 ForecastBucket(Forecast* f, Date d, Date e, double w, ForecastBucket* p) 00733 : Demand(f->getName() + " - " + string(d)), weight(w), consumed(0.0), 00734 total(0.0), timebucket(d,e), prev(p), next(NULL) 00735 { 00736 if (p) p->next = this; 00737 setOwner(f); 00738 setHidden(true); // Avoid the subdemands show up in the output 00739 setItem(&*(f->getItem())); 00740 setDue(d); 00741 setPriority(f->getPriority()); 00742 setMaxLateness(f->getMaxLateness()); 00743 setMinShipment(f->getMinShipment()); 00744 setOperation(&*(f->getOperation())); 00745 } 00746 virtual const MetaClass& getType() const {return *metadata;} 00747 static const MetaClass *metadata; 00748 virtual size_t getSize() const 00749 { 00750 return sizeof(ForecastBucket) + Demand::extrasize(); 00751 } 00752 00753 /** Returns the relative weight of this forecast bucket when distributing 00754 * forecast over different buckets. 00755 */ 00756 double getWeight() const { return weight; } 00757 00758 /** Returns the total, gross forecast. */ 00759 double getTotal() const { return total; } 00760 00761 /** Returns the consumed forecast. */ 00762 double getConsumed() const { return consumed; } 00763 00764 /** Update the weight of this forecasting bucket. */ 00765 void setWeight(double n) 00766 { 00767 if (n<0) 00768 throw DataException("Forecast bucket weight must be greater or equal to 0"); 00769 weight = n; 00770 } 00771 00772 /** Increment the total, gross forecast. */ 00773 void incTotal(double n) 00774 { 00775 total += n; 00776 if (total<0) total = 0.0; 00777 setQuantity(total>consumed ? total - consumed : 0.0); 00778 } 00779 00780 /** Update the total, gross forecast. */ 00781 void setTotal(double n) 00782 { 00783 if (n<0) 00784 throw DataException("Gross forecast must be greater or equal to 0"); 00785 if (total == n) return; 00786 total = n; 00787 setQuantity(total>consumed ? total - consumed : 0.0); 00788 } 00789 00790 /** Increment the consumed forecast. */ 00791 void incConsumed(double n) 00792 { 00793 consumed += n; 00794 if (consumed<0) consumed = 0.0; 00795 setQuantity(total>consumed ? total - consumed : 0.0); 00796 } 00797 00798 /** Update the consumed forecast.<br> 00799 * This field is normally updated through the forecast netting solver, but 00800 * you can use this method to update it directly. 00801 */ 00802 void setConsumed(double n) 00803 { 00804 if (n<0) 00805 throw DataException("Consumed forecast must be greater or equal to 0"); 00806 if (consumed == n) return; 00807 consumed = n; 00808 setQuantity(total>consumed ? total - consumed : 0.0); 00809 } 00810 00811 /** Return the date range for this bucket. */ 00812 DateRange getDueRange() const { return timebucket; } 00813 00814 /** Return a pointer to the next forecast bucket. */ 00815 ForecastBucket* getNextBucket() const { return next; } 00816 00817 /** Return a pointer to the previous forecast bucket. */ 00818 ForecastBucket* getPreviousBucket() const { return prev; } 00819 00820 private: 00821 double weight; 00822 double consumed; 00823 double total; 00824 DateRange timebucket; 00825 ForecastBucket* prev; 00826 ForecastBucket* next; 00827 }; 00828 00829 00830 /** @brief Implementation of a forecast netting algorithm. 00831 * 00832 * As customer orders are being received they need to be deducted from 00833 * the forecast to avoid double-counting demand. 00834 * 00835 * The netting solver will process each order as follows: 00836 * - <b>First search for a matching forecast.</b><br> 00837 * A matching forecast has the same item and customer as the order.<br> 00838 * If no match is found at this level, a match is tried at higher levels 00839 * of the customer and item.<br> 00840 * Ultimately a match is tried with a empty customer or item field. 00841 * - <b>Next, the remaining net quantity of the forecast is decreased.</b><br> 00842 * The forecast bucket to be reduced is the one where the order is due.<br> 00843 * If the net quantity is already completely depleted in that bucket 00844 * the solver will look in earlier and later buckets. The parameters 00845 * Net_Early and Net_Late control the limits for the search in the 00846 * time dimension. 00847 * 00848 * The logging levels have the following meaning: 00849 * - 0: Silent operation. Default logging level. 00850 * - 1: Log demands being netted and the matching forecast. 00851 * - 2: Same as 1, plus details on forecast buckets being netted. 00852 */ 00853 class ForecastSolver : public Solver 00854 { 00855 friend class Forecast; 00856 public: 00857 /** Constructor. */ 00858 ForecastSolver(const string& n) : Solver(n) {} 00859 00860 /** This method handles the search for a matching forecast, followed 00861 * by decreasing the net forecast. 00862 */ 00863 void solve(const Demand*, void* = NULL); 00864 00865 /** This is the main solver method that will appropriately call the other 00866 * solve methods.<br> 00867 */ 00868 void solve(void *v = NULL); 00869 00870 virtual const MetaClass& getType() const {return *metadata;} 00871 static const MetaClass *metadata; 00872 virtual size_t getSize() const {return sizeof(ForecastSolver);} 00873 void writeElement(XMLOutput*, const Keyword&, mode=DEFAULT) const; 00874 00875 /** Callback function, used for netting orders against the forecast. */ 00876 bool callback(Demand* l, const Signal a); 00877 00878 private: 00879 /** Given a demand, this function will identify the forecast model it 00880 * links to. 00881 */ 00882 Forecast* matchDemandToForecast(const Demand* l); 00883 00884 /** Implements the netting of a customer order from a matching forecast 00885 * (and its delivery plan). 00886 */ 00887 void netDemandFromForecast(const Demand*, Forecast*); 00888 00889 /** Used for sorting demands during netting. */ 00890 struct sorter 00891 { 00892 bool operator()(const Demand* x, const Demand* y) const 00893 { return SolverMRP::demand_comparison(x,y); } 00894 }; 00895 00896 /** Used for sorting demands during netting. */ 00897 typedef multiset < Demand*, sorter > sortedDemandList; 00898 }; 00899 00900 00901 class PythonForecast : public FreppleClass<PythonForecast,PythonDemand,Forecast> 00902 { 00903 public: 00904 PythonForecast(Forecast* p) 00905 : FreppleClass<PythonForecast,PythonDemand,Forecast>(p) {} 00906 static int initialize(PyObject*); 00907 private: 00908 virtual PyObject* getattro(const Attribute&); 00909 virtual int setattro(const Attribute&, const PythonObject&); 00910 static PyObject* timeseries(PyObject *, PyObject *); 00911 }; 00912 00913 00914 class PythonForecastBucket : public FreppleClass<PythonForecastBucket,PythonDemand,ForecastBucket> 00915 { 00916 public: 00917 PythonForecastBucket(ForecastBucket* p) 00918 : FreppleClass<PythonForecastBucket,PythonDemand,ForecastBucket>(p) {} 00919 static int initialize(PyObject*); 00920 private: 00921 virtual PyObject* getattro(const Attribute&); 00922 virtual int setattro(const Attribute&, const PythonObject&); 00923 }; 00924 00925 00926 class PythonForecastSolver : public FreppleClass<PythonForecastSolver,PythonSolver,ForecastSolver> 00927 { 00928 public: 00929 PythonForecastSolver(ForecastSolver* p) 00930 : FreppleClass<PythonForecastSolver,PythonSolver,ForecastSolver>(p) {} 00931 virtual PyObject* getattro(const Attribute&); 00932 virtual int setattro(const Attribute&, const PythonObject&); 00933 }; 00934 00935 00936 } // End namespace 00937 00938 #endif 00939 00940