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