/* Stata Macro -- Written by David B. Wilson
   Version 2021.04.06
   Code for computing tau^2
   Code Based on Wolfgang Viechtbauer metafor package for R
   http://https://cran.r-project.org/web/packages/metafor/index.html
   License:
   Creative Commons Attribution-ShareAlike
 */

program define _matau2, rclass
version 14.0

syntax varlist(min=2 numeric) [if] [in], [model(string)] [modtype(string)]

tokenize `varlist'
marksample touse
markout `touse'

tempvar P PP PVdiag RSS SJW SJWdiag SJw V Vdiag W W1 W1diag Wdiag X Y
tempvar _intercept K _sumw _vv _ww _xx _y adj b change invXX invXprimeWX iter
tempvar ivs oldtau2 p qe resid2 tau2 tau2start trP trPV se_tau2 v_tau2 k Wdiag2
tempvar sumPP sumPVP sumPVtPV sumw

tempfile _origfile
qui save "`_origfile'", replace
qui keep if `touse'

/* Get data */
mkmat `1', matrix(`Y')
mkmat `2', matrix(`W')
qui generate `_intercept' = 1
if "`modtype'"=="" {
    mkmat `_intercept', matrix(`X')
    }
if "`modtype'"=="aov" {
    qui tab `3', gen(`_xx')
    mkmat `_xx'*, matrix(`X')
    }
if "`modtype'"=="reg" {
    mkmat `varlist' `_intercept', matrix(`X')
    matrix `X' = `X'[1...,4...]
    }
matrix `K' = rowsof(`X')
scalar `k' = `K'[1,1]
scalar `p' = colsof(`X')
matrix `Wdiag' = diag(`W')
matrix `_sumw' = trace(`Wdiag')
qui generate `_vv' = 1/`2'
qui generate `_ww' = 1/`_vv'
mkmat `_vv', matrix(`V')
scalar `iter' = 0

/* Hedges (HE) estimator (also initial value for ML, REML, EB) */
if "`model'" == "HE" |  "`model'" == "ML" |  "`model'" == "REML" |  "`model'" == "EB" {
    mkmat `_intercept', matrix(`W1')
    matrix `W1diag' = diag(`W1')
    matrix `invXX' = syminv(`X'' * `W1diag' * `X')
    matrix `P' = `W1diag' - `X' * (`invXX'*`X'')
    matrix `RSS' = (`Y''*`P') * `Y'
    matrix `Vdiag' = diag(`V')
    matrix `PVdiag' = `P' * `Vdiag'
    matrix `trPV' = trace(`PVdiag')
    matrix `tau2' = (`RSS'[1,1] - `trPV'[1,1])/(`K'[1,1]-`p')
    scalar `tau2' = `tau2'[1,1]
}

/* Hunter and Schmidt */
if "`model'" == "HS" {
    matrix `invXprimeWX' = syminv(`X'' * `Wdiag' * `X')
    matrix `P' = `Wdiag' - `Wdiag' * `X' * `invXprimeWX' * (`X''*`Wdiag')
    matrix `RSS' = (`Y''*`P') * `Y'
    matrix `tau2' = (`RSS'[1,1]-`K'[1,1])/`_sumw'[1,1]
    scalar `tau2' = `tau2'[1,1]
}

/* Dersimonian-Laird (DL) estimator */
if "`model'" == "DL" {
    matrix `Wdiag' = diag(`W')
    matrix `invXprimeWX' = syminv(`X'' * `Wdiag' * `X')
    matrix `P' = `Wdiag' - `Wdiag' * `X' * `invXprimeWX' * (`X''*`Wdiag')
    matrix `trP' = trace(`P')
    matrix `RSS' = (`Y''*`P') * `Y'
    matrix `tau2' = (`RSS'[1,1] - (`K'[1,1] - `p'))/`trP'[1,1]
    scalar `tau2' = `tau2'[1,1]
}

