%r = nr_38_211_low_papr_seq(n, u, v, alpha, delta, M_ZC)
%
% Generates Low-PAPR sequence as specified by 3GPP 38.211 
% sec. 5.2.2.
%
% Arguments:
%  n         - vector of sequence indices to be generated
%  u         - group number
%  v         - base sequence number
%  d         - vector of modulation symbols
%  alpha     - cyclic shift of the sequence
%  delta     - sequence stretch parameter
%  M_ZC      - sequence length
%
% Returns:
%  r         - vector of low-PAPR sequence

% Copyright 2018 Grzegorz Cisek (grzegorzcisek@gmail.com)

function r = nr_38_211_low_papr_seq(n, u, v, alpha, delta, M_ZC)
  assert(all(n >= 0), 'indices in n must be non-negative');

  persistent phi6 phi12 phi18 phi24

  if isempty(phi6)
    phi6 = [-3  -1  3 3 -1  -3
            -3  3 -1  -1  3 -3
            -3  -3  -3  3 1 -3
            1 1 1 3 -1  -3
            1 1 1 -3  -1  3
            -3  1 -1  -3  -3  -3
            -3  1 3 -3  -3  -3
            -3  -1  1 -3  1 -1
            -3  -1  -3  1 -3  -3
            -3  -3  1 -3  3 -3
            -3  1 3 1 -3  -3
            -3  -1  -3  1 1 -3
            1 1 3 -1  -3  3
            1 1 3 3 -1  3
            1 1 1 -3  3 -1
            1 1 1 -1  3 -3
            -3  -1  -1  -1  3 -1
            -3  -3  -1  1 -1  -3
            -3  -3  -3  1 -3  -1
            -3  1 1 -3  -1  -3
            -3  3 -3  1 1 -3
            -3  1 -3  -3  -3  -1
            1 1 -3  3 1 3
            1 1 -3  -3  1 -3
            1 1 3 -1  3 3
            1 1 -3  1 3 3
            1 1 -1  -1  3 -1
            1 1 -1  3 -1  -1
            1 1 -1  3 -3  -1
            1 1 -3  1 -1  -1];

    phi12 = [  1  -1  3 1 1 -1  -1  -1  1 3 -3  1
              -1  -1  -1  -1  1 -3  -1  3 3 -1  -3  1
              -3  1 -3  -3  -3  3 -3  -1  1 1 1 -3
              -3  3 1 3 -3  1 1 1 1 3 -3  3
              -3  1 3 -1  -1  -3  -3  -1  -1  3 1 -3
              -1  1 1 -1  1 3 3 -1  -1  -3  1 -3
              -3  -3  -1  3 3 3 -3  3 -3  1 -1  -3
              -3  3 -3  3 3 -3  -1  -1  3 3 1 -3
              -3  -1  -3  -1  -1  -3  3 3 -1  -1  1 -3
              -3  3 3 3 -1  -3  -3  -1  -3  1 3 -3
              1 3 -3  1 3 3 3 1 -1  1 -1  3
              -1  -3  3 -1  -3  -3  -3  -1  1 -1  1 -3
              3 1 3 1 3 -3  -1  1 3 1 -1  -3
              -3  -3  3 3 3 -3  -1  1 -3  3 1 -3
              -3  -1  1 -3  1 3 3 3 -1  -3  3 3
              -3  -3  3 1 -3  -3  -3  -1  3 -1  1 3
              -1  1 3 -3  1 -1  1 -1  -1  -3  1 -1
              -3  -1  -1  1 3 1 1 -1  1 -1  -3  1
              -3  -1  3 -3  -3  -1  -3  1 -1  -3  3 3
              -3  -3  3 -3  -1  3 3 3 -1  -3  1 -3
              -3  1 -1  -1  3 3 -3  -1  -1  -3  -1  -3
              -3  1 3 3 -1  -1  -3  3 3 -3  3 -3
              -3  -1  -1  -3  -3  -1  -3  3 1 3 -1  -3
              -3  -1  3 1 -3  -1  -3  3 1 3 3 1
              -3  3 3 1 -3  3 -1  1 3 -3  3 -3
              3 -1  -3  3 -3  -1  3 3 3 -3  -1  -3
              1 -1  3 -1  -1  -1  -3  -1  1 1 1 -3
              -3  3 1 -3  1 3 -1  -1  1 3 3 3
              -3  3 -3  3 -3  -3  3 -1  -1  1 3 -3
              -3  3 1 -1  3 3 -3  1 -1  1 -1  1];

    phi18 = [ 3  -3  3 -1  1 3 -3  -1  -3  -3  -1  -3  3 1 -1  3 -3  3
              3 -3  1 1 3 -1  1 -1  -1  -3  1 1 -1  3 3 -3  3 -1
              -3  3 -1  -3  -1  -3  1 1 -3  -3  -1  -1  3 -3  1 3 1 1
              1 1 -1  -1  -3  -1  1 -3  -3  -3  1 -3  -1  -1  1 -1  3 1
              1 1 -3  3 3 1 3 -3  3 -1  1 1 -1  1 -3  -3  -1  3
              -3  -3  1 -3  3 3 3 -1  3 1 1 -3  -3  -3  3 -3  -1  -1
              -1  3 -1  -3  3 1 -3  -1  3 -3  -1  -1  1 1 1 -1  -1  -1
              -3  1 -3  -3  1 -3  -3  3 1 -3  -1  -3  -3  -3  -1  1 1 3
              1 -3  -1  -3  3 3 -1  -3  1 -3  -3  -1  -3  -1  1 3 3 3
              -3  3 1 -1  -1  -1  -1  1 -1  3 3 -3  -1  1 3 -1  3 -1
              -3  -3  1 -1  -1  1 1 -3  -1  3 3 3 3 -1  3 1 3 1
              -3  -3  3 3 -3  1 3 -1  -3  1 -1  -3  3 -3  -1  -1  -1  3
              -3  -3  3 3 3 1 -3  1 3 3 1 -3  -3  3 -1  -3  -1  1
              -3  3 -1  1 3 1 -3  -1  1 1 -3  1 3 3 -1  -3  -3  -3
              -3  1 -3  -1  -1  3 1 -3  -3  -3  -1  -3  -3  1 1 1 -1  -1
              -3  -3  3 3 3 -1  -1  -3  -1  -1  -1  3 1 -3  -3  -1  3 -1
              -3  -1  3 3 -1  3 -1  -3  -1  1 -1  -3  -1  -1  -1  3 3 1
              -3  -1  -3  -1  -3  1 3 -3  -1  3 3 3 1 -1  -3  3 -1  -3
              -3  3 1 -1  -1  3 -3  -1  1 1 1 1 1 -1  3 -1  -3  -1
              3 -1  -3  1 -3  -3  -3  3 3 -1  1 -3  -1  3 1 1 3 3
              3 3 3 -3  -1  -3  -1  3 -1  1 -1  -3  1 -3  -3  -1  3 3
              3 -1  3 1 -3  -3  -1  1 -3  -3  3 3 3 1 3 -3  3 -3
              -3  1 1 -3  1 1 3 -3  -1  -3  -1  3 -3  3 -1  -1  -1  -3
              -3  -1  -1  -3  1 -3  3 -1  -1  -3  3 3 -3  -1  3 -1  -1  -1
              -3  -3  -3  1 -3  3 1 1 3 -3  -3  1 3 -1  3 -3  -3  3
              1 1 -3  -3  -3  -3  1 3 -3  3 3 1 -3  -1  3 -1  -3  1
              3 -1  -1  1 -3  -1  -3  -1  -3  -3  -1  -3  1 1 1 -3  -3  3
              3 1 -3  1 -3  3 3 -1  -3  -3  -1  -3  -3  3 -3  -1  1 3
              -1  -3  1 -3  -3  -3  1 1 3 3 -3  3 3 -3  -1  3 -3  1
              -3  -1  -3  -3  1 1 -1  -3  -1  -3  -1  -1  3 3 -1  3 1 3];

    phi24 = [ -1 -3  3 1 1 -3  1 -3  -3  1 -3  -1  -1  3 -3  3 3 3 -3  1 3 3 -3  -3
              -1  -3  3 -1  3 1 3 -1  1 -3  -1  -3  -1  1 3 -3  -1  -3  3 3 3 -3  -3  -3
              -3  3 1 3 -1  1 -3  1 -3  1 -1  -3  -1  -3  -3  -3  -3  -1  -1  -1  1 1 -3  -3
              3 -1  3 -1  1 -3  1 1 -3  -3  3 -3  -1  -1  -1  -1  -1  -3  -3  -1  1 1 -3  -3
              1 -3  3 -1  -3  -1  3 3 1 -1  1 1 3 -3  -1  -3  -3  -3  -1  3 -3  -1  -3  -3
              3 -1  1 -1  3 -3  1 1 3 -1  -3  3 1 -3  3 -1  -1  -1  -1  1 -3  -3  -3  -3
              -3  3 -1  3 1 -1  -1  -1  3 3 1 1 1 3 3 1 -3  -3  -1  1 -3  1 3 -3
              -3  -1  1 -3  -3  1 1 -3  3 -1  -1  -3  1 3 1 -1  -3  -1  -3  1 -3  -3  -3  -3
              -3  1 -3  1 -3  -3  1 -3  1 -3  -3  -3  -3  -3  1 -3  -3  1 1 -3  1 1 -3  -3
              3 -3  -3  -1  3 3 -3  -1  3 1 1 1 3 -1  3 -3  -1  3 -1  3 1 -1  -3  -3
              -3  -3  -1  -1  -1  -3  1 -1  -3  -1  3 -3  1 -3  3 -3  3 3 1 -1  -1  1 -3  -3
              -3  -3  3 3 1 -1  -1  -1  1 -3  -1  1 -1  3 -3  -1  -3  -1  -1  1 -3  3 -1  -3
              -3  -3  1 -1  3 3 -3  -1  1 -1  -1  1 1 -1  -1  3 -3  1 -3  1 -1  -1  -1  -3
              -3  1 -3  3 -1  -1  -1  -3  3 1 -1  -3  -1  1 3 -1  1 -1  1 -3  -3  -3  -3  -3
              -3  -3  -3  -1  3 -3  3 1 3 1 -3  -1  -1  -3  1 1 3 1 -1  -3  3 1 3 -3
              1 1 -1  -3  -1  1 1 -3  1 -1  1 -3  3 -3  -3  3 -1  -3  1 3 -3  1 -3  -3
              -3  3 -1  3 -1  3 3 1 1 -3  1 3 -3  3 -3  -3  -1  1 3 -3  -1  -1  -3  -3
              -1  -3  -3  1 -1  -1  -3  1 3 -1  -3  -1  -1  -3  1 1 3 1 -3  -1  -1  3 -3  -3
              -3  1 -3  1 -3  1 1 3 1 -3  -3  -1  1 3 -1  -3  3 1 -1  -3  -3  -3  -3  -3
              3 -3  3 -1  -3  1 3 1 -1  -1  -3  -1  3 -3  3 -1  -1  3 3 -3  -3  3 -3  -3
              -1  3 -3  -3  -1  3 -1  -1  1 3 1 3 -1  -1  -3  1 3 1 -1  -3  1 -1  -3  -3
              -3  1 -3  -1  -1  3 1 3 -3  1 -1  3 3 -1  -3  3 -3  -1  -1  -3  -3  -3  3 -3
              -3  -1  -1  -3  1 -3  -3  -1  -1  3 -1  1 -1  3 1 -3  -1  3 1 1 -1  -1  -3  -3
              -3  1 -3  3 -3  1 -3  3 1 -1  -3  -1  -3  -3  -3  -3  1 3 -1  1 3 3 3 -3
              -3  -1  1 -3  -1  -1  1 1 1 3 3 -1  1 -1  1 -1  -1  -3  -3  -3  3 1 -1  -3
              3 -3  -1  1 3 -1  -1  -3  -1  3 -1  -3  -1  -3  3 -1  3 1 1 -3  3 -3  -3  -3
              -3  1 3 -1  1 -1  3 -3  3 -1  -3  -1  -3  3 -1  -1  -1  -3  -1  -1  -3  3 3 -3
              -3  3 -1  -3  -1  -1  -1  3 -1  -1  3 -3  -1  3 -3  3 -3  -1  3 1 1 -1  -3  -3
              -3  1 -1  -3  -3  -1  1 -3  -1  -3  1 1 -1  1 1 3 3 3 -1  1 -1  1 -1  -3
              -1  3 -1  -1  3 3 -1  -1  -1  3 -1  -3  1 3 1 1 -3  -3  -3  -1  -3  -1  -3  -3];
  end

  if nargin < 5
    delta = 0;
  end
  if nargin < 6
    M_ZC = ceil((max(n) + 1) / 6) * 6;
  else
    assert(M_ZC > 0 && mod(M_ZC, 6) == 0, 'M_ZC must be a positive multiple od 6');
  end

  assert(ismember(u, 0:29), 'u must be a scalar in range 0:29');
  assert(isscalar(u) && isscalar(v) && isscalar(alpha) && isscalar(delta) && isscalar(M_ZC), 'u, v, alpha, delta and M_ZC must be scalars');

  if M_ZC >= 36
    N_ZC = max(primes(M_ZC - 1));
    qd = N_ZC * (u + 1) / 31;
    q = floor(qd + 0.5) + v * (-1) .^ floor(2*qd);
    m = mod(n, N_ZC);
    rd = exp(-1i * pi * q * m .* (m+1) / N_ZC);
  else
    switch(M_ZC)
      case 6
        rd = exp(1i * phi6(u+1,n+1) * pi / 4);
      case 12
        rd = exp(1i * phi12(u+1,n+1) * pi / 4);
      case 18
        rd = exp(1i * phi18(u+1,n+1) * pi / 4);
      case 24
        rd = exp(1i * phi24(u+1,n+1) * pi / 4);
      case 30
        rd = exp(-1i * pi * (u+1) * (n+1) .* (n+2) / 31);
    end
  end

  r = exp(1i * alpha * (n + delta/2)) .* rd;
end