*! version 1.1.3  03apr2020  Ben Jann & Simon Seiler

version 11
mata:
mata set matastrict on

void udiff_lf2(transmorphic scalar ML, real scalar todo, real rowvector b,
    real colvector lnf, real matrix S, real matrix H)
{
    real scalar    i, ii, k, kk, K, j, jj, J, base, N, r, rr, c, cc, done
    real rowvector out
    real colvector Y, Yout, D
    real matrix    Zphi, Xpsi, Wtheta, Q, expQ, P, XpsiYi, sumPXpsi, kj, djk

    // constant fluidity model
    if (st_global("UDIFF_mtype")=="0") {
        udiff_lf2_m0(ML, todo, b, lnf, S, H)
        return
    }

    // read globals
    K    = strtoreal(st_global("UDIFF_nunidiff"))
    J    = strtoreal(st_global("UDIFF_nout"))
    out  = strtoreal(tokens(st_global("UDIFF_out")))
    base = strtoreal(st_global("UDIFF_ibase"))

    // get rid of base outcome
    out = select(out, (1..J):!=base)
    J = J - 1

    // get Y and compute equation-level linear predictors
    Y = moptimize_util_depvar(ML, 1); N = rows(Y)
    i = 0
    Zphi = J(N, K, 0)
    for (k=1; k<=K; k++) Zphi[,k] = exp(moptimize_util_xb(ML, b, ++i)) // !!
    Xpsi = J(N, K*J, .); ii = 0; kj = J(K, J, .)
    for (k=1; k<=K; k++) {
        for (j=1; j<=J; j++) {
            Xpsi[,++ii] = moptimize_util_xb(ML, b, ++i)
            kj[k, j] = ii
        }
    }
    Wtheta = J(N, J, 0)
    for (j=1; j<=J; j++) Wtheta[,j] = moptimize_util_xb(ML, b, ++i)

    // compute outcome-level linear predictors
    Q = J(N, J, 0)
    for (j=1; j<=J; j++) Q[,j] = Wtheta[,j] + rowsum(Xpsi[, kj[,j]] :* Zphi)

    // compute denominator
    expQ = exp(Q)
    D = rowsum(expQ) :+ 1

    // generate matrix to select outcomes
    Yout = (Y:==J(N, 1, out))

    // fill in likelihood
    lnf = rowsum(Q :* Yout) - ln(D)
    if (todo==0) return

    // compute probabilities for all outcomes
    P = expQ :/ D

    // sum(P * Xpsi)
    sumPXpsi = J(N, K, 0)
    for (k=1; k<=K; k++) sumPXpsi[,k] = rowsum(P :* Xpsi[, kj[k,]])

    // XPsi for observed outcome
    XpsiYi = J(N, K, 0)
    for (k=1; k<=K; k++) XpsiYi[,k] = rowsum(Xpsi[, kj[k,]] :* Yout)

    // derivatives for Phi
    S = J(N, K + K*J + J, .)
    S[|1,1 \ N,K|] = (XpsiYi - sumPXpsi) :* Zphi        // dPhi
    i = K + 1
    for (k=1; k<=K; k++) {                              // dPsi
        S[|1,i \ N,i+J-1|] = (Yout - P) :* Zphi[,k]
        i = i + J
    }
    S[|1,i \ N,.|] = Yout - P                           // dTheta
    if (todo==1) return

    // generate d_jk = P*(XPsi - sum(P*Xpsi))*exp(ZPhi)
    djk = J(N, K*J, .)
    for (k=1; k<=K; k++) {
        for (j=1; j<=J; j++) {
            djk[,kj[k,j]] = P[,j] :* (Xpsi[,kj[k,j]] - sumPXpsi[,k]) :* Zphi[,k]
        }
    }

    // fill in Hessian
    H = J(length(b), length(b), .)
    r = rr = 1
    for (k=1; k<=K; k++) {
        c = cc = 1
        done = 0
        for (kk=1; kk<=K; kk++) {           // dPhi*dPhi
            if (k==kk)
                done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                (XpsiYi[,k] - rowsum((djk[,kj[k,]] + P):*Xpsi[,kj[k,]]))
                :* Zphi[,k])
            else
                done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                -rowsum(djk[,kj[kk,]]:*Xpsi[,kj[k,]]) :* Zphi[,k])
            if (done) break
        }
    }
    for (k=1; k<=K; k++) {
        for (j=1; j<=J; j++) {
            c = cc = 1
            for (kk=1; kk<=K; kk++) {       // dPsi*dPhi
                if (k==kk)
                    done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                    (Yout[,j] - djk[,kj[k,j]] - P[,j]) :* Zphi[,k])
                else
                    done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                    -djk[,kj[kk,j]] :* Zphi[,k])
            }
            done = 0
            for (kk=1; kk<=K; kk++) {
                for (jj=1; jj<=J; jj++) {   // dPsi*dPsi
                    if (j==jj)
                        done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                        P[,j] :* (P[,j] :- 1) :* Zphi[,k] :* Zphi[,kk])
                    else
                        done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                        P[,j] :* P[,jj] :* Zphi[,k] :* Zphi[,kk])
                    if (done) break
                }
                if (done) break
            }
        }
    }
    for (j=1; j<=J; j++) {
        c = cc = 1
        for (k=1; k<=K; k++) {              // dTheta*dPhi
            done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc, -djk[,kj[k,j]])
        }
        for (k=1; k<=K; k++) {
            for (jj=1; jj<=J; jj++) {       // dTheta*dPsi
                if (j==jj)
                    done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                    P[,j] :* (P[,j] :- 1) :* Zphi[,k])
                else
                    done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                    P[,j] :* P[,jj] :* Zphi[,k])
            }
        }
        done = 0
        for (jj=1; jj<=J; jj++) {           // dTheta*dTheta
            if (j==jj)
                done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                P[,j] :* (P[,j] :- 1))
            else
                done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc, P[,j] :* P[,jj])
            if (done) break
        }
    }
}

