Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove varargs on subscripts to avoid subtle bugs when compiling code… #45

Merged
merged 2 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 43 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ Like NumPy's ndarray, slices and strides can be created.

```swift
let a = NdArray<Double>.range(to: 10)
let b = NdArray(a[0... ~ 2]) // every second element
let b = NdArray(a[[0... ~ 2]]) // every second element
print(a) // [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
print(b) // [0.0, 2.0, 4.0, 6.0, 8.0]
print(b.strides) // [2]
b[0...].set(0)
b[[0...]].set(0)
print(a) // [0.0, 1.0, 0.0, 3.0, 0.0, 5.0, 0.0, 7.0, 0.0, 9.0]
print(b) // [0.0, 0.0, 0.0, 0.0, 0.0]
```
Expand All @@ -106,24 +106,24 @@ NumPy's syntax.

```
NdArray NumPy
a[0...] a[::]
a[0... ~ 2] a[::2]
a[..<42 ~ 2] a[:42:2]
a[3..<42 ~ 2] a[3:42:2]
a[3...42 ~ 2] a[3:41:2]
a[[0...]] a[::]
a[[0... ~ 2]] a[::2]
a[[..<42 ~ 2]] a[:42:2]
a[[3..<42 ~ 2]] a[3:42:2]
a[[3...42 ~ 2]] a[3:41:2]
```

Alternatively, slice objects can be created programmatically. The following notations are equivalent:

```
a[0...] ≡ a[Slice()]
a[1...] ≡ a[Slice(lowerBound: 1)]
a[..<42] ≡ a[Slice(upperBound: 42)]
a[...42] ≡ a[Slice(upperBound: 43)]
a[1..<42] ≡ a[Slice(lowerBound: 1, upperBound: 42)]
a[1... ~ 2] ≡ a[Slice(lowerBound: 1, upperBound, stride: 2)]
a[..<42 ~ 3] ≡ a[Slice(upperBound: 42, stride: 3)]
a[1..<42 ~ 3] ≡ a[Slice(lowerBound: 1, upperBound: 42, stride: 3)]
a[[0...]] ≡ a[Slice()]
a[[1...]] ≡ a[Slice(lowerBound: 1)]]
a[[..<42]] ≡ a[Slice(upperBound: 42)]]
a[[...42]] ≡ a[Slice(upperBound: 43)]]
a[[1..<42]] ≡ a[Slice(lowerBound: 1, upperBound: 42)]]
a[[1... ~ 2]] ≡ a[Slice(lowerBound: 1, upperBound, stride: 2)]]
a[[..<42 ~ 3]] ≡ a[Slice(upperBound: 42, stride: 3)]]
a[[1..<42 ~ 3]] ≡ a[Slice(lowerBound: 1, upperBound: 42, stride: 3)]]
```

