|
| 1 | +# |
| 2 | +# Some utilities for affine constraints |
| 3 | +# |
| 4 | + |
| 5 | +# |
| 6 | +# compute the square-root and inverse square-root of a non-negative |
| 7 | +# definite matrix |
| 8 | +# |
| 9 | + |
| 10 | +factor_covariance = function(S, rank=NA) { |
| 11 | + if (is.na(rank)) { |
| 12 | + rank = nrow(S) |
| 13 | + } |
| 14 | + svd_X = svd(S, nu=rank, nv=rank) |
| 15 | + sqrt_cov = t(sqrt(svd_X$d[1:rank]) * t(svd_X$u[,1:rank])) |
| 16 | + sqrt_inv = t((1. / sqrt(svd_X$d[1:rank])) * t(svd_X$u[,1:rank])) |
| 17 | + |
| 18 | + return(list(sqrt_cov=sqrt_cov, sqrt_inv=sqrt_inv)) |
| 19 | +} |
| 20 | + |
| 21 | +# |
| 22 | +# from a constraint, return an equivalent |
| 23 | +# constraint and a whitening and inverse |
| 24 | +# whitening map |
| 25 | +# |
| 26 | + |
| 27 | +# law is Z \sim N(mean_param, covariance) subject to constraints linear_part %*% Z \leq offset |
| 28 | + |
| 29 | +whiten_constraint = function(linear_part, offset, mean_param, covariance) { |
| 30 | + |
| 31 | + factor_cov = factor_covariance(covariance) |
| 32 | + sqrt_cov = factor_cov$sqrt_cov |
| 33 | + sqrt_inv = factor_cov$sqrt_inv |
| 34 | + |
| 35 | + new_A = linear_part %*% sqrt_cov |
| 36 | + new_b = offset - linear_part %*% mean_param |
| 37 | + |
| 38 | + # rescale rows to have length 1 |
| 39 | + |
| 40 | + scaling = sqrt(apply(new_A^2, 1, sum)) |
| 41 | + new_A = new_A / scaling |
| 42 | + new_b = new_b / scaling |
| 43 | + |
| 44 | + # TODO: check these functions will behave when Z is a matrix. |
| 45 | + |
| 46 | + inverse_map = function(Z) { |
| 47 | + # broadcasting here |
| 48 | + # the columns of Z are same length as mean_param |
| 49 | + return(sqrt_cov %*% Z + as.numeric(mean_param)) |
| 50 | + } |
| 51 | + |
| 52 | + forward_map = function(W) { |
| 53 | + return(sqrt_inv %*% (W - mean_param)) |
| 54 | + } |
| 55 | + |
| 56 | + return(list(linear_part=new_A, |
| 57 | + offset=new_b, |
| 58 | + inverse_map=inverse_map, |
| 59 | + forward_map=forward_map)) |
| 60 | +} |
| 61 | + |
| 62 | +# |
| 63 | +# sample from the law |
| 64 | +# |
| 65 | +# Z \sim N(mean_param, covariance) subject to constraints linear_part %*% Z \leq offset |
| 66 | + |
| 67 | +sample_from_constraints = function(linear_part, |
| 68 | + offset, |
| 69 | + mean_param, |
| 70 | + covariance, |
| 71 | + initial_point, # point must be feasible for constraints |
| 72 | + ndraw=8000, |
| 73 | + burnin=2000, |
| 74 | + accept_reject_params=NA) #TODO: implement accept reject check |
| 75 | +{ |
| 76 | + |
| 77 | + whitened_con = whiten_constraint(linear_part, |
| 78 | + offset, |
| 79 | + mean_param, |
| 80 | + covariance) |
| 81 | + white_initial = whitened_con$forward_map(initial_point) |
| 82 | + |
| 83 | +# # try 100 draws of accept reject |
| 84 | +# # if we get more than 50 good draws, then just return a smaller sample |
| 85 | +# # of size (burnin+ndraw)/5 |
| 86 | + |
| 87 | +# if accept_reject_params: |
| 88 | +# use_hit_and_run = False |
| 89 | +# num_trial, min_accept, num_draw = accept_reject_params |
| 90 | + |
| 91 | +# def _accept_reject(sample_size, linear_part, offset): |
| 92 | +# Z_sample = np.random.standard_normal((100, linear_part.shape[1])) |
| 93 | +# constraint_satisfied = (np.dot(Z_sample, linear_part.T) - |
| 94 | +# offset[None,:]).max(1) < 0 |
| 95 | +# return Z_sample[constraint_satisfied] |
| 96 | + |
| 97 | +# Z_sample = _accept_reject(100, |
| 98 | +# white_con.linear_part, |
| 99 | +# white_con.offset) |
| 100 | + |
| 101 | +# if Z_sample.shape[0] >= min_accept: |
| 102 | +# while True: |
| 103 | +# Z_sample = np.vstack([Z_sample, |
| 104 | +# _accept_reject(num_draw / 5, |
| 105 | +# white_con.linear_part, |
| 106 | +# white_con.offset)]) |
| 107 | +# if Z_sample.shape[0] > num_draw: |
| 108 | +# break |
| 109 | +# white_samples = Z_sample |
| 110 | +# else: |
| 111 | +# use_hit_and_run = True |
| 112 | +# else: |
| 113 | +# use_hit_and_run = True |
| 114 | + |
| 115 | + use_hit_and_run = TRUE |
| 116 | + |
| 117 | + if (use_hit_and_run) { |
| 118 | + |
| 119 | + white_linear = whitened_con$linear_part |
| 120 | + white_offset = whitened_con$offset |
| 121 | + |
| 122 | + # Inf cannot be used in C code |
| 123 | + # In theory, these rows can be dropped |
| 124 | + |
| 125 | + rows_to_keep = white_offset < Inf |
| 126 | + white_linear = white_linear[rows_to_keep,,drop=FALSE] |
| 127 | + white_offset = white_offset[rows_to_keep] |
| 128 | + |
| 129 | + nstate = length(white_initial) |
| 130 | + if (sum(rows_to_keep) > 0) { |
| 131 | + if (ncol(white_linear) > 1) { |
| 132 | + nconstraint = nrow(white_linear) |
| 133 | + |
| 134 | + directions = rbind(diag(rep(1, nstate)), |
| 135 | + matrix(rnorm(nstate^2), nstate, nstate)) |
| 136 | + |
| 137 | + # normalize rows to have length 1 |
| 138 | + |
| 139 | + scaling = apply(directions, 1, function(x) { return(sqrt(sum(x^2))) }) |
| 140 | + directions = directions / scaling |
| 141 | + ndirection = nrow(directions) |
| 142 | + |
| 143 | + alphas = directions %*% t(white_linear) |
| 144 | + U = white_linear %*% white_initial - white_offset |
| 145 | + Z_sample = matrix(rep(0, nstate * ndraw), nstate, ndraw) |
| 146 | + |
| 147 | + result = .C("sample_truncnorm_white", |
| 148 | + as.numeric(white_initial), |
| 149 | + as.numeric(U), |
| 150 | + as.numeric(t(directions)), |
| 151 | + as.numeric(t(alphas)), |
| 152 | + output=Z_sample, |
| 153 | + as.integer(nconstraint), |
| 154 | + as.integer(ndirection), |
| 155 | + as.integer(nstate), |
| 156 | + as.integer(burnin), |
| 157 | + as.integer(ndraw), |
| 158 | + package="selectiveInference") |
| 159 | + Z_sample = result$output |
| 160 | + } else { # the distribution is univariate |
| 161 | + # we can just work out upper and lower limits |
| 162 | + |
| 163 | + white_linear = as.numeric(white_linear) |
| 164 | + pos = (white_linear * white_offset) >= 0 |
| 165 | + neg = (white_linear * white_offset) <= 0 |
| 166 | + if (sum(pos) > 0) { |
| 167 | + U = min((white_offset / white_linear)[pos]) |
| 168 | + } else { |
| 169 | + U = Inf |
| 170 | + } |
| 171 | + if (sum(neg) < 0) { |
| 172 | + L = max((white_offset / white_linear)[neg]) |
| 173 | + } else { |
| 174 | + L = -Inf |
| 175 | + } |
| 176 | + Z_sample = matrix(qnorm((pnorm(U) - pnorm(L)) * runif(ndraw) + pnorm(L)), 1, ndraw) |
| 177 | + } |
| 178 | + } else { |
| 179 | + Z_sample = matrix(rnorm(nstate * ndraw), nstate, ndraw) |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + Z = t(whitened_con$inverse_map(Z_sample)) |
| 184 | + return(Z) |
| 185 | +} |
| 186 | + |
0 commit comments