*! imperfectiv: Estimating bounds with Nevo and Rosen's Imperfect IV procedure
*! Version 1.1.0 Dec 26, 2018 @ 12:12:12
*! Authors: Benjamin Matta and Damian Clarke

/*
Version highlights
 0.1.0 Original SSC finds reports 95% CI end points
 1.0.0 Updating based on Adam Rosen's comments.  Implements moment selection
 1.1.0 Bug fix line 275 stripped out due to non-standard character
*/

cap program drop imperfectiv
program imperfectiv, eclass
version 8.0

#delimit ;

syntax anything(name=0 id="variable list")
    [if] [in]
    [fweight pweight aweight iweight]
    [,
     Ncorr
     Prop5
     NOASsumption4
     Level(cilevel)
     vce(string)
     verbose
     bootstraps(integer 50)
     seed(numlist min=1 max=1)
     exogvars(varlist fv ts)
     ]
;
#delimit cr

*-------------------------------------------------------------------------------
*-- (1) Unpack arguments, check for valid syntax, general error capture
*-------------------------------------------------------------------------------
local 0: subinstr local 0 "=" " = ", count(local equal)
local 0: subinstr local 0 "(" " ( ", count(local lparen)
local 0: subinstr local 0 ")" " ) ", count(local rparen)

local rops  `if' `in' [`weight' `exp']
if length("`vce'")!=0 local rops  `if' `in' [`weight' `exp'], vce(`vce')

if `equal'!=1|`lparen'!=1|`rparen'!=1 {
    dis as error "Specification of varlist is incorrect."
    dis as error "Ensure that syntax is: method yvar [exog] (endog=iv), [opts]"
    exit 200
}

tokenize `0'

local yvar `1'
macro shift

local varlist1
while regexm(`"`1'"', "\(")==0 {
    local varlist1 `varlist1' `1'
    macro shift
}

local varlist2
while regexm(`"`1'"', "=")==0 {
    local var=subinstr(`"`1'"', "(", "", 1)
    local varlist2 `varlist2' `var'
    macro shift
}

local varlist_iv
while regexm(`"`1'"', "\)")==0 {
    local var=subinstr(`"`1'"', "=", "", 1)
    local varlist_iv `varlist_iv' `var'
    macro shift
}
macro shift

if length("`1'")!=0 {
    dis as error "Specification of varlist is incorrect."
    dis as error "Ensure that syntax is: imperfectiv yvar [exog] (endog=iv)"
    exit 200
}

local ci=(100-`level')/200


local kEx   : word count `varlist1'
local kEn   : word count `varlist2'
local kIV   : word count `varlist_iv'

if `kEn'!=1 {
    dis as error "One endogenous variable must be specified. Currently `kEn'."
    exit 200
}
if length(`"`exogvars'"')!=0 {
    local m1 "included in exogvars(), but not included as exogenous variable"
    local m2 "Please include all exogenous variables in model and re-estimate."
    if `kEx'==0 {
        dis as error "Variables `m1' in model."
        dis as error "`m2'"
        exit `vcheck'
    }
    foreach var1 of varlist `exogvars' {
        local vcheck = 200
        foreach var2 of varlist `varlist1' {
            if `"`var1'"'==`"`var2'"' local vcheck = 0
        }
        if `vcheck'!=0 {
            dis as error "`var1' `m1' in model."
            dis as error "`m2'"
            exit `vcheck'
        }
    }
}

*-------------------------------------------------------------------------------
*-- (2) Regressions used to construct bounds
*-------------------------------------------------------------------------------
foreach var of varlist `varlist2' `exogvars' {
    local b_l_`var'
    local b_u_`var'
    local se_l_`var'
    local se_u_`var'
}
local model_upper
local model_lower

