*! version 1.1.2  22aug2014  Ben Jann, Jennie E. Brand, Yu Xie

program hte
    version 10
    local caller : di _caller()
    gettoken subcmd 0 : 0
    if inlist(`"`subcmd'"', "sm", "ms", "sd") {
        version `caller': hte_`subcmd' `0'
    }
    else {
        di as err `"`subcmd' is not a valid subcommand;"' _c
        di as err " must type {cmd:hte sm}, {cmd:hte ms}, or {cmd:hte sd}"
        exit 198
    }
end

/* SM ---------------------------------------------------------------------- */

program hte_sm, rclass
    version 9.2
    syntax [anything(equalok)] [if] [in] [fw iw pw] [, * ]
    if replay() {
        hte_sm_tabulate `0'
        exit
    }
    gettoken first second : anything
    local flen = strlen(`"`first'"')
    if `"`first'"'==substr("graph",1,max(2,`flen')) & `"`second'"'=="" {
        hte_sm_graph `if' `in' [`weight'`exp'], `options'
        exit
    }
    hte_sm_compute `anything' `if' `in' [`weight'`exp'], `options'
end

program hte_sm_graph
    syntax [, Level(cilevel) Outcomes(numlist integer min=1) noCI * ]
    local z = invnorm((100+`level')/200)

    if `"`e(cmd)'"'!="hte sm" {
        di as err "hte sm results not found"
        exit 301
    }

    _get_gropts , graphopts(`options') getallowed(LINEOPts CIOPts plot addplot)
    local options `"`s(graphopts)'"'
    local lineopts `"`s(lineopts)'"'
    local ciopts `"`s(ciopts)'"'
    _check4gropts lineopts, opt(`lineopts')
    _check4gropts ciopts, opt(`ciopts')
    local plot `"`s(plot)'"'
    local addplot `"`s(addplot)'"'

    tempname b se block lfit coefs tmp
    mat `b'     = e(b)
    mat `se'    = e(se)
    mat `block' = e(block)
    mat `lfit'  = e(lfit)
    local neq   = e(neq)
    if "`outcomes'"=="" {
        if `neq'==1 local outcomes "_"
        else {
            numlist "1/`neq'"
            local outcomes "`r(numlist)'"
        }
    }
    else if `neq'==1 {
        local outcomes: subinstr local outcomes "1" "_", word all
    }
    foreach eq of local outcomes {
        mat `tmp' = `b'[1...,`"`eq':"']', `se'[1...,`"`eq':"']' // => error if outcome not found
        mat `tmp' = `tmp'[1..rowsof(`tmp')-2,1...], ///
            `block'[1...,`"`eq':"']', `lfit'[1...,`"`eq':"']'
        mat `coefs' = nullmat(`coefs') \ `tmp'
    }
    tempname B SE ID FIT touse
    mat coln `coefs' = `B' `SE' `ID' `FIT'
    svmat `coefs', names(col)
    qui gen byte `touse' = `ID'<.

    local depvar `"`e(depvar)'"'
    local ndepvar : list sizeof depvar
    local hasby = (`"`e(byvar)'"'!="")
    if `:list sizeof outcomes'==1 {
        local Bvars   `B'
        local FITvars `FIT'
        lab var `B'     "TE within strata"
        lab var `FIT'   "linear trend"
        if "`ci'"==""   {
            tempname LB UB
            qui gen `LB' = `B' - `z'*`SE'
            qui gen `UB' = `B' + `z'*`SE'
            local CIgraph (rcap `LB' `UB' `ID' if `touse', `ciopts')
            local legend legend(label(1 "`level'% CI"))
        }
        local title
        if `neq'>1 {
            if `ndepvar'>1 {
                local title `"`e(depvar`outcomes')'"'
                if `hasby' {
                    local title `"`title': "'
                }
            }
            if `hasby' {
                local title `"`title'`e(byvar)' = `e(by`outcomes')'"'
            }
            local title `"title(`title')"'
            local trend subtitle(`"`e(trend`outcomes')'"')
        }
        else {
            local trend subtitle(`"`e(trend)'"')
        }
    }
    else {
        local eqlist: roweq `coefs'
        local k = rowsof(`coefs')
        tempname by
        qui gen byte `by' = .
        forv i = 1/`k' {
            gettoken eq eqlist : eqlist
            qui replace `by' = `eq' in `i'
        }
        local i 0
        foreach eq of local outcomes {
            local ++i
            tempname B_`i' FIT_`i' LB_`i' UB_`i'
            qui gen `B_`i''   = `B'     if `by'==`eq'
            qui gen `FIT_`i'' = `FIT'   if `by'==`eq'
            local eqlab
            if `ndepvar'>1 {
                local eqlab `"`e(depvar`eq')'"'
                if `hasby' {
                    local eqlab `"`eqlab': "'
                }
            }
            if `hasby' {
                local eqlab `"`eqlab'`e(byvar)' = `e(by`eq')'"'
            }
            lab var `B_`i''   `"`eqlab'"'
            lab var `FIT_`i'' `"`eqlab'"'
            local Bvars   `Bvars' `B_`i''
            local FITvars `FITvars' `FIT_`i''
            local pstyles `pstyles' p`i'
            local legend `legend' `=`neqs'+`i''
        }
        local pstyles pstyle(`pstyles')
        local legend legend(order(`legend'))
    }
    su `ID', mean
    local xlabel "xlabel(1(1)`r(max)')"
    if r(max)==1 local xlabel // fix stata graph bug

    local Lgraph (line `FITvars' `ID' if `touse', `pstyles' `lineopts')
    graph twoway                                    ///
        `CIgraph'                                   ///
        (scatter `Bvars' `ID' if `touse',           ///
            `pstyles'                               ///
            `xlabel'                                ///
            xvarlabel("Propensity Score Strata")    ///
            ytitle("Treatment Effect")              ///
            `legend' `title' `trend'                ///
            `options'                               ///
        )                                           ///
        `Lgraph'                                    ///
        || `plot' || `addplot'                      ///
        // blank
end

