Skip to content
This repository was archived by the owner on Jul 1, 2023. It is now read-only.

Add Complex Numbers #129

Merged
merged 33 commits into from
Aug 2, 2019
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ee905cf
Initial implementation of Complex numbers.
bartchr808 May 21, 2019
9c2d11b
WIP: too strict conformances.
bartchr808 May 21, 2019
346b9b7
Force complex numbers to be differentiable always due to crash.
bartchr808 May 21, 2019
d5e78cf
Loosen constraints on complex number.
bartchr808 May 22, 2019
de575fe
Move T Tangent constraint.
bartchr808 May 22, 2019
683366d
Cleanup comments and commented out code.
bartchr808 May 22, 2019
01efbd9
Flip from inlinable to usableFromInline.
bartchr808 May 23, 2019
23870cb
Remove trailing whitespace, add comment to make init differentiable.
bartchr808 May 23, 2019
fdd1c50
Move around tests and file for complex.
bartchr808 May 23, 2019
3e1d56e
Add license and modify spacing.
bartchr808 May 23, 2019
1e5758a
public -> internal.
bartchr808 May 23, 2019
e58f400
Fix tests for running with XCTest.
bartchr808 May 23, 2019
9e62906
Merge branch 'master' into complex-numbers2
bartchr808 May 23, 2019
4ac97e3
Add target to package for third_party.
bartchr808 May 24, 2019
3350c21
WIP: see if tests pass while figuring out target issue.
bartchr808 May 24, 2019
85b43bb
WIP: see if tests pass while local toolchain is old.
bartchr808 May 24, 2019
46e68c9
Fix package.swift
bartchr808 May 24, 2019
dc4930c
Fix typo in conjugate test.
bartchr808 May 24, 2019
e4b7106
Fix colon spacing and Linux testing static var.
bartchr808 May 24, 2019
ae53238
Format and License fix.
bartchr808 May 24, 2019
09c53d0
Spacing and license update.
bartchr808 May 25, 2019
1856a2f
Fix line wrapping.
bartchr808 May 25, 2019
90bff29
Clean up documentation, slight code cleanup
bartchr808 May 28, 2019
a7d88bb
Pull more multiplication code out to helper func.
bartchr808 May 28, 2019
57bca41
Update licensing and mention Autograd.
bartchr808 May 30, 2019
40522c6
Merge branch 'master' into complex-numbers2
bartchr808 May 30, 2019
4e2053a
Update third party LICENSE file with tensorflow apache license.
bartchr808 May 30, 2019
80cab5c
WIP: add extra tests.
bartchr808 Jun 3, 2019
b5cf071
Merge branch 'complex-numbers2' of github.com:tensorflow/swift-apis i…
bartchr808 Jun 3, 2019
1b4c33a
Merge branch 'master' into complex-numbers2
bartchr808 Jun 3, 2019
33b178f
Add vjpInit test.
bartchr808 Jun 3, 2019
59989bd
Merge branch 'master' into complex-numbers2
bartchr808 Aug 2, 2019
4e01fed
Add another init test and fix test typos.
bartchr808 Aug 2, 2019
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
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,20 @@ let package = Package(
products: [
.library(
name: "DeepLearning",
targets: ["DeepLearning"]),
targets: ["DeepLearning"])
],
dependencies: [],
targets: [
.target(
name: "DeepLearning",
dependencies: []),
.target(
name: "Experimental",
dependencies: [],
path: "Sources/third_party/Experimental"),
.testTarget(
name: "ExperimentalTests",
dependencies: ["Experimental"]),
.testTarget(
name: "DeepLearningTests",
dependencies: ["DeepLearning"]),
Expand Down
2 changes: 1 addition & 1 deletion Sources/DeepLearning/Core/Dataset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ extension DatasetIterator : IteratorProtocol {
}
}

