*Title: drm
*Description: Fits Sobel's DRM model
*Version: 0.5 (19th November 2018)
*Author: Caspar Kaiser (caspar.kaiser@nuffield.ox.ax.uk)

*==============================*
*1. Wrapper that allows for replay
*==============================*
program drm
	version 12
	if replay()==1 {
		syntax [, Level(integer 95) COEFLegend]
		if ("`e(cmd)'" != "ml" & "`e(cmd)'" != "nl" ) error 301 // if the previous command was not drm, previous results of drm won't have been saved
		if "`e(cmd)'" == "nl" nl, level(`level') `coeflegend'
		if "`e(cmd)'" == "ml" ml, level(`level') `coeflegend' nocnsreport
	}
	else {
		Estimate `0'
	}
end

*==============================*
*2. Main routine
*==============================*
program Estimate, eclass

	*======================================================
	*2.1 Parsing, checks and setup
	*======================================================

	// Drop globals that may have been left over
	cap macro drop DRMCONSTRAIN
	cap macro drop DRMWGTVAR
	cap macro drop DRMINTERVARS
	cap macro drop DRMSETOFP DRMSETOFQ
	cap macro drop DRMRHO
	cap macro drop DRMBYVAR

	// drops constrains if any are left
	cap cons drop _all

	// Setup
	syntax varlist(min=3 fv) [aw fw iw pw] [if] [, ///
	vce(string) 		///
	wgt(string)				///
	INTERvars(varlist fv)	///
	ITERate(integer 1000)	///
	tech(string) 			///
	DIFFicult				///
	link(string) 			///
	Level(integer 95)		///
	COEFLegend				///
	OWNConstrains(string)	///
	Sobel					///
	Alternative				///
	CLassic					///
	Nl						///
	by(varlist fv) 			///
	Constrain				///
	old						///
	KEep]					//

	gettoken depvar varlist : varlist
	gettoken rowvar varlist	: varlist
	gettoken colvar controls: varlist

	// Marksample
	marksample touse
	markout `touse' `depvar' `rowvar' `colvar' `controls' `by' `intervars' // Listwise deletion

	// Check if wgt is correctly specified
	if ("`wgt'" != "") & ("`wgt'" != "cons") & ("`wgt'" != "row") & ("`wgt'" != "col") {
		disp as error "`wgt' is not a valid wgt option. Choose one of: cons, row, col."
		exit
	}

	// Check if link is correctly specified
	if ("`link'"!="") & ("`link'"!="linear") & ("`link'"!="logit") & ("`link'"!="probit") {
		disp as error "`link' is not a valid link option. Choose one of: linear, logit, probit, or omit link option."
		exit
	}
	if "`nl'"=="nl" & "`link'"!="" {
		disp as error "May not specify both nl and link()."
		exit
	}

	// Create intervar global
	if "`intervars'" != "" {
		fvrevar `intervars'
		local intervars `r(varlist)'
		_rmcoll `intervars', forcedrop
		local intervars `r(varlist)'
		global DRMINTERVARS `intervars'
	}

	// Check if rowvar and colvar are correctly specified
	qui sum `rowvar'
	*Is minimum==1?
	if r(min) != 1 {
		disp as error "Minimum of rowvar (`rowvar') and colvar (`colvar') must be 1"
		exit
	}
	*Same number of categories?
	local NROWS = `r(max)'
	qui sum `colvar'
	local NCOLS = `r(max)'
	if `NROWS' != `NCOLS' { // rowvar and colvar must have same number of levels
		disp as error "Numbers of categories of rowvar (`rowvar') and colvar (`colvar') are not equal."
		exit
	}
	*Consecutive values?
	qui levelsof `rowvar', local(values)
	forvalues i=1/`NROWS' {
		local current: word `i' of `values'
		if "`current'" != "`i'" {
			local error = 1
		}
	}
	qui levelsof `colvar', local(values)
	forvalues i=1/`NCOLS' {
		local current: word `i' of `values'
		if "`current'" != "`i'" {
			local error = 1
		}
	}
	if "`error'"=="1" {
		dis as error "rowvar (`rowvar') and colvar (`colvar') must only contain consecutive values and may not contain gaps"
		exit
	}
	// Check if depvar is binary when link is logit or probit
	if "`link'" == "logit" | "`link'" == "probit" {
		qui levelsof `depvar', local(levels)
		if "`levels'" != "0 1" {
			dis as error "depvar (`depvar') may only be 0 or 1 when using `link'."
			exit
		}
	}
	// If weight is made conditional, set which it is and store in global
	if "`wgt'" == "row" global DRMWGTVAR = "`rowvar'"
	if "`wgt'" == "col" global DRMWGTVAR = "`colvar'"

	*======================================================
	*2.2 Begin of part specific to nl
	*======================================================

	if "`nl'" == "nl" {

		*----------------------------------------------------
		*	2.2.1 More setup
		*----------------------------------------------------

		// Handle factor variables and drop collinear variables
		fvrevar `controls'
		local controls `r(varlist)'
		_rmcoll `controls', forcedrop
		local controls `r(varlist)'

		// Create byvar and set global for byvar
		if "`by'" != "" {
			tempvar byvar
			egen `byvar' = group(`by')
			global DRMBYVAR = "`byvar'"
		}

		// Check if p should be explicitly constrained in nl
		if "`constrain'" == "constrain" global DRMCONSTRAIN = "yes"

		// Specify parameter names for mu
		if "`by'" != "" {
			qui sum `byvar'
			local NBYVAR = `r(max)'
			forvalues i=1(1)`NBYVAR'{
				forvalues j=1(1)`NROWS'{
					local mu_names "`mu_names' mu_`j'`j'_`i'"
				}
			}
		}

		if "`by'" == "" {
			forvalues i=1(1)`NROWS'{
				local mu_names "`mu_names' mu_`i'`i'"
			}
		}

		// If relevant assign rhonames
		if "`intervars'" != "" {
			foreach i of local intervars {
				local rhonames "`rhonames' rho_`i'"
			}
		}

		*----------------------------------------------------
		*	2.2.2 Run nl
		*----------------------------------------------------

		// For constant weights
		if "`wgt'" == "" | "`wgt'" == "cons" {

			if "`constrain'" == "constrain" 	local wgtname = "gamma"
			else 								local wgtname = "p"

			// run routine
			nl drm_constant @ `depvar' `rowvar' `colvar' `controls' [`weight'`exp']  if `touse', parameters(`wgtname' `mu_names' `controls' `rhonames') vce(`vce') iterate(`iterate')  level(`level') `coeflegend'

			if "`constrain'" == "constrain" nlcom (p: exp(_b[/gamma]) / ( 1 + exp(_b[/gamma]))) (q: 1 - (exp(_b[/gamma]) / ( 1 + exp(_b[/gamma]))))
		}

		// For row or column specific weights
		if "`wgt'" == "row" | "`wgt'" == "col"  { // Row or column weight
			// Specify wgt names
			forvalues i=1(1)`NROWS'{
				if "`constrain'" == "constrain" 	local wgt_names "`wgt_names' gamma_`i'"
				else 								local wgt_names "`wgt_names' p_`i'"
			}

			// run routine
			nl drm_rowcol @ `depvar' `rowvar' `colvar' `controls' [`weight'`exp'] if `touse' , parameters(`wgt_names' `mu_names' `controls' `rhonames') vce(`vce') iterate(`iterate') level(`level') `coeflegend'

			if "`constrain'" == "constrain" {
				forvalues i=1/`NROWS' {
					local to_test_row `to_test_row' (p_`i': exp(_b[/gamma_`i']) / (1+exp(_b[/gamma_`i'])))
					local to_test_col `to_test_col' (q_`i': 1-exp(_b[/gamma_`i']) / (1+exp(_b[/gamma_`i'])))
				}
				nlcom `to_test_row'
				nlcom `to_test_col'
			}
		}
	}

	*======================================================
	*2.3. Begin of Routine specific to ml
	*======================================================
	if "`nl'" != "nl" {

		*----------------------------------------------------
		*	2.3.1 Declare wgt settings if no wgt is specified, correct iterate option, drop previous constrains, initialize NCONSTR, start loop
		*----------------------------------------------------

		if "$DRMWGTVAR" == "" {
			local 	pestimate 	= "(p: `depvar' = )"
			local 	qestimate 	= "(q: `depvar' = )"
			global 	DRMSETOFP 	= "p"
			global 	DRMSETOFQ 	= "q"
		}

		cons drop _all
		local NCONSTR = 1
		qui sum `rowvar'
		local NROWS = r(max)
		forvalues i=1/`NROWS' {

			*----------------------------------------------------
			*	2.3.2 Create Row and column dummies and set constraint: mu_ii=mu_jj
			*----------------------------------------------------
				if "`old'" != "old" {
					capture confirm variable _`rowvar'_`i'
					if _rc != 0 {
						qui gen _`rowvar'_`i' = 0
						qui replace _`rowvar'_`i' = 1 if `rowvar' == `i'
					}
					else local notifydummy = "yes"
					capture confirm variable _`colvar'_`i'
					if _rc != 0 {
						qui gen  _`colvar'_`i' = 0
						qui replace _`colvar'_`i' = 1 if `colvar' == `i'
					}
					else local notifydummy = "yes"

					local ROWVARS `ROWVARS' _`rowvar'_`i'
					local COLVARS `COLVARS' _`colvar'_`i'

					// Set constraints
					cons def `NCONSTR' [row]_`rowvar'_`i'=[col]_`colvar'_`i'
					local ++NCONSTR

					local SUMCONSTR "`SUMCONSTR' [row]_`rowvar'_`i' +"
					local COLCONSTR "`COLCONSTR' [col]_`colvar'_`i' +"
				}
				else {
					tempvar `rowvar'`i'
					tempvar `colvar'`i'
					qui gen ``rowvar'`i'' = 0
					qui replace ``rowvar'`i'' = 1 if `rowvar' == `i'
					qui gen  ``colvar'`i'' = 0
					qui replace ``colvar'`i'' = 1 if `colvar' == `i'


					local ROWVARS `ROWVARS' ``rowvar'`i''
					local COLVARS `COLVARS' ``colvar'`i''

					cons def `NCONSTR' [row]``rowvar'`i''=[col]``colvar'`i''
					local ++NCONSTR

					local SUMCONSTR "`SUMCONSTR' [row]``rowvar'`i'' +"
					local COLCONSTR "`COLCONSTR' [col]``colvar'`i'' +"
				}

			*----------------------------------------------------
			*	2.3.3 Set options for p
			*----------------------------------------------------
			if "$DRMWGTVAR" != "" {
				local 	pestimate	= "`pestimate' (p`i': `depvar' = )"
				local 	qestimate	= "`qestimate' (q`i': `depvar' = )"
				global 	DRMSETOFP 	= "$DRMSETOFP p`i'"
				global 	DRMSETOFQ 	= "$DRMSETOFQ q`i'"

			}
		}
		if "`notifydummy'" == "yes" dis "Note: One or more dummies for rowvar (`rowvar') and colvar (`colvar') already existed and were not newly generated by drm"

		*----------------------------------------------------
		*	2.3.4 Set options for rho
		*----------------------------------------------------

		if "$DRMINTERVARS" != "" {
			local 	rhoestimate	= "(rho: `depvar' = $DRMINTERVARS, nocons)"
			global 	DRMRHO = "rho"
			local NRHO : list sizeof local intervars

			if "`alternative'" != "alternative" & "`classic'" != "classic" {
				forvalues i=1/`NRHO' {
					local rhoinit `rhoinit' 0
				}
			}
		}

		*----------------------------------------------------
		*	2.3.5 Find initial values for mu_ii (classic and alternative)
		*----------------------------------------------------

		if "`alternative'" == "alternative" | "`classic'" 	== "classic" {

			if "$DRMWGTVAR" == "" {
				local 	pinit		= "p:_cons=0.5"
				local 	qinit		= "q:_cons=0.5"
			}
			forvalues i=1/`NROWS' {
				if "$DRMWGTVAR" != "" {
					local 	pinit		= "`pinit' p`i':_cons=0.5"
					local 	qinit		= "`qinit' q`i':_cons=0.5"
				}
				qui sum `depvar' if `rowvar'==`i' & `colvar'==`i' & `touse', meanonly
				local MUINIT`i'_temp = r(mean)
				local MUINIT_SUM = "`MUINIT_SUM' `MUINIT`i'_temp' + "
			}

			forvalues i=1(1)`NROWS' {
				if "`alternative'" == "alternative"	local MUINIT`i' = exp(`MUINIT`i'_temp'-(`MUINIT_SUM' 0/`NROWS')) // Gives exponentiated deviation of diagonal elements from mean diagonal elements
				if "`classic'" 	== "classic"		local MUINIT`i' = `MUINIT`i'_temp'-(`MUINIT_SUM' 0/`NROWS') // Gives deviation of diagonal elements from mean diagonal elements
				local rowinit "`rowinit' row:``rowvar'`i''=`MUINIT`i''"
				local colinit "`colinit' col:``colvar'`i''=`MUINIT`i''"
			}
		}

		*----------------------------------------------------
		*	2.3.7 Find initial values using Sobel's method (default)
		*----------------------------------------------------
		if "`alternative'" != "alternative" & "`classic'" != "classic" {
			fvrevar `controls' if `touse'
			local controlssobel `r(varlist)'

			if "`link'" == "linear" | "`link'" == "" {
				qui reg `depvar' i.`rowvar' `controlssobel' [`weight'`exp'] if `touse' & `rowvar'==`colvar'
				local sigmainit = (e(rss)/e(N))^(0.5)
			}
			if "`link'" == "probit"  qui probit `depvar' i.`rowvar' `controlssobel' [`weight'`exp'] if `touse' & `rowvar'==`colvar'
			if "`link'" == "logit"  qui logit `depvar' i.`rowvar' `controlssobel' [`weight'`exp'] if `touse' & `rowvar'==`colvar'

			tempname sobel1 muresult xbinit avgdev
			matrix `sobel1' = e(b)
			matrix `muresult' = `sobel1'[1,1..`NROWS']
			matrix `xbinit' = `sobel1'[1,`NROWS'+1...]
			mata : st_matrix("`avgdev'", (rowsum(st_matrix("`muresult'"))/`NROWS'))
			matrix `xbinit'[1,colsof(`xbinit')] = `xbinit'[1,colsof(`xbinit')] + `avgdev'[1,1]
			forvalues i=1/`NROWS' {
				local MUINIT`i' = `sobel1'[1,`i'] - `avgdev'[1,1]
				local rowinit `rowinit' `MUINIT`i''
				local colinit `colinit' `MUINIT`i''
			}
			tempvar muii
			qui predict `muii' , xb
			nobreak {
				rename `rowvar' `rowvar'_temp
				rename `colvar' `rowvar'
				tempvar mujj
				qui predict `mujj' , xb
				rename `rowvar' `colvar'
				rename `rowvar'_temp `rowvar'
			}
			if "`link'" == "linear" | "`link'" == "" {
				if "`wgt'" == "" | "`wgt'"=="cons" {
					qui reg `depvar' `muii' `mujj' [`weight'`exp'] if `touse'
					local pinit = _b[`muii']
					local qinit = _b[`mujj']
				}
				if "`wgt'" != "" {
					qui reg `depvar' i.$DRMWGTVAR#c.`muii' i.$DRMWGTVAR#c.`mujj' [`weight'`exp'] if `touse'
					local pinit ""
					local qinit ""
					forvalues i=1/`NROWS' {
						local ptemp = _b[`i'.$DRMWGTVAR#c.`muii']
						local pinit  `pinit' `ptemp'
						local qtemp = _b[`i'.$DRMWGTVAR#c.`mujj']
						local qinit  `qinit' `qtemp'
					}
				}
			}
			if "`link'" == "probit" {
				if "`wgt'" == "" {
					qui probit `depvar' `muii' `mujj' [`weight'`exp'] if `touse'
					local pinit = _b[`muii']
					local qinit = _b[`mujj']
				}
				else {
					qui probit `depvar' i.$DRMWGTVAR#c.`muii' i.$DRMWGTVAR#c.`mujj' [`weight'`exp'] if `touse'
					local pinit ""
					local qinit ""
					forvalues i=1/`NROWS' {
						local ptemp = _b[`i'.$DRMWGTVAR#c.`muii']
						local pinit  `pinit' `ptemp'
						local qtemp = _b[`i'.$DRMWGTVAR#c.`mujj']
						local qinit  `qinit' `qtemp'
					}
				}
			}
			if "`link'" == "logit" {
				if "`wgt'" == "" {
					qui logit `depvar' `muii' `mujj' [`weight'`exp'] if `touse'
					local pinit = _b[`muii']
					local qinit = _b[`mujj']
				}
				else {
					qui logit `depvar' i.$DRMWGTVAR#c.`muii' i.$DRMWGTVAR#c.`mujj' [`weight'`exp'] if `touse'
					local pinit ""
					local qinit ""
					forvalues i=1/`NROWS' {
						local ptemp = _b[`i'.$DRMWGTVAR#c.`muii']
						local pinit  `pinit' `ptemp'
						local qtemp = _b[`i'.$DRMWGTVAR#c.`mujj']
						local qinit  `qinit' `qtemp'
					}
				}
			}
			local copy ", copy"
		}

		*----------------------------------------------------
		*	2.3.8 Set constraint: sum(mu_ii)=0 & sum(mu_jj)=0 ; p+q=1
		*----------------------------------------------------

		cons def `NCONSTR' `SUMCONSTR' 0 = 0
		local ++NCONSTR
		cons def `NCONSTR' `COLCONSTR' 0 = 0
		local ++NCONSTR

		if "`wgt'" == "" {
			cons def `NCONSTR' [q]_cons + [p]_cons=1
			local ++NCONSTR
		}
		else {
			forvalues i=1/`NROWS' {
				cons def `NCONSTR' [q`i']_cons + [p`i']_cons=1
				local ++NCONSTR
			}
		}

		*----------------------------------------------------
		*	2.3.9 Declare link
		*----------------------------------------------------

		if "`link'" == "" | "`link'" == "linear" {
			local model_type = "drm_normal_ll"
			local sigma "(sigma: `depvar' = )"
			if "`alternative'" == "alternative" | "`classic'" == "classic" {
				qui sum `depvar'
				local sigmainit "sigma:_cons=`r(sd)'"
			}
		}

		if "`link'" == "logit" local model_type = "drm_logit_ll"
		if "`link'" == "probit" local model_type = "drm_probit_ll"

		*----------------------------------------------------
		*	2.3.10 Add user defined constrains
		*----------------------------------------------------

		local Nownconstrains : list sizeof local ownconstrains
		tokenize `ownconstrains'
		forvalues i=1(1)`Nownconstrains' {
			if "``i''" == "[p]_cons=1" local `i' "[p]_cons=1.0000001"
			if "``i''" == "[q]_cons=1" local `i' "[q]_cons=1.0000001"
			cons def `NCONSTR' ``i''
			local ++NCONSTR
		}
		*----------------------------------------------------
		*	2.3.11 Maximize and display
		*----------------------------------------------------
		ml model lf `model_type' ///
			(row: `depvar' = `ROWVARS', nocons) ///
			(col: `depvar' = `COLVARS', nocons) ///
			`pestimate' `qestimate' ///
			`rhoestimate' ///
			(xb: `depvar' = `controls') ///
			`sigma' ///
			[`weight'`exp'] if `touse', ///
			nopre tech(`tech') vce(`vce') constraint(2/`NCONSTR')  ///
			iter(`iterate') `difficult' max   ///
			init(`rowinit' `colinit' `pinit' `qinit' `rhoinit' `xbinit' `sigmainit' `copy') //
		if "`link'" == "linear" | "`link'" == ""  local NEQ = e(k_eq) - 1
		else  local NEQ = e(k_eq)
		ml display, level(`level') `coeflegend' nocnsreport neq(`NEQ')
	}
	cap macro drop DRMCONSTRAIN
	cap macro drop DRMWGTVAR
	cap macro drop DRMINTERVARS
	cap macro drop DRMSETOFP DRMSETOFQ
	cap macro drop DRMRHO
	cap macro drop DRMBYVAR
	cap cons drop _all
	if "`keep'"!="keep" {
		cap drop _`rowvar'_*
		cap drop _`colvar'_*
	}
end