00001 /*************************************************************************** 00002 file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/tags/0.8.0/src/solver/solverload.cpp $ 00003 version : $LastChangedRevision: 1185 $ $LastChangedBy: jdetaeye $ 00004 date : $LastChangedDate: 2010-02-21 01:00:40 +0100 (Sun, 21 Feb 2010) $ 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * 00024 * USA * 00025 * * 00026 ***************************************************************************/ 00027 00028 #define FREPPLE_CORE 00029 #include "frepple/solver.h" 00030 00031 namespace frepple 00032 { 00033 00034 00035 bool sortLoad(const Load* lhs, const Load* rhs) 00036 { 00037 return lhs->getPriority() < rhs->getPriority(); 00038 } 00039 00040 00041 void SolverMRP::solve(const Load* l, void* v) 00042 { 00043 // Note: This method is only called for decrease loadplans and for the leading 00044 // load of an alternate group. See SolverMRP::checkOperation 00045 SolverMRPdata* data = static_cast<SolverMRPdata*>(v); 00046 unsigned int loglevel = data->getSolver()->getLogLevel(); 00047 00048 if (data->state->q_qty >= 0.0) 00049 { 00050 // The loadplan is an increase in size, and the resource solver only needs 00051 // the decreases. 00052 // Or, it's a zero quantity loadplan. E.g. because it is not effective. 00053 data->state->a_qty = data->state->q_qty; 00054 data->state->a_date = data->state->q_date; 00055 return; 00056 } 00057 00058 if (!l->hasAlternates() && !l->getAlternate()) 00059 { 00060 // CASE I: It is not an alternate load. 00061 // Delegate the answer to the resource 00062 l->getResource()->solve(*this,v); 00063 if (data->state->a_qty == 0.0 00064 && data->state->q_operationplan->getQuantity() != 0.0) 00065 // In case of a zero reply, we resize the operationplan to 0 right away. 00066 // This is required to make sure that the buffer inventory profile also 00067 // respects this answer. 00068 data->state->q_operationplan->setQuantity(0.0); 00069 return; 00070 } 00071 00072 // CASE II: It is an alternate load. 00073 // We ask each alternate load in order of priority till we find a flow 00074 // that has a non-zero reply. 00075 00076 // 1) collect a list of alternates 00077 list<const Load*> thealternates; 00078 const Load *x = l->hasAlternates() ? l : l->getAlternate(); 00079 SearchMode search = l->getSearch(); 00080 for (Operation::loadlist::const_iterator i = l->getOperation()->getLoads().begin(); 00081 i != l->getOperation()->getLoads().end(); ++i) 00082 if ((i->getAlternate() == x || &*i == x) 00083 && i->getEffective().within(data->state->q_loadplan->getDate())) 00084 thealternates.push_back(&*i); 00085 00086 // 2) Sort the list 00087 thealternates.sort(sortLoad); // @todo cpu-intensive - better is to maintain the list in the correct order 00088 00089 // 3) Control the planning mode 00090 bool originalPlanningMode = data->constrainedPlanning; 00091 data->constrainedPlanning = true; 00092 00093 // 4) Loop through all alternates or till we find a non-zero 00094 // reply (priority search) 00095 Date min_next_date(Date::infiniteFuture); 00096 LoadPlan *lplan = data->state->q_loadplan; 00097 double bestAlternateValue = DBL_MAX; 00098 double bestAlternateQuantity = DBL_MIN; 00099 const Load* bestAlternateSelection = NULL; 00100 double beforeCost = data->state->a_cost; 00101 double beforePenalty = data->state->a_penalty; 00102 OperationPlanState originalOpplan(lplan->getOperationPlan()); 00103 for (list<const Load*>::const_iterator i = thealternates.begin(); 00104 i != thealternates.end();) 00105 { 00106 const Load *curload = *i; 00107 data->state->q_loadplan = lplan; // because q_loadplan can change! 00108 00109 // 4a) Switch to this load 00110 if (lplan->getLoad() != curload) lplan->setLoad(curload); 00111 lplan->getOperationPlan()->restore(originalOpplan); 00112 data->state->q_qty = lplan->getQuantity(); 00113 data->state->q_date = lplan->getDate(); 00114 00115 // 4b) Ask the resource 00116 Command* topcommand = data->getLastCommand(); 00117 if (search == PRIORITY) 00118 curload->getResource()->solve(*this,data); 00119 else 00120 { 00121 data->getSolver()->setLogLevel(0); 00122 try {curload->getResource()->solve(*this,data);} 00123 catch (...) 00124 { 00125 data->getSolver()->setLogLevel(loglevel); 00126 // Restore the planning mode 00127 data->constrainedPlanning = originalPlanningMode; 00128 throw; 00129 } 00130 data->getSolver()->setLogLevel(loglevel); 00131 } 00132 00133 // 4c) Evaluate the result 00134 if (data->state->a_qty > ROUNDING_ERROR 00135 && lplan->getOperationPlan()->getQuantity() > 0) 00136 { 00137 if (search == PRIORITY) 00138 { 00139 // Priority search: accept any non-zero reply 00140 // Restore the planning mode 00141 data->constrainedPlanning = originalPlanningMode; 00142 return; 00143 } 00144 else 00145 { 00146 // Other search modes: evaluate all 00147 double deltaCost = data->state->a_cost - beforeCost; 00148 double deltaPenalty = data->state->a_penalty - beforePenalty; 00149 // Message 00150 if (loglevel>1 && search != PRIORITY) 00151 logger << indent(l->getOperation()->getLevel()) << " Operation '" 00152 << l->getOperation()->getName() << "' evaluates alternate '" 00153 << curload->getResource() << "': cost " << deltaCost 00154 << ", penalty " << deltaPenalty << endl; 00155 if (deltaCost < ROUNDING_ERROR && deltaPenalty < ROUNDING_ERROR) 00156 { 00157 // Zero cost and zero penalty on this alternate. It won't get any better 00158 // than this, so we accept this alternate. 00159 if (loglevel) 00160 logger << indent(l->getOperation()->getLevel()) << " Operation '" 00161 << l->getOperation()->getName() << "' chooses alternate '" 00162 << curload->getResource() << "' " << search << endl; 00163 // Restore the planning mode 00164 data->constrainedPlanning = originalPlanningMode; 00165 return; 00166 } 00167 data->state->a_cost = beforeCost; 00168 data->state->a_penalty = beforePenalty; 00169 double val; 00170 switch (search) 00171 { 00172 case MINCOST: 00173 val = deltaCost / lplan->getOperationPlan()->getQuantity(); 00174 break; 00175 case MINPENALTY: 00176 val = deltaPenalty / lplan->getOperationPlan()->getQuantity(); 00177 break; 00178 case MINCOSTPENALTY: 00179 val = (deltaCost + deltaPenalty) / lplan->getOperationPlan()->getQuantity(); 00180 break; 00181 default: 00182 LogicException("Unsupported search mode for alternate load"); 00183 } 00184 if (val + ROUNDING_ERROR < bestAlternateValue 00185 || (fabs(val - bestAlternateValue) < ROUNDING_ERROR 00186 && lplan->getOperationPlan()->getQuantity() > bestAlternateQuantity)) 00187 { 00188 // Found a better alternate 00189 bestAlternateValue = val; 00190 bestAlternateSelection = curload; 00191 bestAlternateQuantity = lplan->getOperationPlan()->getQuantity(); 00192 } 00193 } 00194 } 00195 else if (loglevel>1 && search != PRIORITY) 00196 logger << indent(l->getOperation()->getLevel()) << " Operation '" 00197 << l->getOperation()->getName() << "' evaluates alternate '" 00198 << curload->getResource() << "': not available before " 00199 << data->state->a_date << endl; 00200 00201 // 4d) Undo the plan on the alternate 00202 data->undo(topcommand); 00203 00204 // 4e) Prepare for the next alternate 00205 if (data->state->a_date < min_next_date) 00206 min_next_date = data->state->a_date; 00207 if (++i != thealternates.end() && loglevel>1 && search == PRIORITY) 00208 logger << indent(curload->getOperation()->getLevel()) << " Alternate load switches from '" 00209 << curload->getResource()->getName() << "' to '" 00210 << (*i)->getResource()->getName() << "'" << endl; 00211 } 00212 00213 // 5) Unconstrained plan: plan on the first alternate 00214 if (!originalPlanningMode && !(search != PRIORITY && bestAlternateSelection)) 00215 { 00216 // Switch to unconstrained planning 00217 data->constrainedPlanning = false; 00218 bestAlternateSelection = *(thealternates.begin()); 00219 } 00220 00221 // 6) Finally replan on the best alternate 00222 if (!originalPlanningMode || (search != PRIORITY && bestAlternateSelection)) 00223 { 00224 // Message 00225 if (loglevel) 00226 logger << indent(l->getOperation()->getLevel()) << " Operation '" 00227 << l->getOperation()->getName() << "' chooses alternate '" 00228 << bestAlternateSelection->getResource() << "' " << search << endl; 00229 00230 // Switch back 00231 data->state->q_loadplan = lplan; // because q_loadplan can change! 00232 data->state->a_cost = beforeCost; 00233 data->state->a_penalty = beforePenalty; 00234 if (lplan->getLoad() != bestAlternateSelection) 00235 lplan->setLoad(bestAlternateSelection); 00236 lplan->getOperationPlan()->restore(originalOpplan); 00237 data->state->q_qty = lplan->getQuantity(); 00238 data->state->q_date = lplan->getDate(); 00239 //xxxif (originalPlanningMode) 00240 bestAlternateSelection->getResource()->solve(*this,data); 00241 00242 // Restore the planning mode 00243 data->constrainedPlanning = originalPlanningMode; 00244 return; 00245 } 00246 00247 // 7) No alternate gave a good result 00248 data->state->a_date = min_next_date; 00249 data->state->a_qty = 0; 00250 // Restore the planning mode 00251 data->constrainedPlanning = originalPlanningMode; 00252 if (lplan->getOperationPlan()->getQuantity() != 0.0) 00253 // In case of a zero reply, we resize the operationplan to 0 right away. 00254 // This is required to make sure that the buffer inventory profile also 00255 // respects this answer. 00256 lplan->getOperationPlan()->setQuantity(0.0); 00257 if (loglevel>1) 00258 logger << indent(lplan->getOperationPlan()->getOperation()->getLevel()) << 00259 " Alternate load doesn't find supply on any alternate : " 00260 << data->state->a_qty << " " << data->state->a_date << endl; 00261 } 00262 00263 00264 }