forecast.h
Go to the documentation of this file.00001 /*************************************************************************** 00002 file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/trunk/modules/forecast/forecast.h $ 00003 version : $LastChangedRevision: 1300 $ $LastChangedBy: jdetaeye $ 00004 date : $LastChangedDate: 2010-07-06 08:08:34 +0200 (Tue, 06 Jul 2010) $ 00005 ***************************************************************************/ 00006 00007 /*************************************************************************** 00008 * * 00009 * Copyright (C) 2007-2010 by Johan De Taeye * 00010 * * 00011 * This library is free software; you can redistribute it and/or modify it * 00012 * under the terms of the GNU Lesser General Public License as published * 00013 * by the Free Software Foundation; either version 2.1 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 * This library is distributed in the hope that it will be useful, * 00017 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * 00019 * General Public License for more details. * 00020 * * 00021 * You should have received a copy of the GNU Lesser General Public * 00022 * License along with this library; if not, write to the Free Software * 00023 * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * 00024 * USA * 00025 * * 00026 ***************************************************************************/ 00027 00028 /** @file forecast.h 00029 * @brief Header file for the module forecast. 00030 * 00031 * @namespace module_forecast 00032 * @brief Module for representing forecast. 00033 * 00034 * The forecast module provides the following functionality: 00035 * 00036 * - A <b>new demand type</b> to model forecasts.<br> 00037 * A forecast demand is bucketized. A demand is automatically 00038 * created for each time bucket.<br> 00039 * A calendar is used to define the time buckets to be used. 00040 * 00041 * - Functionality for <b>distributing / profiling</b> forecast numbers 00042 * into time buckets used for planning.<br> 00043 * This functionality is typically used to translate between the time 00044 * granularity of the sales department (which creates a sales forecast 00045 * per e.g. calendar month) and the manufacturing department (which 00046 * creates manufacturing and procurement plans in weekly or daily buckets 00047 * ).<br> 00048 * Another usage is to model a delivery date profile of the customers. 00049 * Each bucket has a weight that is used to model situations where the 00050 * demand is not evenly spread across buckets: e.g. when more orders are 00051 * expected due on a monday than on a friday, or when a peak of orders is 00052 * expected for delivery near the end of a month. 00053 * 00054 * - A solver for <b>netting orders from the forecast</b>.<br> 00055 * As customer orders are being received they need to be deducted from 00056 * the forecast to avoid double-counting demand.<br> 00057 * The netting solver will for each order search for a matching forecast 00058 * and reduce the remaining net quantity of the forecast. 00059 * 00060 * - A forecasting algorithm to <b>extrapolate historical demand data to 00061 * the future</b>.<br> 00062 * The following classical forecasting methods are implemented: 00063 * - <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 CommandLoadLibrary::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 void writeElement(XMLOutput*, const Keyword&, mode=DEFAULT) const; 00901 void endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement); 00902 void beginElement(XMLInput& pIn, const Attribute& pAttr); 00903 static int initialize(); 00904 00905 /** Returns whether fractional forecasts are allowed or not.<br> 00906 * The default is true. 00907 */ 00908 bool getDiscrete() const {return discrete;} 00909 00910 /** Updates forecast discreteness flag. */ 00911 void setDiscrete(const bool b); 00912 00913 /** Update the item to be planned. */ 00914 virtual void setItem(Item*); 00915 00916 /** Update the customer. */ 00917 virtual void setCustomer(Customer*); 00918 00919 /* Update the maximum allowed lateness for planning. */ 00920 void setMaxLateness(TimePeriod); 00921 00922 /* Update the minumum allowed shipment quantity for planning. */ 00923 void setMinShipment(double); 00924 00925 /** Specify a bucket calendar for the forecast. Once forecasted 00926 * quantities have been entered for the forecast, the calendar 00927 * can't be updated any more. */ 00928 virtual void setCalendar(Calendar*); 00929 00930 /** Returns a reference to the calendar used for this forecast. */ 00931 Calendar* getCalendar() const {return calptr;} 00932 00933 /** Generate a forecast value based on historical demand data.<br> 00934 * This method will call the different forecasting methods and select the 00935 * method with the lowest smape-error.<br> 00936 * It then asks the selected forecast method to generate a value for 00937 * each of the time buckets passed. 00938 */ 00939 void generateFutureValues 00940 (const double[], unsigned int, const Date[], unsigned int, bool=false); 00941 00942 /** Updates the due date of the demand. Lower numbers indicate a 00943 * higher priority level. The method also updates the priority 00944 * in all buckets. 00945 */ 00946 virtual void setPriority(int); 00947 00948 /** Updates the operation being used to plan the demands. */ 00949 virtual void setOperation(Operation *); 00950 00951 /** Updates the due date of the demand. */ 00952 virtual void setDue(const Date& d) 00953 {throw DataException("Can't set due date of a forecast");} 00954 00955 virtual const MetaClass& getType() const {return *metadata;} 00956 static const MetaClass *metadata; 00957 virtual size_t getSize() const 00958 { 00959 return sizeof(Forecast) + Demand::extrasize() 00960 + 6 * sizeof(void*); // Approx. size of an entry in forecast dictionary 00961 } 00962 00963 /** Updates the value of the Customer_Then_Item_Hierarchy module 00964 * parameter. */ 00965 static void setCustomerThenItemHierarchy(bool b) 00966 {Customer_Then_Item_Hierarchy = b;} 00967 00968 /** Returns the value of the Customer_Then_Item_Hierarchy module 00969 * parameter. */ 00970 static bool getCustomerThenItemHierarchy() 00971 {return Customer_Then_Item_Hierarchy;} 00972 00973 /** Updates the value of the Match_Using_Delivery_Operation module 00974 * parameter. */ 00975 static void setMatchUsingDeliveryOperation(bool b) 00976 {Match_Using_Delivery_Operation = b;} 00977 00978 /** Returns the value of the Match_Using_Delivery_Operation module 00979 * parameter. */ 00980 static bool getMatchUsingDeliveryOperation() 00981 {return Match_Using_Delivery_Operation;} 00982 00983 /** Updates the value of the Net_Early module parameter. */ 00984 static void setNetEarly(TimePeriod t) {Net_Early = t;} 00985 00986 /** Returns the value of the Net_Early module parameter. */ 00987 static TimePeriod getNetEarly() {return Net_Early;} 00988 00989 /** Updates the value of the Net_Late module parameter. */ 00990 static void setNetLate(TimePeriod t) {Net_Late = t;} 00991 00992 /** Returns the value of the Net_Late module parameter. */ 00993 static TimePeriod getNetLate() {return Net_Late;} 00994 00995 /** Updates the value of the Forecast.smapeAlfa module parameter. */ 00996 static void setForecastSmapeAlfa(double t) 00997 { 00998 if (t<=0.5 || t>1.0) throw DataException( 00999 "Parameter Forecast.smapeAlfa must be between 0.5 and 1.0" 01000 ); 01001 Forecast_SmapeAlfa = t; 01002 } 01003 01004 /** Returns the value of the Forecast_Iterations module parameter. */ 01005 static double getForecastSmapeAlfa() {return Forecast_SmapeAlfa;} 01006 01007 /** Updates the value of the Forecast_Iterations module parameter. */ 01008 static void setForecastIterations(unsigned long t) 01009 { 01010 if (t<=0) throw DataException( 01011 "Parameter Forecast.Iterations must be bigger than 0" 01012 ); 01013 Forecast_Iterations = t; 01014 } 01015 01016 /** Returns the value of the Forecast_Iterations module parameter. */ 01017 static unsigned long getForecastIterations() {return Forecast_Iterations;} 01018 01019 /** Updates the value of the Forecast_Skip module parameter. */ 01020 static void setForecastSkip(unsigned int t) 01021 { 01022 if (t<0) throw DataException( 01023 "Parameter Forecast.Skip must be bigger than or equal to 0" 01024 ); 01025 Forecast_Skip = t; 01026 } 01027 01028 /** Return the number of timeseries values used to initialize the 01029 * algorithm. The forecast error is not counted for these buckets. 01030 */ 01031 static unsigned int getForecastSkip() {return Forecast_Skip;} 01032 01033 /** A data type to maintain a dictionary of all forecasts. */ 01034 typedef multimap < pair<const Item*, const Customer*>, Forecast* > MapOfForecasts; 01035 01036 /** Callback function, used for prevent a calendar from being deleted when it 01037 * is used for an uninitialized forecast. */ 01038 static bool callback(Calendar*, const Signal); 01039 01040 /** Return a reference to a dictionary with all forecast objects. */ 01041 static const MapOfForecasts& getForecasts() {return ForecastDictionary;} 01042 01043 virtual PyObject* getattro(const Attribute&); 01044 virtual int setattro(const Attribute&, const PythonObject&); 01045 static PyObject* timeseries(PyObject *, PyObject *); 01046 01047 private: 01048 /** Initializion of a forecast.<br> 01049 * It creates demands for each bucket of the calendar. 01050 */ 01051 void instantiate(); 01052 01053 /** A void calendar to define the time buckets. */ 01054 Calendar* calptr; 01055 01056 /** Flags whether fractional forecasts are allowed. */ 01057 bool discrete; 01058 01059 /** A dictionary of all forecasts. */ 01060 static MapOfForecasts ForecastDictionary; 01061 01062 /** Controls how we search the customer and item levels when looking for a 01063 * matching forecast for a demand. 01064 */ 01065 static bool Customer_Then_Item_Hierarchy; 01066 01067 /** Controls whether or not a matching delivery operation is required 01068 * between a matching order and its forecast. 01069 */ 01070 static bool Match_Using_Delivery_Operation; 01071 01072 /** Store the maximum time difference between an order due date and a 01073 * forecast bucket to net from.<br> 01074 * The default value is 0, meaning that only netting from the due 01075 * bucket is allowed. 01076 */ 01077 static TimePeriod Net_Late; 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_Early; 01085 01086 /** Specifies the maximum number of iterations allowed for a forecast 01087 * method to tune its parameters.<br> 01088 * Only positive values are allowed and the default value is 10.<br> 01089 * Set the parameter to 1 to disable the tuning and generate a 01090 * forecast based on the user-supplied parameters. 01091 */ 01092 static unsigned long Forecast_Iterations; 01093 01094 /** Specifies how the sMAPE forecast error is weighted for different time 01095 * buckets. The SMAPE value in the most recent bucket is 1.0, and the 01096 * weight decreases exponentially for earlier buckets.<br> 01097 * Acceptable values are in the interval 0.5 and 1.0, and the default 01098 * is 0.95. 01099 */ 01100 static double Forecast_SmapeAlfa; 01101 01102 /** Number of warmup periods.<br> 01103 * These periods are used for the initialization of the algorithm 01104 * and don't count towards measuring the forecast error.<br> 01105 * The default value is 5. 01106 */ 01107 static unsigned long Forecast_Skip; 01108 }; 01109 01110 01111 /** @brief This class represents a forecast value in a time bucket. 01112 * 01113 * A forecast bucket is never manipulated or created directly. Instead, 01114 * the owning forecast manages the buckets. 01115 */ 01116 class ForecastBucket : public Demand 01117 { 01118 public: 01119 ForecastBucket(Forecast* f, Date d, Date e, double w, ForecastBucket* p) 01120 : Demand(f->getName() + " - " + string(d)), weight(w), consumed(0.0), 01121 total(0.0), timebucket(d,e), prev(p), next(NULL) 01122 { 01123 if (p) p->next = this; 01124 setOwner(f); 01125 setHidden(true); // Avoid the subdemands show up in the output 01126 setItem(&*(f->getItem())); 01127 setDue(DueAtEndOfBucket ? e : d); 01128 setPriority(f->getPriority()); 01129 setMaxLateness(f->getMaxLateness()); 01130 setMinShipment(f->getMinShipment()); 01131 setOperation(&*(f->getOperation())); 01132 initType(metadata); 01133 } 01134 virtual const MetaClass& getType() const {return *metadata;} 01135 static const MetaClass *metadata; 01136 virtual size_t getSize() const 01137 { 01138 return sizeof(ForecastBucket) + Demand::extrasize(); 01139 } 01140 01141 /** Returns the relative weight of this forecast bucket when distributing 01142 * forecast over different buckets. 01143 */ 01144 double getWeight() const {return weight;} 01145 01146 /** Returns the total, gross forecast. */ 01147 double getTotal() const {return total;} 01148 01149 /** Returns the consumed forecast. */ 01150 double getConsumed() const {return consumed;} 01151 01152 /** Update the weight of this forecasting bucket. */ 01153 void setWeight(double n) 01154 { 01155 if (n<0) 01156 throw DataException("Forecast bucket weight must be greater or equal to 0"); 01157 weight = n; 01158 } 01159 01160 /** Increment the total, gross forecast. */ 01161 void incTotal(double n) 01162 { 01163 total += n; 01164 if (total<0) total = 0.0; 01165 setQuantity(total>consumed ? total - consumed : 0.0); 01166 } 01167 01168 /** Update the total, gross forecast. */ 01169 void setTotal(double n) 01170 { 01171 if (n<0) 01172 throw DataException("Gross forecast must be greater or equal to 0"); 01173 if (total == n) return; 01174 total = n; 01175 setQuantity(total>consumed ? total - consumed : 0.0); 01176 } 01177 01178 /** Increment the consumed forecast. */ 01179 void incConsumed(double n) 01180 { 01181 consumed += n; 01182 if (consumed<0) consumed = 0.0; 01183 setQuantity(total>consumed ? total - consumed : 0.0); 01184 } 01185 01186 /** Update the consumed forecast.<br> 01187 * This field is normally updated through the forecast netting solver, but 01188 * you can use this method to update it directly. 01189 */ 01190 void setConsumed(double n) 01191 { 01192 if (n<0) 01193 throw DataException("Consumed forecast must be greater or equal to 0"); 01194 if (consumed == n) return; 01195 consumed = n; 01196 setQuantity(total>consumed ? total - consumed : 0.0); 01197 } 01198 01199 /** Return the date range for this bucket. */ 01200 DateRange getDueRange() const {return timebucket;} 01201 01202 /** Return a pointer to the next forecast bucket. */ 01203 ForecastBucket* getNextBucket() const {return next;} 01204 01205 /** Return a pointer to the previous forecast bucket. */ 01206 ForecastBucket* getPreviousBucket() const {return prev;} 01207 01208 /** A flag to mark whether forecast is due at the start or at the end of a 01209 * bucket.<br> 01210 * The default is false, ie due at the start of the bucket. 01211 */ 01212 static void setDueAtEndOfBucket(bool b) {DueAtEndOfBucket = b;} 01213 01214 virtual PyObject* getattro(const Attribute&); 01215 virtual int setattro(const Attribute&, const PythonObject&); 01216 static int initialize(); 01217 01218 private: 01219 double weight; 01220 double consumed; 01221 double total; 01222 DateRange timebucket; 01223 ForecastBucket* prev; 01224 ForecastBucket* next; 01225 01226 /** A flag to mark whether forecast is due at the start or at the end of a 01227 * bucket. */ 01228 static bool DueAtEndOfBucket; 01229 }; 01230 01231 01232 /** @brief Implementation of a forecast netting algorithm. 01233 * 01234 * As customer orders are being received they need to be deducted from 01235 * the forecast to avoid double-counting demand. 01236 * 01237 * The netting solver will process each order as follows: 01238 * - <b>First search for a matching forecast.</b><br> 01239 * A matching forecast has the same item and customer as the order.<br> 01240 * If no match is found at this level, a match is tried at higher levels 01241 * of the customer and item.<br> 01242 * Ultimately a match is tried with a empty customer or item field. 01243 * - <b>Next, the remaining net quantity of the forecast is decreased.</b><br> 01244 * The forecast bucket to be reduced is the one where the order is due.<br> 01245 * If the net quantity is already completely depleted in that bucket 01246 * the solver will look in earlier and later buckets. The parameters 01247 * Net_Early and Net_Late control the limits for the search in the 01248 * time dimension. 01249 * 01250 * The logging levels have the following meaning: 01251 * - 0: Silent operation. Default logging level. 01252 * - 1: Log demands being netted and the matching forecast. 01253 * - 2: Same as 1, plus details on forecast buckets being netted. 01254 */ 01255 class ForecastSolver : public Solver 01256 { 01257 friend class Forecast; 01258 public: 01259 /** Constructor. */ 01260 ForecastSolver(const string& n) : Solver(n) {initType(metadata);} 01261 01262 /** This method handles the search for a matching forecast, followed 01263 * by decreasing the net forecast. 01264 */ 01265 void solve(const Demand*, void* = NULL); 01266 01267 /** This is the main solver method that will appropriately call the other 01268 * solve methods.<br> 01269 */ 01270 void solve(void *v = NULL); 01271 01272 virtual const MetaClass& getType() const {return *metadata;} 01273 static const MetaClass *metadata; 01274 virtual size_t getSize() const {return sizeof(ForecastSolver);} 01275 void writeElement(XMLOutput*, const Keyword&, mode=DEFAULT) const; 01276 static int initialize(); 01277 01278 /** Callback function, used for netting orders against the forecast. */ 01279 bool callback(Demand* l, const Signal a); 01280 01281 private: 01282 /** Given a demand, this function will identify the forecast model it 01283 * links to. 01284 */ 01285 Forecast* matchDemandToForecast(const Demand* l); 01286 01287 /** Implements the netting of a customer order from a matching forecast 01288 * (and its delivery plan). 01289 */ 01290 void netDemandFromForecast(const Demand*, Forecast*); 01291 01292 /** Used for sorting demands during netting. */ 01293 struct sorter 01294 { 01295 bool operator()(const Demand* x, const Demand* y) const 01296 {return SolverMRP::demand_comparison(x,y);} 01297 }; 01298 01299 /** Used for sorting demands during netting. */ 01300 typedef multiset < Demand*, sorter > sortedDemandList; 01301 }; 01302 01303 } // End namespace 01304 01305 #endif 01306 01307
Documentation generated for frePPLe by
