├── .gitignore ├── FourierSeries.playground ├── Contents.swift ├── Sources │ ├── Fourier.swift │ └── ScatterDiagram.swift └── contents.xcplayground ├── Images ├── 1.png ├── 2.png └── 3.png ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /FourierSeries.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | // 1. define any function and its domain by yourself: 4 | 5 | //let domain = 0.0 ... 1.0 6 | // 7 | //let f: Double -> Double = { x in 8 | // switch x { 9 | // case 0.0 ... 0.25: return -1 10 | // case 0.25 ... 0.5: return 1 11 | // case 0.5 ... 0.75: return -1 12 | // default: return 1 13 | // } 14 | //} 15 | 16 | // or use scatter diagram... 17 | 18 | var digram = ScatterDiagram() 19 | digram.dataPoints[0.0] = 0.5 20 | digram.dataPoints[0.25] = 1.0 21 | digram.dataPoints[0.5] = -0.5 22 | digram.dataPoints[0.75] = 2.0 23 | digram.dataPoints[1.0] = 0.5 24 | 25 | let domain = digram.domain 26 | let f = digram.lineTogether() 27 | 28 | // draw the graph 29 | 30 | for x in stride(from: domain.lowerBound, to: domain.upperBound, by: 0.01) { 31 | f(x) 32 | } 33 | 34 | // 2. do Fourier series expansion on that function 35 | 36 | var result = fourierExpand(f, domain: domain, seriesCount: 15) 37 | result.simplify(tolerance: 0.01) // allow 1% error 38 | 39 | result.expression 40 | 41 | for x in stride(from: domain.lowerBound, to: domain.upperBound, by: 0.01) { 42 | result.function(x) 43 | } 44 | -------------------------------------------------------------------------------- /FourierSeries.playground/Sources/Fourier.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public let 𝜋 = M_PI 4 | 5 | public struct FourierExpansion { 6 | 7 | public internal(set) var coefficients: FourierCoefficients 8 | 9 | public internal(set) var domain: ClosedRange 10 | 11 | public func function(_ x: Double) -> Double { 12 | return coefficients.apply((x - domain.lowerBound) / (domain.upperBound - domain.lowerBound) * 2 * 𝜋 - 𝜋) 13 | } 14 | 15 | public mutating func simplify(tolerance t: Double) { 16 | coefficients.simplify(tolerance: t) 17 | } 18 | 19 | public var expression: String { 20 | let a = coefficients.a 21 | let b = coefficients.b 22 | let half_a0 = a[0] / 2 23 | var str = half_a0 == 0 ? "f(t) = " : "f(t) = \(half_a0)" 24 | for i in 1 ..< a.count { 25 | if a[i] > 0 { 26 | str += " + \(a[i]) * cos\(i)t" 27 | } else if a[i] < 0 { 28 | str += " - \(-a[i]) * cos\(i)t" 29 | } 30 | 31 | if b[i] > 0 { 32 | str += " + \(b[i]) * sin\(i)t" 33 | } else if b[i] < 0 { 34 | str += " - \(-b[i]) * sin\(i)t" 35 | } 36 | } 37 | 38 | if domain.lowerBound == 0.0 { 39 | str += "\nwhere t = (\(2 / (domain.upperBound)) * x - 1) * 𝜋" 40 | } else { 41 | str += "\nwhere t = (\(2 / (domain.upperBound - domain.lowerBound)) * (x - \(domain.lowerBound)) - 1) * 𝜋" 42 | } 43 | 44 | return str 45 | } 46 | 47 | } 48 | 49 | public struct FourierCoefficients { 50 | 51 | public internal(set) var a: [Double] = [] 52 | public internal(set) var b: [Double] = [] 53 | 54 | public func apply(_ x: Double) -> Double { 55 | var sum = a[0] / 2 56 | for i in 1 ..< a.count { 57 | if a[i] != 0 { 58 | sum += a[i] * cos(x * Double(i)) 59 | } 60 | if b[i] != 0 { 61 | sum += b[i] * sin(x * Double(i)) 62 | } 63 | } 64 | return sum 65 | } 66 | 67 | public mutating func simplify(tolerance t: Double) { 68 | let maxCoef = max( 69 | a.map(abs).max()!, 70 | b.map(abs).max()! 71 | ) 72 | 73 | let digits = Int(ceil(log10(1 / maxCoef / t))) 74 | func rnd(_ x: Double) -> Double { 75 | guard digits > 0 else { return x } 76 | let m = pow(10, Double(digits)) 77 | return round(x * m) / m 78 | } 79 | 80 | for (i, v) in a.enumerated() { 81 | if abs(v) / maxCoef < t { 82 | a[i] = 0.0 83 | } else { 84 | a[i] = rnd(v) 85 | } 86 | } 87 | for (i, v) in b.enumerated() { 88 | if abs(v) / maxCoef < t { 89 | b[i] = 0.0 90 | } else { 91 | b[i] = rnd(v) 92 | } 93 | } 94 | } 95 | 96 | } 97 | 98 | private func an(_ f: (Double) -> Double, _ n: Int, _ step: Double) -> Double { 99 | var sum = 0.0 100 | for x in stride(from: (-𝜋), to: 𝜋, by: step) { 101 | sum += step * f(x) * cos(Double(n) * x) 102 | } 103 | return sum / 𝜋 104 | } 105 | 106 | private func bn(_ f: (Double) -> Double, _ n: Int, _ step: Double) -> Double { 107 | var sum = 0.0 108 | for x in stride(from: (-𝜋), to: 𝜋, by: step) { 109 | sum += step * f(x) * sin(Double(n) * x) 110 | } 111 | return sum / 𝜋 112 | } 113 | 114 | private func fourierExpand(_ f: (Double) -> Double, seriesCount count: Int, integralStep step: Double) -> FourierCoefficients { 115 | var a = [Double](), b = [Double]() 116 | a.append(an(f, 0, step)) 117 | a.append(contentsOf: (1...count).map { an(f, $0, step) }) 118 | b.append(0.0) 119 | b.append(contentsOf: (1...count).map { bn(f, $0, step) }) 120 | return FourierCoefficients(a: a, b: b) 121 | } 122 | 123 | public func fourierExpand 124 | (_ f: @escaping (Double) -> Double, domain: ClosedRange, seriesCount count: Int, integralStep step: Double? = nil) -> FourierExpansion { 125 | assert(count > 0, "series count must be positive") 126 | let range = domain.upperBound - domain.lowerBound 127 | let norm_f: (Double) -> Double = { 128 | f(($0 + 𝜋) / (2 * 𝜋) * range + domain.lowerBound) 129 | } 130 | let coeff = fourierExpand(norm_f, seriesCount: count, integralStep: step == nil ? range / 10 : step!) 131 | return FourierExpansion(coefficients: coeff, domain: domain.lowerBound ... domain.upperBound) 132 | } 133 | -------------------------------------------------------------------------------- /FourierSeries.playground/Sources/ScatterDiagram.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct ScatterDiagram { 4 | 5 | public var dataPoints: [Double: Double] = [:] 6 | 7 | public init() {} 8 | 9 | public var domain: ClosedRange { 10 | assert(dataPoints.count > 1, "the diagram must contains two points at least.") 11 | let min_x = dataPoints.keys.min()! 12 | let max_x = dataPoints.keys.max()! 13 | return min_x ... max_x 14 | } 15 | 16 | public func lineTogether() -> (Double) -> Double { 17 | assert(dataPoints.count > 1, "the diagram must contains two points at least.") 18 | let sorted = dataPoints.sorted { $0.0 < $1.0 } 19 | let domain = sorted.first!.0 ... sorted.last!.0 20 | return { x in 21 | assert(domain.contains(x), "x is out of domain") 22 | for (i, e) in sorted.enumerated() { 23 | let xi = e.key, yi = e.value 24 | if x == xi { 25 | return yi 26 | } else { 27 | let (xj, yj) = sorted[i + 1] 28 | if x < xj { 29 | let p = (x - xi) / (xj - xi) 30 | return yi + p * (yj - yi) 31 | } 32 | } 33 | } 34 | fatalError() 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /FourierSeries.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongzhang/swift-fourier-expansion/cf0baae8ec764b519483ae9930179439c7db5aaf/Images/1.png -------------------------------------------------------------------------------- /Images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongzhang/swift-fourier-expansion/cf0baae8ec764b519483ae9930179439c7db5aaf/Images/2.png -------------------------------------------------------------------------------- /Images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongzhang/swift-fourier-expansion/cf0baae8ec764b519483ae9930179439c7db5aaf/Images/3.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Gong Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fourier Series Expansion in Swift Playground 2 | 3 | [https://en.wikipedia.org/wiki/Fourier_series](https://en.wikipedia.org/wiki/Fourier_series) 4 | 5 | For example, there is an input function `f` that is defined on domain `0` to `1`: 6 | 7 | ```swift 8 | let domain = 0.0 ... 1.0 9 | 10 | let f: Double -> Double = { x in 11 | switch x { 12 | case 0.0 ... 0.25: return -1 13 | case 0.25 ... 0.5: return 1 14 | case 0.5 ... 0.75: return -1 15 | default: return 1 16 | } 17 | } 18 | ``` 19 | 20 | The graph of function `f` likes this: 21 | 22 | ![](https://github.com/gongzhang/swift-fourier-expansion/blob/master/Images/1.png) 23 | 24 | Then you can expand function `f` into Fourier series: 25 | 26 | ```swift 27 | var result = fourierExpand(f, domain: domain, seriesCount: 15) 28 | result.simplify(tolerance: 0.05) // allow 5% error 29 | ``` 30 | 31 | To see the result, print `result.expression`: 32 | 33 | ![](https://github.com/gongzhang/swift-fourier-expansion/blob/master/Images/2.png) 34 | 35 | And draw the graph of expanded `f` by calling `result.function(_:)`: 36 | 37 | ```swift 38 | for x in domain.start.stride(to: domain.end, by: 0.01) { 39 | result.function(x) 40 | } 41 | ``` 42 | 43 | ![](https://github.com/gongzhang/swift-fourier-expansion/blob/master/Images/3.png) 44 | --------------------------------------------------------------------------------