program hte_sm_tabulate
    syntax [, Level(cilevel) ]
    if `"`e(cmd)'"'!="hte sm" {
        di as err "hte sm results not found"
        exit 301
    }
    local z = invnorm((100+`level')/200)
    tempname b se
    mat `b' = e(b)
*    capt confirm matrix e(V)
*    if _rc | e(vce)=="bootstrap" {
        mat `se' = e(se)
*    }
*    else { // needed for jackknife, but not for bootstrap
*        mat `se' = vecdiag(e(V))
*        forv i=1/`=colsof(`se')' {
*            mat `se'[1,`i'] = sqrt(`se'[1,`i'])
*        }
*    }
    local namelist : colname `b'
    local eqlist : coleq `b'
    local k = colsof(`b')
    local depvar `"`e(depvar)'"'
    local hasby = (`"`e(byvar)'"'!="")
    di as txt _n _col(56) "Number of obs =" as res %8.0g  e(N)
    local topline as txt "{hline 13}{c TT}{hline 64}"
    local headline as txt %12s abbrev(`"\`depv'"',12) " {c |}" %11s "Coef." ///
        %12s "Std. Err." %8s "z " %8s "P>|z|" %25s "[`level'% Conf. Interval]"
    local sepline as txt "{hline 13}{c +}{hline 64}"
    local botline as txt "{hline 13}{c BT}{hline 64}"
    local legend as txt "TE = treatment effect"
    local eqlabstrata as res %-12s "TE by strata" as txt " {c |}" %64s ""
    local eqlabtrend as res %-12s "Linear trend" as txt " {c |}" %64s ""
    local eq0
    forv i = 1/`k' {
        gettoken name namelist : namelist
        gettoken eq eqlist : eqlist
        if "`eq'"!="`eq0'" {
            local eq0 "`eq'"
            if `i'>1 {
                di `botline'
                di ""
            }
            if "`eq'"=="_"  local depv `"`depvar'"'
            else            local depv `"`e(depvar`eq')'"'
            if `hasby' {
                di as res `"-> `e(byvar)' = `e(by`eq')'"'
            }
            di `topline'
            di `headline'
            di `sepline'
            di `eqlabstrata'
        }
        if `"`name'"'=="_slope" {
            di `sepline'
            di `eqlabtrend'
        }
        local t = `b'[1,`i']/`se'[1,`i']
        di as txt %12s abbrev(`"`name'"',12) " {c |}" ///
            "  "   as res %9.0g `b'[1,`i'] ///
            "  "   as res %9.0g `se'[1,`i'] ///
            "  "   as res %7.2f `t' ///
            "   "  as res %5.3f 2*norm(-abs(`t')) ///
            "    " as res %9.0g `b'[1,`i']-`se'[1,`i']*`z' ///
            "   "  as res %9.0g `b'[1,`i']+`se'[1,`i']*`z'
    }
    di `botline'
    di `legend'
end

program hte_sm_compute
    local stdopts ///
        by(passthru) SEParate NOIsily Level(passthru) CASEwise LISTwise Replace ///
        join(passthru) AUTOjoin AUTOjoin2(passthru) _blockid(passthru) /// _blockid() undocumented
        ALpha(passthru) pscore(passthru) blockid(passthru) logit comsup numblo(passthru) DETail  /// pscore opts
        CONtrols(passthru) ESTcom(passthru) ESTOPts(passthru)  /// regress options
        // blank
    syntax anything(equalok id="varlist") [if] [in] [fw iw pw] [, `stdopts' noGRaph * ]
    if "`graph'"!="" {
        syntax anything(equalok id="varlist") [fw iw pw] [if] [in] [, `stdopts' noGRaph ]
    }
    if "`detail'"!="" local noisily noisily

    gettoken depvar varlist : anything, parse("=")  // Parse "y1 [ y2 y3 ... = ] x1 x2 ..."
    if `"`depvar'"'=="=" {
        di as err "depvar required"
        exit 198
    }
    else if `"`varlist'"'!="" {
        gettoken junk varlist : varlist, parse("=") // get rid of "="
    }
    else {
        gettoken depvar varlist : anything          // just one depvar
    }

    capt which pscore
    if _rc {
        di as err "-pscore- is required. To install, type: " ///
            `"{stata "net install st0026_2, from(http://www.stata-journal.com/software/sj5-3)"}"'
        exit _rc
    }

    _hte_sm_compute `varlist' `if' `in' [`weight'`exp'], depvar(`depvar')          ///
        `by' `separate' `noisily' `casewise' `listwise' `replace'               ///
        `join' `autojoin' `autojoin2' `_blockid'                                ///
        `alpha' `pscore' `blockid' `logit' `comsup' `numblo' `detail'           ///
        `controls' `estcom' `estopts'

    hte_sm_tabulate , `level'

    if "`graph'"=="" {
        hte_sm_graph , `level' `options'
    }
end

program _hte_sm_compute, eclass
    syntax varlist(numeric) [if] [in] [fw iw pw] , depvar(varlist numeric) [ ///
        by(varname) SEParate NOIsily CASEwise LISTwise Replace ///
        join(str) AUTOjoin AUTOjoin2(passthru) _blockid(varname) /// _blockid() undocumented
        ALpha(passthru) pscore(name) blockid(name) logit comsup numblo(passthru) DETail /// pscore opts
        CONtrols(str) ESTcom(str) ESTOPts(passthru) /// regress options
        ]
    if "`autojoin2'"!="" local autojoin autojoin
    foreach opt in alpha join autojoin {
        if "`_blockid'"!="" & `"``opt''"'!="" {
            di as err "`opt'() not allowed with _blockid()"
            exit 198
        }
    }
    if "`_blockid'"!="" & `"`pscore'`blockid'`logit'`comsup'`numblo'"'!="" {
        di as err "{it:pscore_options} not allowed with _blockid()"
        exit 198
    }
    if `"`estcom'"'=="" local estcom regress
    hte_sm_ParseJoin `join' // returns local join
    if `"`controls'"'!="" {
        hte_sm_ParseControls `controls'
    }
    if "`listwise'"!="" local casewise casewise
    if "`replace'"=="" {
        foreach var in `pscore' `blockid' {
            confirm new var `var'
        }
    }

    // mark obs
    gettoken treatvar indepvars : varlist
    marksample touse
    if "`casewise'"!="" {
        markout `touse' `depvar' `controlvars'
    }
    if "`by'"!="" {
        markout `touse' `by', strok
    }
    if "`_blockid'"!="" {
         markout `touse' `_blockid', strok
    }
    tab `treatvar' if `touse', nofreq
    if r(N)==0 error 2000
    if r(r)!=2 {
        if r(r)>2 di as err "more than 2 groups found in treatvar, only 2 allowed"
        else      di as err "less than 2 groups found in treatvar, 2 required"
        exit 420
    }

    // compute treatment effects
    local ndepvar : list sizeof depvar
    if "`by'"!="" {
        qui levelsof `by' if `touse', local(bygrps)
        local byvar `by'
        local nby: list sizeof bygrps
    }
    else {
        local byvar  1
        local bygrps 1
        local nby    1
    }
    local neq = `ndepvar' * `nby'
    if "`_blockid'"!="" {
        local idvar `_blockid'
    }
    else {
        tempvar psvar idvar
        qui gen      `psvar' = .
        qui gen byte `idvar' = .
    }
    if `"`separate'"'=="" & "`_blockid'"=="" {
        _hte_sm_compute_pstrata `varlist' if `touse' [`weight'`exp'], ///
            pscore(`psvar') blockid(`idvar') ///
            `noisily' `join' `autojoin' `autojoin2' ///
            `alpha' `logit' `comsup' `numblo' `detail'
    }
    tempname b se obs block lfit tmp
    local j 0
    local i 0
    foreach depv of local depvar {
        local ++j
        if `ndepvar'>1 {
            qui `noisily' di as res _n `"-> Outcome variable is: `depv'"'
        }
        gettoken byi rest : bygrps, quotes
        while (`"`byi'"'!="") {
            local ++i
            if "`by'"!="" {
                qui `noisily' di as res _n `"-> Results for `by' == `byi':"'
            }
            if `"`separate'"'!="" & "`_blockid'"=="" & `j'==1 {
                _hte_sm_compute_pstrata `varlist' if `touse' & `byvar'==`byi' [`weight'`exp'], ///
                    pscore(`psvar') blockid(`idvar') ///
                    `noisily' `join' `autojoin' `autojoin2' ///
                    `alpha' `logit' `comsup' `numblo' `detail'
            }
            __hte_sm_compute `varlist' if `touse' & `byvar'==`byi' [`weight'`exp'], ///
                depvar(`depv') estcom(`estcom') blockid(`idvar') ///
                `noisily' `controls' `estopts'
            foreach m in b se obs block lfit {
                mat `tmp' = r(`m')
                if `neq'>1 {
                    mat coleq `tmp' = "`i'"
                }
                mat ``m'' = nullmat(``m'') , `tmp'
            }
            gettoken byi rest : rest, quotes
        }
    }

    // returns
    local wtype = cond("`weight'"=="pweight", "aweight", "`weight'")
    su `touse' if `touse' [`wtype'`exp'], mean
    local nobs = r(N)
    ereturn post `b', obs(`nobs') esample(`touse')
    ereturn scalar neq = `neq'
    ereturn local wexp "`exp'"
    ereturn local wtype "`weight'"
    if "`by'"!="" {
        capt confirm str var `by'
        local bynotstr = _rc
        ereturn local byvar `by'
    }
    if `ndepvar'>1 | "`by'"!="" {
        local i 0
        foreach depv of local depvar {
            gettoken byi rest : bygrps, quotes
            while (`"`byi'"'!="") {
                local ++i
                ereturn local depvar`i' `depv'
                if "`by'"!="" {
                    if `bynotstr' {
                        local byi: label (`by') `byi'
                        local byi `"`"`byi'"'"'
                    }
                    ereturn local by`i' `byi'
                }
                local slope: di %5.3f [`i']_b[_slope]
                local slope_se: di %5.3f `se'[1, colnumb(`se', `"`j':_slope"')]
                ereturn local trend`i' `"slope of linear trend (s.e.) = `slope' (`slope_se')"'
                gettoken byi rest : rest, quotes
            }
        }
        assert (`neq'==`i') // must be equal
    }
    else {
        local slope: di %5.3f _b[_slope]
        local slope_se: di %5.3f `se'[1, colnumb(`se', "_slope")]
        ereturn local trend `"slope of linear trend (s.e.) = `slope' (`slope_se')"'
    }
    ereturn local controls  `controls'
    ereturn local indepvars `indepvars'
    ereturn local treatvar  `treatvar'
    ereturn local depvar    `depvar'
    ereturn local estcom    `estcom'
    ereturn local cmd "hte sm"
    foreach mat in lfit block obs se {
        ereturn matrix `mat' = ``mat''
    }
    if "`pscore'"!="" {
        Vreturn `psvar' `pscore' `replace'
    }
    if "`blockid'"!="" {
        Vreturn `idvar' `blockid' `replace'
    }
    // total N vs. N_comsup (or return N_outofsup)
end

program hte_sm_ParseJoin
    gettoken nlist rest : 0, parse(",")
    while (`"`nlist'"'!="") {
        capt numlist `"`nlist'"', integer range(>0) min(2) sort
        if _rc {
            di as err `"join(): numlist invalid"'
            exit 198
        }
        local nlist `"`r(numlist)'"'
        gettoken last nlist0 : nlist
        foreach num of local nlist0 {
            if `num'!=`last'+1 {
                di as err "join(): numlist not consecutive"
                exit 198
            }
            local last `num'
        }
        local dis: list fulllist & nlist
        if `"`dis'"'!="" {
            di as err "join(): numlists not disjunctive"
            exit 198
        }
        local fulllist : list fulllist | nlist
        local join "`join'`comma'`nlist'"
        local comma ", "
        gettoken nlist rest : rest, parse(",") // get rid of the comma
        gettoken nlist rest : rest, parse(",")
    }
    if `"`join'"'!="" {
        local join join(`join')
    }
    c_local join `join'
end

program hte_sm_ParseControls
    local S 0       // number of set
    local vtmp      // hold varlist
    local stmp      // hold strata numbers
    local 0: subinstr local 0 "," " ", all // allow comma separated list
    gettoken chunk rest : 0, parse(" :")
    local colonok 0
    local vlistok 1
    local nlistok 1
    while `"`chunk'"'!="" {
        gettoken dash : rest, parse("-")
        if `"`dash'"'=="-" {   // bind x - y
            gettoken dash rest : rest, parse("-")
            gettoken dash rest : rest, parse(" :")
            local chunk `"`chunk'-`dash'"'
        }
        if `"`chunk'"'==":" {
            if `colonok'==0 {
                di as err "invalid controls()"
                exit 198
            }
            local colonok 0
            local vlistok 1
            local nlistok 0
            gettoken chunk rest : rest, parse(" :")
            local ++S  // next set
            continue
        }
        capt unab v : `chunk'
        if _rc==0 | _rc==111 {
            if `vlistok'==0 {
                di as err "invalid controls()"
                exit 198
            }
            if _rc==111 {
                di as err "invalid controls(): " _c
                unab v : `chunk', name(controls())
                exit _rc // not needed
            }
            local vtmp `vtmp' `v'
            if `"`stmp'"'!="" {
                local s`S' : list uniq stmp
                local stmp
            }
            local colonok 0
            local nlistok 1
            gettoken chunk rest : rest, parse(" :")
            continue
        }
        capt numlist `"`chunk'"', integer range(>0) sort
        if _rc==0 {
            if `nlistok'==0 {
                di as err "invalid controls()"
                exit 198
            }
            local stmp `stmp' `r(numlist)'
            if `"`vtmp'"'!="" {
                local v`S' : list uniq vtmp
                local vtmp
            }
            local colonok 1
            local vlistok 0
            gettoken chunk rest : rest, parse(" :")
            continue
        }
        di as err "invalid controls()"
        exit 198
    }
    if `"`vtmp'"'!="" { // get last varlist
        local v`S' `vtmp'
        local vtmp
    }
    local ncontrols `v0'  // common controls
    local allv `v0'
    forv i=1/`S' {
        foreach s of local s`i' {
            local sv`s' `sv`s'' `v`i''
        }
        local alls : list alls | s`i'
        local allv : list allv | v`i'
    }
    if `"`alls'"'!="" {
        numlist `"`alls'"', sort
        local alls `r(numlist)'
        foreach s of local alls {
            local sv`s' : list uniq sv`s'
            local ncontrols `"`ncontrols',`s':`sv`s''"'
        }
    }
    c_local controls controls(`ncontrols') // layout is: [varlist][,#:varlist[,#:varlist[...]]]
    c_local controlvars `allv'
end

program hte_sm_ParseControls2
    gettoken chunk rest : 0, parse(",")
    if `"`chunk'"'!="," {
        local allv `chunk'
        c_local commoncontrols `chunk'
        gettoken chunk rest : rest, parse(",")
    }
    local i 0
    gettoken chunk rest : rest, parse(",")
    while (`"`chunk'"'!="") {
        gettoken s chunk : chunk, parse(":")  // get #
        gettoken chunk v : chunk, parse(":")  // remove :
        local allv : list allv | v
        c_local controls_`s' `v'
        gettoken chunk rest : rest, parse(",") // remove ,
        gettoken chunk rest : rest, parse(",")
    }
    c_local allcontrols `allv'
end

prog _hte_sm_compute_pstrata
    syntax varlist(numeric) [if] [in] [fw iw pw], pscore(varname) blockid(varname) [ ///
        NOIsily join(str) AUTOjoin AUTOjoin2(integer 0) ALpha(str asis) ///
        logit comsup numblo(passthru) DETail /// pscore opts
        ]
    if `"`join'"'!="" & ("`autojoin'"!="" | `autojoin2'!=0) {
        di as err "only one of join() and autojoin() allowed"
        exit 198
    }
    if "`autojoin'"!="" & `autojoin2'==0 local autojoin2 10
    if `"`alpha'"'!="" {
        local level level(`alpha')
    }
    marksample touse
    gettoken treatvar indepvars : varlist

    tempname psvar idvar
    capt confirm variable comsup, exact
    if _rc==0 {     // pscore will overwite variable comsup
        tempname comsupbak
        rename comsup `comsupbak'
    }
    if "`noisily'"=="" local noiqui noisily quietly // make sure that errors and warnings appear
    else               local noiqui noisily
    capture `noiqui' pscore `treatvar' `indepvars' if `touse' [`weight'`exp'], ///
        pscore(`psvar') blockid(`idvar') `logit' `comsup' `level' `numblo' `detail'
    local rc = _rc
    capt confirm variable comsup, exact
    if _rc==0 drop comsup
    if "`comsupbak'"!="" {
        rename `comsupbak' comsup
    }
    if `rc' {
        exit `rc'
    }
    if `autojoin2'>0 {
        local join
        tempname C R
        qui tab `idvar' `treatvar' if `touse', matcell(`C') matrow(`R')
        local N1 0
        local N2 0
        local joinl
        local i 0
        while (`i'<r(r)) {
            local ++i
            local num: di `R'[`i',1]
            local joinl `joinl' `num'
            local N1 = `N1' + `C'[`i',1]
            local N2 = `N2' + `C'[`i',2]
            if (`N1'>= `autojoin2') & (`N2'>=`autojoin2') continue, break
        }
        if `:list sizeof joinl'>1 {
            local join `joinl'
        }
        local N1 0
        local N2 0
        local joinr
        local i = r(r)+1
        while (`i'>1) {
            local --i
            local num: di `R'[`i',1]
            local joinr `num' `joinr'
            local N1 = `N1' + `C'[`i',1]
            local N2 = `N2' + `C'[`i',2]
            if (`N1'>= `autojoin2') & (`N2'>=`autojoin2') continue, break
        }
        if `:list sizeof joinr'>1 {
            if "`join'"!="" {
                if "`:list join & joinr'"!="" {
                    di as err "autojoin(): results in single stratum"
                    exit 499
                }
                local join "`join', `joinr'"
            }
            else local join `joinr'
        }
    }
    if `"`join'"'!="" {
        di as txt "(merged strata: " _c
        gettoken nlist rest : join, parse(",")
        local comma ""
        while (`"`nlist'"'!="") {
            di as txt `"`comma'`nlist'"' _c
            local comma ", "
            gettoken first nlist : nlist
            foreach num of local nlist {
                qui replace `idvar' = `first' if `idvar'==`num' & `touse'
            }
            gettoken nlist rest : rest, parse(",") // get rid of the comma
            gettoken nlist rest : rest, parse(",")
        }
        tempvar idvar2
        qui egen `idvar2' = group(`idvar')  // renumber strata
        drop `idvar'
        rename `idvar2' `idvar'
        di as txt "; strata renumbered)"
    }
    qui replace `pscore' = `psvar' if `touse'
    qui replace `blockid' = `idvar' if `touse'
end

prog __hte_sm_compute, rclass
    syntax varlist(numeric) [if] [in] [fw iw pw] , ///
        blockid(varname) depvar(varname) ESTcom(str) [ ///
        NOIsily COMBine(str) ///
        CONtrols(str) ESTOPts(str) /// regress options
        ]
    hte_sm_ParseControls2 `controls' // returns locals allcontrols, commoncontrols, controls_#

    marksample touse
    markout `touse' `blockid'     /// restrict to common support
        `depvar' `allcontrols'    //  estimation sample
    gettoken treatvar indepvars : varlist

    qui levelsof `blockid' if `touse', local(blocks)
    tempname coefs
    local k 0
    foreach l of local blocks {
        local ++k
        local controls_`k' : list commoncontrols | controls_`k'
        qui `noisily' di _n as txt "Treatment Effect within Propensity Score Stratum " `l' ":"
        qui `noisily' `estcom' `depvar' `treatvar' `controls_`k'' ///
            if `touse' & `blockid'==`l' [`weight'`exp'], `estopts'
        capt di _b[`treatvar']   // check whether treatvar has been dropped from model
        local rc = _rc
        if `rc'==0 {
            if _b[`treatvar']==0 & _se[`treatvar']==0 local rc 111  // "omitted" coef
        }
        if `rc'==111 {
            di as err "error in model for stratum `l': treatvar dropped due to collinearity"
            exit `rc'
        }
        mat `coefs' = nullmat(`coefs') \ (`l', e(N), _b[`treatvar'], _se[`treatvar'])
        local blocklbls `blocklbls' `l'
    }

    tempname IDvar Nvar Bvar SEvar
    mat coln `coefs' = `IDvar' `Nvar' `Bvar' `SEvar'
    mat rown `coefs' = `blocklbls'
    svmat `coefs', names(col)
    qui `noisily' di _n as txt "Linear Fit of Treatment Effect on Propensity Score Rank:"
    *qui `noisily' regress `Bvar' `IDvar', depname("TreatEfct")
    if `k'>1 {
        qui `noisily' vwls `Bvar' `IDvar', sd(`SEvar')
        tempvar lfit
        qui predict `lfit' if e(sample), xb
    }
    else if `k'==1 {
        local singleton_b = _b[`treatvar']
        local singleton_se = _se[`treatvar']
        tempvar lfit
        qui gen `lfit' = `singleton_b' in 1
    }
    else { // should never happen
        di as err "somethings wrong; no propensity score strata were generated by -pscore-"
        exit 499
    }
    mkmat `lfit' in 1/`k'
    drop `lfit'
    mat `coefs' = `coefs', `lfit'
    mat coln `coefs' = id N b se lfit

    tempname b se obs block
    if `k'>1 {
        mat `b'   = `coefs'[1...,3]', _b[`IDvar'] , _b[_cons]
        mat `se'  = `coefs'[1...,4]', _se[`IDvar'] , _se[_cons]
    }
    else {
        mat `b'   = `coefs'[1...,3]', 0, `singleton_b'
        mat `se'  = `coefs'[1...,4]', 0, `singleton_se'
    }
    mat coln `b'  = `blocklbls' _slope _cons
    mat coln `se' = `blocklbls' _slope _cons
    mat `obs'     = `coefs'[1...,2]'
    mat `block'   = `coefs'[1...,1]'
    mat `lfit'    = `coefs'[1...,5]'
    foreach mat in b se obs block lfit {
        return mat `mat' = ``mat''
    }
end

/* MS ---------------------------------------------------------------------- */

program hte_ms
    version 9.2

    // syntax
    syntax anything(equalok id="varlist") [if] [in] /*[fw iw pw]*/ [, ///
        /*by(varname) SEParate*/ ///
        /*CASEwise LISTwise*/ /// psmatch2 enforces listwise!
        lowess LOWESS2(str asis)        ///
        lpoly LPOLY2(str asis)          /// Stata 10 required
        lpolyci LPOLYCI2(str asis)      /// Stata 10 required
        tt TTOPTs(str asis) tc TCOPTs(str asis) noSCatter ///
        /// psmatch2 options
        Pscore(passthru) Neighbor(passthru) TIES RADIUS CALiper(passthru)       ///
        KERNEL LLR Kerneltype(passthru) BWidth(passthru)                        ///
        COMmon TRIM(passthru) ODDS LOGIT INDEX NOREPLacement DESCending SPLINE  ///
        NKnots(passthru) QUIetly NOWARNings                                     ///
        ///
        name(passthru) overlay combine(str asis) * /// twoway options
        ]
    if "`listwise'"!=""     local casewise casewise
    if `"`lowess2'"'!=""    local lowess lowess
    if `"`lpoly2'"'!=""     local lpoly lpoly
    if `"`lpolyci2'"'!=""   local lpolyci lpolyci
    if "`lowess'`lpoly'`lpolyci'"=="" local lpoly lpoly
    if "`overlay'"!="" {
        if `"`ttopts'"'!="" {
            di as err "ttopts() not allowed with -overlay-"
            exit 198
        }
        if `"`tcopts'"'!="" {
            di as err "tcopts() not allowed with -overlay-"
            exit 198
        }
        if ("`lowess'"!="") + ("`lpoly'"!="") + ("`lpolyci'"!="") > 1 {
            di as err "only one of lowess, lpoly, and lpolyci allowed with -overlay-"
            exit 198
        }
        local scatter noscatter
        local smcmd "`lowess'`lpoly'`lpolyci'"
    }

    // psmatch2 installed?
    capt which psmatch2
    if _rc {
        di as err "-psmatch2- is required. To install, type: " ///
            `"{stata "ssc install psmatch2"}"'
        exit _rc
    }

    // parse varlist
    gettoken depvar varlist : anything, parse("=")  // Parse "y1 [ y2 y3 ... = ] x1 x2 ..."
    if `"`depvar'"'=="=" {
        di as err "depvar required"
        exit 198
    }
    else if `"`varlist'"'!="" {
        gettoken junk varlist : varlist, parse("=") // get rid of "="
    }
    else {
        gettoken depvar varlist : anything          // just one depvar
    }
    gettoken treatvar indepvars : varlist

    // mark sample
    marksample touse
    //if "`casewise'"!="" {
        markout `touse' `depvar'
    //}
*     if "`by'"!="" {
*         markout `touse' `by', strok
*     }
    tab `treatvar' if `touse', nofreq
    if r(N)==0 error 2000
    if r(r)!=2 {
        if r(r)>2 di as err "more than 2 groups found in treatvar, only 2 allowed"
        else      di as err "less than 2 groups found in treatvar, 2 required"
        exit 420
    }

    // matching
    di as txt "(running psmatch2 ...)"
    local ate ate
    if "`tt'"!="" & "`tc'"=="" local ate
    psmatch2 `treatvar' `indepvars' if `touse', outcome(`depvar') `ate'     ///
        `pscore' `neighbor' `ties' `radius' `caliper' `kernel'              ///
        `llr' `kerneltype' `bwidth' `common' `trim' `odds' `logit' `index'  ///
        `noreplacement' `descending' `spline' `nknots' `quietly' `nowarnings'

    // compute individual TEs
    foreach v of local depvar {
        tempvar _`v'
        qui generate `_`v'' = cond(_treated==0, _`v'-`v', `v'-_`v') if _support==1
    }

    // graph
    di as txt "(compiling HTE graph ...)"
    local p 0
    local tlabs
    if "`tc'`tt'"=="" {
        local tcond `""& 1""'
    }
    else {
        local tcond
        if "`tc'"!="" {
            local tcond `""& _treated==0" "'
        }
        if "`tt'"!="" {
            local tcond `"`tcond'"& _treated==1""'
        }
        if "`tc'"!="" & "`tt'"!="" {
            local tlabs `"" (untreated)" " (treated)""'
        }
    }
    local ndepv: list sizeof depvar
    if "`overlay'"!="" {
        local plots
        local legend
        _parse comma `smcmd'2opts `smcmd'2 : `smcmd'2
        foreach v of local depvar {
            local i 0
            foreach t of local tcond {
                if "`smcmd'"=="lpolyci" local p = `p' + 1 // skip CI in legend
                local tlab: word `++i' of `tlabs'
                local plots `macval(plots)' ///
                    (`smcmd' `_`v'' _pscore if `touse' & _support `t', ///
                        leg(lab(`++p' "`v'`tlab'")) ``smcmd'2opts')
                local legend `legend' `p'
            }
            if `"``smcmd'2'"'!="" {
                gettoken trash `smcmd'2 : `smcmd'2, parse(",")
                _parse comma `smcmd'2opts `smcmd'2 : `smcmd'2
            }
        }
        twoway `plots', ytitle("Treatment Effect") xtitle("Propensity Score") ///
            leg(order(`legend')) `name' `options'
    }
    else {
        if "`scatter'"=="" {
            if "`tc'`tt'"=="" {
                local showtc showtc
                local showtt showtt
            }
            else {
                if "`tc'"!="" local showtc showtc
                if "`tt'"!="" local showtt showtt
            }
        }
        if "`scatter'"=="" {
            if "`showtc'"!="" {
                local gr_tc (scatter \`_\`v'' _pscore ///
                    if _treated==0 & `touse' & _support, ///
                    leg(lab(`++p' "untreated")) msize(*.5)  `tcopts')
            }
            if "`showtt'"!="" {
                local gr_tt (scatter \`_\`v'' _pscore ///
                    if _treated==1 & `touse' & _support, ///
                    leg(lab(`++p' "treated")) msize(*.5) `ttopts')
            }
        }
        if "`lpolyci'"!="" {
            local gr_lpolyci
            local i 0
            foreach t of local tcond {
                local tlab: word `++i' of `tlabs'
                local gr_lpolyci `macval(gr_lpolyci)' ///
                    (lpolyci \`_\`v'' _pscore if `touse' & _support `t', ///
                        leg(lab(`++p' "lpoly CI`tlab'") lab(`++p' "lpoly smooth`tlab'")) ///
                        \`lpolyci2opts')
            }
        }
        if "`lpoly'"!="" {
            local gr_lpoly
            local i 0
            foreach t of local tcond {
                local tlab: word `++i' of `tlabs'
                local gr_lpoly `macval(gr_lpoly)' ///
                    (lpoly \`_\`v'' _pscore if `touse' & _support `t', ///
                        leg(lab(`++p' "lpoly smooth`tlab'")) \`lpoly2opts')
            }
        }
        if "`lowess'"!="" {
            local gr_lowess
            local i 0
            foreach t of local tcond {
                local tlab: word `++i' of `tlabs'
                local gr_lowess `macval(gr_lowess)' ///
                    (lowess \`_\`v'' _pscore if `touse' & _support `t', ///
                        leg(lab(`++p' "lowess smooth`tlab'")) \`lowess2opts')
            }
        }
        if `ndepv'>1 {
            local nodraw nodraw
            local plots
        }
        else local nodraw
        foreach smcmd in `lpolyci' `lpoly' `lowess' {
            _parse comma `smcmd'2opts `smcmd'2 : `smcmd'2
        }
        foreach v of local depvar {
            if `ndepv'>1 {
                tempname _gr_`v'
                local plots `plots' `_gr_`v''
                local iname name(`_gr_`v'')
            }
            else {
                local iname `name'
                local lpolyci2opt: copy local lpolyci2
                local lpoly2opt: copy local lpoly2
                local lowess2opt: copy local lowess2
            }
            twoway `gr_tc' `gr_tt' `gr_lpolyci' `gr_lpoly' `gr_lowess' ///
                , `nodraw' ytitle("Treatment Effect") xtitle("Propensity Score") ///
                title(`v') `iname' `options'
            foreach smcmd in `lpolyci' `lpoly' `lowess' {
                if `"``smcmd'2'"'!="" {
                    gettoken trash `smcmd'2 : `smcmd'2, parse(",")
                    _parse comma `smcmd'2opts `smcmd'2 : `smcmd'2
                }
            }
        }
        if `ndepv'>1 {
            graph combine `plots', `combine' `name'
        }
    }
end

/* SD ---------------------------------------------------------------------- */

// should make computation and graph separate processes:
//  - makes implementation of bootstrap possible
//  - possibility to redraw graph on replay() (would this interfere with bootstrap?)

program hte_sd
    version 10
    if replay() {
        if ("`e(cmd)'"!="hte sd") error 301
        exit
    }
    _hte_sd `0'
end

program _hte_sd, rclass

    // syntax
    syntax anything(equalok id="varlist") [if] [in] [fw aw] [, ///
        /// propensity score estimation
        logit                /// use logit instead of probit
        ESTOPTs(string asis) /// estimation options
        CASEwise LISTwise    /// restrict sample
        NOIsily              /// display output
        pscore(name)         /// save estimated pscore
        _pscore(varname)     /// provide pscore and skip estimation
        replace              /// allow replacing existing variables
        /// lpoly fit
        comsup              /// restrict to common support
        DEGree(integer 1)   ///
        AT(varname)         ///
        atmat(name)         ///
        NGrid(integer 50)   /// number of grid points
        Kernel(passthru)    ///
        BWidth(string)      /// bwidth(# [#] | varname)
        PWidth(string)      /// pwidth(# [#])
        Var(string)         /// var(# [#] | varname)
        /// treatment effect fit
        post                /// post retults in e()
        GENerate(namelist max=2)  /// store fit; generate(fit [at])
        SEgen(name)          /// store SE of fit
        CIgen(namelist min=2 max=2)  /// store CI of fit
        Level(cilevel)      ///
        CIOPTs(str asis)    /// options for CI
        noci                /// suppress CI
        overlay             /// put all smooths into one plot
        NOGRaph             /// suppress graph
        combine(str asis)   /// option for -graph combine-
        name(passthru)  *   /// twoway options
        ]
    if "`listwise'"!="" local casewise casewise
    local ci = cond("`ci'"=="", "ci", "")
    if `:list sizeof bwidth'>2 {
        di as error "invalid bwidth()"
        exit 198
    }
    if `:list sizeof pwidth'>2 {
        di as error "invalid pwidth()"
        exit 198
    }
    if `:list sizeof var'>2 {
        di as error "invalid var()"
        exit 198
    }
    if "`at'"!="" & "`atmat'"!="" {
        di as err "at() and atmat() not both allowed"
        exit 198
    }
    if "`atmat'"!="" {
        if colsof(`atmat')>1 & rowsof(`atmat')>1 {
            di as err "invalid atmat(): matrix must be a vector"
            exit 499
        }
    }

    // check newvars
    if "`generate'"!="" {
            gettoken genfit genat : generate
            gettoken genat : genat
            if "`at'"!="" & "`genat'"!="" {
                di as err "only one name allowed in generate() if at() is specified"
                exit 198
            }
    }
    if "`cigen'"!="" {
            gettoken cilb ciub : cigen
            gettoken ciub : ciub
    }
    if "`replace'"=="" {
        if "`pscore'"!="" confirm new var `pscore', exact
        if "`genfit'"!="" confirm new var `genfit', exact
        if "`genat'"!=""  confirm new var `genfat', exact
        if "`segen'"!=""  confirm new var `segen', exact
        if "`cilb'"!=""   confirm new var `cilb', exact
        if "`ciub'"!=""   confirm new var `ciub', exact
    }

    // parse varlist
    gettoken depvar varlist : anything, parse("=")  // Parse "y1 [ y2 y3 ... = ] x1 x2 ..."
    if `"`depvar'"'=="=" {
        di as err "depvar required"
        exit 198
    }
    else if `"`varlist'"'!="" {
        gettoken junk varlist : varlist, parse("=") // get rid of "="
    }
    else {
        gettoken depvar varlist : anything          // just one depvar
    }
    local varlist: list retok varlist
    confirm numeric variable `depvar' `varlist'
    if `:list sizeof varlist'<2 {
        if "`_pscore'"=="" {
            di as err "must specify at least one independent variable"
            exit 198
        }
    }
    else if "`_pscore'"!="" {
        di as err "independent variables not allowed if _pscore() is specified"
        exit 198
    }
    local ndepv: list sizeof depvar
    gettoken treatvar indepvars : varlist

    // mark sample
    marksample touse
    if "`casewise'"!="" {
        markout `touse' `depvar'
    }
    if "`_pscore'"!="" {
        markout `touse' `_pscore'
    }
    tempname R
    tab `treatvar' if `touse', nofreq matrow(`R')
    if r(N)==0 error 2000
    if r(r)!=2 {
        if r(r)>2 di as err "more than 2 groups found in treatvar, only 2 allowed"
        else      di as err "less than 2 groups found in treatvar, 2 required"
        exit 420
    }
    local grp0 = `R'[1,1]
    local grp1 = `R'[2,1]
    mat drop `R'

    // n() for lpoly
    qui count
    if r(N)<`ngrid' {
        local ngrid = r(N)
        di as txt "(ngrid() reset to `ngrid')"
    }

    // estimation of PS
    if "`_pscore'"=="" {
        local estcmd `logit'
        if "`estcmd'"=="" local estcmd probit
        local qui quietly
        if "`noisily'"!="" local qui
        `qui' `estcmd' `treatvar' `indepvars' if `touse' [`weight'`exp'], `estopts'
        tempname ps
        qui predict `ps' if e(sample), pr
        qui replace `touse' = 0 if e(sample)==0
    }
    else {
        capt assert inrange(`_pscore',0,1) if `touse'
        if _rc {
            di as err "invalid _pscore(): must be within [0,1]"
            exit 499
        }
        markout `touse' `_pscore'
        local ps `_pscore'
    }

    // comput group-specific lpoly fit
    if "`at'"!="" {
        local userat 1
        local atj `at'

    }
    else if "`atmat'"!="" {
        local userat 1
        tempname at atrnames tmp
        mat `tmp' = `atmat'
        if colsof(`tmp')>1 {
            mat `tmp' = `tmp''
        }
        matrix coln `tmp' = `at'
        svmat `tmp', names(col)
        local atmatrnames: rownames `tmp'
        qui gen str1 `atrnames' = ""
        forv i = 1/`=rowsof(`tmp')' {
            gettoken rname atmatrnames : atmatrnames
            qui replace `atrnames' = "`rname'" in `i'
        }
        mat drop `tmp'
        local atj `at'
    }
    else {
        local userat 0
        tempname at atj
        local grid_lb = 1 / (`ngrid'*2)
        local grid_ub = 1 - `grid_lb'
        qui range `at' `grid_lb' `grid_ub' `ngrid'
    }
    foreach opt in bwidth pwidth var {
        gettoken `opt'0 `opt'1 : `opt'
        gettoken `opt'1 : `opt'1
        if `"``opt'1'"'=="" local `opt'1 : copy local `opt'0
        if `"``opt'0'"'!="" local `opt'0 `opt'(``opt'0')
        if `"``opt'1'"'!="" local `opt'1 `opt'(``opt'1')
    }
    local j 0
    tempname g0fit g1fit
    if "`ci'"!="" {
        tempvar g0se g1se
        local g0seopt se(`g0se')
        local g1seopt se(`g1se')
        local z = invnormal(1 - (100-`level')/200)
    }
    foreach v of local depvar {
        local ++j
        if `userat'==0 {        // determine common support
            su `ps' if `touse' & `treatvar'==`grp0' & `v'<., meanonly
            local min = r(min)
            local max = r(max)
            su `ps' if `touse' & `treatvar'==`grp1' & `v'<., meanonly
            if "`comsup'"!="" {
                local min = max(`min', r(min))
                local max = min(`max', r(max))
            }
            else {
                local min = min(`min', r(min))
                local max = max(`max', r(max))
            }
            qui gen `atj' = `at' if `at'>=`min' & `at'<=`max'
        }
        forv i = 0/1 {
            lpoly `v' `ps' if `touse' & `treatvar'==`grp`i'' [`weight'`exp'], ///
                nograph degree(`degree') `kernel' `bwidth`i'' `pwidth`i'' `var`i'' ///
                generate(`g`i'fit') at(`atj') `ci' `g`i'seopt'
            local r_N`i'      = r(N)
            local r_bwidth`i' = r(bwidth)
            if `"`ci'"'!="" {
                local r_pwidth`i' = r(pwidth)
            }
        }
        local r_ngrid  = r(ngrid)
        local r_kernel `r(kernel)'
        local r_degree = r(degree)
        if (`userat'==0) & (`j'<`ndepv') drop `atj' // (keep last)
        tempvar fit`j'
        qui gen `fit`j'' = `g1fit' - `g0fit'
        drop `g0fit' `g1fit'
        if "`ci'"!="" {
            tempvar se`j' lb`j' ub`j'
            qui gen `se`j'' = sqrt(`g0se'^2 + `g1se'^2)
            qui gen `lb`j'' = `fit`j'' + `z' * `se`j''
            qui gen `ub`j'' = `fit`j'' - `z' * `se`j''
            drop `g0se' `g1se'
        }
    }

    // draw graph
    if "`nograph'"=="" {
        if "`overlay'"=="" {
            local legend
            local i 0
            if "`ci'"!="" {
                local ci_plot (rarea \`lb\`j'' \`ub\`j'' `at' ///
                    if \`fit\`j''<., psty(ci) `ciopts')
                local legend label(`++i' "`level'% CI")
            }
            local te_plot (line \`fit\`j'' `at' if \`fit\`j''<., ///
                legend(`legend' label(`++i' "lpoly fit")) ///
                \`ti' xti(Propensity Score) ///
                yti(Treatment Effect) `options')
            if `ndepv'>1 {
                local nodraw nodraw
                local plots
            }
            else local nodraw
            local j 0
            foreach v of local depvar {
                local ++j
                if `ndepv'>1 {
                    tempname _gr_`v'
                    local plots `plots' `_gr_`v''
                    local iname name(`_gr_`v'')
                    local ti: variable label `v'
                    if `"`ti'"'=="" local ti "`v'"
                    local ti title(`ti')
                }
                else {
                    local iname `name'
                }
                twoway `ci_plot' `te_plot', `nodraw' `iname'
            }
            if `ndepv'>1 {
                graph combine `plots', `combine' `name'
            }
        }
        else {
            tempvar touse2
            qui gen byte `touse2' = 0
            local j 0
            local lj = ("`ci'"!="") * 2 * `ndepv'
            local legend
            local leglab
            local vlist
            local lblist
            local ublist
            local cilplist
            foreach v of local depvar {
                local ++j
                qui replace `touse2' = 1 if `fit`j''<.
                local vlist `vlist' `fit`j''
                if "`ci'"!="" {
                    local lblist `lblist' `lb`j''
                    local ublist `ublist' `ub`j''
                    local cilplist `cilplist' dash
                }
                local ti: variable label `v'
                if `"`ti'"'=="" local ti "`v'"
                local leglab `leglab' lab(`++lj' `"`ti'"')
                local legend `legend' `lj'
            }
            local legend legend(`leglab' order(`legend'))
            if "`ci'"!="" {
                local ci_plot (line `lblist' `at' if `touse2', lpattern(`cilplist') `ciopts') ///
                    (line `ublist' `at' if `touse2', lpattern(`cilplist') `ciopts')
                local pcycleopt pcycle(`ndepv')
            }
            two `ci_plot' (line `vlist' `at' if `touse2', ///
                xti(Propensity Score) yti(Treatment Effect) ///
                `pcycleopt' `legend' `name' `options')
            drop `touse2'
        }
    }

    // returns (last depvar only)
    local lastdepvar: word `ndepv' of `depvar'
    if "`post'"!="" {
        if "`atmat'"!="" local mkmatopt "rownames(`atrnames')"
        else             local mkmatopt "obs"
        tempname B AT
        mkmat `fit`ndepv'' if `at'<. & `fit`ndepv''<., matrix(`B') `mkmatopt'
        mat `B' = `B''
        mat rown `B' = b
        mkmat `at' if `at'<. & `fit`ndepv''<., matrix(`AT') `mkmatopt'
        mat `AT' = `AT''
        mat rown `AT' = at
        if `"`ci'"'!="" {
            tempname SE LB UB
            mkmat `se`ndepv'' if `at'<. & `fit`ndepv''<., matrix(`SE') `mkmatopt'
            mat `SE' = `SE''
            mat rown `SE' = se
            mkmat `lb`ndepv'' if `at'<. & `fit`ndepv''<., matrix(`LB') `mkmatopt'
            mat `LB' = `LB''
            mat rown `LB' = lb
            mkmat `ub`ndepv'' if `at'<. & `fit`ndepv''<., matrix(`UB') `mkmatopt'
            mat `UB' = `UB''
            mat rown `UB' = ub
        }
        qui count if `touse'
        eret post `B', esample(`touse') depname(`lastdepvar') obs(`r(N)')
        myeret local cmd "hte sd"
        myeret matrix at = `AT'
        if `"`ci'"'!="" {
            myeret matrix se = `SE'
            myeret matrix lb = `LB'
            myeret matrix ub = `UB'
            myeret scalar cilevel = `level'
        }
        local rtype mye
    }
    else local rtype
    `rtype'ret local indepvars `indepvars'
    `rtype'ret local treatvar  `treatvar'
    `rtype'ret local depvar    `lastdepvar'
    forv i = 1(-1)0 {
        if `"`ci'"'!="" {
            `rtype'ret scalar pwidth`i' = `r_pwidth`i''
        }
        `rtype'ret scalar bwidth`i'    = `r_bwidth`i''
        `rtype'ret scalar N`i'         = `r_N`i''
        `rtype'ret local treatgrp`i'   `"`grp`i''"'
    }
    `rtype'ret scalar degree = `r_degree'
    `rtype'ret scalar ngrid  = `r_ngrid'
    `rtype'ret local kernel  `r_kernel'

    // return variables (last depvar only)
    if "`pscore'"!="" {
        Vreturn `ps' `pscore' `replace'
    }
    if "`genfit'"!="" {
        Vreturn `fit`ndepv'' `genfit' `replace'
    }
    if "`genat'"!="" {
        Vreturn `at' `genat' `replace'
    }
    if "`segen'"!="" {
        Vreturn `se`ndepv'' `segen' `replace'
    }
    if "`cilb'"!="" {
        Vreturn `lb`ndepv'' `cilb' `replace'
    }
    if "`ciub'"!="" {
        Vreturn `ub`ndepv'' `ciub' `replace'
    }
end

/* COMMON ------------------------------------------------------------------ */

program myeret, eclass
    eret `0'
end

program Vreturn
    args oldname newname replace
    if "`replace'"!="" {
        capt confirm var `newname', exact
        if !_rc drop `newname'
    }
    rename `oldname' `newname'
end