Note, to avoid confusion with pure indexing, integer literals need to be converted to a slice explicitly. This means
Expand All @@ -146,11 +146,11 @@ let a = NdArray<Double>.ones([2, 2])
print(a)
// [[1.0, 1.0],
// [1.0, 1.0]]
a[Slice(1)].set(0.0)
a[[Slice(1)]].set(0.0)
print(a)
// [[1.0, 1.0],
// [0.0, 0.0]]
a[0..., Slice(1)].set(2.0)
a[[0..., Slice(1)]].set(2.0)
print(a)
// [[1.0, 2.0],
// [0.0, 2.0]]
Expand All @@ -161,7 +161,7 @@ use [element indexing](#element-manipulation) instead or use the `Vector` subtyp

```swift
let a = NdArray<Double>.range(to: 4)
print(a[Slice(0)]) // [0.0]
print(a[[Slice(0)]]) // [0.0]
print(a[[0]]) // 0.0
let v = Vector(a)
print(v[0] as Double) // 0.0
Expand All @@ -177,7 +177,7 @@ let a = NdArray<Double>.ones([2, 2])
print(a)
// [[1.0, 1.0],
// [1.0, 1.0]]
a[0..., Slice(1)].set(0.0)
a[[0..., Slice(1)]].set(0.0)
print(a)
// [[1.0, 0.0],
// [1.0, 0.0]]
Expand All @@ -193,7 +193,7 @@ print(a)
// [4.0, 5.0],
// [6.0, 7.0],
// [8.0, 9.0]]
a[0... ~ 2].set(0.0)
a[[0... ~ 2]].set(0.0)
print(a)
// [[0.0, 0.0],
// [2.0, 3.0],
Expand All @@ -212,9 +212,9 @@ Ranges `n..<m` and closed ranges `n...m` allow selecting certain sub arrays.

```swift
let a = NdArray<Double>.range(to: 10)
print(a[2..<4]) // [2.0, 3.0]
print(a[2...4]) // [2.0, 3.0, 4.0]
print(a[2...4 ~ 2]) // [2.0, 4.0]
print(a[[2..<4]]) // [2.0, 3.0]
print(a[[2...4]]) // [2.0, 3.0, 4.0]
print(a[[2...4 ~ 2]]) // [2.0, 4.0]
```

### `PartialRangeFrom`, `PartialRangeUpTo` and `PartialRangeThrough` Slices
Expand All @@ -223,16 +223,16 @@ Partial ranges `...<m`, `...m` and `n...` define only one bound.

```swift
let a = NdArray<Double>.range(to: 10)
print(a[..<4]) // [0.0, 1.0, 2.0, 3.0]
print(a[...4]) // [0.0, 1.0, 2.0, 3.0, 4.0]
print(a[4...]) // [4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
print(a[4..., 2]) // [4.0, 6.0, 8.0]
print(a[[..<4]]) // [0.0, 1.0, 2.0, 3.0]
print(a[[...4]]) // [0.0, 1.0, 2.0, 3.0, 4.0]
print(a[[4...]]) // [4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
print(a[[4... ~ 2]]) // [4.0, 6.0, 8.0]
```

## Element Manipulation

Individual elements can be indexed by passing a (Swift) array as index. In the future, there may be varargs support on
subscript to be able to pass indices directly.
subscript to be able to pass indices directly [#44](https://github.com/dastrobu/NdArray/issues/44).

```swift
let a = NdArray<Double>.range(to: 12).reshaped([2, 2, 3])
Expand Down Expand Up @@ -266,7 +266,7 @@ Scaling every second element in a matrix by its row index could be done in the f
```swift
let a = NdArray<Double>.ones([4, 3])
for i in 0..<a.shape[0] {
a[Slice(i), 0... ~ 2] *= Double(i)
a[[Slice(i), 0... ~ 2]] *= Double(i)
}
print(a)
// [[0.0, 1.0, 0.0],
Expand Down Expand Up @@ -471,8 +471,8 @@ When creating a new array from an existing one, no copy is made unless necessary
let A = NdArray<Double>.ones(5)
var B = NdArray(A) // no copy
B = NdArray(copy: A) // copy explicitly required
B = NdArray(A[0... ~ 2]) // no copy, but B will not be contiguous
B = NdArray(A[0... ~ 2], order: .C) // copy, because otherwise new array will not have C ordering
B = NdArray(A[[0... ~ 2]]) // no copy, but B will not be contiguous
B = NdArray(A[[0... ~ 2]], order: .C) // copy, because otherwise new array will not have C ordering
```

### Subtypes
Expand Down Expand Up @@ -509,13 +509,16 @@ The functions of these libraries are provided by the

### TLDR

To migrate from `<=0.3.0` to `0.4.0` you should upgrade to 0.4.0 and fix all compile warnings. Here are a few rules of
thumb:
To migrate from `<=0.3.0` to `0.4.0` upgrade to `0.4.0` first and fix all compile warnings. Do not skip `0.4.0`, since
this can result in undesired behaviour (`a[0..., 2]` will be interpreted as "take slice along zeroth and first
dimension" from `0.5.0` instead of "take slice along zeroth dimension with stride 2" `<=0.3.0`).

Here are a few rules of thumb to fix compile warnings after upgrading to `0.4.0`:

```
a[...] => a[0...] // UnboundedRange is now expresed by 0...
a[..., 2] => a[0... ~ 2] // strides are now expressed by the stride operator ~
a[...][3] => a[0..., Slice(3)] // multi dimensional slices are now created within one subscript call [] not many [][][]
a[...] => a[[0...]] // UnboundedRange is now expresed by 0...
a[..., 2] => a[[0... ~ 2]] // strides are now expressed by the stride operator ~
a[...][3] => a[[0..., Slice(3)]] // multi dimensional slices are now created within one subscript call [] not many [][][]
```

### Removal of `NdArraySlice`
Expand Down Expand Up @@ -556,10 +559,10 @@ object, slices are obtained by

```swift
let A = NdArray<Double>.ones([2, 2, 2])
var B = A[0...] // NdArray with sliced = 1, i.e. one dimension has been sliced
B = A[0..., 0... ~ 2] // NdArray with sliced = 2, i.e. one dimension has been sliced
B = A[0..., 0... ~ 2, ..<1] // NdArray with sliced = 3, i.e. one dimension has been sliced
B = A[0..., 0... ~ 2, ..<1, 0...] // Precondition failed: Cannot slice array with ndim 3 more than 3 times.
var B = A[[0...]] // NdArray with sliced = 1, i.e. one dimension has been sliced
B = A[[0..., 0... ~ 2]] // NdArray with sliced = 2, i.e. one dimension has been sliced
B = A[[0..., 0... ~ 2, ..<1]] // NdArray with sliced = 3, i.e. one dimension has been sliced
B = A[[0..., 0... ~ 2, ..<1, 0...]] // Precondition failed: Cannot slice array with ndim 3 more than 3 times.
```

With this API, there is no subtypes returned when slicing, requiring to remember how many times the array was already
Expand Down
14 changes: 7 additions & 7 deletions Sources/NdArray/Matrix.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ open class Matrix<T>: NdArray<T> {
Cannot transpose matrix with shape \(shape) to matrix with shape \(out.shape).
Precondition failed while trying to transpose \(debugDescription) to \(out.debugDescription).
""")
out[0..., 0...] = self.transposed()[0..., 0...]
out[[0..., 0...]] = self.transposed()[[0..., 0...]]
}
}
}
Expand Down Expand Up @@ -167,11 +167,11 @@ public extension Matrix where T == Double {
return B
}
// copy rhs to work space (thereby also making sure it is F contiguous)
B[0...] = rhs[0...]
B[[0...]] = rhs[[0...]]

// copy self to A, since it is modified (thereby also making sure it is F contiguous)
let A = Matrix<T>(empty: shape, order: .F)
A[0...] = self[0...]
A[[0...]] = self[[0...]]
var nrhs = __CLPK_integer(B.shape[1])
var ipiv: [__CLPK_integer] = [__CLPK_integer].init(repeating: 0, count: Int(n))
var lda: __CLPK_integer = __CLPK_integer(n)
Expand Down Expand Up @@ -202,7 +202,7 @@ public extension Matrix where T == Double {
Precondition failed while trying to solve \(debugDescription).
""")
let A = out ?? Matrix(empty: shape, order: .F)
A[0...] = self[0...]
A[[0...]] = self[[0...]]

var ipiv = try A.luFactor()

Expand Down Expand Up @@ -326,11 +326,11 @@ public extension Matrix where T == Float {
return B
}
// copy rhs to work space (thereby also making sure it is F contiguous)
B[0...] = rhs[0...]
B[[0...]] = rhs[[0...]]

// copy self to A, since it is modified (thereby also making sure it is F contiguous)
let A = Matrix<T>(empty: shape, order: .F)
A[0...] = self[0...]
A[[0...]] = self[[0...]]
var nrhs = __CLPK_integer(B.shape[1])
var ipiv: [__CLPK_integer] = [__CLPK_integer].init(repeating: 0, count: Int(n))
var lda: __CLPK_integer = __CLPK_integer(n)
Expand Down Expand Up @@ -361,7 +361,7 @@ public extension Matrix where T == Float {
Precondition failed while trying to solve \(debugDescription).
""")
let A = out ?? Matrix(empty: shape, order: .F)
A[0...] = self[0...]
A[[0...]] = self[[0...]]

var ipiv = try A.luFactor()

Expand Down
12 changes: 0 additions & 12 deletions Sources/NdArray/NdArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -446,18 +446,6 @@ open class NdArray<T>: CustomDebugStringConvertible,
newValue.copyTo(self[slices])
}
}

/**
slice access
*/
public subscript(slices: Slice...) -> NdArray<T> {
get {
self[slices]
}
set {
self[slices] = newValue
}
}
}

// extension helping to handle different memory alignments
Expand Down
4 changes: 2 additions & 2 deletions Sources/NdArray/Vector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public extension Vector where T == Double {
// make a copy sort it and copy back if array is not contiguous
let cpy = Vector(copy: self)
vDSP_vsortD(cpy.data, n, sortOrder)
self[0...] = cpy[0...]
self[[0...]] = cpy[[0...]]
}
}

Expand Down Expand Up @@ -130,7 +130,7 @@ public extension Vector where T == Float {
// make a copy sort it and copy back if array is not contiguous
let cpy = Vector(copy: self)
vDSP_vsort(cpy.data, n, sortOrder)
self[0...] = cpy[0...]
self[[0...]] = cpy[[0...]]
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/NdArray/apply.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public extension NdArray {
// make sure the array is not sliced
let a = NdArray(self)
for i in 0..<shape[0] {
try fSlice(a[Slice(i)])
try fSlice(a[[Slice(i)]])
}
}
}
Expand Down Expand Up @@ -59,7 +59,7 @@ public extension NdArray {
let a = NdArray(self)
let b = NdArray(other)
for i in 0..<shape[0] {
try fSlice(a[Slice(i)], b[Slice(i)])
try fSlice(a[[Slice(i)]], b[[Slice(i)]])
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/NdArray/copy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public extension NdArray {
let dst = NdArray(out) // make a NdArray view to get rid of sliced when doing subscripts
let src = NdArray(self)
for i in 0..<shape[0] {
dst[Slice(i)] = src[Slice(i)]
dst[[Slice(i)]] = src[[Slice(i)]]
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/NdArray/string.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fileprivate extension NdArray {
let depthSeperator: String = String(repeating: separator, count: ndim - 1)
let a = NdArray(self)
for i in 0..<shape[0] {
s += a[Slice(i)].string(separator: separator, indent: indent, depth: depth + 1, formatter: formatter)
s += a[[Slice(i)]].string(separator: separator, indent: indent, depth: depth + 1, formatter: formatter)
s += ","
s += depthSeperator
s += String(repeating: " ", count: indent * (depth + 1))
Expand Down
12 changes: 6 additions & 6 deletions Tests/NdArrayTests/EquitableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class EquitableTests: XCTestCase {
// 1d not contiguous
do {
let a = NdArray<Double>.zeros([3])
let b = NdArray<Double>(NdArray<Double>.zeros([6])[0... ~ 2])
let c = NdArray<Double>(NdArray<Double>.ones([6])[0... ~ 2])
let b = NdArray<Double>(NdArray<Double>.zeros([6])[[0... ~ 2]])
let c = NdArray<Double>(NdArray<Double>.ones([6])[[0... ~ 2]])
XCTAssertEqual(a, a)
XCTAssertEqual(a, b)
XCTAssertNotEqual(a, c)
Expand All @@ -52,8 +52,8 @@ class EquitableTests: XCTestCase {
// 2d not contiguous
do {
let a = NdArray<Double>.zeros([2, 3])
let b = NdArray<Double>(NdArray<Double>.zeros([2, 6])[0..., 0... ~ 2])
let c = NdArray<Double>(NdArray<Double>.ones([2, 6])[0..., 0... ~ 2])
let b = NdArray<Double>(NdArray<Double>.zeros([2, 6])[[0..., 0... ~ 2]])
let c = NdArray<Double>(NdArray<Double>.ones([2, 6])[[0..., 0... ~ 2]])
XCTAssertEqual(a, a)
XCTAssertEqual(a, b)
XCTAssertNotEqual(a, c)
Expand All @@ -64,8 +64,8 @@ class EquitableTests: XCTestCase {
let a = NdArray<Double>.zeros([2, 3])
let b = NdArray<Double>.zeros([2, 3])
XCTAssertEqual(a, b)
XCTAssertNotEqual(a, b[Slice(0)])
XCTAssertEqual(a[0...], b[0...])
XCTAssertNotEqual(a, b[[Slice(0)]])
XCTAssertEqual(a[[0...]], b[[0...]])
}
}
}
Loading