*! version 1.4.2  06feb2024
*! Sebastian Kripfganz, www.kripfganz.de
*! Vasilis Sarafidis, sites.google.com/view/vsarafidis

*==================================================*
****** Defactored IV dynamic panel data estimation ******

*** citation ***

/*	Kripfganz, S., and V. Sarafidis. 2021.
	Instrumental-variable estimation of large-T panel-data models with common factors.
	Stata Journal 21: 659-686.		*/

*** version history at the end of the file ***

program define xtivdfreg, eclass prop(xt)
	version 13.0
	if replay() {
		if !inlist("`e(cmd)'", "xtivdfreg", "spxtivdfreg") {
			error 301
		}
		xtivdfreg_parse_display `0'
		if `"`s(options)'"' != "" {
			di as err `"`s(options)' invalid"'
			exit 198
		}
		if `"`s(cformat)'"' != "" {
			loc diopts			`"cfmt(`s(cformat)')"'
		}
		xtivdfreg_display `0' `diopts'
	}
	else {
		_xt, treq
		syntax varlist(num ts fv) [if] [in] [, SPVARLIST(passthru) SPREGNAMES(passthru) *]
		xtivdfreg_parse_display , `options'
		loc diopts			`"`s(diopts)'"'
		if `"`s(cformat)'"' != "" {
			loc diopts			`"`diopts' cfmt(`s(cformat)')"'
		}
		xtivdfreg_init , `s(options)'
		loc mopt			"`s(mopt)'"
		loc doubledefact	= cond("`s(mg)'" == "", "DOUBLEdefact", "noDOUBLEdefact")
		xtivdfreg_iv `doubledefact' `varlist' `if' `in', mopt(`mopt') `s(mg)' `s(mgi)' `s(options)'
		if "`spvarlist'" != "" {
			xtivdfreg_sp , `spvarlist' `spregnames'
			loc diopts		"`diopts' neq(2)"
		}

		eret loc vcetype	"Robust"
		eret loc marginsok	"XB default"
		eret loc predict	"xtivdfreg_p"
		eret loc estat_cmd	"xtivdfreg_estat"
		eret loc tvar		"`_dta[_TStvar]'"
		eret loc ivar		"`_dta[_TSpanel]'"
		eret loc cmdline 	`"xtivdfreg `0'"'
		eret loc cmd		"xtivdfreg"
		eret hidden loc mopt	"`mopt'"			// undocumented
		xtivdfreg_display , `diopts'
	}
end

program define xtivdfreg_iv, eclass sort
	version 13.0
	gettoken fsyntax 0 : 0
	syntax varlist(num ts fv) [if] [in] , MOPT(name) [	Absorb(varlist num fv)				///
														noCONStant							///
														FACTmax(integer 4)					///
														noEIGratio							///
														STD									///
														FSTAGE								///
														FSTEP								/// historical since version 1.0.2
														MG									///
														MGi(numlist int min=2 max=2)		///
														`fsyntax'							/// DOUBLEdefact noDOUBLEdefact
														*]									// parsed separately: IV()
	loc fv				= ("`s(fvops)'" == "true")
	if `fv' {
		fvexpand `varlist'
		loc varlist			"`r(varlist)'"
	}
	marksample touse
	gettoken depvar indepvars : varlist
	if "`indepvars'" == "" & "`constant'" != "" {
		error 102
	}
	if `fv' {
		_fv_check_depvar `depvar'
	}
	_rmdcoll `depvar' `indepvars', `constant'
	loc indepvars		"`r(varlist)'"
	loc regnames		"`indepvars'"
	if "`constant'" != "" {
		mata: xtivdfreg_init_cons(`mopt', "off")		// constant term
	}
	else {
		loc regnames		"`regnames' _cons"
	}
	if "`mg'" != "" {
		loc id				"`_dta[_TSpanel]'"
		if !`: list id in absorb' & "`constant'" == "" {
			di as err "option absorb() or noconstant required with option mg"
			exit 198
		}
		if "`mgi'" != "" {
			gettoken mg_id mgi : mgi
			mata: xtivdfreg_init_mg(`mopt', "on", `mgi')
		}
		else {
			mata: xtivdfreg_init_mg(`mopt', "on")
		}
		loc fstage			"fstage"
		if "`doubledefact'" == "" {
			loc doubledefact	"doubledefact"
		}
	}
	if "`fstep'" != "" {
		loc fstage			"fstage"
	}
	if "`fstage'" != "" {
		mata: xtivdfreg_init_stages(`mopt', 1)
	}

	*--------------------------------------------------*
	*** syntax parsing of options for instruments ***
	if `factmax' < 0 {
		di as err "option factmax() incorrectly specified -- outside of allowed range"
		exit 125
	}
	mata: xtivdfreg_init_factmax(`mopt', `factmax')
	if "`eigratio'" != "" {
		mata: xtivdfreg_init_eigratio(`mopt', "off")
	}
	loc standardize		= ("`std'" != "")
	loc ivnum			= 0
	loc ivset			= 0
	while `"`options'"' != "" {
		loc ++ivset
		xtivdfreg_parse_options , factmax(`factmax') `eigratio' `std' `doubledefact' `options'
		forv l = 0 / `s(lags)' {
			loc ++ivnum
			loc ivset`ivnum'	= `ivset'
			loc ivvars`ivnum'	"`s(ivvars`l')'"
			loc factvars`ivnum'	"`s(factvars`l')'"
			loc factmax`ivnum'	= `s(factmax)'
			loc eigratio`ivnum'	"`s(eigratio)'"
			loc double`ivnum'	= (`l' == 0 & "`s(doubledefact)'" == "on")
			loc std`ivnum'		= ("`s(std)'" == "on")
			loc ivnames`ivnum'	"`s(ivvarnames)'"
			markout `touse' `ivvars`ivnum'' `factvars`ivnum''
			if !`standardize' & `std`ivnum'' {
				loc standardize		= 1
			}
		}
		loc options			`"`s(options)'"'
	}
	cap xtdes if `touse'
	if _rc == 459 | r(N) == 0 {
		error 2000
	}
	if `factmax' >= r(min) {
		error 2001
	}

	*--------------------------------------------------*
	*** absorption of fixed effects ***
	tempvar depvar_a
	if "`absorb'" == "" {
	    sum `depvar' if `touse', mean
		qui gen double `depvar_a' = `depvar' - r(mean) if `touse'
		if "`indepvars'" != "" {
			forv j = 1 / `: word count `indepvars'' {
				tempvar indepvar`j'_a
				loc var				: word `j' of `indepvars'
				sum `var' if `touse', mean
				qui gen double `indepvar`j'_a' = `var' - r(mean) if `touse'
				loc indepvars_a		"`indepvars_a' `indepvar`j'_a'"
			}
		}
		mata: xtivdfreg_init_touse(`mopt', "`touse'")				// marker variable
		mata: xtivdfreg_init_by(`mopt', "`_dta[_TSpanel]'")		// panel identifier
		mata: xtivdfreg_init_time(`mopt', "`_dta[_TStvar]'")		// time identifier
		mata: xtivdfreg_init_depvar(`mopt', "`depvar_a'", "`depvar'")					// (demeaned) dependent variable
		mata: xtivdfreg_init_indepvars(`mopt', "`indepvars_a'", "`indepvars'")		// (demeaned) independent variables
		forv k = 1 / `ivnum' {
			forv j = 1 / `: word count `ivvars`k''' {
				tempvar ivvar`k'_`j'_a
				loc var				: word `j' of `ivvars`k''
				sum `var' if `touse', mean
				qui gen double `ivvar`k'_`j'_a' = `var' - r(mean) if `touse'
				loc ivvars`k'_a		"`ivvars`k'_a' `ivvar`k'_`j'_a'"
			}
			forv j = 1 / `: word count `factvars`k''' {
				tempvar factvar`k'_`j'_a
				loc var				: word `j' of `factvars`k''
				sum `var' if `touse', mean
				qui gen double `factvar`k'_`j'_a' = `var' - r(mean) if `touse'
				loc factvars`k'_a		"`factvars`k'_a' `factvar`k'_`j'_a'"
			}
			mata: xtivdfreg_init_ivvars(`mopt', `k', "`ivvars`k'_a'")		// demeaned instrumental variables
			if !`std`k'' {
				mata: xtivdfreg_init_ivvars_factvars(`mopt', `k', "`factvars`k'_a'")		// demeaned defactoring variables
			}
			mata: xtivdfreg_init_ivvars_factmax(`mopt', `k', `factmax`k'')
			mata: xtivdfreg_init_ivvars_eigratio(`mopt', `k', "`eigratio`k''")
			mata: xtivdfreg_init_ivvars_group(`mopt', `k', `ivset`k'')
			if `double`k'' {
				loc factnames		"`factnames' `ivvars`k''"
				loc factvars		"`factvars' `ivvars`k'_a'"
			}
		}
	}
	else {
		cap {
			which reghdfe
			which ftools
		}
		if _rc {
			di as err "option absorb() requires further community-contributed packages:"
			di as err "  type {stata ssc install reghdfe} to install {bf:reghdfe}"
			di as err "  type {stata ssc install ftools} to install {bf:ftools}"
			exit 199
		}
		_rmcoll i.(`absorb')
		if "`indepvars'" != "" {
			forv j = 1 / `: word count `indepvars'' {
				tempvar indepvar`j'_a
				loc indepvars_a		"`indepvars_a' `indepvar`j'_a'"
			}
		}
// 		cap mata: xtivdfreg_hdfe = fixed_effects("`absorb'", "`touse'", "", "", 1, -1)
// 		if _rc {
// 			di as err "{bf:reghdfe} Mata library not found:"
// 			di as err "  type {stata reghdfe, check} to compile the library"
// 			exit 3499
// 		}
		cap include "reghdfe.mata", adopath
		cap {
			mata: xtivdfreg_hdfe = FixedEffects()
			mata: xtivdfreg_hdfe.absvars = "`absorb'"
			mata: xtivdfreg_hdfe.tousevar = "`touse'"
			mata: xtivdfreg_hdfe.init()
			mata: xtivdfreg_hdfe.partial_out("`depvar' `indepvars'", 1, 0)
			mata: st_store(xtivdfreg_hdfe.sample, st_addvar("double", tokens("`depvar_a' `indepvars_a'")), xtivdfreg_hdfe.solution.data)
		}
		if _rc {
			di as err "option absorb() requires reghdfe version 6.12.3 or higher and ftools version 2.49.1 or higher:"
			di as err "  type {stata adoupdate reghdfe, update} to update {bf:reghdfe} to its latest version"
			di as err "  type {stata adoupdate ftools, update} to update {bf:ftools} to its latest version"
			exit 3499
		}
// 		mata: xtivdfreg_hdfe_var = xtivdfreg_hdfe.partial_out("`depvar' `indepvars'")
// 		mata: st_store(xtivdfreg_hdfe.sample, st_addvar("double", tokens("`depvar_a' `indepvars_a'")), xtivdfreg_hdfe_var)
		markout `touse' `depvar_a'
		mata: xtivdfreg_init_touse(`mopt', "`touse'")				// marker variable
		mata: xtivdfreg_init_by(`mopt', "`_dta[_TSpanel]'")		// panel identifier
		mata: xtivdfreg_init_time(`mopt', "`_dta[_TStvar]'")		// time identifier
		mata: xtivdfreg_init_depvar(`mopt', "`depvar_a'", "`depvar'")					// (demeaned) dependent variable
		mata: xtivdfreg_init_indepvars(`mopt', "`indepvars_a'", "`indepvars'")		// (demeaned) independent variables
		forv k = 1 / `ivnum' {
			forv j = 1 / `: word count `ivvars`k''' {
				tempvar ivvar`k'_`j'_a
				loc ivvars`k'_a		"`ivvars`k'_a' `ivvar`k'_`j'_a'"
			}
			cap {
				mata: xtivdfreg_hdfe.partial_out("`ivvars`k''", 0, 0)
				mata: st_store(xtivdfreg_hdfe.sample, st_addvar("double", tokens("`ivvars`k'_a'")), xtivdfreg_hdfe.solution.data)
			}
			if _rc {
				di as err "option absorb() requires reghdfe version 6.12.3 or higher and ftools version 2.49.1 or higher:"
				di as err "  type {stata adoupdate reghdfe, update} to update {bf:reghdfe} to its latest version"
				di as err "  type {stata adoupdate ftools, update} to update {bf:ftools} to its latest version"
				exit 3499
			}
// 			mata: xtivdfreg_hdfe_var = xtivdfreg_hdfe.partial_out("`ivvars`k''")
// 			mata: st_store(xtivdfreg_hdfe.sample, st_addvar("double", tokens("`ivvars`k'_a'")), xtivdfreg_hdfe_var)
			forv j = 1 / `: word count `factvars`k''' {
				tempvar factvar`k'_`j'_a
				loc factvars`k'_a	"`factvars`k'_a' `factvar`k'_`j'_a'"
			}
			cap {
				mata: xtivdfreg_hdfe.partial_out("`factvars`k''", 0, 0)
				mata: st_store(xtivdfreg_hdfe.sample, st_addvar("double", tokens("`factvars`k'_a'")), xtivdfreg_hdfe.solution.data)
			}
			if _rc {
				di as err "option absorb() requires reghdfe version 6.12.3 or higher and ftools version 2.49.1 or higher:"
				di as err "  type {stata adoupdate reghdfe, update} to update {bf:reghdfe} to its latest version"
				di as err "  type {stata adoupdate ftools, update} to update {bf:ftools} to its latest version"
				exit 3499
			}
// 			mata: xtivdfreg_hdfe_var = xtivdfreg_hdfe.partial_out("`factvars`k''")
// 			mata: st_store(xtivdfreg_hdfe.sample, st_addvar("double", tokens("`factvars`k'_a'")), xtivdfreg_hdfe_var)
			mata: xtivdfreg_init_ivvars(`mopt', `k', "`ivvars`k'_a'")		// demeaned instrumental variables
			if !`std`k'' {
				mata: xtivdfreg_init_ivvars_factvars(`mopt', `k', "`factvars`k'_a'")		// demeaned defactoring variables
			}
			mata: xtivdfreg_init_ivvars_factmax(`mopt', `k', `factmax`k'')
			mata: xtivdfreg_init_ivvars_eigratio(`mopt', `k', "`eigratio`k''")
			mata: xtivdfreg_init_ivvars_group(`mopt', `k', `ivset`k'')
			if `double`k'' {
				loc factnames		"`factnames' `factvars`k''"
				loc factvars		"`factvars' `factvars`k'_a'"
			}
		}
// 		mata: mata drop xtivdfreg_hdfe xtivdfreg_hdfe_var
		mata: mata drop xtivdfreg_hdfe
	}
	if "`factvars'" != "" {
		mata: xtivdfreg_init_factvars(`mopt', "`factvars'")
	}

	*--------------------------------------------------*
	*** standardization ***
	if `standardize' {
		sort `_dta[_TStvar]' `_dta[_TSpanel]'
		forv k = 1 / `ivnum' {
			if `std`k'' {
				foreach var in `factvars`k'_a' {
					tempname std`var'
					cap by `_dta[_TStvar]': egen double `std`var'' = std(`var') if `touse'
					if _rc == 190 {
						di as err "option std requires Stata 16.1 (with latest updates) or higher"
						exit 199
					}
					loc stdvars`k'		"`stdvars`k'' `std`var''"
					if "`std'" != "" & `double`k'' {
						loc stdvars			"`stdvars' `std`var''"
					}
				}
			}
		}
		sort `_dta[_TSpanel]' `_dta[_TStvar]'
		forv k = 1 / `ivnum' {
			if `std`k'' {
				mata: xtivdfreg_init_ivvars_factvars(`mopt', `k', "`stdvars`k''")		// standardized defactoring variables
			}
		}
		if "`stdvars'" != "" {
			mata: xtivdfreg_init_factvars(`mopt', "`stdvars'")
		}
	}

	*--------------------------------------------------*
	*** estimation ***
	di _n as txt "Defactored instrumental variables estimation"
	mata: xtivdfreg(`mopt')

	mata: st_numscalar("r(N)", xtivdfreg_result_N(`mopt'))
	mata: st_numscalar("r(chi2_J)", xtivdfreg_result_overid(`mopt'))
	mata: st_numscalar("r(rank)", xtivdfreg_result_rank(`mopt'))
	mata: st_numscalar("r(zrank)", xtivdfreg_result_zrank(`mopt'))
	mata: st_matrix("r(b)", xtivdfreg_result_coefs(`mopt'))
	mata: st_matrix("r(V)", xtivdfreg_result_V(`mopt'))
	loc N				= r(N)
	loc chi2_J			= r(chi2_J)
	loc rank			= r(rank)
	loc zrank			= r(zrank)
	tempname b V factnum
	mat `b'				= r(b)
	mat `V'				= r(V)
	mat coln `b'		= `regnames'
	mat rown `V'		= `regnames'
	mat coln `V'		= `regnames'
	if "`mg'" != "" {
		qui levelsof `_dta[_TSpanel]' if `touse'
		loc groups			"`r(levels)'"
		tempname b_mg se_mg
		mata: st_matrix("r(b_mg)", xtivdfreg_result_coefs(`mopt', ., 0))
		mata: st_matrix("r(se_mg)", sqrt(xtivdfreg_result_V(`mopt', ., 0)))
		mat `b_mg'			= r(b_mg)
		mat `se_mg'			= r(se_mg)
		mat rown `b_mg'		= `groups'
		mat coln `b_mg'		= `regnames'
		mat rown `se_mg'	= `groups'
		mat coln `se_mg'	= `regnames'
	}

	if "`factvars'" != "" {
		mata: st_numscalar("r(fact1)", xtivdfreg_result_factnum(`mopt', 1))
		loc fact1double		= r(fact1)
		loc fact1			= `fact1double'
	}
	else {
		loc fact1			= .
	}
	loc fact1equal		= 1
	if `ivnum' {
		forv k = 1 / `ivnum' {
			if !`: list ivset`k' in ivsets' {
				loc ivsets			"`ivsets' `ivset`k''"
				mata: st_numscalar("r(fact1)", xtivdfreg_result_factnum(`mopt', 1, `k'))
				mat `factnum'		= (nullmat(`factnum'), r(fact1))
				if `fact1equal' {
					if `fact1' < . {
						loc fact1equal		= (r(fact1) == `fact1')
					}
					else {
						loc fact1			= r(fact1)
					}
				}
				if "`ivnames`k''" != "" {
					loc ivlist`ivset`k'' "`ivnames`k''"
					if "`ivvars`k''" == "`factvars`k''" {
						loc factlist`ivset`k'' "`ivnames`k''"
					}
					else {
						loc factlist`ivset`k'' "`factvars`k''"
					}
				}
				else {
					loc ivlist`ivset`k'' "`ivvars`k''"
					loc factlist`ivset`k'' "`factvars`k''"
				}
			}
		}
		if "`factvars'" != "" {
			mat `factnum'		= (`factnum', `fact1double')
			loc ivsets			"`ivsets' ."
		}
		mat coln `factnum'	= `ivsets'
	}

	*--------------------------------------------------*
	*** current estimation results ***
	if `fv' {
		loc fvopt			"buildfv"
	}
	eret post `b' `V', dep(`depvar') o(`N') e(`touse') `fvopt' findomitted
	eret sca df_m		= `rank'
	mata: st_numscalar("e(N_g)", xtivdfreg_result_Ng(`mopt'))
	mata: st_numscalar("e(g_min)", xtivdfreg_result_Tmin(`mopt'))
	eret sca g_avg		= e(N) / e(N_g)
	mata: st_numscalar("e(g_max)", xtivdfreg_result_Tmax(`mopt'))
	if "`fstage'" == "" {
		mata: st_numscalar("e(sigma2u)", xtivdfreg_result_sigma2(`mopt', 1))
		mata: st_numscalar("e(sigma2f)", xtivdfreg_result_sigma2(`mopt', 2))
		eret sca rho		= e(sigma2f) / e(sigma2u)
	}
	if "`mg'" == "" {
		eret sca chi2_J		= `chi2_J'
		eret sca df_J		= `zrank' - `rank'
		eret sca p_J		= chi2tail(`zrank' - `rank', `chi2_J')
	}
	eret sca rank		= `rank' + ("`constant'" == "")
	eret sca zrank		= `zrank'
	if `fact1equal' {
		eret sca fact1		= `fact1'
	}
	if "`fstage'" == "" {
		mata: st_numscalar("e(fact2)", xtivdfreg_result_factnum(`mopt', 2))
		eret loc estimator	"sstage"
	}
	else if "`mg'" == "" {
		eret loc estimator	"fstage"
	}
	else {
		eret loc estimator	"mg"
		eret mat se_mg		= `se_mg'
		eret mat b_mg		= `b_mg'
	}
	if "`mg_id'" != "" {
		eret sca mg_id		= `mg_id'
	}

	*--------------------------------------------------*
	*** hidden estimation results ***					// undocumented
	if `ivnum' {
		foreach k of num `ivsets' {
		    if `k' < . {
				eret hidden loc ivset`k' "`ivlist`k''"
				eret hidden loc factset`k' "`factlist`k''"
			}
			else {
				eret hidden loc doubledefact "`: list retok factnames'"
			}
		}
		if `ivnum' {
			eret hidden mat factnum = `factnum'
		}
	}
end

*==================================================*
**** display of estimation results ****
program define xtivdfreg_display
	version 13.0
	syntax [, noHEader noTABle CFMT(string asis) *]

	if "`header'" == "" {
		di _n as txt "Group variable: " as res abbrev("`e(ivar)'", 12) _col(46) as txt "Number of obs" _col(68) "=" _col(70) as res %9.0f e(N)
		di as txt "Time variable: " as res abbrev("`e(tvar)'", 12) _col(46) as txt "Number of groups" _col(68) "=" _col(70) as res %9.0f e(N_g)
		di _n as txt "Number of instruments  =" as res %7.0f e(zrank) _col(46) as txt "Obs per group" _col(64) "min =" _col(70) as res %9.0g e(g_min)
		di as txt "Number of factors in X =" _c
		if e(fact1) < . {
			di as res %7.0f e(fact1) _col(64) as txt "avg =" _col(70) as res %9.0g e(g_avg)
		}
		else {
			di _col(31) "*" _col(64) as txt "avg =" _col(70) as res %9.0g e(g_avg)
		}
		if "`e(estimator)'" == "sstage" {
			di as txt "Number of factors in u =" as res %7.0f e(fact2) _col(64) as txt "max =" _col(70) as res %9.0g e(g_max)
		}
		else {
			di _col(64) as txt "max =" _col(70) as res %9.0g e(g_max)
		}
		if "`e(estimator)'" == "sstage" {
			di _n as txt "Second-stage estimator (model with homogeneous slope coefficients)"
		}
		else if "`e(estimator)'" == "fstage" {
			di _n as txt "First-stage estimator (model with homogeneous slope coefficients)"
		}
		else if "`e(estimator)'" == "mg" {
			di _n as txt "Mean-group estimator " _c
			if e(mg_id) < . {
				di "for group " as res e(mg_id)
			}
			else {
				di "(model with heterogeneous slope coefficients)"
			}
		}
	}
	if "`table'" == "" {
		if "`e(estimator)'" == "sstage" {
			_coef_table, `options' plus
			loc wc1				= `s(width_col1)' - 1
			loc wc2				= `s(width)' - `s(width_col1)' - 1
			di as txt %`wc1's "sigma_f" " {c |} " as res %10s "`: di `cfmt' sqrt(e(sigma2f))'" as txt "   (std. dev. of factor error component)"
			di as txt %`wc1's "sigma_e" " {c |} " as res %10s "`: di `cfmt' sqrt(e(sigma2u) - e(sigma2f))'" as txt "   (std. dev. of idiosyncratic error component)"
			di as txt %`wc1's "rho" " {c |} " as res %10s "`: di `cfmt' e(rho)'" as txt "   (fraction of variance due to factors)"
			loc ++wc1
			di as txt "{hline `wc1'}{c BT}{hline `wc2'}"
		}
		else {
			_coef_table, `options'
		}
	}
	if "`header'" == "" {
		if "`e(estimator)'" != "mg" {
			di as txt "Hansen test of the overidentifying restrictions" _col(56) "chi2(" as res e(df_J) as txt ")" _col(68) "=" _col(70) as res %9.4f e(chi2_J)
			if e(df_J) {
				di as txt "H0: overidentifying restrictions are valid" _c
			}
			else {
				di as txt "note: coefficients are exactly identified" _c
			}
			di _col(56) as txt "Prob > chi2" _col(68) "=" _col(73) as res %6.4f e(p_J)
		}
		if e(fact1) == . {
			if "`e(estimator)'" != "mg" {
				di ""
			}
			cap conf mat e(factnum)
			if _rc {
				di as txt "* note: no instruments specified"
			}
			else {
				di as txt "* Number of factors in stage 1:"
				loc ivsets			: coln e(factnum)
				loc K				= colsof(e(factnum))
				forv k = 1 / `K' {
					loc ivset			: word `k' of `ivsets'
					di as res %5.0f el(e(factnum), 1, `k') as txt " -> " _c
					loc ivnames			= cond(`ivset' < ., "`e(factset`ivset')'", "`e(doubledefact)' (doubledefact)")
					loc p				= 1
					loc piece			: piece 1 69 of "`ivnames'", nobreak
					while "`piece'" != "" {
						di _col(10) "`piece'"
						loc ++p
						loc piece			: piece `p' 69 of "`ivnames'", nobreak
					}
				}
			}
		}
	}
end

*==================================================*
**** syntax parsing of additional display options ****
program define xtivdfreg_parse_display, sclass
	version 13.0
	sret clear
	syntax , [noHEader noTABle PLus *]
	_get_diopts diopts options, `options'

	sret loc diopts		`"`header' `table' `plus' `diopts'"'
	sret loc options	`"`options'"'
end

*==================================================*
**** syntax parsing of the optimization options ****
program define xtivdfreg_init, sclass
	version 13.0
	sret clear
	loc maxiter			= c(maxiter)
	syntax [, ITERate(integer `maxiter') noDOTs LTOLerance(real 1e-4) MG MGi(numlist int miss max=1) *]

	if `iterate' < 0 {
		di as err "option iterate() incorrectly specified -- outside of allowed range"
		exit 125
	}
	loc mopt			"xtivdfreg_iv"
	mata: `mopt' = xtivdfreg_init()
	mata: xtivdfreg_init_conv_maxiter(`mopt', `iterate')
	if "`dots'" != "" {
		mata: xtivdfreg_init_dots(`mopt', "off")
	}
	mata: xtivdfreg_init_conv_vtol(`mopt', `ltolerance')
	if "`mgi'" != "" {
		if `mgi' < . {
			tempvar id
			egen long `id' = group(`_dta[_TSpanel]')
			sum `id' if `_dta[_TSpanel]' == `mgi', mean
			if r(N) == 0 {
				di as err "option mg() out of range"
				exit 175
			}
			loc mgi				"mgi(`mgi' `r(mean)')"
		}
		else {
			loc mgi				""
		}
		loc mg				"mg"
	}

	sret loc mopt		"`mopt'"
	sret loc mg			"`mg'"
	sret loc mgi		"`mgi'"
	sret loc options	`"`options'"'
end

*==================================================*
**** adjustment for spatial regressors ****
program define xtivdfreg_sp, eclass
	version 13.0
	syntax [, SPVARLIST(varlist num ts) SPREGNAMES(varlist num ts fv)]

	tempname b V
	mat `b'				= e(b)
	mat `V'				= e(V)
	loc regnames		: coln `b'
	foreach spvar of var `spvarlist' {
		gettoken spregname spregnames : spregnames
		loc regnames		: subinstr loc regnames "`spvar'" "", w
		loc regnames		: subinstr loc regnames "o.`spvar'" "", w
		loc regnames		"`regnames' W:`spregname'"
	}
	loc cons			"_cons"
	loc hascons			: list cons in regnames
	loc K				= colsof(`b')
	loc Ksp				: word count `spvarlist'
	if `hascons' {
		if `K' == `Ksp' + 1 {
			mata: st_matrix("`b'", st_matrix("`b'")[1, (`K', `K'-`Ksp'..`K'-1)])
			mata: st_matrix("`V'", st_matrix("`V'")[(`K' \ `K'-`Ksp'::`K'-1), (`K', `K'-`Ksp'..`K'-1)])
		}
		else {
			mata: st_matrix("`b'", st_matrix("`b'")[1, (1..`K'-`Ksp'-1, `K', `K'-`Ksp'..`K'-1)])
			mata: st_matrix("`V'", st_matrix("`V'")[(1::`K'-`Ksp'-1 \ `K' \ `K'-`Ksp'::`K'-1), (1..`K'-`Ksp'-1, `K', `K'-`Ksp'..`K'-1)])
		}
	}
	mat coln `b'		= `regnames'
	mat coln `V'		= `regnames'
	mat rown `V'		= `regnames'
	eret repost b = `b' V = `V', ren
end

*==================================================*
**** syntax parsing of options for instruments ****
program define xtivdfreg_parse_options, sclass
	version 13.0
	sret clear
	syntax , FACTmax(integer) [noEIGratio DOUBLEdefact STD IV(string) *]

	if `"`iv'"' != "" {
		loc eigratio		= cond("`eigratio'" == "", "noEIGratio", "EIGratio")
		loc doubledefact	= cond("`doubledefact'" == "", "DOUBLEdefact", "noDOUBLEdefact")
		loc std				= cond("`std'" == "", "STD", "noSTD")
		xtivdfreg_parse_iv `factmax' `eigratio' `doubledefact' `std' `iv'
	}
	else {
		di as err `"`options' invalid"'
		exit 198
	}

	sret loc options	`"`options'"'
end

*==================================================*
**** syntax parsing for instruments ****
program define xtivdfreg_parse_iv, sclass
	version 13.0
	gettoken factmax 0 : 0
	gettoken esyntax 0 : 0
	gettoken fsyntax 0 : 0
	gettoken ssyntax 0 : 0
	syntax varlist(num ts fv), [FVAR(varlist num ts fv) Lags(integer 0) FACTmax(integer `factmax') `esyntax' `fsyntax' `ssyntax'		///
								VARNames(string)]																						// undocumented

	if `lags' < 0 {
		di as err "option lags() incorrectly specified -- outside of allowed range"
		exit 125
	}
	if `factmax' < 0 {
		di as err "option factmax() incorrectly specified -- outside of allowed range"
		exit 125
	}
	loc eigratio		= cond(("`esyntax'" == "noEIGratio" & "`eigratio'" == "") | ("`esyntax'" == "EIGratio" & "`eigratio'" != ""), "on", "off")
	loc doubledefact	= cond(("`fsyntax'" == "noDOUBLEdefact" & "`doubledefact'" == "") | ("`fsyntax'" == "DOUBLEdefact" & "`doubledefact'" != ""), "on", "off")
	loc std				= cond(("`ssyntax'" == "noSTD" & "`std'" == "") | ("`ssyntax'" == "STD" & "`std'" != ""), "on", "off")
	if "`s(fvops)'" == "true" {
		fvexpand `varlist'
		loc varlist			"`r(varlist)'"
		foreach var in `varlist' {
			forv l = 0 / `lags' {
				loc ivvar`l'		: subinstr loc var "#" "#L`l'.", all
				fvunab ivvar`l' : L`l'.`ivvar`l''
				loc ivvars`l'		"`ivvars`l'' `ivvar`l''"
			}
		}
		if "`fvar'" == "" {
			loc fvar			"`varlist'"
		}
		else {
			fvexpand `fvar'
			loc fvar			"`r(varlist)'"
		}
		foreach var in `fvar' {
			forv l = 0 / `lags' {
				loc factvar`l'		: subinstr loc var "#" "#L`l'.", all
				fvunab factvar`l' : L`l'.`factvar`l''
				loc factvars`l'		"`factvars`l'' `factvar`l''"
			}
		}
	}
	else {
		if "`fvar'" == "" {
			loc fvar			"`varlist'"
		}
		forv l = 0 / `lags' {
			fvunab ivvars`l' : L`l'.(`varlist')
			fvunab factvars`l' : L`l'.(`fvar')
		}
	}

	sret loc ivvarnames	"`varnames'"
	sret loc std		"`std'"
	sret loc doubledefact "`doubledefact'"
	sret loc eigratio	"`eigratio'"
	sret loc factmax	"`factmax'"
	sret loc lags		"`lags'"
	forv l = 0 / `lags' {
		sret loc ivvars`l'	"`: list retok ivvars`l''"
		sret loc factvars`l' "`: list retok factvars`l''"
	}
end

*==================================================*
*** version history ***
* version 1.4.2  06feb2024  option mg now requires either option absorb(panelvar) or noconstant; bug fixed with option mg() in combination with option nodoubledefact
* version 1.4.1  31jan2024  bug fixed with estat impact under Stata versions before Stata 16
* version 1.4.0  30jan2024  option mg() added for group-specific estimates
* version 1.3.7  24jan2024  matrix e(se_mg) returned with option mg
* version 1.3.6  17jan2024  bug fixed due to incompatibility of sortpreserve with reghdfe; matrix e(b_mg) returned with option mg
* version 1.3.5  11sep2023  option absorb() updated for version 6 of reghdfe
* version 1.3.4  13jul2023  bug fixed with option std in combination with option doubledefact
* version 1.3.3  11jul2023  stability condition for long-run impacts in estat impact corrected
* version 1.3.2  26may2025  bug fixed with too few time periods; stability condition for long-run impacts in estat impact corrected
* version 1.3.1  28feb2023  model stability checks implemented for estat impact; short-run impacts for lagged dependent variable in estat impact suppressed
* version 1.3.0  26feb2023  options tlags() and sptlags() added to spxtivdfreg; options lr and post added to estat impact
* version 1.2.5  03jan2023  varlist and option constant added to estat impact; Delta method standard errors implemented for estat impact
* version 1.2.4  22dec2022  postestimation command estat impact added for spxtivdfreg
* version 1.2.3  17dec2022  postestimation command predict added for spxtivdfreg
* version 1.2.2  04dec2022  option std added; suboption splags added to option iv() in spxtivdfreg; bug with option absorb() fixed
* version 1.2.1  28nov2022  various bugs fixed with spxtivdfreg; option spiv() integrated in option iv()
* version 1.2.0  21nov2022  command spxtivdfreg added
* version 1.1.0  01sep2022  speed improvements; bug fixed with large data sets
* version 1.0.3  12feb2021  Stata Journal version; suboption fvar() for option iv() added
* version 1.0.2  24jan2021  option fstep replaced by option fstage
* version 1.0.1  10oct2020  bug fixed with interaction terms as instruments
* version 1.0.0  06aug2020  available online at www.kripfganz.de
* version 0.3.1  04aug2020
* version 0.3.0  03aug2020
* version 0.2.3  24jul2020
* version 0.2.2  23jul2020
* version 0.2.1  22jul2020
* version 0.2.0  19jul2020
* version 0.1.1  01jul2020
* version 0.1.0  29jun2020
* version 0.0.3  19jun2020
* version 0.0.2  18jun2020
* version 0.0.1  17jun2020