/*
Changelog

v.1.0.0 -- 1 September 2023
(1) Original release.

v.2.0.0 -- 11 November 2023
(1) Option -endogenous()- is now used to specify the names of the endogenous variables if any of them are not the same as the auxiliary dependent variables and are instead functions of the auxiliary dependent variables.
(2) Original -endogenous- option renamed -auxiliary-; use this to specify the auxiliary dependent variables.
(3) Replay functionality: previous estimation output is replayed after inputting "ivcloglog".
(4) Modified output for improved clarity.
(5) Bug fixes and minor tweaks.

v.2.0.1 -- 17 November 2023
(1) Bug fix for when multiple endogenous variables are supplied with -endogenous()-
*/

*! Version 2.0.1 -- William Liu (刘威廉) -- 17 November 2023
*! ivcloglog -- Complementary log-log model with endogenous covariates (either continuous or functions of observed continuous covariates), instrumented via the control function approach (i.e., 2SRI)
*!
*! Syntax:
*! ivcloglog binary_outcome_var [controls] [if] [in], VHATname(string) AUXiliary(auxdep_vars = instruments[, NOCONstant]) [ENDOgenous(transformed_endo_vars) NOCONstant order(integer) vce(vcetype) NOGENerate DIFFicult_vce SHOWstages]

program ivcloglog, eclass byable(onecall)
	version 11											// FVvars and -_rmcoll- option -touse()- not introduced until v11.0

	qui ds												// Get original varlist
	local orig_varlist `r(varlist)'

	if _by() {
		local BY `"by `_byvars'`_byrc0':"'
	}

	`BY' _vce_parserun ivcloglog, noeqlist jkopts(eclass): `0'

	if "`s(exit)'" != "" {
		ereturn local cmdline `"ivcloglog `0'"'
		exit
	}

	if replay() {										// For replaying the results
		if `"`e(cmd)'"' != "ivcloglog" {
			error 301
		}
		else if _by() {
			error 190
		}
		else {
			ivcloglog_out `0'
			exit
		}
	}

	cap noi {											// To allow us to drop the cfvars
		`BY' Estimate `0'
	}
	local est_rc = _rc
	ereturn local cmdline `"ivcloglog `0'"'

	drop_cfvars `0' orig_varlist(`orig_varlist')		// Drop powers of vhat if requested with -nogenerate- (if they exist)

	if `est_rc' {										// To restore the original behavior -- with -capture noisily-, the error message is displayed but not the return code
		exit `est_rc'									// Note: -exit- with a return code only displays the error code; -error- with a return code also displays the corresponding generic error message
	}
end



