*! Date    : 03 Jan 2019
*! Version : 1.1
*! Authors : Michael J Grayling & Adrian P Mander

/*
  30/10/17 v1.0 Basic version complete.
  03/01/19 v1.1 Minor changes for speed. Converted delta and sigma to be
                optional with internal defaults. Removed the method option for
				simplicity.
*/

program define rtmvt, rclass
version 15.0
syntax , LOWERTruncation(numlist miss) UPPERTruncation(numlist miss) ///
         [n(integer 1) DELta(numlist) Sigma(string) df(real 1)]

///// Check input variables ////////////////////////////////////////////////////

local lendelta:list  sizeof delta
local lenlowert:list sizeof lowertruncation
local lenuppert:list sizeof uppertruncation
if (`lenlowert' != `lenuppert') {
  di "{error}Lower truncation vector (lowertruncation) and upper truncation vector (uppertruncation) must be of equal length."
  exit(198)
}
forvalues i = 1/`lenlowert' {
  local lowerti:word `i' of `lowertruncation'
  local upperti:word `i' of `uppertruncation'
  if ((`lowerti' != .) & (`upperti' != .)) {
    if (`lowerti' >= `upperti') {
      di "{error}Each lower truncation limit (in lowertruncation) must be strictly less than the corresponding upper truncation limit (in uppertruncation)."
      exit(198)
    }
  }
}
if (`n' < 1) {
  di "{error}Number of random vectors to generate (n) must be a strictly positive integer."
  exit(198)
}
if ((`lendelta' != 0) & (`lenlowert' != `lendelta')) {
  di "{error}Vector of non-centrality parameters (delta) and lower truncation vector (lowertruncation) must be of equal length."
  exit(198)
}
if ("`sigma'" != "") {
  if (colsof(`sigma') != rowsof(`sigma')) {
    di "{error}Scale matrix Sigma (sigma) must be square."
    exit(198)
  }
  if (`lenlowert' != colsof(`sigma')) {
    di "{error}Lower truncation vector (lowertruncation) must be the same length as the dimension of scale matrix Sigma (sigma)."
    exit(198)
  }
  cap mat chol = cholesky(`sigma')
  if (_rc > 0) {
    di "{error}Scale matrix Sigma (sigma) must be symmetric positive-definite."
    exit(198)
  }
  mat sigma    = (`sigma')
}
else {
  mat sigma    = I(`lenlowert')
  mat chol     = I(`lenlowert')
}
if (`df' < 0) {
  di "{error}Degrees of freedom (df) must be greater than or equal to zero."
  exit(198)
}

///// Perform main computations ////////////////////////////////////////////////

local matalowert ""
foreach l of local lowertruncation {
  if "`matalowert'" == "" local matalowert "`l'"
  else local matalowert "`matalowert',`l'"
}
mat lowert  = (`matalowert')
local matauppert ""
foreach l of local uppertruncation {
  if "`matauppert'" == "" local matauppert "`l'"
  else local matauppert "`matauppert',`l'"
}
mat uppert  = (`matauppert')
if (`lendelta' != 0) {
  local matadelta ""
  foreach l of local delta {
    if "`matadelta'" == "" local matadelta "`l'"
    else local matadelta "`matadelta',`l'"
  }
  mat delta = (`matadelta')
}
else {
  mat delta = J(1, `lenlowert', 0)
}
mata: rtmvt_void(`n', `df')

///// Output ///////////////////////////////////////////////////////////////////

return mat rtmvt = rmatrix
end

///// Mata /////////////////////////////////////////////////////////////////////

mata:

void rtmvt_void(n, df) {
  lowert = st_matrix("lowert")
  uppert = st_matrix("uppert")
  delta  = st_matrix("delta")
  C      = st_matrix("chol")
  st_matrix("rmatrix", rtmvt_mata(n, lowert, uppert, delta, ., df, C))
}

real matrix rtmvt_mata(real scalar n, real vector lowert, real vector uppert,
                       real vector delta, real matrix Sigma,
					   real scalar df,| real matrix C) {
  if (args() < 7) {
    C              = cholesky(Sigma)
  }
  k                = rows(C)
  rmatrix          = J(n, k, 0)
  i                = 1
  while (i <= n) {
    if ((df > 0) & (df != .)) {
      gensample    = (C*rnormal(k, 1, 0, 1))':/sqrt(rchi2(1, 1, df)) + delta
	}
	else {
	  gensample    = (C*rnormal(k, 1, 0, 1))' + delta
	}
    check          = 1
	for (j = 1; j <= k; j++) {
	  if ((gensample[j] < lowert[j]) | (gensample[j] > uppert[j])) {
	    check      = 0
		break
	  }
	}
	if (check == 1) {
	  rmatrix[i, ] = gensample
	  i            = i + 1
	}
  }
  return(rmatrix)
}

end