local i=1
foreach iv of varlist `varlist_iv' {
    if length("`noassumption4'")!=0{
        if `i'==1 {
            **MODEL 1: OLS [Considered max 1 time.  Invariant to diff IVs.]
            qui: reg `yvar' `varlist1' `varlist2' `rops'
            local model m1
            foreach var of varlist `varlist2' `exogvars' {
                local beta_v_`var'= _b[`var']
                local se_v_`var'  = _se[`var']
            }
        }
        else {
            foreach var of varlist `varlist2' `exogvars' {
                local beta_v_`var'
                local se_v_`var'
            }
        }
    }
    else{
        qui: sum `varlist2'
        local endogSD = r(sd)
        qui: sum `iv'
        local ivSD    =r(sd)
        tempvar v_var_`iv'
        qui gen `v_var_`iv''=`endogSD'*`iv'-`ivSD'*`varlist2'

        **MODEL 2: Each IV based on transformed instrument where lambda=1
        qui: ivregress 2sls `yvar' `varlist1' (`varlist2'=`v_var_`iv'') `rops'
        local model m2_`iv'
        foreach var of varlist `varlist2' `exogvars' {
            local beta_v_`var'= _b[`var']
            local se_v_`var'  = _se[`var']
        }
    }

    **MODEL 3: Each IV based on original IV
    qui: ivregress 2sls `yvar' `varlist1' (`varlist2'=`iv') `rops'
    tempvar estsample
    qui gen `estsample'=e(sample)
    local N=e(N)

    foreach var of varlist `varlist2' `exogvars' {
        local beta_iv_`var'= _b[`var']
        local se_iv_`var'  = _se[`var']
    }

    ****In what follows, select bound candidates based on correlations
    if `kEx'==0 | length("`noassumption4'")!=0{
        qui corr `varlist2' `iv'
        if r(rho)<0{
            if length("`ncorr'")!=0{
                foreach var of varlist `varlist2' `exogvars' {
                    local b_l_`var'  `b_l_`var'' `beta_v_`var''
                    local b_u_`var'  `b_u_`var'' `beta_iv_`var''
                    local se_l_`var' `se_l_`var'' `se_v_`var''
                    local se_u_`var' `se_u_`var'' `se_iv_`var''
                }
                local model_lower `model_lower' `model'
                local model_upper `model_upper' m3_`iv'
            }
            else {
                foreach var of varlist `varlist2' `exogvars' {
                    local b_l_`var'  `b_l_`var'' `beta_iv_`var''
                    local b_u_`var'  `b_u_`var'' `beta_v_`var''
                    local se_l_`var' `se_l_`var'' `se_iv_`var''
                    local se_u_`var' `se_u_`var'' `se_v_`var''
                }
                local model_lower `model_lower' m3_`iv'
                local model_upper `model_upper' `model'
            }
        }
        else{
            if length("`ncorr'")!=0{
                foreach var of varlist `varlist2' `exogvars' {
                    local b_l_`var'  `b_l_`var''  `beta_iv_`var'' `beta_v_`var''
                    local se_l_`var' `se_l_`var'' `se_iv_`var'' `se_v_`var''
                }
                local model_lower `model_lower' m3_`iv' `model'
            }
            else {
                foreach var of varlist `varlist2' `exogvars' {
                    local b_u_`var'  `b_u_`var''  `beta_iv_`var'' `beta_v_`var''
                    local se_u_`var' `se_u_`var'' `se_iv_`var'' `se_v_`var''
                }
                local model_upper `model_upper' m3_`iv' `model'
            }
        }
    }
    else{
        qui: reg `varlist2' `varlist1' `rops'
        tempvar x2
        predict `x2', residuals
        qui: corr `varlist2' `x2', covariance

        local endogCOV = r(cov_12)
        qui: corr `iv' `x2', covariance
        local ivCOV   = r(cov_12)
        local Q1 = (`endogCOV'*`ivSD'-`ivCOV'*`endogSD')*`ivCOV'
        if length("`ncorr'")!=0 local ivCOV = (-1)*`ivCOV'

        if `Q1'<0{
            if `ivCOV'<0{
                foreach var of varlist `varlist2' `exogvars' {
                    local b_l_`var'  `b_l_`var'' `beta_iv_`var''
                    local b_u_`var'  `b_u_`var'' `beta_v_`var''
                    local se_l_`var' `se_l_`var'' `se_iv_`var''
                    local se_u_`var' `se_u_`var'' `se_v_`var''
                }
                local model_lower `model_lower' m3_`iv'
                local model_upper `model_upper' `model'
            }
            else {
                foreach var of varlist `varlist2' `exogvars' {
                    local b_l_`var'  `b_l_`var'' `beta_v_`var''
                    local b_u_`var'  `b_u_`var'' `beta_iv_`var''
                    local se_l_`var' `se_l_`var'' `se_v_`var''
                    local se_u_`var' `se_u_`var'' `se_iv_`var''
                }
                local model_lower `model_lower' `model'
                local model_upper `model_upper' m3_`iv'
            }
        }
        else{
            if `ivCOV'>0{
                foreach var of varlist `varlist2' `exogvars' {
                    local b_u_`var'  `b_u_`var''  `beta_iv_`var'' `beta_v_`var''
                    local se_u_`var' `se_u_`var'' `se_iv_`var'' `se_v_`var''
                }
                local model_upper `model_upper' m3_`iv' `model'
            }
            else {
                foreach var of varlist `varlist2' `exogvars' {
                    local b_l_`var'  `b_l_`var''  `beta_iv_`var'' `beta_v_`var''
                    local se_l_`var' `se_l_`var'' `se_iv_`var'' `se_v_`var''
                }
                local model_lower `model_lower' m3_`iv' `model'
            }
        }
    }
    local ++i
    local model
}

