////////////////////////////////////////////////////////////////////////////////
// STATA FOR        Chiang, H.D., Hsu, Y.-C. & Sasaki, Y. (2019): Robust Uniform
// Inference for Quantile Treatment Effects in Regression Discontinuity Designs.
// Journal of Econometrics 211 (2), 589-618.
//
// Use it when you consider a regression discontinuity design and you are
// interested in analyzing heterogeneous causal effects.
////////////////////////////////////////////////////////////////////////////////
program define rdqte, rclass
    version 14.2

    syntax varlist(numeric) [if] [in] [, c(real 0) fuzzy(varname numeric) cover(real 0.95) ql(real 0.25) qh(real 0.75) qn(real 3) bw(real -1)]
    marksample touse

    gettoken depvar indepvars : varlist
    _fv_check_depvar `depvar'
    fvexpand `indepvars'
    local cnames `r(varlist)'

    tempname b V N cb h q CBl CBu

	// Case of Sharp Design ////////////////////////////////////////////////////
	if "`fuzzy'" == "" {
		mata: estimate_sharpqte("`depvar'", "`cnames'", ///
							    `c', `ql', `qh', ///
						        `qn', `bw', "`touse'", ///
						        "`b'", "`V'", "`N'", ///
						        `cover', "`cb'", "`h'", ///
								"`q'", "`CBl'", "`CBu'")
   	} //END IF SHARP////////////////////////////////////////////////////////////

	// Case of Fuzzy Design ////////////////////////////////////////////////////
	if "`fuzzy'" != "" {
		tempvar treat
		gen `treat' = `fuzzy'
		mata: estimate_fuzzyqte("`depvar'", "`cnames'", "`treat'", ///
							    `c', `ql', `qh', ///
						        `qn', `bw', "`touse'", ///
						        "`b'", "`V'", "`N'", ///
						        `cover', "`cb'", "`h'", ///
								"`q'", "`CBl'", "`CBu'")
   	} //END IF FUZZY////////////////////////////////////////////////////////////

	matrix colnames `b' = QTE
	matrix colnames `V' = QTE
	matrix rownames `V' = QTE

	return scalar h    = `h'
	return scalar c    = `c'
    return scalar N    = `N'
	return matrix CBupper = `CBu'
	return matrix CBlower = `CBl'
	return matrix V    = `V'
	return matrix b    = `b'
	return matrix q    = `q'
    return local  cmd  "rdqte"
end




mata:
////////////////////////////////////////////////////////////////////////////////
// Kernel Function
void kernel(u, kout){
	kout = 0.75 :* ( 1 :- (u:^2) ) :* ( -1 :< u ) :* ( u :< 1 )
	// kout = (70/81) :* (1 :- (u:^2):^(3/2) ):^3 :* ( -1 :< u ) :* ( u :< 1 )
}
////////////////////////////////////////////////////////////////////////////////
// Estimation of QTE in the Sharp Design
void estimate_sharpqte( string scalar yv,      string scalar xv,
						real scalar cut, 	   real scalar q_low, 	   real scalar q_high,
						real scalar q_num, 	   real scalar b_w,	       string scalar touse,
						string scalar bname,   string scalar Vname,    string scalar nname,
						real scalar cover, 	   string scalar cbname,   string scalar hname,
						string scalar qname,   string scalar cblname,  string scalar cbuname)
{
	printf("\n{hline 78}\n")
	printf("Executing:    Chiang, H.D., Hsu, Y.-C. & Sasaki, Y. (2019): Robust Uniform    \n")
	printf("              Inference for Quantile Treatment Effects in Regression          \n")
	printf("              Discontinuity Designs. Journal of Econometrics 211 (2), 589-618.\n")
	printf("{hline 78}\n")
    real vector y, d, x, qlist
    real scalar n

    y      = st_data(., yv, touse)
    x      = st_data(., xv, touse) :- cut
	d      = x :> 0
    n      = rows(y)
	// List of quantiles at which to evaluate causal effects
	qlist = (q_high - q_low) :* (0..(q_num-1)) :/ (q_num-1) :+ q_low
	// Grid of y values at which the Wald ratio is ccomputed
	y_num = 5000
	ylist = (sort(y,1)[trunc(n*0.99)] - sort(y,1)[trunc(n*0.01)]) :* (0..(y_num-1)) :/ (y_num-1) :+ sort(y,1)[trunc(n*0.01)]
	// Bandwidth
	h = b_w
	if( b_w <= 0 ){
		h = variance(x)^0.5 / n^0.2
	}

	////////////////////////////////////////////////////////////////////////////
	// Estimate Wald Ratios
	real vector kout
	kernel(x :/ h, kout)
	wald_d0 = J(length(ylist),1,0)
	wald_d1 = J(length(ylist),1,0)
	mu1d0plus = J(length(ylist),1,0)
	mu1d0minus = J(length(ylist),1,0)
	mu1d1plus = J(length(ylist),1,0)
	mu1d1minus = J(length(ylist),1,0)
	reg = diag((0.05*n/h) :* (1 \ variance(x)^0.5 \ variance(x)))

	Xplus = kout:^0.5, x :* kout:^0.5, x:^2 :/ 2 :* kout:^0.5 :* (x :> 0)
	Xminus = kout:^0.5, x :* kout:^0.5, x:^2 :/ 2 :* kout:^0.5 :* (x :<= 0)

	dval = 0
	Dplus = (d :== dval) :* kout:^0.5 :* (x :> 0)
	Dminus = (d :== dval) :* kout:^0.5 :* (x :<= 0)
	mu2plus = luinv( Xplus' * Xplus :+ reg ) * Xplus' * Dplus
	mu2minus = luinv( Xminus' * Xminus :+ reg ) * Xminus' * Dminus
	mu2d0plus = mu2plus[1]
	mu2d0minus = mu2minus[1]
	for( idx = 1 ; idx <= y_num ; idx++ ){
		yval = ylist[idx]
		YDplus = (y :<= yval) :* (d :== dval) :* kout:^0.5 :* (x :> 0)
		YDminus = (y :<= yval) :* (d :== dval) :* kout:^0.5 :* (x :<= 0)
		mu1plus = luinv( Xplus' * Xplus :+ reg ) * Xplus' * YDplus
		mu1minus = luinv( Xminus' * Xminus :+ reg ) * Xminus' * YDminus
		mu1d0plus[idx] = mu1plus[1]
		mu1d0minus[idx] = mu1minus[1]
		wald_d0[idx] = (mu1plus[1] - mu1minus[1]) / (mu2plus[1] - mu2minus[1])
		wald_d0[idx] = (wald_d0[idx] < 0)*0 + (wald_d0[idx] >= 0)*wald_d0[idx]
		wald_d0[idx] = (wald_d0[idx] > 1)*1 + (wald_d0[idx] <= 1)*wald_d0[idx]
	}
	wald_d0 = sort(wald_d0,1)

	dval = 1
	Dplus = (d :== dval) :* kout:^0.5 :* (x :> 0)
	Dminus = (d :== dval) :* kout:^0.5 :* (x :<= 0)
	mu2plus = luinv( Xplus' * Xplus :+ reg ) * Xplus' * Dplus
	mu2minus = luinv( Xminus' * Xminus :+ reg ) * Xminus' * Dminus
	mu2d1plus = mu2plus[1]
	mu2d1minus = mu2minus[1]
	for( idx = 1 ; idx <= y_num ; idx++ ){
		yval = ylist[idx]
		YDplus = (y :<= yval) :* (d :== dval) :* kout:^0.5 :* (x :> 0)
		YDminus = (y :<= yval) :* (d :== dval) :* kout:^0.5 :* (x :<= 0)
		mu1plus = luinv( Xplus' * Xplus :+ reg ) * Xplus' * YDplus
		mu1minus = luinv( Xminus' * Xminus :+ reg ) * Xminus' * YDminus
		mu1d1plus[idx] = mu1plus[1]
		mu1d1minus[idx] = mu1minus[1]
		wald_d1[idx] = (mu1plus[1] - mu1minus[1]) / (mu2plus[1] - mu2minus[1])
		wald_d1[idx] = (wald_d1[idx] < 0)*0 + (wald_d1[idx] >= 0)*wald_d1[idx]
		wald_d1[idx] = (wald_d1[idx] > 1)*1 + (wald_d1[idx] <= 1)*wald_d1[idx]
	}
	wald_d1 = sort(wald_d1,1)

	////////////////////////////////////////////////////////////////////////////
	// Estimate QTE
	QY0list = qlist
	QY1list = qlist
	QTElist = qlist

	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		q = qlist[idx]
		QY0 = min( select( ylist', wald_d0 :>= q) )
		QY1 = min( select( ylist', wald_d1 :>= q) )
		QY0list[idx] = QY0
		QY1list[idx] = QY1
		QTElist[idx] = QY1 - QY0
	}
	//QTElist

	////////////////////////////////////////////////////////////////////////////
	// Estimate Densities in Preparation for Variance Estimation
	hay = variance(y)^0.5 / n^(1/6)
	ha = variance(x)^0.5 / n^(1/6)
	hb = variance(x)^0.5 / n^0.2
	hc = variance(x)^0.5 / n^0.2
	real vector kyout, kouta, koutb
	kernel(x :/ ha, kouta)
	kernel(x :/ hb, koutb)
	kernel(x :/ hc, koutc)
	fx = mean( koutb :/ hb )
	fy0 = J(1,length(qlist),0)
	fy1 = J(1,length(qlist),0)


	dval = 0
	pd_plus = sum( koutc :* (d :== dval) :* (x :> 0) ) / sum( koutc :* (x :> 0) )
	pd_minus = sum( koutc :* (d :== dval) :* (x :<= 0) ) / sum( koutc :* (x :<= 0) )

	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		yval = QY0list[idx]
		kernel((y :- yval) :/ hay, kyout)
		fyxd_plus = sum( kouta :* kyout :* (d :== dval) :* (x :> 0) :/ (n*ha*hay) ) / sum( kouta :* (d :== dval) :* (x :> 0) :/ (n*ha) :+ 0.000000001)
		fyxd_minus = sum( kouta :* kyout :* (d :== dval) :* (x :<= 0) :/ (n*ha*hay) ) / sum( kouta :* (d :== dval) :* (x :<= 0) :/ (n*ha) :+ 0.000000001 )

		fy0[idx] = ( fyxd_plus * pd_plus - fyxd_minus * pd_minus ) / (mu2d0plus - mu2d0minus)
	}

	dval = 1
	pd_plus = sum( koutc :* (d :== dval) :* (x :> 0) ) / sum( koutc :* (x :> 0) )
	pd_minus = sum( koutc :* (d :== dval) :* (x :<= 0) ) / sum( koutc :* (x :<= 0) )

	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		yval = QY1list[idx]
		kernel((y :- yval) :/ hay, kyout)
		fyxd_plus = mean( kouta :* kyout :* (d :== dval) :* (x :> 0) :/ (n*ha*hay) ) / mean( kouta :* (d :== dval) :* (x :> 0) :/ (n*ha) :+ 0.000000001)
		fyxd_minus = mean( kouta :* kyout :* (d :== dval) :* (x :<= 0) :/ (n*ha*hay) ) / mean( kouta :* (d :== dval) :* (x :<= 0) :/ (n*ha) :+ 0.000000001 )

		fy1[idx] = ( fyxd_plus * pd_plus - fyxd_minus * pd_minus ) / (mu2d1plus - mu2d1minus)
	}

	////////////////////////////////////////////////////////////////////////////
	// Prepare Other Auxiliary Objects for Variance Estimation
	real scalar ku
	Gammaplus = J(3,3,0)
	ulist = (0..100) :/ 100
	for( idx = 1 ; idx <= length(ulist) ; idx++ ){
		u = ulist[idx]
		kernel(u,ku)
		Gammaplus = Gammaplus + ( (1 \ u \ u^2) * (ku) * (1, u, u^2) ) :* (ulist[2]-ulist[1])
	}

	Gammaminus = J(3,3,0)
	ulist = (-100..0) :/ 100
	for( idx = 1 ; idx <= length(ulist) ; idx++ ){
		u = ulist[idx]
		kernel(u,ku)
		Gammaminus = Gammaminus + ( (1 \ u \ u^2) * (ku) * (1, u, u^2) ) :* (ulist[2]-ulist[1])
	}

	////////////////////////////////////////////////////////////////////////////
	// Influence Functions
	IF1d0plus = J(n,length(qlist),0)
	IF1d0minus = J(n,length(qlist),0)
	IF1d1plus = J(n,length(qlist),0)
	IF1d1minus = J(n,length(qlist),0)
	IF2d0plus = J(n,1,0)
	IF2d0minus = J(n,1,0)
	IF2d1plus = J(n,1,0)
	IF2d1minus = J(n,1,0)

	dval = 0
	IF2d0plus = kout :* ( (d :== dval) :- mu2d0plus ) :* (x :> 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaplus))[1,.]'
	IF2d0plus = IF2d0plus :/ (fx * (n*h)^0.5)
	IF2d0minus = kout :* ( (d :== dval) :- mu2d0minus ) :* (x :<= 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaminus))[1,.]'
	IF2d0minus = IF2d0minus :/ (fx * (n*h)^0.5)
	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		yval = QY0list[idx]
		ylistidx = length( select(ylist, ylist :<= yval) )
		IF1d0plus[.,idx] = kout :* ( (y :<= yval) :* (d :== dval) :- mu1d0plus[ylistidx] ) :* (x :> 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaplus))[1,.]'
		IF1d0plus[.,idx] = IF1d0plus[.,idx] :/ (fx * (n*h)^0.5)
		IF1d0minus[.,idx] = kout :* ( (y :<= yval) :* (d :== dval) :- mu1d0minus[ylistidx] ) :* (x :<= 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaminus))[1,.]'
		IF1d0minus[.,idx] = IF1d0minus[.,idx] :/ (fx * (n*h)^0.5)
	}

	dval = 1
	IF2d1plus = kout :* ( (d :== dval) :- mu2d1plus ) :* (x :> 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaplus))[1,.]'
	IF2d1plus = IF2d1plus :/ (fx * (n*h)^0.5)
	IF2d1minus = kout :* ( (d :== dval) :- mu2d1minus ) :* (x :<= 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaminus))[1,.]'
	IF2d1minus = IF2d1minus :/ (fx * (n*h)^0.5)
	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		yval = QY1list[idx]
		ylistidx = length( select(ylist, ylist :<= yval) )
		IF1d1plus[.,idx] = kout :* ( (y :<= yval) :* (d :== dval) :- mu1d1plus[ylistidx] ) :* (x :> 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaplus))[1,.]'
		IF1d1plus[.,idx] = IF1d1plus[.,idx] :/ (fx * (n*h)^0.5)
		IF1d1minus[.,idx] = kout :* ( (y :<= yval) :* (d :== dval) :- mu1d1minus[ylistidx] ) :* (x :<= 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaminus))[1,.]'
		IF1d1minus[.,idx] = IF1d1minus[.,idx] :/ (fx * (n*h)^0.5)
	}

	////////////////////////////////////////////////////////////////////////////
	// Multiplier Bootstrap
	num_bootstrap = 2500
	Glist = J(length(qlist),num_bootstrap,0)
	maxGlist = J(1,num_bootstrap,0)
	heteroGlist = J(1,num_bootstrap,0)

	for( jdx = 1 ; jdx <= num_bootstrap ; jdx++ ){
		xi = invnormal(uniform(n,1))

		for( idx = 1 ; idx <= length(qlist) ; idx++ ){
			yval = QY1list[idx]
			ylistidx = length( select(ylist, ylist :<= yval) )
			G =
			( sum( xi :* IF1d1plus[.,idx] ) - sum( xi :* IF1d1minus[.,idx] ) ) * (mu2d1plus - mu2d1minus) / fy1[idx] / (mu2d1plus - mu2d1minus)^2 -
			( sum( xi :* IF2d1plus ) - sum( xi :* IF2d1minus ) ) * ( mu1d1plus[ylistidx] - mu1d1minus[ylistidx] ) / fy1[idx] / (mu2d1plus - mu2d1minus)^2 -
			( sum( xi :* IF1d0plus[.,idx] ) - sum( xi :* IF1d0minus[.,idx] ) ) * (mu2d0plus - mu2d0minus) / fy0[idx] / (mu2d0plus - mu2d0minus)^2 +
			( sum( xi :* IF2d0plus ) - sum( xi :* IF2d0minus ) ) * ( mu1d0plus[ylistidx] - mu1d0minus[ylistidx] ) / fy0[idx] / (mu2d0plus - mu2d0minus)^2
			Glist[idx,jdx] = G
		}
		maxGlist[jdx] = max(abs(Glist[.,jdx]))
		heteroGlist[jdx] = max(abs(Glist[.,jdx] :- mean( Glist[.,jdx] )))
	}

	// 99.5%-Winsorize Glist
	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		Glist995 = sort( abs(Glist[idx,.]), 1 )[trunc(0.995*num_bootstrap)]
		temp = Glist[idx,.]
		temp = -abs(Glist995):*( Glist[idx,.] :< -abs(Glist995) ) + temp:*( Glist[idx,.] :<= -abs(Glist995) )
		temp = abs(Glist995):*( Glist[idx,.] :> abs(Glist995) ) + temp:*( Glist[idx,.] :<= abs(Glist995) )
		Glist[idx,.] = temp
	}

	////////////////////////////////////////////////////////////////////////////
	// Uniform Confidence Band and Hypothesis Testing
	half_band_length = sort(maxGlist,1)[trunc(cover*num_bootstrap)] / (n*h)^0.5
	bl = QTElist :- half_band_length
	bu = QTElist :+ half_band_length
	pval_nullity = mean(( (n*h)^0.5 :* max(abs(QTElist)) :<= maxGlist )')
	pval_homogeneity = mean(( (n*h)^0.5 :* max(abs(QTElist :- mean(QTElist'))) :<= heteroGlist )')

	////////////////////////////////////////////////////////////////////////////
	// Estimation Results
	b = QTElist
	V = V = Glist * Glist' :/ num_bootstrap - (Glist :/ num_bootstrap) * (Glist :/ num_bootstrap)'
	V = V :/ (n*h)

    st_matrix(bname, b)
    st_matrix(Vname, V)
	st_matrix(qname, qlist)
	st_matrix(cblname, bl)
	st_matrix(cbuname, bu)
    st_numscalar(nname, n)
	st_numscalar(hname, h)

	////////////////////////////////////////////////////////////////////////////
	// Console output
	printf("\n{hline 78}\n")
	printf(  "Sharp Regression Discontinuity Design\n")
	printf(  "Number of observations:                                        n = %f\n", n)
	printf(  "The discontinuity location of the running variable:            c = %f\n", cut)
	printf(  "{hline 78}\n")
	printf(  "Quantile          QTE     [%2.0f%% Unif. Conf. Band]\n",100*cover)
	printf(  "{hline 48}\n")
	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
	    printf("   %5.3f   %10.5f   %10.5f   %10.5f\n",qlist[idx],b[idx],bl[idx],bu[idx])
	}
	printf(  "{hline 78}\n")
	printf(  "Test of the hypothesis that QTE=0 for all quantiles:           p-value = %4.3f\n", pval_nullity)
	printf(  "Test of the hypothesis that QTE is constant across quantiles:  p-value = %4.3f\n", pval_homogeneity)
	printf(  "{hline 78}\n")
}
////////////////////////////////////////////////////////////////////////////////
// Estimation of QTE in the Fuzzy Design
void estimate_fuzzyqte( string scalar yv,      string scalar xv,	   string scalar dv,
						real scalar cut, 	   real scalar q_low, 	   real scalar q_high,
						real scalar q_num, 	   real scalar b_w,	       string scalar touse,
						string scalar bname,   string scalar Vname,    string scalar nname,
						real scalar cover,	   string scalar cbname,   string scalar hname,
						string scalar qname,   string scalar cblname,  string scalar cbuname)
{
	printf("\n{hline 78}\n")
	printf("Executing:    Chiang, H.D., Hsu, Y.-C. & Sasaki, Y. (2019): Robust Uniform    \n")
	printf("              Inference for Quantile Treatment Effects in Regression          \n")
	printf("              Discontinuity Designs. Journal of Econometrics 211 (2), 589-618.\n")
	printf("{hline 78}\n")
    real vector y, d, x, qlist
    real scalar n

    y      = st_data(., yv, touse)
	d      = st_data(., dv, touse)
    x      = st_data(., xv, touse) :- cut
    n      = rows(y)
	// List of quantiles at which to evaluate causal effects
	qlist = (q_high - q_low) :* (0..(q_num-1)) :/ (q_num-1) :+ q_low
	// Grid of y values at which the Wald ratio is ccomputed
	y_num = 5000
	ylist = (sort(y,1)[trunc(n*0.99)] - sort(y,1)[trunc(n*0.01)]) :* (0..(y_num-1)) :/ (y_num-1) :+ sort(y,1)[trunc(n*0.01)]
	// Bandwidth
	h = b_w
	if( b_w <= 0 ){
		h = variance(x)^0.5 / n^0.2
	}

	////////////////////////////////////////////////////////////////////////////
	// Estimate Wald Ratios
	real vector kout
	kernel(x :/ h, kout)
	wald_d0 = J(length(ylist),1,0)
	wald_d1 = J(length(ylist),1,0)
	mu1d0plus = J(length(ylist),1,0)
	mu1d0minus = J(length(ylist),1,0)
	mu1d1plus = J(length(ylist),1,0)
	mu1d1minus = J(length(ylist),1,0)
	reg = diag((0.05*n/h) :* (1 \ variance(x)^0.5 \ variance(x)))

	Xplus = kout:^0.5, x :* kout:^0.5, x:^2 :/ 2 :* kout:^0.5 :* (x :> 0)
	Xminus = kout:^0.5, x :* kout:^0.5, x:^2 :/ 2 :* kout:^0.5 :* (x :<= 0)

	dval = 0
	Dplus = (d :== dval) :* kout:^0.5 :* (x :> 0)
	Dminus = (d :== dval) :* kout:^0.5 :* (x :<= 0)
	mu2plus = luinv( Xplus' * Xplus :+ reg ) * Xplus' * Dplus
	mu2minus = luinv( Xminus' * Xminus :+ reg ) * Xminus' * Dminus
	mu2d0plus = mu2plus[1]
	mu2d0minus = mu2minus[1]
	for( idx = 1 ; idx <= y_num ; idx++ ){
		yval = ylist[idx]
		YDplus = (y :<= yval) :* (d :== dval) :* kout:^0.5 :* (x :> 0)
		YDminus = (y :<= yval) :* (d :== dval) :* kout:^0.5 :* (x :<= 0)
		mu1plus = luinv( Xplus' * Xplus :+ reg ) * Xplus' * YDplus
		mu1minus = luinv( Xminus' * Xminus :+ reg ) * Xminus' * YDminus
		mu1d0plus[idx] = mu1plus[1]
		mu1d0minus[idx] = mu1minus[1]
		wald_d0[idx] = (mu1plus[1] - mu1minus[1]) / (mu2plus[1] - mu2minus[1])
		wald_d0[idx] = (wald_d0[idx] < 0)*0 + (wald_d0[idx] >= 0)*wald_d0[idx]
		wald_d0[idx] = (wald_d0[idx] > 1)*1 + (wald_d0[idx] <= 1)*wald_d0[idx]
	}
	wald_d0 = sort(wald_d0,1)

	dval = 1
	Dplus = (d :== dval) :* kout:^0.5 :* (x :> 0)
	Dminus = (d :== dval) :* kout:^0.5 :* (x :<= 0)
	mu2plus = luinv( Xplus' * Xplus :+ reg ) * Xplus' * Dplus
	mu2minus = luinv( Xminus' * Xminus :+ reg ) * Xminus' * Dminus
	mu2d1plus = mu2plus[1]
	mu2d1minus = mu2minus[1]
	for( idx = 1 ; idx <= y_num ; idx++ ){
		yval = ylist[idx]
		YDplus = (y :<= yval) :* (d :== dval) :* kout:^0.5 :* (x :> 0)
		YDminus = (y :<= yval) :* (d :== dval) :* kout:^0.5 :* (x :<= 0)
		mu1plus = luinv( Xplus' * Xplus :+ reg ) * Xplus' * YDplus
		mu1minus = luinv( Xminus' * Xminus :+ reg ) * Xminus' * YDminus
		mu1d1plus[idx] = mu1plus[1]
		mu1d1minus[idx] = mu1minus[1]
		wald_d1[idx] = (mu1plus[1] - mu1minus[1]) / (mu2plus[1] - mu2minus[1])
		wald_d1[idx] = (wald_d1[idx] < 0)*0 + (wald_d1[idx] >= 0)*wald_d1[idx]
		wald_d1[idx] = (wald_d1[idx] > 1)*1 + (wald_d1[idx] <= 1)*wald_d1[idx]
	}
	wald_d1 = sort(wald_d1,1)

	////////////////////////////////////////////////////////////////////////////
	// Estimate QTE
	QY0list = qlist
	QY1list = qlist
	QTElist = qlist

	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		q = qlist[idx]
		QY0 = min( select( ylist', wald_d0 :>= q) )
		QY1 = min( select( ylist', wald_d1 :>= q) )
		QY0list[idx] = QY0
		QY1list[idx] = QY1
		QTElist[idx] = QY1 - QY0
	}
	//QTElist

	////////////////////////////////////////////////////////////////////////////
	// Estimate Densities in Preparation for Variance Estimation
	hay = variance(y)^0.5 / n^(1/6)
	ha = variance(x)^0.5 / n^(1/6)
	hb = variance(x)^0.5 / n^0.2
	hc = variance(x)^0.5 / n^0.2
	real vector kyout, kouta, koutb
	kernel(x :/ ha, kouta)
	kernel(x :/ hb, koutb)
	kernel(x :/ hc, koutc)
	fx = mean( koutb :/ hb )
	fy0 = J(1,length(qlist),0)
	fy1 = J(1,length(qlist),0)


	dval = 0
	pd_plus = sum( koutc :* (d :== dval) :* (x :> 0) ) / sum( koutc :* (x :> 0) )
	pd_minus = sum( koutc :* (d :== dval) :* (x :<= 0) ) / sum( koutc :* (x :<= 0) )

	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		yval = QY0list[idx]
		kernel((y :- yval) :/ hay, kyout)
		fyxd_plus = sum( kouta :* kyout :* (d :== dval) :* (x :> 0) :/ (n*ha*hay) ) / sum( kouta :* (d :== dval) :* (x :> 0) :/ (n*ha) :+ 0.000000001)
		fyxd_minus = sum( kouta :* kyout :* (d :== dval) :* (x :<= 0) :/ (n*ha*hay) ) / sum( kouta :* (d :== dval) :* (x :<= 0) :/ (n*ha) :+ 0.000000001 )

		fy0[idx] = ( fyxd_plus * pd_plus - fyxd_minus * pd_minus ) / (mu2d0plus - mu2d0minus)
	}

	dval = 1
	pd_plus = sum( koutc :* (d :== dval) :* (x :> 0) ) / sum( koutc :* (x :> 0) )
	pd_minus = sum( koutc :* (d :== dval) :* (x :<= 0) ) / sum( koutc :* (x :<= 0) )

	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		yval = QY1list[idx]
		kernel((y :- yval) :/ hay, kyout)
		fyxd_plus = mean( kouta :* kyout :* (d :== dval) :* (x :> 0) :/ (n*ha*hay) ) / mean( kouta :* (d :== dval) :* (x :> 0) :/ (n*ha) :+ 0.000000001)
		fyxd_minus = mean( kouta :* kyout :* (d :== dval) :* (x :<= 0) :/ (n*ha*hay) ) / mean( kouta :* (d :== dval) :* (x :<= 0) :/ (n*ha) :+ 0.000000001 )

		fy1[idx] = ( fyxd_plus * pd_plus - fyxd_minus * pd_minus ) / (mu2d1plus - mu2d1minus)
	}

	////////////////////////////////////////////////////////////////////////////
	// Prepare Other Auxiliary Objects for Variance Estimation
	real scalar ku
	Gammaplus = J(3,3,0)
	ulist = (0..100) :/ 100
	for( idx = 1 ; idx <= length(ulist) ; idx++ ){
		u = ulist[idx]
		kernel(u,ku)
		Gammaplus = Gammaplus + ( (1 \ u \ u^2) * (ku) * (1, u, u^2) ) :* (ulist[2]-ulist[1])
	}

	Gammaminus = J(3,3,0)
	ulist = (-100..0) :/ 100
	for( idx = 1 ; idx <= length(ulist) ; idx++ ){
		u = ulist[idx]
		kernel(u,ku)
		Gammaminus = Gammaminus + ( (1 \ u \ u^2) * (ku) * (1, u, u^2) ) :* (ulist[2]-ulist[1])
	}

	////////////////////////////////////////////////////////////////////////////
	// Influence Functions
	IF1d0plus = J(n,length(qlist),0)
	IF1d0minus = J(n,length(qlist),0)
	IF1d1plus = J(n,length(qlist),0)
	IF1d1minus = J(n,length(qlist),0)
	IF2d0plus = J(n,1,0)
	IF2d0minus = J(n,1,0)
	IF2d1plus = J(n,1,0)
	IF2d1minus = J(n,1,0)

	dval = 0
	IF2d0plus = kout :* ( (d :== dval) :- mu2d0plus ) :* (x :> 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaplus))[1,.]'
	IF2d0plus = IF2d0plus :/ (fx * (n*h)^0.5)
	IF2d0minus = kout :* ( (d :== dval) :- mu2d0minus ) :* (x :<= 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaminus))[1,.]'
	IF2d0minus = IF2d0minus :/ (fx * (n*h)^0.5)
	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		yval = QY0list[idx]
		ylistidx = length( select(ylist, ylist :<= yval) )
		IF1d0plus[.,idx] = kout :* ( (y :<= yval) :* (d :== dval) :- mu1d0plus[ylistidx] ) :* (x :> 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaplus))[1,.]'
		IF1d0plus[.,idx] = IF1d0plus[.,idx] :/ (fx * (n*h)^0.5)
		IF1d0minus[.,idx] = kout :* ( (y :<= yval) :* (d :== dval) :- mu1d0minus[ylistidx] ) :* (x :<= 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaminus))[1,.]'
		IF1d0minus[.,idx] = IF1d0minus[.,idx] :/ (fx * (n*h)^0.5)
	}

	dval = 1
	IF2d1plus = kout :* ( (d :== dval) :- mu2d1plus ) :* (x :> 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaplus))[1,.]'
	IF2d1plus = IF2d1plus :/ (fx * (n*h)^0.5)
	IF2d1minus = kout :* ( (d :== dval) :- mu2d1minus ) :* (x :<= 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaminus))[1,.]'
	IF2d1minus = IF2d1minus :/ (fx * (n*h)^0.5)
	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		yval = QY1list[idx]
		ylistidx = length( select(ylist, ylist :<= yval) )
		IF1d1plus[.,idx] = kout :* ( (y :<= yval) :* (d :== dval) :- mu1d1plus[ylistidx] ) :* (x :> 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaplus))[1,.]'
		IF1d1plus[.,idx] = IF1d1plus[.,idx] :/ (fx * (n*h)^0.5)
		IF1d1minus[.,idx] = kout :* ( (y :<= yval) :* (d :== dval) :- mu1d1minus[ylistidx] ) :* (x :<= 0) :* (J(n,1,1), x:/h, (x:/h):^2) * (luinv(Gammaminus))[1,.]'
		IF1d1minus[.,idx] = IF1d1minus[.,idx] :/ (fx * (n*h)^0.5)
	}

	////////////////////////////////////////////////////////////////////////////
	// Multiplier Bootstrap
	num_bootstrap = 2500
	Glist = J(length(qlist),num_bootstrap,0)
	maxGlist = J(1,num_bootstrap,0)
	heteroGlist = J(1,num_bootstrap,0)

	for( jdx = 1 ; jdx <= num_bootstrap ; jdx++ ){
		xi = invnormal(uniform(n,1))

		for( idx = 1 ; idx <= length(qlist) ; idx++ ){
			yval = QY1list[idx]
			ylistidx = length( select(ylist, ylist :<= yval) )
			G =
			( sum( xi :* IF1d1plus[.,idx] ) - sum( xi :* IF1d1minus[.,idx] ) ) * (mu2d1plus - mu2d1minus) / fy1[idx] / (mu2d1plus - mu2d1minus)^2 -
			( sum( xi :* IF2d1plus ) - sum( xi :* IF2d1minus ) ) * ( mu1d1plus[ylistidx] - mu1d1minus[ylistidx] ) / fy1[idx] / (mu2d1plus - mu2d1minus)^2 -
			( sum( xi :* IF1d0plus[.,idx] ) - sum( xi :* IF1d0minus[.,idx] ) ) * (mu2d0plus - mu2d0minus) / fy0[idx] / (mu2d0plus - mu2d0minus)^2 +
			( sum( xi :* IF2d0plus ) - sum( xi :* IF2d0minus ) ) * ( mu1d0plus[ylistidx] - mu1d0minus[ylistidx] ) / fy0[idx] / (mu2d0plus - mu2d0minus)^2
			Glist[idx,jdx] = G
		}
		maxGlist[jdx] = max(abs(Glist[.,jdx]))
		heteroGlist[jdx] = max(abs(Glist[.,jdx] :- mean( Glist[.,jdx] )))
	}

	// 99.5%-Winsorize Glist
	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
		Glist995 = sort( abs(Glist[idx,.]), 1 )[trunc(0.995*num_bootstrap)]
		temp = Glist[idx,.]
		temp = -abs(Glist995):*( Glist[idx,.] :< -abs(Glist995) ) + temp:*( Glist[idx,.] :<= -abs(Glist995) )
		temp = abs(Glist995):*( Glist[idx,.] :> abs(Glist995) ) + temp:*( Glist[idx,.] :<= abs(Glist995) )
		Glist[idx,.] = temp
	}

	////////////////////////////////////////////////////////////////////////////
	// Uniform Confidence Band and Hypothesis Testing
	half_band_length = sort(maxGlist,1)[trunc(cover*num_bootstrap)] / (n*h)^0.5
	bl = QTElist :- half_band_length
	bu = QTElist :+ half_band_length
	pval_nullity = mean(( (n*h)^0.5 :* max(abs(QTElist)) :<= maxGlist )')
	pval_homogeneity = mean(( (n*h)^0.5 :* max(abs(QTElist :- mean(QTElist'))) :<= heteroGlist )')

	////////////////////////////////////////////////////////////////////////////
	// Estimation Results
	b = QTElist
	V = V = Glist * Glist' :/ num_bootstrap - (Glist :/ num_bootstrap) * (Glist :/ num_bootstrap)'
	V = V :/ (n*h)

    st_matrix(bname, b)
    st_matrix(Vname, V)
	st_matrix(qname, qlist)
	st_matrix(cblname, bl)
	st_matrix(cbuname, bu)
    st_numscalar(nname, n)
	st_numscalar(hname, h)

	////////////////////////////////////////////////////////////////////////////
	// Console output
	printf("\n{hline 78}\n")
	printf(  "Fuzzy Regression Discontinuity Design\n")
	printf(  "Number of observations:                                        n = %f\n", n)
	printf(  "The discontinuity location of the running variable:            c = %f\n", cut)
	printf(  "{hline 78}\n")
	printf(  "Quantile          QTE     [%2.0f%% Unif. Conf. Band]\n",100*cover)
	printf(  "{hline 48}\n")
	for( idx = 1 ; idx <= length(qlist) ; idx++ ){
	    printf("   %5.3f   %10.5f   %10.5f   %10.5f\n",qlist[idx],b[idx],bl[idx],bu[idx])
	}
	printf(  "{hline 78}\n")
	printf(  "Test of the hypothesis that QTE=0 for all quantiles:           p-value = %4.3f\n", pval_nullity)
	printf(  "Test of the hypothesis that QTE is constant across quantiles:  p-value = %4.3f\n", pval_homogeneity)
	printf(  "{hline 78}\n")
}
end
////////////////////////////////////////////////////////////////////////////////