File size: 16,184 Bytes
6ee917b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
import Foundation
import SwiftNetCDF


struct Array2D {
    /// The underlying data storage for the 2D array.
    var data: [Float]
    
    /// The number of elements in x dimension
    let nx: Int
    
    /// The number of elements in y dimension
    let ny: Int
    
    /// The total number of elements in this array
    var count: Int {
        return nx * ny
    }
    
    init(data: [Float], nx: Int, ny: Int) {
        precondition(data.count == nx * ny)
        self.data = data
        self.nx = nx
        self.ny = ny
    }
    
    func writeNetcdf(filename: String) throws {
        let file = try NetCDF.create(path: filename, overwriteExisting: true)
        try file.setAttribute("TITLE", "My data set")
        let dimensions = [
            try file.createDimension(name: "LAT", length: ny),
            try file.createDimension(name: "LON", length: nx)
        ]
        var variable = try file.createVariable(name: "data", type: Float.self, dimensions: dimensions)
        try variable.write(data)
    }
    
    mutating func shift180LongitudeAndFlipLatitude() {
        data.shift180LongitudeAndFlipLatitude(nt: 1, ny: ny, nx: nx)
    }
    
    mutating func flipEverySecondScanLine() {
        // flip every second line
        for y in stride(from: 1, to: ny, by: 2) {
            for x in 0 ..< nx / 2 {
                let temp = data[y * nx + x]
                data[y * nx + x] = data[y * nx + nx - x - 1]
                data[y * nx + nx - x - 1] = temp
            }
        }
    }
    
    mutating func shift180Longitudee() {
        data.shift180Longitude(nt: 1, ny: ny, nx: nx)
    }
    
    mutating func flipLatitude() {
        data.flipLatitude(nt: 1, ny: ny, nx: nx)
    }
}

/**
 `Array2DFastSpace` is a struct that represents a 2D array of Float values with fast space indexing. It allows accessing and modifying individual elements using subscript notation.
 
 Data is stored to be accessed quickly for multiple locatios in a row, while accessing timesteps is slower
*/
struct Array2DFastSpace {
    /// The underlying data storage for the 2D array.
    var data: [Float]
    
    /// The number of spatial locations in the array.
    let nLocations: Int
    
    /// The number of time steps in the array.
    let nTime: Int
    
    /**
     Initializes a new instance of `Array2DFastSpace`.
     
     - Parameters:
        - data: The data to be used as the underlying storage of the 2D array. Its count should be equal to `nLocations * nTime`.
        - nLocations: The number of spatial locations in the array.
        - nTime: The number of time steps in the array.
     
     - Precondition: `data.count` should be equal to `nLocations * nTime`, otherwise the initializer will fatalError.
     */
    public init(data: [Float], nLocations: Int, nTime: Int) {
        if (data.count != nLocations * nTime) {
            fatalError("Wrong Array2DFastTime dimensions. nLocations=\(nLocations) nTime=\(nTime) count=\(data.count)")
        }
        self.data = data
        self.nLocations = nLocations
        self.nTime = nTime
    }
    
    /**
     Initializes a new instance of `Array2DFastSpace` with all elements set to `NaN`.
     
     - Parameters:
        - nLocations: The number of spatial locations in the array.
        - nTime: The number of time steps in the array.
     */
    public init(nLocations: Int, nTime: Int) {
        self.data = .init(repeating: .nan, count: nLocations * nTime)
        self.nLocations = nLocations
        self.nTime = nTime
    }

    /**
     Writes the 2D array data to a NetCDF file.
     
     - Parameters:
        - filename: The name of the file to write to.
        - nx: The number of grid points in the x (longitude) direction.
        - ny: The number of grid points in the y (latitude) direction.
     
     - Throws: An error of type `NetCDFError` if the write operation fails.
     */
    func writeNetcdf(filename: String, nx: Int, ny: Int) throws {
        let file = try NetCDF.create(path: filename, overwriteExisting: true)

        try file.setAttribute("TITLE", "My data set")

        let dimensions = [
            try file.createDimension(name: "TIME", length: nTime),
            try file.createDimension(name: "LAT", length: ny),
            try file.createDimension(name: "LON", length: nx)
        ]

        var variable = try file.createVariable(name: "MyData", type: Float.self, dimensions: dimensions)
        try variable.write(data)
    }
    