if `kIV'>1 & length("`prop5'")!=0 {
    tokenize `varlist_iv'
    tempvar omega
    *qui gen `omega'=0.5*`1'-0.5*`2'
    qui gen `omega'=0.5*`2'-0.5*`1'

    **MODEL 4: IV based on generated instrument from better and worse IV
    qui: ivregress 2sls `yvar' `varlist1' (`varlist2'=`omega') `rops'
    foreach var of varlist `varlist2' `exogvars' {
        local beta_omega_`var'= _b[`var']
        local se_omega_`var'  =_se[`var']
    }
    local num_lo : word count `model_lower'
    local num_up : word count `model_upper'

    if `num_lo'==0  {
        foreach var of varlist `varlist2' `exogvars' {
            local b_l_`var'  `beta_omega_`var''
            local se_l_`var' `se_omega_`var''
        }
        local model_lower `model_lower' m4
    }
    if `num_up'==0 {
        foreach var of varlist `varlist2' `exogvars' {
            local b_u_`var'  `beta_omega_`var''
            local se_u_`var' `se_omega_`var''
        }
        local model_upper `model_upper' m4
    }
    if length("`noassumption4'")==0 {
        qui: sum `varlist2'
        local endogSD = r(sd)
        qui: sum `omega'
        local omegaSD = r(sd)

        tempvar omega_var
        qui gen `omega_var'=`endogSD'*`omega'-`omegaSD'*`varlist2'
        **MODEL 5: IV based on generated instrument from better and worse IV with omega
        qui: ivregress 2sls `yvar' `varlist1' (`varlist2'=`omega_var') `rops'
        foreach var of varlist `varlist2' `exogvars' {
            local beta_omega_var_`var'= _b[`var']
            local se_omega_var_`var'  =_se[`var']
        }

        if `num_lo'==0  {
            foreach var of varlist `varlist2' `exogvars' {
                local b_u_`var'  `b_u_`var'' `beta_omega_var_`var''
                local se_u_`var' `se_u_`var'' `se_omega_var_`var''
            }
            local model_upper `model_upper' m5
        }

        if `num_up'==0 {
            foreach var of varlist `varlist2' `exogvars' {
                local b_l_`var'   `b_l_`var''  `beta_omega_var_`var''
                local se_l_`var'  `se_l_`var'' `se_omega_var_`var''
            }
            local model_lower `model_lower' m5
        }
    }
}

*-------------------------------------------------------------------------------
*-- (3) Find betas and max SEs
*-------------------------------------------------------------------------------
local num_lo : word count `model_lower'
local num_up : word count `model_upper'
if `num_lo'>1 {
    foreach var of varlist `varlist2' `exogvars' {
        tokenize `se_l_`var''
        local maxSEl_`var'=0
        local j = 1
        foreach beta of local b_l_`var' {
            if `j'==1 {
                local maxLB_`var' = `beta'
                local maxLBse_`var' = ``j''
            }
            if `beta'>`maxLB_`var'' {
                local maxLB_`var' = `beta'
                local maxLBse_`var' = ``j''
            }
            if ``j''>`maxSEl_`var'' local maxSEl_`var' = ``j''
            local ++j
        }
    }
}
else if `num_lo'==1 {
    foreach var of varlist `varlist2' `exogvars' {
        local maxLB_`var' = `b_l_`var''
        local maxLBse_`var' = `se_l_`var''
    }
}

