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

Documentation generated for frePPLe by  doxygen