/* Sidik-Jonkman (SJ) estimator */
if "`model'" == "SJ" | "`model'" == "SJIT"{
    qui sum `1'
    scalar `tau2start' = r(Var) * (`K'[1,1]-1)/`K'[1,1]
    qui generate `SJw' = 1/(`_vv' + `tau2start')
    mkmat `SJw', matrix(`SJW')
    matrix `SJWdiag' = diag(`SJW')
    matrix `invXprimeWX' = syminv(`X'' * `SJWdiag' * `X')
    matrix `P' = `SJWdiag' - `SJWdiag' * `X' * `invXprimeWX' * (`X''*`SJWdiag')
    matrix `RSS' = (`Y''*`P') * `Y'
    matrix `Vdiag' = diag(`V')
    matrix `PVdiag' = `P' * `Vdiag'
    matrix `tau2' = (`RSS'[1,1] * `tau2start')/(`k'-`p')
    scalar `tau2' = `tau2'[1,1]
}

/* Sidik-Jonkman (SJIT) estimator with iteration */
if "`model'" == "SJIT" {
    scalar `iter' = 0
    scalar `change' = 1
    while abs(`change') > .0000001 & `iter'<100 {
        scalar `iter' = `iter' + 1
        matrix `oldtau2' = `tau2'
        qui replace `SJw' = 1/(`_vv' + `tau2')
        mkmat `SJw', matrix(`SJW')
        matrix `SJWdiag' = diag(`SJW')
        matrix `invXprimeWX' = syminv(`X'' * `SJWdiag' * `X')
        matrix `P' = `SJWdiag' - `SJWdiag' * `X' * `invXprimeWX' * (`X''*`SJWdiag')
        matrix `RSS' = (`Y''*`P') * `Y'
        matrix `Vdiag' = diag(`V')
        matrix `PVdiag' = `P' * `Vdiag'
        matrix `tau2' = (`RSS'[1,1] * `tau2')/(`k'-`p')
        scalar `tau2' = `tau2'[1,1]
        scalar `change' = `tau2' - `oldtau2'[1,1]
    }
     if `tau2' > 0 {
        qui replace `_ww' = 1/(`_vv' + `tau2')
        mkmat `_ww', matrix(`W')
        matrix `Wdiag' = diag(`W')
     }
     if `tau2' <= 0 {
        qui replace `_ww' = 1/(`_vv')
        mkmat `_ww', matrix(`W')
        matrix `Wdiag' = diag(`W')
     }
    }

/* Maximum likelihood (ML, REML, EB) */
if "`model'" == "REML" | "`model'" == "ML" | "`model'" == "EB" {
    scalar `change' = 1
    scalar `iter' = 0
    while abs(`change') > .0000001 & `iter'<100 {
        scalar `iter' = `iter' + 1
        scalar `oldtau2' = `tau2'
        qui replace `_ww' = 1/(`_vv' + `tau2')
        mkmat `_ww', matrix(`W')
        matrix `Wdiag' = diag(`W')
        matrix `invXprimeWX' = syminv(`X'' * `Wdiag' * `X')
        matrix `P' = `Wdiag' - `Wdiag' * `X' * `invXprimeWX' * (`X''*`Wdiag')
        matrix `PP' = `P' * `P'
        if "`model'" == "ML" {
            matrix `adj' = (`Y''*`PP' * `Y' - trace(`Wdiag'))/trace(`Wdiag'*`Wdiag')
            }
        if "`model'" == "REML" {
            matrix `adj' = (`Y''*`PP' * `Y' - trace(`P'))/trace(`PP')
            }
        if "`model'" == "EB" {
            matrix `adj' = (`Y''*`P' * `Y' * `k'/(`k'-`p') - `k')/trace(`Wdiag')
            }
        scalar `tau2' = `tau2' + `adj'[1,1]
        scalar `change' = `tau2' - `oldtau2'
     }
     if `tau2' > 0 {
        qui replace `_ww' = 1/(`_vv' + `tau2')
        mkmat `_ww', matrix(`W')
        matrix `Wdiag' = diag(`W')
        matrix `invXprimeWX' = syminv(`X'' * `Wdiag' * `X')
        matrix `P' = `Wdiag' - `Wdiag' * `X' * `invXprimeWX' * (`X''*`Wdiag')
     }
     if `tau2' <= 0 {
        qui replace `_ww' = 1/(`_vv')
        mkmat `_ww', matrix(`W')
        matrix `Wdiag' = diag(`W')
        matrix `invXprimeWX' = syminv(`X'' * `Wdiag' * `X')
        matrix `P' = `Wdiag' - `Wdiag' * `X' * `invXprimeWX' * (`X''*`Wdiag')
     }
}

