*! 2.1.0 Stephen P. Jenkins and Fernando Rios-Avila, March 2026
* 		New SF approach
* 		Halton as well as uniform draws; different draws derivation method

********************************************************************************
* Bivariate Mixed-Poisson Regression using Simulated Maximum Likelihood
* Implementation of Munkin & Trivedi (1999). Simulated maximum likelihood
* 		estimation of multivariate mixed-Poisson regression
* 		models, with application. The Econometrics Journal 2, 29-48.
* 		https://doi.org/10.1111/1368-423X.00019
*
* Compared to MT, we: 	(i) allow Halton draws optionally
*						(ii) use bivariate normal sampling function with free
*								variances (not 1), and allow non-zero rho_sf
********************************************************************************

program define bimpoisson, eclass byable(onecall)

	// code borrowed from -mvprobit-
	version 15
    if replay() {
		if "`e(cmd)'" != "bimpoisson" {
			noi di as error "results for bimpoisson not found"
            exit 301
        }
        if _by() {
            error 190
        }
        Display `0'
        exit `rc'
	}
    if _by() {
		by `_byvars'`_byrc0': Estimate `0'
		 ereturn local cmdline `"bimpoisson `0'"'
    }
    else    Estimate `0'
	ereturn local cmdline `"bimpoisson `0'"'
end


