Repository: gonum/gonum
Pull request #1790 (2022-03-21 10:52:56)

lapack/gonum: add Dlapmr
Please take a look.

<!--
Checklist:

- API changes have been discussed
- code is goformated correctly (goimports)
- packages with generated code have had code generation run
- tests pass locally
- linked to relevant issues

Please make sure your commit message summary line and pull request
title match the Go convention; a one-line summary of the change,
prefixed by the primary affected package that should complete the
sentence, "This change modifies Gonum to _____."
-->


codecov-commenter: # [Codecov](https://codecov.io/gh/gonum/gonum/pull/1790?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) Report
> Merging [#1790](https://codecov.io/gh/gonum/gonum/pull/1790?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) (33461fd) into [master](https://codecov.io/gh/gonum/gonum/commit/1180659542815ddb51545f83860386272ebf0d9b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) (1180659) will **increase** coverage by `0.00%`.
> The diff coverage is `75.00%`.

```diff
@@           Coverage Diff           @@
##           master    #1790   +/-   ##
=======================================
  Coverage   73.42%   73.42%           
=======================================
  Files         523      524    +1     
  Lines       61719    61759   +40     
=======================================
+ Hits        45315    45345   +30     
- Misses      13848    13858   +10     
  Partials     2556     2556           
```


| [Impacted Files](https://codecov.io/gh/gonum/gonum/pull/1790?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum) | Coverage Δ | |
|---|---|---|
| [lapack/gonum/dlapmr.go](https://codecov.io/gh/gonum/gonum/pull/1790/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum#diff-bGFwYWNrL2dvbnVtL2RsYXBtci5nbw==) | `75.00% <75.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gonum/gonum/pull/1790?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gonum/gonum/pull/1790?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum). Last update [1180659...33461fd](https://codecov.io/gh/gonum/gonum/pull/1790?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gonum).


Patch:
diff --git a/lapack/gonum/dlapmr.go b/lapack/gonum/dlapmr.go
new file mode 100644
index 00000000..bd484eaf
--- /dev/null
+++ b/lapack/gonum/dlapmr.go
@@ -0,0 +1,85 @@
+// Copyright ©2022 The Gonum Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gonum
+
+import "gonum.org/v1/gonum/blas/blas64"
+
+// Dlapmr rearranges the rows of the m×n matrix X as specified by the permutation
+// k[0],k[1],...,k[m-1] of the integers 0,...,m-1.
+//
+// If forward is true, a forward permutation is applied:
+//  X[k[i],0:n] is moved to X[i,0:n] for i=0,1,...,m-1.
+// If forward is false, a backward permutation is applied:
+//  X[i,0:n] is moved to X[k[i],0:n] for i=0,1,...,m-1.
+//
+// k must have length m, otherwise Dlapmr will panic.
+func (impl Implementation) Dlapmr(forward bool, m, n int, x []float64, ldx int, k []int) {
+	switch {
+	case m < 0:
+		panic(mLT0)
+	case n < 0:
+		panic(nLT0)
+	case ldx < max(1, n):
+		panic(badLdX)
+	}
+
+	// Quick return if possible.
+	if m == 0 || n == 0 {
+		return
+	}
+
+	switch {
+	case len(x) < (m-1)*ldx+n:
+		panic(shortX)
+	case len(k) != m:
+		panic(badLenK)
+	}
+
+	// Quick return if possible.
+	if m == 1 {
+		return
+	}
+
+	bi := blas64.Implementation()
+
+	for i, ki := range k {
+		k[i] = -(ki + 1)
+	}
+	if forward {
+		for i, ki := range k {
+			if ki >= 0 {
+				continue
+			}
+			j := i
+			k[j] = -k[j] - 1
+			in := k[j]
+			for {
+				if k[in] >= 0 {
+					break
+				}
+				bi.Dswap(n, x[j*ldx:], 1, x[in*ldx:], 1)
+				k[in] = -k[in] - 1
+				j = in
+				in = k[in]
+			}
+		}
+	} else {
+		for i, ki := range k {
+			if ki >= 0 {
+				continue
+			}
+			k[i] = -ki - 1
+			j := k[i]
+			for {
+				if j == i {
+					break
+				}
+				bi.Dswap(n, x[i*ldx:], 1, x[j*ldx:], 1)
+				k[j] = -k[j] - 1
+				j = k[j]
+			}
+		}
+	}
+}
diff --git a/lapack/lapack.go b/lapack/lapack.go
index 22ed1a7b..5f60438f 100644
--- a/lapack/lapack.go
+++ b/lapack/lapack.go
@@ -24,6 +24,7 @@ type Float64 interface {
 	Dlantr(norm MatrixNorm, uplo blas.Uplo, diag blas.Diag, m, n int, a []float64, lda int, work []float64) float64
 	Dlange(norm MatrixNorm, m, n int, a []float64, lda int, work []float64) float64
 	Dlansy(norm MatrixNorm, uplo blas.Uplo, n int, a []float64, lda int, work []float64) float64
+	Dlapmr(forward bool, m, n int, x []float64, ldx int, k []int)
 	Dlapmt(forward bool, m, n int, x []float64, ldx int, k []int)
 	Dormqr(side blas.Side, trans blas.Transpose, m, n, k int, a []float64, lda int, tau, c []float64, ldc int, work []float64, lwork int)
 	Dormlq(side blas.Side, trans blas.Transpose, m, n, k int, a []float64, lda int, tau, c []float64, ldc int, work []float64, lwork int)
diff --git a/lapack/lapack64/lapack64.go b/lapack/lapack64/lapack64.go
index aabad611..12de74b2 100644
--- a/lapack/lapack64/lapack64.go
+++ b/lapack/lapack64/lapack64.go
@@ -551,6 +551,19 @@ func Lantb(norm lapack.MatrixNorm, a blas64.TriangularBand, work []float64) floa
 	return gonum.Implementation{}.Dlantb(norm, a.Uplo, a.Diag, a.N, a.K, a.Data, max(1, a.Stride), work)
 }
 
+// Lapmr rearranges the rows of the m×n matrix X as specified by the permutation
+// k[0],k[1],...,k[m-1] of the integers 0,...,m-1.
+//
+// If forward is true, a forward permutation is applied:
+//  X[k[i],0:n] is moved to X[i,0:n] for i=0,1,...,m-1.
+// If forward is false, a backward permutation is applied:
+//  X[i,0:n] is moved to X[k[i],0:n] for i=0,1,...,m-1.
+//
+// k must have length m, otherwise Lapmr will panic.
+func Lapmr(forward bool, x blas64.General, k []int) {
+	lapack64.Dlapmr(forward, x.Rows, x.Cols, x.Data, max(1, x.Stride), k)
+}
+
 // Lapmt rearranges the columns of the m×n matrix X as specified by the
 // permutation k_0, k_1, ..., k_{n-1} of the integers 0, ..., n-1.
 //


Test patch:
diff --git a/lapack/gonum/lapack_test.go b/lapack/gonum/lapack_test.go
index 50ae3e0f..5a3c418c 100644
--- a/lapack/gonum/lapack_test.go
+++ b/lapack/gonum/lapack_test.go
@@ -248,6 +248,11 @@ func TestDlapll(t *testing.T) {
 	testlapack.DlapllTest(t, impl)
 }
 
+func TestDlapmr(t *testing.T) {
+	t.Parallel()
+	testlapack.DlapmrTest(t, impl)
+}
+
 func TestDlapmt(t *testing.T) {
 	t.Parallel()
 	testlapack.DlapmtTest(t, impl)
diff --git a/lapack/testlapack/dlapmr.go b/lapack/testlapack/dlapmr.go
new file mode 100644
index 00000000..c71ae030
--- /dev/null
+++ b/lapack/testlapack/dlapmr.go
@@ -0,0 +1,85 @@
+// Copyright ©2022 The Gonum Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package testlapack
+
+import (
+	"fmt"
+	"testing"
+
+	"golang.org/x/exp/rand"
+	"gonum.org/v1/gonum/blas"
+	"gonum.org/v1/gonum/blas/blas64"
+)
+
+type Dlapmrer interface {
+	Dlapmr(forwrd bool, m, n int, x []float64, ldx int, k []int)
+}
+
+func DlapmrTest(t *testing.T, impl Dlapmrer) {
+	rnd := rand.New(rand.NewSource(1))
+	for _, fwd := range []bool{true, false} {
+		for _, m := range []int{0, 1, 2, 3, 4, 5, 10} {
+			for _, n := range []int{0, 1, 4} {
+				for _, ldx := range []int{max(1, n), n + 3} {
+					dlapmrTest(t, impl, rnd, fwd, m, n, ldx)
+				}
+			}
+		}
+	}
+}
+
+func dlapmrTest(t *testing.T, impl Dlapmrer, rnd *rand.Rand, fwd bool, m, n, ldx int) {
+	name := fmt.Sprintf("forwrd=%v,m=%d,n=%d,ldx=%d", fwd, m, n, ldx)
+
+	bi := blas64.Implementation()
+
+	// Generate a random permutation and simultaneously apply it to the rows of the identity matrix.
+	k := make([]int, m)
+	for i := range k {
+		k[i] = i
+	}
+	p := eye(m, m)
+	for i := 0; i < m-1; i++ {
+		j := i + rnd.Intn(m-i)
+		k[i], k[j] = k[j], k[i]
+		bi.Dswap(m, p.Data[i*p.Stride:], 1, p.Data[j*p.Stride:], 1)
+	}
+	kCopy := make([]int, len(k))
+	copy(kCopy, k)
+
+	// Generate a random matrix X.
+	x := randomGeneral(m, n, ldx, rnd)
+
+	// Applying the permutation k with Dlapmr is the same as multiplying X with P or Pᵀ from the left:
+	//  - forward permutation:  P * X
+	//  - backward permutation: Pᵀ* X
+	trans := blas.NoTrans
+	if !fwd {
+		trans = blas.Trans
+	}
+	want := zeros(m, n, n)
+	bi.Dgemm(trans, blas.NoTrans, m, n, m, 1, p.Data, p.Stride, x.Data, x.Stride, 0, want.Data, want.Stride)
+
+	// Apply the permutation in k to X.
+	impl.Dlapmr(fwd, m, n, x.Data, x.Stride, k)
+	got := x
+
+	// Check that k hasn't been modified in Dlapmr.
+	var kmod bool
+	for i, ki := range k {
+		if ki != kCopy[i] {
+			kmod = true
+			break
+		}
+	}
+	if kmod {
+		t.Errorf("%s: unexpected modification of k", name)
+	}
+
+	// Check that Dlapmr yields the same result as multiplication with P.
+	if !equalGeneral(got, want) {
+		t.Errorf("%s: unexpected result", name)
+	}
+}