gonum.org/v1/gonum@v0.14.0/unit/unit_test.go (about) 1 // Copyright ©2013 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 unit 6 7 import ( 8 "fmt" 9 "math" 10 "testing" 11 ) 12 13 // ether is a non-existent unit used for testing. 14 type ether float64 15 16 func (e ether) Unit() *Unit { 17 return New(float64(e), Dimensions{reserved: 1}) 18 } 19 20 var formatTests = []struct { 21 unit Uniter 22 format string 23 expect string 24 }{ 25 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2}), "%f", "9.810000 kg s^-2"}, 26 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2}), "%1.f", "10 kg s^-2"}, 27 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2}), "%10.f", "10 kg s^-2"}, 28 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2}), "%.1f", "9.8 kg s^-2"}, 29 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2}), "%20f", " 9.810000 kg s^-2"}, 30 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2}), "%1f", "9.810000 kg s^-2"}, 31 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2, LengthDim: 0}), "%f", "9.810000 kg s^-2"}, 32 {New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1}), "%e", "6.626070e-34 kg^2 s^-1"}, 33 {New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1}), "%20.3e", " 6.626e-34 kg^2 s^-1"}, 34 {New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1}), "%.3e", "6.626e-34 kg^2 s^-1"}, 35 {New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1}), "%25e", " 6.626070e-34 kg^2 s^-1"}, 36 {New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1}), "%1e", "6.626070e-34 kg^2 s^-1"}, 37 {New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1}), "%v", "6.62606957e-34 kg^2 s^-1"}, 38 {New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1}), "%s", "%!s(*Unit=6.62606957e-34 kg^2 s^-1)"}, 39 {Dimless(math.E), "%v", "2.718281828459045"}, 40 {Dimless(math.E), "%.2v", "2.7"}, 41 {Dimless(math.E), "%6.2v", " 2.7"}, 42 {Dimless(math.E), "%20v", " 2.718281828459045"}, 43 {Dimless(math.E), "%1v", "2.718281828459045"}, 44 {Dimless(math.E), "%#v", "unit.Dimless(2.718281828459045)"}, 45 {Dimless(math.E), "%s", "%!s(unit.Dimless=2.718281828459045)"}, 46 {Angle(1), "%v", "1 rad"}, 47 {Angle(0.45), "%.1v", "0.5 rad"}, 48 {Angle(0.45), "%10.1v", " 0.5 rad"}, 49 {Angle(0.45), "%10v", " 0.45 rad"}, 50 {Angle(0.45), "%1v", "0.45 rad"}, 51 {Angle(1), "%#v", "unit.Angle(1)"}, 52 {Angle(1), "%s", "%!s(unit.Angle=1 rad)"}, 53 {Current(1), "%v", "1 A"}, 54 {Current(0.45), "%.1v", "0.5 A"}, 55 {Current(0.45), "%10.1v", " 0.5 A"}, 56 {Current(0.45), "%10v", " 0.45 A"}, 57 {Current(0.45), "%1v", "0.45 A"}, 58 {Current(1), "%#v", "unit.Current(1)"}, 59 {Current(1), "%s", "%!s(unit.Current=1 A)"}, 60 {LuminousIntensity(1), "%v", "1 cd"}, 61 {LuminousIntensity(0.45), "%.1v", "0.5 cd"}, 62 {LuminousIntensity(0.45), "%10.1v", " 0.5 cd"}, 63 {LuminousIntensity(0.45), "%10v", " 0.45 cd"}, 64 {LuminousIntensity(0.45), "%1v", "0.45 cd"}, 65 {LuminousIntensity(1), "%#v", "unit.LuminousIntensity(1)"}, 66 {LuminousIntensity(1), "%s", "%!s(unit.LuminousIntensity=1 cd)"}, 67 {Mass(1), "%v", "1 kg"}, 68 {Mass(0.45), "%.1v", "0.5 kg"}, 69 {Mass(0.45), "%10.1v", " 0.5 kg"}, 70 {Mass(0.45), "%10v", " 0.45 kg"}, 71 {Mass(0.45), "%1v", "0.45 kg"}, 72 {Mass(1), "%#v", "unit.Mass(1)"}, 73 {Mass(1), "%s", "%!s(unit.Mass=1 kg)"}, 74 {Mole(1), "%v", "1 mol"}, 75 {Mole(0.45), "%.1v", "0.5 mol"}, 76 {Mole(0.45), "%10.1v", " 0.5 mol"}, 77 {Mole(0.45), "%10v", " 0.45 mol"}, 78 {Mole(0.45), "%1v", "0.45 mol"}, 79 {Mole(1), "%#v", "unit.Mole(1)"}, 80 {Mole(1), "%s", "%!s(unit.Mole=1 mol)"}, 81 {Length(1.61619926e-35), "%v", "1.61619926e-35 m"}, 82 {Length(1.61619926e-35), "%.2v", "1.6e-35 m"}, 83 {Length(1.61619926e-35), "%10.2v", " 1.6e-35 m"}, 84 {Length(1.61619926e-35), "%20v", " 1.61619926e-35 m"}, 85 {Length(1.61619926e-35), "%1v", "1.61619926e-35 m"}, 86 {Length(1.61619926e-35), "%#v", "unit.Length(1.61619926e-35)"}, 87 {Length(1.61619926e-35), "%s", "%!s(unit.Length=1.61619926e-35 m)"}, 88 {Temperature(15.2), "%v", "15.2 K"}, 89 {Temperature(15.2), "%.2v", "15 K"}, 90 {Temperature(15.2), "%6.2v", " 15 K"}, 91 {Temperature(15.2), "%10v", " 15.2 K"}, 92 {Temperature(15.2), "%1v", "15.2 K"}, 93 {Temperature(15.2), "%#v", "unit.Temperature(15.2)"}, 94 {Temperature(15.2), "%s", "%!s(unit.Temperature=15.2 K)"}, 95 {Time(15.2), "%v", "15.2 s"}, 96 {Time(15.2), "%.2v", "15 s"}, 97 {Time(15.2), "%6.2v", " 15 s"}, 98 {Time(15.2), "%10v", " 15.2 s"}, 99 {Time(15.2), "%1v", "15.2 s"}, 100 {Time(15.2), "%#v", "unit.Time(15.2)"}, 101 {Time(15.2), "%s", "%!s(unit.Time=15.2 s)"}, 102 } 103 104 func TestFormat(t *testing.T) { 105 t.Parallel() 106 for _, ts := range formatTests { 107 if r := fmt.Sprintf(ts.format, ts.unit); r != ts.expect { 108 t.Errorf("Format %q: got: %q expected: %q", ts.format, r, ts.expect) 109 } 110 } 111 } 112 113 func TestCopy(t *testing.T) { 114 t.Parallel() 115 for _, ts := range formatTests { 116 u := ts.unit.Unit() 117 if len(u.Dimensions()) == 0 { 118 continue 119 } 120 c := u.Copy() 121 c.Mul(c) 122 if DimensionsMatch(u, c) { 123 t.Errorf("dimensions match after mutating copy of %v: %v == %v", u, u.Dimensions(), c.Dimensions()) 124 } 125 } 126 } 127 128 func TestGoStringFormat(t *testing.T) { 129 t.Parallel() 130 expect1 := `&unit.Unit{dimensions:unit.Dimensions{4:2, 7:-1}, value:6.62606957e-34}` 131 expect2 := `&unit.Unit{dimensions:unit.Dimensions{7:-1, 4:2}, value:6.62606957e-34}` 132 if r := fmt.Sprintf("%#v", New(6.62606957e-34, Dimensions{MassDim: 2, TimeDim: -1})); r != expect1 && r != expect2 { 133 t.Errorf("Format %q: got: %q expected: %q", "%#v", r, expect1) 134 } 135 } 136 137 var initializationTests = []struct { 138 unit *Unit 139 expValue float64 140 expMap map[Dimension]int 141 }{ 142 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2}), 9.81, Dimensions{MassDim: 1, TimeDim: -2}}, 143 {New(9.81, Dimensions{MassDim: 1, TimeDim: -2, LengthDim: 0, CurrentDim: 0}), 9.81, Dimensions{MassDim: 1, TimeDim: -2}}, 144 } 145 146 func TestInitialization(t *testing.T) { 147 t.Parallel() 148 for _, ts := range initializationTests { 149 if ts.expValue != ts.unit.value { 150 t.Errorf("Value wrong on initialization: got: %v expected: %v", ts.unit.value, ts.expValue) 151 } 152 if len(ts.expMap) != len(ts.unit.dimensions) { 153 t.Errorf("Map mismatch: got: %#v expected: %#v", ts.unit.dimensions, ts.expMap) 154 } 155 for key, val := range ts.expMap { 156 if ts.unit.dimensions[key] != val { 157 t.Errorf("Map mismatch: got: %#v expected: %#v", ts.unit.dimensions, ts.expMap) 158 } 159 } 160 } 161 } 162 163 var dimensionEqualityTests = []struct { 164 name string 165 a Uniter 166 b Uniter 167 shouldMatch bool 168 }{ 169 {"same_empty", New(1.0, Dimensions{}), New(1.0, Dimensions{}), true}, 170 {"same_one", New(1.0, Dimensions{TimeDim: 1}), New(1.0, Dimensions{TimeDim: 1}), true}, 171 {"same_mult", New(1.0, Dimensions{TimeDim: 1, LengthDim: -2}), New(1.0, Dimensions{TimeDim: 1, LengthDim: -2}), true}, 172 {"diff_one_empty", New(1.0, Dimensions{}), New(1.0, Dimensions{TimeDim: 1, LengthDim: -2}), false}, 173 {"diff_same_dim", New(1.0, Dimensions{TimeDim: 1}), New(1.0, Dimensions{TimeDim: 2}), false}, 174 {"diff_same_pow", New(1.0, Dimensions{LengthDim: 1}), New(1.0, Dimensions{TimeDim: 1}), false}, 175 {"diff_numdim", New(1.0, Dimensions{TimeDim: 1, LengthDim: 2}), New(1.0, Dimensions{TimeDim: 2}), false}, 176 {"diff_one_same_dim", New(1.0, Dimensions{LengthDim: 1, TimeDim: 1}), New(1.0, Dimensions{LengthDim: 1, TimeDim: 2}), false}, 177 } 178 179 func TestDimensionEquality(t *testing.T) { 180 t.Parallel() 181 for _, ts := range dimensionEqualityTests { 182 if DimensionsMatch(ts.a, ts.b) != ts.shouldMatch { 183 t.Errorf("Dimension comparison incorrect for case %s. got: %v, expected: %v", ts.name, !ts.shouldMatch, ts.shouldMatch) 184 } 185 } 186 } 187 188 func TestOperations(t *testing.T) { 189 operationTests := []struct { 190 recvOp func(Uniter) *Unit 191 param Uniter 192 want Uniter 193 }{ 194 {Dimless(1).Unit().Add, Dimless(2), Dimless(3)}, 195 {Dimless(1).Unit().Mul, Dimless(2), Dimless(2)}, 196 {Dimless(1).Unit().Mul, Length(2), Length(2)}, 197 {Length(1).Unit().Mul, Dimless(2), Length(2)}, 198 {Dimless(1).Unit().Div, Length(2), New(0.5, Dimensions{LengthDim: -1})}, 199 {Length(1).Unit().Div, Dimless(2), Length(0.5)}, 200 } 201 t.Parallel() 202 for i, test := range operationTests { 203 var got Uniter 204 if panics(func() { got = test.recvOp(test.param) }) { 205 t.Errorf("unexpected panic for test %d", i) 206 continue 207 } 208 if !DimensionsMatch(got, test.want) { 209 t.Errorf("dimension mismatch for test %d: got=%v want=%v", i, got.Unit(), test.want.Unit()) 210 } 211 if got.Unit().Value() != test.want.Unit().Value() { 212 t.Errorf("value mismatch for test %d: got=%v want=%v", i, got.Unit().Value(), test.want.Unit().Value()) 213 } 214 } 215 } 216 217 // panics returns true if the function panics. 218 func panics(f func()) (b bool) { 219 defer func() { 220 err := recover() 221 if err != nil { 222 b = true 223 } 224 }() 225 f() 226 return 227 } 228 229 type UnitStructer interface { 230 UnitStruct() *UnitStruct 231 } 232 233 type UnitStruct struct { 234 current int 235 length int 236 luminosity int 237 mass int 238 temperature int 239 time int 240 chemamt int // For mol 241 value float64 242 } 243 244 // Check if the dimensions of two units are the same 245 func DimensionsMatchStruct(aU, bU UnitStructer) bool { 246 a := aU.UnitStruct() 247 b := bU.UnitStruct() 248 if a.length != b.length { 249 return false 250 } 251 if a.time != b.time { 252 return false 253 } 254 if a.mass != b.mass { 255 return false 256 } 257 if a.current != b.current { 258 return false 259 } 260 if a.temperature != b.temperature { 261 return false 262 } 263 if a.luminosity != b.luminosity { 264 return false 265 } 266 if a.chemamt != b.chemamt { 267 return false 268 } 269 return true 270 } 271 272 func (u *UnitStruct) UnitStruct() *UnitStruct { 273 return u 274 } 275 276 func (u *UnitStruct) Add(aU UnitStructer) *UnitStruct { 277 a := aU.UnitStruct() 278 if !DimensionsMatchStruct(a, u) { 279 panic("dimension mismatch") 280 } 281 u.value += a.value 282 return u 283 } 284 285 func (u *UnitStruct) Mul(aU UnitStructer) *UnitStruct { 286 a := aU.UnitStruct() 287 u.length += a.length 288 u.time += a.time 289 u.mass += a.mass 290 u.current += a.current 291 u.temperature += a.temperature 292 u.luminosity += a.luminosity 293 u.chemamt += a.chemamt 294 u.value *= a.value 295 return u 296 } 297 298 func BenchmarkAddStruct(b *testing.B) { 299 u1 := &UnitStruct{current: 1, chemamt: 5, value: 10} 300 u2 := &UnitStruct{current: 1, chemamt: 5, value: 100} 301 for i := 0; i < b.N; i++ { 302 u2.Add(u1) 303 } 304 } 305 306 func BenchmarkMulStruct(b *testing.B) { 307 u1 := &UnitStruct{current: 1, chemamt: 5, value: 10} 308 u2 := &UnitStruct{mass: 1, time: 1, value: 100} 309 for i := 0; i < b.N; i++ { 310 u2.Mul(u1) 311 } 312 } 313 314 type UnitMapper interface { 315 UnitMap() *UnitMap 316 } 317 318 type dimensionMap int 319 320 const ( 321 LengthB dimensionMap = iota 322 TimeB 323 MassB 324 CurrentB 325 TemperatureB 326 LuminosityB 327 ChemAmtB 328 ) 329 330 type UnitMap struct { 331 dimension map[dimensionMap]int 332 value float64 333 } 334 335 // Check if the dimensions of two units are the same 336 func DimensionsMatchMap(aU, bU UnitMapper) bool { 337 a := aU.UnitMap() 338 b := bU.UnitMap() 339 if len(a.dimension) != len(b.dimension) { 340 panic("Unequal dimension") 341 } 342 for key, dimA := range a.dimension { 343 dimB, ok := b.dimension[key] 344 if !ok || dimA != dimB { 345 panic("Unequal dimension") 346 } 347 } 348 return true 349 } 350 351 func (u *UnitMap) UnitMap() *UnitMap { 352 return u 353 } 354 355 func (u *UnitMap) Add(aU UnitMapper) *UnitMap { 356 a := aU.UnitMap() 357 if !DimensionsMatchMap(a, u) { 358 panic("dimension mismatch") 359 } 360 u.value += a.value 361 return u 362 } 363 364 func (u *UnitMap) Mul(aU UnitMapper) *UnitMap { 365 a := aU.UnitMap() 366 for key, val := range a.dimension { 367 u.dimension[key] += val 368 } 369 u.value *= a.value 370 return u 371 } 372 373 var sink float64 374 375 func BenchmarkAddFloat(b *testing.B) { 376 sink = 0 377 c := 10.0 378 for i := 0; i < b.N; i++ { 379 sink += c 380 } 381 } 382 383 func BenchmarkMulFloat(b *testing.B) { 384 sink = 0 385 c := 10.0 386 for i := 0; i < b.N; i++ { 387 sink *= c 388 } 389 } 390 391 func BenchmarkAddMapSmall(b *testing.B) { 392 u1 := &UnitMap{value: 10} 393 u1.dimension = make(map[dimensionMap]int) 394 u1.dimension[CurrentB] = 1 395 u1.dimension[ChemAmtB] = 5 396 397 u2 := &UnitMap{value: 10} 398 u2.dimension = make(map[dimensionMap]int) 399 u2.dimension[CurrentB] = 1 400 u2.dimension[ChemAmtB] = 5 401 for i := 0; i < b.N; i++ { 402 u2.Add(u1) 403 } 404 } 405 406 func BenchmarkMulMapSmallDiff(b *testing.B) { 407 u1 := &UnitMap{value: 10} 408 u1.dimension = make(map[dimensionMap]int) 409 u1.dimension[LengthB] = 1 410 411 u2 := &UnitMap{value: 10} 412 u2.dimension = make(map[dimensionMap]int) 413 u2.dimension[MassB] = 1 414 for i := 0; i < b.N; i++ { 415 u2.Mul(u1) 416 } 417 } 418 419 func BenchmarkMulMapSmallSame(b *testing.B) { 420 u1 := &UnitMap{value: 10} 421 u1.dimension = make(map[dimensionMap]int) 422 u1.dimension[LengthB] = 1 423 424 u2 := &UnitMap{value: 10} 425 u2.dimension = make(map[dimensionMap]int) 426 u2.dimension[LengthB] = 2 427 for i := 0; i < b.N; i++ { 428 u2.Mul(u1) 429 } 430 } 431 432 func BenchmarkMulMapLargeDiff(b *testing.B) { 433 u1 := &UnitMap{value: 10} 434 u1.dimension = make(map[dimensionMap]int) 435 u1.dimension[LengthB] = 1 436 u1.dimension[MassB] = 1 437 u1.dimension[ChemAmtB] = 1 438 u1.dimension[TemperatureB] = 1 439 u1.dimension[LuminosityB] = 1 440 u1.dimension[TimeB] = 1 441 u1.dimension[CurrentB] = 1 442 443 u2 := &UnitMap{value: 10} 444 u2.dimension = make(map[dimensionMap]int) 445 u2.dimension[MassB] = 1 446 for i := 0; i < b.N; i++ { 447 u2.Mul(u1) 448 } 449 } 450 451 func BenchmarkMulMapLargeSame(b *testing.B) { 452 u1 := &UnitMap{value: 10} 453 u1.dimension = make(map[dimensionMap]int) 454 u1.dimension[LengthB] = 2 455 u1.dimension[MassB] = 2 456 u1.dimension[ChemAmtB] = 2 457 u1.dimension[TemperatureB] = 2 458 u1.dimension[LuminosityB] = 2 459 u1.dimension[TimeB] = 2 460 u1.dimension[CurrentB] = 2 461 462 u2 := &UnitMap{value: 10} 463 u2.dimension = make(map[dimensionMap]int) 464 u2.dimension[LengthB] = 3 465 u2.dimension[MassB] = 3 466 u2.dimension[ChemAmtB] = 3 467 u2.dimension[TemperatureB] = 3 468 u2.dimension[LuminosityB] = 3 469 u2.dimension[TimeB] = 3 470 u2.dimension[CurrentB] = 3 471 for i := 0; i < b.N; i++ { 472 u2.Mul(u1) 473 } 474 }