forecast.h
Go to the documentation of this file.
00001 /*************************************************************************** 00002 file : $URL: http://svn.code.sf.net/p/frepple/code/trunk/modules/forecast/forecast.h $ 00003 version : $LastChangedRevision: 1715 $ $LastChangedBy: jdetaeye $ 00004 date : $LastChangedDate: 2012-07-19 21:37:46 +0200 (Thu, 19 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 /** @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 * - <b>Single exponential smoothing</b>, which is applicable for 00063 * constant demands . 00064 * - <b>Double exponential smoothing</b>, which is applicable for 00065 * trended demands. 00066 * - <b>Holt-Winter's exponential smoothing with mutiplicative 00067 * seasonality</b>, which is applicable for seasonal demands. 00068 * - <b>Croston's method</b>, which is applicable for intermittent 00069 * demand (i.e. demand patterns with a lot of zero demand buckets). 00070 * - <b>Moving average</b>, which is applicable when there is little 00071 * demand history to rely on. 00072 * The forecast method giving the smallest symmetric mean percentage error (aka 00073 * "smape"-error) will be automatically picked to produce the forecast.<br> 00074 * The algorithm will automatically tune the parameters for the 00075 * forecasting methods (i.e. alfa for the single exponential smoothing, 00076 * or alfa and gamma for the double exponential smoothing) to their 00077 * optimal value. The user can specify minimum and maximum boundaries 00078 * for the parameters and the maximum allowed number of iterations 00079 * for the algorithm. 00080 * 00081 * The XML schema extension enabled by this module is (see mod_forecast.xsd): 00082 * <PRE> 00083 * <!-- Define the forecast type --> 00084 * <xsd:complexType name="demand_forecast"> 00085 * <xsd:complexContent> 00086 * <xsd:extension base="demand"> 00087 * <xsd:choice minOccurs="0" maxOccurs="unbounded"> 00088 * <xsd:element name="calendar" type="calendar" /> 00089 * <xsd:element name="discrete" type="xsd:boolean" /> 00090 * <xsd:element name="buckets"> 00091 * <xsd:complexType> 00092 * <xsd:choice minOccurs="0" maxOccurs="unbounded"> 00093 * <xsd:element name="bucket"> 00094 * <xsd:complexType> 00095 * <xsd:all> 00096 * <xsd:element name="total" type="positiveDouble" 00097 * minOccurs="0" /> 00098 * <xsd:element name="net" type="positiveDouble" 00099 * minOccurs="0" /> 00100 * <xsd:element name="consumed" type="positiveDouble" 00101 * minOccurs="0" /> 00102 * <xsd:element name="start" type="xsd:dateTime" 00103 * minOccurs="0"/> 00104 * <xsd:element name="end" type="xsd:dateTime" 00105 * minOccurs="0"/> 00106 * </xsd:all> 00107 * <xsd:attribute name="total" type="positiveDouble" /> 00108 * <xsd:attribute name="net" type="positiveDouble" /> 00109 * <xsd:attribute name="consumed" type="positiveDouble" /> 00110 * <xsd:attribute name="start" type="xsd:dateTime" /> 00111 * <xsd:attribute name="end" type="xsd:dateTime" /> 00112 * </xsd:complexType> 00113 * </xsd:element> 00114 * </xsd:choice> 00115 * </xsd:complexType> 00116 * </xsd:element> 00117 * </xsd:choice> 00118 * <xsd:attribute name="discrete" type="xsd:boolean" /> 00119 * </xsd:extension> 00120 * </xsd:complexContent> 00121 * </xsd:complexType> 00122 * 00123 * <!-- Define the netting solver. --> 00124 * <xsd:complexType name="solver_forecast"> 00125 * <xsd:complexContent> 00126 * <xsd:extension base="solver"> 00127 * <xsd:choice minOccurs="0" maxOccurs="unbounded"> 00128 * <xsd:element name="loglevel" type="loglevel" /> 00129 * </xsd:choice> 00130 * </xsd:extension> 00131 * </xsd:complexContent> 00132 * </xsd:complexType> 00133 * </PRE> 00134 * 00135 * The module support the following configuration parameters: 00136 * 00137 * - DueAtEndOfBucket:<br> 00138 * By default forecast demand is due at the start of the forecasting 00139 * bucket. Since the actual customer demand will come in any time in the 00140 * bucket this is a conservative setting.<br> 00141 * By setting this flag to true, the forecast will be due at the end of 00142 * the forecast bucket. 00143 * 00144 * - Net_CustomerThenItemHierarchy:<br> 00145 * As part of the forecast netting a demand is assiociated with a certain 00146 * forecast. When no matching forecast is found for the customer and item 00147 * of the demand, frePPLe looks for forecast at higher level customers 00148 * and items.<br> 00149 * This flag allows us to control whether we first search the customer 00150 * hierarchy and then the item hierarchy, or the other way around.<br> 00151 * The default value is true, ie search higher customer levels before 00152 * searching higher levels of the item. 00153 * 00154 * - Net_MatchUsingDeliveryOperation:<br> 00155 * Specifies whether or not a demand and a forecast require to have the 00156 * same delivery operation to be a match.<br> 00157 * The default value is true. 00158 * 00159 * - Net_NetEarly:<br> 00160 * Defines how much time before the due date of an order we are allowed 00161 * to search for a forecast bucket to net from.<br> 00162 * The default value is 0, meaning that we can net only from the bucket 00163 * where the demand is due. 00164 * 00165 * - Net_NetLate:<br> 00166 * Defines how much time after the due date of an order we are allowed 00167 * to search for a forecast bucket to net from.<br> 00168 * The default value is 0, meaning that we can net only from the bucket 00169 * where the demand is due. 00170 * 00171 * - Forecast_Iterations:<br> 00172 * Specifies the maximum number of iterations allowed for a forecast 00173 * method to tune its parameters.<br> 00174 * Only positive values are allowed and the default value is 10.<br> 00175 * Set the parameter to 1 to disable the tuning and generate a forecast 00176 * based on the user-supplied parameters. 00177 * 00178 * - Forecast_smapeAlfa:<br> 00179 * Specifies how the sMAPE forecast error is weighted for different time 00180 * buckets. The sMAPE value in the most recent bucket is 1.0, and the 00181 * weight decreases exponentially for earlier buckets.<br> 00182 * Acceptable values are in the interval 0.5 and 1.0, and the default 00183 * is 0.95. 00184 * 00185 * - Forecast_Skip:<br> 00186 * Specifies the number of time series values used to initialize the 00187 * forecasting method. The forecast error in these bucket isn't counted. 00188 * 00189 * - Forecast_MovingAverage.buckets<br> 00190 * This parameter controls the number of buckets to be averaged by the 00191 * moving average forecast method. 00192 * 00193 * - Forecast_SingleExponential.initialAlfa,<br> 00194 * Forecast_SingleExponential.minAlfa,<br> 00195 * Forecast_SingleExponential.maxAlfa:<br> 00196 * Specifies the initial value and the allowed range of the smoothing 00197 * parameter in the single exponential forecasting method.<br> 00198 * The allowed range is between 0 and 1. Values lower than about 0.05 00199 * are not advisible. 00200 * 00201 * - Forecast_DoubleExponential.initialAlfa,<br> 00202 * Forecast_DoubleExponential.minAlfa,<br> 00203 * Forecast_DoubleExponential.maxAlfa:<br> 00204 * Specifies the initial value and the allowed range of the smoothing 00205 * parameter in the double exponential forecasting method.<br> 00206 * The allowed range is between 0 and 1. Values lower than about 0.05 00207 * are not advisible. 00208 * 00209 * - Forecast_DoubleExponential.initialGamma,<br> 00210 * Forecast_DoubleExponential.minGamma,<br> 00211 * Forecast_DoubleExponential.maxGamma:<br> 00212 * Specifies the initial value and the allowed range of the trend 00213 * smoothing parameter in the double exponential forecasting method.<br> 00214 * The allowed range is between 0 and 1. 00215 * 00216 * - Forecast_DoubleExponential_dampenTrend:<br> 00217 * Specifies how the trend is dampened for future buckets.<br> 00218 * The allowed range is between 0 and 1, and the default value is 0.8. 00219 * 00220 * - Forecast_Seasonal_initialAlfa,<br> 00221 * Forecast_Seasonal_minAlfa,<br> 00222 * Forecast_Seasonal_maxAlfa:<br> 00223 * Specifies the initial value and the allowed range of the smoothing 00224 * parameter in the seasonal forecasting method.<br> 00225 * The allowed range is between 0 and 1. Values lower than about 0.05 are 00226 * not advisible. 00227 * 00228 * - Forecast_Seasonal_initialBeta,<br> 00229 * Forecast_Seasonal_minBeta,<br> 00230 * Forecast_Seasonal_maxBeta:<br> 00231 * Specifies the initial value and the allowed range of the trend 00232 * smoothing parameter in the seasonal forecasting method.<br> 00233 * The allowed range is between 0 and 1. 00234 * 00235 * - Forecast_Seasonal_initialGamma,<br> 00236 * Forecast_Seasonal_minGamma,<br> 00237 * Forecast_Seasonal_maxGamma:<br> 00238 * Specifies the initial value and the allowed range of the seasonal 00239 * smoothing parameter in the seasonal forecasting method.<br> 00240 * The allowed range is between 0 and 1. 00241 * 00242 * - Forecast_Seasonal_minPeriod,<br> 00243 * Forecast_Seasonal_maxPeriod:<br> 00244 * Specifies the periodicity of the seasonal cycles to check for.<br> 00245 * The interval of cycles we try to detect should be broad enough. For 00246 * instance, if we expect to find a yearly cycle use a minimum period of 00247 * 10 and maximum period of 14. 00248 * 00249 * - Forecast_Seasonal_dampenTrend<br> 00250 * Specifies how the trend is dampened for future buckets.<br> 00251 * The allowed range is between 0 and 1, and the default value is 0.8. 00252 * 00253 * - Forecast_Croston_initialAlfa,<br> 00254 * Forecast_Croston_minAlfa,<br> 00255 * Forecast_Croston_maxAlfa:<br> 00256 * Specifies the initial value and the allowed range of the smoothing 00257 * parameter in the Croston forecasting method.<br> 00258 * The allowed range is between 0 and 1. Values lower than about 0.05 00259 * are not advisible. 00260 * 00261 * - Forecast_Croston_minIntermittence:<br> 00262 * Minimum intermittence (defined as the percentage of zero demand 00263 * buckets) before the Croston method is applied. When the intermittence 00264 * exceeds this value, only Croston and moving average are considered 00265 * suitable forecast methods.<br> 00266 * The default value is 0.33. 00267 */ 00268 00269 #ifndef FORECAST_H 00270 #define FORECAST_H 00271 00272 #include "frepple.h" 00273 using namespace frepple; 00274 00275 namespace module_forecast 00276 { 00277 00278 00279 /** Initialization routine for the library. */ 00280 MODULE_EXPORT const char* initialize(const Environment::ParameterList&); 00281 00282 /** @brief This class represents a bucketized demand signal. 00283 * 00284 * The forecast object defines the item and priority of the demands.<br> 00285 * A calendar (of type void, double, integer or boolean) divides the time horizon 00286 * in individual time buckets. The calendar value is used to assign priorities 00287 * to the time buckets.<br> 00288 * The class basically works as an interface for a hierarchy of demands, where the 00289 * lower level demands represent forecasting time buckets. 00290 */ 00291 class Forecast : public Demand 00292 { 00293 friend class ForecastSolver; 00294 public: 00295 00296 static const Keyword tag_total; 00297 static const Keyword tag_net; 00298 static const Keyword tag_consumed; 00299 00300 /** @brief Abstract base class for all forecasting methods. */ 00301 class ForecastMethod 00302 { 00303 public: 00304 /** Forecast evaluation. */ 00305 virtual double generateForecast 00306 (Forecast*, const double[], unsigned int, const double[], bool) = 0; 00307 00308 /** This method is called when this forecast method has generated the 00309 * lowest forecast error and now needs to set the forecast values. 00310 */ 00311 virtual void applyForecast 00312 (Forecast*, const Date[], unsigned int, bool) = 0; 00313 00314 /** The name of the method. */ 00315 virtual string getName() = 0; 00316 }; 00317 00318 00319 /** @brief A class to calculate a forecast based on a moving average. */ 00320 class MovingAverage : public ForecastMethod 00321 { 00322 private: 00323 /** Number of smoothed buckets. */ 00324 static unsigned int defaultbuckets; 00325 00326 /** Number of buckets to average. */ 00327 unsigned int buckets; 00328 00329 /** Calculated average.<br> 00330 * Used to carry results between the evaluation and applying of the forecast. 00331 */ 00332 double avg; 00333 00334 public: 00335 /** Constructor. */ 00336 MovingAverage(int i = defaultbuckets) : buckets(i), avg(0) 00337 { 00338 if (i < 1) 00339 throw DataException("Moving average needs to smooth over at least 1 bucket"); 00340 } 00341 00342 /** Forecast evaluation. */ 00343 double generateForecast(Forecast* fcst, const double history[], 00344 unsigned int count, const double weight[], bool debug); 00345 00346 /** Forecast value updating. */ 00347 void applyForecast(Forecast*, const Date[], unsigned int, bool); 00348 00349 /** Update the initial value for the alfa parameter. */ 00350 static void setDefaultBuckets(int x) 00351 { 00352 if (x < 1) 00353 throw DataException("Parameter MovingAverage.buckets needs to smooth over at least 1 bucket"); 00354 defaultbuckets = x; 00355 } 00356 00357 string getName() {return "moving average";} 00358 }; 00359 00360 /** @brief A class to perform single exponential smoothing on a time series. */ 00361 class SingleExponential : public ForecastMethod 00362 { 00363 private: 00364 /** Smoothing constant. */ 00365 double alfa; 00366 00367 /** Default initial alfa value.<br> 00368 * The default value is 0.2. 00369 */ 00370 static double initial_alfa; 00371 00372 /** Lower limit on the alfa parameter.<br> 00373 * The default value is 0. 00374 **/ 00375 static double min_alfa; 00376 00377 /** Upper limit on the alfa parameter.<br> 00378 * The default value is 1. 00379 **/ 00380 static double max_alfa; 00381 00382 /** Smoothed result.<br> 00383 * Used to carry results between the evaluation and applying of the forecast. 00384 */ 00385 double f_i; 00386 00387 public: 00388 /** Constructor. */ 00389 SingleExponential(double a = initial_alfa) : alfa(a), f_i(0) 00390 { 00391 if (alfa < min_alfa) alfa = min_alfa; 00392 if (alfa > max_alfa) alfa = max_alfa; 00393 } 00394 00395 /** Forecast evaluation. */ 00396 double generateForecast(Forecast* fcst, const double history[], 00397 unsigned int count, const double weight[], bool debug); 00398 00399 /** Forecast value updating. */ 00400 void applyForecast(Forecast*, const Date[], unsigned int, bool); 00401 00402 /** Update the initial value for the alfa parameter. */ 00403 static void setInitialAlfa(double x) 00404 { 00405 if (x<0 || x>1.0) throw DataException( 00406 "Parameter SingleExponential.initialAlfa must be between 0 and 1"); 00407 initial_alfa = x; 00408 } 00409 00410 /** Update the minimum value for the alfa parameter. */ 00411 static void setMinAlfa(double x) 00412 { 00413 if (x<0 || x>1.0) throw DataException( 00414 "Parameter SingleExponential.minAlfa must be between 0 and 1"); 00415 min_alfa = x; 00416 } 00417 00418 /** Update the maximum value for the alfa parameter. */ 00419 static void setMaxAlfa(double x) 00420 { 00421 if (x<0 || x>1.0) throw DataException( 00422 "Parameter SingleExponential.maxAlfa must be between 0 and 1"); 00423 max_alfa = x; 00424 } 00425 00426 string getName() {return "single exponential";} 00427 }; 00428 00429 /** @brief A class to perform double exponential smoothing on a time 00430 * series. 00431 */ 00432 class DoubleExponential : public ForecastMethod 00433 { 00434 private: 00435 /** Smoothing constant. */ 00436 double alfa; 00437 00438 /** Default initial alfa value.<br> 00439 * The default value is 0.2. 00440 */ 00441 static double initial_alfa; 00442 00443 /** Lower limit on the alfa parameter.<br> 00444 * The default value is 0. 00445 **/ 00446 static double min_alfa; 00447 00448 /** Upper limit on the alfa parameter.<br> 00449 * The default value is 1. 00450 **/ 00451 static double max_alfa; 00452 00453 /** Trend smoothing constant. */ 00454 double gamma; 00455 00456 /** Default initial gamma value.<br> 00457 * The default value is 0.05. 00458 */ 00459 static double initial_gamma; 00460 00461 /** Lower limit on the gamma parameter.<br> 00462 * The default value is 0.05. 00463 **/ 00464 static double min_gamma; 00465 00466 /** Upper limit on the gamma parameter.<br> 00467 * The default value is 1. 00468 **/ 00469 static double max_gamma; 00470 00471 /** Smoothed result.<br> 00472 * Used to carry results between the evaluation and applying of the forecast. 00473 */ 00474 double trend_i; 00475 00476 /** Smoothed result.<br> 00477 * Used to carry results between the evaluation and applying of the forecast. 00478 */ 00479 double constant_i; 00480 00481 /* Factor used to smoothen the trend in the future buckets. */ 00482 static double dampenTrend; 00483 00484 public: 00485 /** Constructor. */ 00486 DoubleExponential(double a = initial_alfa, double g = initial_gamma) 00487 : alfa(a), gamma(g), trend_i(0), constant_i(0) {} 00488 00489 /** Forecast evaluation. */ 00490 double generateForecast(Forecast* fcst, const double history[], 00491 unsigned int count, const double weight[], bool debug); 00492 00493 /** Forecast value updating. */ 00494 void applyForecast(Forecast*, const Date[], unsigned int, bool); 00495 00496 /** Update the initial value for the alfa parameter. */ 00497 static void setInitialAlfa(double x) 00498 { 00499 if (x<0 || x>1.0) throw DataException( 00500 "Parameter DoubleExponential.initialAlfa must be between 0 and 1"); 00501 initial_alfa = x; 00502 } 00503 00504 /** Update the minimum value for the alfa parameter. */ 00505 static void setMinAlfa(double x) 00506 { 00507 if (x<0 || x>1.0) throw DataException( 00508 "Parameter DoubleExponential.minAlfa must be between 0 and 1"); 00509 min_alfa = x; 00510 } 00511 00512 /** Update the maximum value for the alfa parameter. */ 00513 static void setMaxAlfa(double x) 00514 { 00515 if (x<0 || x>1.0) throw DataException( 00516 "Parameter DoubleExponential.maxAlfa must be between 0 and 1"); 00517 max_alfa = x; 00518 } 00519 00520 /** Update the initial value for the alfa parameter.<br> 00521 * The default value is 0.05. <br> 00522 * Setting this parameter to too low a value can create false 00523 * positives: the double exponential method is selected for a time 00524 * series without a real trend. A single exponential is better for 00525 * such cases. 00526 */ 00527 static void setInitialGamma(double x) 00528 { 00529 if (x<0 || x>1.0) throw DataException( 00530 "Parameter DoubleExponential.initialGamma must be between 0 and 1"); 00531 initial_gamma = x; 00532 } 00533 00534 /** Update the minimum value for the alfa parameter. */ 00535 static void setMinGamma(double x) 00536 { 00537 if (x<0 || x>1.0) throw DataException( 00538 "Parameter DoubleExponential.minGamma must be between 0 and 1"); 00539 min_gamma = x; 00540 } 00541 00542 /** Update the maximum value for the alfa parameter. */ 00543 static void setMaxGamma(double x) 00544 { 00545 if (x<0 || x>1.0) throw DataException( 00546 "Parameter DoubleExponential.maxGamma must be between 0 and 1"); 00547 max_gamma = x; 00548 } 00549 00550 /** Update the dampening factor for the trend. */ 00551 static void setDampenTrend(double x) 00552 { 00553 if (x<0 || x>1.0) throw DataException( 00554 "Parameter DoubleExponential.dampenTrend must be between 0 and 1"); 00555 dampenTrend = x; 00556 } 00557 00558 string getName() {return "double exponential";} 00559 }; 00560 00561 /** @brief A class to perform seasonal forecasting on a time 00562 * series. 00563 */ 00564 class Seasonal : public ForecastMethod 00565 { 00566 private: 00567 /** Smoothing constant. */ 00568 double alfa; 00569 00570 /** Trend smoothing constant. */ 00571 double beta; 00572 00573 /** Seasonality smoothing constant. */ 00574 double gamma; 00575 00576 /** Default initial alfa value.<br> 00577 * The default value is 0.2. 00578 */ 00579 static double initial_alfa; 00580 00581 /** Lower limit on the alfa parameter.<br> 00582 * The default value is 0. 00583 **/ 00584 static double min_alfa; 00585 00586 /** Upper limit on the alfa parameter.<br> 00587 * The default value is 1. 00588 **/ 00589 static double max_alfa; 00590 00591 /** Default initial beta value.<br> 00592 * The default value is 0.05. 00593 */ 00594 static double initial_beta; 00595 00596 /** Lower limit on the beta parameter.<br> 00597 * The default value is 0.05. 00598 **/ 00599 static double min_beta; 00600 00601 /** Upper limit on the beta parameter.<br> 00602 * The default value is 1. 00603 **/ 00604 static double max_beta; 00605 00606 /** Default initial gamma value.<br> 00607 * The default value is 0.05. 00608 */ 00609 static double initial_gamma; 00610 00611 /** Lower limit on the gamma parameter.<br> 00612 * The default value is 0.05. 00613 **/ 00614 static double min_gamma; 00615 00616 /** Upper limit on the gamma parameter.<br> 00617 * The default value is 1. 00618 **/ 00619 static double max_gamma; 00620 00621 /** Used to dampen a trend in the future. */ 00622 static double dampenTrend; 00623 00624 /** Minimum cycle to be check for.<br> 00625 * The interval of cycles we try to detect should be broad enough. 00626 * If eg we normally expect a yearly cycle use a minimum cycle of 10. 00627 */ 00628 static unsigned int min_period; 00629 00630 /** Maximum cycle to be check for.<br> 00631 * The interval of cycles we try to detect should be broad enough. 00632 * If eg we normally expect a yearly cycle use a maximum cycle of 14. 00633 */ 00634 static unsigned int max_period; 00635 00636 /** Period of the cycle. */ 00637 unsigned short period; 00638 00639 /** Smoothed result - constant component.<br> 00640 * Used to carry results between the evaluation and applying of the forecast. 00641 */ 00642 double L_i; 00643 00644 /** Smoothed result - trend component.<br> 00645 * Used to carry results between the evaluation and applying of the forecast. 00646 */ 00647 double T_i; 00648 00649 /** Smoothed result - seasonal component.<br> 00650 * Used to carry results between the evaluation and applying of the forecast. 00651 */ 00652 double* S_i; 00653 00654 /** Remember where in the cycle we are. */ 00655 unsigned int cycleindex; 00656 00657 /** A check for seasonality.<br> 00658 * The cycle period is returned if seasonality is detected. Zero is 00659 * returned in case no seasonality is present. 00660 */ 00661 void detectCycle(const double[], unsigned int); 00662 00663 /** Compute the determinant of a 3x3 matrix. */ 00664 inline double determinant(const double a, const double b, const double c, 00665 const double d, const double e, const double f, 00666 const double g, const double h, const double i) 00667 { return a * e * i + b * f * g + c * d * h - a * f * h - b * d * i - c * e * g; } 00668 00669 public: 00670 /** Constructor. */ 00671 Seasonal(double a = initial_alfa, double b = initial_beta, double g = initial_gamma) 00672 : alfa(a), beta(b), gamma(g), period(0), L_i(0), T_i(0), S_i(NULL) {} 00673 00674 /** Destructor. */ 00675 ~Seasonal() {if (period) delete S_i;} 00676 00677 /** Forecast evaluation. */ 00678 double generateForecast(Forecast* fcst, const double history[], 00679 unsigned int count, const double weight[], bool debug); 00680 00681 /** Forecast value updating. */ 00682 void applyForecast(Forecast*, const Date[], unsigned int, bool); 00683 00684 /** Update the minimum period that can be detected. */ 00685 static void setMinPeriod(int x) 00686 { 00687 if (x <= 1) throw DataException( 00688 "Parameter Seasonal.minPeriod must be greater than 1"); 00689 min_period = x; 00690 } 00691 00692 /** Update the maximum period that can be detected. */ 00693 static void setMaxPeriod(int x) 00694 { 00695 if (x <= 1) throw DataException( 00696 "Parameter Seasonal.maxPeriod must be greater than 1"); 00697 max_period = x; 00698 } 00699 00700 /** Update the initial value for the alfa parameter. */ 00701 static void setInitialAlfa(double x) 00702 { 00703 if (x<0 || x>1.0) throw DataException( 00704 "Parameter Seasonal.initialAlfa must be between 0 and 1"); 00705 initial_alfa = x; 00706 } 00707 00708 /** Update the minimum value for the alfa parameter. */ 00709 static void setMinAlfa(double x) 00710 { 00711 if (x<0 || x>1.0) throw DataException( 00712 "Parameter Seasonal.minAlfa must be between 0 and 1"); 00713 min_alfa = x; 00714 } 00715 00716 /** Update the maximum value for the alfa parameter. */ 00717 static void setMaxAlfa(double x) 00718 { 00719 if (x<0 || x>1.0) throw DataException( 00720 "Parameter Seasonal.maxAlfa must be between 0 and 1"); 00721 max_alfa = x; 00722 } 00723 00724 /** Update the initial value for the beta parameter. */ 00725 static void setInitialBeta(double x) 00726 { 00727 if (x<0 || x>1.0) throw DataException( 00728 "Parameter Seasonal.initialBeta must be between 0 and 1"); 00729 initial_beta = x; 00730 } 00731 00732 /** Update the minimum value for the beta parameter. */ 00733 static void setMinBeta(double x) 00734 { 00735 if (x<0 || x>1.0) throw DataException( 00736 "Parameter Seasonal.minBeta must be between 0 and 1"); 00737 min_beta = x; 00738 } 00739 00740 /** Update the maximum value for the beta parameter. */ 00741 static void setMaxBeta(double x) 00742 { 00743 if (x<0 || x>1.0) throw DataException( 00744 "Parameter Seasonal.maxBeta must be between 0 and 1"); 00745 max_beta = x; 00746 } 00747 00748 /** Update the initial value for the alfa parameter.<br> 00749 * The default value is 0.05. <br> 00750 */ 00751 static void setInitialGamma(double x) 00752 { 00753 if (x<0 || x>1.0) throw DataException( 00754 "Parameter Seasonal.initialGamma must be between 0 and 1"); 00755 initial_gamma = x; 00756 } 00757 00758 /** Update the minimum value for the alfa parameter. */ 00759 static void setMinGamma(double x) 00760 { 00761 if (x<0 || x>1.0) throw DataException( 00762 "Parameter Seasonal.minGamma must be between 0 and 1"); 00763 min_gamma = x; 00764 } 00765 00766 /** Update the maximum value for the alfa parameter. */ 00767 static void setMaxGamma(double x) 00768 { 00769 if (x<0 || x>1.0) throw DataException( 00770 "Parameter Seasonal.maxGamma must be between 0 and 1"); 00771 max_gamma = x; 00772 } 00773 00774 /** Update the dampening factor for the trend. */ 00775 static void setDampenTrend(double x) 00776 { 00777 if (x<0 || x>1.0) throw DataException( 00778 "Parameter Seasonal.dampenTrend must be between 0 and 1"); 00779 dampenTrend = x; 00780 } 00781 00782 string getName() {return "seasonal";} 00783 }; 00784 00785 /** @brief A class to calculate a forecast with Croston's method. */ 00786 class Croston : public ForecastMethod 00787 { 00788 private: 00789 /** Smoothing constant. */ 00790 double alfa; 00791 00792 /** Default initial alfa value.<br> 00793 * The default value is 0.2. 00794 */ 00795 static double initial_alfa; 00796 00797 /** Lower limit on the alfa parameter.<br> 00798 * The default value is 0. 00799 **/ 00800 static double min_alfa; 00801 00802 /** Upper limit on the alfa parameter.<br> 00803 * The default value is 1. 00804 **/ 00805 static double max_alfa; 00806 00807 /** Minimum intermittence before this method is applicable. */ 00808 static double min_intermittence; 00809 00810 /** Smoothed forecast.<br> 00811 * Used to carry results between the evaluation and applying of the forecast. 00812 */ 00813 double f_i; 00814 00815 public: 00816 /** Constructor. */ 00817 Croston(double a = initial_alfa) : alfa(a), f_i(0) 00818 { 00819 if (alfa < min_alfa) alfa = min_alfa; 00820 if (alfa > max_alfa) alfa = max_alfa; 00821 } 00822 00823 /** Forecast evaluation. */ 00824 double generateForecast(Forecast* fcst, const double history[], 00825 unsigned int count, const double weight[], bool debug); 00826 00827 /** Forecast value updating. */ 00828 void applyForecast(Forecast*, const Date[], unsigned int, bool); 00829 00830 /** Update the initial value for the alfa parameter. */ 00831 static void setInitialAlfa(double x) 00832 { 00833 if (x<0 || x>1.0) throw DataException( 00834 "Parameter Croston.initialAlfa must be between 0 and 1"); 00835 initial_alfa = x; 00836 } 00837 00838 /** Update the minimum value for the alfa parameter. */ 00839 static void setMinAlfa(double x) 00840 { 00841 if (x<0 || x>1.0) throw DataException( 00842 "Parameter Croston.minAlfa must be between 0 and 1"); 00843 min_alfa = x; 00844 } 00845 00846 /** Update the maximum value for the alfa parameter. */ 00847 static void setMaxAlfa(double x) 00848 { 00849 if (x<0 || x>1.0) throw DataException( 00850 "Parameter Croston.maxAlfa must be between 0 and 1"); 00851 max_alfa = x; 00852 } 00853 00854 /** Update the minimum intermittence before applying this method. */ 00855 static void setMinIntermittence(double x) 00856 { 00857 if (x<0 || x>1.0) throw DataException( 00858 "Parameter Croston.minIntermittence must be between 0 and 1"); 00859 min_intermittence = x; 00860 } 00861 00862 /** Return the minimum intermittence before applying this method. */ 00863 static double getMinIntermittence() { return min_intermittence; } 00864 00865 string getName() {return "croston";} 00866 }; 00867 00868 public: 00869 /** Constructor. */ 00870 explicit Forecast(const string& nm) 00871 : Demand(nm), calptr(NULL), discrete(true) {initType(metadata);} 00872 00873 /** Destructor. */ 00874 ~Forecast(); 00875 00876 /** Updates the quantity of the forecast. This method is empty. */ 00877 virtual void setQuantity(double f) 00878 {throw DataException("Can't set quantity of a forecast");} 00879 00880 /** Update the forecast quantity.<br> 00881 * The forecast quantity will be distributed equally among the buckets 00882 * available between the two dates, taking into account also the bucket 00883 * weights.<br> 00884 * The logic applied is briefly summarized as follows: 00885 * - If the daterange has its start and end dates equal, we find the 00886 * matching forecast bucket and update the quantity. 00887 * - Otherwise the quantity is distributed among all intersecting 00888 * forecast buckets. This distribution is considering the weigth of 00889 * the bucket and the time duration of the bucket.<br> 00890 * The bucket weight is the value specified on the calendar.<br> 00891 * If a forecast bucket only partially overlaps with the daterange 00892 * only the overlapping time is used as the duration. 00893 * - If only buckets with zero weigth are found in the daterange a 00894 * dataexception is thrown. It indicates a situation where forecast 00895 * is specified for a date where no values are allowed. 00896 */ 00897 virtual void setTotalQuantity(const DateRange& , double); 00898 00899 /** Update the gross quantity in a single forecast bucket. */ 00900 virtual void setTotalQuantity(const Date , double); 00901 00902 /** Python method to update the total quantity of one or more 00903 * forecast buckets. 00904 */ 00905 static PyObject* setPythonTotalQuantity(PyObject *, PyObject *); 00906 00907 void writeElement(XMLOutput*, const Keyword&, mode=DEFAULT) const; 00908 void endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement); 00909 void beginElement(XMLInput& pIn, const Attribute& pAttr); 00910 static int initialize(); 00911 00912 /** Returns whether fractional forecasts are allowed or not.<br> 00913 * The default is true. 00914 */ 00915 bool getDiscrete() const {return discrete;} 00916 00917 /** Updates forecast discreteness flag. */ 00918 void setDiscrete(const bool b); 00919 00920 /** Update the item to be planned. */ 00921 virtual void setItem(Item*); 00922 00923 /** Update the customer. */ 00924 virtual void setCustomer(Customer*); 00925 00926 /* Update the maximum allowed lateness for planning. */ 00927 void setMaxLateness(TimePeriod); 00928 00929 /* Update the minumum allowed shipment quantity for planning. */ 00930 void setMinShipment(double); 00931 00932 /** Specify a bucket calendar for the forecast. Once forecasted 00933 * quantities have been entered for the forecast, the calendar 00934 * can't be updated any more. */ 00935 virtual void setCalendar(Calendar*); 00936 00937 /** Returns a reference to the calendar used for this forecast. */ 00938 Calendar* getCalendar() const {return calptr;} 00939 00940 /** Generate a forecast value based on historical demand data.<br> 00941 * This method will call the different forecasting methods and select the 00942 * method with the lowest smape-error.<br> 00943 * It then asks the selected forecast method to generate a value for 00944 * each of the time buckets passed. 00945 */ 00946 void generateFutureValues 00947 (const double[], unsigned int, const Date[], unsigned int, bool=false); 00948 00949 /** Updates the due date of the demand. Lower numbers indicate a 00950 * higher priority level. The method also updates the priority 00951 * in all buckets. 00952 */ 00953 virtual void setPriority(int); 00954 00955 /** Updates the operation being used to plan the demands. */ 00956 virtual void setOperation(Operation *); 00957 00958 /** Updates the due date of the demand. */ 00959 virtual void setDue(const Date& d) 00960 {throw DataException("Can't set due date of a forecast");} 00961 00962 virtual const MetaClass& getType() const {return *metadata;} 00963 static const MetaClass *metadata; 00964 virtual size_t getSize() const 00965 { 00966 return sizeof(Forecast) + Demand::extrasize() 00967 + 6 * sizeof(void*); // Approx. size of an entry in forecast dictionary 00968 } 00969 00970 /** Updates the value of the Customer_Then_Item_Hierarchy module 00971 * parameter. */ 00972 static void setCustomerThenItemHierarchy(bool b) 00973 {Customer_Then_Item_Hierarchy = b;} 00974 00975 /** Returns the value of the Customer_Then_Item_Hierarchy module 00976 * parameter. */ 00977 static bool getCustomerThenItemHierarchy() 00978 {return Customer_Then_Item_Hierarchy;} 00979 00980 /** Updates the value of the Match_Using_Delivery_Operation module 00981 * parameter. */ 00982 static void setMatchUsingDeliveryOperation(bool b) 00983 {Match_Using_Delivery_Operation = b;} 00984 00985 /** Returns the value of the Match_Using_Delivery_Operation module 00986 * parameter. */ 00987 static bool getMatchUsingDeliveryOperation() 00988 {return Match_Using_Delivery_Operation;} 00989 00990 /** Updates the value of the Net_Early module parameter. */ 00991 static void setNetEarly(TimePeriod t) {Net_Early = t;} 00992 00993 /** Returns the value of the Net_Early module parameter. */ 00994 static TimePeriod getNetEarly() {return Net_Early;} 00995 00996 /** Updates the value of the Net_Late module parameter. */ 00997 static void setNetLate(TimePeriod t) {Net_Late = t;} 00998 00999 /** Returns the value of the Net_Late module parameter. */ 01000 static TimePeriod getNetLate() {return Net_Late;} 01001 01002 /** Updates the value of the Forecast.smapeAlfa module parameter. */ 01003 static void setForecastSmapeAlfa(double t) 01004 { 01005 if (t<=0.5 || t>1.0) throw DataException( 01006 "Parameter Forecast.smapeAlfa must be between 0.5 and 1.0" 01007 ); 01008 Forecast_SmapeAlfa = t; 01009 } 01010 01011 /** Returns the value of the Forecast_Iterations module parameter. */ 01012 static double getForecastSmapeAlfa() {return Forecast_SmapeAlfa;} 01013 01014 /** Updates the value of the Forecast_Iterations module parameter. */ 01015 static void setForecastIterations(unsigned long t) 01016 { 01017 if (t<=0) throw DataException( 01018 "Parameter Forecast.Iterations must be bigger than 0" 01019 ); 01020 Forecast_Iterations = t; 01021 } 01022 01023 /** Returns the value of the Forecast_Iterations module parameter. */ 01024 static unsigned long getForecastIterations() {return Forecast_Iterations;} 01025 01026 /** Updates the value of the Forecast_Skip module parameter. */ 01027 static void setForecastSkip(unsigned int t) 01028 { 01029 if (t<0) throw DataException( 01030 "Parameter Forecast.Skip must be bigger than or equal to 0" 01031 ); 01032 Forecast_Skip = t; 01033 } 01034 01035 /** Return the number of timeseries values used to initialize the 01036 * algorithm. The forecast error is not counted for these buckets. 01037 */ 01038 static unsigned int getForecastSkip() {return Forecast_Skip;} 01039 01040 /** A data type to maintain a dictionary of all forecasts. */ 01041 typedef multimap < pair<const Item*, const Customer*>, Forecast* > MapOfForecasts; 01042 01043 /** Callback function, used for prevent a calendar from being deleted when it 01044 * is used for an uninitialized forecast. */ 01045 static bool callback(Calendar*, const Signal); 01046 01047 /** Return a reference to a dictionary with all forecast objects. */ 01048 static const MapOfForecasts& getForecasts() {return ForecastDictionary;} 01049 01050 virtual PyObject* getattro(const Attribute&); 01051 virtual int setattro(const Attribute&, const PythonObject&); 01052 static PyObject* timeseries(PyObject *, PyObject *); 01053 01054 private: 01055 /** Initializion of a forecast.<br> 01056 * It creates demands for each bucket of the calendar. 01057 */ 01058 void instantiate(); 01059 01060 /** A void calendar to define the time buckets. */ 01061 Calendar* calptr; 01062 01063 /** Flags whether fractional forecasts are allowed. */ 01064 bool discrete; 01065 01066 /** A dictionary of all forecasts. */ 01067 static MapOfForecasts ForecastDictionary; 01068 01069 /** Controls how we search the customer and item levels when looking for a 01070 * matching forecast for a demand. 01071 */ 01072 static bool Customer_Then_Item_Hierarchy; 01073 01074 /** Controls whether or not a matching delivery operation is required 01075 * between a matching order and its forecast. 01076 */ 01077 static bool Match_Using_Delivery_Operation; 01078 01079 /** Store the maximum time difference between an order due date and a 01080 * forecast bucket to net from.<br> 01081 * The default value is 0, meaning that only netting from the due 01082 * bucket is allowed. 01083 */ 01084 static TimePeriod Net_Late; 01085 01086 /** Store the maximum time difference between an order due date and a 01087 * forecast bucket to net from.<br> 01088 * The default value is 0, meaning that only netting from the due 01089 * bucket is allowed. 01090 */ 01091 static TimePeriod Net_Early; 01092 01093 /** Specifies the maximum number of iterations allowed for a forecast 01094 * method to tune its parameters.<br> 01095 * Only positive values are allowed and the default value is 10.<br> 01096 * Set the parameter to 1 to disable the tuning and generate a 01097 * forecast based on the user-supplied parameters. 01098 */ 01099 static unsigned long Forecast_Iterations; 01100 01101 /** Specifies how the sMAPE forecast error is weighted for different time 01102 * buckets. The SMAPE value in the most recent bucket is 1.0, and the 01103 * weight decreases exponentially for earlier buckets.<br> 01104 * Acceptable values are in the interval 0.5 and 1.0, and the default 01105 * is 0.95. 01106 */ 01107 static double Forecast_SmapeAlfa; 01108 01109 /** Number of warmup periods.<br> 01110 * These periods are used for the initialization of the algorithm 01111 * and don't count towards measuring the forecast error.<br> 01112 * The default value is 5. 01113 */ 01114 static unsigned long Forecast_Skip; 01115 }; 01116 01117 01118 /** @brief This class represents a forecast value in a time bucket. 01119 * 01120 * A forecast bucket is never manipulated or created directly. Instead, 01121 * the owning forecast manages the buckets. 01122 */ 01123 class ForecastBucket : public Demand 01124 { 01125 public: 01126 ForecastBucket(Forecast* f, Date d, Date e, double w, ForecastBucket* p) 01127 : Demand(f->getName() + " - " + string(d)), weight(w), consumed(0.0), 01128 total(0.0), timebucket(d,e), prev(p), next(NULL) 01129 { 01130 if (p) p->next = this; 01131 setOwner(f); 01132 setHidden(true); // Avoid the subdemands show up in the output 01133 setItem(&*(f->getItem())); 01134 setDue(DueAtEndOfBucket ? e : d); 01135 setPriority(f->getPriority()); 01136 setMaxLateness(f->getMaxLateness()); 01137 setMinShipment(f->getMinShipment()); 01138 setOperation(&*(f->getOperation())); 01139 initType(metadata); 01140 } 01141 virtual const MetaClass& getType() const {return *metadata;} 01142 static const MetaClass *metadata; 01143 virtual size_t getSize() const 01144 { 01145 return sizeof(ForecastBucket) + Demand::extrasize(); 01146 } 01147 01148 /** Returns the relative weight of this forecast bucket when distributing 01149 * forecast over different buckets. 01150 */ 01151 double getWeight() const {return weight;} 01152 01153 /** Returns the total, gross forecast. */ 01154 double getTotal() const {return total;} 01155 01156 /** Returns the consumed forecast. */ 01157 double getConsumed() const {return consumed;} 01158 01159 /** Update the weight of this forecasting bucket. */ 01160 void setWeight(double n) 01161 { 01162 if (n<0) 01163 throw DataException("Forecast bucket weight must be greater or equal to 0"); 01164 weight = n; 01165 } 01166 01167 /** Increment the total, gross forecast. */ 01168 void incTotal(double n) 01169 { 01170 total += n; 01171 if (total<0) total = 0.0; 01172 setQuantity(total>consumed ? total - consumed : 0.0); 01173 } 01174 01175 /** Update the total, gross forecast. */ 01176 void setTotal(double n) 01177 { 01178 if (n<0) 01179 throw DataException("Gross forecast must be greater or equal to 0"); 01180 if (total == n) return; 01181 total = n; 01182 setQuantity(total>consumed ? total - consumed : 0.0); 01183 } 01184 01185 /** Increment the consumed forecast. */ 01186 void incConsumed(double n) 01187 { 01188 consumed += n; 01189 if (consumed<0) consumed = 0.0; 01190 setQuantity(total>consumed ? total - consumed : 0.0); 01191 } 01192 01193 /** Update the consumed forecast.<br> 01194 * This field is normally updated through the forecast netting solver, but 01195 * you can use this method to update it directly. 01196 */ 01197 void setConsumed(double n) 01198 { 01199 if (n<0) 01200 throw DataException("Consumed forecast must be greater or equal to 0"); 01201 if (consumed == n) return; 01202 consumed = n; 01203 setQuantity(total>consumed ? total - consumed : 0.0); 01204 } 01205 01206 /** Return the date range for this bucket. */ 01207 DateRange getDueRange() const {return timebucket;} 01208 01209 /** Return a pointer to the next forecast bucket. */ 01210 ForecastBucket* getNextBucket() const {return next;} 01211 01212 /** Return a pointer to the previous forecast bucket. */ 01213 ForecastBucket* getPreviousBucket() const {return prev;} 01214 01215 /** A flag to mark whether forecast is due at the start or at the end of a 01216 * bucket.<br> 01217 * The default is false, ie due at the start of the bucket. 01218 */ 01219 static void setDueAtEndOfBucket(bool b) {DueAtEndOfBucket = b;} 01220 01221 virtual PyObject* getattro(const Attribute&); 01222 virtual int setattro(const Attribute&, const PythonObject&); 01223 static int initialize(); 01224 01225 private: 01226 double weight; 01227 double consumed; 01228 double total; 01229 DateRange timebucket; 01230 ForecastBucket* prev; 01231 ForecastBucket* next; 01232 01233 /** A flag to mark whether forecast is due at the start or at the end of a 01234 * bucket. */ 01235 static bool DueAtEndOfBucket; 01236 }; 01237 01238 01239 /** @brief Implementation of a forecast netting algorithm. 01240 * 01241 * As customer orders are being received they need to be deducted from 01242 * the forecast to avoid double-counting demand. 01243 * 01244 * The netting solver will process each order as follows: 01245 * - <b>First search for a matching forecast.</b><br> 01246 * A matching forecast has the same item and customer as the order.<br> 01247 * If no match is found at this level, a match is tried at higher levels 01248 * of the customer and item.<br> 01249 * Ultimately a match is tried with a empty customer or item field. 01250 * - <b>Next, the remaining net quantity of the forecast is decreased.</b><br> 01251 * The forecast bucket to be reduced is the one where the order is due.<br> 01252 * If the net quantity is already completely depleted in that bucket 01253 * the solver will look in earlier and later buckets. The parameters 01254 * Net_Early and Net_Late control the limits for the search in the 01255 * time dimension. 01256 * 01257 * The logging levels have the following meaning: 01258 * - 0: Silent operation. Default logging level. 01259 * - 1: Log demands being netted and the matching forecast. 01260 * - 2: Same as 1, plus details on forecast buckets being netted. 01261 */ 01262 class ForecastSolver : public Solver 01263 { 01264 friend class Forecast; 01265 public: 01266 /** Constructor. */ 01267 ForecastSolver(const string& n) : Solver(n) {initType(metadata);} 01268 01269 /** This method handles the search for a matching forecast, followed 01270 * by decreasing the net forecast. 01271 */ 01272 void solve(const Demand*, void* = NULL); 01273 01274 /** This is the main solver method that will appropriately call the other 01275 * solve methods.<br> 01276 */ 01277 void solve(void *v = NULL); 01278 01279 virtual const MetaClass& getType() const {return *metadata;} 01280 static const MetaClass *metadata; 01281 virtual size_t getSize() const {return sizeof(ForecastSolver);} 01282 void writeElement(XMLOutput*, const Keyword&, mode=DEFAULT) const; 01283 static int initialize(); 01284 01285 /** Callback function, used for netting orders against the forecast. */ 01286 bool callback(Demand* l, const Signal a); 01287 01288 private: 01289 /** Given a demand, this function will identify the forecast model it 01290 * links to. 01291 */ 01292 Forecast* matchDemandToForecast(const Demand* l); 01293 01294 /** Implements the netting of a customer order from a matching forecast 01295 * (and its delivery plan). 01296 */ 01297 void netDemandFromForecast(const Demand*, Forecast*); 01298 01299 /** Used for sorting demands during netting. */ 01300 struct sorter 01301 { 01302 bool operator()(const Demand* x, const Demand* y) const 01303 {return SolverMRP::demand_comparison(x,y);} 01304 }; 01305 01306 /** Used for sorting demands during netting. */ 01307 typedef multiset < Demand*, sorter > sortedDemandList; 01308 }; 01309 01310 } // End namespace 01311 01312 #endif 01313 01314