├── MAS.m ├── README.md ├── SS.m ├── demo.m ├── rot.m ├── subset_simulation.pdf ├── tpf.m └── tpfparams.mat /MAS.m: -------------------------------------------------------------------------------- 1 | function [theta_ip1, gval] = MAS(theta_i,pi_prop,Sigma,... 2 | g,b,gsettings,mma,gval_old) 3 | 4 | if (nargin ~= 8) 5 | error('Incorrect number of parameters in function "MAS"'); 6 | end 7 | 8 | %% Initialize variables 9 | n = length(theta_i); % Number of uncertain parameters 10 | xi = zeros(1,n); % \xi 11 | hat_xi = zeros(1,n); % \tilde{\xi} 12 | 13 | %% 1. Sampling from proposal PDF with media the current state 14 | if mma==1 % mma tells whether use Au/Beck MMA algorthm or new multivar draw 15 | accept = 0; % Change accept below if any value changes in theta 16 | for k = 1:n % We loop through each element of theta 17 | hat_xi(k) = pi_prop(theta_i(k),Sigma); % Propose new theta_i(k) 18 | % Get likelihood ratio: must use MH since we using truncated normals 19 | lhr_num = (normcdf(1-theta_i(k))-normcdf(-theta_i(k)) ) * ... 20 | (hat_xi(k)<1) * (hat_xi(k)>0); 21 | lhr_den = normcdf(1-hat_xi(k)) - normcdf(-hat_xi(k)); 22 | r = lhr_num/lhr_den; 23 | 24 | if rand <= min(1,r) % Accept new draw with probability r 25 | xi(k) = hat_xi(k); % Accept the candidate 26 | accept = 1; 27 | else 28 | xi(k) = theta_i(k); % Reject the candidate and use the same state 29 | end 30 | end 31 | else % If using new multivar draw instead of Au/Beck MMA algorithm: 32 | hat_xi = pi_prop(theta_i,Sigma); % Get proposal for new theta_i draw 33 | % Get likelihood ratio on log scale: 34 | lhr_num = sum( log(hat_xi .* (1-hat_xi))); 35 | lhr_den = sum( log(theta_i .* (1-theta_i))); 36 | r = (lhr_num-lhr_den); % Here's the likelihood ratio 37 | if log(rand) <= r % We accept the new draw with probability r 38 | xi = hat_xi; 39 | accept=1; 40 | else 41 | xi = theta_i; 42 | accept=0; 43 | end 44 | end 45 | 46 | %% 2. Check the location of xi by system analysis 47 | % The support for the distribution we're drawing from is limited to those 48 | % points with g-value above the current threshold b. So if our newly 49 | % accepted draw has g-value below b, then it falls outside of the support 50 | % of the distribution we're trying to draw from, and we should reject it 51 | % after all. So here we test its g-value, and reject the new draw if it 52 | % does not rise above the current threshold b. 53 | if accept == 1 % We only need to check the g-value if we accepted new draw 54 | gval = g(gsettings,xi); 55 | if gval >= b 56 | theta_ip1 = xi; % Accept the candidate in failure region 57 | new = 1; 58 | else 59 | theta_ip1=theta_i; 60 | gval = gval_old; 61 | new=0; 62 | end 63 | else 64 | gval = gval_old; 65 | theta_ip1 = theta_i; 66 | new = 0; 67 | end 68 | 69 | return; 70 | %%END -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Subset simulation with multivariate draw 2 | Carl Ehrett, 2017 3 | 4 | ## Nature of project 5 | Subset simulation (SS) is a method of estimating low probability events. Suppose that one has a computer model predicting outcomes in a situation of interest. An example would be a model that predicts the amount of downtime suffered by a power plant following an earthquake. To assess the robustness of one's power infrastructure, one might wish to know the probability that the plant's downtime will exceed (e.g.) two months. Downtimes this long are extremely rare, and as a result, it is difficult to estimate their probability. 6 | 7 | For example, suppose that the probability of suffering such a long downtime following an earthquake (given that an earthquake of a given magnitude occurs) is (unbeknownst to us) 0.001. Thus our region of interest F is the set of downtimes exceeding two months, and we wish to estimate the probability of F. A common method of estimating the probability of a subregion of a computer model's range is to perform Monte Carlo estimation of that subregion, in which inputs to the model are randomly generated (from a prior on those inputs) and then the computer model output corresponding to those inputs is observed. Where M sets of inputs are drawn, some fraction p/M of the computer model outputs will be seen to fall in the region of interest F. Thus p/M becomes our estimate of the probability of F. This approach is problematic if our computer model is computationally expensive, since we will have to run the model on average 1000 times to get a single observation falling in the region F. So unless we are willing to spend copious time and computational resources on running our model millions of times, our estimate will have very high variance. 8 | 9 | By contrast, subset simulation uses a series of intermediate failure regions, finding their conditional probabilities and using these to estimate the probability of F. For example, we might first find the probability of downtime exceeding one day, then the probability that downtime exceeds one week *given* that it exceeds one day, then the probability that downtime exceeds one month *given* that it exceeds one week, and so on. Markov chain Monte Carlo methods (MCMC) are used to sample from each of these conditional distributions. The product of the probabilities of these failure regions gives the probability of F, the region of interest. 10 | 11 | Here I adapt SS to perform well with inputs that are correlated with respect to the failure region. That is, SS typically draws the model inputs when performing each MCMC using independent proposal densities. When the failure region F is spherical, this works well. But when F is not spherical, the inputs are correlated with respect to the failure region, and we can use the correlation observed in the intermediate failure regions to improve the proposal density of the model inputs and thereby massively improve the efficiency of the algorithm. 12 | 13 | For full background, mathematical details, and results (both in the example case demonstrated in demo.m and in an application predicting power plant downtime following seismic events), please see the [included report](subset_simulation.pdf). 14 | 15 | ## Files 16 | [demo.m](demo.m) is a MATLAB script which demonstrates the proposed methodology and "stock" SS, using the example computer model tpf.m. 17 | 18 | [tpf.m](tpf.m) is an example performance function to demonstrate SS. What tpf does is to find the inverse distance of a point from the origin, scaled using a hyperellipse. Different dimensionalities of inputs can be used, but by default demo.m uses a 100-dimensional input space of which four dimensions form the hyperellipse. So the set of points such that tpf returns a value greater than (e.g.) 1 is the set of points inside a shape which is a hyperellipse in four dimensions and which fills the unit hypercube in the remaining 96 dimensions. The only important point here is that having a tpf score above 1 means that you are inside a certain subregion of the unit hypercube, and the volume of that region is difficult to estimate via Monte Carlo because it is so small. Subset simulation can be used to estimate its volume. 19 | 20 | [tpfparams.mat](tpfparams.mat) contains settings used by the example performance function tpf.m. 21 | 22 | [rot.m](rot.m) is a script that produces a random rotation matrix of any desired dimension; it was used to produce tpfparams.mat. 23 | 24 | [SS.m](SS.m) performs subset simulation. To do so, it also calls MAS.m. 25 | 26 | [MAS.m](MAS.m) performs the Metropolis-Hastings sampling for the MCMC in each intermediate failure region. 27 | 28 | [subset_simulation.pdf](subset_simulation.pdf) This report describes in detail subset simulation, the proposed alteration of subset simulation, and the results achieved thereby. 29 | 30 | ## To use this software 31 | 32 | Load the file demo.m to run the demo. If desired, you can there replace tpf.m with whichever performance function you wish to use. the variable B in demo.m should also be changed to define whatever is the region of interest in the computer model you use to replace tpf. 33 | 34 | Running the demo will show that the proposed version of SS can be significantly faster and more computationally efficient than the "stock" version. 35 | -------------------------------------------------------------------------------- /SS.m: -------------------------------------------------------------------------------- 1 | function [Pf_SS,Pf,gsort,b,F_total,F_seeds,... 2 | theta_rec,theta_rec_u,uniques,Nf,geval] = ... 3 | SS(n,N,p0,B,g,gsettings,mma) 4 | % Subset Simulation 5 | 6 | %% Check inputs 7 | 8 | if (nargin ~= 7) 9 | error('Incorrect number of parameters in function "SS"'); 10 | end 11 | tic; 12 | 13 | %%% Proposal and target distributions 14 | pi_targ = @() rand(n,1); % Prior distribution 15 | if mma == 1 % Recall mma tells whether use Au/Beck MMA or 16 | % new multivar draw w/ logit transform 17 | pi_prop = @(x,Sigma) (Sigma * normrnd(0,1) + x) ; 18 | else 19 | logit_trans = @(x) log(x ./ (1-x)); 20 | logit_rev_trans = @(x) exp(x) ./ (1+exp(x)); 21 | pi_prop = @(x,Sigma) logit_rev_trans(mvnrnd(logit_trans(x),Sigma)); 22 | end 23 | 24 | %% Initialization of variables and storage 25 | j = 1; % initial conditional level 26 | Ns = 1/p0; % number of samples simulated form each Markov chain 27 | if mod(N,Ns) ~= 0 28 | N = N + Ns-mod(N,Ns) ; % Necessary to make Nc an integer 29 | end 30 | Nc = N*p0; % number of markov chains 31 | est_it = 20; % estimated # of iteratns (serves as ad hoc max # of iteratns) 32 | theta_j = zeros(n,N); 33 | geval = zeros(1,N); 34 | gsort = zeros(1,N); 35 | Nf = zeros(est_it,1); 36 | Pf = zeros(est_it,1); 37 | b = zeros(est_it,1); 38 | F_total = cell(est_it,1); 39 | F_seeds = cell(est_it,1); 40 | theta_l = zeros(n,Ns); 41 | theta_jk = cell(Nc,1); 42 | theta_rec = cell(1,1); 43 | theta_rec_u = cell(1,1); 44 | uniques = zeros(est_it,1); 45 | Sigma = 1e-1; % This is changed below before being used if mma==0 46 | sig_c1 = 1e-1; % These constants scale the variance and the identity 47 | sig_c2 = 1e-6; % addition to it to improve convergence; 1e0,1e-8; 1e1,1e-8 48 | % work well for hyperellipsoid, earthquake respectively 49 | 50 | %%%% SS procedure 51 | %%%% initial MCS stage 52 | fprintf('Evaluating performance function:\t'); 53 | msg=0; 54 | for i = 1:N 55 | theta_j(:,i) = pi_targ(); 56 | geval(j,i) = g(gsettings,theta_j(:,i)'); 57 | if geval(j,i) >= B 58 | Nf(j) = Nf(j)+1; 59 | end 60 | 61 | % Update user via console 62 | fprintf(repmat('\b',1,msg)); 63 | msg = fprintf('\n**Evaluating sample no. %g/%g',i,N); 64 | 65 | end 66 | % Update console 67 | fprintf('\nInitial MCS complete! Nf(1)=%g\n',Nf(1)); 68 | 69 | %%%% SS stage 70 | last = 0; % This indicator will turn to 1 when we're on final loop 71 | while last ~= 1 72 | if Nf(j)/N >= p0 73 | last = 1; % need one more run to compute the curve 74 | end 75 | j = j+1; % next level 76 | 77 | % Sort values and define new intermediate failure region threshold 78 | [gsort(j-1,:),idx] = sort(geval(j-1,:)); % ascending 79 | F_total{j-1} = theta_j(:,idx); % store ordered level samples 80 | F_seeds{j-1} = theta_j(:,idx(N-Nc+1:N));% store ordered level seeds 81 | % Recall mma tells whether use Au/Beck MMA algorthm or new multivar draw 82 | if mma == 0 83 | % Get new cov matrix for proposal dist: 84 | Sigma = sig_c1*(cov(logit_trans(F_seeds{j-1})') + sig_c2 * eye(n)); 85 | end 86 | b(j-1) = (gsort(j-1,N-Nc)+gsort(j-1,N-Nc+1))/2; % Get the p0 quantile 87 | fprintf('\n-Current threshold level %g is: %g \n', j-1, b(j-1)); 88 | 89 | % Sampling process using MCMC 90 | gvals = zeros(N,1); % Will hold g values for this round of theta_j draws 91 | for k = 1:Nc % For each MCMC chain used this round: 92 | msg = fprintf('***Sampling from seed no. %g/%g', k, Nc); 93 | theta_l(:,1) = F_seeds{j-1}(:,k); % Set the seed for MCMC 94 | gvals((k-1)*Ns+1) = g(gsettings,theta_l(:,1)'); % Get seed's g-value 95 | for l = 1:Ns-1 % For each step in the MCMC chain: 96 | % Make a draw from the MCMC chain (using MAS function): 97 | [theta_l(:,l+1),gval] = ... 98 | MAS(theta_l(:,l),pi_prop,Sigma,g,... 99 | b(j-1),gsettings,mma,gvals((k-1)*Ns+l)); 100 | gvals((k-1)*Ns+1+l) = gval; % Record g-value of new draw 101 | end 102 | theta_jk{k} = theta_l; % This chain is recorded in theta_jk as cell k 103 | fprintf(repmat('\b',1,msg)); % Remove old console output 104 | end 105 | 106 | % Recordkeeping 107 | theta_j = cell2mat(theta_jk'); % Transform all chains drawn to matrix 108 | theta_rec{j-1}=theta_j; % Record matrix of this round's draws in a cell 109 | theta_rec_u{j-1}=unique(theta_j','rows'); % Record unique samples only 110 | % This records how many unique samples we got in each step: 111 | uniques(j-1) = size(theta_rec_u{j-1},1); 112 | fprintf('Unique samples: %g\n',uniques(j-1)); 113 | 114 | % Count new samples in failure region of interest 115 | fprintf('\nEvaluating performance function:\t'); 116 | msg=0; 117 | for i = 1:N 118 | %geval(j,i) = g(pga,t_vec,theta_j(:,i)'); 119 | geval(j,i) = gvals(i); 120 | if geval(j,i) >= B 121 | Nf(j) = Nf(j)+1; 122 | end 123 | end 124 | fprintf('\nOK! Nf(%g)=%g\n',j,Nf(j)); 125 | 126 | if j > est_it % Change as desired to stop loop early; o/w set to est_it 127 | last=1; 128 | end 129 | end 130 | 131 | % delete unnecesary data 132 | if j < est_it 133 | F_total(j:end) = []; 134 | F_seeds(j:end) = []; 135 | Nf(j:end) = []; 136 | Pf(j:end) = []; 137 | b(j:end) = []; 138 | end 139 | 140 | % %% pf calculation 141 | % Pf(1) = p0; 142 | % Pf_line(1,:) = linspace(p0,1,1000); 143 | % b_line(1,:) = gsort(1,round((N-N*Pf_line(1,:))+1)); 144 | % for i = 2:j-1 145 | % Pf(i) = Pf(i-1)*p0; 146 | % Pf_line(i,:) = Pf_line(i-1,:)*p0; 147 | % b_line(i,:) = gsort(i,round((N-N*Pf_line(1,:))+1)); 148 | % end 149 | % Pf_line = sort(Pf_line(:),'descend'); 150 | % pf_ss = Pf_line; 151 | % b_line = sort(b_line(:)); 152 | % b_ss = b_line; 153 | % Pf_SS = p0^(j-2)*(Nf(j-1)/N); 154 | 155 | Pf_SS = 0; % This will be our prob estimate of F 156 | % % OLD VERSION: 157 | % for ii = 1:(j-1) 158 | % Pf_SS = Pf_SS + p0^(ii-1) * (Nf(ii)/N); 159 | % end 160 | % Pf_SS = Pf_SS / (j-1); 161 | % New version: 162 | Pf_SS = p0^(j-2) * Nf(j-1)/N ; 163 | 164 | % gvals = sort(gsort(:)); 165 | % b_line(1,:) = linspace(min(gvals),max(gvals),1000); 166 | % Pf_line = zeros(1,size(b_line,2)); 167 | % for ii = 1:size(b_line,2) 168 | % pfv=0; 169 | % n_levels = min(sum(b<=b_line(ii))+1,j-1); 170 | % for jj = 1:n_levels 171 | % pfv = pfv + sum(gsort(jj,:)>= b_line(ii))/N * p0^(jj-1); 172 | % end 173 | % Pf_line(ii) = pfv/n_levels; 174 | % end 175 | 176 | %b_ss=b_line; 177 | %pf_ss=Pf_line; 178 | 179 | toc; 180 | 181 | return; 182 | 183 | end 184 | %%END -------------------------------------------------------------------------------- /demo.m: -------------------------------------------------------------------------------- 1 | % Master file for running Multivariate Draw Subset Simulation 2 | clear; close all; clc; 3 | 4 | %% Initial settings 5 | %%% Configuration parameters 6 | N = 1000; % Total number of samples for each level 7 | p0 = 1/10; % Probability of each adaptively chosen subset 8 | 9 | %%% Set performance function, and settings for it: 10 | g = @tpf; 11 | % B is the threshold defining region of interest: it defines the region of 12 | % interest to be those points x such that g(x)>B. When g is the function 13 | % tpf.m, B=1 corresponds to finding points that are within the 14 | % hyperellipsoid with four semiprincipal axes, the jth semi-principal axis 15 | % of which is of length 1/(j^2). 16 | B = 1; 17 | n = 100; % Dimensionality of the hyperellipse 18 | load tpfparams 19 | % tpfparams.mat contains two things: 20 | % 1. rotation_matrix - included so that the hyperellipse of which we're 21 | % finding the volume gets rotated. The semiprincipal axes of the resulting 22 | % hyperellipse are not axis-aligned in the supporting R^n space. This 23 | % matrix was produced using the rot.m file, with rng(1). Since the volume 24 | % we're trying to find is actually the intersection of a hyperellipse with 25 | % a hypercube, the volume of the resulting intersection changes depending 26 | % on how the hyperellipse is rotated in the hypercube. Which is to say that 27 | % a different rotation than the one provided here will yield a different 28 | % volume. 29 | % 2. hyperellipse_indices - this tells which of the n dimensions are the 30 | % semiprincipal axes of the (unrotated, axis-aligned) hyperellipse. 31 | 32 | %% Perform Subset simulation 33 | % Pf_SS is the estimated probability of the failure region. When g is the 34 | % function tpf.m and B is 1 with the rotation defined in tpfparams, the 35 | % true value here is approximately 1.5e-3. The estimate Pf_SS will vary 36 | % around this value. 37 | 38 | m = 10 ; 39 | % We'll run each of the two versions of SS m times, recording each time the 40 | % estimating probability Pf_SS of the failure region and the time taken to 41 | % run the algorithm. After the two algorithms have each run m times, we can 42 | % compare their estimates and the time taken to achieve those estimates. 43 | 44 | %%% First use the ``stock'' version of SS 45 | mma = 1 ; % flag; set to 0 to use new multivariate draw, 46 | %%%% to 1 for Au/Beck MMA algorithm 47 | Pf_SS_stock = zeros(m,1); 48 | times_stock = zeros(m,1); 49 | for ii=1:m 50 | tic; 51 | fprintf('\n\n=================='); 52 | fprintf('\nSTOCK SS LOOP %g/%g',ii,m); 53 | fprintf('\n==================\n\n'); 54 | [Pf_SS,Pf,gsort,b,F_total,F_seeds,... 55 | theta_rec,theta_rec_u,uniques,Nf,geval] = ... 56 | SS(n,N,p0,B,g,gsettings,mma); 57 | fprintf('\n***SubSim Pf: %g ***\n', Pf_SS); 58 | Pf_SS_stock(ii) = Pf_SS; 59 | times_stock(ii) = toc; 60 | end 61 | 62 | %%% Now use the new proposed multivariate draw version of SS 63 | mma = 0 ; % flag; set to 0 to use new multivariate draw, 64 | %%%% to 1 for Au/Beck MMA algorithm 65 | Pf_SS_mvd = zeros(m,1); 66 | times_mvd = zeros(m,1); 67 | for ii=1:m 68 | tic; 69 | fprintf('\n\n=============================='); 70 | fprintf('\nMULTIVARIATE DRAW SS LOOP %g/%g\n\n',ii,m); 71 | fprintf('\n==============================\n\n'); 72 | [Pf_SS,Pf,gsort,b,F_total,F_seeds,... 73 | theta_rec,theta_rec_u,uniques,Nf,geval] = ... 74 | SS(n,N,p0,B,g,gsettings,mma); 75 | fprintf('\n***SubSim Pf: %g ***\n', Pf_SS); 76 | Pf_SS_mvd(ii) = Pf_SS; 77 | times_mvd(ii) = toc; 78 | end 79 | 80 | %%% Now, compare the results 81 | fprintf('\n====================='); 82 | fprintf('\nCOMPARISON OF RESULTS'); 83 | fprintf('\n=====================\n'); 84 | fprintf... 85 | ('\n\nMean estimate found via stock SS: %g\n',mean(Pf_SS_stock)); 86 | fprintf('Std. dev. of estimate found via stock SS: %g\n',std(Pf_SS_stock)); 87 | fprintf('Mean time in seconds to complete stock draw SS: %g\n',... 88 | mean(times_stock)); 89 | fprintf... 90 | ('\nMean estimate found via multivariate draw SS: %g\n',... 91 | mean(Pf_SS_mvd)); 92 | fprintf('Std. dev. of estimate found via multivariate draw SS: %g\n',... 93 | std(Pf_SS_mvd)); 94 | fprintf('Mean time in seconds to complete multivariate draw SS: %g\n',... 95 | mean(times_mvd)); 96 | 97 | 98 | -------------------------------------------------------------------------------- /rot.m: -------------------------------------------------------------------------------- 1 | % Produce rotation matrix. 2 | % Details: input is dimension n of vector space for rotation. 3 | % A random permutation of 1:n is generated and stored as indx. 4 | % Then for i=1,...,n-1, a rotation of pi/4 rad between indx(i) 5 | % and indx(i+1) is performed. 6 | % The resulting rotation matrix Q is output. 7 | % Carl Ehrett 2017 8 | 9 | function [Q] = rot(n) 10 | 11 | rindx = randperm(n); 12 | 13 | Q = eye(n); 14 | stot=sqrt(2)/2; 15 | msg=0; 16 | 17 | for ii = 1 : (n-1) 18 | rii = rindx(ii); 19 | rjj = rindx(ii+1); 20 | q = speye(n); 21 | q(rii,rii) = stot; 22 | q(rjj,rjj) = stot; 23 | q(rjj,rii) = stot; 24 | q(rii,rjj) = -stot; 25 | Q = q * Q; 26 | fprintf(repmat('\b',1,msg)); 27 | msg=fprintf('Current column: %g/%g\n', ii,n); 28 | end 29 | 30 | end 31 | 32 | % Saving the below as backup 33 | % function [Q] = rot(n) 34 | % 35 | % rindx = randperm(n); 36 | % 37 | % Q = eye(n); 38 | % stot=sqrt(2)/2; 39 | % msg=0; 40 | % 41 | % for ii = 1 : (n-1) 42 | % rii = rindx(ii); 43 | % for jj = (ii+1):n 44 | % rjj = rindx(jj); 45 | % q = speye(n); 46 | % q(rii,rii) = stot; 47 | % q(rjj,rjj) = stot; 48 | % q(rjj,rii) = stot; 49 | % q(rii,rjj) = -stot; 50 | % Q = q * Q; 51 | % end 52 | % fprintf(repmat('\b',1,msg)); 53 | % msg=fprintf('Current column: %g/%g\n', ii,n); 54 | % end 55 | % 56 | % end -------------------------------------------------------------------------------- /subset_simulation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cehrett/Subset_simulation_with_multivariate_draw/d4b50b63c8736f1ba8d724a8f83dee07a7e091eb/subset_simulation.pdf -------------------------------------------------------------------------------- /tpf.m: -------------------------------------------------------------------------------- 1 | % Toy performance function 2 | % Just distance from the origin scaled by a hyperellipsoid. 3 | % The jth semi-principal axis is of length 1/(j^2*sqrt(c)), c given below. 4 | 5 | function [gval] = tpf(gsettings,x) % settings here includes the rotation mat 6 | % Unpack settings 7 | Q = gsettings.rotation_matrix ; 8 | idx = gsettings.hyperellipse_indices ; 9 | 10 | y = 2 * x - 1; % Convert from [0,1] to [-1,1] 11 | unrotx = Q * y'; % Q "unrotates" so the hyperellipse is axis-aligned 12 | % The axes of the hyperellipse are given by idx 13 | pt = unrotx(idx); 14 | n=length(idx); % Dimensionality of the ellipse 15 | c=1; 16 | sdist = 0; % We'll add to this in below loop to get point's scaled norm 17 | for ii = 1:n 18 | %denoms[ii]=ii^2 19 | sdist = sdist + pt(ii)^2 * ii^4 * c; 20 | end 21 | gval=1/sqrt(sdist); 22 | return; 23 | 24 | -------------------------------------------------------------------------------- /tpfparams.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cehrett/Subset_simulation_with_multivariate_draw/d4b50b63c8736f1ba8d724a8f83dee07a7e091eb/tpfparams.mat --------------------------------------------------------------------------------