forecastsolver.cpp

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

Documentation generated by  doxygen