/// A 2-tuple-like struct that conforms to TensorGroup that represents a tuple
/// A 2-tuple-like struct that conforms to TensorGroup that represents a tuple
/// of 2 types conforming to TensorGroup.
@_fixed_layout
public struct Zip2TensorGroup<T : TensorGroup, U : TensorGroup> : TensorGroup {
Expand Down
339 changes: 339 additions & 0 deletions Sources/third_party/Experimental/Complex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
// Copyright 2017-2019 Xiaodi Wu and The TensorFlow Authors. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Note
// ====
//
// Code in libc++ is dual-licensed under the MIT and UIUC/NCSA licenses.
// Copyright © 2009-2017 contributors to the LLVM/libc++ project.
/// A type to represent a complex value in Cartesian form.
///
/// Create new instances of `Complex<T>` using integer or floating-point
/// literals and the imaginary unit `Complex<T>.i`. For example:
///
/// ```swift
/// let x: Complex<Double> = 2 + 4 * .i
/// ```
///
/// Additional Considerations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should add an additional Additional Consideration linking to https://github.com/HIPS/autograd/blob/master/docs/tutorial.md#complex-numbers and explaining that we are using that convention.

It's a very non-obvious choice about how to do things. The first time I thought about complex differentiation, I thought that we should only define derivatives for holomorphic functions, but that convention lets us define derivatives for all functions and that link explains why that's a good thing to do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed I added the following:

/// Our implementation of complex number differentiation follows the same
/// convention as Autograd. In short, we can get the derivative of a
/// holomorphic function, functions whose codomain are the Reals, and
/// functions whose codomain and domain are the Reals. You can read more about
/// Autograd at
///
///   https://github.com/HIPS/autograd/blob/master/docs/tutorial.md#complex-numbers

/// -------------------------
///
/// Floating-point types have special values that represent infinity or NaN
/// ("not a number"). Complex functions in different languages may return
/// different results when working with special values.

struct Complex<T: FloatingPoint> {
var real: T
var imaginary: T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried making real and imaginary differentiable? If you do that, then the compiler will in principle be able to automatically compute derivatives for all the complex operations because they just take real and imaginary parts, then perform differentiable operations on those, then construct a new complex number (which is also a differentiable operation)!

In practice, the compiler can't quite derive these yet because most of the complex operations are implemented in terms of mutation and control flow that the compiler can't handle. But soon it should be able to handle it!

It would be very interesting to add some tests that define a few operations in ways that autodiff can handle, and then see if autodiff computes the expected derivatives for those. e.g.

func simplifiedAdd(_ lhs: Complex, _ rhs: Complex) -> Complex {
  return Complex(lhs.real + rhs.real, lhs.imaginary + rhs.imaginary)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe derived conformances already made stored properties differentiable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried making real and imaginary differentiable?

I did in that when I mark a method as differentiable, I put the requirement that @differentiable(vjp: _vjpFunc where T: Differentiable). I didn't require real and imaginary to be differentiable where I define the Complex struct, because when we are to differentiate the standard library's version of complex numbers, we won't be able to change it, and additionally it will restrict Complex numbers to what they can be more than what the standard library will likely have.

It would be very interesting to add some tests that define a few operations in ways that autodiff can handle, and then see if autodiff computes the expected derivatives for those.

I'll give it a shot! 😄


@differentiable(vjp: _vjpInit where T: Differentiable, T.TangentVector == T)
init(real: T = 0, imaginary: T = 0) {
self.real = real
self.imaginary = imaginary
}
}

extension Complex: Differentiable where T: Differentiable {
typealias TangentVector = Complex
typealias AllDifferentiableVariables = Complex
}

extension Complex {
static var i: Complex {
return Complex(real: 0, imaginary: 1)
}

var isFinite: Bool {
return real.isFinite && imaginary.isFinite
}

var isInfinite: Bool {
return real.isInfinite || imaginary.isInfinite
}

var isNaN: Bool {
return (real.isNaN && !imaginary.isInfinite) || (imaginary.isNaN && !real.isInfinite)
}

var isZero: Bool {
return real.isZero && imaginary.isZero
}
}

extension Complex: ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) {
self.real = T(value)
self.imaginary = 0
}
}

extension Complex: CustomStringConvertible {
var description: String {
return real.isNaN && real.sign == .minus
? imaginary.sign == .minus
? "-\(-real) - \(-imaginary)i"
: "-\(-real) + \(imaginary)i"
: imaginary.sign == .minus
? "\(real) - \(-imaginary)i"
: "\(real) + \(imaginary)i"
}
}

extension Complex: Equatable {
static func == (lhs: Complex, rhs: Complex) -> Bool {
return lhs.real == rhs.real && lhs.imaginary == rhs.imaginary
}
}

