Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add doubly stochastic matrices #72

Open
spinkney opened this issue Feb 13, 2022 · 0 comments
Open

add doubly stochastic matrices #72

spinkney opened this issue Feb 13, 2022 · 0 comments

Comments

@spinkney
Copy link
Owner

https://discourse.mc-stan.org/t/doubly-stochastic-matrices-in-stan/9987?u=spinkney

functions {
  /*
     Returns a doubly stochastic matrix by transforming pre-parameters based on
     Section 3.1 of http://proceedings.mlr.press/v84/linderman18a/linderman18a.pdf
     and has the side-effect of modifying target to account for the Jacobian based on
     Section B.2 of http://proceedings.mlr.press/v84/linderman18a/linderman18a-supp.pdf
   
     @param B matrix (square) of pre-parameters all on (0,1)
     @return matrix that is doubly stochastic
     @throws if B is not square, is 0x0, or its elements are not all on (0,1)
  */
  matrix stick_break_lp(matrix B) {
    int Nm1 = rows(B);
    int N = Nm1 + 1;
    matrix[N, N] X;  // the doubly stochastic matrix to output
    row_vector[N] c; // keeps track of the column sums of X
    real r = Nm1 > 0 ? B[1,1] : not_a_number(); // keeps track of the sum of a row of X
    if (cols(B) != Nm1) reject("B is not a square matrix")
    if (Nm1 == 0) return(rep_matrix(1.0, 1, 1));
    X[1,1] = r;
    c[1] = r;
    for (n in 2:Nm1) {
      real B_ = B[1, n];
      real x = B_ * (1 - r);
      if (B_ < 0) reject("B is negative in row 1 and column ", n);
      if (B_ > 1) reject("B is greater than 1.0 in row 1 and column ", n);
      X[1, n] = x;
      r += x;
      c[n] = x;
    }
    X[1, N] = 1 - r;
    c[N] = 1 - r;
    for (m in 2:Nm1) {
      real top_right = sum(c) - c[1]; // keeps track of the top right block of X
      r = 0;
      for (n in 1:Nm1) {
        real l = fmax(0, 1 - N  + n - r + top_right);
        real u = fmin(1 - r, 1 - c[n]);
        real d = u - l;
        real B_ = B[m, n];
        real x = l + B_ * d;
        if (B_ < 0) reject("B is negative in row ", m, " and column ", n);
        if (B_ > 1) reject("B is greater than 1.0 in row ", m, " and column ", n);
/*      
        UNCOMMENT THIS SECTION TO VERIFY THE ACCUMULATIONS USED IN THIS FUNCTION
        if (fabs(r - sum(sub_row(X, m, 1, n - 1))) > 1e-15) 
          reject("wrong row sum");
        if (fabs(c[n] - sum(sub_col(X, 1, n, m - 1))) > 1e-15)
          reject("wrong column sum");
        if (fabs(top_right - sum(block(X, 1, n + 1, m - 1, N - n))) > 1e-15) 
          reject("wrong sum of top right block")
*/      
        target += log(d); // due to Jacobian of map from B to X
        X[m,n] = x;
        r += x;
        top_right -= c[n + 1];
        c[n] += x;
      }
      X[m, N] = 1 - r;
      c[N] += 1 - r;
    }
    for (n in 1:N) X[N, n] = 1 - c[n];
    return X;
  }
}
data {
  int<lower = 1> N;
}
parameters {
  matrix<lower = 0, upper = 1>[N - 1, N - 1] B;
}
model {
  // implies: X ~ jointly uniform over Birkhoff polytope
  matrix[N, N] X = stick_break_lp(B);
/*
  UNCOMMENT THIS SECTION TO VERIFY X IS A DOUBLY STOCHASTIC MATRIX
  vector[N] rowsums = X * rep_vector(1, N);
  row_vector[N] colsums = rep_row_vector(1, N) * X;
  for (n in 1:N) {
    if (fabs(1 - rowsums[n]) > 1e-15) reject("rows not normalized correctly");
    if (fabs(1 - colsums[n]) > 1e-15) reject("columns not normalized correctly");
  }
*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant