*! inorm.ado JCG 05jul2007
* Note: read INORM ado files with tab set equal to 6 spaces
*--------------------------------------------------------------------------------------------------------
* program inorm, rclass
* syntax: inorm cmd cmdline
* where cmd is em or da.
*--------------------------------------------------------------------------------------------------------
program define inorm, rclass
	version 9
	set more off					/* shall remain off for all commands */

	* EXTRACT CMD FROM COMMAND LINE
	gettoken cmd 0 : 0				/* extract command */

	* EXTRACT IRAND OPTION FROM COMMAND LINE AND LOAD IRAND PLUGIN, IF NECESSARY
	syntax [varlist] [if] [in] [using/] [, IRAND * ]
	if "`irand'"!="" {
		cap irandpi load				/* load irand plugin, if it exists */
		if ( _rc==199 | _rc==601 ) {
			display as error "irand plugin not found"
			exit 198
		}
	}

	* EXPLICITLY DROP AND RELOAD INORM PLUGIN, IF IT EXISTS
	* (THIS IS TO OVERCOME A SUBTLE PROBLEM WITH NORM3)
	if ( "`cmd'"=="em" | "`cmd'"=="da" ) {
		cap program drop inormpi		/* this will explicity drop the plugin */
		cap inormpi load				/* load plugin, if it exists */
		if ( _rc==199 | _rc==601 ) {		/* if inormpi.ad or inormdll.plugin do not exist */
			local hasplugin "hasplugin(-1)"
		}
		else {
			local hasplugin "hasplugin(0)"
		}
		if ( `"`irand'"'=="" & `"`options'"'=="" ) {
			local hasplugin ", `hasplugin'"
		}
	}

	* PROCESS CMD
	if ( "`cmd'"=="em" | "`cmd'"=="da" ) {
		if ( "`cmd'" == "em" ) {
			em `0' `hasplugin'
			return add
		}
		else {
			da `0' `hasplugin'
			return add
		}
	}
	else {
		display as error "subcommand em or da expected"
		exit 198
	}