extension Complex: AdditiveArithmetic {
@differentiable(vjp: _vjpAdd(lhs:rhs:) where T: Differentiable)
static func + (lhs: Complex, rhs: Complex) -> Complex {
var temp = lhs
temp += rhs
return temp
}

static func += (lhs: inout Complex, rhs: Complex) {
lhs.real += rhs.real
lhs.imaginary += rhs.imaginary
}

@differentiable(vjp: _vjpSubtract(lhs:rhs:) where T: Differentiable)
static func - (lhs: Complex, rhs: Complex) -> Complex {
var temp = lhs
temp -= rhs
return temp
}

static func -= (lhs: inout Complex, rhs: Complex) {
lhs.real -= rhs.real
lhs.imaginary -= rhs.imaginary
}
}

extension Complex: Numeric {
init?<U>(exactly source: U) where U: BinaryInteger {
guard let t = T(exactly: source) else { return nil }
self.real = t
self.imaginary = 0
}

static private func handleMultiplyNaN(infiniteA: T, infiniteB: T, nanA: T, nanB: T) -> Complex {
var a = infiniteA
var b = infiniteB
var c = nanA
var d = nanB

a = T(signOf: infiniteA, magnitudeOf: infiniteA.isInfinite ? 1 : 0)
b = T(signOf: infiniteB, magnitudeOf: infiniteB.isInfinite ? 1 : 0)

if nanA.isNaN { c = T(signOf: nanA, magnitudeOf: 0) }
if nanB.isNaN { d = T(signOf: nanB, magnitudeOf: 0) }

return Complex(
real: .infinity * (a * c - b * d),
imaginary: .infinity * (a * d + b * c)
)
}

@differentiable(vjp: _vjpMultiply(lhs:rhs:) where T: Differentiable)
static func * (lhs: Complex, rhs: Complex) -> Complex {
var a = lhs.real, b = lhs.imaginary, c = rhs.real, d = rhs.imaginary
let ac = a * c, bd = b * d, ad = a * d, bc = b * c
let x = ac - bd
let y = ad + bc

if x.isNaN && y.isNaN {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this logic would read a lot nicer if the body of this 'if' were pulled out of line into its own function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True it could explain the logic behind this part of the function better. I'll give this a shot Sunday night!

if a.isInfinite || b.isInfinite {
return handleMultiplyNaN(infiniteA: a, infiniteB: b, nanA: c, nanB: d)
} else if c.isInfinite || d.isInfinite {
return handleMultiplyNaN(infiniteA: c, infiniteB: d, nanA: a, nanB: b)
} else if ac.isInfinite || bd.isInfinite || ad.isInfinite || bc.isInfinite {
if a.isNaN { a = T(signOf: a, magnitudeOf: 0) }
if b.isNaN { b = T(signOf: b, magnitudeOf: 0) }
if c.isNaN { c = T(signOf: c, magnitudeOf: 0) }
if d.isNaN { d = T(signOf: d, magnitudeOf: 0) }
return Complex(
real: .infinity * (a * c - b * d),
imaginary: .infinity * (a * d + b * c)
)
}
}
return Complex(real: x, imaginary: y)
}

static func *= (lhs: inout Complex, rhs: Complex) {
lhs = lhs * rhs
}

var magnitude: T {
var x = abs(real)
var y = abs(imaginary)
if x.isInfinite { return x }
if y.isInfinite { return y }
if x == 0 { return y }
if x < y { swap(&x, &y) }
let ratio = y / x
return x * (1 + ratio * ratio).squareRoot()
}
}

extension Complex: SignedNumeric {
@differentiable(vjp: _vjpNegate where T: Differentiable)
static prefix func - (operand: Complex) -> Complex {
return Complex(real: -operand.real, imaginary: -operand.imaginary)
}

mutating func negate() {
real.negate()
imaginary.negate()
}
}

extension Complex {
@differentiable(vjp: _vjpDivide(lhs:rhs:) where T: Differentiable)
static func / (lhs: Complex, rhs: Complex) -> Complex {
var a = lhs.real, b = lhs.imaginary, c = rhs.real, d = rhs.imaginary
var x: T
var y: T
if c.magnitude >= d.magnitude {
let ratio = d / c
let denominator = c + d * ratio
x = (a + b * ratio) / denominator
y = (b - a * ratio) / denominator
} else {
let ratio = c / d
let denominator = c * ratio + d
x = (a * ratio + b) / denominator
y = (b * ratio - a) / denominator
}
if x.isNaN && y.isNaN {
if c == 0 && d == 0 && (!a.isNaN || !b.isNaN) {
x = T(signOf: c, magnitudeOf: .infinity) * a
y = T(signOf: c, magnitudeOf: .infinity) * b
} else if (a.isInfinite || b.isInfinite) && c.isFinite && d.isFinite {
a = T(signOf: a, magnitudeOf: a.isInfinite ? 1 : 0)
b = T(signOf: b, magnitudeOf: b.isInfinite ? 1 : 0)
x = .infinity * (a * c + b * d)
y = .infinity * (b * c - a * d)
} else if (c.isInfinite || d.isInfinite) && a.isFinite && b.isFinite {
c = T(signOf: c, magnitudeOf: c.isInfinite ? 1 : 0)
d = T(signOf: d, magnitudeOf: d.isInfinite ? 1 : 0)
x = 0 * (a * c + b * d)
y = 0 * (b * c - a * d)
}
}
return Complex(real: x, imaginary: y)
}

static func /= (lhs: inout Complex, rhs: Complex) {
lhs = lhs / rhs
}
}

