*! version 1.0.0  25jul2026  Alexandre Poirier and Tymon Słoczyński

* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Alexandre Poirier and Tymon Słoczyński
* See the LICENSE file in this distribution for full text.

capture program drop _causalrep_did
program define _causalrep_did, eclass sortpreserve
	version 14
	syntax varlist(min=2 max=2 numeric) [if] [in], Treatment(varname numeric) [Outcome(varname numeric) cate(varname numeric) NOIsily vce(str)]

	tokenize `varlist'

	if "`outcome'"=="" & "`cate'"!="" {
		display as error "option {bf:cate} requires outcome to be specified"
		exit 198
	}

	preserve
	if "`outcome'"!="" {
		drop `outcome'
		capture summarize `1'
		if _rc!=0 {
			display as error "outcome and group variables must not be the same"
			exit 498
		}
		capture summarize `2'
		if _rc!=0 {
			display as error "outcome and time variables must not be the same"
			exit 498
		}
		capture summarize `treatment'
		if _rc!=0 {
			display as error "outcome and treatment must not be the same"
			exit 498
		}
	}

	restore, preserve
	drop `treatment'
	capture summarize `1'
	if _rc!=0 {
		display as error "treatment and group variables must not be the same"
		exit 498
	}
	capture summarize `2'
	if _rc!=0 {
		display as error "treatment and time variables must not be the same"
		exit 498
	}

	restore, preserve
	drop `1'
	capture summarize `2'
	if _rc!=0 {
		display as error "group and time variables must not be the same"
		exit 498
	}

	restore

	quietly {
		if "`outcome'"!="" {
			tempvar touse
			tempname b V N df_r twfe

			if "`vce'"=="" local vce = "cluster `1'"
			`noisily' regress `outcome' `treatment' i.`1' i.`2' `if' `in', vce(`vce')
			matrix `b' = e(b)
			matrix `V' = e(V)
			scalar `N' = e(N)
			scalar `df_r' = e(df_r)
			scalar `twfe' = _b[`treatment']
			generate byte `touse' = e(sample)
		}
		else {
			tempvar touse
			tempname N

			regress `treatment' i.`1' i.`2' `if' `in'
			scalar `N' = e(N)
			generate byte `touse' = e(sample)
		}

		*the time variable must be recorded as integer values
		capture assert `2'==int(`2') if `touse'==1
		if _rc!=0 {
			noisily display as error "the time variable must be recorded as integer values"
			exit 459
		}

		*causalrep did only allows binary treatments
		capture tabulate `treatment' if `touse'==1
		if _rc!=0 | (_rc==0 & r(r)!=2) {
			noisily display as error "treatment must be binary"
			exit 450
		}

		*causalrep did only allows treatments that take on values zero or one
		quietly summarize `treatment' if `touse'==1
		if r(min)!=0 | r(max)!=1 {
			noisily display as error "treatment must only take on values zero or one"
			exit 450
		}

		*verify whether there are any missing CATE values within the estimation sample
		if "`cate'"!="" {
			quietly count if `touse'==1 & `treatment'==1 & missing(`cate')
			if r(N)>0 {
				noisily display as error "CATE must be nonmissing for all treated units in the estimation sample"
				exit 416
			}
		}

		*verify the balanced group-period structure
		tempvar tag_t tag_gt nperiods_g
		tempvar n_gt min_n_gt max_n_gt
		tempvar dmin_gt dmax_gt

		*count the number of distinct periods in the estimation sample
		egen byte `tag_t' = tag(`2') if `touse'==1
		count if `tag_t'==1
		local nperiods = r(N)

		*check that every group is represented in every period
		egen byte `tag_gt' = tag(`1' `2') if `touse'==1
		bysort `1': egen long `nperiods_g' = total(`tag_gt')
		capture assert `nperiods_g'==`nperiods' if `touse'==1
		if _rc!=0 {
			noisily display as error "the estimation sample is not balanced across group-period cells; every group must be represented in every sample period"
			exit 459
		}

		*count observations in each group-period cell
		bysort `1' `2': egen long `n_gt' = total(`touse')

		*check that each group contributes the same number of observations in every period
		bysort `1': egen long `min_n_gt' = min(cond(`touse'==1, `n_gt', .))
		bysort `1': egen long `max_n_gt' = max(cond(`touse'==1, `n_gt', .))
		capture assert `min_n_gt'==`max_n_gt' if `touse'==1
		if _rc!=0 {
			noisily display as error "group-period cell sizes must be constant over time within each group"
			exit 459
		}

		*check that treatment is constant within each group-period cell
		bysort `1' `2': egen double `dmin_gt' = min(cond(`touse'==1, `treatment', .))
		bysort `1' `2': egen double `dmax_gt' = max(cond(`touse'==1, `treatment', .))
		capture assert `dmin_gt'==`dmax_gt' if `touse'==1
		if _rc!=0 {
			noisily display as error "treatment must be constant within each group-period cell"
			exit 459
		}

		*record all timing groups
		tempvar aux1 aux2 group
		tempname always

		summarize `2' if `touse'==1
		local minT = r(min)
		local maxT = r(max)

		summarize `2' if `touse'==1 & `treatment'==1
		local minTD = r(min)
		local maxTD = r(max)

		scalar `always' = `minT'==`minTD'

		if `always'==1 {
			noisily display as error "at least one group is treated in the first sample period"
			exit 459
		}

		sort `touse' `1' `2'

		generate double `aux1' = `2' if `touse'==1 & `treatment'==1
		by `touse' `1': egen double `group' = min(`aux1')

		generate double `aux2' = `group' - `2'
		summarize `aux2' if `touse'==1 & `treatment'==0
		if r(min)<0 {
			noisily display as error "there is a group that exits the treatment"
			exit 459
		}

		*compute the weights
		tempvar share_t share_g weight
		tempname N_neg N_tot

		by `touse' `1': egen double `share_t' = mean(`treatment')
		bysort `touse' `2': egen double `share_g' = mean(`treatment')

		summarize `treatment' if `touse'==1
		generate double `weight' = 1 - `share_t' - `share_g' + r(mean)

		count if `touse'==1 & `treatment'==1 & `weight'<0
		scalar `N_neg' = r(N)

		count if `touse'==1 & `treatment'==1
		scalar `N_tot' = r(N)

		if `N_neg'>0 {
			noisily display ""
			noisily display as error "Negative weights detected: " `N_neg' " of " `N_tot' " estimated weights are below 0."
			noisily display ""
			noisily display as error "The value reported below as the measure of internal validity (MIV) when the CATE function is unrestricted is the inverse maximum weight. The value reported as the measure of representativeness (MR) is the product of the inverse maximum weight and the estimated proportion of treated units. If any population weights are negative, the corresponding MIV and MR are zero. See Poirier and Słoczyński (2026) for details."
		}
		else {
			noisily display ""
			noisily display as text "No negative weights detected."
		}

		*compute the MIV and MR
		tempname prtreat miv mr

		summarize `treatment' if `touse'==1
		scalar `prtreat' = r(mean)

		summarize `weight' if `touse'==1 & `treatment'==1
		local max_weight = r(max)
		if abs(`max_weight') <= 1e-12 {
			noisily display ""
			noisily display as error "the maximum estimated TWFE weight is zero; the unrestricted-CATE MIV cannot be computed"
			exit 459
		}

		scalar `miv' = r(mean)/`max_weight'
		scalar `mr' = `prtreat'*`miv'

		*consider a given CATE function
		if "`cate'"!="" {
			tempvar diff
			tempname att miv_cate mr_cate

			summarize `cate' if `touse'==1 & `treatment'==1
			scalar `att' = r(mean)

			generate double `diff' = `cate' - `twfe'
			summarize `diff' if `touse'==1 & `treatment'==1
			local full_diff = r(mean)

			summarize `cate' if `touse'==1 & `treatment'==1
			local min_cate = r(min)
			local max_cate = r(max)

			local cate_tol = 1e-10 * max(1, abs(`twfe'), abs(`att'), abs(`min_cate'), abs(`max_cate'))

			if `twfe' < `min_cate' - `cate_tol' | `twfe' > `max_cate' + `cate_tol' {
				scalar `miv_cate' = 0
				scalar `mr_cate' = 0
			}
			else if abs(`full_diff') <= `cate_tol' {
				scalar `miv_cate' = 1
				scalar `mr_cate' = `prtreat'
			}
			else if abs(`twfe' - `min_cate') <= `cate_tol' | abs(`twfe' - `max_cate') <= `cate_tol' {
				count if `touse'==1 & `treatment'==1 & abs(`diff') <= `cate_tol'
				scalar `miv_cate' = r(N)/`N_tot'
				scalar `mr_cate' = `prtreat'*`miv_cate'
			}
			else {
				*if original value of MIV is sufficiently small, search from the top; otherwise, search from the bottom
				if `miv' < 0.2 {
					local i = 0

					if `twfe' > `att' {
						*the starting value of local mean can be an arbitrary value with a specific sign
						local mean = 1
						gsort -`touse' -`treatment' -`diff'
						while `mean'>0 {
							local i = `i' + 1
							summarize `diff' in 1/`i'
							local mean = r(mean)
						}
					}
					else {
						local mean = -1
						gsort -`touse' -`treatment' +`diff'
						while `mean'<0 {
							local i = `i' + 1
							summarize `diff' in 1/`i'
							local mean = r(mean)
						}
					}
				}
				else {
					local i = `N_tot' + 1

					if `twfe' > `att' {
						local mean = -1
						gsort -`touse' -`treatment' -`diff'
						while `mean'<0 {
							local i = `i' - 1
							summarize `diff' in 1/`i'
							local mean = r(mean)
						}
					}
					else {
						local mean = 1
						gsort -`touse' -`treatment' +`diff'
						while `mean'>0 {
							local i = `i' - 1
							summarize `diff' in 1/`i'
							local mean = r(mean)
						}
					}

					if `i' < `N_tot' {
						local i = `i' + 1
						summarize `diff' in 1/`i'
						local mean = r(mean)
					}
				}

				scalar `miv_cate' = (`i'/`N_tot') - ((`i'/`N_tot')*`mean')/`diff'[`i']
				scalar `mr_cate' = `prtreat'*`miv_cate'
			}
		}
	}

	if "`outcome'"!="" {
		display ""
		display as text `""TWFE" is the TWFE estimate of the effect of "' as result `"`treatment'"' as text `" on "' as result `"`outcome'"' as text `"."'
		display ""
		if abs(`twfe')>1 display as text "  TWFE  =  " as result %-9.4g `twfe'
		else display as text "  TWFE  =  " as result %5.3f `twfe'
	}
	display ""
	display as text `""MIV" is the estimated measure of internal validity of the TWFE estimand with respect to the subpopulation of treated units."'
	display ""
	if "`cate'"=="" display as text "   MIV  =  " as result %5.3f `miv'
	else {
		display as text "No restrictions on the CATE function:   MIV  =  " as result %5.3f `miv'
		display as text "   Using the estimated CATE function:   MIV  =  " as result %5.3f `miv_cate'
	}
	display ""
	display as text `""MR" is the estimated measure of representativeness, which equals the MIV times the proportion of treated units."'
	display ""
	if "`cate'"=="" display as text "    MR  =  " as result %5.3f `mr'
	else {
		display as text "No restrictions on the CATE function:    MR  =  " as result %5.3f `mr'
		display as text "   Using the estimated CATE function:    MR  =  " as result %5.3f `mr_cate'
	}

	if "`outcome'"!="" ereturn post `b' `V', esample(`touse') depname(`outcome') buildfvinfo
	else {
		ereturn clear
		ereturn post, esample(`touse')
	}

	ereturn scalar N = `N'
	if "`outcome'"!="" ereturn scalar df_r = `df_r'
	if "`outcome'"!="" ereturn scalar twfe = `twfe'
	if "`cate'"!="" ereturn scalar att = `att'
	ereturn scalar miv = `miv'
	if "`cate'"!="" ereturn scalar miv_cate = `miv_cate'
	ereturn scalar mr = `mr'
	if "`cate'"!="" ereturn scalar mr_cate = `mr_cate'

	if "`outcome'"!="" ereturn local depvar `"`outcome'"'
end