2
2
# Some utilities for affine constraints
3
3
#
4
4
5
- #
5
+ #
6
6
# compute the square-root and inverse square-root of a non-negative
7
7
# definite matrix
8
8
#
9
9
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
+ # '
10
15
factor_covariance = function (S , rank = NA ) {
11
16
if (is.na(rank )) {
12
17
rank = nrow(S )
@@ -26,9 +31,16 @@ factor_covariance = function(S, rank=NA) {
26
31
27
32
# law is Z \sim N(mean_param, covariance) subject to constraints linear_part %*% Z \leq offset
28
33
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.
29
41
whiten_constraint = function (linear_part , offset , mean_param , covariance ) {
30
42
31
- factor_cov = factor_covariance(covariance )
43
+ factor_cov = factor_covariance(as.matrix( covariance ) )
32
44
sqrt_cov = factor_cov $ sqrt_cov
33
45
sqrt_inv = factor_cov $ sqrt_inv
34
46
@@ -41,7 +53,6 @@ whiten_constraint = function(linear_part, offset, mean_param, covariance) {
41
53
new_A = new_A / scaling
42
54
new_b = new_b / scaling
43
55
44
- # TODO: check these functions will behave when Z is a matrix.
45
56
46
57
inverse_map = function (Z ) {
47
58
# broadcasting here
@@ -51,73 +62,62 @@ whiten_constraint = function(linear_part, offset, mean_param, covariance) {
51
62
52
63
forward_map = function (W ) {
53
64
return (sqrt_inv %*% (W - mean_param ))
54
- }
65
+ }
55
66
56
67
return (list (linear_part = new_A ,
57
68
offset = new_b ,
58
69
inverse_map = inverse_map ,
59
70
forward_map = forward_map ))
60
71
}
61
72
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 ,
72
108
ndraw = 8000 ,
73
- burnin = 2000 ,
74
- accept_reject_params = NA ) # TODO: implement accept reject check
109
+ burnin = 2000 )
75
110
{
76
111
77
- whitened_con = whiten_constraint(linear_part ,
112
+ whitened_con = whiten_constraint(linear_part ,
78
113
offset ,
79
- mean_param ,
114
+ mean_param ,
80
115
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
121
121
122
122
# Inf cannot be used in C code
123
123
# In theory, these rows can be dropped
@@ -136,7 +136,7 @@ sample_from_constraints = function(linear_part,
136
136
137
137
# normalize rows to have length 1
138
138
139
- scaling = apply(directions , 1 , function (x ) { return (sqrt(sum(x ^ 2 ))) })
139
+ scaling = apply(directions , 1 , function (x ) { return (sqrt(sum(x ^ 2 ))) })
140
140
directions = directions / scaling
141
141
ndirection = nrow(directions )
142
142
@@ -177,10 +177,54 @@ sample_from_constraints = function(linear_part,
177
177
}
178
178
} else {
179
179
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
+ }
182
186
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 )
185
227
}
186
228
229
+
230
+
0 commit comments