extension Complex {
@differentiable(vjp: _vjpComplexConjugate where T: Differentiable)
func complexConjugate() -> Complex {
return Complex(real: real, imaginary: -imaginary)
}
}

func abs<T>(_ z: Complex<T>) -> Complex<T> {
return Complex(real: z.magnitude)
}

extension Complex {
@differentiable(vjp: _vjpAdding(real:) where T: Differentiable, T.TangentVector == T)
func adding(real: T) -> Complex {
var c = self
c.real += real
return c
}

@differentiable(vjp: _vjpSubtracting(real:) where T: Differentiable, T.TangentVector == T)
func subtracting(real: T) -> Complex {
var c = self
c.real -= real
return c
}

@differentiable(vjp: _vjpAdding(imaginary:) where T: Differentiable, T.TangentVector == T)
func adding(imaginary: T) -> Complex {
var c = self
c.imaginary += imaginary
return c
}

@differentiable(vjp: _vjpSubtracting(imaginary:) where T: Differentiable, T.TangentVector == T)
func subtracting(imaginary: T) -> Complex {
var c = self
c.imaginary -= imaginary
return c
}
}

extension Complex where T: Differentiable, T.TangentVector == T {
static func _vjpInit(real: T, imaginary: T) -> (Complex, (Complex) -> (T, T)) {
return (Complex(real: real, imaginary: imaginary), { ($0.real, $0.imaginary) })
}
}

extension Complex where T: Differentiable {
static func _vjpAdd(lhs: Complex, rhs: Complex)
-> (Complex, (Complex) -> (Complex, Complex)) {
return (lhs + rhs, { v in (v, v) })
}

static func _vjpSubtract(lhs: Complex, rhs: Complex)
-> (Complex, (Complex) -> (Complex, Complex)) {
return (lhs - rhs, { v in (v, -v) })
}

static func _vjpMultiply(lhs: Complex, rhs: Complex)
-> (Complex, (Complex) -> (Complex, Complex)) {
return (lhs * rhs, { v in (rhs * v, lhs * v) })
}

static func _vjpDivide(lhs: Complex, rhs: Complex)
-> (Complex, (Complex) -> (Complex, Complex)) {
return (lhs / rhs, { v in (v / rhs, -lhs / (rhs * rhs) * v) })
}

static func _vjpNegate(operand: Complex)
-> (Complex, (Complex) -> Complex) {
return (-operand, { -$0 })
}

func _vjpComplexConjugate() -> (Complex, (Complex) -> Complex) {
return (complexConjugate(), { v in v.complexConjugate() })
}
}

extension Complex where T: Differentiable, T.TangentVector == T {
func _vjpAdding(real: T) -> (Complex, (Complex) -> (Complex, T)) {
return (self.adding(real: real), { ($0, $0.real) })
}

func _vjpSubtracting(real: T) -> (Complex, (Complex) -> (Complex, T)) {
return (self.subtracting(real: real), { ($0, -$0.real) })
}

func _vjpAdding(imaginary: T) -> (Complex, (Complex) -> (Complex, T)) {
return (self.adding(real: real), { ($0, $0.imaginary) })
}

func _vjpSubtracting(imaginary: T) -> (Complex, (Complex) -> (Complex, T)) {
return (self.subtracting(real: real), { ($0, -$0.imaginary) })
}
}
19 changes: 19 additions & 0 deletions Sources/third_party/Experimental/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright © 2017 Xiaodi Wu (https://github.com/xwu).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please contact Xiaodi about this to see if he is willing to contribute it to the s4tf project. We don't want to mix licenses in the swift-apis repos.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya that sounds good I sent him an email about this! 😄


Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading