Repository: scipy/scipy

Issue:
LAPACK Rectangular Full Packed routines 
`scipy.linalg.lapack` does not expose the LAPACK routines using the [Rectangular Full Packed storage (RFP)](https://software.intel.com/en-us/mkl-developer-reference-c-matrix-storage-schemes-for-lapack-routines#RFP_STORAGE) for upper triangular, lower triangular, symmetric, or Hermitian matrices. This includes 

- rank-k operations ([xfrk](https://software.intel.com/en-us/mkl-developer-reference-c-sfrk))
- triangular system ([xtfsm](https://software.intel.com/en-us/mkl-developer-reference-c-tfsm))
- Cholesky:  factorise ([xpftrf](https://software.intel.com/en-us/mkl-developer-reference-c-pftrf)), solve ([xpftrs](https://software.intel.com/en-us/mkl-developer-reference-c-pftrs)), and inverse ([xpftri](https://software.intel.com/en-us/mkl-developer-reference-c-pftri))
- copy from RFP to standard packed format ([xtfttp](https://software.intel.com/en-us/node/521386)), and the reverse conversion ([tpttf](https://software.intel.com/en-us/mkl-developer-reference-c-tpttf))



Discussion:
Nice idea. These routines were introduced in LAPACK 3.2, so they should be added to the Cython LAPACK API for when we drop support for LAPACK 3.1 and Accelerate (see the discussion in https://github.com/scipy/scipy/pull/6051). On the other hand, I'm not aware of any plans for Python-level wrappers. It'll probably just depend on when someone steps forward to implement it.
Thanks for the comment and sorry for not spotting that discussion. Cython support would be great and probably enough for a lot of people, in which I count myself. Do I understand correctly that the scipy 1.1.0 will have moved away from Accelerate and could then use LAPACK 3.2?
@luc-j-bourhis Fingers crossed :) Dropping Accelerate support turned out to be a bigger problem than just avoiding it. We've gathered the summary on wiki page https://github.com/scipy/scipy/wiki/Dropping-support-for-Accelerate

So the story is a bit convoluted and depends on many stakeholders. So far we only have an agreement about *we should do something about it*. But I'm optimistic about a positive answer to your question. Then we will move to 3.4.0.
Don't worry about missing the other discussion. It's on a fairly old PR. This issue helps provide another concrete reason for the version bump even if it is something we're already working on.

Another idea that's worth at least mentioning: it's likely possible to provide these routines without dropping Accelerate since Accelerate provides at least some of LAPACK 3.2. We'd have to formally drop support for  LAPACK 3.1 though, so I'm still inclined to do this as a part of the transition to LAPACK 3.4, particularly since that change will hopefully be happening soon.
For the record, LAPACK devs stopped developing for packed arrays for sym/her matrices due to lack of benefit of packed storage. (See https://github.com/Reference-LAPACK/lapack/issues/245#issuecomment-372060216 )

I guess we might have to close this as *won't fix*
They stopped developing for the traditional packing, i.e. 11 12 13 ... 1n 22 21 ... 2n 33 34 ... 3n etc, which is old story really, since this packing has been known to have a horrible speed performance for ages. However this issue of mine is not related to that packing but to the new RFP, and the conclusion of the comment you cite is much less definite: « we worked on a few routines, performance were good, but half the storage might not be worth the effort any longer» Not the "might". Clearly, the routines operating on RFP have shown no sign of deprecation at all. So my issue stands imho.
Yes indeed I missed that detail. I've asked for clarification from Julien.
@ilayn The [answer](https://github.com/Reference-LAPACK/lapack/issues/245#issuecomment-372126132) to your question on the discussion thread of Lapack issue 245  confirms what I wrote above: the RFP routines are and will stay alive in LAPACK. There is, I think, definitively use cases for GEMM-like speed with half the memory footprint. 
May I respectfully ping!
These routines have Cython wrappers as of https://github.com/scipy/scipy/pull/8670. I don't think there are Python level wrappers yet.
@luc-j-bourhis Sorry for the delay. Would you like to work on it in terms of a PR? Please feel free to decline, I'm just asking. Otherwise I'll combine with a PR that I'll submit soon. 
@ilayn No worries. I would be more than happy to help. That PR would be about writing the Python wrappers then I guess. I know next to nothing about this but again if you think I can be useful, yes.
Great. If you haven't wrapped any LAPACK routines it might be a bit too much to ask from you. Let me wrap the functions and if you can help me derive some tests for them that would be really valuable. 

Since I have never used these routines, real use cases would be really nice to have as test cases. Let me create a PR and we can continue the discussion there. Hopefully tonight I can submit it. 

Resolving patch:
diff --git a/scipy/linalg/lapack.py b/scipy/linalg/lapack.py
--- a/scipy/linalg/lapack.py
+++ b/scipy/linalg/lapack.py
@@ -373,6 +373,21 @@
    ctrtrs
    ztrtrs
 
+   spftrf
+   dpftrf
+   cpftrf
+   zpftrf
+
+   spftri
+   dpftri
+   cpftri
+   zpftri
+
+   spftrs
+   dpftrs
+   cpftrs
+   zpftrs
+
    cunghr
    zunghr
 
@@ -385,6 +400,12 @@
    cunmqr
    zunmqr
 
+   cunmrz
+   zunmrz
+
+   cunmrz_lwork
+   zunmrz_lwork
+
    sgtsv
    dgtsv
    cgtsv
@@ -409,6 +430,12 @@
    sormqr
    dormqr
 
+   sormrz
+   dormrz
+
+   sormrz_lwork
+   dormrz_lwork
+
    ssbev
    dsbev
 
@@ -451,6 +478,62 @@
    ssygvx
    dsygvx
 
+   ssfrk
+   dsfrk
+
+   chfrk
+   zhfrk
+
+   stfsm
+   dtfsm
+   ctfsm
+   ztfsm
+
+   stpttf
+   dtpttf
+   ctpttf
+   ztpttf
+
+   stfttp
+   dtfttp
+   ctfttp
+   ztfttp
+
+   stfttr
+   dtfttr
+   ctfttr
+   ztfttr
+
+   strttf
+   dtrttf
+   ctrttf
+   ztrttf
+
+   stpttr
+   dtpttr
+   ctpttr
+   ztpttr
+
+   strttp
+   dtrttp
+   ctrttp
+   ztrttp
+
+   stfsm
+   dtfsm
+   ctfsm
+   dtfsm
+
+   stzrzf
+   dtzrzf
+   ctzrzf
+   ztzrzf
+
+   stzrzf_lwork
+   dtzrzf_lwork
+   ctzrzf_lwork
+   ztzrzf_lwork
+
    slange
    dlange
    clange


Test patch:
diff --git a/scipy/linalg/tests/test_lapack.py b/scipy/linalg/tests/test_lapack.py
--- a/scipy/linalg/tests/test_lapack.py
+++ b/scipy/linalg/tests/test_lapack.py
@@ -7,6 +7,7 @@
 import sys
 import subprocess
 import time
+from functools import reduce
 
 from numpy.testing import (assert_equal, assert_array_almost_equal, assert_,
                            assert_allclose, assert_almost_equal,
@@ -15,11 +16,13 @@
 from pytest import raises as assert_raises
 
 import numpy as np
+from numpy import (eye, ones, zeros, zeros_like, triu, tril, tril_indices,
+                   triu_indices)
+
 from numpy.random import rand, seed
 
 from scipy.linalg import _flapack as flapack
-from scipy.linalg import inv
-from scipy.linalg import svd
+from scipy.linalg import inv, svd, cholesky, solve
 from scipy.linalg.lapack import _compute_lwork
 
 try:
@@ -557,7 +560,7 @@ def test_larfg_larf():
         a[1:, :] = larf(v, tau.conjugate(), a[1:, :], np.zeros(a.shape[1]))
 
         # apply transform from the right
-        a[:, 1:] = larf(v, tau, a[:,1:], np.zeros(a.shape[0]), side='R')
+        a[:, 1:] = larf(v, tau, a[:, 1:], np.zeros(a.shape[0]), side='R')
 
         assert_allclose(a[:, 0], expected, atol=1e-5)
         assert_allclose(a[0, :], expected, atol=1e-5)
@@ -831,7 +834,9 @@ def test_sygst():
         # DTYPES = <s,d> sygst
         n = 10
 
-        potrf, sygst, syevd, sygvd = get_lapack_funcs(('potrf', 'sygst', 'syevd', 'sygvd'), dtype=dtype)
+        potrf, sygst, syevd, sygvd = get_lapack_funcs(('potrf', 'sygst',
+                                                       'syevd', 'sygvd'),
+                                                      dtype=dtype)
 
         A = rand(n, n).astype(dtype)
         A = (A + A.T)/2
@@ -860,7 +865,9 @@ def test_hegst():
         # DTYPES = <c,z> hegst
         n = 10
 
-        potrf, hegst, heevd, hegvd = get_lapack_funcs(('potrf', 'hegst', 'heevd', 'hegvd'), dtype=dtype)
+        potrf, hegst, heevd, hegvd = get_lapack_funcs(('potrf', 'hegst',
+                                                       'heevd', 'hegvd'),
+                                                      dtype=dtype)
 
         A = rand(n, n).astype(dtype) + 1j * rand(n, n).astype(dtype)
         A = (A + A.conj().T)/2
@@ -881,3 +888,358 @@ def test_hegst():
         eig, _, info = heevd(a)
         assert_(info == 0)
         assert_allclose(eig, eig_gvd, rtol=1e-4)
+
+
+def test_tzrzf():
+    """
+    This test performs an RZ decomposition in which an m x n upper trapezoidal
+    array M (m <= n) is factorized as M = [R 0] * Z where R is upper triangular
+    and Z is unitary.
+    """
+    seed(1234)
+    m, n = 10, 15
+    for ind, dtype in enumerate(DTYPES):
+        tzrzf, tzrzf_lw = get_lapack_funcs(('tzrzf', 'tzrzf_lwork'),
+                                           dtype=dtype)
+        lwork = _compute_lwork(tzrzf_lw, m, n)
+
+        if ind < 2:
+            A = triu(rand(m, n).astype(dtype))
+        else:
+            A = triu((rand(m, n) + rand(m, n)*1j).astype(dtype))
+
+        # assert wrong shape arg, f2py returns generic error
+        assert_raises(Exception, tzrzf, A.T)
+        rz, tau, info = tzrzf(A, lwork=lwork)
+        # Check success
+        assert_(info == 0)
+
+        # Get Z manually for comparison
+        R = np.hstack((rz[:, :m], np.zeros((m, n-m), dtype=dtype)))
+        V = np.hstack((np.eye(m, dtype=dtype), rz[:, m:]))
+        Id = np.eye(n, dtype=dtype)
+        ref = [Id-tau[x]*V[[x], :].T.dot(V[[x], :].conj()) for x in range(m)]
+        Z = reduce(np.dot, ref)
+        assert_allclose(R.dot(Z) - A, zeros_like(A, dtype=dtype),
+                        atol=10*np.spacing(dtype(1.0).real), rtol=0.)
+
+
+def test_tfsm():
+    """
+    Test for solving a linear system with the coefficient matrix is a
+    triangular array stored in Full Packed (RFP) format.
+    """
+    seed(1234)
+    for ind, dtype in enumerate(DTYPES):
+        n = 20
+        if ind > 1:
+            A = triu(rand(n, n) + rand(n, n)*1j + eye(n)).astype(dtype)
+            trans = 'C'
+        else:
+            A = triu(rand(n, n) + eye(n)).astype(dtype)
+            trans = 'T'
+
+        trttf, tfttr, tfsm = get_lapack_funcs(('trttf', 'tfttr', 'tfsm'),
+                                              dtype=dtype)
+
+        Afp, _ = trttf(A)
+        B = rand(n, 2).astype(dtype)
+        soln = tfsm(-1, Afp, B)
+        assert_array_almost_equal(soln, solve(-A, B),
+                                  decimal=4 if ind % 2 == 0 else 6)
+
+        soln = tfsm(-1, Afp, B, trans=trans)
+        assert_array_almost_equal(soln, solve(-A.conj().T, B),
+                                  decimal=4 if ind % 2 == 0 else 6)
+
+        # Make A, unit diagonal
+        A[np.arange(n), np.arange(n)] = dtype(1.)
+        soln = tfsm(-1, Afp, B, trans=trans, diag='U')
+        assert_array_almost_equal(soln, solve(-A.conj().T, B),
+                                  decimal=4 if ind % 2 == 0 else 6)
+
+        # Change side
+        B2 = rand(3, n).astype(dtype)
+        soln = tfsm(-1, Afp, B2, trans=trans, diag='U', side='R')
+        assert_array_almost_equal(soln, solve(-A, B2.T).conj().T,
+                                  decimal=4 if ind % 2 == 0 else 6)
+
+
+def test_ormrz_unmrz():
+    """
+    This test performs a matrix multiplication with an arbitrary m x n matric C
+    and a unitary matrix Q without explicitly forming the array. The array data
+    is encoded in the rectangular part of A which is obtained from ?TZRZF. Q
+    size is inferred by m, n, side keywords.
+    """
+    seed(1234)
+    qm, qn, cn = 10, 15, 15
+    for ind, dtype in enumerate(DTYPES):
+        tzrzf, tzrzf_lw = get_lapack_funcs(('tzrzf', 'tzrzf_lwork'),
+                                           dtype=dtype)
+        lwork_rz = _compute_lwork(tzrzf_lw, qm, qn)
+
+        if ind < 2:
+            A = triu(rand(qm, qn).astype(dtype))
+            C = rand(cn, cn).astype(dtype)
+            orun_mrz, orun_mrz_lw = get_lapack_funcs(('ormrz', 'ormrz_lwork'),
+                                                     dtype=dtype)
+        else:
+            A = triu((rand(qm, qn) + rand(qm, qn)*1j).astype(dtype))
+            C = (rand(cn, cn) + rand(cn, cn)*1j).astype(dtype)
+            orun_mrz, orun_mrz_lw = get_lapack_funcs(('unmrz', 'unmrz_lwork'),
+                                                     dtype=dtype)
+
+        lwork_mrz = _compute_lwork(orun_mrz_lw, cn, cn)
+        rz, tau, info = tzrzf(A, lwork=lwork_rz)
+
+        # Get Q manually for comparison
+        V = np.hstack((np.eye(qm, dtype=dtype), rz[:, qm:]))
+        Id = np.eye(qn, dtype=dtype)
+        ref = [Id-tau[x]*V[[x], :].T.dot(V[[x], :].conj()) for x in range(qm)]
+        Q = reduce(np.dot, ref)
+
+        # Now that we have Q, we can test whether lapack results agree with
+        # each case of CQ, CQ^H, QC, and QC^H
+        trans = 'T' if ind < 2 else 'C'
+        tol = 10*np.spacing(dtype(1.0).real)
+
+        cq, info = orun_mrz(rz, tau, C, lwork=lwork_mrz)
+        assert_(info == 0)
+        assert_allclose(cq - Q.dot(C), zeros_like(C), atol=tol, rtol=0.)
+
+        cq, info = orun_mrz(rz, tau, C, trans=trans, lwork=lwork_mrz)
+        assert_(info == 0)
+        assert_allclose(cq - Q.conj().T.dot(C), zeros_like(C), atol=tol,
+                        rtol=0.)
+
+        cq, info = orun_mrz(rz, tau, C, side='R', lwork=lwork_mrz)
+        assert_(info == 0)
+        assert_allclose(cq - C.dot(Q), zeros_like(C), atol=tol, rtol=0.)
+
+        cq, info = orun_mrz(rz, tau, C, side='R', trans=trans, lwork=lwork_mrz)
+        assert_(info == 0)
+        assert_allclose(cq - C.dot(Q.conj().T), zeros_like(C), atol=tol,
+                        rtol=0.)
+
+
+def test_tfttr_trttf():
+    """
+    Test conversion routines between the Rectengular Full Packed (RFP) format
+    and Standard Triangular Array (TR)
+    """
+    seed(1234)
+    for ind, dtype in enumerate(DTYPES):
+        n = 20
+        if ind > 1:
+            A_full = (rand(n, n) + rand(n, n)*1j).astype(dtype)
+            transr = 'C'
+        else:
+            A_full = (rand(n, n)).astype(dtype)
+            transr = 'T'
+
+        trttf, tfttr = get_lapack_funcs(('trttf', 'tfttr'), dtype=dtype)
+        A_tf_U, info = trttf(A_full)
+        assert_(info == 0)
+        A_tf_L, info = trttf(A_full, uplo='L')
+        assert_(info == 0)
+        A_tf_U_T, info = trttf(A_full, transr=transr, uplo='U')
+        assert_(info == 0)
+        A_tf_L_T, info = trttf(A_full, transr=transr, uplo='L')
+        assert_(info == 0)
+
+        # Create the RFP array manually (n is even!)
+        A_tf_U_m = zeros((n+1, n//2), dtype=dtype)
+        A_tf_U_m[:-1, :] = triu(A_full)[:, n//2:]
+        A_tf_U_m[n//2+1:, :] += triu(A_full)[:n//2, :n//2].conj().T
+
+        A_tf_L_m = zeros((n+1, n//2), dtype=dtype)
+        A_tf_L_m[1:, :] = tril(A_full)[:, :n//2]
+        A_tf_L_m[:n//2, :] += tril(A_full)[n//2:, n//2:].conj().T
+
+        assert_array_almost_equal(A_tf_U, A_tf_U_m.reshape(-1, order='F'))
+        assert_array_almost_equal(A_tf_U_T,
+                                  A_tf_U_m.conj().T.reshape(-1, order='F'))
+
+        assert_array_almost_equal(A_tf_L, A_tf_L_m.reshape(-1, order='F'))
+        assert_array_almost_equal(A_tf_L_T,
+                                  A_tf_L_m.conj().T.reshape(-1, order='F'))
+
+        # Get the original array from RFP
+        A_tr_U, info = tfttr(n, A_tf_U)
+        assert_(info == 0)
+        A_tr_L, info = tfttr(n, A_tf_L, uplo='L')
+        assert_(info == 0)
+        A_tr_U_T, info = tfttr(n, A_tf_U_T, transr=transr, uplo='U')
+        assert_(info == 0)
+        A_tr_L_T, info = tfttr(n, A_tf_L_T, transr=transr, uplo='L')
+        assert_(info == 0)
+
+        assert_array_almost_equal(A_tr_U, triu(A_full))
+        assert_array_almost_equal(A_tr_U_T, triu(A_full))
+        assert_array_almost_equal(A_tr_L, tril(A_full))
+        assert_array_almost_equal(A_tr_L_T, tril(A_full))
+
+
+def test_tpttr_trttp():
+    """
+    Test conversion routines between the Rectengular Full Packed (RFP) format
+    and Standard Triangular Array (TR)
+    """
+    seed(1234)
+    for ind, dtype in enumerate(DTYPES):
+        n = 20
+        if ind > 1:
+            A_full = (rand(n, n) + rand(n, n)*1j).astype(dtype)
+        else:
+            A_full = (rand(n, n)).astype(dtype)
+
+        trttp, tpttr = get_lapack_funcs(('trttp', 'tpttr'), dtype=dtype)
+        A_tp_U, info = trttp(A_full)
+        assert_(info == 0)
+        A_tp_L, info = trttp(A_full, uplo='L')
+        assert_(info == 0)
+
+        # Create the TP array manually
+        inds = tril_indices(n)
+        A_tp_U_m = zeros(n*(n+1)//2, dtype=dtype)
+        A_tp_U_m[:] = (triu(A_full).T)[inds]
+
+        inds = triu_indices(n)
+        A_tp_L_m = zeros(n*(n+1)//2, dtype=dtype)
+        A_tp_L_m[:] = (tril(A_full).T)[inds]
+
+        assert_array_almost_equal(A_tp_U, A_tp_U_m)
+        assert_array_almost_equal(A_tp_L, A_tp_L_m)
+
+        # Get the original array from TP
+        A_tr_U, info = tpttr(n, A_tp_U)
+        assert_(info == 0)
+        A_tr_L, info = tpttr(n, A_tp_L, uplo='L')
+        assert_(info == 0)
+
+        assert_array_almost_equal(A_tr_U, triu(A_full))
+        assert_array_almost_equal(A_tr_L, tril(A_full))
+
+
+def test_pftrf():
+    """
+    Test Cholesky factorization of a positive definite Rectengular Full
+    Packed (RFP) format array
+    """
+    seed(1234)
+    for ind, dtype in enumerate(DTYPES):
+        n = 20
+        if ind > 1:
+            A = (rand(n, n) + rand(n, n)*1j).astype(dtype)
+            A = A + A.conj().T + n*eye(n)
+        else:
+            A = (rand(n, n)).astype(dtype)
+            A = A + A.T + n*eye(n)
+
+        pftrf, trttf, tfttr = get_lapack_funcs(('pftrf', 'trttf', 'tfttr'),
+                                               dtype=dtype)
+
+        # Get the original array from TP
+        Afp, info = trttf(A)
+        Achol_rfp, info = pftrf(n, Afp)
+        assert_(info == 0)
+        A_chol_r, _ = tfttr(n, Achol_rfp)
+        Achol = cholesky(A)
+        assert_array_almost_equal(A_chol_r, Achol)
+
+
+def test_pftri():
+    """
+    Test Cholesky factorization of a positive definite Rectengular Full
+    Packed (RFP) format array to find its inverse
+    """
+    seed(1234)
+    for ind, dtype in enumerate(DTYPES):
+        n = 20
+        if ind > 1:
+            A = (rand(n, n) + rand(n, n)*1j).astype(dtype)
+            A = A + A.conj().T + n*eye(n)
+        else:
+            A = (rand(n, n)).astype(dtype)
+            A = A + A.T + n*eye(n)
+
+        pftri, pftrf, trttf, tfttr = get_lapack_funcs(('pftri',
+                                                       'pftrf',
+                                                       'trttf',
+                                                       'tfttr'),
+                                                      dtype=dtype)
+
+        # Get the original array from TP
+        Afp, info = trttf(A)
+        A_chol_rfp, info = pftrf(n, Afp)
+        A_inv_rfp, info = pftri(n, A_chol_rfp)
+        assert_(info == 0)
+        A_inv_r, _ = tfttr(n, A_inv_rfp)
+        Ainv = inv(A)
+        assert_array_almost_equal(A_inv_r, triu(Ainv),
+                                  decimal=4 if ind % 2 == 0 else 6)
+
+
+def test_pftrs():
+    """
+    Test Cholesky factorization of a positive definite Rectengular Full
+    Packed (RFP) format array and solve a linear system
+    """
+    seed(1234)
+    for ind, dtype in enumerate(DTYPES):
+        n = 20
+        if ind > 1:
+            A = (rand(n, n) + rand(n, n)*1j).astype(dtype)
+            A = A + A.conj().T + n*eye(n)
+        else:
+            A = (rand(n, n)).astype(dtype)
+            A = A + A.T + n*eye(n)
+
+        B = ones((n, 3), dtype=dtype)
+        Bf1 = ones((n+2, 3), dtype=dtype)
+        Bf2 = ones((n-2, 3), dtype=dtype)
+        pftrs, pftrf, trttf, tfttr = get_lapack_funcs(('pftrs',
+                                                       'pftrf',
+                                                       'trttf',
+                                                       'tfttr'),
+                                                      dtype=dtype)
+
+        # Get the original array from TP
+        Afp, info = trttf(A)
+        A_chol_rfp, info = pftrf(n, Afp)
+        # larger B arrays shouldn't segfault
+        soln, info = pftrs(n, A_chol_rfp, Bf1)
+        assert_(info == 0)
+        assert_raises(Exception, pftrs, n, A_chol_rfp, Bf2)
+        soln, info = pftrs(n, A_chol_rfp, B)
+        assert_(info == 0)
+        assert_array_almost_equal(solve(A, B), soln,
+                                  decimal=4 if ind % 2 == 0 else 6)
+
+
+def test_sfrk_hfrk():
+    """
+    Test for performing a symmetric rank-k operation for matrix in RFP format.
+    """
+    seed(1234)
+    for ind, dtype in enumerate(DTYPES):
+        n = 20
+        if ind > 1:
+            A = (rand(n, n) + rand(n, n)*1j).astype(dtype)
+            A = A + A.conj().T + n*eye(n)
+        else:
+            A = (rand(n, n)).astype(dtype)
+            A = A + A.T + n*eye(n)
+
+        prefix = 's'if ind < 2 else 'h'
+        trttf, tfttr, shfrk = get_lapack_funcs(('trttf', 'tfttr', '{}frk'
+                                                ''.format(prefix)),
+                                               dtype=dtype)
+
+        Afp, _ = trttf(A)
+        C = np.random.rand(n, 2).astype(dtype)
+        Afp_out = shfrk(n, 2, -1, C, 2, Afp)
+        A_out, _ = tfttr(n, Afp_out)
+        assert_array_almost_equal(A_out, triu(-C.dot(C.conj().T) + 2*A),
+                                  decimal=4 if ind % 2 == 0 else 6)