gonum / matrix

Matrix packages for the Go language [DEPRECATED]

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

matrix/mat64: add `Exp' to TriDense

ChristopherRabotin opened this issue · comments

commented

Upper and lower triangular matrices can be assimilated to dual numbers, herein represented as a+b\epsilon. There's an interesting property with dual numbers where exp(b\epislon)=1+b\epsilon (where \epsilon is a N dimensional representation of a dual number), cf. this paper. This can be trivially grown to any (2n)x(2n) triangular matrix, but is not trivially applicable to triangular matrices of even rows/columns.

Hence, implementing Exp on the Triangular struct would ensure an extremely simple computation as described below in pseudo code:

let A be a lower triangular;
if diagonal is non-nil:
    extract diagonal as a scaled identity matrix
    expDiag <- trivially compute its exponential;
endif
expLower <- expDiag * (identity + A)

If the matrix is an upper triangular, perform the same computation on the transpose of A (and return the transpose).

If there is no objection to this feature, I volunteer to implement it.

Edit: I forgot to mention that trivial operations also include sums, products and divisions.

Second edit: Looking a bit more into what is currently available in TriDense, I see that part of what is in this issue would involve enhancing MulTri for the specific case where there is an even number of rows/columns.

I don't understand what's being suggested. The original title is to add Exp to Triangular. That seems fine. By "arithmetic functions" do you mean Add etc? That would be fine too, though it would probably better to have a separate tracking issue.

Is the part about dual numbers an implementation suggestion for Exp, or a suggestion to modify the implementation of TriDense to support the use case of a triangle of dual numbers?

(I do have https://github.com/btracey/hyperdual if you need it)

commented

The suggestion is only to add an Exp function to TriDense. Subsequently, I think it could be interesting to add Sub to TriDense and slightly modify Add of TriDense to simplify the operation if the provided matrix has an even number of rows.

There will be no support for dual numbers, I'll just be using some of their properties as a tool to simplify the number of operations needed.

(P.S.: I think there's a bug in your hyperdual implementation in the Add function. I think line 44 should be a.data[i] += v.)

commented

Sorry for the multiple comments here. I'm encountering an oddity in the implementation, and I suspect that it's due to my total misunderstanding of how reuseAs works. Any hints would be appreciated.

It seems to me that reuseAs prevents modifications of the underlying data, as if there was a deferred function which resets it to the original value.

Here's an output from a basic test that I'm running: the first matrix shown is printed within the new Exp function, and the second one is printed from the main (input matrix is mat64.NewTriDense(2, matrix.Lower, []float64{0, 0, 2, 0})).

⎡1  0⎤
⎣2  1⎦
⎡0  0⎤
⎣0  0⎦

Below is the code executed in this test:

func (t *TriDense) Exp(a Triangular) {
	n, kind := a.Triangle()
	t.reuseAs(a.Triangle())

	if n%2 == 0 {
		// Exponential is trivial. Calculation performed on lower triangular.
		aP := NewTriDense(n, kind, nil)
		isUpper := kind == matrix.Upper
		if isUpper {
			aP.Copy(a.T())
		} else {
			aP.Copy(a)
		}
		computeKind := matrix.Lower
		t = NewTriDense(n, computeKind, nil)
		diag := NewTriDense(n, computeKind, nil)
		eye := NewTriDense(n, computeKind, nil)
		nilDiag := true
		for i := 0; i < n; i++ {
			eye.SetTri(i, i, 1)
			v := aP.At(i, i)
			if v != 0 {
				nilDiag = false
			}
			diag.SetTri(i, i, v)
			aP.SetTri(i, i, 0)
		}
		// TODO(ChristopherRabotin): Use (*TriDense).Add(a, b) once written.
		// Compute exponential which is simply diag*(eye + aP) (if diag != [0]).
		for i := 0; i < n; i++ {
			for j := 0; j < n; j++ {
				if i >= j {
					t.SetTri(i, j, eye.At(i, j)+aP.At(i, j))
				}
			}
		}
		if !nilDiag {
			t.MulTri(diag, t)
		}
		if isUpper {
			t.Copy(t.T())
		}
		fmt.Printf("%+v\n", Formatted(t))
	} else {
		// Convert to Dense then convert back to Triangle.
		// The exponential of a triangular matrix is always a triangular matrix
		// of the same kind.
		var triExp Dense
		triExp.Exp(DenseCopyOf(t))
		t.Copy(&triExp) // Store result in receiver.
	}
}

Did you figure out the internal issue (I can describe if not)?

reuseAs doesn't do anything with copying and defer. reuseAs checks the size of the receiver if it is non-zero, and makes it the correct size if it is non-zero.

I'm not exactly sure where you're printing, but t.Copy(t.T()) is wrong. Copy can't be used with itself as a receiver. This is a bug in TriDense.Copy, it should panic like the Dense version does.

As a side note, you should probably working with upper triangular matrices. They're faster in gonum since we use row-major storage.

commented

The problem is t = NewTriDense(n, computeKind, nil). The pointer itself is passed by value (copied). This doesn't modify what the pointer points to, it changes the pointer altogether. What you really want is *t = *NewTriDense, you want to assign the value the pointer points to for the code to have the intended effect.

commented

Closed as per #420 (comment).