Skip to content

Commit 8eeece3

Browse files
authored
V0.9.0 (#22)
# v0.9.0 # The changes made in this PR is aimed at better supporting v0.9.0 of Gorgonia itself. Along the way there are some new features and optimizations, as well as some bug fixes. The majority of the work in supporting v0.9.0 of Gorgonia is to shore up the underlying architecture to support CUDA related engines. This means moving more things to rely on `Engine` while keeping the engine interface overheads low. Additionally this also means better support for column major data layouts. * Heavier reliance on `Engine` for most functions. This allows for extensibility on the data structure. * Long standing bugbear - concepts of `RowVec` and `ColVec` has been removed (thanks to @matdodgson) - Touch points: `ap.go`, `iterator.go`, `iterator_mult.go`.`shape.go`, and the tests that were correct prior to this change have semantic meaning changes too. - **POTENTIAL TECH DEBT**: `iterator_mult.go` - the solution of filling with ones is a little too dodgy for my liking. The alternative would be to change `BroadcastStrides` which will change even more things (`Concat`, `Stack` etc) * **Optimization**: - `AP` has been depointerized in `*Dense` (thanks to @docmerlin). This reduces *some* amount of GC pointer chasing, but not all - allocation is slightly improved. (`(array).fromSliceOrArrayer`, `(array).fix()` and `(array).forcefix()` are part of the improvement around the logic of allocating data. * **Bug fixes**: - Fixes subtle errors in linear algebra functions. The result is a slightly longer function but easier to reason with. - Fixes some subtle bugs in `Concat` - see also gorgonia/gorgonia#218 - Fixed some small bugs with regards to `SampleIndex` that only show up when the slices have extreme lengths. This API should have been deprecated 2 years ago, but eh... it touched a lot of external projects. * **API changes**: - `Diag` is made available. Relies heavily on an `Engine`'s implementation - `NewFlatIterator` is unexported. - `NewAP` is unexported. - `MakeAP` is used instead. - `(Tensor).DataOrder()` is added to the definiiton of what a `Tensor` is. - `(Shape).IsScalarEquiv()` is a new method. This corresponds to the change of semantics of what a `Shape` should be. - `(Shape).CalcStrides()` is exported now. This enables users to correctly calculate strides that are consistent to what the package expects. - `(Shape).CalcStridesColMajor()` is exported as the method to calculate the strides of a Col-Major `*Dense`. * **New Interfaces**: - `NonStdEngine` is an `Engine that does not allocate using the default allocator. This allows for both embedding a `DefaultEngine` while overriding the allocation behaviour. - `Diager` - any engine that can return a tensor that only contains the diagonal values of the input - `NaNChecker` and `InfChecker` - engines that can check a tensor for NaN and Inf * **New Features**: * Added full support for colmajor tensors. (fixes #10) - TODO: colmajor iterator's prev() method (see #34) - Added serialization to Protobuf and Flatbuffers * TODO: Add example for serialization (see #35 and #36) - Added more support for sparse CS tensors. * **New Subpackages**: * `native` is a subpackage that essentially gives users a native, Go-based iterator. Basically the ability to go from a `*Dense` to a `[][]T` or `[][][]T` **without extra allocations** (for the data). This was pulled into `master` earlier, but as of v0.9.0, the generic version is available too. * **Semantic Changes**: - `Shape` has semantic changes regarding whether or not a shape is scalar. A scalar shape is defined to be `Shape{}` or `Shape{1}` only. Formerly, `Shape{1,1}` was also considered to be scalar. Now they're considered to be `ScalarEquivalent` (along with `Shape{1, 1, .... , 1}`) - A `Dtype` that is is orderable is also now comparable for equality. If `RegisterOrd` is called with a new `Dtype`, it is also automatically registered as `Eq`. * **Cosmetic Changes**: - README has been updated to point to correct doc pages
1 parent 1ad4131 commit 8eeece3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+5010
-1243
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ branches:
77
go:
88
- 1.8.x
99
- 1.9.x
10+
- 1.10.x
1011
- tip
1112

1213
env:

.travis/test.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ go test -v -a -covermode=atomic -coverprofile=test.cover .
66
go test -tags='avx' -a -covermode=atomic -coverprofile=avx.cover .
77
go test -tags='sse' -a -covermode=atomic -coverprofile=sse.cover .
88
go test -tags='inplacetranspose' -a -covermode=atomic -coverprofile=inplacetranspose.cover .
9+
go test -a -covermode=atomic -coverprofile=native.cover ./native/.
910

1011
# because coveralls only accepts one coverage file at one time... we combine them into one gigantic one
11-
covers=(./test.cover ./avx.cover ./sse.cover ./inplacetranspose.cover)
12+
covers=(./test.cover ./avx.cover ./sse.cover ./inplacetranspose.cover ./native.cover)
1213
echo "mode: set" > ./final.cover
1314
tail -q -n +2 "${covers[@]}" >> ./final.cover
1415
goveralls -coverprofile=./final.cover -service=travis-ci

CONTRIBUTORS.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Naseer Dari (@ndari) - errors and error handling
55
* Joe Kabaka (@kabaka0) - masked array functionality
66
* Stuart Carnie (@stuartcarnie) - performance optimization for iterators
7+
* Jorge Landivar (@docmerlin) - performance optimization for `*Dense`
78

89
# Contributors
910

@@ -13,8 +14,8 @@
1314
* David Soller | @3ygun
1415
* Davor Kapsa | @dvrkps
1516
* James Michael DuPont | @h4ck3rm1k3
16-
* Jorge Landivar | @docmerlin
1717
* Yuanlin Lian | @alienchow
18+
* Andrew SnodGrass | @pointlander
1819

1920

2021

README.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# Package `tensor` [![GoDoc](https://godoc.org/github.com/gorgonia/tensor?status.svg)](https://godoc.org/github.com/gorgonia/tensor) [![Build Status](https://travis-ci.org/gorgonia/tensor.svg?branch=master)](https://travis-ci.org/gorgonia/tensor) [![Coverage Status](https://coveralls.io/repos/github/gorgonia/tensor/badge.svg?branch=master)](https://coveralls.io/github/gorgonia/tensor?branch=master) #
1+
# Package `tensor` [![GoDoc](https://godoc.org/gorgonia.org/tensor?status.svg)](https://godoc.org/gorgonia.org/tensor) [![GitHub version](https://badge.fury.io/gh/gorgonia%2Ftensor.svg)](https://badge.fury.io/gh/gorgonia%2Ftensor) [![Build Status](https://travis-ci.org/gorgonia/tensor.svg?branch=master)](https://travis-ci.org/gorgonia/tensor) [![Coverage Status](https://coveralls.io/repos/github/gorgonia/tensor/badge.svg?branch=master)](https://coveralls.io/github/gorgonia/tensor?branch=master) [![Go Report Card](https://goreportcard.com/badge/gorgonia.org/tensor)](https://goreportcard.com/report/gorgonia.org/tensor) [![unstable](http://badges.github.io/stability-badges/dist/unstable.svg)](http://github.com/badges/stability-badges)#
2+
23
Package `tensor` is a package that provides efficient, generic (by some definitions of generic) n-dimensional arrays in Go. Also in this package are functions and methods that are used commonly in arithmetic, comparison and linear algebra operations.
34

4-
The main purpose of this package is to support the operations required by [Gorgonia](https://github.com/chewxy/gorgonia).
5+
The main purpose of this package is to support the operations required by [Gorgonia](https://gorgonia.org/gorgonia).
56

67
## Introduction ##
78
In the data analysis world, [Numpy](http://http://www.numpy.org/) and [Matlab](https://www.mathworks.com/products/matlab.html) currently reign supreme. Both tools rely heavily on having performant n-dimensional arrays, or tensors. **There is an obvious need for multidimensional arrays in Go**.
@@ -50,15 +51,15 @@ The `*Dense` tensor is the primary tensor and is represented by a singular flat
5051

5152
### Compressed Sparse Column Matrix ###
5253

53-
Coming soon
54+
Documentation Coming soon
5455

5556
### Compressed Sparse Row Matrix ###
5657

57-
Coming soon
58+
Documentation Coming soon
5859

5960
## Usage ##
6061

61-
To install: `go get -u "github.com/chewxy/gorgonia/tensor"`
62+
To install: `go get -u "gorgonia.org/tensor"`
6263

6364
To create a matrix with package `tensor` is easy:
6465

@@ -129,7 +130,7 @@ b.SetAt(1000, 0, 1, 2)
129130
fmt.Printf("b:\n%v", b)
130131
```
131132

132-
There is a whole laundry list of methods and functions available at the [godoc](https://godoc.org/github.com/chewxy/gorgonia/tensor) page
133+
There is a whole laundry list of methods and functions available at the [godoc](https://godoc.org/gorgonia.org/tensor) page
133134

134135

135136

@@ -198,7 +199,7 @@ The above call will use `myEngine` to allocate memory instead. This is useful in
198199

199200
### Other failed designs ###
200201

201-
The alternative designs can be seen in the [ALTERNATIVE DESIGNS document](https://github.com/chewxy/gorgonia/blob/master/tensor/ALTERNATIVEDESIGNS.md)
202+
The alternative designs can be seen in the [ALTERNATIVE DESIGNS document](https://github.com/tensor/blob/master/ALTERNATIVEDESIGNS.md)
202203

203204
## Generic Features ##
204205

ap.go

+111-46
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,30 @@ type AP struct {
2626
Δ Triangle
2727
}
2828

29-
// NewAP creates a new AP, given the shape and strides
30-
func NewAP(shape Shape, strides []int) *AP {
31-
ap := borrowAP()
29+
func makeAP(size int) AP {
30+
return AP{
31+
shape: Shape(BorrowInts(size)),
32+
strides: BorrowInts(size),
33+
}
34+
}
35+
36+
// MakeAP creates an AP, given the shape and strides.
37+
func MakeAP(shape Shape, strides []int, o DataOrder, Δ Triangle) AP {
38+
return AP{
39+
shape: shape,
40+
strides: strides,
41+
o: o,
42+
Δ: Δ,
43+
fin: true,
44+
}
45+
}
46+
47+
// Init initalizes an already created AP with a shape and stries.
48+
// It will panic if AP is nil.
49+
func (ap *AP) Init(shape Shape, strides []int) {
3250
ap.shape = shape
3351
ap.strides = strides
3452
ap.fin = true
35-
return ap
3653
}
3754

3855
// SetShape is for very specific times when modifying the AP is necessary, such as reshaping and doing I/O related stuff
@@ -46,6 +63,9 @@ func (ap *AP) SetShape(s ...int) {
4663
if !ap.fin {
4764
// scalars are a special case, we don't want to remove it completely
4865
if len(s) == 0 {
66+
if ap.shape == nil || ap.strides == nil {
67+
ap.shape = Shape{}
68+
}
4969
ap.shape = ap.shape[:0]
5070
ap.strides = ap.strides[:0]
5171
return
@@ -102,9 +122,54 @@ func (ap *AP) IsScalar() bool { return ap.shape.IsScalar() }
102122
// IsMatrix returns true if it's a matrix. This is mostly a convenience method. RowVec and ColVecs are also considered matrices
103123
func (ap *AP) IsMatrix() bool { return len(ap.shape) == 2 }
104124

105-
// Clone clones the *AP. Clearly.
106-
func (ap *AP) Clone() (retVal *AP) {
107-
retVal = BorrowAP(len(ap.shape))
125+
// IsZero tell us if the ap has zero size
126+
func (ap *AP) IsZero() bool {
127+
return len(ap.shape) == 0 && len(ap.strides) == 0 && !ap.fin && ap.o == 0 && ap.Δ == 0
128+
}
129+
130+
// Zero zeros out an AP.
131+
func (ap *AP) zero() {
132+
// log.Printf("ZEROING. Called by %v", string(debug.Stack()))
133+
134+
// Jorge's original implementation for zeroing a AP is as below
135+
// but to cater for the (*Dense).fix() method of the *Dense
136+
// a nil shape is used to signal unsetness
137+
// so we cannot just truncate the shape even though it would be a lot more efficient
138+
139+
// ap.shape = ap.shape[:0]
140+
// ap.strides = ap.strides[:0]
141+
ReturnInts([]int(ap.shape))
142+
ReturnInts(ap.strides)
143+
ap.zeroOnly()
144+
}
145+
146+
// side effect free zeroing
147+
func (ap *AP) zeroOnly() {
148+
ap.shape = nil
149+
ap.strides = nil
150+
151+
ap.fin = false
152+
ap.o = 0
153+
ap.Δ = 0
154+
}
155+
156+
func (ap *AP) zeroWithDims(dims int) {
157+
//ap.shape = BorrowInts(dims)
158+
//ap.strides = BorrowInts(dims)
159+
if cap(ap.shape) >= dims {
160+
ap.shape = ap.shape[:dims]
161+
}
162+
ap.shape = BorrowInts(dims)
163+
if cap(ap.strides) >= dims {
164+
ap.strides = ap.strides[:dims]
165+
}
166+
ap.strides = BorrowInts(dims)
167+
}
168+
169+
// Clone clones the *AP. Clearly. It returns AP
170+
func (ap *AP) Clone() (retVal AP) {
171+
retVal = makeAP(cap(ap.shape))
172+
108173
copy(retVal.shape, ap.shape)
109174
copy(retVal.strides, ap.strides)
110175

@@ -118,21 +183,25 @@ func (ap *AP) Clone() (retVal *AP) {
118183
return
119184
}
120185

186+
func (ap *AP) CloneTo(dest *AP) {
187+
dest.shape = append(dest.shape[:0], ap.shape...)
188+
dest.strides = append(dest.strides[:0], ap.strides...)
189+
dest.fin = ap.fin
190+
dest.o = ap.o
191+
dest.Δ = ap.Δ
192+
}
193+
121194
// DataOrder returns the data order of the AP.
122195
func (ap *AP) DataOrder() DataOrder { return ap.o }
123196

124197
// C returns true if the access pattern is C-contiguous array
125-
func (ap *AP) C() bool {
126-
return ap.o.isRowMajor() && ap.o.isContiguous()
127-
}
198+
func (ap *AP) C() bool { return ap.o.IsRowMajor() && ap.o.IsContiguous() }
128199

129200
// F returns true if the access pattern is Fortran contiguous array
130-
func (ap *AP) F() bool {
131-
return ap.o.isColMajor() && ap.o.isContiguous()
132-
}
201+
func (ap *AP) F() bool { return ap.o.IsColMajor() && ap.o.IsContiguous() }
133202

134203
// S returns the metadata of the sliced tensor.
135-
func (ap *AP) S(size int, slices ...Slice) (newAP *AP, ndStart, ndEnd int, err error) {
204+
func (ap *AP) S(size int, slices ...Slice) (newAP AP, ndStart, ndEnd int, err error) {
136205
if len(slices) > len(ap.shape) {
137206
// error
138207
err = errors.Errorf(dimMismatch, len(ap.shape), len(slices))
@@ -146,7 +215,7 @@ func (ap *AP) S(size int, slices ...Slice) (newAP *AP, ndStart, ndEnd int, err e
146215

147216
var outerDim int
148217
order := ap.o
149-
if ap.o.isRowMajor() || ap.IsVector() {
218+
if ap.o.IsRowMajor() || ap.IsVector() {
150219
outerDim = 0
151220
} else {
152221
outerDim = len(ap.shape) - 1
@@ -160,12 +229,13 @@ func (ap *AP) S(size int, slices ...Slice) (newAP *AP, ndStart, ndEnd int, err e
160229

161230
size := ap.shape[i]
162231
var stride int
163-
if ap.IsVector() {
164-
// handles non-vanilla vectors
165-
stride = ap.strides[0]
166-
} else {
167-
stride = ap.strides[i]
168-
}
232+
stride = ap.strides[i]
233+
// if ap.IsVector() {
234+
// // handles non-vanilla vectors
235+
// stride = ap.strides[0]
236+
// } else {
237+
// stride = ap.strides[i]
238+
// }
169239

170240
var start, end, step int
171241
if start, end, step, err = SliceDetails(sl, size); err != nil {
@@ -196,37 +266,29 @@ func (ap *AP) S(size int, slices ...Slice) (newAP *AP, ndStart, ndEnd int, err e
196266

197267
if ndEnd-ndStart == 1 {
198268
// scalars are a special case
199-
newAP = borrowAP()
269+
newAP = AP{}
200270
newAP.SetShape() // make it a Scalar
201271
newAP.lock()
202272
} else {
203273

204274
// drop any dimension with size 1, except the last dimension
275+
offset := 0
205276
for d := 0; d < dims; d++ {
206-
if newShape[d] == 1 /*&& d != t.dims-1 && dims > 2*/ {
277+
if newShape[d] == 1 && offset+d <= len(slices)-1 && slices[offset+d] != nil /*&& d != t.dims-1 && dims > 2*/ {
207278
newShape = append(newShape[:d], newShape[d+1:]...)
208279
newStrides = append(newStrides[:d], newStrides[d+1:]...)
209280
d--
210281
dims--
282+
offset++
211283
}
212284
}
213-
214-
//fix up strides
215-
if newShape.IsColVec() {
216-
stride0 := newStrides[0]
217-
ReturnInts(newStrides)
218-
newStrides = BorrowInts(1)
219-
newStrides[0] = stride0
220-
}
221-
222-
newAP = NewAP(newShape, newStrides)
223-
newAP.o = order
285+
newAP = MakeAP(newShape, newStrides, order, ap.Δ)
224286
}
225287
return
226288
}
227289

228290
// T returns the transposed metadata based on the given input
229-
func (ap *AP) T(axes ...int) (retVal *AP, a []int, err error) {
291+
func (ap *AP) T(axes ...int) (retVal AP, a []int, err error) {
230292
// prep axes
231293
if len(axes) > 0 && len(axes) != ap.Dims() {
232294
err = errors.Errorf(dimMismatch, ap.Dims(), len(axes))
@@ -244,7 +306,7 @@ func (ap *AP) T(axes ...int) (retVal *AP, a []int, err error) {
244306

245307
// if axes is 0, 1, 2, 3... then no op
246308
if monotonic, incr1 := IsMonotonicInts(axes); monotonic && incr1 && axes[0] == 0 {
247-
return ap, a, noopError{}
309+
return ap.Clone(), a, noopError{}
248310
}
249311

250312
currentShape := ap.shape
@@ -270,12 +332,8 @@ func (ap *AP) T(axes ...int) (retVal *AP, a []int, err error) {
270332
}
271333
}
272334

273-
retVal = borrowAP()
274-
retVal.shape = shape
275-
retVal.strides = strides
276-
if ap.IsVector() {
277-
retVal.strides = retVal.strides[:1]
278-
}
335+
o := MakeDataOrder(ap.o, Transposed)
336+
retVal = MakeAP(shape, strides, o, ap.Δ)
279337
retVal.fin = true
280338
return
281339
}
@@ -286,14 +344,21 @@ func (ap *AP) unlock() { ap.fin = false }
286344

287345
func (ap *AP) calcStrides() []int {
288346
switch {
289-
case ap.o.isRowMajor():
290-
return ap.shape.calcStrides()
291-
case ap.o.isColMajor():
292-
return ap.shape.calcStridesColMajor()
347+
case ap.o.IsRowMajor():
348+
return ap.shape.CalcStrides()
349+
case ap.o.IsColMajor():
350+
return ap.shape.CalcStridesColMajor()
293351
}
294352
panic("unreachable")
295353
}
296354

355+
// setDataOrder is a method such that any tensor that embeds *AP will have the same method
356+
func (ap *AP) setDataOrder(o DataOrder) {
357+
if !o.HasSameOrder(ap.o) {
358+
ap.o = ap.o.toggleColMajor()
359+
}
360+
}
361+
297362
// TransposeIndex returns the new index given the old index
298363
func TransposeIndex(i int, oldShape, pattern, oldStrides, newStrides []int) int {
299364
oldCoord, err := Itol(i, oldShape, oldStrides)

0 commit comments

Comments
 (0)