if `num_up'>1 {
    foreach var of varlist `varlist2' `exogvars' {
        tokenize `se_u_`var''
        local maxSEu_`var'=0
        local minUB_`var'=.
        foreach beta of local b_u_`var' {
            if `beta'<`minUB_`var'' {
                local minUB_`var' = `beta'
                local minUBse_`var' = `1'
            }
            if `1'>`maxSEu_`var''   local maxSEu_`var' = `1'
            macro shift
        }
    }
}
else if `num_up'==1 {
    foreach var of varlist `varlist2' `exogvars' {
        local minUB_`var' = `b_u_`var''
        local minUBse_`var' = `se_u_`var''
    }
}

*-------------------------------------------------------------------------------
*-- (4) Determine contact set for inference (only necessary if >1 candidate)
*-------------------------------------------------------------------------------
local c=2
if `num_lo'>1 {
    foreach var of varlist `varlist2' `exogvars' {
        local contactLower_`var'
        local contactLowerB_`var'
        local contactLowerSE_`var'
        foreach num of numlist 1(1)`num_lo' {
            tokenize `b_l_`var''
            local beta = ``num''
            tokenize `se_l_`var''
            local se   = ``num''
            tokenize `model_lower'
            local model  "``num''"

            if `beta'>=(`maxLB_`var''-(`c'*`maxSEl_`var''*sqrt(log(`N')))) {
                local contactLower_`var'   `contactLower_`var''   `model'
                local contactLowerB_`var'  `contactLowerB_`var''  `beta'
                local contactLowerSE_`var' `contactLowerSE_`var'' `se'
            }
        }
    }
}

if `num_up'>1 {
    foreach var of varlist `varlist2' `exogvars' {
        local contactUpper_`var'
        local contactUpperB_`var'
        local contactUpperSE_`var'
        foreach num of numlist 1(1)`num_up' {
            tokenize `b_u_`var''
            local beta = ``num''
            tokenize `se_u_`var''
            local se   = ``num''
            tokenize `model_upper'
            local model  "``num''"

            if `beta'<=(`minUB_`var''+(`c'*`maxSEu_`var''*sqrt(log(`N')))) {
                local contactUpper_`var'   `contactUpper_`var''   `model'
                local contactUpperB_`var'  `contactUpperB_`var''  `beta'
                local contactUpperSE_`var' `contactUpperSE_`var'' `se'
            }
        }
    }
}

*-------------------------------------------------------------------------------
*-- (5) Bootstrap to create the Omega matrices (only necessary if >1 candidate)
*-------------------------------------------------------------------------------
local nmods = 0
local bootmodels
foreach var of varlist `varlist2' `exogvars' {
    local num_up_`var' : word count `contactUpper_`var''
    local num_lo_`var' : word count `contactLower_`var''
    if `num_lo_`var''>1 local bootmodels_`var' `contactLower_`var''
    if `num_up_`var''>1 local bootmodels_`var' `bootmodels_`var'' `contactUpper_`var''
    local nmods_`var' : word count `bootmodels_`var''
    foreach model_donor in `bootmodels_`var'' {
        local added = 0
        foreach model_total in `bootmodels' {
            if `"`model_donor'"' == `"`model_total'"' local ++added
        }
        if `added' == 0 local bootmodels `bootmodels' `model_donor'
        if `added' == 0 local ++nmods
    }
}

if length("`seed'")!=0 set seed `seed'

