*! version 1.0.1  04dec2023  S. Derya Uysal, Tymon Sloczynski, and Jeffrey M. Wooldridge

** changes:
** tau_t,norm --> tau_u
** output: CBPS --> logit CB
** output: logit --> logit ML
** output: probit --> probit ML

capture program drop kappalate
program define kappalate, eclass
	version 17
	syntax anything [if] [in] [, zmodel(string) vce(string) std(string) which(string)]

	// parse
	_iv_parse `0'
	local cmd `cmd'
	local yvar `s(lhs)'
	local tvar `s(endog)'
	local xvarsips `s(exog)'
	local zvar `s(inst)'
	local zero `s(zero)'

	sreturn clear

	// mark sample
	marksample touse
	markout `touse' `yvar' `zvar' `tvar' `xvarsips', strok

	// assign default values to options that were not set
	if "`zmodel'"=="" {
		local zmodel cbps
	}

	if "`vce'"=="" {
		local vce robust
	}

	if "`std'"=="" {
		local std on
	}

	if "`which'"=="" {
		local which norm
	}

	// check some conditions on the inputs
	if "`zmodel'"!="cbps" & "`zmodel'"!="logit" & "`zmodel'"!="probit" {
		noi di as error "zmodel(`zmodel') not allowed"
		error 198
		exit
	}

	if "`std'"!="on" & "`std'"!="off" {
		noi di as error "std(`std') not allowed"
		error 198
		exit
	}

	if "`which'"!="norm" & "`which'"!="all" {
		noi di as error "which(`which') not allowed"
		error 198
		exit
	}

	qui tab `tvar' if `touse'
	if r(r)!=2 {
		noi di as error "treatment must be binary"
		error 450
		exit
	}

	qui sum `tvar' if `touse'
	if r(min)!=0 | r(max)!=1 {
		noi di as error "treatment must only take on values zero or one"
		error 450
		exit
	}

	qui tab `zvar' if `touse'
	if r(r)!=2 {
		noi di as error "instrument must be binary"
		error 450
		exit
	}

	qui sum `zvar' if `touse'
	if r(min)!=0 | r(max)!=1 {
		noi di as error "instrument must only take on values zero or one"
		error 450
		exit
	}

	// standardize the non-binary variables
	if "`std'"=="on" {
		foreach x in `xvarsips' {
			tempvar `x'_ips_st
			qui tab `x' if `touse'
			if r(r)!=2 {
				egen double ``x'_ips_st' = std(`x')
			}
			else {
				gen double ``x'_ips_st' = `x'
			}
		}

		local xvarsips_STD
		foreach item in `xvarsips' {
			local xvarsips_STD `xvarsips_STD' ``item'_ips_st'
		}

		local xvarsips "`xvarsips_STD'"
	}

	// declare tempvars and tempnames
	tempvar ips ipsxb1 ipsxb2 numhat kappaw kappa_0 kappa_1 num1hat num0hat y1hat y0hat d1hat d0hat
	tempname dmeanz1 dmeanz0 bips bips1 bips2 starting initial I nums kappa_1s kappa_0s kappas late_a late_a1 late_a0 num1hats num0hats late_a10 by1 by0 bd1 bd0 denom1s denom0s num1s num0s num_norms denom_norms late_norm

	// examine compliance with the instrument
	qui sum `tvar' if `zvar'==1 & `touse'==1
	scalar `dmeanz1' = r(mean)
	qui sum `tvar' if `zvar'==0 & `touse'==1
	scalar `dmeanz0' = r(mean)

	if `dmeanz0'==`dmeanz1' {
		noi di as error "zero denominator: LATE not defined"
		error 459
		exit
	}

	else if `dmeanz1'==1 & `dmeanz0'==0 {
		noi di as error "instrument identical to treatment"
		error 459
		exit
	}

	else if `dmeanz1'==0 & `dmeanz0'==1 {
		noi di as error "instrument identical to treatment"
		error 459
		exit
	}

	// main estimation procedure
	quietly {
		// estimation of the instrument propensity score
		if "`zmodel'"!="cbps" {
			`zmodel' `zvar' `xvarsips' if `touse'==1
			matrix `bips' = e(b)
			predict double `ips'
		}
		else {
			// determine starting values from logit
			logit `zvar' `xvarsips' if `touse'==1
			matrix `starting' = e(b)

			// moment function
			local eqips (eqips: (`zvar' - exp({zhat: `xvarsips' _cons})/(1+exp({zhat:})))/((exp({zhat:})/(1+exp({zhat:})))*(1/(1+exp({zhat:})))))
			local eqips_inst instruments(eqips: `xvarsips')
			matrix `initial' = `starting'
			local k = colsof(`initial')
			matrix `I' = I(`k')

			// GMM estimation
			capture gmm `eqips' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce')
			matrix `bips1' = e(b)
			predict double `ipsxb1'

			if e(converged)==1 {
				matrix `bips' = `bips1'
				gen double `ips' = logistic(`ipsxb1')
			}
			else {
				capture gmm `eqips' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce') technique(nr)
				matrix `bips2' = e(b)
				predict double `ipsxb2'

				if e(converged)==1 {
					matrix `bips' = `bips2'
					gen double `ips' = logistic(`ipsxb2')
				}
				else {
					matrix `bips' = `bips1'
					gen double `ips' = logistic(`ipsxb1')
					noi di as error "convergence not achieved for CBPS estimation"
				}
			}
		}

		// estimation of the LATE
		gen double `numhat' = ((`zvar')/`ips')*`yvar'-(((1-`zvar'))/(1-`ips'))*`yvar' if `touse'==1
		gen double `kappa_1' = ((`zvar')/`ips')*`tvar'-(((1-`zvar'))/(1-`ips'))*`tvar' if `touse'==1
		gen double `kappa_0' = (1-`tvar')*((1-`zvar')-(1-`ips'))/(`ips'*(1-`ips')) if `touse'==1
		gen double `kappaw' = 1-(`tvar'*(1-`zvar'))/(1-`ips')-((1-`tvar')*`zvar')/`ips' if `touse'==1
		gen double `num1hat' = `kappa_1'*`yvar' if `touse'==1
		gen double `num0hat' = `kappa_0'*`yvar' if `touse'==1

		sum `numhat' if `touse'==1
		matrix `nums' = r(mean)

		sum `kappa_1' if `touse'==1
		matrix `kappa_1s' = r(mean)

		sum `kappa_0' if `touse'==1
		matrix `kappa_0s' = r(mean)

		sum `kappaw' if `touse'==1
		matrix `kappas' = r(mean)

		sum `num1hat' if `touse'==1
		matrix `num1hats' = r(mean)

		sum `num0hat' if `touse'==1
		matrix `num0hats' = r(mean)

		matrix `late_a' = `nums'*inv(`kappas')
		matrix `late_a1' = `nums'*inv(`kappa_1s')
		matrix `late_a0' = `nums'*inv(`kappa_0s')
		matrix `late_a10' = `num1hats'*inv(`kappa_1s')-`num0hats'*inv(`kappa_0s')

		matrix list `late_a'
		matrix list `late_a1'
		matrix list `late_a0'
		matrix list `late_a10'

		regress `yvar' if `zvar'==1 & `touse'==1 [pw = 1/`ips']
		matrix `by1' = e(b)
		predict double `y1hat'

		regress `yvar' if `zvar'==0 & `touse'==1 [pw = 1/(1-`ips')]
		matrix `by0' = e(b)
		predict double `y0hat'

		if `dmeanz1'==1 {
			gen double `d1hat' = 1
		}
		else if `dmeanz1'==0 {
			gen double `d1hat' = 0
		}
		else {
			regress `tvar' if `zvar'==1 & `touse'==1 [pw = 1/`ips']
			matrix `bd1' = e(b)
			predict double `d1hat'
		}

		if `dmeanz0'==0 {
			gen double `d0hat' = 0
		}
		else if `dmeanz0'==1 {
			gen double `d0hat' = 1
		}
		else {
			regress `tvar' if `zvar'==0 & `touse'==1 [pw = 1/(1-`ips')]
			matrix `bd0' = e(b)
			predict double `d0hat'
		}

		sum `d1hat' if `touse'==1
		matrix `denom1s' = r(mean)

		sum `d0hat' if `touse'==1
		matrix `denom0s' = r(mean)

		sum `y1hat' if `touse'==1
		matrix `num1s' = r(mean)

		sum `y0hat' if `touse'==1
		matrix `num0s' = r(mean)

		matrix `num_norms' = `num1s'-`num0s'
		matrix `denom_norms' = `denom1s'-`denom0s'
		matrix `late_norm' = `num_norms'*inv(`denom_norms')
		matrix list `late_norm'

		// moment conditions
		if "`zmodel'"=="logit" {
			local eqips (eqips: `zvar' - exp({zhat: `xvarsips' _cons})/(1+exp({zhat:})))
			local eqips_inst instruments(eqips: `xvarsips')
		}
		else if "`zmodel'"=="probit" {
			local eqips (eqips: ((`zvar' - normal({zhat: `xvarsips' _cons}))/(normal({zhat:})*(1-normal({zhat:}))))*normalden({zhat:}))
			local eqips_inst instruments(eqips: `xvarsips')
		}
		else if "`zmodel'"=="cbps" {
			local eqips (eqips: (`zvar' - exp({zhat: `xvarsips' _cons})/(1+exp({zhat:})))/((exp({zhat:})/(1+exp({zhat:})))*(1/(1+exp({zhat:})))))
			local eqips_inst instruments(eqips: `xvarsips')
		}

		// other moments
		// psi_delta
		local eq_delta (eq_delta: ((`zvar'*`yvar')/(exp({zhat:})/(1+exp({zhat:})))-((1-`zvar')*`yvar')/(1-(exp({zhat:})/(1+exp({zhat:}))))-{deltap}))
		local eq_delta_inst instruments(eq_delta: )
		// psi_gamma
		local eq_gamma (eq_gamma: (1-((1-`zvar')*`tvar')/(1-(exp({zhat:})/(1+exp({zhat:}))))-(`zvar'*(1-`tvar'))/(exp({zhat:})/(1+exp({zhat:})))-{gammap}))
		local eq_gamma_inst instruments(eq_gamma: )
		// psi_gamma1
		local eq_gamma1 (eq_gamma1: ((`zvar'*`tvar')/(exp({zhat:})/(1+exp({zhat:})))-((1-`zvar')*`tvar')/(1-(exp({zhat:})/(1+exp({zhat:}))))-{gamma1}))
		local eq_gamma1_inst instruments(eq_gamma1: )
		// psi_gamma0
		local eq_gamma0 (eq_gamma0: ((`zvar'*(`tvar'-1))/(exp({zhat:})/(1+exp({zhat:})))-((1-`zvar')*(`tvar'-1))/(1-(exp({zhat:})/(1+exp({zhat:}))))-{gamma0}))
		local eq_gamma0_inst instruments(eq_gamma0: )
		// psi_delta1
		local eq_delta1 (eq_delta1: (`tvar'*((`zvar'-(exp({zhat:})/(1+exp({zhat:}))))/((exp({zhat:})/(1+exp({zhat:})))*(1-(exp({zhat:})/(1+exp({zhat:}))))))*`yvar'-{delta1}))
		local eq_delta1_inst instruments(eq_delta1: )
		// psi_delta0
		local eq_delta0 (eq_delta0: ((1-`tvar')*(((1-`zvar')-(1-(exp({zhat:})/(1+exp({zhat:})))))/((exp({zhat:})/(1+exp({zhat:})))*(1-(exp({zhat:})/(1+exp({zhat:}))))))*`yvar'-{delta0}))
		local eq_delta0_inst instruments(eq_delta0: )
		// psi_mu1
		local eq_mu1 (eq_mu1: ((`zvar'*(`yvar'-{mu1}))/(exp({zhat:})/(1+exp({zhat:})))))
		local eq_mu1_inst instruments(eq_mu1: )
		// psi_mu0
		local eq_mu0 (eq_mu0: (((1-`zvar')*(`yvar'-{mu0}))/(1-(exp({zhat:})/(1+exp({zhat:}))))))
		local eq_mu0_inst instruments(eq_mu0: )
		// psi_m1
		local eq_m1 (eq_m1: ((`zvar'*(`tvar'-{m1}))/(exp({zhat:})/(1+exp({zhat:})))))
		local eq_m1_inst instruments(eq_m1: )
		// psi_m0
		local eq_m0 (eq_m0: (((1-`zvar')*(`tvar'-{m0}))/(1-(exp({zhat:})/(1+exp({zhat:}))))))
		local eq_m0_inst instruments(eq_m0: )
		// psi_taua
		local eq_tau_a (eq_tau_a: ({tau_a} - {deltap}/{gammap}))
		// psi_taua1
		local eq_tau_a1 (eq_tau_a1: ({tau_a1} - {deltap}/{gamma1}))
		// psi_taua0
		local eq_tau_a0 (eq_tau_a0: ({tau_a0} - {deltap}/{gamma0}))
		// psi_taua10
		local eq_tau_a10 (eq_tau_a10: ({tau_a10} - ({delta1}/{gamma1}-{delta0}/{gamma0})))
		// psi_taunorm
		if `dmeanz0'!=0 & `dmeanz0'!=1 & `dmeanz1'!=0 & `dmeanz1'!=1 {
			local eq_tau_norm (eq_tau_norm: ({tau_norm} - (({mu1}-{mu0})/({m1}-{m0}))))
		}
		else if `dmeanz0'==0 & `dmeanz1'!=0 & `dmeanz1'!=1 {
			local eq_tau_norm (eq_tau_norm: ({tau_norm} - (({mu1}-{mu0})/({m1}))))
		}
		else if `dmeanz0'!=0 & `dmeanz0'!=1 & `dmeanz1'==1 {
			local eq_tau_norm (eq_tau_norm: ({tau_norm} - (({mu1}-{mu0})/(1-{m0}))))
		}
		else if `dmeanz0'==1 & `dmeanz1'!=0 & `dmeanz1'!=1 {
			local eq_tau_norm (eq_tau_norm: ({tau_norm} - (({mu1}-{mu0})/({m1}-1))))
		}
		else if `dmeanz0'!=0 & `dmeanz0'!=1 & `dmeanz1'==0 {
			local eq_tau_norm (eq_tau_norm: ({tau_norm} - (({mu1}-{mu0})/(-{m0}))))
		}

		// declare further tempnames
		tempname N tau_a vc_tau_a r_tau_a var_tau_a tau_a1 vc_tau_a1 r_tau_a1 var_tau_a1 tau_a0 vc_tau_a0 r_tau_a0 var_tau_a0
		tempname tau_norm vc_tau_norm r_tau_norm var_tau_norm tau_a10 vc_tau_a10 r_tau_a10 var_tau_a10

		// estimation of the LATE
		// tau_a
		matrix `initial' = (`bips', `nums', `kappas', `late_a')
		local k = colsof(`initial')
		matrix `I' = I(`k')
		gmm `eqips' `eq_delta' `eq_gamma' `eq_tau_a' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce') iterate(0)
		scalar `tau_a' = _b[tau_a:_cons]
		matrix `vc_tau_a' = e(V)
		scalar `r_tau_a' = rownumb(`vc_tau_a', "tau_a:_cons")
		scalar `var_tau_a' = `vc_tau_a'[`r_tau_a', `r_tau_a']

		// tau_a1
		matrix `initial' = (`bips', `nums', `kappa_1s', `late_a1')
		local k = colsof(`initial')
		matrix `I' = I(`k')
		gmm `eqips' `eq_delta' `eq_gamma1' `eq_tau_a1' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce') iterate(0)
		scalar `tau_a1' = _b[tau_a1:_cons]
		matrix `vc_tau_a1' = e(V)
		scalar `r_tau_a1' = rownumb(`vc_tau_a1', "tau_a1:_cons")
		scalar `var_tau_a1' = `vc_tau_a1'[`r_tau_a1', `r_tau_a1']

		// tau_a0
		matrix `initial' = (`bips', `nums', `kappa_0s', `late_a0')
		local k = colsof(`initial')
		matrix `I' = I(`k')
		gmm `eqips' `eq_delta' `eq_gamma0' `eq_tau_a0' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce') iterate(0)
		scalar `tau_a0' = _b[tau_a0:_cons]
		matrix `vc_tau_a0' = e(V)
		scalar `r_tau_a0' = rownumb(`vc_tau_a0', "tau_a0:_cons")
		scalar `var_tau_a0' = `vc_tau_a0'[`r_tau_a0', `r_tau_a0']

		// tau_a10
		matrix `initial' = (`bips', `num1hats', `kappa_1s', `num0hats', `kappa_0s', `late_a10')
		local k = colsof(`initial')
		matrix `I' = I(`k')
		gmm `eqips' `eq_delta1' `eq_gamma1' `eq_delta0' `eq_gamma0' `eq_tau_a10' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce') iterate(0)
		scalar `tau_a10' = _b[tau_a10:_cons]
		matrix `vc_tau_a10' = e(V)
		scalar `r_tau_a10' = rownumb(`vc_tau_a10', "tau_a10:_cons")
		scalar `var_tau_a10' = `vc_tau_a10'[`r_tau_a10', `r_tau_a10']

		// tau_norm
		if `dmeanz0'!=0 & `dmeanz0'!=1 & `dmeanz1'!=0 & `dmeanz1'!=1 {
			matrix `initial' = (`bips', `num1s', `num0s', `denom1s', `denom0s', `late_norm')
			local k = colsof(`initial')
			matrix `I' = I(`k')
			gmm `eqips' `eq_mu1' `eq_mu0' `eq_m1' `eq_m0' `eq_tau_norm' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce') iterate(0)
		}
		else if `dmeanz0'==0 | `dmeanz0'==1 {
			matrix `initial' = (`bips', `num1s', `num0s', `denom1s', `late_norm')
			local k = colsof(`initial')
			matrix `I' = I(`k')
			gmm `eqips' `eq_mu1' `eq_mu0' `eq_m1' `eq_tau_norm' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce') iterate(0)
		}
		else if `dmeanz1'==0 | `dmeanz1'==1 {
			matrix `initial' = (`bips', `num1s', `num0s', `denom0s', `late_norm')
			local k = colsof(`initial')
			matrix `I' = I(`k')
			gmm `eqips' `eq_mu1' `eq_mu0' `eq_m0' `eq_tau_norm' if `touse', `eqips_inst' onestep winitial(`I') from(`initial') quickderivatives vce(`vce') iterate(0)
		}

		scalar `tau_norm' = _b[tau_norm:_cons]
		matrix `vc_tau_norm' = e(V)
		scalar `r_tau_norm' = rownumb(`vc_tau_norm', "tau_norm:_cons")
		scalar `var_tau_norm' = `vc_tau_norm'[`r_tau_norm', `r_tau_norm']

		scalar `N' = e(N)
	}

	// display results
	if "`which'"=="all" {
		if "`zmodel'"!="cbps" {
			tempname b V
			matrix `b' = (`tau_a', `tau_a1', `tau_a0', `tau_a10', `tau_norm')
			matrix `V' = (`var_tau_a', 0, 0, 0, 0 \ 0, `var_tau_a1', 0, 0, 0 \ 0, 0,`var_tau_a0', 0, 0 \ 0, 0, 0, `var_tau_a10', 0 \ 0, 0, 0, 0, `var_tau_norm')
			matrix rownames `b' = " "
			matrix colnames `b' = "tau_a" "tau_a,1" "tau_a,0" "tau_a,10" "tau_u"
			matrix rownames `V' = "tau_a" "tau_a,1" "tau_a,0" "tau_a,10" "tau_u"
			matrix colnames `V' = "tau_a" "tau_a,1" "tau_a,0" "tau_a,10" "tau_u"
		}
		else if "`zmodel'"=="cbps" {
			tempname b V
			matrix `b' = (`tau_a', `tau_norm')
			matrix `V' = (`var_tau_a', 0 \ 0, `var_tau_norm')
			matrix rownames `b' = " "
			matrix colnames `b' = "tau_a" "tau_u"
			matrix rownames `V' = "tau_a" "tau_u"
			matrix colnames `V' = "tau_a" "tau_u"
		}
	}

	else if "`which'"=="norm" {
		if "`zmodel'"!="cbps" {
			tempname b V
			matrix `b' = (`tau_a10', `tau_norm')
			matrix `V' = (`var_tau_a10', 0 \ 0, `var_tau_norm')
			matrix rownames `b' = " "
			matrix colnames `b' = "tau_a,10" "tau_u"
			matrix rownames `V' = "tau_a,10" "tau_u"
			matrix colnames `V' = "tau_a,10" "tau_u"
		}
		else if "`zmodel'"=="cbps" {
			tempname b V
			matrix `b' = (`tau_norm')
			matrix `V' = (`var_tau_norm')
			matrix rownames `b' = " "
			matrix colnames `b' = "tau_u"
			matrix rownames `V' = "tau_u"
			matrix colnames `V' = "tau_u"
		}
	}

	di as text _newline "Weighting estimation of the LATE"
	di
	di as text "Outcome     :   "  "`yvar'"
	di as text "Treatment   :   "  "`tvar'"
	di as text "Instrument  :   "  "`zvar'"
	if "`zmodel'"=="logit" {
		di as text "IPS         :   logit ML"
	}
	else if "`zmodel'"=="probit" {
		di as text "IPS         :   probit ML"
	}
	else if "`zmodel'"=="cbps" {
		di as text "IPS         :   logit CB"
	}
	di
	di as text "Number of obs   =   " as result `N'
	di

	ereturn post `b' `V', esample(`touse')
	ereturn scalar N = `N'

	ereturn local title "LATE estimation"
	ereturn local depvar `yvar'
	ereturn local tvar `tvar'
	ereturn local zvar `zvar'
	ereturn local zmodel `zmodel'
	ereturn local vce `vce'
	ereturn local cmd "kappalate"
	ereturn local cmdline `"kappalate `0'"'

	ereturn display
end