*! dcreate 1.4.1  18Aug2017
*! author arh

*  1.3.0:	a minor bug has been fixed
*  1.4.0:	Bayesian designs supported
*  1.4.1:	a bug preventing the use of evaldes with Bayesian designs has been fixed

program define dcreate, rclass
	version 11.1
	syntax varlist(fv), NALT(integer) NSET(integer) Bmat(name) [Vmat(name) ///
	ASC(numlist) FIXedalt(name) MAXiter(integer 10) CRIterion(real 0.00001) ///
	NREP(integer 50) BURN(integer 15) SEED(integer 435)]

	fvexpand `varlist'
	local explist `r(varlist)'

	tempname noomit
	local kexp : word count `explist'
	matrix `noomit' = J(1,`kexp',1)

	local k = 1
	foreach var of local explist {
		_ms_parse_parts `var'
		matrix `noomit'[1,`k'] = `r(omit)'!=1
		local k = `k' + 1
	}

	quietly {

	** Add fixed alternative to nalt if specified**
	if ("`fixedalt'" != "") {
		local nalt = `nalt' + 1
	}

	count
	local ntot = r(N)
	local nobs = `nset'*`nalt'

	desc
	local nvars = r(k)

	tempname d dif

	set seed `seed'

	** Create matrix containing alternative-specific constants **
	mata: dcreate_HASASC = 0
	if ("`asc'" != "") {
		mata: dcreate_HASASC = 1
		local k = 1
		foreach alt of numlist `asc' {
			if (`alt' < 1) | (round(`alt') != `alt') | (`alt' > `nalt') {
				di in r "The elements in the list of alternative-specific constants must be integers greater than zero"
				di in r "and not higher than the number of alternatives"
				exit 498
			}
			if (`k' >= `nalt') {
				di in r "At most nalt-1 alternative-specific constants can be specified"
				exit 498
			}
			if `k'==1 {
				mata: dcreate_ASCMAT = J(strtoreal(st_local("nalt")),1,0)
				mata: dcreate_ASCMAT[strtoreal(st_local("alt")),1] = 1
				mata: dcreate_ASCMAT = J(strtoreal(st_local("nset")),1,dcreate_ASCMAT)
			}
			else {
				mata: dcreate_TEMPMAT = J(strtoreal(st_local("nalt")),1,0)
				mata: dcreate_TEMPMAT[strtoreal(st_local("alt")),1] = 1
				mata: dcreate_TEMPMAT = J(strtoreal(st_local("nset")),1,dcreate_TEMPMAT)
				mata: dcreate_ASCMAT = dcreate_ASCMAT,dcreate_TEMPMAT
			}
			local k = `k' + 1
		}
	}

	mata: dcreate_CANDMAT = select(st_data(.,st_local("explist")),st_matrix(st_local("noomit")))
	mata: dcreate_ORIGMAT = st_data(.,.)

	if ("`fixedalt'" != "") {
		local nobsnofix = `nobs'-`nset'
		mata: dcreate_MAXMAT = J(strtoreal(st_local("nobsnofix")),1,0)
	}
	else mata: dcreate_MAXMAT = J(strtoreal(st_local("nobs")),1,0)

	** Check that the number of specified coefficients is equal to the number of effects in the design **
	if ("`asc'" != "") mata: st_local("colscandmat",strofreal(cols((dcreate_CANDMAT[1,.],dcreate_ASCMAT[1,.]))))
	else mata: st_local("colscandmat",strofreal(cols(dcreate_CANDMAT)))
	local ncoef = colsof(`bmat')
	if (`ncoef' != `colscandmat') {
		di in r "There are `colscandmat' effects in the design and `ncoef' coefficients in `bmat'"
		exit 498
	}

	** Check that there is a sufficent number of choice sets in the design **
	local minset = `ncoef'/(`nalt'-1)
	if (`nset' < `minset') {
		di in r "There are too few choice sets in the design relative to the number of effects to be estimated"
		exit 498
	}

	** Create DEVMAT matrix used to calculate the D-efficiency **
	mata: dcreate_DEVMAT = J(strtoreal(st_local("nobs")),strtoreal(st_local("ncoef")),0)

	keep in 1

	** Check that fixedalt has the correct number of elements if specified **
	mata: dcreate_HASFIX = 0
	if ("`fixedalt'" != "") {
		mata: st_local("colsorigmat",strofreal(cols(dcreate_ORIGMAT)))
		local colsfix = colsof(`fixedalt')
		if (`colsfix' != `colsorigmat') {
			mata: st_local("nobs_restore", strofreal(rows(dcreate_ORIGMAT)))
			set obs `nobs_restore'
			mata: st_view(dcreate_RESTORE=.,.,.)
			mata: dcreate_RESTORE[.,.] = dcreate_ORIGMAT
			di in r "There are `colsorigmat' attributes in the design and `colsfix' attribute levels in `fixedalt'"
			exit 498
		}
		mata: st_view(dcreate_FIX=.,.,.)
		mata: dcreate_FIX[.,.] = st_matrix("`fixedalt'")
		mata: dcreate_FIXORIG = dcreate_FIX
		mata: dcreate_FIX = select(st_data(.,st_local("explist")),st_matrix(st_local("noomit")))
		mata: dcreate_HASFIX = 1
	}

	set obs `nobs'

	** Deal with inputs relating to Bayesian designs **
	if ("`vmat'" != "") {
		mata: dcreate_HASV = 1
		mata: dcreate_B = st_matrix(st_local("bmat"))'
		mata: dcreate_nrep = strtoreal(st_local("nrep"))
		mata: dcreate_B = dcreate_B :+ cholesky(st_matrix(st_local("vmat")))*invnormal(halton(dcreate_nrep,rows(dcreate_B),(1+strtoreal(st_local("burn"))))')
	}
	else {
		mata: dcreate_HASV = 0
		mata: dcreate_B = st_matrix(st_local("bmat"))
	}

	** Evaluate efficiency of random starting design **
	mata: dcreate_DESMAT = startdes(dcreate_CANDMAT)
	scalar `d' = r(d_eff)
	if (r(d_eff) == 0) | (r(d_eff) >= .) {
		mata: st_local("nobs_restore", strofreal(rows(dcreate_ORIGMAT)))
		set obs `nobs_restore'
		mata: st_view(dcreate_RESTORE=.,.,.)
		mata: dcreate_RESTORE[.,.] = dcreate_ORIGMAT
		di in r "The random starting design does not identify all of the effects to be estimated."
		di in r "Possible options include:"
		di in r "- Simplify the model"
		di in r "- Increase the number of choice sets in the design"
		di in r "- Run dcreate again specifying a different random number seed"
		exit 498
	}
	n di
	n di in g "The D-efficiency of the random starting design is: "   /*
			*/ _col(50) in y %13.10f r(d_eff)

	** Modified Fedorov algorithm **
	local k = 1
	while `k' <= `maxiter' {

		mata: dcreate_DESMAT = fedorov(dcreate_CANDMAT, dcreate_DESMAT)
		scalar `dif' = r(d_eff) - `d'
		scalar `d' = r(d_eff)

		n di
		n di in g "D-efficiency after iteration " `k' ":"  /*
			*/ _col(35) in y %13.10f r(d_eff)
		n di in g "Difference:"  /*
			*/ _col(35) in y %13.10f `dif'

		if `dif' < `criterion' {
			n di
			n di in g "The algorithm has converged."
			local k = `maxiter'
		}

		local k = `k' + 1
		if (`k' > `maxiter') & (`dif' >= `criterion') {
			n di
			n di in r "The algorithm did not converge. Try increasing the maximum number of iterations."
		}
	}

	mata: st_view(dcreate_FINDESMAT=.,.,.)

	if ("`fixedalt'" != "") {
		mata: dcreate_nalt = strtoreal(st_local("nalt"))
		mata: dcreate_nset = strtoreal(st_local("nset"))
		mata: dcreate_DESMAT = dcreate_ORIGMAT[dcreate_MAXMAT,.]
		mata: dcreate_FINDESMAT[.,.] = colshape((colshape(dcreate_DESMAT,cols(dcreate_DESMAT)*(dcreate_nalt-1)),J(dcreate_nset,1,dcreate_FIXORIG)),cols(dcreate_DESMAT))
	}
	else mata: dcreate_FINDESMAT[.,.] = dcreate_ORIGMAT[dcreate_MAXMAT,.]

	** Generate variable identifying groups/ choice sets **
	gen choice_set = 0
	local k = 1
	forvalues j = 1(`nalt')`nobs' {
		forvalues i = 1(1)`nalt' {
			local num = `j' + `i' - 1
			qui replace choice_set   = cond(_n==`num',`k',choice_set)
		}
		local k = `k' + 1
	 }

	** Generate variable identifying choices within groups **
	sort choice_set, stable
	by choice_set: gen alt = _n

	** Compare with clogit results - will be identical if design is identified **
	local i = 1
	local estvars
	foreach var of local explist {
		tempvar x`i'
		if `noomit'[1,`i']==1 {
			gen `x`i'' = `var' if `noomit'[1,`i']==1
			local estvars `estvars' `x`i''
		}
		local i = `i'+1
	}
	if ("`asc'" != "") {
		foreach alt of numlist `asc' {
			tempvar asc`alt'
			gen `asc`alt'' = alt==`alt'
			local estvars `estvars' `asc`alt''
		}
	}

	tempvar y
	gen `y' = 0
	replace `y' = 1 if alt==1

	if ("`vmat'" != "") {
		tempname deff
		matrix `deff' = 0

		* Note that dcreate calculates the simulated D-error to be consistent with Sandor and Wedel
		* The simulated D-error is then inverted to calculate the simulated D-efficiency
		forvalues r = 1(1)`nrep' {
			tempname breps
			mata: st_matrix("`breps'",dcreate_B[.,`r'])
			clogit `y' `estvars', group(choice_set) iter(0) from(`breps', copy)
			matrix `deff' = `deff' + det(e(V))^(1/e(df_m))
		}
		matrix `deff' = 1/(`deff'[1,1]/`nrep')

		if (`d'==0 | `d'>=.) | (float(`deff'[1,1])!=float(`d')) {
			n di
			di in r "The generated design does not identify all of the effects to be estimated."
			di in r "Possible options include:"
			di in r "- Simplify the model"
			di in r "- Increase the number of choice sets in the design"
			di in r "- Run dcreate again specifying a different random number seed"
			exit 498
		}
	}
	else {
		clogit `y' `estvars', group(choice_set) iter(0) from(`bmat', copy)
		tempname deff
		matrix `deff' = det(e(V))^(-1/e(df_m))

		if (`d'==0 | `d'>=.) | (float(`deff'[1,1])!=float(`d')) {
			n di
			di in r "The generated design does not identify all of the effects to be estimated."
			di in r "Possible options include:"
			di in r "- Simplify the model"
			di in r "- Increase the number of choice sets in the design"
			di in r "- Run dcreate again specifying a different random number seed"
			exit 498
		}
	}

	return scalar d_eff = `d'

	} // end quietly
end

version 9.2
mata:
function startdes(matrix CANDMAT)
{
	external dcreate_DEVMAT
	external dcreate_HASASC
	external dcreate_HASFIX
	external dcreate_HASV
	external dcreate_B
	if (dcreate_HASASC == 1) external dcreate_ASCMAT
	if (dcreate_HASFIX == 1) external dcreate_FIX

	nalt = strtoreal(st_local("nalt"))
	nset = strtoreal(st_local("nset"))
	ntot = strtoreal(st_local("ntot"))
	nobs = strtoreal(st_local("nobs"))
	nvars = strtoreal(st_local("nvars"))
	ncoef = strtoreal(st_local("ncoef"))

	if (dcreate_HASFIX == 1) nobs = nobs-nset

	RND = floor(rows(CANDMAT) :* uniform(nobs,1)) :+ 1

	DESMAT = CANDMAT[RND,.]

	if (dcreate_HASASC == 1) {
		if (dcreate_HASFIX == 1) {
			EVALMAT = colshape((colshape(DESMAT,cols(DESMAT)*(nalt-1)),J(nset,1,dcreate_FIX)),cols(DESMAT))
			d = deffi((EVALMAT,dcreate_ASCMAT), dcreate_DEVMAT, dcreate_B, nalt, ncoef)
		}
		else d = deffi((DESMAT,dcreate_ASCMAT), dcreate_DEVMAT, dcreate_B, nalt, ncoef)
	}
	else {
		if (dcreate_HASFIX == 1) {
			TEMPMAT = colshape((colshape(DESMAT,cols(DESMAT)*(nalt-1)),J(nset,1,dcreate_FIX)),cols(DESMAT))
			d = deffi(TEMPMAT, dcreate_DEVMAT, dcreate_B, nalt, ncoef)
		}
		else d = deffi(DESMAT, dcreate_DEVMAT, dcreate_B, nalt, ncoef)
	}
	st_numscalar("r(d_eff)", d)

	return(DESMAT)
}
end

version 9.2
mata:
function fedorov(matrix CANDMAT, matrix DESMAT)
{
	external dcreate_MAXMAT
	external dcreate_DEVMAT
	external dcreate_HASASC
	external dcreate_HASFIX
	external dcreate_HASV
	external dcreate_B
	if (dcreate_HASASC == 1) external dcreate_ASCMAT
	if (dcreate_HASFIX == 1) external dcreate_FIX

	nalt = strtoreal(st_local("nalt"))
	nset = strtoreal(st_local("nset"))
	ntot = strtoreal(st_local("ntot"))
	nobs = strtoreal(st_local("nobs"))
	dmax = st_numscalar("r(d_eff)")
	nvars = strtoreal(st_local("nvars"))
	ncoef = strtoreal(st_local("ncoef"))

	if (dcreate_HASFIX == 1) nobs = nobs-nset

	TEMPMAT = DESMAT

	for (j=1; j<=nobs; j++) {
		maxrow = 1
		for (i=1; i<=ntot; i++) {
			TEMPMAT[j,.] = CANDMAT[i,.]

			if (dcreate_HASASC == 1) {
				if (dcreate_HASFIX == 1) {
					EVALMAT = colshape((colshape(TEMPMAT,cols(TEMPMAT)*(nalt-1)),J(nset,1,dcreate_FIX)),cols(TEMPMAT))
					d = deffi((EVALMAT,dcreate_ASCMAT), dcreate_DEVMAT, dcreate_B, nalt, ncoef)
				}
				else d = deffi((TEMPMAT,dcreate_ASCMAT), dcreate_DEVMAT, dcreate_B, nalt, ncoef)
			}
			else {
				if (dcreate_HASFIX == 1) {
					EVALMAT = colshape((colshape(TEMPMAT,cols(TEMPMAT)*(nalt-1)),J(nset,1,dcreate_FIX)),cols(TEMPMAT))
					d = deffi(EVALMAT, dcreate_DEVMAT, dcreate_B, nalt, ncoef)
				}
				else d = deffi(TEMPMAT, dcreate_DEVMAT, dcreate_B, nalt, ncoef)
			}

			if ((d >= dmax) & (d < .)) {
				maxrow = i
				dmax = d
			}
		}
		DESMAT[j,.] = CANDMAT[maxrow,.]
		TEMPMAT = DESMAT
		dcreate_MAXMAT[j,.] = maxrow
	}

	if (dcreate_HASASC == 1) {
		if (dcreate_HASFIX == 1) {
			EVALMAT = colshape((colshape(DESMAT,cols(DESMAT)*(nalt-1)),J(nset,1,dcreate_FIX)),cols(DESMAT))
			d = deffi((EVALMAT,dcreate_ASCMAT), dcreate_DEVMAT, dcreate_B, nalt, ncoef)
		}
		else d = deffi((DESMAT,dcreate_ASCMAT), dcreate_DEVMAT, dcreate_B, nalt, ncoef)
	}
	else {
		if (dcreate_HASFIX == 1) {
			EVALMAT = colshape((colshape(DESMAT,cols(DESMAT)*(nalt-1)),J(nset,1,dcreate_FIX)),cols(DESMAT))
			d = deffi(EVALMAT, dcreate_DEVMAT, dcreate_B, nalt, ncoef)
		}
		else d = deffi(DESMAT, dcreate_DEVMAT, dcreate_B, nalt, ncoef)
	}
	st_numscalar("r(d_eff)", d)

	return(DESMAT)
}
end

version 9.2
mata:
function deffi(matrix XMAT, matrix DEVMAT, matrix B, real scalar nalt, real scalar nvars)
{
	external dcreate_HASV

	if (dcreate_HASV == 1) {
		external dcreate_nrep

		EV = exp(XMAT*B)
		V = 0

		for (r=1; r<=dcreate_nrep; r++) {
			P = colshape(EV[.,r],nalt)'
			P = P :/ colsum(P,1)
			P = colshape(P',1)
			for (k=1; k<=nvars; k++) {
				XP = XMAT[.,k] :* P
				XP = colshape(XP,nalt)'
				XP = colshape(XMAT[.,k],nalt)' :- colsum(XP,1)
				XP = colshape(XP',1)
				DEVMAT[.,k] = XP
			}
			V = V :+ det(cross(DEVMAT, P, DEVMAT))^(-1/nvars)
		}
		return(1/(V:/dcreate_nrep))
	}
	else {
		EV = exp(XMAT*B')
		P = colshape(EV,nalt)'
		P = P :/ colsum(P,1)
		P = colshape(P',1)
		for (k=1; k<=nvars; k++) {
			XP = XMAT[.,k] :* P
			XP = colshape(XP,nalt)'
			XP = colshape(XMAT[.,k],nalt)' :- colsum(XP,1)
			XP = colshape(XP',1)
			DEVMAT[.,k] = XP
		}
		V = cross(DEVMAT, P, DEVMAT)
		return(det(V)^(1/nvars))
	}
}
end