Edit
Rounding error is not the issue. The issue is inherent bias in the likelihood with small sample size. I have accepted JimB's answer. Although, his likelihood and mine are identical and return identical estimates when the same constraints are applied.
Earlier answer
Yesterday I answered my own question. That answer is retained further below. I suggested the reason the estimated $N$ of $79.496$ was different from the true $N$ of $80$ was because of rounding error in the betas. I no longer believe that because if I remove the betas from the likelihood I still get the incorrect answer of $79.496$, as shown with the following R code. Either my likelihood is wrong or there is some fundamental bias in a model of die rolls. I am baffled as to why I cannot accurately estimate the true $N$ of $80$.
# generate some data
N <- 80 # number of trials
pA <- 0.30 # probability of obtaining State A
pB <- 0.45 # probability of obtaining State B
pC <- 0.10 # probability of obtaining State C
pD <- 0.15 # probability of obtaining State D
nA <- N * pA # number of trials resulting in State A
nB <- N * pB # number of trials resulting in State B
nC <- N * pC # number of trials resulting in State C
nD <- N * pD # number of trials resulting in State D
# estimate N with log-likelihood
my.function <- function(betas){
N.est = betas[1]
nA = 24
nB = 36
nC = 8
pA = 0.30
pB = 0.45
pC = 0.10
llh <- ( log(factorial(N.est)) -
(log(factorial(nA)) + log(factorial(nB)) + log(factorial(nC)) + log(factorial(N.est - (nA+nB+nC)))) +
(nA * log(pA) +
nB * log(pB) +
nC * log(pC) +
(N.est - (nA+nB+nC)) * log(1 - (pA+pB+pC))) )
-1 * llh
}
# estimate of N does not match true value of N
my.output <- optim(c((nA+nB+nC)), my.function, method="Brent", lower = 0, upper = 200, hessian=TRUE)
my.output$par
#[1] 79.49601
nlm(my.function, c(80))$estimate
#[1] 79.49597
Here is my answer from yesterday:
I think I have figured out why my search algorithm was returning $N$ = 79.5 instead of the true value of $N$ = 80. I think it was due solely to rounding error in the betas used in the multinomial logistic link.
Originally I obtained the betas using closed-form equations I derived here:
https://math.stackexchange.com/questions/2587203/invert-multinomial-logit-link-with-three-unknown
Then I estimated betas via multinomial logistic regression:
N <- 80 # number of trials
pA <- 0.30 # probability of obtaining State A
pB <- 0.45 # probability of obtaining State B
pC <- 0.10 # probability of obtaining State C
pD <- 0.15 # probability of obtaining State D
nA <- N * pA # number of trials resulting in State A
nB <- N * pB # number of trials resulting in State B
nC <- N * pC # number of trials resulting in State C
nD <- N * pD # number of trials resulting in State D
nA; nB; nC; nD
require(nnet)
my.state <- c(rep('nA', nA), rep('nB', nB), rep('nC', nC), rep('nD', nD))
my.state <- relevel(as.factor(my.state), ref = "nD")
multinom(my.state ~ 1)
#
# weights: 8 (3 variable)
# initial value 110.903549
# final value 98.827745
# converged
# Call:
# multinom(formula = my.state ~ 1)
#
# Coefficients:
# (Intercept)
# nA 0.6931183
# nB 1.0985900
# nC -0.4054899
#
# Residual Deviance: 197.6555
# AIC: 203.6555
#
These multinomial logistic regression betas differ from my closed-form betas in the fifth decimal place, a difference which initially I thought was trivial. However, that difference turns out not to be trivial, as shown below.
Next I compared the counts obtained using my closed-form betas against the true counts, and I compared the counts obtained using the multinomial logistic regression betas against the true counts. My closed-form betas returned counts closer to the true counts. The difference in counts obtained using the two sets of betas are small, but again not trivial:
# These betas are from my closed-form equations
beta.x = 0.6931472
beta.y = 1.098612
beta.z = -0.4054651
# check counts in each class using my closed form betas
(N * exp(beta.x) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z))) - nA
(N * exp(beta.y) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z))) - nB
(N * exp(beta.z) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z))) - nC
(N * (1 - exp(beta.x) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)) -
exp(beta.y) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)) -
exp(beta.z) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)))) - nD
# These betas are obtained using multinomial logistic regression
mr.beta.x = 0.6931183
mr.beta.y = 1.0985900
mr.beta.z = -0.4054899
# check counts in each class using multinomial logistic regression betas
(N * exp(beta.x) / (1 + exp(mr.beta.x) + exp(mr.beta.y) + exp(mr.beta.z))) - nA
(N * exp(beta.y) / (1 + exp(mr.beta.x) + exp(mr.beta.y) + exp(mr.beta.z))) - nB
(N * exp(beta.z) / (1 + exp(mr.beta.x) + exp(mr.beta.y) + exp(mr.beta.z))) - nC
(N * (1 - exp(beta.x) / (1 + exp(mr.beta.x) + exp(mr.beta.y) + exp(mr.beta.z)) -
exp(beta.y) / (1 + exp(mr.beta.x) + exp(mr.beta.y) + exp(mr.beta.z)) -
exp(beta.z) / (1 + exp(mr.beta.x) + exp(mr.beta.y) + exp(mr.beta.z)))) - nD
If I use the multinomial logistic regression betas in my search algorithm the estimated $N$ is 82:
# These betas are from multinomial logistic regression
beta.x = 0.6931183
beta.y = 1.0985900
beta.y = -0.4054899
N.trial <- seq((nA+nB+nC+10), (N+2), by = 0.001)
llh.a <- log( (factorial(N.trial) / (factorial(nA) * factorial(nB) * factorial(nC) * factorial((N.trial - (nA+nB+nC))))) *
( exp(beta.x) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)))^nA *
( exp(beta.y) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)))^nB *
( exp(beta.z) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)))^nC *
(1 - exp(beta.x) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)) -
exp(beta.y) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)) -
exp(beta.z) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)))^(N.trial - (nA+nB+nC)) )
llh.a <- -1 * llh.a
llh <- ( log(factorial(N.trial)) -
(log(factorial(nA)) + log(factorial(nB)) + log(factorial(nC)) + log(factorial((N.trial - (nA+nB+nC))))) +
(nA * log( exp(beta.x) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z))) +
nB * log( exp(beta.y) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z))) +
nC * log( exp(beta.z) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z))) +
(N.trial - (nA+nB+nC)) * log( (1 - exp(beta.x) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)) -
exp(beta.y) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z)) -
exp(beta.z) / (1 + exp(beta.x) + exp(beta.y) + exp(beta.z))))) )
llh <- -1 * llh
# identify number of trials, N, that corresponds to minimum values of -llh
my.data <- data.frame(N.trial = N.trial, llh.a = llh.a, llh = llh)
my.data[my.data$llh.a == min(my.data$llh.a), ]
# N.trial llh.a llh
#4001 82 25.27071 25.27071
my.data[my.data$llh == min(my.data$llh), ]
# N.trial llh.a llh
#4001 82 25.27071 25.27071
The betas obtained via my closed-form equations allowed the algorithm to return an estimate of $N$ within 0.504 of the true value, while the betas obtained from multinomial logistic regression resulted in an estimate of $N$ that differed from the true value by 2.
This is only one data set. Maybe if I try other data sets the beta returned by the two different methods will not differ as much. I guess it makes sense that my closed form equations performed better because there is no real estimation going on to obtain them.
I feel a little bad that rounding or estimation error in the fifth decimal place of the betas can have such a fairly big impact on the estimate on $N$. I do not know how to improve on the estimate. However, I guess a difference of 0.504 from the true value of $N$ is not real bad.