mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
915 lines
25 KiB
Swift
915 lines
25 KiB
Swift
// RUN: %target-run-simple-swift
|
|
// REQUIRES: executable_test
|
|
|
|
// FIXME: Enable test for all platforms after debugging (https://github.com/apple/swift/issues/55186).
|
|
// iphonesimulator-i386-specific failures.
|
|
// REQUIRES: CPU=x86_64
|
|
|
|
// rdar://71642726 this test is crashing with optimizations.
|
|
// REQUIRES: swift_test_mode_optimize_none
|
|
|
|
import _Differentiation
|
|
import StdlibUnittest
|
|
|
|
var ControlFlowTests = TestSuite("ControlFlow")
|
|
|
|
ControlFlowTests.test("Conditionals") {
|
|
func cond1(_ x: Float) -> Float {
|
|
if x > 0 {
|
|
return x * x
|
|
}
|
|
return x + x
|
|
}
|
|
expectEqual(8, gradient(at: 4, of: cond1))
|
|
expectEqual(2, gradient(at: -10, of: cond1))
|
|
|
|
func cond2(_ x: Float) -> Float {
|
|
let y: Float
|
|
if x > 0 {
|
|
y = x * x
|
|
} else if x == -1337 {
|
|
y = 0
|
|
} else {
|
|
y = x + x
|
|
}
|
|
return y
|
|
}
|
|
expectEqual(8, gradient(at: 4, of: cond2))
|
|
expectEqual(2, gradient(at: -10, of: cond2))
|
|
expectEqual(0, gradient(at: -1337, of: cond2))
|
|
|
|
func cond2_var(_ x: Float) -> Float {
|
|
var y: Float = x
|
|
if x > 0 {
|
|
y = y * x
|
|
} else if x == -1337 {
|
|
y = x // Dummy assignment; shouldn't affect computation.
|
|
y = x // Dummy assignment; shouldn't affect computation.
|
|
y = 0
|
|
} else {
|
|
y = x + y
|
|
}
|
|
return y
|
|
}
|
|
expectEqual(8, gradient(at: 4, of: cond2_var))
|
|
expectEqual(2, gradient(at: -10, of: cond2_var))
|
|
expectEqual(0, gradient(at: -1337, of: cond2_var))
|
|
|
|
func cond3(_ x: Float, _ y: Float) -> Float {
|
|
if x > 0 {
|
|
return x * y
|
|
}
|
|
return y - x
|
|
}
|
|
expectEqual((5, 4), gradient(at: 4, 5, of: cond3))
|
|
expectEqual((-1, 1), gradient(at: -3, -2, of: cond3))
|
|
|
|
func cond4_var(_ x: Float) -> Float {
|
|
var outer = x
|
|
// TODO: cannot use literal `true` because it crashes
|
|
outerIf: if 1 == 1 {
|
|
var inner = outer
|
|
inner = inner * x
|
|
// TODO: cannot use literal `false` because it crashes
|
|
if 1 == 0 {
|
|
break outerIf
|
|
}
|
|
outer = inner
|
|
}
|
|
return outer
|
|
}
|
|
expectEqual((9, 6), valueWithGradient(at: 3, of: cond4_var))
|
|
|
|
func cond_tuple(_ x: Float) -> Float {
|
|
// Convoluted function returning `x + x`.
|
|
let y: (Float, Float) = (x, x)
|
|
if x > 0 {
|
|
return y.0 + y.1
|
|
}
|
|
return y.0 + y.0 - y.1 + y.0
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: cond_tuple))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: cond_tuple))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: cond_tuple))
|
|
|
|
func cond_tuple2(_ x: Float) -> Float {
|
|
// Convoluted function returning `x + x`.
|
|
let y: (Float, Float) = (x, x)
|
|
let y0 = y.0
|
|
if x > 0 {
|
|
let y1 = y.1
|
|
return y0 + y1
|
|
}
|
|
let y0_double = y0 + y.0
|
|
let y1 = y.1
|
|
return y0_double - y1 + y.0
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: cond_tuple2))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: cond_tuple2))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: cond_tuple2))
|
|
|
|
func cond_tuple_var(_ x: Float) -> Float {
|
|
// Convoluted function returning `x + x`.
|
|
var y: (Float, Float) = (x, x)
|
|
var z: (Float, Float) = (x + x, x - x)
|
|
if x > 0 {
|
|
var w = (x, x)
|
|
y.0 = w.1
|
|
y.1 = w.0
|
|
z.0 = z.0 - y.0
|
|
z.1 = z.1 + y.0
|
|
} else {
|
|
z = (x, x)
|
|
}
|
|
return y.0 + y.1 - z.0 + z.1
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: cond_tuple_var))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: cond_tuple_var))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: cond_tuple_var))
|
|
|
|
func cond_nestedtuple_var(_ x: Float) -> Float {
|
|
// Convoluted function returning `x + x`.
|
|
var y: (Float, Float) = (x + x, x - x)
|
|
var z: ((Float, Float), Float) = (y, x)
|
|
if x > 0 {
|
|
var w = (x, x)
|
|
y.0 = w.1
|
|
y.1 = w.0
|
|
z.0.0 = z.0.0 - y.0
|
|
z.0.1 = z.0.1 + y.0
|
|
} else {
|
|
z = ((y.0 - x, y.1 + x), x)
|
|
}
|
|
return y.0 + y.1 - z.0.0 + z.0.1
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: cond_nestedtuple_var))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: cond_nestedtuple_var))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: cond_nestedtuple_var))
|
|
|
|
struct FloatPair : Differentiable {
|
|
var first, second: Float
|
|
init(_ first: Float, _ second: Float) {
|
|
self.first = first
|
|
self.second = second
|
|
}
|
|
}
|
|
|
|
struct Pair<T : Differentiable, U : Differentiable> : Differentiable {
|
|
var first: T
|
|
var second: U
|
|
init(_ first: T, _ second: U) {
|
|
self.first = first
|
|
self.second = second
|
|
}
|
|
}
|
|
|
|
func cond_struct(_ x: Float) -> Float {
|
|
// Convoluted function returning `x + x`.
|
|
let y = FloatPair(x, x)
|
|
if x > 0 {
|
|
return y.first + y.second
|
|
}
|
|
return y.first + y.first - y.second + y.first
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: cond_struct))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: cond_struct))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: cond_struct))
|
|
|
|
func cond_struct2(_ x: Float) -> Float {
|
|
// Convoluted function returning `x + x`.
|
|
let y = FloatPair(x, x)
|
|
let y0 = y.first
|
|
if x > 0 {
|
|
let y1 = y.second
|
|
return y0 + y1
|
|
}
|
|
let y0_double = y0 + y.first
|
|
let y1 = y.second
|
|
return y0_double - y1 + y.first
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: cond_struct2))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: cond_struct2))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: cond_struct2))
|
|
|
|
func cond_struct_var(_ x: Float) -> Float {
|
|
// Convoluted function returning `x + x`.
|
|
var y = FloatPair(x, x)
|
|
var z = FloatPair(x + x, x - x)
|
|
if x > 0 {
|
|
var w = y
|
|
y.first = w.second
|
|
y.second = w.first
|
|
z.first = z.first - y.first
|
|
z.second = z.second + y.first
|
|
} else {
|
|
z = FloatPair(x, x)
|
|
}
|
|
return y.first + y.second - z.first + z.second
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: cond_struct_var))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: cond_struct_var))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: cond_struct_var))
|
|
|
|
func cond_nestedstruct_var(_ x: Float) -> Float {
|
|
// Convoluted function returning `x + x`.
|
|
var y = FloatPair(x + x, x - x)
|
|
var z = Pair(y, x)
|
|
if x > 0 {
|
|
var w = FloatPair(x, x)
|
|
y.first = w.second
|
|
y.second = w.first
|
|
z.first.first = z.first.first - y.first
|
|
z.first.second = z.first.second + y.first
|
|
} else {
|
|
z = Pair(FloatPair(y.first - x, y.second + x), x)
|
|
}
|
|
return y.first + y.second - z.first.first + z.first.second
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: cond_nestedstruct_var))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: cond_nestedstruct_var))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: cond_nestedstruct_var))
|
|
|
|
func guard1(_ x: Float, _ y: Float) -> Float {
|
|
guard x > 0 else {
|
|
return x * x
|
|
}
|
|
return y * y
|
|
}
|
|
expectEqual((0, 10), gradient(at: 4, 5, of: guard1))
|
|
expectEqual((-6, 0), gradient(at: -3, -2, of: guard1))
|
|
|
|
func guard2(_ x: Float, _ y: Float) -> Float {
|
|
guard x > 0 else {
|
|
if y > 0 {
|
|
return x * y
|
|
} else if x == -1337 {
|
|
return x * x
|
|
}
|
|
return 0
|
|
}
|
|
return y * y
|
|
}
|
|
expectEqual((0, 10), gradient(at: 4, 5, of: guard2))
|
|
expectEqual((5, -1337), gradient(at: -1337, 5, of: guard2))
|
|
expectEqual((-2674, 0), gradient(at: -1337, -5, of: guard2))
|
|
expectEqual((2, -3), gradient(at: -3, 2, of: guard2))
|
|
|
|
func guard2_var(_ x: Float, _ y: Float) -> Float {
|
|
var z = y
|
|
guard x > 0 else {
|
|
if y > 0 {
|
|
z = z * x
|
|
} else if x == -1337 {
|
|
z = x
|
|
z = z * z
|
|
} else {
|
|
z = 0
|
|
}
|
|
return z
|
|
}
|
|
return z * y
|
|
}
|
|
expectEqual((0, 10), gradient(at: 4, 5, of: guard2_var))
|
|
expectEqual((5, -1337), gradient(at: -1337, 5, of: guard2_var))
|
|
expectEqual((-2674, 0), gradient(at: -1337, -5, of: guard2_var))
|
|
expectEqual((2, -3), gradient(at: -3, 2, of: guard2_var))
|
|
|
|
func guard3(_ x: Float, _ y: Float) -> Float {
|
|
guard x > 0 else {
|
|
fatalError()
|
|
}
|
|
return y * y
|
|
}
|
|
expectEqual((0, 10), gradient(at: 4, 5, of: guard3))
|
|
expectCrash {
|
|
_ = gradient(at: -3, -2, of: guard3)
|
|
}
|
|
|
|
func cond_empty(_ x: Float) -> Float {
|
|
if x > 0 {
|
|
// Create empty trampoline blocks.
|
|
}
|
|
return x * x
|
|
}
|
|
expectEqual(4, gradient(at: 2, of: cond_empty))
|
|
expectEqual(-6, gradient(at: -3, of: cond_empty))
|
|
|
|
func cond_generic<T : Differentiable & FloatingPoint>(
|
|
_ x: T, _ y: T
|
|
) -> T {
|
|
if x > 0 {
|
|
return x
|
|
}
|
|
return y
|
|
}
|
|
expectEqual((1, 0), gradient(at: 4, 5, of: { x, y in cond_generic(x, y) }))
|
|
expectEqual((0, 1), gradient(at: -4, 5, of: { x, y in cond_generic(x, y) }))
|
|
}
|
|
|
|
ControlFlowTests.test("NestedConditionals") {
|
|
func nested1(_ x: Float) -> Float {
|
|
if x > 0 {
|
|
if x > 10 {
|
|
return x * x + x
|
|
} else {
|
|
return x * x
|
|
}
|
|
}
|
|
return x * -x
|
|
}
|
|
expectEqual(23, gradient(at: 11, of: nested1))
|
|
expectEqual(8, gradient(at: 4, of: nested1))
|
|
expectEqual(20, gradient(at: -10, of: nested1))
|
|
|
|
func nested2(_ x: Float, _ y: Float) -> Float {
|
|
if x > 0 {
|
|
if y > 10 {
|
|
return x * y
|
|
} else {
|
|
return x + y
|
|
}
|
|
}
|
|
return -y
|
|
}
|
|
expectEqual((20, 4), gradient(at: 4, 20, of: nested2))
|
|
expectEqual((1, 1), gradient(at: 4, 5, of: nested2))
|
|
expectEqual((0, -1), gradient(at: -3, -2, of: nested2))
|
|
|
|
func nested3(_ x: Float, _ y: Float) -> Float {
|
|
if x > 0 {
|
|
if y > 10 {
|
|
let z = x * y
|
|
if z > 100 {
|
|
return x + z
|
|
} else if y == 20 {
|
|
return z + z
|
|
}
|
|
} else {
|
|
return x + y
|
|
}
|
|
}
|
|
return -y
|
|
}
|
|
expectEqual((40, 8), gradient(at: 4, 20, of: nested3))
|
|
expectEqual((0, -1), gradient(at: 4, 21, of: nested3))
|
|
expectEqual((1, 1), gradient(at: 4, 5, of: nested3))
|
|
expectEqual((0, -1), gradient(at: -3, -2, of: nested3))
|
|
|
|
func nested3_var(_ x: Float, _ y: Float) -> Float {
|
|
var w = y
|
|
if x > 0 {
|
|
if y > 10 {
|
|
var z = x * w
|
|
if z > 100 {
|
|
z = x + z
|
|
return z
|
|
} else if y == 20 {
|
|
z = z + z
|
|
return z
|
|
}
|
|
} else {
|
|
w = x + w
|
|
return w
|
|
}
|
|
}
|
|
w = -w
|
|
return w
|
|
}
|
|
expectEqual((40, 8), gradient(at: 4, 20, of: nested3))
|
|
expectEqual((0, -1), gradient(at: 4, 21, of: nested3))
|
|
expectEqual((1, 1), gradient(at: 4, 5, of: nested3))
|
|
expectEqual((0, -1), gradient(at: -3, -2, of: nested3))
|
|
|
|
// TF-781: nested if derivative correctness.
|
|
do {
|
|
struct TF_781: Differentiable {
|
|
var w: Float = 3
|
|
|
|
@differentiable(reverse, wrt: self) // wrt only self is important
|
|
func callAsFunction(_ input: Float) -> Float {
|
|
var x = input
|
|
// TODO: cannot use literal `true` because it crashes
|
|
if 1 == 1 {
|
|
if 1 == 1 {
|
|
// Function application below should make `self` have non-zero
|
|
// derivative.
|
|
x = x * w
|
|
}
|
|
}
|
|
return x
|
|
}
|
|
}
|
|
let x: Float = 10
|
|
expectEqual(TF_781.TangentVector(w: x), gradient(at: TF_781()) { $0(x) })
|
|
}
|
|
|
|
// Non-method version of TF-781.
|
|
do {
|
|
@differentiable(reverse, wrt: x)
|
|
func TF_781(_ x: Float, _ y: Float) -> Float {
|
|
var result = y
|
|
// TODO: cannot use literal `true` because it crashes
|
|
if 1 == 1 {
|
|
if 1 == 1 {
|
|
result = result * x
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
let x: Float = 10
|
|
expectEqual(x, gradient(at: 3) { TF_781($0, x) })
|
|
}
|
|
}
|
|
|
|
ControlFlowTests.test("Recursion") {
|
|
func factorial(_ x: Float) -> Float {
|
|
if x == 1 {
|
|
return 1
|
|
}
|
|
return x * factorial(x - 1)
|
|
}
|
|
expectEqual(0, gradient(at: 1, of: factorial))
|
|
expectEqual(1, gradient(at: 2, of: factorial))
|
|
expectEqual(5, gradient(at: 3, of: factorial))
|
|
expectEqual(26, gradient(at: 4, of: factorial))
|
|
expectEqual(154, gradient(at: 5, of: factorial))
|
|
|
|
func factorial_var1(_ x: Float) -> Float {
|
|
var y: Float = x
|
|
if x == 1 {
|
|
y = 1
|
|
} else {
|
|
y = x
|
|
y = y * factorial_var1(y - 1)
|
|
}
|
|
return y
|
|
}
|
|
expectEqual(0, gradient(at: 1, of: factorial_var1))
|
|
expectEqual(1, gradient(at: 2, of: factorial_var1))
|
|
expectEqual(5, gradient(at: 3, of: factorial_var1))
|
|
expectEqual(26, gradient(at: 4, of: factorial_var1))
|
|
expectEqual(154, gradient(at: 5, of: factorial_var1))
|
|
|
|
func factorial_var2(_ x: Float) -> Float {
|
|
// Next line is the only difference with `factorial_var1`.
|
|
var y: Float = 1
|
|
if x == 1 {
|
|
y = 1
|
|
} else {
|
|
y = x
|
|
y = y * factorial_var2(y - 1)
|
|
}
|
|
return y
|
|
}
|
|
expectEqual(0, gradient(at: 1, of: factorial_var2))
|
|
expectEqual(1, gradient(at: 2, of: factorial_var2))
|
|
expectEqual(5, gradient(at: 3, of: factorial_var2))
|
|
expectEqual(26, gradient(at: 4, of: factorial_var2))
|
|
expectEqual(154, gradient(at: 5, of: factorial_var2))
|
|
|
|
func product(_ x: Float, count: Int) -> Float {
|
|
precondition(count > 0)
|
|
if count == 1 {
|
|
return x
|
|
}
|
|
return x * product(x, count: count - 1)
|
|
}
|
|
expectEqual(300, gradient(at: 10, of: { x in product(x, count: 3) }))
|
|
expectEqual(-20, gradient(at: -10, of: { x in product(x, count: 2) }))
|
|
expectEqual(1, gradient(at: 100, of: { x in product(x, count: 1) }))
|
|
}
|
|
|
|
ControlFlowTests.test("Enums") {
|
|
enum Enum {
|
|
case a(Float)
|
|
case b(Float, Float)
|
|
|
|
func enum_notactive1(_ x: Float) -> Float {
|
|
switch self {
|
|
case let .a(a): return x * a
|
|
case let .b(b1, b2): return x * b1 * b2
|
|
}
|
|
}
|
|
}
|
|
|
|
func enum_notactive1(_ e: Enum, _ x: Float) -> Float {
|
|
switch e {
|
|
case let .a(a): return x * a
|
|
case let .b(b1, b2): return x * b1 * b2
|
|
}
|
|
}
|
|
expectEqual(10, gradient(at: 2, of: { x in enum_notactive1(.a(10), x) }))
|
|
expectEqual(10, gradient(at: 2, of: { x in Enum.a(10).enum_notactive1(x) }))
|
|
expectEqual(20, gradient(at: 2, of: { x in enum_notactive1(.b(4, 5), x) }))
|
|
expectEqual(20, gradient(at: 2, of: { x in Enum.b(4, 5).enum_notactive1(x) }))
|
|
|
|
func enum_notactive2(_ e: Enum, _ x: Float) -> Float {
|
|
var y = x
|
|
if x > 0 {
|
|
var z = y + y
|
|
switch e {
|
|
case .a: z = z - y
|
|
case .b: y = y + x
|
|
}
|
|
var w = y
|
|
if case .a = e {
|
|
w = w + z
|
|
}
|
|
return w
|
|
} else if case .b = e {
|
|
return y + y
|
|
}
|
|
return x + y
|
|
}
|
|
expectEqual((8, 2), valueWithGradient(at: 4, of: { x in enum_notactive2(.a(10), x) }))
|
|
expectEqual((20, 2), valueWithGradient(at: 10, of: { x in enum_notactive2(.b(4, 5), x) }))
|
|
expectEqual((-20, 2), valueWithGradient(at: -10, of: { x in enum_notactive2(.a(10), x) }))
|
|
expectEqual((-2674, 2), valueWithGradient(at: -1337, of: { x in enum_notactive2(.b(4, 5), x) }))
|
|
|
|
func optional_notactive1(_ optional: Float?, _ x: Float) -> Float {
|
|
if let y = optional {
|
|
return x * y
|
|
}
|
|
return x + x
|
|
}
|
|
expectEqual(2, gradient(at: 2, of: { x in optional_notactive1(nil, x) }))
|
|
expectEqual(10, gradient(at: 2, of: { x in optional_notactive1(10, x) }))
|
|
|
|
struct Dense : Differentiable {
|
|
var w1: Float
|
|
@noDerivative var w2: Float?
|
|
|
|
@differentiable(reverse)
|
|
func callAsFunction(_ input: Float) -> Float {
|
|
if let w2 = w2 {
|
|
return input * w1 * w2
|
|
}
|
|
return input * w1
|
|
}
|
|
}
|
|
expectEqual((Dense.TangentVector(w1: 10), 20),
|
|
gradient(at: Dense(w1: 4, w2: 5), 2) { dense, x in dense(x) })
|
|
expectEqual((Dense.TangentVector(w1: 2), 4),
|
|
gradient(at: Dense(w1: 4, w2: nil), 2) { dense, x in dense(x) })
|
|
|
|
indirect enum Indirect {
|
|
case e(Float, Enum)
|
|
case indirect(Indirect)
|
|
}
|
|
|
|
func enum_indirect_notactive1(_ indirect: Indirect, _ x: Float) -> Float {
|
|
switch indirect {
|
|
case let .e(f, e):
|
|
switch e {
|
|
case .a: return x * f * enum_notactive1(e, x)
|
|
case .b: return x * f * enum_notactive1(e, x)
|
|
}
|
|
case let .indirect(ind): return enum_indirect_notactive1(ind, x)
|
|
}
|
|
}
|
|
do {
|
|
let ind: Indirect = .e(10, .a(3))
|
|
expectEqual(120, gradient(at: 2, of: { x in enum_indirect_notactive1(ind, x) }))
|
|
expectEqual(120, gradient(at: 2, of: { x in enum_indirect_notactive1(.indirect(ind), x) }))
|
|
}
|
|
}
|
|
|
|
ControlFlowTests.test("Loops") {
|
|
func for_loop(_ x: Float) -> Float {
|
|
var result = x
|
|
for _ in 0..<2 {
|
|
result = result * x
|
|
}
|
|
return result
|
|
}
|
|
expectEqual((8, 12), valueWithGradient(at: 2, of: for_loop))
|
|
expectEqual((27, 27), valueWithGradient(at: 3, of: for_loop))
|
|
|
|
func for_loop_nonactive_initial_value(_ x: Float) -> Float {
|
|
var result: Float = 1
|
|
for _ in 0..<2 {
|
|
result = result * x
|
|
}
|
|
return result
|
|
}
|
|
expectEqual((4, 4), valueWithGradient(at: 2, of: for_loop_nonactive_initial_value))
|
|
expectEqual((9, 6), valueWithGradient(at: 3, of: for_loop_nonactive_initial_value))
|
|
|
|
func while_loop(_ x: Float) -> Float {
|
|
var result = x
|
|
var i = 0
|
|
while i < 2 {
|
|
result = result * x
|
|
i += 1
|
|
}
|
|
return result
|
|
}
|
|
expectEqual((8, 12), valueWithGradient(at: 2, of: while_loop))
|
|
expectEqual((27, 27), valueWithGradient(at: 3, of: while_loop))
|
|
|
|
func while_loop_nonactive_initial_value(_ x: Float) -> Float {
|
|
var result: Float = 1
|
|
var i = 0
|
|
while i < 2 {
|
|
result = result * x
|
|
i += 1
|
|
}
|
|
return result
|
|
}
|
|
expectEqual((4, 4), valueWithGradient(at: 2, of: while_loop_nonactive_initial_value))
|
|
expectEqual((9, 6), valueWithGradient(at: 3, of: while_loop_nonactive_initial_value))
|
|
|
|
func repeat_while_loop(_ x: Float) -> Float {
|
|
var result = x
|
|
var i = 0
|
|
repeat {
|
|
result = result * x
|
|
i += 1
|
|
} while i < 2
|
|
return result
|
|
}
|
|
expectEqual((8, 12), valueWithGradient(at: 2, of: repeat_while_loop))
|
|
expectEqual((27, 27), valueWithGradient(at: 3, of: repeat_while_loop))
|
|
|
|
func repeat_while_loop_nonactive_initial_value(_ x: Float) -> Float {
|
|
var result: Float = 1
|
|
var i = 0
|
|
repeat {
|
|
result = result * x
|
|
i += 1
|
|
} while i < 2
|
|
return result
|
|
}
|
|
expectEqual((4, 4), valueWithGradient(at: 2, of: repeat_while_loop_nonactive_initial_value))
|
|
expectEqual((9, 6), valueWithGradient(at: 3, of: repeat_while_loop_nonactive_initial_value))
|
|
|
|
@differentiable(reverse)
|
|
func repeat_while_loop_nested_repeat(_ x: Float) -> Float {
|
|
var result = x
|
|
var i = 0
|
|
repeat {
|
|
var temp = x
|
|
var j = 0
|
|
repeat {
|
|
temp = temp * x
|
|
j += 1
|
|
} while j < 2
|
|
result = result * temp
|
|
i += 1
|
|
} while i < 2
|
|
return result
|
|
}
|
|
|
|
expectEqual((128, 448), valueWithGradient(at: 2, of: repeat_while_loop_nested_repeat))
|
|
expectEqual((2187, 5103), valueWithGradient(at: 3, of: repeat_while_loop_nested_repeat))
|
|
|
|
@differentiable(reverse)
|
|
func repeat_while_loop_nested_while(_ x: Float) -> Float {
|
|
var result = x
|
|
var i = 0
|
|
repeat {
|
|
var temp = x
|
|
var j = 0
|
|
while j < 2 {
|
|
temp = temp * x
|
|
j += 1
|
|
}
|
|
result = result * temp
|
|
i += 1
|
|
} while i < 2
|
|
return result
|
|
}
|
|
|
|
expectEqual((128, 448), valueWithGradient(at: 2, of: repeat_while_loop_nested_while))
|
|
expectEqual((2187, 5103), valueWithGradient(at: 3, of: repeat_while_loop_nested_while))
|
|
|
|
@differentiable(reverse)
|
|
func repeat_while_loop_nested_for(_ x: Float) -> Float {
|
|
var result = x
|
|
var i = 0
|
|
repeat {
|
|
var temp = x
|
|
for j in 0..<2 {
|
|
temp = temp * x
|
|
}
|
|
result = result * temp
|
|
i += 1
|
|
} while i < 2
|
|
return result
|
|
}
|
|
|
|
expectEqual((128, 448), valueWithGradient(at: 2, of: repeat_while_loop_nested_for))
|
|
expectEqual((2187, 5103), valueWithGradient(at: 3, of: repeat_while_loop_nested_for))
|
|
|
|
@differentiable(reverse)
|
|
func repeat_while_loop_condition(_ x: Float) -> Float {
|
|
var result = x
|
|
var i = 0
|
|
repeat {
|
|
if i == 2 {
|
|
break
|
|
}
|
|
if i == 0 {
|
|
result = result * x
|
|
} else {
|
|
result = result * result
|
|
}
|
|
i += 1
|
|
} while i < 10
|
|
return result
|
|
}
|
|
|
|
expectEqual((16, 32), valueWithGradient(at: 2, of: repeat_while_loop_condition))
|
|
expectEqual((81, 108), valueWithGradient(at: 3, of: repeat_while_loop_condition))
|
|
|
|
func loop_continue(_ x: Float) -> Float {
|
|
var result = x
|
|
for i in 1..<10 {
|
|
if i.isMultiple(of: 2) {
|
|
continue
|
|
}
|
|
result = result * x
|
|
}
|
|
return result
|
|
}
|
|
expectEqual((64, 192), valueWithGradient(at: 2, of: loop_continue))
|
|
expectEqual((729, 1458), valueWithGradient(at: 3, of: loop_continue))
|
|
|
|
func loop_break(_ x: Float) -> Float {
|
|
var result = x
|
|
for i in 1..<10 {
|
|
if i.isMultiple(of: 2) {
|
|
continue
|
|
}
|
|
result = result * x
|
|
}
|
|
return result
|
|
}
|
|
expectEqual((64, 192), valueWithGradient(at: 2, of: loop_break))
|
|
expectEqual((729, 1458), valueWithGradient(at: 3, of: loop_break))
|
|
|
|
func nested_loop1(_ x: Float) -> Float {
|
|
var outer = x
|
|
for _ in 0..<2 {
|
|
outer = outer * x
|
|
|
|
var inner = outer
|
|
var i = 0
|
|
while i < 2 {
|
|
inner = inner + x
|
|
i += 1
|
|
}
|
|
outer = inner
|
|
}
|
|
return outer
|
|
}
|
|
expectEqual((20, 22), valueWithGradient(at: 2, of: nested_loop1))
|
|
expectEqual((104, 66), valueWithGradient(at: 4, of: nested_loop1))
|
|
|
|
func nested_loop2(_ x: Float, count: Int) -> Float {
|
|
var outer = x
|
|
outerLoop: for _ in 0..<count {
|
|
outer = outer * x
|
|
|
|
var inner = outer
|
|
var i = 0
|
|
while i < count {
|
|
inner = inner + x
|
|
i += 1
|
|
|
|
switch Int(inner.truncatingRemainder(dividingBy: 7)) {
|
|
case 0: break outerLoop
|
|
case 1: break
|
|
default: continue
|
|
}
|
|
}
|
|
outer = inner
|
|
}
|
|
return outer
|
|
}
|
|
expectEqual((6, 5), valueWithGradient(at: 2, of: { x in nested_loop2(x, count: 1) }))
|
|
expectEqual((20, 22), valueWithGradient(at: 2, of: { x in nested_loop2(x, count: 2) }))
|
|
expectEqual((52, 80), valueWithGradient(at: 2, of: { x in nested_loop2(x, count: 3) }))
|
|
expectEqual((24, 28), valueWithGradient(at: 2, of: { x in nested_loop2(x, count: 4) }))
|
|
|
|
// https://github.com/apple/swift/issues/56342
|
|
// Loops in methods caused a runtime segfault.
|
|
struct S_56342 {
|
|
func loopInMethod(_ x: Float) -> Float {
|
|
var result = x
|
|
for _ in 0..<2 {
|
|
result *= result
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
expectEqual((0, 0), valueWithGradient(at: 0, of: { S_56342().loopInMethod($0) }))
|
|
expectEqual((1, 4), valueWithGradient(at: 1, of: { S_56342().loopInMethod($0) }))
|
|
}
|
|
|
|
ControlFlowTests.test("BranchingCastInstructions") {
|
|
// checked_cast_br
|
|
func typeCheckOperator<T>(_ x: Float, _ metatype: T.Type) -> Float {
|
|
if metatype is Int.Type {
|
|
return x + x
|
|
}
|
|
return x * x
|
|
}
|
|
expectEqual((6, 2), valueWithGradient(at: 3, of: { typeCheckOperator($0, Int.self) }))
|
|
expectEqual((9, 6), valueWithGradient(at: 3, of: { typeCheckOperator($0, Float.self) }))
|
|
|
|
// checked_cast_addr_br
|
|
func conditionalCast<T: Differentiable>(_ x: T) -> T {
|
|
if let _ = x as? Float {
|
|
// Do nothing with `y: Float?` value.
|
|
}
|
|
return x
|
|
}
|
|
expectEqual((3, 1), valueWithGradient(at: Float(3), of: conditionalCast))
|
|
}
|
|
|
|
ControlFlowTests.test("ThrowingCalls") {
|
|
// TF-433: Test non-active `try_apply` differentiation.
|
|
func throwing() throws -> Void {}
|
|
|
|
@differentiable(reverse)
|
|
func testThrowing(_ x: Float) -> Float {
|
|
try! throwing()
|
|
return x
|
|
}
|
|
expectEqual(10, pullback(at: 3, of: testThrowing)(10))
|
|
|
|
@differentiable(reverse)
|
|
func testThrowingGeneric<T: Differentiable>(_ x: T) -> T {
|
|
try! throwing()
|
|
return x
|
|
}
|
|
expectEqual(10, pullback(at: 3, of: testThrowingGeneric)(10))
|
|
|
|
func rethrowing(_ body: () throws -> Void) rethrows -> Void {}
|
|
|
|
@differentiable(reverse)
|
|
func testRethrowingIdentity(_ x: Float) -> Float {
|
|
rethrowing({}) // non-active `try_apply`
|
|
return x
|
|
}
|
|
expectEqual(10, pullback(at: 3, of: testRethrowingIdentity)(10))
|
|
|
|
@differentiable(reverse)
|
|
func testRethrowingIdentityGeneric<T: Differentiable>(_ x: T) -> T {
|
|
rethrowing({}) // non-active `try_apply`
|
|
return x
|
|
}
|
|
expectEqual(10, pullback(at: 3, of: testRethrowingIdentityGeneric)(10))
|
|
|
|
@differentiable(reverse)
|
|
func testComplexControlFlow(_ x: Float) -> Float {
|
|
rethrowing({})
|
|
for _ in 0..<Int(x) {
|
|
// TODO: cannot use literal `true` because it crashes
|
|
if 1 == 1 {
|
|
rethrowing({})
|
|
}
|
|
rethrowing({}) // non-active `try_apply`
|
|
}
|
|
rethrowing({})
|
|
return x
|
|
}
|
|
expectEqual(10, pullback(at: 3, of: testComplexControlFlow)(10))
|
|
|
|
@differentiable(reverse)
|
|
func testComplexControlFlowGeneric<T: Differentiable>(_ x: T) -> T {
|
|
rethrowing({})
|
|
for _ in 0..<10 {
|
|
// TODO: cannot use literal `true` because it crashes
|
|
if 1 == 1 {
|
|
rethrowing({})
|
|
}
|
|
rethrowing({}) // non-active `try_apply`
|
|
}
|
|
rethrowing({})
|
|
return x
|
|
}
|
|
expectEqual(10, pullback(at: 3, of: testComplexControlFlowGeneric)(10))
|
|
|
|
// Test `Array.map(_:)`, which is rethrowing.
|
|
func testArrayMap(_ x: [Float]) -> [Float] {
|
|
let max = x.map { $0 }.max()! // non-active `try_apply`
|
|
_blackHole(max)
|
|
return x
|
|
}
|
|
expectEqual([10, 10], pullback(at: [2, 3], of: testArrayMap)([10, 10]))
|
|
|
|
// Test `Bool.&&(_:)`, which is rethrowing.
|
|
func testBooleanShortCircuitingOperations(_ x: Float, bool: Bool) -> Float {
|
|
if bool && bool || bool { // non-active `try_apply`
|
|
return x * x
|
|
}
|
|
return x + x
|
|
}
|
|
expectEqual(6, gradient(at: 3, of: { x in testBooleanShortCircuitingOperations(x, bool: true) }))
|
|
expectEqual(2, gradient(at: 3, of: { x in testBooleanShortCircuitingOperations(x, bool: false) }))
|
|
}
|
|
|
|
runAllTests()
|