program define Estimate, eclass byable(recall)
	version 15

	/* two syntaxes, handle one at a time */

	gettoken first : 0, match(paren)
	if "`paren'" == "" {
			/* syntax 1: 2 depvars followed by common predictors */

			gettoken dep1 0:0, parse (" =,[")
			_fv_check_depvar `dep1'
			tsunab dep1 : `dep1'
			rmTS `dep1'
			confirm variable `r(rmTS)'
			gettoken dep2 0:0, parse (" =,[")
			_fv_check_depvar `dep2'
			tsunab dep2 : `dep2'
			rmTS `dep2'
			confirm variable `r(rmTS)'
			gettoken junk left :0, parse ("=")

			if "`junk'" == "=" {
					local 0 "`left'"
			}

		syntax [varlist(default=none ts fv)]  [if] [in]  [pw aw iw fw] 	///
			[ , Robust Cluster(varname) 	///
			 rho_sf(real 0)					///
			 ANTIthetic 					///
			 bias 							///
			 seed(str)  					///
			 alt 							///
			 HAlton							///
			 burn(integer 5)				///
			 nsim(integer 10) 				///
			 rng0							///
			 IRR							///
			 EForm							///
			 NOLOg LOg MLOpts(string) 		///
			 FROM(string) VCE(passthru)     ///
			 * 		///
			]

			local fvops = "`s(fvops)'" == "true" | _caller() >= 11

			if _by() {
					_byoptnotallowed score() `"`score'"'
			}

			local ind1 `varlist'
			local ind2 `varlist'
			local dep1n "`dep1'"
			local dep1n : subinstr local dep1 "." "_"
			local dep2n "`dep2'"
			local dep2n : subinstr local dep2 "." "_"
			local e_neq = 2
			local nc1 `constant'
			local nc2 `constant'

			if "`eform'" != "" &  "`irr'" != "" {
				display as error "only one of options eform or irr is allowed"
				exit 198
			}

			if abs(`rho_sf') >= 1  {
				display as error "rho_sf must be between -1 and 1"
				exit 198
			}

		if "`alt'" == ""   local title "Bivariate mixed Poisson regression (MSL, with sampling function)"
		else               local title "Bivariate mixed Poisson regression (MSL)"

			local option0 `options'
			marksample touse
			markout `touse' `dep1' `dep2' `offset1' `offset2', strok
	}


	else {
		/* syntax 2: 2 depvars, separate equations */

		// get first equation
		gettoken first 0:0, parse(" ,[") match(paren)
		local left "`0'"
		local junk: subinstr local first ":" ":", count(local number)
		if "`number'" == "1" {
				gettoken dep1n first: first, parse(":")
				gettoken junk first: first, parse(":")
		}
		local first : subinstr local first "=" " "
		gettoken dep1 0: first, parse(" ,[")
		_fv_check_depvar `dep1'
		tsunab dep1: `dep1'
		rmTS `dep1'
		confirm variable `r(rmTS)'
		if "`dep1n'" == "" {
				local dep1n "`dep1'"
		}
		syntax [varlist(default=none ts fv)] [, /*
				*/      OFFset(varname numeric) noCONstant]

		local fvops = "`s(fvops)'" == "true" | _caller() >= 11

		local ind1 `varlist'
		local offset1 `offset'
		local nc1 `constant'

		// get second equation
		local 0 "`left'"
		gettoken second 0:0, parse(" ,[") match(paren)
		if "`paren'" != "(" {
				dis in red "two equations required"
				exit 110
		}
		local left "`0'"
		local junk : subinstr local second ":" ":", count(local number)
		if "`number'" == "1" {
				gettoken dep2n second: second, parse(":")
				gettoken junk second: second, parse(":")
		}
		local second : subinstr local second "=" " "
		gettoken dep2 0: second, parse(" ,[")
		_fv_check_depvar `dep2'
		tsunab dep2: `dep2'
		rmTS `dep2'
		confirm variable `r(rmTS)'
		if "`dep2n'" == "" {
				local dep2n "`dep2'"
		}
		syntax [varlist(default=none ts fv)] [, /*
				*/  OFFset(varname numeric) noCONstant ]

		if !`fvops' {
				local fvops = "`s(fvops)'" == "true"
		}

		local ind2 `varlist'
		local offset2 `offset'
		local nc2 `constant'

		// remain options
		local 0 "`left'"
		syntax [if] [in]  [pw aw iw fw] 	///
			[ , Robust Cluster(varname) 	///
			 rho_sf(real 0)					///
			 ANTIthetic 					///
			 HAlton							///
			 burn(integer 5)				///
			 bias 							///
			 seed(str)  					///
			 alt 							///
			 nsim(integer 10) 				///
			 rng0							///
			 IRR							///
			 EForm				 			///
			 NOLOg LOg MLOpts(string) 		///
			 FROM(string) VCE(passthru)     ///
			 * 		///
			]

		if "`eform'" != "" &  "`irr'" != "" {
			display as error "only one of options eform or irr is allowed"
			exit 198
		}

		if "`alt'" == ""   local title "Bivariate mixed Poisson regression (MSL, with sampling function)"
		else               local title "Bivariate mixed Poisson regression (MSL)"

		local option0 `options'
		marksample touse
		markout `touse' `dep1' `dep2' `ind1' `ind2' `offset1'  `offset2'
	}

    local wtype `weight'
    local wtexp `"`exp'"'
    if "`weight'" != ""   local wgt `"[`weight'`exp']"'

    _get_diopts diopts option0, `option0'
    mlopts stdopts, `option0'
    local coll `s(collinear)'
    local cns `s(constraints)'
    if "`cluster'" != ""   local clopt "cluster(`cluster')"
	_vce_parse, argopt(CLuster) opt(Robust oim opg) old: ///
            [`weight'`exp'], `vce' `clopt' `robust'

    local vceopt  `r(vceopt)'
    local cluster `r(cluster)'
    local robust `r(robust)'
    if "`cluster'" ! = ""  local clopt "cluster(`cluster')"
    if `"`robust'"' != ""  local crtype crittype("log pseudolikelihood")

    // test collinearity
    qui:_rmcoll `ind1' `wgt' if `touse', `nc1' `coll'
    local ind1 "`r(varlist)'"
    qui:_rmcoll `ind2' `wgt' if `touse', `nc2' `coll'
    local ind2 "`r(varlist)'"

    if "`level'" != ""   local level "level(`level')"

    if "`offset1'" != "" local offo1 "offset(`offset1')"
    if "`offset2'" != "" local offo2 "offset(`offset2')"

        * -mllog- passed to command to let -log- overwrite c(iterlog) off
    local mllog `log' `nolog'
    _parse_iterlog, `log' `nolog'
    local log "`s(nolog)'"
    qui {
        if "`log'" == "" {
                local log "noisily"
        }
		else    local log "quietly"

		count if `touse'
		local nuse = r(N)
		if `nuse' == 0  {
			noi di "no observations"
			exit 2000
		}
		global touse___ `touse'
	}

	global rho_sf___ `rho_sf'

	if "`nc1'`nc2'" != ""    local skip "skip"
    if "`ind1'`ind2'" == ""  local skip "skip"
    if "`robust'" != ""      local skip "skip"

	if "`antithetic'" != ""  global antithetic___ = 1
	else 				     global antithetic___ = 0

	if "`halton'" != ""  global halton___ = 1
	else 				 global halton___ = 0

	if "`bias'" != ""        global bias___ = 1
	else                     global bias___ = 0

	// set seed
	if "`seed'" != "" {
		set seed `seed'
		global rngstate___ = c(rngstate)
	}
	else {
		global rngstate___ = c(rngstate)
		if "`halton'" == "0" {
			display _n "Warning: no seed set. Command will use current rng state"
		}
	}

	global nsim___ = `nsim'

	// create draws

	if "`halton'" != "" {

		di " "
		di as text "Generating Halton draws ..."

		mata: H_all = halton(`nuse'*`nsim', 2, `=`burn'+1')

		forvalues s = 1/`nsim' {
			tempvar h1_`s' h2_`s'
			quietly gen double `h1_`s'' = .
			quietly gen double `h2_`s'' = .

			mata: st_store(., "`h1_`s''", "`touse'", extract_block(H_all, `nsim', `s', 1))
			mata: st_store(., "`h2_`s''", "`touse'", extract_block(H_all, `nsim', `s', 2))
			// set up globals so that can refer to draws in the evaluator function
			global  dr1_`s'__ "`h1_`s''"
			global  dr2_`s'__ "`h2_`s''"
		}
	}
	else {
		di " "
        di as text "Generating pseudorandom uniform draws ..."
        forvalues s = 1/`nsim' {
            tempvar u1_`s' u2_`s'
            quietly gen double `u1_`s'' = runiform() if `touse'
            quietly gen double `u2_`s'' = runiform() if `touse'
			// set up globals so that can refer to draws in the evaluator function
			global  dr1_`s'__ "`u1_`s''"
			global  dr2_`s'__ "`u2_`s''"
        }
	}

************ models ********************************************
	// 2 models: (i) with sampling function (ii) without ('alt')

	if "`alt'" == "" {

		`log' ml model lf bimpoisson_lf 								///
			(`dep1n': `dep1' = `ind1', `nc1' `offo1' )  			///
			(`dep2n': `dep2' = `ind2', `nc2' `offo2' )  			///
			/ln_sig1 /ln_sig2 /arho 								///
			if `touse' `wgt' , 										///
			collinear missing maximize nooutput 					///
			nopreserve  title(`title')  `vceopt'		///
			init(`from') search(off)  `diopts' `irr'				///
			`level' `mlopts'  `stdopts' 							///
			`iterate'  `mllog'
	}
	else  {

		`log' ml model lf bimpoisson2_lf 							///
			(`dep1n': `dep1' = `ind1', `nc1' `offo1' )  			///
			(`dep2n': `dep2' = `ind2', `nc2' `offo2' )  			///
			/ln_sig1 /ln_sig2 /arho 								///
			if `touse' `wgt', 										///
			collinear missing maximize nooutput 					///
			nopreserve  title(`title')  `vceopt'			///
			init(`from') search(off)  `diopts'	`irr'				///
			`level' `mlopts'  `stdopts' 							///
			`iterate' `mllog'
	}

		ereturn local cmd "bimpoisson"
        ereturn local predict "bimpoisson_p"
		ereturn scalar sigma1 = exp( e(b)[1,"/ln_sig1"] )
		ereturn scalar sigma2 = exp( e(b)[1,"/ln_sig2"] )
		ereturn scalar rho = tanh( e(b)[1,"/arho"] )
		ereturn scalar nsims = `nsim'
		ereturn scalar k_eform = 2
		ereturn scalar rho_sf = `rho_sf'
		ereturn scalar burn = `burn'

		if "`seed'" != ""  ereturn local seed = `seed'
		else  ereturn local seed = "seed not set"

		if "`antithetic'" != "" ereturn scalar antithetic = 1
		else if "`antithetic'" == "" ereturn scalar antithetic = 0
		if "`halton'" != "" ereturn scalar halton = 1
		else if "`halton'" == "" ereturn scalar halton = 0
		if "`bias'" != ""  ereturn scalar bias = 1
		else if "`bias'" == ""  ereturn scalar bias = 0
		if "`rng0'" != ""  ereturn scalar rng0 = 1
		else if "`rng0'" == ""  ereturn scalar rng0 = 0

		ereturn local rngstate $rngstate___

        Display , `level' `diopts' `irr' `eform'
        exit `e(rc)'

		// set rngstate back to initial value, optionally
		if "`rng0'" != "" { set rngstate $rngstate___	}

		global nsim___
		global seed___
		global bias___
		global antithetic___
		global halton___
		global rngstate___
		global norm___
		global touse___
		forvalues s = 1/`nsim' {
			global  dr1_`s'__
			global  dr2_`s'__
		}

end

program define Display, eclass

        syntax [, Level(cilevel) IRR EForm *]

        _get_diopts diopts, `options'

		ml display,  level(`level') `diopts' `irr' `eform' plus
		_diparm ln_sig1 , exp label("sigma1") prob
		ereturn scalar sigma1_se = r(se)
		_diparm ln_sig2, exp label("sigma2") prob
		ereturn scalar sigma2_se = r(se)
		_diparm arho, tanh label("rho") prob
		ereturn scalar rho_se = r(se)

		di in smcl  "{hline 13}{c BT}{hline 64}"
		if "`e(antithetic)'" == "0" & "`e(bias)'" == "0" {
			di "Note: `e(nsims)' draws "
		}
		else if "`e(antithetic)'" == "1" & "`e(bias)'" == "0" {
			di "Note: `e(nsims)' draws " _c
			di "plus `e(nsims)' antithetic draws"
		}
		else if "`e(antithetic)'" == "0" & "`e(bias)'" == "1" {
			di "Note: `e(nsims)' draws, " _c
			di "with first-order bias correction"
		}
		else if "`e(antithetic)'" == "1" & "`e(bias)'" == "1" {
			di "Note: `e(nsims)' draws " _c
			di "plus `e(nsims)' antithetic draws, "	_c
			di "with first-order bias correction"
		}

end

program define rmTS, rclass

        local tsnm = cond( match("`0'", "*.*"),                 /*
                        */ bsubstr("`0'",                       /*
                        */        (index("`0'",".")+1),.),      /*
                        */ "`0'")

        return local rmTS `tsnm'
end

mata:
real colvector extract_block(real matrix H, real scalar S,
                              real scalar sim, real scalar col) {
    real scalar nobs, i
    real colvector result

    nobs = rows(H) / S
    result = J(nobs, 1, .)

    for (i = 1; i <= nobs; i++) {
        result[i] = H[(i-1)*S + sim, col]
    }
    return(result)
}
end