local addsamples=0
if `nmods'>0 {
    foreach var of varlist `varlist2' `exogvars' {
        foreach model in `bootmodels_`var'' {
            tempvar `model'_`var'
            qui gen ``model'_`var''=.
        }
    }

    cap set obs `bootstraps'
    if _rc==0 local addsamples=1
    if length(`"`verbose'"')>0 dis "Various candidates for upper or lower bounds"
    if length(`"`verbose'"')>0 dis "Bootstrap models will be listed below"
    forvalues samp=1/`bootstraps' {
        preserve
        bsample if `estsample'==1

        foreach model in `bootmodels' {
            if `"`model'"'=="m1" {
                if length(`"`verbose'"')>0&`samp'==1 dis "Original OLS"
                qui: reg `yvar' `varlist1' `varlist2' `rops'
                foreach var of varlist `varlist2' `exogvars' {
                    local m1b_`var' = _b[`var']
                }
            }
            foreach iv of varlist `varlist_iv' {
                if `"`model'"'=="m2_`iv'" {
                    if length(`"`verbose'"')>0&`samp'==1 dis "Lambda=1 IV (with `iv')"
                    qui: ivregress 2sls `yvar' `varlist1' (`varlist2'=`v_var_`iv'') `rops'
                    foreach var of varlist `varlist2' `exogvars' {
                        local m2_`iv'b_`var' = _b[`var']
                    }
                }
                if `"`model'"'=="m3_`iv'" {
                    if length(`"`verbose'"')>0&`samp'==1 dis "Original IV (with `iv')"
                    qui: ivregress 2sls `yvar' `varlist1' (`varlist2'=`iv') `rops'
                    foreach var of varlist `varlist2' `exogvars' {
                        local m3_`iv'b_`var' = _b[`var']
                    }
                }
            }
            if `"`model'"'=="m4" {
                if length(`"`verbose'"')>0&`samp'==1 dis "Better/Worse IV"
                qui: ivregress 2sls `yvar' `varlist1' (`varlist2'= `omega') `rops'
                foreach var of varlist `varlist2' `exogvars' {
                    local m4b_`var' = _b[`var']
                }
            }
            if `"`model'"'=="m5" {
                if length(`"`verbose'"')>0&`samp'==1 dis "Better/Worse Lambda=1 IV"
                qui: ivregress 2sls `yvar' `varlist1' (`varlist2'=`omega_var') `rops'
                foreach var of varlist `varlist2' `exogvars' {
                    local m5b_`var' = _b[`var']
                }
            }
        }
        restore
        foreach var of varlist `varlist2' `exogvars' {
            foreach model in `bootmodels_`var'' {
                qui replace ``model'_`var''=``model'b_`var'' in `samp'
            }
        }
        if length(`"`verbose'"')>0 dis "Bootstrap replication `samp' complete."
    }
}

