diff --git a/mat64/shadow.go b/mat64/shadow.go index 0402ee6..277ed11 100644 --- a/mat64/shadow.go +++ b/mat64/shadow.go @@ -191,15 +191,44 @@ func (v *Vector) checkOverlap(a blas64.Vector) bool { // rectanglesOverlap returns whether the strided rectangles a and b overlap // when b is offset by off elements after a but has at least one element before // the end of a. a and b have aCols and bCols respectively. +// +// rectanglesOverlap works by shifting both matrices left such that the left +// column of a is at 0. The column indexes are flattened by obtaining the shifted +// relative left and right column positions modulo the common stride. This allows +// direct comparison of the column offsets when the matrix backing data slices +// are known to overlap. func rectanglesOverlap(off, aCols, bCols, stride int) bool { if stride == 1 { + // Unit stride means overlapping data + // slices must overlap as matrices. return true } + + // Flatten the shifted matrix column positions + // so a starts at 0, modulo the common stride. + const aFrom = 0 aTo := aCols + // The mod stride operations here make the from + // and to indexes comparable between a and b when + // the data slices of a and b overlap. bFrom := off % stride bTo := (bFrom + bCols) % stride + + // Compare the case where the b matrix is + // not wrapped. if bFrom < bTo { return aTo > bFrom } - return bTo > 0 + + // Compare the case where the right column + // of b aligns with the stride of the backing + // data slice. + if bTo == 0 { + return bFrom < aTo + } + + // Compare the case where the right column of + // b wraps around to the left again, modulo + // stride. + return bTo > aFrom } diff --git a/mat64/shadow_test.go b/mat64/shadow_test.go index 8eb3957..c38faf4 100644 --- a/mat64/shadow_test.go +++ b/mat64/shadow_test.go @@ -89,3 +89,29 @@ type interval struct{ from, to int } func intervalsOverlap(a, b interval) bool { return a.to > b.from && b.to > a.from } + +// See https://github.com/gonum/matrix/issues/359 for details. +func TestIssue359(t *testing.T) { + for xi := 0; xi < 2; xi++ { + for xj := 0; xj < 2; xj++ { + for yi := 0; yi < 2; yi++ { + for yj := 0; yj < 2; yj++ { + a := NewDense(3, 3, []float64{ + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, + }) + x := a.View(xi, xj, 2, 2).(*Dense) + y := a.View(yi, yj, 2, 2).(*Dense) + + panicked, _ := panics(func() { x.checkOverlap(y.mat) }) + if !panicked { + t.Errorf("expected panic for aliased with offsets x(%d,%d) y(%d,%d):\nx:\n%v\ny:\n%v", + xi, xj, yi, yj, Formatted(x), Formatted(y), + ) + } + } + } + } + } +}