*! 1.0.0 Ariel Linden 09Sep2023


capture program drop power_cmd_kappa
program define power_cmd_kappa, rclass
version 12.0


			/* obtain settings */
			syntax anything(id="numlist"),	///
				MArg(numlist)				///
				[ Alpha(real 0.05)        	/// significance level
				n(string)					/// total sample size
				Power(string)				///
				ONESIDed					///
				]

				numlist "`anything'", min(2) max(2)

				gettoken kappa0 rest : anything
				gettoken kappa1 rest : rest

				if `kappa0' < 0 | `kappa0' > 1 {
					noi di in red "Kappa0 must be a number between 0 and 1"
					exit 198
				}
				if `kappa1' < 0 | `kappa1' > 1 {
					noi di in red "Kappa1 must be a number between 0 and 1"
					exit 198
				}

				local varcnt : word count `anything'

				// error checking in marg()
				local margcnt : word count `marg'
				if `margcnt' < 2 {
					noi di in red "At least 2 marginal probabilities must be specified"
					exit 198
				}
				if `kappa0' == `kappa1' {
					noi di in red "Kappa0 must be different than Kappa1"
					exit 198
				}
				if `alpha' < 0 | `alpha' > 1 {
					di as err "option {bf:alpha()} must contain numbers between 0 and 1"
					exit 121
				}

				// tempnames
				tempname A B C D m BB BC CC DD EE equal coeff rhs1 rhsequal lowerbd upperbd margsum PIe PI0 Part1 Part3 Part2 tausq tau tau0 tau1

				clear matrix

				foreach el of local marg {
					if `el' < 0 {
						noi di in red "All marginal probabilities must be positive"
						exit
					}
					matrix `A' = nullmat(`A'),`el'
				}
				mata : st_matrix("`B'", rowsum(st_matrix("`A'")))
				scalar `margsum' = el("`B'",1,1)

				if round(`margsum',0.01) != 1 {
					noi di in red "Marginal probabilities must sum to 1.0"
					exit 198
				}

				*****************************
				// find maximum std error  //
				*****************************
				quietly {
					local cnt = 1

					foreach var of numlist `anything' {

						mat `C' = `A'' * `A'
						mata : st_matrix("`C'", rowsum(diag(st_matrix("`C'"))))
						mata : st_matrix("`D'", colsum(st_matrix("`C'")))
						scalar `PIe'=el("`D'",1,1)
						scalar `PI0' = `var' + `PIe' * (1 - `var')
						scalar `Part1' = `PI0' * (1 - `PIe')^2
						scalar `Part3' = (`PI0' * `PIe'- 2 * `PIe' + `PI0')^2

						mat `m' = J(`margcnt',`margcnt',0)

						forval ii = 1/`margcnt' {
							forval jj = 1/`margcnt' {
								if (`ii'==`jj') {
									mat	`m'[`ii',`ii'] = -1 * (1 -`PI0') * 2 * `A'[1,`ii'] * (2 * (1 - `PIe') - (1 - `PI0') * 2 * `A'[1,`ii'])
								}
								else {
									mat `m'[`ii',`jj'] = (1 - `PI0')^2 * (`A'[1,`ii'] + `A'[1,`jj'])^2
								}
							}
						}

						mat `BC' = I(`margcnt')
						forval i = 1/`margcnt'{
							forval j = 1/`margcnt'{
								mat `BB' = nullmat(`BB'), `BC'[`i', 1...]'
							}
						}

						forval i = 2/`margcnt'{
							mata : st_matrix("`CC'", mm_repeat(st_matrix("`BC'"),1,`i'))
						}

						mat `DD' = J(1,`margcnt'^2,1)
						mata : st_matrix("`EE'", rowshape(st_matrix("`BC'"), 1))

						// constraints for equals
						mat `equal' = `BB' \ `CC' \ `DD' \ `EE'

						// coefficients
						mata : st_matrix("`coeff'", rowshape(st_matrix("`m'"), 1))

						// rhs variables for equals
						mata : st_matrix("`rhs1'", mm_repeat(st_matrix("`A'"),1,2))
						mat `rhsequal' = (`rhs1', 1, `PI0')'

						// set bounds
						mata : st_matrix("`lowerbd'", mm_repeat(0,`margcnt'^2,1)')
						mata : st_matrix("`upperbd'", mm_repeat(.,`margcnt'^2,1)')

						// Compute Part2 using linear programming
						mata:	q = LinearProgram()
						mata:	`coeff'=st_matrix("`coeff'") // coefficients
						mata:	`equal'=st_matrix("`equal'") // constraints that are tested to be equal
						mata:	`rhsequal'=st_matrix("`rhsequal'") // constraints that are tested to be equal
						mata:	`lowerbd' = st_matrix("`lowerbd'")
						mata:	`upperbd' = st_matrix("`upperbd'")
						mata:	q.setCoefficients(`coeff')
						mata:	q.setMaxOrMin("max")
						mata:	q.setEquality(`equal', `rhsequal')
						mata:	q.setBounds(`lowerbd', `upperbd')
						mata:	q.optimize()
						mata:	q.value()
						mata:	st_numscalar("`Part2'", q.value())

						scalar `tausq' = (`Part1' + `Part2' - `Part3')
						scalar `tau' = sqrt(`tausq')/(1-`PIe')^2

						if `cnt' == 1 {
							scalar `tau0' = `tau'
						}
						else if `cnt' == 2 {
							scalar `tau1' = `tau'
						}
						local cnt = `cnt' + 1

						matrix drop `BB' `BC' `CC' `DD' `EE'
					} // end foreach

				} // end quietly

				*******************
				// Sample size  //
				*******************
				if ("`n'" == "") {

					if `power' < 0 | `power' > 1 {
						di as err "option {bf:alpha()} must contain numbers between 0 and 1"
						exit 121
					}
					tempname zalpha zbeta delta n

					// set default test type to "twosided"
					if "`onesided'" == "" {
						local test = `alpha'/ 2
					}
					else local test = `alpha'

					scalar `zalpha' = invnorm(1-`test')
					scalar `zbeta' = invnorm(`power')
					scalar `delta' = abs(`kappa0' - `kappa1')

 					scalar `n' =  ceil((((`zalpha' * `tau0') + (`zbeta' * `tau1')) / (`delta'))^2)
				} // end sample size

				*******************
				// Power  //
				*******************
				else if ("`n'" !="") {

					tempname zalpha delta power

					// set default test type to "twosided"
					if "`onesided'" == "" {
						local test = `alpha'/ 2
					}
					else local test = `alpha'

					scalar `zalpha' = invnorm(1-`test')
					scalar `delta' = abs(`kappa0' - `kappa1')

					scalar `power' = normal(((sqrt(`n' * `delta'^2) - (`zalpha' * `tau0')) / (`tau1')))

				} // end power

				// saved results
				return scalar N = `n'
				return scalar alpha = `alpha'
				return scalar power = `power'
				return scalar kappa0 = `kappa0'
				return scalar kappa1 = `kappa1'
				return scalar delta = `delta'
				return scalar tau0 = `tau0'
				return scalar tau1 = `tau1'


end