hops
ParallelTempering.hpp
Go to the documentation of this file.
1 #ifndef HOPS_PARALLELTEMPERING_HPP
2 #define HOPS_PARALLELTEMPERING_HPP
3 
4 #ifdef HOPS_MPI_SUPPORTED
5 
6 #include <random>
7 
14 
15 namespace hops {
20  template<typename MarkovChainImpl>
21  class ParallelTempering : public MarkovChainImpl {
22  public:
23  ParallelTempering(const MarkovChainImpl &markovChainImpl, // NOLINT(cppcoreguidelines-pro-type-member-init)
24  RandomNumberGenerator synchronizedRandomNumberGenerator,
25  double exchangeAttemptProbability = 0.1) :
26  MarkovChainImpl(markovChainImpl),
27  synchronizedRandomNumberGenerator(synchronizedRandomNumberGenerator),
28  exchangeAttemptProbability(exchangeAttemptProbability) {
29  if (exchangeAttemptProbability > 1) {
30  this->exchangeAttemptProbability = 1;
31  } else if (exchangeAttemptProbability < 0) {
32  this->exchangeAttemptProbability = 0;
33  }
34 
36  MPI_Comm_dup(MPI_COMM_WORLD, &communicator);
37  MPI_Comm_size(communicator, &numberOfChains);
38 
39  int chainIndex;
40  MPI_Comm_rank(communicator, &chainIndex);
41  int largestChainIndex = numberOfChains == 1 ? 1 : numberOfChains - 1;
42  MarkovChainImpl::setColdness(1. - static_cast<double>(chainIndex) / largestChainIndex);
43  }
44 
45  void draw(RandomNumberGenerator &randomNumberGenerator) {
46  MarkovChainImpl::draw(randomNumberGenerator);
47  executeParallelTemperingStep();
48  }
49 
50  void writeRecordsToFile(const FileWriter *const fileWriter) const {
51  if constexpr(IsWriteRecordsToFileAvailable<MarkovChainImpl>::value) {
52  int chainIndex;
53  MPI_Comm_rank(communicator, &chainIndex);
54  if (chainIndex == 0) {
55  MarkovChainImpl::writeRecordsToFile(fileWriter);
56  }
57  }
58  }
59 
60  void storeRecord() {
61  if constexpr(IsStoreRecordAvailable<MarkovChainImpl>::value) {
62  int chainIndex;
63  MPI_Comm_rank(communicator, &chainIndex);
64  if (chainIndex == 0) {
65  MarkovChainImpl::storeRecord();
66  }
67  }
68  }
69 
73  bool executeParallelTemperingStep() {
74  if (shouldProposeExchange()) {
75  // TODO store log acceptance in some kind of log?
76  std::pair<int, int> chainPair = generateChainPairForExchangeProposal();
77  int world_rank;
78  MPI_Comm_rank(communicator, &world_rank);
79  if (chainPair.first == world_rank || chainPair.second == world_rank) {
80  int otherChainRank = world_rank == chainPair.first ? chainPair.second : chainPair.first;
81 
82  double acceptanceProbability = computeExchangeAcceptanceProbability(otherChainRank);
83  double chance = uniformRealDistribution(synchronizedRandomNumberGenerator);
84  if (chance <= acceptanceProbability) {
85  exchangeStates(otherChainRank);
86  return true;
87  }
88  return false;
89 
90  } else {
91  // keeps all random number generators in sync
92  uniformRealDistribution(synchronizedRandomNumberGenerator);
93  }
94  }
95  return false;
96  }
97 
98  double computeExchangeAcceptanceProbability(int otherChainRank) {
99  double coldness = this->getColdness();
100  double coldNegativeLogLikelihood = this->getNegativeLogLikelihoodOfCurrentState() / coldness;
101  double thisChainProperties[] = {
102  coldness,
103  coldNegativeLogLikelihood
104  };
105 
106  double otherChainProperties[2];
107  std::memcpy(otherChainProperties, thisChainProperties, sizeof(double) * 2);
108 
109  MPI_Sendrecv_replace(otherChainProperties, 2, MPI_DOUBLE, otherChainRank, MpiInitializerFinalizer::getInternalMpiTag(),
110  otherChainRank, MpiInitializerFinalizer::getInternalMpiTag(), communicator, MPI_STATUS_IGNORE);
111 
112  // 1*1=(-1)*(-1) => the signs come out consistently for both chains for the acceptance probability
113  double diffColdness = thisChainProperties[0] - otherChainProperties[0];
114  double diffNegativeLoglikelihoods = thisChainProperties[1] - otherChainProperties[1];
115 
116  double acceptanceProbability = std::exp(diffColdness * diffNegativeLoglikelihoods);
117  return acceptanceProbability;
118  }
119 
120  void exchangeStates(int otherChainRank) {
121  VectorType thisState = MarkovChainImpl::getState();
122 
123  MPI_Sendrecv_replace(thisState.data(), thisState.size(), MPI_DOUBLE, otherChainRank, MpiInitializerFinalizer::getInternalMpiTag(),
124  otherChainRank, MpiInitializerFinalizer::getInternalMpiTag(), communicator, MPI_STATUS_IGNORE);
125 
126  MarkovChainImpl::setState(thisState);
127  }
128 
129  std::pair<int, int> generateChainPairForExchangeProposal() {
130 
131  int chainIndex = uniformIntDistribution(synchronizedRandomNumberGenerator,
132  std::uniform_int_distribution<int>::param_type(0,
133  numberOfChains - 2));
134  return std::make_pair(chainIndex, chainIndex + 1);
135  }
136 
137  bool shouldProposeExchange() {
138  double chance = uniformRealDistribution(synchronizedRandomNumberGenerator);
139  return (chance < exchangeAttemptProbability);
140  }
141 
142  double getExchangeAttemptProbability() const {
143  return exchangeAttemptProbability;
144  }
145 
146  void setExchangeAttemptProbability(double newExchangeAttemptProbability) {
147  ParallelTempering::exchangeAttemptProbability = newExchangeAttemptProbability;
148  }
149 
150  private:
151  int numberOfChains;
152  double exchangeAttemptProbability;
153  std::uniform_int_distribution<int> uniformIntDistribution;
154  std::uniform_real_distribution<double> uniformRealDistribution;
155  MPI_Comm communicator;
156  RandomNumberGenerator synchronizedRandomNumberGenerator;
157  };
158 }
159 
160 #else
161 
162 namespace hops {
167  template<typename MarkovChainImpl>
168  class ParallelTempering : public MarkovChainImpl {
169  public:
170  ParallelTempering(const MarkovChainImpl &markovChainImpl // NOLINT(cppcoreguidelines-pro-type-member-init)
171  ) : MarkovChainImpl(markovChainImpl) {
172  throw std::runtime_error("MPI not supported on current platform");
173  }
174  ParallelTempering(const MarkovChainImpl &markovChainImpl, // NOLINT(cppcoreguidelines-pro-type-member-init)
175  double) : MarkovChainImpl(markovChainImpl) {
176  throw std::runtime_error("MPI not supported on current platform");
177  }
178  };
179 }
180 #endif //HOPS_MPI_SUPPORTED
181 
182 #endif //HOPS_PARALLELTEMPERING_HPP
RandomNumberGenerator.hpp
hops::MpiInitializerFinalizer::getInternalMpiTag
static const int & getInternalMpiTag()
Definition: MpiInitializerFinalizer.hpp:31
IsStoreRecordAvailable.hpp
hops::ParallelTempering::ParallelTempering
ParallelTempering(const MarkovChainImpl &markovChainImpl)
Definition: ParallelTempering.hpp:170
hops::RandomNumberGenerator
pcg64 RandomNumberGenerator
Definition: RandomNumberGenerator.hpp:7
hops::ParallelTempering
Mixin for adding parallel tempering to Markov chains. Requires MPI.
Definition: ParallelTempering.hpp:168
hops
Definition: CsvReader.hpp:8
hops::ParallelTempering::ParallelTempering
ParallelTempering(const MarkovChainImpl &markovChainImpl, double)
Definition: ParallelTempering.hpp:174
hops::MpiInitializerFinalizer::initializeAndQueueFinalizeAtExit
static void initializeAndQueueFinalizeAtExit()
Definition: MpiInitializerFinalizer.hpp:11
VectorType.hpp
IsWriteRecordsToFileAvailable.hpp
hops::VectorType
Eigen::VectorXd VectorType
Definition: VectorType.hpp:7
FileWriter.hpp
MpiInitializerFinalizer.hpp