foreach var of varlist `varlist2' `exogvars' {
    if `num_lo_`var''>1 {
        local OMEGAl_`var'
        foreach m of local contactLower_`var' {
            local OMEGAl_`var' `OMEGAl_`var'' ``m'_`var''
        }
        qui corr `OMEGAl_`var''
        matrix OMEGAl_`var' = r(C)
    }

    if `num_up_`var''>1 {
        local OMEGAu_`var'
        foreach m of local contactUpper_`var' {
            local OMEGAu_`var' `OMEGAu_`var'' ``m'_`var''
        }
        qui corr `OMEGAu_`var''
        matrix OMEGAu_`var' = r(C)
    }

    foreach model in `bootmodels_`var'' {
        qui drop ``model'_`var''
    }
}
local N1 = `N'+1
if `addsamples'==1 qui drop in `N1'/`bootstraps'

*-------------------------------------------------------------------------------
*-- (6) Generate bounds on endogenous variable (parameter and CI)
*-------------------------------------------------------------------------------
local ii=1
foreach var of varlist `varlist2' `exogvars' {
    if `num_lo_`var''<=1 {
        tokenize `b_l_`var''
        if length(`"`1'"')==0 {
            if `ii'==1{
                if length(`"`verbose'"')>0 dis "No candidates for Lower Bounds."
                local ++ii
            }
            local maxLB_`var' .
            local lowerbound_`var' .
        }
        else {
            local lowerbound_`var'=`maxLB_`var''-invnormal(1-`ci')*`maxLBse_`var''
        }
    }
    if `num_up_`var''<=1 {
        tokenize `b_u_`var''
        if length(`"`1'"')==0 {
            if `ii'==1{
                if length(`"`verbose'"')>0 dis "No candidates for Upper Bounds."
                local ++ii
            }
            local minUB_`var' .
            local upperbound_`var' .
        }
        else {
            local upperbound_`var' = `minUB_`var''+invnormal(1-`ci')*`minUBse_`var''
        }
    }

    if `num_up_`var''!=0&`num_lo_`var''!=0 {
        local Delta = abs(`minUB_`var''-`maxLB_`var'')
        local pn    = 1-(normal(log(`N')*`Delta')*`ci')
    }
    else local pn   = 1-`ci'

    local R     = 10000
    local seed  = round(runiform()*10000)

    if `num_lo_`var''>1 {
        mata: st_numscalar("pkl",genQuantP(st_matrix("OMEGAl_`var'"),`num_lo_`var'',`R',`seed',`pn'))
        tokenize `contactLowerSE_`var''
        local j = 1
        foreach beta of local contactLowerB_`var' {
            local LB = `beta'-``j''*pkl
            if `j'==1 {
                local lowerbound_`var' = `LB'
            }
            if `LB'>`lowerbound_`var'' local lowerbound_`var' = `LB'
            local ++j
        }
    }
    if `num_up_`var''>1 {
        mata: st_numscalar("pku",genQuantP(st_matrix("OMEGAu_`var'"),`num_up_`var'',`R',`seed',`pn'))
        local upperbound_`var' .
        tokenize `contactUpperSE_`var''
        foreach beta of local contactUpperB_`var' {
            local UB = `beta'+`1'*pku
            if `UB'<`upperbound_`var'' local upperbound_`var' = `UB'
            macro shift
        }
    }
}


*-------------------------------------------------------------------------------
*-- (7) Return
*-------------------------------------------------------------------------------
local NR "Nevo and Rosen (2012)"
local exolong  : word count `exogvars'
matrix M_bounds = J(`exolong'+1,4,.)
local j=1
foreach var of varlist `varlist2' `exogvars' {
    matrix M_bounds[`j',2] = `maxLB_`var''
    matrix M_bounds[`j',3] = `minUB_`var''
    matrix M_bounds[`j',1] = `lowerbound_`var''
    matrix M_bounds[`j',4] = `upperbound_`var''
    local ++j
}
matrix colnames M_bounds = ci_LB  LB UB ci_UB
matrix rowname  M_bounds = `varlist2' `exogvars'

#delimit ;
dis _newline;
dis "`NR''s Imperfect IV bounds" _col(55) "Number of obs =    " e(N);
dis in yellow in smcl "{hline 78}";
dis "Variable" _col(18) "Lower Bound(CI) " _col(32) " LB(Estimator) " _col(46)
    "UB(Estimator) "  _col(60) "Upper Bound(CI)";
dis in yellow in smcl "{hline 78}";
#delimit cr

foreach var of varlist `varlist2' `exogvars' {
    local eLB = `maxLB_`var''
    local eCIlb = `lowerbound_`var''
    local eCIub = `upperbound_`var''
    local eUB = `minUB_`var''
    #delimit ;
    dis in green "`var' " _col(20) "["`eCIlb' _col(36) "("`eLB'
    _col(50) `eUB' ")" _col(65) `eCIub' "]";
    #delimit cr
}
if `"`eCIlb'"'=="."|`"`eCIub'"'=="." {
    dis in yellow in smcl "{hline 78}"
    dis "Note: One sided bounds only are returned. Refer to `NR' "
    dis "for situations in which two-sided bounds are possible."
}
else dis in yellow in smcl "{hline 78}"

foreach var of varlist `varlist2' {
    ereturn scalar lb_`var'=`maxLB_`var''
    ereturn scalar ub_`var'=`minUB_`var''
    ereturn scalar CIlb_`var'=`lowerbound_`var''
    ereturn scalar CIub_`var'=`upperbound_`var''
    ereturn matrix LRbounds M_bounds
}



end

*-------------------------------------------------------------------------------
*-- (8) Mata functions for generating analytical quantile bound
*-------------------------------------------------------------------------------
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