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