if `iter' == 100 {
  di "  "
  di "**********************************************"
  di "Failed to converge. Try a different model type"
  di "**********************************************"
}

if `tau2' < 0 {
  scalar `tau2' = 0
  }

/* standard error of tau^2 */
/* compute sum(P*P) and sum(PV * t(PV)) and sum(PV*P) */
if "`model'" == "HS" | "`model'" == "DL" | "`model'" == "SJ" | "`model'" == "REML" | "`model'" == "HE" {
   scalar `sumPP' = 0
   scalar `sumPVP' = 0
   scalar `sumPVtPV' = 0
   local n = `k'
   forvalues i = 1/`n' {
      forvalues j = 1/`n' {
      scalar `sumPP'    = `sumPP'    + `P'[`i',`j']^2
      }
   }
   if "`model'" == "HE" | "`model'" == "SJ" {
     forvalues i = 1/`n' {
         forvalues j = 1/`n' {
            scalar `sumPVtPV' = `sumPVtPV' + `PVdiag'[`i',`j']*`PVdiag'[`j',`i']
         }
     }
   }
   if "`model'" == "SJ" {
     forvalues i = 1/`n' {
         forvalues j = 1/`n' {
            scalar `sumPVP'   = `sumPVP'   + `PVdiag'[`i',`j']*`P'[`i',`j']
         }
     }
   }
}

if "`model'" == "HE" {
   scalar `se_tau2' = sqrt( 1/(`k'-`p')^2 * (2*`sumPVtPV' + 4*`tau2'*`trPV'[1,1] + 2*`tau2'^2*(`k'*`p')))
}

if "`model'" == "HS" {
   qui replace `_ww' = 1/`_vv'
   qui sum `_ww'
   scalar `sumw' = r(sum)
   di `sumw'
   scalar `se_tau2' = sqrt(1/`sumw'^2 * (2 * (`k'-`p')  + 4*`tau2'*trace(`P') + 2 * `tau2'^2 * `sumPP'))
}
if "`model'" == "DL" {
   scalar `se_tau2' = sqrt(1/`trP'[1,1]^2 * (2*(`k'-`p') + 4*`tau2'*`trP'[1,1] + 2*`tau2'^2*`sumPP'))
}

if "`model'" == "SJ" {
   scalar `se_tau2' = sqrt(`tau2start'^2/(`k'-`p')^2 * (2*`sumPVtPV' + 4*`tau2'*`sumPVP' + 2*`tau2'^2*`sumPP'))
}

if "`model'" == "ML" {
   matrix `Wdiag2' = `Wdiag'' * `Wdiag'
   scalar `se_tau2' = sqrt(2/trace(`Wdiag2'))
    }
if "`model'" == "REML" {
   scalar `se_tau2' = sqrt(2/`sumPP')
   }
if "`model'" == "EB" | "`model'" == "SJIT"{
   scalar `se_tau2' = sqrt(2*`k'^2/(`k'-`p') /(trace(`Wdiag')^2))
   }

return scalar tau2 = `tau2'
return scalar se_tau2 = `se_tau2'

qui use "`_origfile'", replace

end