*! igeintb v 9
*! Pablo Mitnik
*!
*! Set estimates IGEs with multiple sets of instruments, computes confidence intervals that are appropriate for partially
*! identified IGEs estimated in intersection-bounds contexts, and optionally provides half-median unbiased (hmu) estimates of the upper bound
*!
*! Last updated Feb. 2019

program igeintb, eclass

	version 13
	syntax varlist (min=2 max=2) [if] [in] [fw pw iw], insts1(varlist) insts2(varlist) ige(string) [exvars(varlist) insts3(varlist) insts4(varlist) insts5(varlist) insts6(varlist) ///
	                insts7(varlist) insts8(varlist) insts9(varlist) insts10(varlist) hmu gmm Cluster(varlist) TECHnique(string) Level(cilevel) nsim(integer 10000) seed(integer 123456789) show ]

	/*1. Process and check inputs*/

	local cvar : word 1 of `varlist'
	local pvar : word 2 of `varlist'

	if "`ige'" != "igeg" & "`ige'" != "igee" {
		di as error "ige option may only be igeg or igee"
		exit 198
	}

	local ninstsets = 0

	foreach cand of numlist 1/10 {

		if "`insts`cand''" != "" {
			local ninstsets = `ninstsets' + 1
		}
	}

	local startempty = `ninstsets' + 1

	if `startempty' < 10 {
		foreach instset of numlist `startempty'/10 {
			if "`insts`instset''"!="" {
				di as error "cannot specify set of instrumetns `instset' if any of the set of instruments 1 to `ninstsets' has not been specified"
				exit 198
			}
		}
	}

	if "`ige'"=="igeg" {
		if "`gmm'"=="" local est 2sls
		else if "`gmm'"!="" local est gmm
	}

	else if "`ige'"=="igee" local est gmm

	if `"`exp'"' != "" local wgt `"[`weight' `exp']"'
	else local wgt

	if "`ige'" == "igeg" local cmd regress
	else if "`ige'" == "igee" local cmd poisson

	if "`show'"=="" local mod qui
	else if "`show'"!="" local mod noi

	tempname current
	_estimates hold `current', restore nullok
	preserve

	/*2. Common sample*/

	marksample touse
	markout `touse' `cvar' `pvar' `exvars' `insts1' `insts2' `insts3' `insts4' `insts5' `insts6' `insts7' `insts8' `insts9' `insts10' `cluster'

	qui keep if `touse'
	qui gen __id =_n
	tempfile msamp
	qui save `msamp', replace

	/*3. Intrument-set-specific samples/variables*/

	foreach instset of numlist 1/`ninstsets' {

		qui use `msamp', clear

		qui gen __one_`instset' = 1
		qui gen __pvar_`instset'= `pvar'

		foreach evar in `exvars' {
			qui gen __ex`evar'_`instset'=`evar'
		}

		foreach inst in `insts`instset'' {
			qui gen __ins`inst'_`instset' = `inst'
		}
		tempfile samp`instset'
		qui save `samp`instset'', replace
	}

	/*4.Combine samples*/

	clear

	foreach instset of numlist 1/`ninstsets' {

		append using `samp`instset''
	}

	if "`exvars'"=="" local ex
	else local ex __ex*

	foreach var of varlist __one* __pvar* `ex' __ins* {

		qui replace `var'=0 if `var'==.
	}

	tempfile expmsamp
	qui save `expmsamp', replace

	/*5. Estimate upper bounds, and compute correlation matrices and contact set*/

	/*Joint estimation*/

	di ""
	di as result "Joint estimation of upper bounds of partially identified IGE. . ."
	di ""

	qui use `expmsamp', clear

	if "`cluster'" == "" local clu __id
	else if "`cluster'" != "" local clu `cluster'

	if "`ige'"=="igee" | ("`ige'"=="igeg" & "`est'"=="gmm") local wm wmatrix(robust)
	else if "`ige'"=="igeg" & "`est'"=="2sls" local wm

	if "`technique'" != "" local tech technique(`technique')
	else if "`technique'" == "" {
		local tech
		if "`ige'"=="igee" local technique gn
	}

	if "`level'"=="" local lev
	else if "`level'"!="" local lev level(`level')

	`mod' iv`cmd' `est' `cvar' __one* `ex' (__pvar* = __ins*) `wgt', `wm' vce(cluster `clu') nocons `tech' `lev'

	tempname peub seub /*row vectors with point estimates and standard errors*/
	tempname minpeub

	matrix `peub' = e(b)
	matrix `peub'=`peub'[1, 1..`ninstsets']
	mata: st_numscalar("`minpeub'", min(st_matrix("`peub'")))

	local insindex
	foreach index of numlist 1/`ninstsets' {
		if `peub'[1,`index']==`minpeub' local insindex = `index'
	}

	matrix `seub' = e(V)
	matrix `seub' = vecdiag(`seub')
	matrix `seub' = `seub'[1, 1..`ninstsets']
	foreach col of numlist 1/`ninstsets' {
		matrix `seub'[1, `col'] = (`seub'[1, `col'])^(1/2)
	}

	/*Overall correlation matrix*/

	tempname omega

	mat `omega'=e(V)
	mat `omega'=`omega'[1..`ninstsets', 1..`ninstsets']
	mat `omega' = corr(`omega')

	/*Contact set*/

	if "`show'"!="" di ""
	di as result "Computing contact set using adaptive inequality selection . . ."
	di ""

	qui sum __id
	local n = r(max)

	local omegaDim = colsof(`omega')

	tempname p_bar k_bar forUstar Ustar forContactset

	scalar `p_bar' = 1 - (1/(10 * ln(`n')))

	mata: st_numscalar("`k_bar'", genQuantP(st_matrix("`omega'"),`omegaDim',`nsim',`seed', st_numscalar("`p_bar'")))

	matrix `forUstar' = `peub' + `k_bar' * `seub'
	scalar `Ustar' = .
	foreach col of numlist 1/`ninstsets' {
		if `forUstar'[1,`col'] < `Ustar' scalar `Ustar'=`forUstar'[1,`col']
	}

	matrix `forContactset' =  2 * `k_bar' * `seub'
	foreach col of numlist 1/`ninstsets' {
		mat `forContactset'[1, `col']= `forContactset'[1, `col'] + `Ustar'
	}

	local contact_set

	local cs_dim = 0
	foreach col of numlist 1/`ninstsets' {
		if `peub'[1,`col'] <= `forContactset'[1,`col'] {
			local contact_set `contact_set' `col'
			local cs_dim = `cs_dim' + 1
		}
	}

	/*Correlation matrix restricted to contact set*/

	local counter = 0
	local contact_set_rows
	local contact_set_cols
	foreach item in `contact_set' {
		local counter = `counter' + 1
		if `counter' == 1 {
			local contact_set_rows (`item' \
			local contact_set_cols (`item',
		}
		else if `counter' > 1 & `counter' < `cs_dim' {
			local contact_set_rows `contact_set_rows' `item' \
			local contact_set_cols `contact_set_cols' `item',
		}
		else if `counter' == `cs_dim' {
			local contact_set_rows `contact_set_rows' `item')
			local contact_set_cols `contact_set_cols' `item')
		}
	}

	tempname omega_contact_set

	mata: st_matrix("`omega_contact_set'", st_matrix("`omega'")[`contact_set_rows', `contact_set_cols'])

	/*6. Estimate lower bound*/

	di as result "Estimation of lower bound of partially identified IGE. . ."
	di ""

	qui use `msamp', clear

	if "`cluster'" == ""  `mod' `cmd' `cvar' `pvar' `exvars' `wgt', robust `lev'
	else if "`cluster'" != ""  `mod' `cmd' `cvar' `pvar' `exvars' `wgt', cluster(`cluster') `lev'

	tempname pelb selb

	scalar `pelb' = _b[`pvar']
	scalar `selb' = _se[`pvar']

	local n = e(N)

	/*7. Compute half-median unbiased estimate of upper bound */

	if "`hmu'"!="" {

		di as result "Computing half-median unbiased estimate of the upper-bound . . ."
		di ""

		tempname kp_hmu for_minpeub_hmu minpeub_hmu

		mata: st_numscalar("`kp_hmu'", genQuantP(st_matrix("`omega_contact_set'"),`cs_dim',`nsim',`seed', 0.5))
		mat `for_minpeub_hmu' = `peub' + `kp_hmu' * `seub'
		mata: st_numscalar("`minpeub_hmu'", min(st_matrix("`for_minpeub_hmu'")))

		local insindex_hmu
		foreach index of numlist 1/`ninstsets' {
			if `for_minpeub_hmu'[1,`index']==`minpeub_hmu' local insindex_hmu = `index'
		}
	}

	/*8. Compute confidence interval*/

	di as result "Computing confidence interval for partially identified IGE . . ."
	di ""

	tempname  deltan pn
	scalar `deltan' = `minpeub' - `pelb'
	scalar `pn' = 1 - normal(ln(`n') * `deltan') * (1 - (`level'/100))

	if "`hmu'"!="" {
		tempname  deltan_hmu pn_hmu
		scalar `deltan_hmu' = `minpeub_hmu' - `pelb'
		scalar `pn_hmu' = 1 - normal(ln(`n') * `deltan_hmu') * (1 - (`level'/100))
	}

	/*upper ci*/

	tempname kpub for_ciub ciub
	if `cs_dim' == 1 scalar `kpub' = invnormal(`pn')
	else if `cs_dim' > 1 mata: st_numscalar("`kpub'", genQuantP(st_matrix("`omega_contact_set'"),`cs_dim',`nsim',`seed',st_numscalar("`pn'")))

	matrix `for_ciub' = `peub' + `kpub' * `seub'
	mata: st_numscalar("`ciub'", min(st_matrix("`for_ciub'")))

	if "`hmu'"!="" {

		tempname kpub_hmu for_ciub_hmu ciub_hmu
		if `cs_dim' == 1 scalar `kpub_hmu' = invnormal(`pn_hmu')
		else if `cs_dim' > 1 mata: st_numscalar("`kpub_hmu'", genQuantP(st_matrix("`omega_contact_set'"),`cs_dim',`nsim',`seed',st_numscalar("`pn_hmu'")))
		matrix `for_ciub_hmu' = `peub' + `kpub_hmu' * `seub'
		mata: st_numscalar("`ciub_hmu'", min(st_matrix("`for_ciub_hmu'")))
	}

	/*lower ci*/
	tempname kplb cilb
	scalar `kplb' = invnormal(`pn')
	scalar `cilb' = `pelb' - `kplb' * `selb'

	if "`hmu'"!="" {
		tempname kplb_hmu cilb_hmu
		scalar `kplb_hmu' = invnormal(`pn_hmu')
		scalar `cilb_hmu' = `pelb' - `kplb_hmu' * `selb'
	}


	/*9. Return and display results*/

	ereturn post

	if "`ige'"=="igeg" local rige IGE of geometric mean
	else if "`ige'"=="igee" local rige IGE of expectation

	ereturn scalar N = `n'
	ereturn scalar pe_lb = `pelb'
	ereturn scalar pe_ub = `minpeub'
	if "`hmu'"!="" ereturn scalar pe_ub_hmu = `minpeub_hmu'
	ereturn scalar ci_lb = `cilb'
	ereturn scalar ci_ub = `ciub'
	if "`hmu'"!="" {
		ereturn scalar ci_lb_hmu = `cilb_hmu'
		ereturn scalar ci_ub_hmu = `ciub_hmu'
	}
	ereturn scalar k_lb = `kplb'
	ereturn scalar k_ub = `kpub'
	if "`hmu'"!="" {
		ereturn scalar k_lb_hmu = `kplb_hmu'
		ereturn scalar k_ub_hmu = `kpub_hmu'
	}
	ereturn scalar confidence_level = `level'
	ereturn scalar nsim = `nsim'
	ereturn scalar seed = `seed'

	ereturn scalar confidence_level = `level'

	if "`technique'"!="" ereturn local technique "`technique'"
	if "`cluster'"!="" ereturn local clustvar "`cluster'"
	if "`wgt'"!="" {
		ereturn local wtype "`weight'"
		ereturn local wexp "`exp'"
	}

	ereturn local iv_estimator "`est'"
	if "`exvars'"!= "" ereturn local exvars "`exvars'"

	if "`hmu'"!="" ereturn local insts_ub_hmu "`insts`insindex_hmu''"
	ereturn local insts_ub "`insts`insindex''"
	ereturn local pvar "`pvar'"
	ereturn local cvar "`cvar'"
	ereturn local ige "`ige'"
	ereturn local cmd "igeintb"

	di as txt "{hline 60}"
	di as text "{p 0 0 0 60}Partially identified `rige'{p_end}"
	di ""
	di as text _col(1) "Set estimate" _col(35) as res %6.5f `pelb' _col(43) as res "- " %6.5f `minpeub'
	di as text _col(1) "`level'% confidence interval" _col(35) as res %6.5f `cilb' _col(43) as res "- " %6.5f `ciub'
	if "`hmu'"!="" {
		di ""
		di as text _col(1) "Based on HMU estimate of upper bound"
		di as text _col(1) "Set estimate" _col(35) as res %6.5f `pelb' _col(43) as res "- " %6.5f `minpeub_hmu'
		di as text _col(1) "`level'% confidence interval" _col(35) as res %6.5f `cilb_hmu' _col(43) as res "- " %6.5f `ciub_hmu'
	}
	di as txt "{hline 60}"
	di as txt "Note:"
	di as txt "{p 0 2 60}Upper-bound estimate obtained with these instruments:{p_end}"
	di as txt "{p 4 4 6 60}`insts`insindex''{p_end}"
	if "`hmu'"!="" {
		di as txt "{p 0 2 60}HMU upper-bound estimate obtained with these instruments:{p_end}"
		di as txt "{p 4 4 6 60}`insts`insindex_hmu''{p_end}"
	}

	restore
	_estimates unhold `current', not

end

/* Mata function for generating quantile of maximum Zu (from program
   imperfectiv, by Benjamin Matta and Damian Clarke) */

cap mata: mata drop genQuantP()
mata:
    function genQuantP(OMEGA,dimOm,R,seed,p) {
        rseed(seed)
        Z  = cholesky(OMEGA)*rnormal(dimOm,R,0,1)
       pk = sort(colmax(Z)',1)[round(R*p),1]

       return(pk)
    }
end