I'm writing a program in Scala to perform Hamiltonian Monte Carlo (HMC), coupled with Gibbs sampling of some variables. The algorithm, with the modifications such as perturbing epsilon and l and performing Gibbs updates on some variables may be found in the easily available book chapter "MCMC using Hamiltonian Dynamics" by R Neal. The program works by alternating an HMC update of a set of variables q via the hmcUpdate()
method with Gibbs updates of the variables gibbsVar
via some unspecified method, then putting the two sets together in a tuple. The hmcRunner()
method generates these tuples and prepends them onto a list.
I'm very new to Scala (last Friday) so I would like some feedback on the idiomacity of the code, as well as advice in general on improving readability. I've tried to write this in the form of an abstract class so that myself and my other group members can use this in their own code by supplying the unspecified methods themselves - is this the most appropriate structure to use?
import breeze.linalg._
import breeze.stats.distributions.Uniform
import breeze.stats.distributions.Gaussian
import breeze.stats.distributions.Gamma
import math.log, math.sqrt
import scala.annotation.tailrec
abstract class HMC(ul: Int, ue: Double) {
val uDev = new Uniform(0.0,1.0)
val nDev = new Gaussian(0.0,1.0)
val l = ul
val ep = ue
final def metCheck(ho: Double, hn: Double): Boolean = log(uDev.sample()) < ho - hn
def calcU(q: DenseVector[Double], sigs: List[Double]): Double
def calcUgrad(q: DenseVector[Double], sigs: List[Double]): DenseVector[Double]
def gibbsUpdate(q: DenseVector[Double]): List[Double]
final def hmcUpdate(qo: DenseVector[Double], sigs: List[Double], acc: Int): (DenseVector[Double], Int) = {
val uep = ep * min(sigs) * min(sigs) * (0.9 + 0.2 * uDev.sample()) // perturb ep, l pm 10% to avoid cyclic orbits
val ul = (l.toDouble * (0.9 + 0.2 * uDev.sample())).toInt
val po = DenseVector(nDev.sample(qo.length).toArray)
val ho = calcU(qo, sigs) + (po dot po) / 2.0
@tailrec
def leapFrog(qo: DenseVector[Double], po: DenseVector[Double], ll: Int): (DenseVector[Double], DenseVector[Double]) = {
if (ll == 1) {
val qn = qo + (po :* uep)
val pn = po - (calcUgrad(qn, sigs) :* uep) / 2.0
(qn, pn)
}
else {
val qn = qo + (po :* uep)
val pn = po - (calcUgrad(qn, sigs) :* uep)
leapFrog(qn, pn, ll - 1)
}
}
val (qn, pn) = leapFrog(qo, po - (calcUgrad(qo, sigs) :* uep) / 2.0, ul)
val hn = calcU(qn, sigs) + (pn dot pn) / 2.0
if (metCheck(ho,hn)) (qn, acc + 1) else (qo, acc)
}
final def stdDevSample(sqmis: Double, n: Int) = sqrt(
sqmis / (2.0 * Gamma((n.toDouble + 3) / 2.0, 1.0).sample())
)
//Pass initial values to rl
@tailrec
final def hmcRunner(maxIt: Int, report: Int, iter: Int, acc: Int,
rl: List[(DenseVector[Double], List[Double])]):
List[(DenseVector[Double], List[Double])] = {
if (iter == maxIt) {
val (qn, accn) = hmcUpdate(rl(0)._1, rl(0)._2, acc)
val gibbsVar = gibbsUpdate(qn)
(qn, gibbsVar) :: rl
}
else {
val (qn, accn) = hmcUpdate(rl(0)._1, rl(0)._2, acc)
val gibbsVar = gibbsUpdate(qn)
val currU = calcU(qn, gibbsVar)
if (iter % report == 0) {
print("\nIteration "); print(iter); print(" of "); println(maxIt)
print("U: "); println(currU)
print("Acceptance: "); println(acc.toDouble / iter.toDouble)
}
hmcRunner(maxIt, report, iter + 1, accn, (qn, gibbsVar) :: rl)
}
}
}
gibbsUpdate
belongs in normal HMC. \$\endgroup\$