real scalar udiff_lf2_H(transmorphic scalar ML, real scalar r, real scalar c,
    real colvector lnf, real matrix H, real scalar rr, real scalar cc,
    real colvector l2)
{
    real scalar a, b, done
    real matrix h

    h = moptimize_util_matsum(ML, r, c, l2, lnf[1])
    a = rows(h)-1; b = cols(h)-1
    H[|rr,cc \ rr+a,cc+b|] = h
    done = (r==c)
    if (done) {
        rr = rr + a + 1
        r++
    }
    else H[|cc,rr \ cc+b,rr+a|] = h'
    cc = cc + b + 1
    c++
    return(done)
}

void udiff_lf2_m0(transmorphic scalar ML, real scalar todo, real rowvector b,
    real colvector lnf, real matrix S, real matrix H)
{
    real scalar    i, ii, k, kk, K, j, jj, J, base, N, r, rr, c, cc, done
    real rowvector out
    real colvector Y, Yout, D
    real matrix    Xpsi, Wtheta, Q, expQ, P, kj

    // read globals
    K    = strtoreal(st_global("UDIFF_nunidiff"))
    J    = strtoreal(st_global("UDIFF_nout"))
    out  = strtoreal(tokens(st_global("UDIFF_out")))
    base = strtoreal(st_global("UDIFF_ibase"))

    // get rid of base outcome
    out = select(out, (1..J):!=base)
    J = J - 1

    // get Y and compute equation-level linear predictors
    Y = moptimize_util_depvar(ML, 1); N = rows(Y)
    i = 0
    Xpsi = J(N, K*J, .); ii = 0; kj = J(K, J, .)
    for (k=1; k<=K; k++) {
        for (j=1; j<=J; j++) {
            Xpsi[,++ii] = moptimize_util_xb(ML, b, ++i)
            kj[k, j] = ii
        }
    }
    Wtheta = J(N, J, 0)
    for (j=1; j<=J; j++) Wtheta[,j] = moptimize_util_xb(ML, b, ++i)

    // compute outcome-level linear predictors
    Q = J(N, J, 0)
    for (j=1; j<=J; j++) Q[,j] = Wtheta[,j] + rowsum(Xpsi[, kj[,j]])

    // compute denominator
    expQ = exp(Q)
    D = rowsum(expQ) :+ 1

    // generate matrix to select outcomes
    Yout = (Y:==J(N, 1, out))

    // fill in likelihood
    lnf = rowsum(Q :* Yout) - ln(D)
    if (todo==0) return

    // compute probabilities for all outcomes
    P = expQ :/ D

    // derivatives for Phi
    S = J(N, K*J + J, .)
    i = 1
    for (k=1; k<=K; k++) {                  // dPsi
        S[|1,i \ N,i+J-1|] = Yout - P
        i = i + J
    }
    S[|1,i \ N,.|] = Yout - P               // dTheta
    if (todo==1) return

    // fill in Hessian
    H = J(length(b), length(b), .)
    r = rr = 1
    for (k=1; k<=K; k++) {
        for (j=1; j<=J; j++) {
            c = cc = 1
            done = 0
            for (kk=1; kk<=K; kk++) {
                for (jj=1; jj<=J; jj++) {   // dPsi*dPsi
                    if (j==jj)
                        done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                        P[,j] :* (P[,j] :- 1))
                    else
                        done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                        P[,j] :* P[,jj])
                    if (done) break
                }
                if (done) break
            }
        }
    }
    for (j=1; j<=J; j++) {
        c = cc = 1
        for (k=1; k<=K; k++) {
            for (jj=1; jj<=J; jj++) {       // dTheta*dPsi
                if (j==jj)
                    done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                    P[,j] :* (P[,j] :- 1))
                else
                    done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                    P[,j] :* P[,jj])
            }
        }
        done = 0
        for (jj=1; jj<=J; jj++) {           // dTheta*dTheta
            if (j==jj)
                done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc,
                P[,j] :* (P[,j] :- 1))
            else
                done = udiff_lf2_H(ML, r, c, lnf, H, rr, cc, P[,j] :* P[,jj])
            if (done) break
        }
    }
}

end
exit