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