program define Estimate, eclass byable(recall)
	syntax varlist(numeric fv ts) [if] [in]								///
								  [fweight iweight pweight aweight],	///
								  [										///
								  VHATname(string)						///
								  noCONstant							///
								  order(integer 1)						///
								  vce(string)							///
								  NOGENerate							///
								  DIFFicult_vce							///
								  SHOWstages							///
								  *										///
								  ]

	_get_diopts diopts options, `options'		// Note: the second argument must be provided anyway when there are non-display options

	if ("`weight'" != "") {
		local wgt [`weight'`exp']
	}

	if ("`vhatname'" == "") {
		local vhatname "vhat"
	}
	else if (strpos("`vhatname'", " ")) {
		di as err "option {bf:vhatname()} should only contain one word"
		exit 198
	}

	gettoken depvar exogvars : varlist
	_fv_check_depvar `depvar', k(1)				// Stops depvar being a factor variable
	local exogvars : list uniq exogvars			// Check for duplicates

	local nocon2 `constant'						// For better readability

	* Parse -auxiliary(...)- option
	parse_remaining_opt, exogvars(`exogvars') `options'
	local options `s(options)'
	local num_endo = `s(num_endo)'
	local num_aux = `s(num_aux)'

	local allinst `s(allinst)'
	local endovars `s(endovars)'
	local auxdepvars `s(auxdepvars)'
	local nocon1 `s(nocons1)'

	marksample touse
	_vce_parse `touse', optlist(Robust) argoptlist(CLuster) : , vce(`vce')			// Apart from parsing the -vce()- option, this will also check variables inside of it for missing values
	local clustervar "`r(cluster)'"
	markout `touse' `endovars' `allinst'

	* Check for "perfect predictors" of the outcome and collinearity amongst the regressors; also expand factor variables
	// Checking the exogvars once rather than twice would be more efficient. (Recall that they appear as instruments!)
	// Must check the second stage first since the number of observations can decrease if "perfect predictors" are present
	tempname omit1 omit2

	di as text "Checking second stage for perfect predictors of the outcome and regressor collinearity..."
	iden_check_and_fvexpand_2, lhs(`depvar') exog(`exogvars') endo(`endovars') touse(`touse') wgt(`wgt') `difficult_vce' `nocon2'
	if ("`exogvars'" != "") {
		local exogvars `r(exog)'
		matrix `omit2' = r(exog_omit)
		matrix `omit2' = `omit2', J(1, `=`num_aux'*`order' + `num_endo'', 1)		// Add elements for the endovars and residual powers
	}
	else {
		matrix `omit2' = J(1, `=`num_aux'*`order' + `num_endo'', 1)
	}

	di as text "Checking first stage for regressor collinearity..."
	iden_check_and_fvexpand_1, allinst(`allinst') touse(`touse') wgt(`wgt') `difficult_vce' `nocon1'
	local allinst `r(allinst)'
	matrix `omit1' = r(allinst_omit)

	* Initial values
	** First stage
	tempname F1 F2 F1_forone_eq F
	forval i = 1/`num_aux' {
		forval p = 1/`order' {
			local resid`i'_`p' `vhatname'`i'_`p'		// For better readability
			qui gen double `resid`i'_`p'' = .			// Previously, for some reason, initializing the higher powers with missing values didn't work when not using -from(...)-
			local resid`i'_powers = "`resid`i'_powers' `resid`i'_`p''"
		}

		local endovar`i' : word `i' of `auxdepvars'
		init_values_1 `endovar`i'' `allinst' if `touse' `wgt', `nocon1' predictvars(`resid`i'_powers') order(`order') `showstages'		// Store powers of vhat for auxdep variable `i' in local macro `resid`i'_powers'
		matrix `F1_forone_eq' = r(param_est)
		matrix `F1' = nullmat(`F1'), `F1_forone_eq'		// -nullmat()- relaxes the restriction that `A' must exist
		local yparam1 "`r(yparam)'"
		local yparam1_combined "`yparam1_combined' `yparam1'"

		local resid_combo "`resid_combo' `resid`i'_1'"							// vhats from all endo. variables
		forval p = 1/`order' {
			local resid_powers_combo "`resid_powers_combo' `resid`i'_`p''"		// Powers of the vhats from all endo. variables
		}

		local inst_opt1 "`inst_opt1' instruments(`endovar`i'':`allinst', `nocon1')"
	}

	** Second stage
	local 2ndstage_vars "`exogvars' `endovars' `resid_powers_combo'"
	init_values_2 `depvar' `2ndstage_vars' if `touse' `wgt', `nocon2' `showstages'
	local yparam2 "`r(yparam)'"
	local ll = `r(ll)'
	local ll_0 = `r(ll_0)'
	local rc = `r(rc)'
	local ic = `r(ic)'
	local converged = `r(converged)'

	matrix `F2' = r(param_est)
	matrix `F' = `F1', `F2'

	local inst_opt2 "instruments(`depvar': `2ndstage_vars', `nocon2')"

	* Calculate VCE
	// The undocumented option -iter(0)- prevents numerical iterations -- we only want the sandwich formula.
	// -haslfderivatives- differs from -hasderivatives- in that you don't need to specify each parameter, only the parameter groupings.
	// These are what I call the parameters that form linear combinations.
	// They are specified using the curly braces in the -parameter()- option or through residual equation names.
	tempname b V N k N_clust

	* -difficult_vce-
	if ("`difficult_vce'" != "") {
		if ("`clustervar'" == "") {
			di as err "only vcetype {bf:cluster} is allowed with option {bf:difficult_vce}"
			exit 198
		}

		forval eq = 1/`num_aux' {
			forval q = 1/`=`order'-1' {
				local resid_powerm1s_combo "`vhatname'`eq'_`q' `resid_powerm1s_combo'"
			}
		}

		mata: calc_vce("`depvar'", "`2ndstage_vars'", "`allinst'",		///
					"`resid_powerm1s_combo'",							///
					"`resid_combo'", `order',							///
					"`F'", "`touse'", "`clustervar'",					///
					"`nocon1'", "`nocon2'", "`V'",						///
					"`omit1'", "`omit2'")

		qui gmm gmm_ivcloglog if `touse', equations(`auxdepvars' `depvar')					///
											parameters("`yparam1_combined' `yparam2'")		///
											y1(`depvar') y2(`auxdepvars') 					///		// Wooldridge-style notation for this line; the 1 and 2 on other lines represent stages
											vhatname(`vhatname') order(`order')				///
											`inst_opt1' `inst_opt2'							///
											winitial(identity)								///
											hasderivatives fakederivatives					///
											onestep iter(0) from(`F')						///
											vce(`vce')										///
											valueid("EE criterion") `diopts'

		matrix `b' = e(b)
		matrix colnames `V' = `yparam1_combined' `yparam2'
		matrix rownames `V' = `yparam1_combined' `yparam2'

		ereturn repost b = `b' V = `V'
	}

	* Directly use GMM if no -difficult_vce- option
	else {
		qui gmm gmm_ivcloglog if `touse', equations(`auxdepvars' `depvar')					///
											parameters("`yparam1_combined' `yparam2'")		///
											y1(`depvar') y2(`auxdepvars') 					///		// Wooldridge-style notation for this line; the 1 and 2 on other lines represent stages
											vhatname(`vhatname') order(`order')				///
											`inst_opt1' `inst_opt2'							///
											winitial(identity)								///
											haslfderivatives onestep iter(0) from(`F')		///
											vce(`vce')										///
											valueid("EE criterion") `diopts'
	}

	* -ereturn- stuff and display results
	// Note: in versions < 14 of Stata, -gmm- can count the number of parameters incorrectly,
	// failing to exclude omission-flagged variables.
	matrix `b' = e(b)
	matrix `V' = e(V)
	scalar `N' = e(N)
	scalar `k' = e(n_moments)					// Equals number of parameters but is not incorrectly counted in versions 14 of Stata
	scalar `N_clust' = e(N_clust)
	local clustvar `e(clustvar)'
	local e_vce "`e(vce)'"						// Named this way as a reminder that this is different from `vce'
	local vcetype "`e(vcetype)'"

	ereturn post `b' `V' /*`wgt'*/, depname(`depvar') o(`=`N'') esample(`touse') buildfvinfo

	// Macros (apart from those from -ereturn post-)
	ereturn hidden local vcetype "`vcetype'"
	ereturn local clustvar `clustvar'
	ereturn local vce "`e_vce'"

	if ("`nocon1'" == "") {
		ereturn local inst `allinst' _con
	}
	else {
		ereturn local inst `allinst'
	}
	ereturn local auxdep `auxdepvars'
	ereturn local endog `endovars'
	if ("`nocon2'" == "") {
		ereturn local exog `exogvars' _con
	}
	else {
		ereturn local exog `exogvars'
	}
	ereturn local depvar `depvar'
	ereturn local cmd "ivcloglog"

	// Scalars
	ereturn scalar k = `k'
	ereturn scalar endog_ct = `num_endo'
	ereturn scalar auxdep_ct = `num_aux'
	ereturn scalar N_clust = `N_clust'

	foreach var in `exogvars' `endovars' {
		local temp "`temp' [`depvar']`var'"
	}
	qui test `temp'								// Wald test for second stage, excluding constant (if it exists) and control function
	ereturn scalar chi2 = r(chi2)
	ereturn scalar df_m = r(df)
	ereturn scalar p = chiprob(r(df), r(chi2))

	ereturn scalar ll = `ll'
	ereturn scalar ll_0 = `ll_0'
	_post_vce_rank

	ereturn scalar ic = `ic'					// From -cloglog-
	ereturn scalar rc = `rc'					// From -cloglog-
	ereturn scalar converged = `converged'		// From -cloglog-

	ivcloglog_out, `diopts'
end



program parse_remaining_opt, sclass		// Parses -auxiliary()- and also checks for remaining options (should be none)
	syntax [, exogvars(varlist fv ts) AUXiliary(string) ENDOgenous(varlist fv ts) *]

	* Check for remaining options (should be none)
	if ("`options'"!="") {
		di as err "option(s) {bf:`options'} not allowed"
		exit 198
	}

	* Parse contents of -auxiliary()-
	gettoken auxiliary temp : auxiliary, parse(",")			// Delimiter is stored in `temp' (which is not str.) and is then deleted
	local options = substr("`temp'", 2, .)
	parse_subopt_noconstant, `options'
	local nocons1 `r(constant)'								// Don't directly store `options' in `nocons1' since the former can be abbreviated

	gettoken auxdepvars temp : auxiliary, parse("=")
	local auxdepvars : list uniq auxdepvars					// Check for duplicates
	local extinst = substr("`temp'", 2, .)
	local extinst : list uniq extinst						// Check for duplicates
	local extinst : list extinst - exogvars					// Check for exogvars being explicitly added as instruments

	* Count and parse auxiliary dependent variables
	local num_aux : list sizeof auxdepvars
	forval i = 1/`num_aux' {
		local temp : word `i' of `auxdepvars'				// Format is auxiliary(auxdepvar_1 ... auxdepvar_k = ...)
		_fv_check_depvar `temp', k(1)						// Stops the dependent variables in the auxiliary equations being factor variables
	}

	local num_extinst : list sizeof extinst
	if (`num_aux' > `num_extinst') {
		di as err "equation not identified; must have at least as many instruments not in the regression as there are instrumented variables"
		exit 481
	}

	if ("`endogenous'" == "") {
		local endogenous "`auxdepvars'"
		local num_endo = `num_aux'							// If option -endogenous()- not supplied, assume that the auxdepvars are the endovars
	}
	else {
		local endogenous : list uniq endogenous				// Check for duplicates
		local num_endo : word count `endogenous'			// Should not have quotes otherwise will return 1
	}

	sreturn local allinst "`exogvars' `extinst'"
	sreturn local endovars "`endogenous'"
	sreturn local auxdepvars "`auxdepvars'"
	sreturn local nocons1 "`nocons1'"

	sreturn local num_endo `num_endo'
	sreturn local num_aux `num_aux'
	sreturn local options `options'
end

program define parse_subopt_noconstant, rclass
	syntax [anything], [noCONstant * ]
	if ("`options'"!="") {
		di as err "suboption(s) {bf:`options'} not allowed in option {bf:auxiliary()}"
		exit 198
	}

	return local constant "`constant'"
end



program define iden_check_and_fvexpand_1, rclass
	syntax, allinst(varlist fv ts) touse(name) [wgt(name) DIFFicult_vce noCONstant]

	* Auxiliary model
	cap noi _rmcoll `allinst' if `touse', expand `constant' noskipline		// Note: all instruments are shared between the auxiliary models
	if _rc {
		error _rc
	}

	local vlist "`r(varlist)'"

	* Recording the position of variables flagged for omission
	// Constants are never flagged for omission. Since we focus on the variables that are flagged here, we don't need to worry about constants.
	tempname temp allinst_omit

	// "`allinst'" can obviously never be an empty string, so we do not need to worry about the problem explained below
	matrix `temp' = J(1, wordcount("`vlist'"), 1)
	matrix colnames `temp' = `vlist'
	_ms_omit_info `temp'
	matrix `allinst_omit' = `temp' - r(omit)

	return local allinst "`vlist'"
	return matrix allinst_omit = `allinst_omit'
end

program define iden_check_and_fvexpand_2, rclass
	syntax, [lhs(varname ts) exog(varlist fv ts) endo(varlist ts) touse(name) wgt(name) DIFFicult_vce noCONstant]

	* Primary model
	cap noi _rmcoll `lhs' `endo' `exog' if `touse', touse(`touse') logit expand `constant' noskipline
	if _rc {						// Using c(rc), which means `=_rc', also works
		error _rc
	}

	// Note: putting the exogenous variables at the end instead prioritizes them for omission.
	// -logit- is an undocumented option of -_rmcoll-. Like ivprobit.ado, I use it here too.
	// I use -_rmcoll, logit- instead of -_rmdcoll- and -_rmcoll- separately because -_rmdcoll- does not allow the option -touse()-.

	* Extract the exogenous controls post-flagging
	local vlist "`r(varlist)'"			// `r(varlist)' is technically not a macro but is usually (not always) treated as such by Stata. Verbose here for safety just in case.
	gettoken lhs vlist : vlist
	extract_varlist, small(`endo') big(`vlist')
	local endo `r(small)'
	local vlist `r(big)'
	local exog : copy local vlist

	* Disallow dropping endogenous variables
	checkifdropped, vars(`endo')

	* Recording the position of variables flagged for omission
	// Constants are never flagged for omission. Since we focus on the variables that are flagged here, we don't need to worry about constants.
	tempname temp exog_omit

	// Annoyingly, void matrices are allowed in Mata but not Stata, so I need to work around this.
	if ("`exog'" != "") {
		matrix `temp' = J(1, wordcount("`exog'"), 1)		// Annoyingly, -_ms_omit_info- only works with matrices
		matrix colnames `temp' = `exog'
		_ms_omit_info `temp'
		matrix `exog_omit' = `temp' - r(omit)				// r(omit) from -_ms_omit_info- uses 1 to represent omission; we want it the other way round for Mata
	}

	return local exog "`exog'"
	if ("`exog'" != "") {
		return matrix exog_omit = `exog_omit'
	}
end

program define extract_varlist, rclass
	syntax, small(string) big(string)

	local n : list sizeof small
	local small							// Clear this local macro since it is not empty
	forval i = 1/`n' {
		gettoken v big : big
		local small `small' `v'
	}

	return local small `small'
	return local big `big'
end

program define checkifdropped		// Adapted from ivprobit.ado
	syntax, vars(string)

	while "`vars'" != "" {
		gettoken var vars : vars
		_ms_parse_parts `var'			// Parses the matrix stripe `var' and returns the token parts in r()

		if r(omit) {					// From _ms_parse_parts
			di as err "tried to drop an endogenous variable, which is not allowed"
			exit 498
		}
	}
end



program define init_values_1, rclass		// First stage
	syntax [anything][if][in]							///
					 [fweight iweight pweight aweight], ///
					 [									///
					 noCONstant 						///
					 predictvars(string)				///
					 order(integer 1)					///
					 SHOWstages							///
					 ]

		marksample touse
		if ("`weight'" != "") {
			local wgt [`weight'`exp']
		}

		tempname param_est
		if ("`showstages'" == "") {
			local qui quietly
		}
		`qui' regress `anything' if `touse' `wgt', `constant'

		matrix `param_est' = e(b)		// e(b) from -regress- DOES NOT have the format "depvar: ...", so we must change the format
		reformat_params					// (Renaming the columns is unnecessary because -from()- extracts positionally and ignores names.)
		local yparam "`r(yparam)'"

		tempvar prediction
		qui predict double `prediction' if `touse', resid
		forval p = 1/`order' {
			local predictvar_`p' : word `p' of `predictvars'
			qui replace `predictvar_`p'' = `prediction'^`p'
		}

		return matrix param_est = `param_est'
		return local yparam "`yparam'"
end

program define init_values_2, rclass		// Second stage
	syntax [anything][if][in]							///
					 [fweight iweight pweight aweight], ///
					 [									///
					 noCONstant 						///
					 SHOWstages							///
					 ]

		marksample touse
		if ("`weight'" != "") {
			local wgt [`weight'`exp']
		}

		tempname param_est
		if ("`showstages'" == "") {
			local qui quietly
		}
		`qui' cloglog `anything' if `touse' `wgt', `constant'

		matrix `param_est' = e(b)		// e(b) from -cloglog- DOES have the format "depvar: ..."
		local yparam: colfullnames e(b)

		return matrix param_est = `param_est'
		return local yparam "`yparam'"
		return local ll = e(ll)
		return local ll_0 = e(ll_0)
		return local ic = e(ic)
		return local rc = e(rc)
		return local converged = e(converged)
end

program define reformat_params, rclass
	syntax [anything], [subtract(string)]

	local paramy: colfullnames e(b)
	local yvar "`e(depvar)'"

	foreach var in `paramy' {
		local yparam "`yparam' `yvar':`var'"
	}

	return local yparam "`yparam'"
end



mata:
//% Subroutine to get VCE
void calc_vce(string scalar y, string scalar x, string scalar z,							///
				string scalar resid_powerm1s_combo,											///
				string scalar resid_combo, real scalar order,								///
				string scalar point_est, string scalar touse, string scalar clustervar,		///
				string scalar nocon1, string scalar nocon2, string scalar vcename,			///
				string scalar omit1, string scalar omit2)
{	//% Type declarations (what types these objects *initially* are)
	real matrix temp_mat, X, Z, R1, info, g1_c, g2_c, g_c, Omega, vhat_powerm1s_combo_rump, vhat_powerm1s_eq, H_OLS, G_11, G_21_eq, G_21, G_22, V
	real rowvector X_omit, Z_omit,  multiZ_omit, ZX_omit, temp_b, b, rho_rump, rho_eq
	real colvector y1, Xb, expXb, r1, r2, cvar, d, d_tilde_eq
	real scalar n, num_endo, k_multiZ, k_multiZ_orig, nc, k_X, k_ZX, eq, orderm1

	//% Set up matrices (representing the data)
	// Note: st_data ignores factor variables with any omitted levels.
	// It treats all levels as omitted, even ones with the omission operator "o", setting all their columns to zero!
	st_view(y1=., ., y, touse)					// Used "y" rather than "y1" as argument to avoid conflict

	st_view(temp_mat=., ., x, touse)			// Need "temp" because identical arguments not allowed
	X_omit = st_matrix(omit2)
	st_select(X, temp_mat, X_omit)

	st_view(temp_mat=., ., z, touse)
	Z_omit = st_matrix(omit1)
	st_select(Z, temp_mat, Z_omit)

	st_view(R1=., ., resid_combo, touse)		// Matrix of auxiliary model residuals

	n = rows(X)									// All variables should have the same number of observations, conditioning on `touse'

	// Add vector of ones to X and Z if constant requested.
	// cross()/quadcross() can automatically do this for you, but this way is more readable.
	if (nocon1 == "") {
		Z = Z, J(n, 1, 1)
		Z_omit = (Z_omit, 1)
	}

	if (nocon2 == "") {
		X = X, J(n, 1, 1)
		X_omit = (X_omit, 1)
	}



	//% Set up vectors (representing parameter estimates)
	// Need to transpose because the estimates are inputted as `F', a row vector
	num_endo = cols(R1)
	k_multiZ = cols(Z) * num_endo					// Defined in a SUR style; each instrument is counted once per eq. This makes intuitive sense if you consider having different instruments for each eq.
	k_multiZ_orig = cols(Z_omit) * num_endo
	temp_b = st_matrix(point_est)[|k_multiZ_orig+1 \ .|]
	st_select(b, temp_b, X_omit)
	b = b'
	// Reminder: range subscripts are faster than list subscripts for subsetting a contiguous matrix (larger than 1x1)
	// Reminder: "..." is not allowed for Mata matrix/vector subsetting



	//% Omega (meat in the sandwich formula)
	// We need to average the moment functions within each cluster to get the monent functions
	st_view(cvar=., ., clustervar, touse)
	info = panelsetup(cvar, 1)											// Note: the other data must be in the same order as clustervar (which is true by default)
	nc = rows(info)
	k_X = cols(X)
	k_ZX = k_multiZ + k_X

	// Primary model, Part 1
	Xb = X*b
	expXb = exp(Xb)
	r2 = mm_cond(y1, exp(Xb - expXb) :/ (-expm1(-expXb)), -expXb)		// Not true residuals but pseudo-residuals

	// Make by-cluster superobservations and then we can get the "robust SE"-style Gram matrix
	g1_c = J(nc, 0, 0)
	for (eq=1; eq<=num_endo; eq++) {
		r1 = R1[., eq]								// Residuals corresponding to auxiliary equation #eq
		g1_c = (g1_c, panelsum(Z, r1, info))		// "c" here means summed within cluster (with R1 as the weights)
	}
	g2_c = panelsum(X, r2, info)
	g_c = (g1_c, g2_c)
	Omega = quadcross(g_c, g_c)



	//% G_inv (bread in the sandwich formula); G = (G_11, G_12 \ G_21, G_22)
	//% G_11
	H_OLS = -quadcross(Z, Z)		// Minus sign in this Hessian comes from maximizing the negative of the OLS objective function
	G_11 = I(num_endo) # H_OLS		// Used Kronecker product operator; could probably be optimized by using a loop with blockdiag() instead

	//% G_12 is just a zero matrix

	//% G_21
	d = mm_cond(y1,														///
				r2 :* (expm1(Xb) + exp(-expXb)) :/ expm1(-expXb),		///
				-expXb)
	// d is a vector of values "C_i", defined in the gmm moment evaluator function.
	// I changed the notation here to avoid confusion since "c" was already used above.

	if (order > 1) {
		st_view(vhat_powerm1s_combo_rump, ., resid_powerm1s_combo, touse)					// This contain the rest when we partition off a subvector from the front (like -gettoken-)
	}

	orderm1 = order - 1

	// rho_rump is defined similarly to the above. (Also, order*num_endo is the size of rho)
	if (nocon2 == "") {
		rho_rump = b[|k_X-order*num_endo \ k_X-1|]
	}
	else {
		rho_rump = b[|k_X-order*num_endo+1 \ .|]
	}

	G_21 = J(k_X, 0, .)																		// Initialize as zero-column matrix so it can be used in the loop
	for (eq=1; eq<=num_endo; eq++) {
		if (order > 1) {
			vhat_powerm1s_eq = vhat_powerm1s_combo_rump[|1, 1 \ ., orderm1|]
			if (eq < num_endo) {
				vhat_powerm1s_combo_rump = vhat_powerm1s_combo_rump[|1, order \ ., .|]		// Only take the rest if that actually exists
			}
			vhat_powerm1s_eq = J(n, 1, 1), vhat_powerm1s_eq									// v^(p-1), p = 1, ...
		}
		else {
			vhat_powerm1s_eq = J(n, 1, 1)
		}

		rho_eq = rho_rump[|1 \ order|]
		if (eq < num_endo) {
			rho_rump = rho_rump[|order+1 \ .|]												// Only take the rest if that actually exists
		}
		d_tilde_eq = -d :* (vhat_powerm1s_eq * rho_eq)
		G_21_eq = quadcross(X, d_tilde_eq, Z)
		G_21 = (G_21, G_21_eq)
	}

	//% G_22
	G_22 = quadcross(X, d, X)



	//% V
	// I use -lusolve()- rather than actually inverting G because this is more efficient.
	// In addition, I use 0 tolerance because we already checked for collinearity with -_rmcoll-.
	// Changing the tolerance is important because the data matrix might pass the -_rmcoll- collinearity check but
	// fail the built-in collinearity check in -lusolve()-.
	V = lusolve_special(G_11, G_21, G_22, lusolve_special(G_11, G_21, G_22, Omega, 0)', 0)		// Would be more efficient to reuse the L and U instead of doing the same LU decomposition twice

	// This V is currently actually only the VCE for the non-omission-flagged variables
	multiZ_omit = J(1, 0, .)
	for (eq=1; eq<=num_endo; eq++) {
		multiZ_omit = (multiZ_omit, Z_omit)
	}
	ZX_omit = (multiZ_omit, X_omit)
	V = insert_col_cons(V, ZX_omit, 0)
	V = insert_col_cons(V', ZX_omit, 0)
	V = makesymmetric(V)
	st_matrix(vcename, V)
}

//% Subroutine similar to -lusolve()- that exploits the block lower triangular nature of G
real matrix lusolve_special(real matrix A1, real matrix A3, real matrix A4,		///
							real matrix B, real scalar tolerance)
{
	//% Type declarations
	real matrix B1, B2, B3, B4, X1, X2, X3, X4, X
	real scalar m1, n1

	//% Main code
	m1 = rows(A1)
	n1 = cols(A3)

	B1 = B[|1, 1 \ m1, n1|]
	B2 = B[|1, n1+1 \ m1, .|]
	B3 = B[|m1+1, 1 \ ., n1|]
	B4 = B[|m1+1, n1+1 \ ., .|]

	X1 = lusolve(A1, B1, tolerance)
	X2 = lusolve(A1, B2, tolerance)
	X3 = lusolve(A4, B3-A3*X1, tolerance)
	X4 = lusolve(A4, B4-A3*X2, tolerance)
	X = (X1, X2 \ X3, X4)
	return(X)
}

//% Subroutine that inserts columns of a constant in a matrix corresponding to the 0s in a specified vector
real matrix insert_col_cons(real matrix M, real matrix v, real scalar cons)
{
	//% Type declarations
	real matrix R, temp
	real scalar end_num_cols, num_rows, i

	//% Main code
	end_num_cols = cols(v)
	num_rows = rows(M)

	R = J(num_rows, end_num_cols, cons)						// Easier to start with matrix of constants with final dimensions than to "expand" the original matrix
	temp = M, J(num_rows, 1, 0)								// Hacky buffer to prevent the loop from breaking at the last column of M

	for (i=1; i<=end_num_cols; i++) {
		if (v[i] == 1) {
			R[., i] = temp[., 1]
			temp = temp[|1, 2 \ ., .|]
		}
	}

	return(R)
}
end



program ivcloglog_out		// Modified version of -GMMout- from gmm.ado
	syntax [, level(cilevel) COEFLegend selegend *]

	_get_diopts diopts, `coeflegend' `selegend' `options'

	local EXPRESSION = 1
	local PROGRAM = 2
	local robust
	local hac
	local bs
	if bsubstr("`e(vcetype)'", 1, 6) == "Robust" | "`e(clustvar)'" != "" {
			local robust "yes"
	}

	if bsubstr(`"`e(vcetype)'"', 1, 9) == "Bootstrap" |		///
			bsubstr(`"`e(vcetype)'"', 1, 9) == "Jackknife" {
			local bs "yes"
	}

	local anyrobust 0

	display ""
	display "{bf:Two-step cloglog model with endogenous covariates}"
	display ""

	tempname left right
	.`left' = {}
	.`right' = {}
	local C1 "_col(1)"
	local C2 "_col(21)"
	local C3 "_col(43)"
	local C4 "_col(64)"

	.`left'.Arrpush `C1' as text "2nd-stage Wald chi2" `C2' "=" as res %14.2f e(chi2)
	.`left'.Arrpush `C1' as text "2nd-stage Wald DF" `C2' "=" as res %14.0fc e(df_m)
	.`left'.Arrpush `C1' as text "Prob > chi2" `C2' "=" as res %14.4f e(p)

	.`right'.Arrpush `C3' as text "Number of parameters" `C4' "=" as res %14.0fc e(k)
	.`right'.Arrpush `C3' as text "Number of obs" `C4' "=" as res %14.0fc e(N)
	.`right'.Arrpush `C3' as text "Log-likelihood" `C4' "=" as res %14.2f e(ll)

	local nl = `.`left'.arrnels'
	local nr = `.`right'.arrnels'
	local k = max(`nl', `nr')
	forvalues i = 1/`k' {
			di as text `.`left'[`i']' as text `.`right'[`i']'
	}

	di

	_coef_table, level(`level') `diopts'		// Stealthily uses e(vcetype), e(N_clust), and e(clustvar)
	di in smcl "{p 0 4 4}{txt}Instruments: {res}`e(inst)'{p_end}"
	ml_footnote
end

program define drop_cfvars
	syntax anything, orig_varlist(varlist) [NOGENerate *]

	if "`nogenerate'" != "" {
		keep `orig_varlist'
		// Yes, -nogenerate- is a misnomer because we do actually generate the powers of vhat. But, to be fair, that's also true for Stata tempvars.
		// We do this because it's easier than renaming the columns of ("column-restriping") the matrix holding the coefficient estimates,
		// which is necessary to make the powers of vhat be called "`vhatname'`i'_`p'".
	}
end