end
*--------------------------------------------------------------------------------------------------------
* subprogram em
*--------------------------------------------------------------------------------------------------------
program define em, rclass
	version 9
	syntax varlist [if] [in], [		///
		Xvars(varlist)			/// fully observed covariates
		MAXits(integer 1000)		/// aborts after this number of iterations (if convergence fails)
		CRIterion(real 0.000001)	///
		RIDge(real 0.0)			///
		MU(string)				/// the name of a Stata matrix containing starting values
		SIGma(string)			/// the name of a Stata matrix containing starting values
		Echo					/// echos log likelihood values to the screen
		MATA					/// use Mata version of norm
		CASEORDER				/// for debuggin (generate caseorder variable to pass Mata da)
		Dump					/// for debugging
		DUMPAT(integer 1)			/// for debugging
		HASPlugin(integer -1)		/// for debugging
	]
	marksample touse, novarlist

	* CAPTURE VALUE OF MU OPTION
	if ( `"`mu'"' != "" ) local beta = "`mu'"

	* CHECK ARGUMENTS
	if ( `maxits' <= 0 ) {
		display as error "maxits option must be a positive integer value"
		exit 198
	}
	if ( `criterion' <= 0 ) {
		display as error "criterion option must be positive"
		exit 198
	}
	if ( `ridge' < 0 ) {
		display as error "ridge option must be positive"
		exit 198
	}
	if ( "`beta'" != "" ) {
		capture confirm matrix `beta'
		if _rc!=0 {
			display as error "matrix `beta' not found"
			exit 198
		}
		matrix INORM_beta = `beta'
		local usebeta = "usebeta"
	}
	if ( "`sigma'" != "" ) {
		capture confirm matrix `sigma'
		if _rc!=0 {
			display as error "matrix `sigma' not found"
			exit 198
		}
		matrix INORM_sigma = `sigma'
		local usesigma = "usesigma"
	}
	foreach yvar of varlist `varlist' {
		capture confirm numeric variable `yvar'
		if ( _rc != 0 ) {
			display as error "the variable `yvar' is not numeric""
			exit 198
		}

	}
	if ( "`xvars'" != "" ) {
		foreach xvar of varlist `xvars' {
			capture confirm numeric variable `xvar'
			if ( _rc != 0 ) {
				display as error "the variable `xvar' is not numeric"
				exit 198
			}
			capture assert ( `xvar' < . | `touse' == 0 )
			if _rc!=0 {
				display ///
					as error "the variable `xvar' has missing values; " ///
					as error " the xvars covariates must be fully observed"
				exit 198
			}
		}
	}

	* GENERATE ALL ONES VARIABLE
	tempvar allones
	qui gen byte `allones'=1
	local xvarnames "_cons `xvars'"
	local xvars "`allones' `xvars'"

	* DO EM
	display as text "Maximum-likelihood estimation for multivariate normal data with missing values"

	* RUN EM USING DLL IF PLUGIN IS AVAILABLE AND IF MATA OPTION WAS NOT SPECIFIED
	local rc=0
	if ( `hasplugin' == 0 & "`mata'" == "" ) {
		display as text "(using plugin)"

		* PASS PARAMETERS TO PLUGIN VIA SCALARS AND MATRICES
		local noofyvars : word count `varlist'
		scalar INORM_noofyvars = `noofyvars'
		local noofxvars : word count `xvars'
		scalar INORM_noofxvars = `noofxvars'
		qui count if `touse'==1
		scalar INORM_noofobs = r(N)
		quietly count
		scalar INORM_nobs = r(N)
		scalar INORM_max_iter = `maxits'
		scalar INORM_criterion = `criterion'
		scalar INORM_ridge = `ridge'
		scalar INORM_useridge = 0
		scalar INORM_dumpat=`dumpat'
		scalar INORM_da = 0
		scalar INORM_echo = 0
		scalar INORM_dump = 0
		scalar INORM_usebeta = 0
		scalar INORM_usesigma = 0
		scalar INORM_gen_case_order = 0
		if ( `ridge' > 0 ) scalar INORM_useridge = 1
		if ( "`echo'"!="" ) scalar INORM_echo = 1
		if ( "`dump'"!="" ) scalar INORM_dump = 1
		if ( "`beta'"!="" ) scalar INORM_usebeta = 1
		else matrix INORM_beta = J( INORM_noofxvars, INORM_noofyvars, 0 )
		if ( "`sigma'"!="" ) scalar INORM_usesigma = 1
		else matrix INORM_sigma = J( INORM_noofyvars, INORM_noofyvars, 0 )
		if ( "`caseorder'"!="" ) {
			scalar INORM_gen_case_order = 1
			cap drop INORM_case_order
			qui gen double INORM_case_order = .
			local caseorder "INORM_case_order"
		}

		* CALL PLUGIN
		cap noi inormpi call `varlist' `xvars' `touse' `caseorder'
		local rc=_rc

		* CLEAN UP PARAMETERS
		scalar drop INORM_noofyvars
		scalar drop INORM_noofxvars
		scalar drop INORM_noofobs
		scalar drop INORM_nobs
		scalar drop INORM_max_iter
		scalar drop INORM_criterion
		scalar drop INORM_ridge
		scalar drop INORM_useridge
		scalar drop INORM_dumpat
		scalar drop INORM_da
		scalar drop INORM_echo
		scalar drop INORM_dump
		scalar drop INORM_usebeta
		scalar drop INORM_usesigma
		scalar drop INORM_gen_case_order
	}
	* ELSE RUN EM IN MATA
	else {
		display as text "(using mata)"
		mata: mata clear
		mata: run_norm_engine_em( ///
			"`xvars'", "`varlist'", "`touse'", `maxits', `criterion', ///
			`ridge', "`usebeta'", "`usesigma'", "`echo'", "`dump'", `dumpat')
	}

	* IF SUCCESSFUL
	if ( `rc' == 0 ) {
		* DISPLAY RESULTS
		if INORM_converged==1 local converged="true"
		else local converged="false"
		display as text "Converged:      " as result "`converged'"
		display as text "Iterations:     " as result INORM_iterations
		display as text "Log-likelihood: " as result INORM_loglik

		* RETURN RESULTS
		return scalar converged = INORM_converged
		return scalar iterations = INORM_iterations
		return scalar ll = INORM_loglik
		matrix colnames INORM_beta = `varlist'
		matrix rownames INORM_beta = `xvarnames'
		matrix colnames INORM_sigma = `varlist'
		matrix rownames INORM_sigma = `varlist'
		matrix rho = corr(INORM_sigma)
		return matrix rho = rho
		return matrix sigma = INORM_sigma
		return matrix mu = INORM_beta
	}

	* CLEAN UP SCALARS AND MATRICES
	cap scalar drop INORM_converged
	cap scalar drop INORM_iterations
	cap scalar drop INORM_loglik
	cap matrix drop INORM_beta
	cap matrix drop INORM_sigma

	exit `rc'
end
*--------------------------------------------------------------------------------------------------------
* subprogram da
*--------------------------------------------------------------------------------------------------------
program define da, rclass
	version 9

	* PRESERVE CURRENT DATASET
	preserve

	syntax varlist [if] [in] using/, [	///
		Xvars(varlist)			/// Fully observed covariates
		M(integer 2)			/// Number of imputed datasets to create
		BURNin(integer 0)			/// Number of iterations to execute first
		ITs(integer 50)			/// Number of iterations between draws of imputed datasets
		RIDge(real 0.0)			/// Ridge prior distribution value
		MU(string)				/// Starting mean vector values
		SIGma(string)			/// Starting covariance matrix values
		SEED1(integer 123)		/// First seed for random number generator
		SEED2(integer 459)		/// Second seed for random number generator
		REPLACE				/// Allows the using file to be overwritten
		MATA					/// Forces use of mata version of da
		Dump					/// For debugging
		DUMPIMP(integer 1)		/// For debugging
		DUMPAT(integer 1)			/// For debugging
		HASPlugin(integer -1)		/// For debugging
		CASEORDER				/// For debugging. Instructs Mata version of da to use the
							/// case order from the INORM_case_order variable generated
							/// by a call to the plugin version of em
		IRAND					/// For debugging. Instructs mata version of da to use the
							/// irand plugin random number generator instead of the Mata
							/// version
	]

	marksample touse, novarlist
	tempvar imps				// Used by mata to pass imputations back to Stata
	gen `imps'=`touse'

	* CAPTURE VALUE OF M AND MU OPTIONS
	local MI_m = `m'
	if ( `"`mu'"' != "" ) local beta = "`mu'"

	* CHECK ARGUMENTS
	if ( `burnin' < 0 ) {
		display as error "burnin option must be positive"
		exit 198
	}
	if ( `m' <= 0 ) {
		display as error "m option must be a positive integer value"
		exit 198
	}
	if ( `its' <= 0 ) {
		display as error "iter option must be a positive integer value"
		exit 198
	}
	if ( `ridge' < 0 ) {
		display as error "ridge option must be positive"
		exit 198
	}
	if ( "`beta'" != "" ) {
		capture confirm matrix `beta'
		if ( _rc != 0 ) {
			display as error "matrix `beta' not found"
			exit 198
		}
		matrix INORM_beta = `beta'
	}
	if ( "`sigma'" != "" ) {
		capture confirm matrix `sigma'
		if ( _rc != 0 ) {
			display as error "matrix `sigma' not found"
			exit 198
		}
		matrix INORM_sigma = `sigma'
	}
	if ( "`caseorder'" != "" ) {
		if ( `hasplugin'==0 & "`mata'"=="" ) {
			display as error "caseorder option not valid with plugin version of da; specify mata option"
			exit 198
		}
		capture confirm variable INORM_case_order
		if ( _rc != 0 ) {
			display as error "Case order variable INORM_case_order not found; rerun em with caseorder option"
			exit 198
		}
	}
	foreach yvar of varlist `varlist' {
		capture confirm numeric variable `yvar'
		if ( _rc != 0 ) {
			display as error "the variable `yvar' is not numeric""
			exit 198
		}
		local `yvar'type : type `yvar'
		if ( `"``yvar'type'"' != "double" & `"``yvar'type'"' != "float" ) {
			cap recast double `yvar'
			local rc = _rc
			if ( `rc' != 0 ) {
				display ///
					as error "unable to recast `yvar' to double; " ///
					as error "try {help set memory} to increase available memory"
				exit `rc'
			}
		}
	}
	if ( "`xvars'" != "" ) {
		foreach xvar of varlist `xvars' {
			capture confirm numeric variable `xvar'
			if ( _rc != 0 ) {
				display as error "the variable `xvar' is not numeric"
				exit 198
			}
			capture assert ( `xvar' < . | `touse' == 0 )
			if ( _rc != 0 ) {
				display ///
					as error "the variable `xvar' has missing values; " ///
					as error "the xvars covariates must be fully observed"
				exit 198
			}
		}
	}
	if ( "`replace'" != "" ) local replace ", `replace'"

	* MAKE SPACE FOR IMPUTED COMPIES OF THE DATA
	capture drop _mj
	capture drop _mi
	quietly generate byte _mi = .
	quietly replace _mi = _n
	tempfile t
	quietly save `t', replace
	capture drop _mj
	quietly generate byte _mj=0
	forvalues i=1/`MI_m' {
		quietly append using `t'
		quietly replace _mj=`i' if _mj>=.
	}
	quietly replace `touse'=0 if _mj>0
	quietly replace `imps'=0 if _mj==0

	* GENERATE ALL ONES VARIABLE
	tempvar allones
	qui gen byte `allones'=1
	local xvarnames "_cons `xvars'"
	local xvars "`allones' `xvars'"

	* DO DA
	display as text "Imputation by data-augmentation for multivariate normal data with missing values"

	* RUN DA USING DLL IF PLUGIN IS AVAILABLE AND IF MATA OPTION WAS NOT SPECIFIED
	local rc=0
	if ( `hasplugin'==0 & "`mata'"=="" ) {
		display as text "(using plugin)"

		* PASS PARAMETERS TO PLUGIN VIA SCALARS AND MATRICES
		local noofyvars : word count `varlist'
		scalar INORM_noofyvars = `noofyvars'
		local noofxvars : word count `xvars'
		scalar INORM_noofxvars = `noofxvars'
		qui count if `touse'==1
		scalar INORM_noofobs = r(N)
		quietly count if _mj==0
		scalar INORM_nobs = r(N)
		scalar INORM_m = `MI_m'
		scalar INORM_ridge = `ridge'
		scalar INORM_useridge = 0
		scalar INORM_burnin = `burnin'
		scalar INORM_its = `its'
		scalar INORM_seed1 = `seed1'
		scalar INORM_seed2 = `seed2'
		scalar INORM_dumpat = `dumpat'
		scalar INORM_dumpimp = `dumpimp'
		scalar INORM_da = 1
		scalar INORM_echo = 0
		scalar INORM_dump = 0
		scalar INORM_usebeta = 0
		scalar INORM_usesigma = 0
		if ( `ridge' > 0 ) scalar INORM_useridge = 1
		if ( "`echo'"!="" ) scalar INORM_echo = 1
		if ( "`dump'"!="" ) scalar INORM_dump = 1
		if ( "`beta'"!="" ) scalar INORM_usebeta = 1
		else matrix INORM_beta = J( INORM_noofxvars, INORM_noofyvars, 0 )
		if ( "`sigma'"!="" ) scalar INORM_usesigma = 1
		else matrix INORM_sigma = J( INORM_noofyvars, INORM_noofyvars, 0 )

		* CALL PLUGIN
		cap noi inormpi call `varlist' `xvars' `touse'
		local rc=_rc

		* CLEAN UP PARAMETERS
		scalar drop INORM_noofyvars
		scalar drop INORM_noofxvars
		scalar drop INORM_noofobs
		scalar drop INORM_nobs
		scalar drop INORM_m
		scalar drop INORM_ridge
		scalar drop INORM_useridge
		scalar drop INORM_burnin
		scalar drop INORM_its
		scalar drop INORM_seed1
		scalar drop INORM_seed2
		scalar drop INORM_dumpat
		scalar drop INORM_dumpimp
		scalar drop INORM_da
		scalar drop INORM_echo
		scalar drop INORM_dump
		scalar drop INORM_usebeta
		scalar drop INORM_usesigma
	}
	* ELSE RUN DA IN MATA
	else {
		display as text "(using mata)"

		if ( "`irand'"=="" ) {
			* INITIALISE MATA VERSION OF IRAND NUMBER GENERATOR
			mata: mata clear
			mata: ran_setall( `seed1', `seed2' )
		}
		else {
			* PASS SEED PARAMETERS TO IRAND PLUGIN VIA SCALARS
			* Note: The irand plugin will extracts these scalar values from Stata each time
			* it is called, and sets its internal state accordingly. Prior to exit, the plugin
			* copies its internal state to these Stata scalars ready for the next call.
			scalar INORM_curr1 = `seed1'
			scalar INORM_curr2 = `seed2'
		}

		* RUN DA
		if ( `burnin' > 0 ) {
			mata: run_norm_engine_da( ///
				"`xvars'", "`varlist'", "`touse'", "`imps'", 1, `burnin', `ridge', ///
				"usebeta", "usesigma", `seed1', "`dump'", `dumpimp', `dumpat', ///
				"`caseorder'", "`irand'", "discard" )
		}

		mata: run_norm_engine_da( ///
			"`xvars'", "`varlist'", "`touse'", "`imps'", `MI_m', `its', `ridge', ///
			"usebeta", "usesigma", `seed1', "`dump'", `dumpimp', `dumpat', ///
			"`caseorder'", "`irand'", "" )

		* CLEAN UP PARAMETERS AND RESULTS SET BY IRAND PLUGIN,  IF NECESSARY
		if ("`irand'"!="") {
			scalar drop INORM_curr1
			scalar drop INORM_curr2
			scalar drop INORM_cmd
			scalar drop INORM_z
			scalar drop INORM_df
		}
	}

	* IF SUCCESSFUL
	if ( `rc' == 0 ) {

		* RETURN RESULTS
		matrix colnames INORM_beta = `varlist'
		matrix rownames INORM_beta = `xvarnames'
		matrix colnames INORM_sigma = `varlist'
		matrix rownames INORM_sigma = `varlist'
		matrix rho = corr(INORM_sigma)
		return matrix rho = rho
		return matrix sigma = INORM_sigma
		return matrix mu = INORM_beta

		* LABEL VARIABLES
		label variable _mj "imputation identifier"
		label variable _mi "observation identifier"
		order _mj _mi
		sort _mj _mi

		* SAVE RESULTS
		capture drop `allones'
		capture drop `imps'
		capture drop `touse'
		quietly save "`using'" `replace'
	}


	* CLEAN UP SCALARS AND MATRICES
	cap matrix drop INORM_beta
	cap matrix drop INORM_sigma

	exit `rc'
end