Skip to content

Commit 66454bf

Browse files
author
yuvalbenjamini
committed
bug fixes in funs.constraint.R for matrix handling and univariate sampling,
and some added documentation. Aligns version of funs.constraints.R with the restrictedNormal package.
1 parent c467ba2 commit 66454bf

File tree

1 file changed

+107
-63
lines changed

1 file changed

+107
-63
lines changed

forLater/maxZ/funs.constraints.R

Lines changed: 107 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
# Some utilities for affine constraints
33
#
44

5-
#
5+
#
66
# compute the square-root and inverse square-root of a non-negative
77
# definite matrix
88
#
99

10+
#' Compute the square-root and inverse square-root of a non-negative definite matrix.
11+
#' @param S matrix
12+
#' @param rank rank of svd
13+
#'
14+
#'
1015
factor_covariance = function(S, rank=NA) {
1116
if (is.na(rank)) {
1217
rank = nrow(S)
@@ -26,9 +31,16 @@ factor_covariance = function(S, rank=NA) {
2631

2732
# law is Z \sim N(mean_param, covariance) subject to constraints linear_part %*% Z \leq offset
2833

34+
#' Transform non-iid problem into iid problem
35+
#' @param linear_part matrix, linear part of constraints
36+
#' @param offset vector, bias of constraints
37+
#' @param mean_param vector of unconditional means
38+
#' @param covariance vector of unconditional covariance
39+
#' @return new \code{linear_part} and \code{offset} for 0-mean iid covariance problem,
40+
#' and functions that map between the two problems.
2941
whiten_constraint = function(linear_part, offset, mean_param, covariance) {
3042

31-
factor_cov = factor_covariance(covariance)
43+
factor_cov = factor_covariance(as.matrix(covariance))
3244
sqrt_cov = factor_cov$sqrt_cov
3345
sqrt_inv = factor_cov$sqrt_inv
3446

@@ -41,7 +53,6 @@ whiten_constraint = function(linear_part, offset, mean_param, covariance) {
4153
new_A = new_A / scaling
4254
new_b = new_b / scaling
4355

44-
# TODO: check these functions will behave when Z is a matrix.
4556

4657
inverse_map = function(Z) {
4758
# broadcasting here
@@ -51,73 +62,62 @@ whiten_constraint = function(linear_part, offset, mean_param, covariance) {
5162

5263
forward_map = function(W) {
5364
return(sqrt_inv %*% (W - mean_param))
54-
}
65+
}
5566

5667
return(list(linear_part=new_A,
5768
offset=new_b,
5869
inverse_map=inverse_map,
5970
forward_map=forward_map))
6071
}
6172

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
73+
#' Sample from multivariate normal distribution under affine restrictions
74+
#' @description
75+
#' \code{sample_from_constraints} returns a sample from the conditional
76+
#' multivariate normal Z~ N(mean,covariance) s.t. A*Z <= B
77+
#'
78+
#' @param linear_part r x d matrix for r restrictions and d dimension of Z
79+
#' @param offset r-dim vector of offsets
80+
#' @param mean_param d-dim mean vector of the unconditional normal
81+
#' @param covariance d x d covariance matrix of unconditional normal
82+
#' @param initial_point d-dim vector that initializes the sampler (must meet restrictions)
83+
#' @param ndraw size of sample
84+
#' @param burnin samples to throw away before storing
85+
#' @return Z ndraw x d matrix of samples
86+
#' @export
87+
#' @examples
88+
#'
89+
#' truncatedNorm = function(1000, c(0,0,0), identity(3), lower = -1,
90+
#' upper = c(2,1,2), start.value = c(0,0,0))
91+
#'
92+
#' constr = thresh2constraints(3, lower = c(1,1,1))
93+
#'
94+
#' samp = sample_from_constraints(linear_part = constr$linear_part,
95+
#' offset= constr$offset,
96+
#' mean_param = c(0,0,0),
97+
#' covariance = diag(3),
98+
#' initial_point = c(1.5,1.5,1.5),
99+
#' ndraw=100,
100+
#' burnin=2000)
101+
#'
102+
103+
sample_from_constraints = function(linear_part,
104+
offset,
105+
mean_param,
106+
covariance,
107+
initial_point,
72108
ndraw=8000,
73-
burnin=2000,
74-
accept_reject_params=NA) #TODO: implement accept reject check
109+
burnin=2000)
75110
{
76111

77-
whitened_con = whiten_constraint(linear_part,
112+
whitened_con = whiten_constraint(linear_part,
78113
offset,
79-
mean_param,
114+
mean_param,
80115
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
116+
white_initial = whitened_con$forward_map(initial_point)
117+
118+
119+
white_linear = whitened_con$linear_part
120+
white_offset = whitened_con$offset
121121

122122
# Inf cannot be used in C code
123123
# In theory, these rows can be dropped
@@ -136,7 +136,7 @@ sample_from_constraints = function(linear_part,
136136

137137
# normalize rows to have length 1
138138

139-
scaling = apply(directions, 1, function(x) { return(sqrt(sum(x^2))) })
139+
scaling = apply(directions, 1, function(x) { return(sqrt(sum(x^2))) })
140140
directions = directions / scaling
141141
ndirection = nrow(directions)
142142

@@ -177,10 +177,54 @@ sample_from_constraints = function(linear_part,
177177
}
178178
} else {
179179
Z_sample = matrix(rnorm(nstate * ndraw), nstate, ndraw)
180-
}
181-
}
180+
}
181+
182+
183+
Z = t(whitened_con$inverse_map(Z_sample))
184+
return(Z)
185+
}
182186

183-
Z = t(whitened_con$inverse_map(Z_sample))
184-
return(Z)
187+
#' Translate between coordinate thresholds and affine constraints
188+
#' @description
189+
#' \code{thresh2constraints} translates lower and upper constraints
190+
#' on coordinates into linear and offset constraints (A*Z <= B).
191+
#' lower and upper can have -Inf or Inf coordinates.
192+
#' @param d dimension of vector
193+
#' @param lower 1 or d-dim lower constraints
194+
#' @param upper 1 or d-dim upper constraints
195+
#' @export
196+
thresh2constraints = function(d, lower = rep(-Inf, d), upper = rep(Inf,d)){
197+
stopifnot(is.element(length(lower),c(1,d)))
198+
stopifnot(is.element(length(upper),c(1,d)))
199+
200+
if (length(lower) == 1){
201+
lower = rep(lower, d)
202+
}
203+
if (length(upper) == 1){
204+
upper = rep(upper, d)
205+
}
206+
207+
208+
linear_part = matrix(ncol = d, nrow = 0)
209+
offset = numeric(0)
210+
lower_constraints = which(lower > -Inf)
211+
for (l in lower_constraints){
212+
new_vec = rep(0,d)
213+
new_vec[l] = -1
214+
linear_part = rbind(linear_part, new_vec)
215+
offset = c(offset, -lower[l])
216+
}
217+
upper_constraints = which(upper < Inf)
218+
for (u in upper_constraints){
219+
new_vec = rep(0,d)
220+
new_vec[u] = 1
221+
linear_part = rbind(linear_part, new_vec)
222+
offset = c(offset, upper[u])
223+
}
224+
225+
constraints = list(linear_part = linear_part, offset = offset)
226+
return(constraints)
185227
}
186228

229+
230+

0 commit comments

Comments
 (0)