*! ovbdc.ado Version 1.0.0 JRC 2024-07-07
program define ovbdc
    version 18.0
    syntax , Means(name) Corr(name) [Verbose ITERate(string) TOLerance(string)]

    mata: ovbdc()
end

version 18.0

mata:
mata set matastrict on

//# Input

real rowvector function Means() {

    real matrix Means
    Means = st_matrix(st_local("means"))

    string scalar orgtype
    orgtype = orgtype(Means)
    if (orgtype == "rowvector") {}
    else if (orgtype == "colvector") Means = Means'
    else {
        errprintf("means must be a vector\n")
        exit(error(503))
    }

    if (rowmissing(Means)) {
        errprintf("elements of means must not be missing\n")
        exit(error(416))
    }
    if (max(Means) >= 1 || min(Means) <= 0) {
        errprintf("elements of means must be between 0 and 1\n")
        exit(error(125))
    }
    return(Means)
}
real matrix function Corr() {

    real matrix Corr
    Corr = st_matrix(st_local("corr"))

    if (orgtype(Corr) != "matrix") {
        errprintf("corr must be a matrix\n")
        exit(error(503))
    }
    if (missing(Corr)) {
        errprintf("elements of the correlation matrix must not be missing\n")
        exit(error(416))
    }
    if (!issymmetric(Corr)) {
        errprintf("corr must be symmetric\n")
        exit(error(505))
    }
    if (max(abs(lowertriangle(Corr, 0))) >= 1) {
        errprintf("elements of corr must be between -1 and 1\n")
        exit(error(125))
    }
    if (!allof(diagonal(Corr), 1)) {
        errprintf("diagonal elements of the correlation matrix must be 1\n")
        exit(508) // exit(error(508))
    }
    return(Corr)
}
void function checkMeansCorr(real vector Means, real matrix Corr) {
    if (length(Means) != rows(Corr)) {
        errprintf("dimensions of means and corr unequal\n")
        exit(error(503))
    }
}

//# Ridders

class Ridders {
    private:
        real scalar tolerance, iterate, converged, iteration
        void new()
        real scalar z0, z1, target
        real scalar f()
        real scalar xl, xm, xh, fl, fm, fh, result, fn
        void initiate(), evaluate(), rebracket(), loop()
    public:
        final real scalar root()
}
void function Ridders::new() {

    string scalar option

    option = st_local("iterate")
    if (strlen(option) > 0) {
        iterate = strtoreal(option)
        if (missing(iterate) | iterate != floor(iterate) | iterate <= 0) {
            printf("option iterate(" + option + ") misspecified; " +
                "using default\n")
            iterate = 30
        }
    }
    else iterate = 30

    option = st_local("tolerance")
    if (strlen(option) > 0) {
        tolerance = strtoreal(option)
        if (missing(tolerance) | tolerance > 1e-6 | tolerance < 1e-10) {
            printf("option tolerance(" + option + ") misspecified; " +
                "using default\n")
            tolerance = 1e-10
        }
    }
    else tolerance = 1e-10
}
real scalar function Ridders::f(real scalar rho) {
    return(binormal(z0, z1, rho) - target)
}
real scalar function Ridders::root(
    real scalar z0,
    real scalar z1,
    real scalar target) {

    this.z0 = z0; this.z1 = z1; this.target = target

    initiate()

    if (converged | missing(result)) return(result)
    else {

        loop()

        if (converged) return(result)
        else return(.c) // Convergence not achieved
    }
}
void function Ridders::initiate() {

    xl = 0.001; xh = 0.999

    converged = iteration = 0

    fl = f(xl)
    if (fl == 0) {
        result = xl
        converged = 1
    }
    else {
        fh = f(xh)
        if (fh == 0) {
            result = xh
            converged = 1
        }
        else if (fl * fh > 0) result = .b // Range does not bound solution
        else {
            result = .n
            evaluate()
        }
    }
}
void function Ridders::evaluate() {

    xm = (xl + xh) / 2
    fm = f(xm)

    real scalar xn
    xn = xm + (xm - xl) * dsign(1, fl - fh) * fm / sqrt(fm * fm - fl * fh)

    if (abs(xn - result) <= tolerance) converged = 1
    else {
        result = xn

        fn = f(xn)
        if (fn == 0) converged = 1
    }
}
void function Ridders::rebracket() {

    if (dsign(fm, fn) != fm) {
        xl = xm
        fl = fm
        xh = result
        fh = fn
    }
    else if (dsign(fl, fn) != fl) {
        xh = result
        fh = fn
    }
    else /* if (dsign(fh, fn) != fh) */ {
        xl = result
        fl = fn
    }
}
void function Ridders::loop() {

    while (!converged & iteration < iterate) {
        rebracket()
        evaluate()
        if (abs(xh - xl) <= tolerance) converged = 1
        iteration++
    }
}

//# PSD

void function warn(real rowvector D) {
    // Uses same tolerance as official Stata _checkpd.ado
    real scalar tolerance
    tolerance =  abs(1e-8 * D[1])

    string scalar warning
    if (rowmin(D) < -tolerance) warning = "indefinite"
    else warning = "not positive definite"

    warning = "Transformed correlation matrix is " + warning +
        "; forcing to positive semidefinite matrix.\n" +
        "Exercise caution in relying upon results.\n"
    printf(warning)
}

real matrix function psd(real matrix C) {

    real matrix Q, D
    symeigensystem(C, Q=(.), D=(.))

    if (strlen(st_local("verbose")) > 0) warn(D)

    D = sqrt(colmax(D \ J(1, cols(D), 0)))
    real matrix A
    A = Q * diag(D)
    return(A')
}

//# Main

void function stripeEm(real scalar size) {

    string vector Names
    Names = "A", "Z"

    real scalar index
    for (index=1; index<=length(Names); index++) {
        st_matrixcolstripe("r(" + Names[index] + ")",
            (J(size, 1, Names[index]) , J(1, 1, strofreal(1::size))))
    }

}

void function ovbdc() {

    real matrix Corr
    Corr = Corr()

    real rowvector Means
    Means = Means()

    checkMeansCorr(Means, Corr)

    class Ridders scalar r

    real matrix C
    C = I(rows(Corr))

    real rowvector Z
    Z = invnormal(Means)

    real scalar row, col, a, b, target, rho, corr
    for (row=2; row<=length(Z); row++) {

        a = Means[row]

        for (col=1; col<=row-1; col++) {

            b = Means[col]

            corr = Corr[row, col]
            if (corr == 0) rho = 0
            else {
                target = a * (1 - a) * b * (1 - b)
                target = sqrt(target)
                target = target * abs(corr)
                target = target + a * b

                rho = r.root(Z[row], Z[col], target)

                if (rho == .b) {
                    printf("failure Ridders to bracket root with proportion " +
                        "pair %1.0f, %1.0f (%04.2f, %04.2f), correlation " +
                        "coefficient %04.2f\n", col, row, b, a, Corr[row, col])
                }
                else if (rho == .c) {
                    printf("failure of Ridders to converge with proportion " +
                        "pair %1.0f, %1.0f (%04.2f, %04.2f), correlation " +
                        "coefficient %04.2f\n", col, row, b, a, Corr[row, col])
                }
                else rho = sign(Corr[row, col]) * abs(rho)
            }

            C[row, col] = rho
        }
    }

    if (hasmissing(lowertriangle(C))) exit(error(504))

    _makesymmetric(C)

    real matrix A
    A = cholesky(C)'

    if (missing(A)) A = psd(C)

    st_matrix("r(A)", A)
    st_matrix("r(Z)", Z)
    stripeEm(cols(Z))
}

end