代碼 | 自適應大鄰域搜索系列之(3) - Destroy和Repair方法代碼實現解析
前言
上一篇文章中我們具體解剖了ALNS類的具體代碼實現過程,不過也留下了很多大坑。接下來的文章基本都是“填坑”了,把各個模塊一一展現解析給大家。不過礙於文章篇幅等原因呢,也不會每一行代碼都進行講解,那些簡單的代碼就跳過了,相信大家也能一眼就看懂。好了,廢話不多說,開始幹活吧。
01 照舊總體概述
前面我們提到,ALNS中的重中之重就是Destroy和Repair方法了,在整一個ALNS框架中呢,涉及這倆貨的有Destroy和Repair方法的具體實現、Destroy和Repair方法管理(包括各個Destroy和Repair方法權重分配、成績打分、按權重選擇哪個Destroy和Repair方法等操作)。所以在這次的ALNS代碼中呢,這倆貨的代碼實現呢也分為兩個模塊:
- Destroy和Repair方法具體實現模塊
- Destroy和Repair方法管理模塊
下面我們將對其進行一一講解,不知道大家小板凳準備好了沒有。
02 Destroy和Repair方法具體實現
關於Destroy和Repair方法,由三個類組成,分別是AOperator、ADestroyOperator、ARepairOperator。它們之間的繼承派生關系如下:
下面對其一一講解。
2.1 AOperator
這是一個基礎父類,它抽象了Destroy和Repair方法共有的一些方法和特征(成績、權重、名稱等等),然後Destroy和Repair方法再各自繼承於它,實現自己的功能模塊。成員變量已經註釋清楚了,關於protected的一個成員noise噪聲模式會在後面講到。其他的方法也很簡單就不做多解釋了。
class AOperator { private: //! Total number of calls during the process. size_t totalNumberOfCalls; //! Number of calls since the last evaluation. size_t nbCallsSinceLastEval; //! score of the operator. double score; //! weight of the operator. double weight; //! designation of the operator. std::string operatorName; protected: //! Indicate if the operator is used in noise mode or not. bool noise; public: //! Constructor. AOperator(std::string name){ operatorName = name; init(); } //! Destructor. virtual ~AOperator(){}; //! Initialize the values of the numbers of calls. void init() { totalNumberOfCalls = 0; nbCallsSinceLastEval = 0; score = 0; weight = 1; } //! reset the number of calls since last eval. void resetNumberOfCalls() { nbCallsSinceLastEval = 0; } //! Simple getter. //! \return the total number of calls to the operator since //! the beginning of the optimization process. size_t getTotalNumberOfCalls(){return totalNumberOfCalls;}; //! Simple getter. //! \return the number of calls to this operator since the last //! evaluation. size_t getNumberOfCallsSinceLastEvaluation(){return nbCallsSinceLastEval;}; void increaseNumberOfCalls() { totalNumberOfCalls++; nbCallsSinceLastEval++; } //! Simple getter. double getScore() const { return score; } //! Simple getter. double getWeight() const { return weight; } //! resetter. void resetScore() { this->score = 0; } //! Simple setter. void setScore(double s) { this->score = s; } //! Simple setter. void setWeight(double weight) { this->weight = weight; } //! Simple getter. std::string getName(){return operatorName;}; //! Set noise to true. void setNoise(){noise=true;}; //! Set noise to false. void unsetNoise(){noise=false;}; };
2.2 ADestroyOperator
該類主要是繼承於上面的AOperator類,然後再此基礎上加上Destroy操作的具體實現。它是一個抽象類,需要在後續的應用中重寫Destroy操作的方法。
class ADestroyOperator : public AOperator {
protected:
//! The minimum destroy size used.
size_t minimunDestroy;
//! The maximum destroy size used.
size_t maximumDestroy;
public:
//! Constructor.
//! \param mini the minimum destroy size.
//! \param maxi the maximum destroy size.
//! \param s the name of the destroy operator.
ADestroyOperator(size_t mini, size_t maxi, std::string s) : AOperator(s)
{
minimunDestroy = mini;
maximumDestroy = maxi;
}
//! Destructor.
virtual ~ADestroyOperator(){};
//! This function is the one called to destroy a solution.
//! \param sol the solution to be destroyed.
virtual void destroySolution(ISolution& sol)=0;
};
2.3 ARepairOperator
同理,也是由AOperator類派生出來並加上Repair自己的實現方法的類。也是一個抽象類,需要在後續的使用中重寫Repair方法。
class ARepairOperator : public AOperator {
public:
ARepairOperator(std::string s) : AOperator(s)
{
}
virtual ~ARepairOperator(){};
virtual void repairSolution(ISolution& sol)=0;
};
03 Destroy和Repair方法管理
對Destroy和Repair方法進行管理的由兩個類來實現:AOperatorManager、OperatorManager。其中AOperatorManager是抽象類,只提供接口,OperatorManager繼承於AOperatorManager。並對其接口進行實現。
3.1 AOperatorManager
該類抽象了OperatorManager的一些特征,只提供接口。因此成員函數都是純虛函數。相關方法的說明已經註釋在代碼裏面了。關於保護成員:stats用於保存算法叠代過程中的一些狀態量,這個類後續也會講解的。
class AOperatorManager
{
public:
//! This method selects a destroy operator.
//! \return a destroy operator.
virtual ADestroyOperator& selectDestroyOperator()=0;
//! This method selects a repair operator.
//! \return a repair operator.
virtual ARepairOperator& selectRepairOperator()=0;
virtual void recomputeWeights()=0;
//! Update the scores of the operators.
virtual void updateScores(ADestroyOperator& des, ARepairOperator& rep, ALNS_Iteration_Status& status)=0;
//! Indicate that the optimization process starts.
virtual void startSignal()=0;
//! Destroy the operators registered to this operator manager.
virtual void end()=0;
//! Simple setter.
void setStatistics(Statistics* statistics){stats = statistics;};
protected:
//! A pointer to the instance of the statistics module.
Statistics* stats;
};
3.2 OperatorManager
該類在AOperatorManager基礎上也添加了一些自己額外的成員變量和函數方法。具體還是看代碼理解吧,挺簡單的,沒有需要多解釋的,我在這多少無益。
class OperatorManager: public AOperatorManager {
private:
//! The set of repair operators.
std::vector<AOperator*> repairOperators;
//! The set of destroy operators.
std::vector<AOperator*> destroyOperators;
//! The sum of the weights of the repair operators.
double sumWeightsRepair;
//! The sum of the weights of the destroy operators.
double sumWeightsDestroy;
//! The paramaters to be used by the ALNS.
ALNS_Parameters* parameters;
//! Indicate whether or not the next operators to be return
//! should be noised or not.
bool noise;
//! A counter that indicates the number of times repair operators with noise have been successfull
double performanceRepairOperatorsWithNoise;
//! A counter that indicates the number of times repair operators without noise have been successfull
double performanceRepairOperatorsWithoutNoise;
//! Use a roulette wheel to select an operator in a vector of operators.
//! \return the selected operator.
AOperator& selectOperator(std::vector<AOperator*>& vecOp, double sumW);
//! Recompute the weight of an operator.
void recomputeWeight(AOperator& op, double& sumW);
public:
//! Constructor
//! \param param the parameters to be used.
OperatorManager(ALNS_Parameters& param);
//! Destructor.
virtual ~OperatorManager();
//! This function recompute the weights of every operator managed by this
//! manager.
void recomputeWeights();
//! This method selects a destroy operator.
//! \return a destroy operator.
ADestroyOperator& selectDestroyOperator();
//! This method selects a repair operator.
//! \return a repair operator.
ARepairOperator& selectRepairOperator();
//! This method adds a repair operator to the list
//! of repair operator managed by this manager.
//! \param repairOperator the repair operator to be added.
void addRepairOperator(ARepairOperator& repairOperator);
//! This method adds a destroy operator to the list
//! of destroy operator managed by this manager.
//! \param destroyOperator the destroy operator to be added.
void addDestroyOperator(ADestroyOperator& destroyOperator);
//! This method run some sanity checks to ensure that it is possible
//! to "safely" use this manager within the ALNS.
void sanityChecks();
//! Update the scores of the operators.
virtual void updateScores(ADestroyOperator& des, ARepairOperator& rep, ALNS_Iteration_Status& status);
//! Indicate that the optimization process starts.
virtual void startSignal();
//! Destroy all the operators registered to this operator.
void end();
};
上面是該類的.h文件,關於其中某些函數方法的實現,小編下面挑一些來重點給大家講講,那些以小編的腦瓜子都能理解的代碼就省略了,大家應該都能懂……
3.3 OperatorManager具體實現
又到了一大波代碼時間,來吧來吧,小板凳準備好,要開始啦~
3.3.1 OperatorManager::recomputeWeight(...)
重新計算單個操作的權重。其有兩個參數AOperator& op, double& sumW,其中 op是要重新計算權重的repair或者destroy方法,sumW是其對應集合的權重總和。
這裏只講一個新權重的計算方式就行:
其中:
Rho為設定的[0, 1]之間的參數,PrevWeight表示舊的權重,nbCalls表示在上一次自上一次更新完權重到現在該方法被調用的次數,timeSegmentsIt表示叠代多少次需要重新計算一下權重的叠代次數,currentScore表示舊的成績。理解了這些就很easy了。
void OperatorManager::recomputeWeight(AOperator& op, double& sumW)
{
double prevWeight = op.getWeight();
sumW -= prevWeight;
double currentScore = op.getScore();
size_t nbCalls = op.getNumberOfCallsSinceLastEvaluation();
double newWeight = (1-parameters->getRho())*prevWeight + parameters->getRho()*(static_cast<double>(nbCalls)/static_cast<double>(parameters->getTimeSegmentsIt()))*currentScore;
// We ensure that the weight is within the bounds.
if(newWeight > parameters->getMaximumWeight())
{
newWeight = parameters->getMaximumWeight();
}
if(newWeight < parameters->getMinimumWeight())
{
newWeight = parameters->getMinimumWeight();
}
sumW += newWeight;
op.setWeight(newWeight);
op.resetScore();
op.resetNumberOfCalls();
}
值得註意的是還有一個OperatorManager::recomputeWeights()成員函數是用於重新計算repair或者destroy方法集合的,它的實現主要也還是調用OperatorManager::recomputeWeight(AOperator& op, double& sumW)方法來實現的。
3.3.2 OperatorManager::selectOperator(...)
相信了解過遺傳算法輪盤賭實現過程的小夥伴對這裏都不會陌生,當然,並不是說權重大的方法一定會被選中,只是被選中的可能性會大而已。具體過程是先生成一個在0到sumWeight之間的中間權重randomWeightPos ,然後從第一個方法開始用變量cumulSum進行權重累加,直到cumulSum>=randomWeightPos 為止,那麽停止累加時最後這個方法就是幸運兒了。
AOperator& OperatorManager::selectOperator(std::vector<AOperator*>& vecOp, double sumW)
{
double randomVal = static_cast<double>(rand())/static_cast<double>(RAND_MAX);
double randomWeightPos = randomVal*sumW;
double cumulSum = 0;
for(size_t i = 0; i < vecOp.size(); i++)
{
cumulSum += vecOp[i]->getWeight();
if(cumulSum >= randomWeightPos)
{
if(noise)
{
vecOp[i]->setNoise();
}
else
{
vecOp[i]->unsetNoise();
}
vecOp[i]->increaseNumberOfCalls();
return *(vecOp[i]);
}
}
assert(false);
return *(vecOp.back());
}
3.3.3 OperatorManager::updateScores(...)
該成員函數用來更新各個Destroy和Repair方法的成績。參數是Destroy和Repair方法的集合,以及ALNS叠代過程中的各種狀態信息。便於說明下面用rScore和dScore分別代表Repair和Destroy方法的成績。具體實現如下:
- 如果找到新的最優解,rScore+=Sigma1,dScore+=Sigma1。其中Sigma1是設定參數。
- 如果當前解得到改進,rScore+=Sigma2,dScore+=Sigma2。其中Sigma2是設定參數。
- 如果當前解沒有得到改進 and 當前解是之前沒有出現過的 and 當前解被接受作為新的解了,rScore+=Sigma3,dScore+=Sigma3。其中Sigma3是設定參數。
void OperatorManager::updateScores(ADestroyOperator& des, ARepairOperator& rep, ALNS_Iteration_Status& status)
{
if(status.getNewBestSolution() == ALNS_Iteration_Status::TRUE)
{
rep.setScore(rep.getScore()+parameters->getSigma1());
des.setScore(des.getScore()+parameters->getSigma1());
performanceRepairOperatorsWithNoise += 1;
performanceRepairOperatorsWithoutNoise += 1;
}
if(status.getImproveCurrentSolution() == ALNS_Iteration_Status::TRUE)
{
rep.setScore(rep.getScore()+parameters->getSigma2());
des.setScore(des.getScore()+parameters->getSigma2());
performanceRepairOperatorsWithNoise += 1;
performanceRepairOperatorsWithoutNoise += 1;
}
if(status.getImproveCurrentSolution() == ALNS_Iteration_Status::FALSE
&& status.getAcceptedAsCurrentSolution() == ALNS_Iteration_Status::TRUE
&& status.getAlreadyKnownSolution() == ALNS_Iteration_Status::FALSE)
{
rep.setScore(rep.getScore()+parameters->getSigma3());
des.setScore(des.getScore()+parameters->getSigma3());
performanceRepairOperatorsWithNoise += 1;
performanceRepairOperatorsWithoutNoise += 1;
}
/* OLD VERSION */
/*
if(parameters->getNoise())
{
double randNoise = static_cast<double>(rand())/RAND_MAX;
noise = (randNoise<parameters->getProbabilityOfNoise());
}
*/
/* NEW VERSION */
if(parameters->getNoise())
{
double performanceRepairOperatorsGlobal = 0;
performanceRepairOperatorsGlobal += performanceRepairOperatorsWithNoise;
performanceRepairOperatorsGlobal += performanceRepairOperatorsWithoutNoise;
double randomVal = static_cast<double>(rand())/RAND_MAX;
double randomWeightPos = randomVal*performanceRepairOperatorsGlobal;
noise = (randomWeightPos < performanceRepairOperatorsGlobal);
}
}
至此,差不過難點都講完了,不知道你萌都understand了嗎?
04 小結
好了,以上就是今天的代碼內容,別急著走開哦,後面還有好多好多的內容沒講呢。
這是個天坑,希望大家和小編一起努力,共同填完它。哈哈,謝謝各位的至此。
代碼 | 自適應大鄰域搜索系列之(3) - Destroy和Repair方法代碼實現解析