**********************************************************************
** lmtest: LAGRANGE-MULTIPLIER TEST AFTER CONSTRAINED ML ESTIMATION **
**********************************************************************
*! version 1.5 2020-12-20 ht /* handling of variable containing nonselection hazard (heckman) */
*! version 1.4 2020-12-19 ht /* handling of vcetype */
*! version 1.3 2020-12-18 ht /* options df() and noomitted */
*! version 1.2 2020-12-17 ht /* handling of vcetype */
*! version 1.1 2020-12-15 ht /* display expanded constraints */
*! version 1.0 2020-12-14 ht
*! author Harald Tauchmann
*! Lagrange-Multiplier Test after Constrained ML Estimation
** DEFINE PROGRAM lmtest **
cap program drop lmtest
program lmtest, rclass
version 15
quietly {
    ** SYNTAX DEFINITION **
    syntax, [noTest] [noCNSReport] [Df(integer -7777)] [noOMITted] [FORCEVce]
	** DISPLAY-FORMAT for WARNINGS **
	local ls = c(linesize)-7
    ** TEMPORARY MATRICES and SCALARS **
	tempname _Cns _Cns1 _Cns2 _rank _cnsest _br _br2 _br3 _LM _V _V1 _V2 _V3
	** CHECK FOR and STORE ESTIMATES **
	cap estimates store `_cnsest'
	if _rc != 0 {
		di as error "{p 0 2 2 `ls'}last estimation results not found{p_end}"
		exit 301
	}
	** CHECK FOR e(gradient) **
	cap confirm matrix e(gradient)
	if _rc != 0 {
		di as error "{p 0 2 2 `ls'}not allowed after {bf:`e(cmd)'}{p_end}"
		exit 321
	}
    ** CHECK FOR CONSTRAINTS TO BE TESTED **
	cap confirm matrix e(Cns)
    if _rc != 0 {
		di as error "{p 0 2 2 `ls'}option {bf:constraints()} not specified in last model; no constraints to be tested{p_end}"
		exit 111
	}
	else {
        mat `_Cns' = e(Cns)
        ** CLEAN CONSTRAINTS MATRIX from BASE CATEGORIES **
        local cnsfn : colfullnames `_Cns'
		di "`cnsfn'"
		local count = 0
		foreach cn in `cnsfn' {
			local count = `count'+1
			if "`omitted'" == "noomitted" {
				local checkbase = strpos("`cn'","b.")+ strpos("`cn'","o.")
			}
			else {
				local checkbase = strpos("`cn'","b.")
			}
			if `checkbase' == 0 {
				cap confirm matrix `_Cns1'
				if _rc != 0 {
					mat `_Cns1' = `_Cns'[1...,`count']
				}
				else {
					mat `_Cns2' = `_Cns'[1...,`count']
					mat `_Cns1' =(`_Cns1',`_Cns2')
				}
			}
		}
        ** RANK of cleaned CONSTRAINTS MATRIX **
		mata : st_numscalar("`_rank'",rank(st_matrix("`_Cns1'")))
        if `_rank' < 1 {
		    local coldif = colsof(`_Cns')- colsof(`_Cns1')
			if `coldif' > 0 {
			    di as error "{p 0 2 2 `ls'}besides base-levels no constraints specified in last model; no constraints to be tested{p_end}"
			}
			else {
				di as error "{p 0 2 2 `ls'}no constraints specified in last model; no constraints to be tested{p_end}"
			}
    		exit 111
        }
	}
	** CHECK DEGREES-OF-FREEDOM SPECIFICATION **
	if `df' != -7777 {
		if `df' < 1 {
			di as error "{p 0 2 2 `ls'}{bf:df(`df')} invalid; rank of e(Cns) used{p_end}"
			local df = `_rank'
		}
		else {
			local udf = 1
		}
	}
	else {
			local df = `_rank'
	}
	** CHECK FOR VCE-TYPE **
	local ovce "`e(vce)'"
	if "`ovce'" != "oim" & "`ovce'" != "opg" {
		if "`forcevce'" != "forcevce" {
		    di as error "{p 0 2 2 `ls'}{bf:vcetype} {bf:oim} or {bf:opg} expected; specify option {bf:forcevce} to switch to {bf:e(V_modelbased)}{p_end}"
			exit 101
		}
		else {
			cap confirm matrix e(V_modelbased)
			if _rc != 0 {
				di as error "{p 0 2 2 `ls'}{bf:vcetype} {bf:`ovce'} not allowed; {bf:e(V_modelbased)} not found{p_end}"
				exit 321
			}
			else {
				noi di as txt "{p 0 2 2 `ls'}{bf:vcetype} {bf:`ovce'} not allowed; switched to {bf:e(V_modelbased)}{p_end}"
			}
			local VCM "eVmb"
		}
	}
	else {
		local VCM "eV"
	}
	mat `_br' = e(b)
    ** PARSE COMMANDLINE ENTRY **
	local cmdlrest "`e(cmdline)'"
	gettoken cmdlcore cmdlrest : cmdlrest, parse(",")
	di "`cmdlcore'"
	di "`cmdlrest'"
	local count = 0
	local cmdlnew "`cmdlcore',"
	while "`cmdlrest'" != "" {
		local count = `count' +1
		gettoken tok`count' cmdlrest : cmdlrest, bind
		local checkcomma = strmatch("`tok`count''",",")
		local checkconst = strmatch("`tok`count''","constr*") + strmatch("`tok`count''","const(*")
		local checkiter = strmatch("`tok`count''","iter*") + strmatch("`tok`count''","iterate(*")
		local checkfrom = strmatch("`tok`count''","from(*")
		if "`forcevce'" != "forcevce" {
			local checkvce = strmatch("`tok`count''","vce(*")+ strmatch("`tok`count''","r*")+ strmatch("`tok`count''","cl*")
		}
		else {
			local checkvce = 0
		}
		if `checkcomma' < 1 & `checkconst' < 1 & `checkiter' < 1 & `checkfrom' < 1 & `checkvce' < 1 {
			local cmdlnew "`cmdlnew' `tok`count''"
		}
		if `checkconst' >= 1 {
		    gettoken skip constlist : tok`count', parse("(")
			gettoken skip constlist : constlist, parse("(")
			gettoken constlist skip : constlist, parse(")")
		}
	}
	** MODEL-SPECIFIC ADJUSTMENTS **
	** mlogit: EXCLUDE BASE OUTCOME FROM e(b) **
	local baselab "`e(baselab)'"
	if "`baselab'" != "" {
		local cnq : coleq `_br'
		local cnq : list uniq cnq
		local cnq : list cnq - baselab
		foreach qq in `cnq' {
			mat `_br3' = `_br'[1,"`qq':"]
			cap confirm matrix `_br2'
			if _rc != 0 {
				mat `_br2' = `_br3'
			}
			else {
				mat `_br2' = (`_br2',`_br3')
			}
		}
		mat `_br' = `_br2'
	}
	** heckman: DROP VARIABLE CONTAINIG NONSELECTION HAZARD **
	if "`e(mills)'" != "" & "`e(cmd)'" == "heckman" {
		cap drop `e(mills)'
	}
	** CALCULATE GRADIENT VECTOR FOR RESTRICTED MODEL **
	cap `cmdlnew' from(`_br') iterate(0)
	if _rc != 0 {
		if _rc == 110 {
			di as error "{p 0 2 2 `ls'}don't specify options that make `e(cmd)' generate new variable(s){p_end}"
		}
	    di as error "{p 0 2 2 `ls'}calculating score vector at restricted paramter values failed{p_end}"
		estimates restore `_cnsest'
		exit 480
	}
	** EXCLUDE BASE OUTCOME FROM e(V) FOR mlogit **
	if "`VCM'" == "eV" {
		mat `_V' = e(V)
	}
	else {
	    cap confirm matrix e(V_modelbased)
		if _rc != 0 {
		    di as error "{p 0 2 2 `ls'}{bf:vcetype} {bf:`ovce'} not allowed; {bf:e(V_modelbased)} not found{p_end}"
			estimates restore `_cnsest'
			exit 321
		}
		else {
			mat `_V' = e(V_modelbased)
			local cfnV : colfullnames e(V)
 		    mat colnames `_V' = `cfnV'
 		    mat rownames `_V' = `cfnV'
		}
	}
	if "`baselab'" != "" {
		local cnq : coleq `_br'
		local cnq : list uniq cnq
		foreach rr in `cnq' {
			foreach qq in `cnq' {
				mat `_V3' = `_V'["`rr':","`qq':"]
				cap confirm matrix `_V2'
				if _rc != 0 {
					mat `_V2' = `_V3'
				}
				else {
					mat `_V2' = (`_V2',`_V3')
				}
			}
			cap confirm matrix `_V1'
			if _rc != 0 {
				mat `_V1' = `_V2'
			}
			else {
				mat `_V1' = (`_V1' \ `_V2')
			}
			mat drop `_V2'
		}
		mat `_V' = `_V1'
	}
	** CALCULATE LM-STATISTIC AND P-VALUE **
	mat `_LM' = e(gradient)*`_V'*(e(gradient))'
	local chi2stat = `_LM'[1,1]
	local pv = 1-chi2(`df',`chi2stat')
	** RESTORE RESTRICTED ESTIMATION RESULTS **
	estimates restore `_cnsest'
	** STORE RESULTS in r() **
	if "`udf'" == "1" {
		return scalar rank = `_rank'
	}
	return scalar df = `df'
	return scalar p = `pv'
	return scalar chi2 = `chi2stat'
	if "`VCM'" != "eV" {
		return local V_modelbased "modelbased"
	}
	** DISPLAY RESULTS **
	if "`test'" != "notest" {
        noi di _newline as text "LM test of constraints(`constlist')"
        if "`cnsreport'" != "nocnsreport" {
            ** DISPLAY CONSTRAINTS **
            noi dispconstr `_Cns1'
        }
		** (code borrowed from testnl.ado) **
	    noi di _newline as txt _col(12) "chi2(" %3.0f `df' ") =" as res %8.2f `chi2stat'
		noi di as txt _col(10) "Prob > chi2 =  " as res %8.4f `pv'
	}
}
end

*************************************************
** PROGRAM FOR DISPLAY OF EXPANDED CONSTRAINTS **
*************************************************
cap program drop dispconstr
program dispconstr, nclass
    syntax name
    cap confirm matrix `namelist'
    if _rc !=0 {
		di as error "{p 0 2 2 `ls'}no constraintsmatrix{p_end}"
		exit 111
    }
    else {
        local noco = colsof(`namelist')
        local noro = rowsof(`namelist')
		local cnf : colfullnames `namelist'
        local ncoe = `noco'-1
		tokenize "`cnf'"
		local resnum = 0
		forvalues rr = 1(1)`noro' {
			local dispc`rr' ""
			forvalues cc = 1(1)`noco' {
				if `namelist'[`rr',`cc'] != 0 & `cc' < `noco' {
					local absc = abs(`namelist'[`rr',`cc'])
					if `absc' == 1 {
						local absc ""
					}
					else {
						local absc "`absc'*"
					}
					gettoken eqname rgname : `cc', parse(":")
                    if "`rgname'" != "" {
					   gettoken skip rgname : rgname, parse(":")
                    }
                    else {
                        gettoken eqname rgname : `cc', parse("/")
                    }
					if `namelist'[`rr',`cc'] > 0 {
						if "`dispc`rr''" == "" {
							local sign ""
						}
						else {
							local sign "+ "
						}
					}
					else {
						local sign "- "
					}
					local dispc`rr' "`dispc`rr'' `sign'`absc'[`eqname']`rgname'"
				}
				if "`dispc`rr''" != "" & `cc' == `noco' {
					local rval = `namelist'[`rr',`cc']
					local dispc`rr' "`dispc`rr'' = `rval'"
				}
			}
			local dispc`rr' : list retokenize dispc`rr'
			if "`dispc`rr''" != "" {
				local resnum = `resnum'+1
                if `resnum' == 1 {
                    local newl "_newline"
                }
                else {
                    local newl ""
                }
				di `newl' as text _col(2) "(" as text _col(4) "`resnum')" as result _col(9) "`dispc`rr''"
			}
		}
    }
end