gonum.org/v1/gonum@v0.14.0/num/dual/dual_test.go (about) 1 // Copyright ©2018 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package dual 6 7 import ( 8 "fmt" 9 "math" 10 "testing" 11 12 "gonum.org/v1/gonum/floats/scalar" 13 ) 14 15 var formatTests = []struct { 16 d Number 17 format string 18 want string 19 }{ 20 {d: Number{1.1, 2.1}, format: "%#v", want: "dual.Number{Real:1.1, Emag:2.1}"}, // Bootstrap test. 21 {d: Number{-1.1, -2.1}, format: "%#v", want: "dual.Number{Real:-1.1, Emag:-2.1}"}, // Bootstrap test. 22 {d: Number{1.1, 2.1}, format: "%+v", want: "{Real:1.1, Emag:2.1}"}, 23 {d: Number{-1.1, -2.1}, format: "%+v", want: "{Real:-1.1, Emag:-2.1}"}, 24 {d: Number{1, 2}, format: "%v", want: "(1+2ϵ)"}, 25 {d: Number{-1, -2}, format: "%v", want: "(-1-2ϵ)"}, 26 {d: Number{1, 2}, format: "%g", want: "(1+2ϵ)"}, 27 {d: Number{-1, -2}, format: "%g", want: "(-1-2ϵ)"}, 28 {d: Number{1, 2}, format: "%e", want: "(1.000000e+00+2.000000e+00ϵ)"}, 29 {d: Number{-1, -2}, format: "%e", want: "(-1.000000e+00-2.000000e+00ϵ)"}, 30 {d: Number{1, 2}, format: "%E", want: "(1.000000E+00+2.000000E+00ϵ)"}, 31 {d: Number{-1, -2}, format: "%E", want: "(-1.000000E+00-2.000000E+00ϵ)"}, 32 {d: Number{1, 2}, format: "%f", want: "(1.000000+2.000000ϵ)"}, 33 {d: Number{-1, -2}, format: "%f", want: "(-1.000000-2.000000ϵ)"}, 34 } 35 36 func TestFormat(t *testing.T) { 37 t.Parallel() 38 for _, test := range formatTests { 39 got := fmt.Sprintf(test.format, test.d) 40 if got != test.want { 41 t.Errorf("unexpected result for fmt.Sprintf(%q, %#v): got:%q, want:%q", test.format, test.d, got, test.want) 42 } 43 } 44 } 45 46 // First derivatives: 47 48 func dSin(x float64) float64 { return math.Cos(x) } 49 func dCos(x float64) float64 { return -math.Sin(x) } 50 func dTan(x float64) float64 { return sec(x) * sec(x) } 51 func dAsin(x float64) float64 { return 1 / math.Sqrt(1-x*x) } 52 func dAcos(x float64) float64 { return -1 / math.Sqrt(1-x*x) } 53 func dAtan(x float64) float64 { return 1 / (1 + x*x) } 54 55 func dSinh(x float64) float64 { return math.Cosh(x) } 56 func dCosh(x float64) float64 { return math.Sinh(x) } 57 func dTanh(x float64) float64 { return sech(x) * sech(x) } 58 func dAsinh(x float64) float64 { return 1 / math.Sqrt(x*x+1) } 59 func dAcosh(x float64) float64 { return 1 / (math.Sqrt(x-1) * math.Sqrt(x+1)) } 60 func dAtanh(x float64) float64 { 61 switch { 62 case math.Abs(x) == 1: 63 return math.NaN() 64 case math.IsInf(x, 0): 65 return negZero 66 } 67 return 1 / (1 - x*x) 68 } 69 70 func dExp(x float64) float64 { return math.Exp(x) } 71 func dLog(x float64) float64 { 72 if x < 0 { 73 return math.NaN() 74 } 75 return 1 / x 76 } 77 func dSqrt(x float64) float64 { 78 // For whatever reason, math.Sqrt(-0) returns -0. 79 // In this case, that is clearly a wrong approach. 80 if x == 0 { 81 return math.Inf(1) 82 } 83 return 0.5 / math.Sqrt(x) 84 } 85 func dInv(x float64) float64 { return -1 / (x * x) } 86 87 // Helpers: 88 89 func sec(x float64) float64 { return 1 / math.Cos(x) } 90 func sech(x float64) float64 { return 1 / math.Cosh(x) } 91 92 var negZero = math.Float64frombits(1 << 63) 93 94 var dualTests = []struct { 95 name string 96 x []float64 97 fnDual func(x Number) Number 98 fn func(x float64) float64 99 dFn func(x float64) float64 100 }{ 101 { 102 name: "sin", 103 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 104 fnDual: Sin, 105 fn: math.Sin, 106 dFn: dSin, 107 }, 108 { 109 name: "cos", 110 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 111 fnDual: Cos, 112 fn: math.Cos, 113 dFn: dCos, 114 }, 115 { 116 name: "tan", 117 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 118 fnDual: Tan, 119 fn: math.Tan, 120 dFn: dTan, 121 }, 122 { 123 name: "sinh", 124 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 125 fnDual: Sinh, 126 fn: math.Sinh, 127 dFn: dSinh, 128 }, 129 { 130 name: "cosh", 131 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 132 fnDual: Cosh, 133 fn: math.Cosh, 134 dFn: dCosh, 135 }, 136 { 137 name: "tanh", 138 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 139 fnDual: Tanh, 140 fn: math.Tanh, 141 dFn: dTanh, 142 }, 143 144 { 145 name: "asin", 146 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 147 fnDual: Asin, 148 fn: math.Asin, 149 dFn: dAsin, 150 }, 151 { 152 name: "acos", 153 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 154 fnDual: Acos, 155 fn: math.Acos, 156 dFn: dAcos, 157 }, 158 { 159 name: "atan", 160 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 161 fnDual: Atan, 162 fn: math.Atan, 163 dFn: dAtan, 164 }, 165 { 166 name: "asinh", 167 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 168 fnDual: Asinh, 169 fn: math.Asinh, 170 dFn: dAsinh, 171 }, 172 { 173 name: "acosh", 174 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 175 fnDual: Acosh, 176 fn: math.Acosh, 177 dFn: dAcosh, 178 }, 179 { 180 name: "atanh", 181 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 182 fnDual: Atanh, 183 fn: math.Atanh, 184 dFn: dAtanh, 185 }, 186 187 { 188 name: "exp", 189 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 190 fnDual: Exp, 191 fn: math.Exp, 192 dFn: dExp, 193 }, 194 { 195 name: "log", 196 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 197 fnDual: Log, 198 fn: math.Log, 199 dFn: dLog, 200 }, 201 { 202 name: "inv", 203 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 204 fnDual: Inv, 205 fn: func(x float64) float64 { return 1 / x }, 206 dFn: dInv, 207 }, 208 { 209 name: "sqrt", 210 x: []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)}, 211 fnDual: Sqrt, 212 fn: math.Sqrt, 213 dFn: dSqrt, 214 }, 215 216 { 217 name: "Fike example fn", 218 x: []float64{1, 2, 3, 4, 5}, 219 fnDual: func(x Number) Number { 220 return Mul( 221 Exp(x), 222 Inv(Sqrt( 223 Add( 224 PowReal(Sin(x), 3), 225 PowReal(Cos(x), 3))))) 226 }, 227 fn: func(x float64) float64 { 228 return math.Exp(x) / math.Sqrt(math.Pow(math.Sin(x), 3)+math.Pow(math.Cos(x), 3)) 229 }, 230 dFn: func(x float64) float64 { 231 return math.Exp(x) * (3*math.Cos(x) + 5*math.Cos(3*x) + 9*math.Sin(x) + math.Sin(3*x)) / 232 (8 * math.Pow(math.Pow(math.Sin(x), 3)+math.Pow(math.Cos(x), 3), 1.5)) 233 }, 234 }, 235 } 236 237 func TestDual(t *testing.T) { 238 t.Parallel() 239 const tol = 1e-15 240 for _, test := range dualTests { 241 for _, x := range test.x { 242 fxDual := test.fnDual(Number{Real: x, Emag: 1}) 243 fx := test.fn(x) 244 dFx := test.dFn(x) 245 if !same(fxDual.Real, fx, tol) { 246 t.Errorf("unexpected %s(%v): got:%v want:%v", test.name, x, fxDual.Real, fx) 247 } 248 if !same(fxDual.Emag, dFx, tol) { 249 t.Errorf("unexpected %s'(%v): got:%v want:%v", test.name, x, fxDual.Emag, dFx) 250 } 251 } 252 } 253 } 254 255 var powRealTests = []struct { 256 d Number 257 p float64 258 want Number 259 }{ 260 // PowReal(NaN+xϵ, ±0) = 1+NaNϵ for any x 261 {d: Number{Real: math.NaN(), Emag: 0}, p: 0, want: Number{Real: 1, Emag: math.NaN()}}, 262 {d: Number{Real: math.NaN(), Emag: 0}, p: negZero, want: Number{Real: 1, Emag: math.NaN()}}, 263 {d: Number{Real: math.NaN(), Emag: 1}, p: 0, want: Number{Real: 1, Emag: math.NaN()}}, 264 {d: Number{Real: math.NaN(), Emag: 2}, p: negZero, want: Number{Real: 1, Emag: math.NaN()}}, 265 {d: Number{Real: math.NaN(), Emag: 3}, p: 0, want: Number{Real: 1, Emag: math.NaN()}}, 266 {d: Number{Real: math.NaN(), Emag: 1}, p: negZero, want: Number{Real: 1, Emag: math.NaN()}}, 267 {d: Number{Real: math.NaN(), Emag: 2}, p: 0, want: Number{Real: 1, Emag: math.NaN()}}, 268 {d: Number{Real: math.NaN(), Emag: 3}, p: negZero, want: Number{Real: 1, Emag: math.NaN()}}, 269 270 // PowReal(x, ±0) = 1 for any x 271 {d: Number{Real: 0, Emag: 0}, p: 0, want: Number{Real: 1, Emag: 0}}, 272 {d: Number{Real: negZero, Emag: 0}, p: negZero, want: Number{Real: 1, Emag: 0}}, 273 {d: Number{Real: math.Inf(1), Emag: 0}, p: 0, want: Number{Real: 1, Emag: 0}}, 274 {d: Number{Real: math.Inf(-1), Emag: 0}, p: negZero, want: Number{Real: 1, Emag: 0}}, 275 {d: Number{Real: 0, Emag: 1}, p: 0, want: Number{Real: 1, Emag: 0}}, 276 {d: Number{Real: negZero, Emag: 1}, p: negZero, want: Number{Real: 1, Emag: 0}}, 277 {d: Number{Real: math.Inf(1), Emag: 1}, p: 0, want: Number{Real: 1, Emag: 0}}, 278 {d: Number{Real: math.Inf(-1), Emag: 1}, p: negZero, want: Number{Real: 1, Emag: 0}}, 279 280 // PowReal(1+xϵ, y) = (1+xyϵ) for any y 281 {d: Number{Real: 1, Emag: 0}, p: 0, want: Number{Real: 1, Emag: 0}}, 282 {d: Number{Real: 1, Emag: 0}, p: 1, want: Number{Real: 1, Emag: 0}}, 283 {d: Number{Real: 1, Emag: 0}, p: 2, want: Number{Real: 1, Emag: 0}}, 284 {d: Number{Real: 1, Emag: 0}, p: 3, want: Number{Real: 1, Emag: 0}}, 285 {d: Number{Real: 1, Emag: 1}, p: 0, want: Number{Real: 1, Emag: 0}}, 286 {d: Number{Real: 1, Emag: 1}, p: 1, want: Number{Real: 1, Emag: 1}}, 287 {d: Number{Real: 1, Emag: 1}, p: 2, want: Number{Real: 1, Emag: 2}}, 288 {d: Number{Real: 1, Emag: 1}, p: 3, want: Number{Real: 1, Emag: 3}}, 289 {d: Number{Real: 1, Emag: 2}, p: 0, want: Number{Real: 1, Emag: 0}}, 290 {d: Number{Real: 1, Emag: 2}, p: 1, want: Number{Real: 1, Emag: 2}}, 291 {d: Number{Real: 1, Emag: 2}, p: 2, want: Number{Real: 1, Emag: 4}}, 292 {d: Number{Real: 1, Emag: 2}, p: 3, want: Number{Real: 1, Emag: 6}}, 293 294 // PowReal(x, 1) = x for any x 295 {d: Number{Real: 0, Emag: 0}, p: 1, want: Number{Real: 0, Emag: 0}}, 296 {d: Number{Real: negZero, Emag: 0}, p: 1, want: Number{Real: negZero, Emag: 0}}, 297 {d: Number{Real: 0, Emag: 1}, p: 1, want: Number{Real: 0, Emag: 1}}, 298 {d: Number{Real: negZero, Emag: 1}, p: 1, want: Number{Real: negZero, Emag: 1}}, 299 {d: Number{Real: math.NaN(), Emag: 0}, p: 1, want: Number{Real: math.NaN(), Emag: 0}}, 300 {d: Number{Real: math.NaN(), Emag: 1}, p: 1, want: Number{Real: math.NaN(), Emag: 1}}, 301 {d: Number{Real: math.NaN(), Emag: 2}, p: 1, want: Number{Real: math.NaN(), Emag: 2}}, 302 303 // PowReal(NaN+xϵ, y) = NaN+NaNϵ 304 {d: Number{Real: math.NaN(), Emag: 0}, p: 2, want: Number{Real: math.NaN(), Emag: math.NaN()}}, 305 {d: Number{Real: math.NaN(), Emag: 0}, p: 3, want: Number{Real: math.NaN(), Emag: math.NaN()}}, 306 {d: Number{Real: math.NaN(), Emag: 1}, p: 2, want: Number{Real: math.NaN(), Emag: math.NaN()}}, 307 {d: Number{Real: math.NaN(), Emag: 1}, p: 3, want: Number{Real: math.NaN(), Emag: math.NaN()}}, 308 {d: Number{Real: math.NaN(), Emag: 2}, p: 2, want: Number{Real: math.NaN(), Emag: math.NaN()}}, 309 {d: Number{Real: math.NaN(), Emag: 2}, p: 3, want: Number{Real: math.NaN(), Emag: math.NaN()}}, 310 311 // PowReal(x, NaN) = NaN+NaNϵ 312 {d: Number{Real: 0, Emag: 0}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 313 {d: Number{Real: 2, Emag: 0}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 314 {d: Number{Real: 3, Emag: 0}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 315 {d: Number{Real: 0, Emag: 1}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 316 {d: Number{Real: 2, Emag: 1}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 317 {d: Number{Real: 3, Emag: 1}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 318 {d: Number{Real: 0, Emag: 2}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 319 {d: Number{Real: 2, Emag: 2}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 320 {d: Number{Real: 3, Emag: 2}, p: math.NaN(), want: Number{Real: math.NaN(), Emag: math.NaN()}}, 321 322 // Handled by math.Pow tests: 323 // 324 // Pow(±0, y) = ±Inf for y an odd integer < 0 325 // Pow(±0, -Inf) = +Inf 326 // Pow(±0, +Inf) = +0 327 // Pow(±0, y) = +Inf for finite y < 0 and not an odd integer 328 // Pow(±0, y) = ±0 for y an odd integer > 0 329 // Pow(±0, y) = +0 for finite y > 0 and not an odd integer 330 // Pow(-1, ±Inf) = 1 331 332 // PowReal(x+0ϵ, +Inf) = +Inf+NaNϵ for |x| > 1 333 {d: Number{Real: 2, Emag: 0}, p: math.Inf(1), want: Number{Real: math.Inf(1), Emag: math.NaN()}}, 334 {d: Number{Real: 3, Emag: 0}, p: math.Inf(1), want: Number{Real: math.Inf(1), Emag: math.NaN()}}, 335 336 // PowReal(x+yϵ, +Inf) = +Inf for |x| > 1 337 {d: Number{Real: 2, Emag: 1}, p: math.Inf(1), want: Number{Real: math.Inf(1), Emag: math.Inf(1)}}, 338 {d: Number{Real: 3, Emag: 1}, p: math.Inf(1), want: Number{Real: math.Inf(1), Emag: math.Inf(1)}}, 339 {d: Number{Real: 2, Emag: 2}, p: math.Inf(1), want: Number{Real: math.Inf(1), Emag: math.Inf(1)}}, 340 {d: Number{Real: 3, Emag: 2}, p: math.Inf(1), want: Number{Real: math.Inf(1), Emag: math.Inf(1)}}, 341 342 // PowReal(x, -Inf) = +0+NaNϵ for |x| > 1 343 {d: Number{Real: 2, Emag: 0}, p: math.Inf(-1), want: Number{Real: 0, Emag: math.NaN()}}, 344 {d: Number{Real: 3, Emag: 0}, p: math.Inf(-1), want: Number{Real: 0, Emag: math.NaN()}}, 345 {d: Number{Real: 2, Emag: 1}, p: math.Inf(-1), want: Number{Real: 0, Emag: math.NaN()}}, 346 {d: Number{Real: 3, Emag: 1}, p: math.Inf(-1), want: Number{Real: 0, Emag: math.NaN()}}, 347 {d: Number{Real: 2, Emag: 2}, p: math.Inf(-1), want: Number{Real: 0, Emag: math.NaN()}}, 348 {d: Number{Real: 3, Emag: 2}, p: math.Inf(-1), want: Number{Real: 0, Emag: math.NaN()}}, 349 350 // PowReal(x+yϵ, +Inf) = +0+NaNϵ for |x| < 1 351 {d: Number{Real: 0.1, Emag: 0}, p: math.Inf(1), want: Number{Real: 0, Emag: math.NaN()}}, 352 {d: Number{Real: 0.1, Emag: 0.1}, p: math.Inf(1), want: Number{Real: 0, Emag: math.NaN()}}, 353 {d: Number{Real: 0.2, Emag: 0.2}, p: math.Inf(1), want: Number{Real: 0, Emag: math.NaN()}}, 354 {d: Number{Real: 0.5, Emag: 0.5}, p: math.Inf(1), want: Number{Real: 0, Emag: math.NaN()}}, 355 356 // PowReal(x+0ϵ, -Inf) = +Inf+NaNϵ for |x| < 1 357 {d: Number{Real: 0.1, Emag: 0}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.NaN()}}, 358 {d: Number{Real: 0.2, Emag: 0}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.NaN()}}, 359 360 // PowReal(x, -Inf) = +Inf-Infϵ for |x| < 1 361 {d: Number{Real: 0.1, Emag: 0.1}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.Inf(-1)}}, 362 {d: Number{Real: 0.2, Emag: 0.1}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.Inf(-1)}}, 363 {d: Number{Real: 0.1, Emag: 0.2}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.Inf(-1)}}, 364 {d: Number{Real: 0.2, Emag: 0.2}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.Inf(-1)}}, 365 {d: Number{Real: 0.1, Emag: 1}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.Inf(-1)}}, 366 {d: Number{Real: 0.2, Emag: 1}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.Inf(-1)}}, 367 {d: Number{Real: 0.1, Emag: 2}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.Inf(-1)}}, 368 {d: Number{Real: 0.2, Emag: 2}, p: math.Inf(-1), want: Number{Real: math.Inf(1), Emag: math.Inf(-1)}}, 369 370 // Handled by math.Pow tests: 371 // 372 // Pow(+Inf, y) = +Inf for y > 0 373 // Pow(+Inf, y) = +0 for y < 0 374 // Pow(-Inf, y) = Pow(-0, -y) 375 376 // PowReal(x, y) = NaN+NaNϵ for finite x < 0 and finite non-integer y 377 {d: Number{Real: -1, Emag: -1}, p: 0.5, want: Number{Real: math.NaN(), Emag: math.NaN()}}, 378 {d: Number{Real: -1, Emag: 2}, p: 0.5, want: Number{Real: math.NaN(), Emag: math.NaN()}}, 379 } 380 381 func TestPowReal(t *testing.T) { 382 t.Parallel() 383 const tol = 1e-15 384 for _, test := range powRealTests { 385 got := PowReal(test.d, test.p) 386 if !sameDual(got, test.want, tol) { 387 t.Errorf("unexpected PowReal(%v, %v): got:%v want:%v", test.d, test.p, got, test.want) 388 } 389 } 390 } 391 392 func sameDual(a, b Number, tol float64) bool { 393 return same(a.Real, b.Real, tol) && same(a.Emag, b.Emag, tol) 394 } 395 396 func same(a, b, tol float64) bool { 397 return (math.IsNaN(a) && math.IsNaN(b)) || scalar.EqualWithinAbsOrRel(a, b, tol, tol) 398 }