%
% Evaluate error for European call option using
% Monte Carlo, Latin Hypercube and Sobol QMC sampling
% with either Cholesky or PCA factorisation
%

clear all; close all

rng('default')

for pass = 1:2  % two passes for Cholesky and PCA factorisations

  N = 2^20;

  r     = 0.05;
  sigma = 0.2;
  T     = 1;
  K     = 100;
  S0    = 100;

  Sigma = sigma^2*T*( eye(5) + 0.1*(ones(5)-eye(5)));

  if pass==1
    disp('Cholesky factorisation:'); disp(' ');
    L = chol(Sigma,'lower');
  else
    disp('PCA factorisation:'); disp(' ');
    [V,D] = eig(Sigma);
    L = V*diag(sqrt(diag(D)));
  end

% first do standard MC

  U = rand(5,N);         % uniform random variable
  Y = norminv(U);        % inverts Normal cum. fn.
  S = S0*exp((r-sigma^2/2)*T + L*Y);
  F = exp(-r*T)*max(0,sum(S,1)/5-K);

  sum1 = sum(F);
  sum2 = sum(F.^2);
  val  = sum1/N;
  var  = sum2/N - val^2;
  err_bound = 3*sqrt(var/N);
  fprintf(' MC_val        = %f \n',val)
  fprintf(' MC_err_bound  = %f \n\n',err_bound)

% now do Latin Hypercube

  M = 1024;  % number of strata
  N = 1024;  % number of Latin Hypercube samples

  sum1 = 0;
  sum2 = 0;
  m = zeros(5,M);

  for n = 1:N
    for d = 1:5
      m(d,:) = randperm(M); % Latin Hypercube locations
    end

    U = (m-1+rand(5,M))/M;  % random location within each cube 
    Y = norminv(U);         % inverts Normal cum. fn.
    S = S0*exp((r-sigma^2/2)*T + L*Y);
    F = exp(-r*T)*max(0,sum(S,1)/5-K);

    sum1 = sum1 +  sum(F)/M;
    sum2 = sum2 + (sum(F)/M)^2;
  end

  val  = sum1/N;
  var  = sum2/N - val^2;
  err_bound = 3*sqrt(var/N);
  fprintf(' LH_val        = %f \n',val)
  fprintf(' LH_err_bound  = %f \n\n',err_bound)

% now do QMC

  Ntot = 2^20;      % total number of samples to take
  N    = 2^4;       % number of sets
  M    = Ntot / N;  % number of QMC points
  
  sum1 = 0;
  sum2 = 0;
  
  for n = 1:N            % simulate for each set of Sobol points
    Ps = sobolset(5);    % dimension 5
    Ps = scramble(Ps,'MatousekAffineOwen');
    U  = net(Ps,M)';     % generate set of M Sobol points

    Y = norminv(U);      % inverts Normal cum. fn.
    S = S0*exp((r-sigma^2/2)*T + L*Y);
    F = exp(-r*T)*max(0,sum(S,1)/5-K);

    sum1 = sum1 +  sum(F)/M;
    sum2 = sum2 + (sum(F)/M)^2;
  end

  val  = sum1/N;
  var  = sum2/N - val^2;
  err_bound = 3*sqrt(var/(N-1));
  fprintf(' QMC_val       = %f \n',val)
  fprintf(' QMC_err_bound = %f \n\n\n',err_bound)
end
