forecastsolver.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/tags/0.9.1/modules/forecast/forecastsolver.cpp $
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 #include "forecast.h"
00029 
00030 namespace module_forecast
00031 {
00032 
00033 const MetaClass *ForecastSolver::metadata;
00034 
00035 int ForecastSolver::initialize()
00036 {
00037   // Initialize the metadata
00038   metadata = new MetaClass("solver", "solver_forecast",
00039       Object::createString<ForecastSolver>);
00040 
00041   // Initialize the Python class
00042   return FreppleClass<ForecastSolver,Solver>::initialize();
00043 }
00044 
00045 
00046 bool ForecastSolver::callback(Demand* l, const Signal a)
00047 {
00048   // Call the netting function
00049   solve(l, NULL);
00050 
00051   // Always return 'okay'
00052   return true;
00053 }
00054 
00055 
00056 void ForecastSolver::writeElement(XMLOutput *o, const Keyword& tag, mode m) const
00057 {
00058   // Writing a reference
00059   if (m == REFERENCE)
00060   {
00061     o->writeElement
00062     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00063     return;
00064   }
00065 
00066   // Write the complete object
00067   if (m != NOHEADER) o->BeginObject
00068     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00069 
00070   // Write the parent class
00071   Solver::writeElement(o, tag, NOHEADER);
00072 }
00073 
00074 
00075 void ForecastSolver::solve(const Demand* l, void* v)
00076 {
00077   // Forecast don't net themselves, and hidden demands either...
00078   if (!l || dynamic_cast<const Forecast*>(l) || l->getHidden()) return;
00079 
00080   // Message
00081   if (getLogLevel()>0)
00082     logger << "  Netting of demand '" << l << "'  ('" << l->getCustomer()
00083         << "','" << l->getItem() << "', '" << l->getDeliveryOperation()
00084         << "'): " << l->getDue() << ", " << l->getQuantity() << endl;
00085 
00086   // Find a matching forecast
00087   Forecast *fcst = matchDemandToForecast(l);
00088 
00089   if (!fcst)
00090   {
00091     // Message
00092     if (getLogLevel()>0)
00093       logger << "    No matching forecast available" << endl;
00094     return;
00095   }
00096   else if (getLogLevel()>0)
00097     logger << "    Matching forecast: " << fcst << endl;
00098 
00099   // Netting the order from the forecast
00100   netDemandFromForecast(l,fcst);
00101 }
00102 
00103 
00104 void ForecastSolver::solve(void *v)
00105 {
00106   // Sort the demands using the same sort function as used for planning.
00107   // Note: the memory consumption of the sorted list can be significant
00108   sortedDemandList l;
00109   for (Demand::iterator i = Demand::begin(); i != Demand::end(); ++i)
00110     // Only sort non-forecast demand.
00111     if (!dynamic_cast<Forecast*>(&*i)
00112         && !dynamic_cast<ForecastBucket*>(&*i))
00113       l.insert(&*i);
00114 
00115   // Netting loop
00116   for(sortedDemandList::iterator i = l.begin(); i != l.end(); ++i)
00117     try {solve(*i, NULL);}
00118     catch (...)
00119     {
00120       // Error message
00121       logger << "Error: Caught an exception while netting demand '"
00122           << (*i)->getName() << "':" << endl;
00123       try {throw;}
00124       catch (const bad_exception&) {logger << "  bad exception" << endl;}
00125       catch (const exception& e) {logger << "  " << e.what() << endl;}
00126       catch (...) {logger << "  Unknown type" << endl;}
00127     }
00128 }
00129 
00130 
00131 Forecast* ForecastSolver::matchDemandToForecast(const Demand* l)
00132 {
00133   pair<const Item*, const Customer*> key
00134     = make_pair(&*(l->getItem()), &*(l->getCustomer()));
00135 
00136   do  // Loop through second dimension
00137   {
00138     do // Loop through first dimension
00139     {
00140       Forecast::MapOfForecasts::iterator x = Forecast::ForecastDictionary.lower_bound(key);
00141 
00142       // Loop through all matching keys
00143       while (x != Forecast::ForecastDictionary.end() && x->first == key)
00144       {
00145         if (!Forecast::getMatchUsingDeliveryOperation()
00146             || x->second->getDeliveryOperation() == l->getDeliveryOperation())
00147           // Bingo! Found a matching key, if required plus matching delivery operation
00148           return x->second;
00149         else
00150           ++ x;
00151       }
00152       // Not found: try a higher level match in first dimension
00153       if (Forecast::Customer_Then_Item_Hierarchy)
00154       {
00155         // First customer hierarchy
00156         if (key.second) key.second = key.second->getOwner();
00157         else break;
00158       }
00159       else
00160       {
00161         // First item hierarchy
00162         if (key.first) key.first = key.first->getOwner();
00163         else break;
00164       }
00165     }
00166     while (true);
00167 
00168     // Not found at any level in the first dimension
00169 
00170     // Try a new level in the second dimension
00171     if (Forecast::Customer_Then_Item_Hierarchy)
00172     {
00173       // Second is item
00174       if (key.first) key.first = key.first->getOwner();
00175       else return NULL;
00176       // Reset to lowest level in the first dimension again
00177       key.second = &*(l->getCustomer());
00178     }
00179     else
00180     {
00181       // Second is customer
00182       if (key.second) key.second = key.second->getOwner();
00183       else return NULL;
00184       // Reset to lowest level in the first dimension again
00185       key.first = &*(l->getItem());
00186     }
00187   }
00188   while (true);
00189 }
00190 
00191 
00192 void ForecastSolver::netDemandFromForecast(const Demand* dmd, Forecast* fcst)
00193 {
00194 
00195   // Empty forecast model
00196   if (!fcst->isGroup())
00197   {
00198     if (getLogLevel()>1)
00199       logger << "    Empty forecast model" << endl;
00200     if (getLogLevel()>0 && dmd->getQuantity()>0.0)
00201       logger << "    Remains " << dmd->getQuantity() << " that can't be netted" << endl;
00202     return;
00203   }
00204 
00205   // Find the bucket with the due date
00206   ForecastBucket* zerobucket = NULL;
00207   for (Forecast::memberIterator i = fcst->beginMember(); i != fcst->end(); ++i)
00208   {
00209     zerobucket = dynamic_cast<ForecastBucket*>(&*i);
00210     if (zerobucket && zerobucket->getDueRange().within(dmd->getDue())) break;
00211   }
00212   if (!zerobucket)
00213     throw LogicException("Can't find forecast bucket for "
00214         + string(dmd->getDue()) + " in forecast '" + fcst->getName() + "'");
00215 
00216   // Netting - looking for time buckets with net forecast
00217   double remaining = dmd->getQuantity();
00218   ForecastBucket* curbucket = zerobucket;
00219   bool backward = true;
00220   while ( remaining > 0 && curbucket
00221       && (dmd->getDue()-Forecast::getNetEarly() < curbucket->getDueRange().getEnd())
00222       && (dmd->getDue()+Forecast::getNetLate() >= curbucket->getDueRange().getStart())
00223         )
00224   {
00225     // Net from the current bucket
00226     double available = curbucket->getQuantity();
00227     if (available > 0)
00228     {
00229       if (available >= remaining)
00230       {
00231         // Partially consume a bucket
00232         if (getLogLevel()>1)
00233           logger << "    Consuming " << remaining << " from bucket "
00234               << curbucket->getDueRange() << " (" << available
00235               << " available)" << endl;
00236         curbucket->incConsumed(remaining);
00237         remaining = 0;
00238       }
00239       else
00240       {
00241         // Completely consume a bucket
00242         if (getLogLevel()>1)
00243           logger << "    Consuming " << available << " from bucket "
00244               << curbucket->getDueRange() << " (" << available
00245               << " available)" << endl;
00246         remaining -= available;
00247         curbucket->incConsumed(available);
00248       }
00249     }
00250     else if (getLogLevel()>1)
00251       logger << "    Nothing available in bucket "
00252           << curbucket->getDueRange() << endl;
00253 
00254     // Find the next forecast bucket
00255     if (backward)
00256     {
00257       // Moving to earlier buckets
00258       curbucket = curbucket->getPreviousBucket();
00259       if (!curbucket)
00260       {
00261         backward = false;
00262         curbucket = zerobucket->getNextBucket();
00263       }
00264     }
00265     else
00266       // Moving to later buckets
00267       curbucket = curbucket->getNextBucket();
00268   }
00269 
00270   // Quantity for which no bucket is found
00271   if (remaining > 0 && getLogLevel()>0)
00272     logger << "    Remains " << remaining << " that can't be netted" << endl;
00273 
00274 }
00275 
00276 }       // end namespace

Documentation generated for frePPLe by  doxygen