libROM  v1.0
Data-driven physical simulation library
DifferentialEvolution.cpp
1 
12 // Description: Implementation of the differential evolution algorithm.
13 
14 #include "DifferentialEvolution.h"
15 #include "utils/Utilities.h"
16 #include "mpi.h"
17 
18 #include <iostream>
19 
20 namespace CAROM {
21 
23  unsigned int populationSize,
24  double F,
25  double CR,
26  int randomSeed,
27  bool shouldCheckConstraints,
28  std::function<void(const DifferentialEvolution&)> callback,
29  std::function<bool(const DifferentialEvolution&)> terminationCondition) :
30  m_cost(costFunction),
31  m_populationSize(populationSize),
32  m_F(F),
33  m_CR(CR),
34  m_bestAgentIndex(0),
35  m_minCost(-std::numeric_limits<double>::infinity()),
36  m_shouldCheckConstraints(shouldCheckConstraints),
37  m_callback(callback),
38  m_terminationCondition(terminationCondition)
39 {
40  // Get the rank of this process, and the number of processors.
41  int mpi_init;
42  MPI_Initialized(&mpi_init);
43  if (mpi_init == 0) {
44  MPI_Init(nullptr, nullptr);
45  }
46 
47  MPI_Comm_rank(MPI_COMM_WORLD, &d_rank);
48 
49  m_generator.seed(randomSeed);
50  CAROM_VERIFY(m_populationSize >= 4);
51 
52  m_numberOfParameters = m_cost.NumberOfParameters();
53 
54  m_population.resize(populationSize);
55  for (auto& agent : m_population)
56  {
57  agent.resize(m_numberOfParameters);
58  }
59 
60  m_minCostPerAgent.resize(m_populationSize);
61 
62  m_constraints = costFunction.GetConstraints();
63 }
64 
65 std::vector<double>
66 DifferentialEvolution::Optimize(int min_iterations, int max_iterations,
67  double cost_tolerance, bool verbose)
68 {
69  CAROM_VERIFY(min_iterations <= max_iterations);
70  CAROM_VERIFY(min_iterations >= 0 && max_iterations > 0);
71 
72  std::vector<double> optimal_parameters(m_numberOfParameters);
73 
74  InitPopulation();
75 
76  double prevMinCost;
77 
78  // Optimization loop
79  for (int i = 0; i < max_iterations; i++)
80  {
81  // Optimization step
82  prevMinCost = m_minCost;
83  SelectionAndCrossing();
84 
85  if (d_rank == 0 && verbose)
86  {
87  std::cout << "Iteration: " << i << "\t\t";
88  std::cout << "Current minimal cost: " << m_minCost << "\t\t";
89  std::cout << "Best agent: ";
90  for (int j = 0; j < m_numberOfParameters; j++)
91  {
92  std::cout << m_population[m_bestAgentIndex][j] << " ";
93  }
94  std::cout << std::endl;
95  }
96 
97  for (int j = 0; j < m_numberOfParameters; j++)
98  {
99  optimal_parameters[j] = m_population[m_bestAgentIndex][j];
100  }
101 
102  if (i >= min_iterations && prevMinCost - m_minCost <= cost_tolerance)
103  {
104  if (d_rank == 0 && verbose)
105  {
106  std::cout <<
107  "Terminated due to cost tolerance condition being met." <<
108  std::endl;
109  }
110  return optimal_parameters;
111  }
112 
113  if (m_callback)
114  {
115  m_callback(*this);
116  }
117 
118  if (m_terminationCondition)
119  {
120  if (m_terminationCondition(*this))
121  {
122  if (d_rank == 0 && verbose)
123  {
124  std::cout <<
125  "Terminated due to positive evaluation of the termination condition." <<
126  std::endl;
127  }
128  return optimal_parameters;
129  }
130  }
131  }
132 
133  if (d_rank == 0 && verbose)
134  {
135  std::cout << "Terminated due to exceeding total number of generations." <<
136  std::endl;
137  }
138 
139  return optimal_parameters;
140 }
141 
142 bool
143 DifferentialEvolution::CheckConstraints(std::vector<double> agent)
144 {
145  for (int i = 0; i < agent.size(); i++)
146  {
147  if (!m_constraints[i].Check(agent[i]))
148  {
149  return false;
150  }
151  }
152 
153  return true;
154 }
155 
156 void
157 DifferentialEvolution::InitPopulation()
158 {
159  // Init population based on random sampling of the cost function
160  std::shared_ptr<std::uniform_real_distribution<double>> distribution;
161 
162  for (auto& agent : m_population)
163  {
164  for (int i = 0; i < m_numberOfParameters; i++)
165  {
166  if (m_constraints[i].isConstrained)
167  {
168  distribution = std::make_shared<std::uniform_real_distribution<double>>
169  (std::uniform_real_distribution<double>(m_constraints[i].lower,
170  m_constraints[i].upper));
171  }
172  else
173  {
174  distribution = std::make_shared<std::uniform_real_distribution<double>>
175  (std::uniform_real_distribution<double>(g_defaultLowerConstraint,
176  g_defaultUpperConstraint));
177  }
178 
179  agent[i] = (*distribution)(m_generator);
180  }
181  }
182 
183  // Initialize minimum cost, best agent and best agent index
184  for (int i = 0; i < m_populationSize; i++)
185  {
186  m_minCostPerAgent[i] = m_cost.EvaluateCost(m_population[i]);
187 
188  if (m_minCostPerAgent[i] < m_minCost)
189  {
190  m_minCost = m_minCostPerAgent[i];
191  m_bestAgentIndex = i;
192  }
193  }
194 }
195 
196 void
197 DifferentialEvolution::SelectionAndCrossing()
198 {
199  std::uniform_real_distribution<double> distribution(0, m_populationSize);
200 
201  double minCost = m_minCostPerAgent[0];
202  int bestAgentIndex = 0;
203 
204  for (int x = 0; x < m_populationSize; x++)
205  {
206  // For x in population select 3 random agents (a, b, c) different from x
207  int a = x;
208  int b = x;
209  int c = x;
210 
211  // Agents must be different from each other and from x
212  while (a == x || b == x || c == x || a == b || a == c || b == c)
213  {
214  a = distribution(m_generator);
215  b = distribution(m_generator);
216  c = distribution(m_generator);
217  }
218 
219  // Form intermediate solution z
220  std::vector<double> z(m_numberOfParameters);
221  for (int i = 0; i < m_numberOfParameters; i ++)
222  {
223  z[i] = m_population[a][i] + m_F * (m_population[b][i] - m_population[c][i]);
224  }
225 
226  // Choose random R
227  std::uniform_real_distribution<double> distributionParam(0,
228  m_numberOfParameters);
229  int R = distributionParam(m_generator);
230 
231  // Choose random r for each dimension
232  std::vector<double> r(m_numberOfParameters);
233  std::uniform_real_distribution<double> distributionPerX(0, 1);
234  for (auto& var : r)
235  {
236  var = distributionPerX(m_generator);
237  }
238 
239  std::vector<double> newX(m_numberOfParameters);
240 
241  // Execute crossing
242  for (int i = 0; i < m_numberOfParameters; i++)
243  {
244  if (r[i] < m_CR || i == R)
245  {
246  newX[i] = z[i];
247  }
248  else
249  {
250  newX[i] = m_population[x][i];
251  }
252  }
253 
254  // Check if newX candidate satisfies constraints and skip it if not.
255  // If agent is skipped loop iteration x is decreased so that it is ensured
256  // that the population has constant size (equal to m_populationSize).
257  if (m_shouldCheckConstraints && !CheckConstraints(newX))
258  {
259  x--;
260  continue;
261  }
262 
263  // Calculate new cost and decide should the newX be kept.
264  double newCost = m_cost.EvaluateCost(newX);
265  if (newCost < m_minCostPerAgent[x])
266  {
267  m_population[x] = newX;
268  m_minCostPerAgent[x] = newCost;
269  }
270 
271  // Track the global best agent.
272  if (m_minCostPerAgent[x] < minCost)
273  {
274  minCost = m_minCostPerAgent[x];
275  bestAgentIndex = x;
276  }
277  }
278 
279  m_minCost = minCost;
280  m_bestAgentIndex = bestAgentIndex;
281 }
282 
283 std::vector<double>
284 DifferentialEvolution::GetBestAgent() const
285 {
286  return m_population[m_bestAgentIndex];
287 }
288 
289 double
290 DifferentialEvolution::GetBestCost() const
291 {
292  return m_minCostPerAgent[m_bestAgentIndex];
293 }
294 
295 std::vector<std::pair<std::vector<double>, double>>
296  DifferentialEvolution::GetPopulationWithCosts() const
297 {
298  std::vector<std::pair<std::vector<double>, double>> toRet;
299  for (int i = 0; i < m_populationSize; i++)
300  {
301  toRet.push_back(std::make_pair(m_population[i], m_minCostPerAgent[i]));
302  }
303 
304  return toRet;
305 }
306 
307 void
308 DifferentialEvolution::PrintPopulation() const
309 {
310  if (d_rank == 0)
311  {
312  for (auto agent : m_population)
313  {
314  for (auto& var : agent)
315  {
316  std::cout << var << " ";
317  }
318  std::cout << std::endl;
319  }
320  }
321 }
322 
323 }
DifferentialEvolution(const IOptimizable &costFunction, unsigned int populationSize, double F=0.8, double CR=0.9, int randomSeed=1, bool shouldCheckConstraints=true, std::function< void(const DifferentialEvolution &)> callback=nullptr, std::function< bool(const DifferentialEvolution &)> terminationCondition=nullptr)
Constructor.
std::vector< double > Optimize(int min_iterations, int max_iterations, double cost_tolerance, bool verbose=true)
Constructor.
virtual unsigned int NumberOfParameters() const =0
Return the number of parameters.
virtual double EvaluateCost(std::vector< double > &inputs) const =0
Evaluate the cost function with the current set of inputs.
virtual std::vector< Constraints > GetConstraints() const =0
Return the list of constraints.