// Copyright ©2017 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 mat

import (
	"testing"

	"golang.org/x/exp/rand"
)

func TestHOGSVD(t *testing.T) {
	t.Parallel()
	const tol = 1e-10
	rnd := rand.New(rand.NewSource(1))
	for cas, test := range []struct {
		r, c int
	}{
		{5, 3},
		{5, 5},
		{150, 150},
		{200, 150},

		// Calculating A_i*A_jᵀ and A_j*A_iᵀ fails for wide matrices.
		{3, 5},
	} {
		r := test.r
		c := test.c
		for n := 3; n < 6; n++ {
			data := make([]Matrix, n)
			dataCopy := make([]*Dense, n)
			for trial := 0; trial < 10; trial++ {
				for i := range data {
					d := NewDense(r, c, nil)
					for j := range d.mat.Data {
						d.mat.Data[j] = rnd.Float64()
					}
					data[i] = d
					dataCopy[i] = DenseCopyOf(d)
				}

				var gsvd HOGSVD
				ok := gsvd.Factorize(data...)
				if r >= c {
					if !ok {
						t.Errorf("HOGSVD factorization failed for %d %d×%d matrices: %v", n, r, c, gsvd.Err())
						continue
					}
				} else {
					if ok {
						t.Errorf("HOGSVD factorization unexpectedly succeeded for %d %d×%d matrices", n, r, c)
					}
					continue
				}
				for i := range data {
					if !Equal(data[i], dataCopy[i]) {
						t.Errorf("A changed during call to HOGSVD.Factorize")
					}
				}
				u, s, v := extractHOGSVD(&gsvd)
				for i, want := range data {
					var got Dense
					sigma := NewDense(c, c, nil)
					for j := 0; j < c; j++ {
						sigma.Set(j, j, s[i][j])
					}

					got.Product(u[i], sigma, v.T())
					if !EqualApprox(&got, want, tol) {
						t.Errorf("test %d n=%d trial %d: unexpected answer\nU_%[4]d * S_%[4]d * Vᵀ:\n% 0.2f\nD_%d:\n% 0.2f",
							cas, n, trial, i, Formatted(&got, Excerpt(5)), i, Formatted(want, Excerpt(5)))
					}
				}
			}
		}
	}
}

func extractHOGSVD(gsvd *HOGSVD) (u []*Dense, s [][]float64, v *Dense) {
	u = make([]*Dense, gsvd.Len())
	s = make([][]float64, gsvd.Len())
	for i := 0; i < gsvd.Len(); i++ {
		u[i] = &Dense{}
		gsvd.UTo(u[i], i)
		s[i] = gsvd.Values(nil, i)
	}
	v = &Dense{}
	gsvd.VTo(v)
	return u, s, v
}