    /// Accesses the element at the specified time and location in the 2D array.
    ///
    /// - Parameters:
    ///   - time: The time index of the element to access.
    ///   - location: The location index of the element to access.
    ///
    /// - Precondition: `location` must be less than `nLocations`.
    /// - Precondition: `time` must be less than `nTime`.
    ///
    /// - Returns: The element at the specified time and location in the 2D array.
    @inlinable subscript(time: Int, location: Int) -> Float {
        get {
            precondition(location < nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time < nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            return data[time * nLocations + location]
        }
        set {
            precondition(location < nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time < nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            data[time * nLocations + location] = newValue
        }
    }
    
    /// Accesses a range of values in the array for a specific time.
    ///
    /// Use this subscript to access a range of values from the `Array2DFastSpace` array
    /// for a specific time. The range of locations is specified as a `Range` object.
    ///
    /// - Parameters:
    ///   - time: The time to access the range of values for.
    ///   - location: The range of locations to access.
    ///
    /// - Returns: An array slice that contains the values of the specified range
    ///   of locations for the specified time.
    ///
    /// - Precondition: `time` must be less than `nTime` and `location.upperBound` must
    ///   be less than or equal to `nLocations`.
    ///
    /// - SeeAlso: `subscript(time: Int, location: Int) -> Float`
    @inlinable subscript(time: Int, location: Range<Int>) -> ArraySlice<Float> {
        get {
            precondition(location.upperBound <= nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time < nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            return data[location.add(time * nLocations)]
        }
        set {
            precondition(location.upperBound <= nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time < nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            data[location.add(time * nLocations)] = newValue
        }
    }
    
    /// Transpose to fast time
    func transpose() -> Array2DFastTime {
        precondition(data.count == nLocations * nTime)
        return data.withUnsafeBufferPointer { data in
            let out = [Float](unsafeUninitializedCapacity: data.count) { buffer, initializedCount in
                for l in 0..<nLocations {
                    for t in 0..<nTime {
                        buffer[l * nTime + t] = data[t * nLocations + l]
                    }
                }
                initializedCount += data.count
            }
            return Array2DFastTime(data: out, nLocations: nLocations, nTime: nTime)
        }
    }
}


/**
 `Array2DFastTime` is a struct that represents a 2D array of Float values with fast time indexing. It allows accessing and modifying individual elements using subscript notation.
 
 Data is stored to be accessed quickly for multiple time temps in a row, while accessing locations is slower
*/
public struct Array2DFastTime {
    /// The underlying data storage for the 2D array.
    public var data: [Float]
    
    /// The number of spatial locations in the array.
    public let nLocations: Int
    
    /// The number of time steps in the array.
    public let nTime: Int
    
    /**
     Initializes a new instance of `Array2DFastTime`.
     
     - Parameters:
        - data: The data to be used as the underlying storage of the 2D array. Its count should be equal to `nLocations * nTime`.
        - nLocations: The number of spatial locations in the array.
        - nTime: The number of time steps in the array.
     
     - Precondition: `data.count` should be equal to `nLocations * nTime`, otherwise the initializer will fatalError.
     */
    public init(data: [Float], nLocations: Int, nTime: Int) {
        if (data.count != nLocations * nTime) {
            fatalError("Wrong Array2DFastTime dimensions. nLocations=\(nLocations) nTime=\(nTime) count=\(data.count)")
        }
        self.data = data
        self.nLocations = nLocations
        self.nTime = nTime
    }
    
    /**
     Initializes a new instance of `Array2DFastTime` with all elements set to `NaN`.
     
     - Parameters:
        - nLocations: The number of spatial locations in the array.
        - nTime: The number of time steps in the array.
     */
    public init(nLocations: Int, nTime: Int) {
        self.data = .init(repeating: .nan, count: nLocations * nTime)
        self.nLocations = nLocations
        self.nTime = nTime
    }
    
    /// Accesses the element at the specified time and location in the 2D array.
    ///
    /// - Parameters:
    ///   - location: The location index of the element to access.
    ///   - time: The time index of the element to access.
    ///
    /// - Precondition: `location` must be less than `nLocations`.
    /// - Precondition: `time` must be less than `nTime`.
    ///
    /// - Returns: The element at the specified time and location in the 2D array.
    @inlinable subscript(location: Int, time: Int) -> Float {
        get {
            precondition(location < nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time < nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            return data[location * nTime + time]
        }
        set {
            precondition(location < nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time < nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            data[location * nTime + time] = newValue
        }
    }
    
    /// Accesses a range of values in the array for a specific location.
    ///
    /// Use this subscript to access a range of values from the `Array2DFastTime` array
    /// for a specific location. The range of time steps is specified as a `Range` object.
    ///
    /// - Parameters:
    ///   - location: The location to access the range of values for.
    ///   - time: The range of time steps to access.
    ///
    /// - Returns: An array slice that contains the values of the specified range
    ///   of timesteps for the specified location.
    ///
    /// - Precondition: `location` must be less than `nLocations` and `time.upperBound` must
    ///   be less than or equal to `nTime`.
    ///
    /// - SeeAlso: `subscript(time: Int, location: Int) -> Float`
    @inlinable subscript(location: Int, time: Range<Int>) -> ArraySlice<Float> {
        get {
            precondition(location < nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time.upperBound <= nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            return data[time.add(location * nTime)]
        }
        set {
            precondition(location < nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time.upperBound <= nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            data[time.add(location * nTime)] = newValue
        }
    }
    
    /// Accesses a range of values in the array for a specific time. This function is relatively slow, because data needs to be transposed.
    ///
    /// Use this subscript to access a range of values from the `Array2DFastTime` array
    /// for a specific time. The range of locations is specified as a `Range` object.
    ///
    /// - Parameters:
    ///   - time: The time to access the range of values for.
    ///   - location: The range of locations to access.
    ///
    /// - Returns: An array slice that contains the values of the specified range
    ///   of locations for the specified time.
    ///
    /// - Precondition: `time` must be less than `nTime` and `location.upperBound` must
    ///   be less than or equal to `nLocations`.
    ///
    /// - SeeAlso: `subscript(time: Int, location: Int) -> Float`
    @inlinable subscript(location: Range<Int>, time: Int) -> [Float] {
        get {
            precondition(location.upperBound <= nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time < nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            var out = [Float]()
            out.reserveCapacity(location.count)
            for loc in location {
                out.append(self[loc, time])
            }
            return out
        }
        set {
            precondition(location.upperBound <= nLocations, "location subscript invalid: \(location) with nLocations=\(nLocations)")
            precondition(time < nTime, "time subscript invalid: \(nTime) with nTime=\(nTime)")
            precondition(newValue.count == location.count, "Array and location count do not match")
            for (loc, value) in zip(location, newValue) {
                data[loc * nTime + time] = value
            }
        }
    }
    
    /// Transpose to fast space
    func transpose() -> Array2DFastSpace {
        precondition(data.count == nLocations * nTime)
        return data.withUnsafeBufferPointer { data in
            let out = [Float](unsafeUninitializedCapacity: data.count) { buffer, initializedCount in
                for t in 0..<nTime {
                    for l in 0..<nLocations {
                        buffer[t * nLocations + l] = data[l * nTime + t]
                    }
                }
                initializedCount += data.count
            }
            return Array2DFastSpace(data: out, nLocations: nLocations, nTime: nTime)
        }
    }
}


/*extension Array {
    /// Calculate start positions for cycles
    static func transposeCalculateCycles(rows: Int, cols: Int) -> [Int] {
        var cycles = [Int]()
        let size = rows * cols - 1
        var b = [Bool](repeating: false, count: rows*cols)
        b[0] = true
        b[size] = true
        var i = 1
        while i < size {
            let cycleBegin = i
            cycles.append(i)
            repeat {
                b[i] = true
                i = (i*rows)%size
            } while i != cycleBegin
     
            i = 1
            while i < size && b[i] == true {
                i += 1
            }
        }
        return cycles
    }
    
    /// Perform inplace transposition using `following cycles algorithm`. This is 10 slower than double buffer transpose, exclusing the cycle calculation
    /// See: https://en.wikipedia.org/wiki/In-place_matrix_transposition
    /// See: https://www.geeksforgeeks.org/inplace-m-x-n-size-matrix-transpose/
    mutating func transpose(rows: Int, cols: Int, cycles: [Int]? = nil) {
        precondition(count == rows * cols)
        let cycles = cycles ?? Self.transposeCalculateCycles(rows: rows, cols: cols)
        self.withUnsafeMutableBufferPointer { ptr in
            let size = rows * cols - 1
            for cycleBegin in cycles {
                var i = cycleBegin
                var t = ptr[i]
                repeat {
                    i = (i*rows)%size
                    swap(&ptr[i], &t)
                } while i != cycleBegin
            }
        }
    }
}*/