github.com/gopherd/gonum@v0.0.4/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 var operationTests = []struct { 189 recvOp func(Uniter) *Unit 190 param Uniter 191 want Uniter 192 }{ 193 {Dimless(1).Unit().Add, Dimless(2), Dimless(3)}, 194 {Dimless(1).Unit().Mul, Dimless(2), Dimless(2)}, 195 {Dimless(1).Unit().Mul, Length(2), Length(2)}, 196 {Length(1).Unit().Mul, Dimless(2), Length(2)}, 197 {Dimless(1).Unit().Div, Length(2), New(0.5, Dimensions{LengthDim: -1})}, 198 {Length(1).Unit().Div, Dimless(2), Length(0.5)}, 199 } 200 201 func TestOperations(t *testing.T) { 202 t.Parallel() 203 for i, test := range operationTests { 204 var got Uniter 205 if panics(func() { got = test.recvOp(test.param) }) { 206 t.Errorf("unexpected panic for test %d", i) 207 continue 208 } 209 if !DimensionsMatch(got, test.want) { 210 t.Errorf("dimension mismatch for test %d: got=%v want=%v", i, got.Unit(), test.want.Unit()) 211 } 212 if got.Unit().Value() != test.want.Unit().Value() { 213 t.Errorf("value mismatch for test %d: got=%v want=%v", i, got.Unit().Value(), test.want.Unit().Value()) 214 } 215 } 216 } 217 218 // panics returns true if the function panics. 219 func panics(f func()) (b bool) { 220 defer func() { 221 err := recover() 222 if err != nil { 223 b = true 224 } 225 }() 226 f() 227 return 228 } 229 230 type UnitStructer interface { 231 UnitStruct() *UnitStruct 232 } 233 234 type UnitStruct struct { 235 current int 236 length int 237 luminosity int 238 mass int 239 temperature int 240 time int 241 chemamt int // For mol 242 value float64 243 } 244 245 // Check if the dimensions of two units are the same 246 func DimensionsMatchStruct(aU, bU UnitStructer) bool { 247 a := aU.UnitStruct() 248 b := bU.UnitStruct() 249 if a.length != b.length { 250 return false 251 } 252 if a.time != b.time { 253 return false 254 } 255 if a.mass != b.mass { 256 return false 257 } 258 if a.current != b.current { 259 return false 260 } 261 if a.temperature != b.temperature { 262 return false 263 } 264 if a.luminosity != b.luminosity { 265 return false 266 } 267 if a.chemamt != b.chemamt { 268 return false 269 } 270 return true 271 } 272 273 func (u *UnitStruct) UnitStruct() *UnitStruct { 274 return u 275 } 276 277 func (u *UnitStruct) Add(aU UnitStructer) *UnitStruct { 278 a := aU.UnitStruct() 279 if !DimensionsMatchStruct(a, u) { 280 panic("dimension mismatch") 281 } 282 u.value += a.value 283 return u 284 } 285 286 func (u *UnitStruct) Mul(aU UnitStructer) *UnitStruct { 287 a := aU.UnitStruct() 288 u.length += a.length 289 u.time += a.time 290 u.mass += a.mass 291 u.current += a.current 292 u.temperature += a.temperature 293 u.luminosity += a.luminosity 294 u.chemamt += a.chemamt 295 u.value *= a.value 296 return u 297 } 298 299 func BenchmarkAddStruct(b *testing.B) { 300 u1 := &UnitStruct{current: 1, chemamt: 5, value: 10} 301 u2 := &UnitStruct{current: 1, chemamt: 5, value: 100} 302 for i := 0; i < b.N; i++ { 303 u2.Add(u1) 304 } 305 } 306 307 func BenchmarkMulStruct(b *testing.B) { 308 u1 := &UnitStruct{current: 1, chemamt: 5, value: 10} 309 u2 := &UnitStruct{mass: 1, time: 1, value: 100} 310 for i := 0; i < b.N; i++ { 311 u2.Mul(u1) 312 } 313 } 314 315 type UnitMapper interface { 316 UnitMap() *UnitMap 317 } 318 319 type dimensionMap int 320 321 const ( 322 LengthB dimensionMap = iota 323 TimeB 324 MassB 325 CurrentB 326 TemperatureB 327 LuminosityB 328 ChemAmtB 329 ) 330 331 type UnitMap struct { 332 dimension map[dimensionMap]int 333 value float64 334 } 335 336 // Check if the dimensions of two units are the same 337 func DimensionsMatchMap(aU, bU UnitMapper) bool { 338 a := aU.UnitMap() 339 b := bU.UnitMap() 340 if len(a.dimension) != len(b.dimension) { 341 panic("Unequal dimension") 342 } 343 for key, dimA := range a.dimension { 344 dimB, ok := b.dimension[key] 345 if !ok || dimA != dimB { 346 panic("Unequal dimension") 347 } 348 } 349 return true 350 } 351 352 func (u *UnitMap) UnitMap() *UnitMap { 353 return u 354 } 355 356 func (u *UnitMap) Add(aU UnitMapper) *UnitMap { 357 a := aU.UnitMap() 358 if !DimensionsMatchMap(a, u) { 359 panic("dimension mismatch") 360 } 361 u.value += a.value 362 return u 363 } 364 365 func (u *UnitMap) Mul(aU UnitMapper) *UnitMap { 366 a := aU.UnitMap() 367 for key, val := range a.dimension { 368 u.dimension[key] += val 369 } 370 u.value *= a.value 371 return u 372 } 373 374 var sink float64 375 376 func BenchmarkAddFloat(b *testing.B) { 377 sink = 0 378 c := 10.0 379 for i := 0; i < b.N; i++ { 380 sink += c 381 } 382 } 383 384 func BenchmarkMulFloat(b *testing.B) { 385 sink = 0 386 c := 10.0 387 for i := 0; i < b.N; i++ { 388 sink *= c 389 } 390 } 391 392 func BenchmarkAddMapSmall(b *testing.B) { 393 u1 := &UnitMap{value: 10} 394 u1.dimension = make(map[dimensionMap]int) 395 u1.dimension[CurrentB] = 1 396 u1.dimension[ChemAmtB] = 5 397 398 u2 := &UnitMap{value: 10} 399 u2.dimension = make(map[dimensionMap]int) 400 u2.dimension[CurrentB] = 1 401 u2.dimension[ChemAmtB] = 5 402 for i := 0; i < b.N; i++ { 403 u2.Add(u1) 404 } 405 } 406 407 func BenchmarkMulMapSmallDiff(b *testing.B) { 408 u1 := &UnitMap{value: 10} 409 u1.dimension = make(map[dimensionMap]int) 410 u1.dimension[LengthB] = 1 411 412 u2 := &UnitMap{value: 10} 413 u2.dimension = make(map[dimensionMap]int) 414 u2.dimension[MassB] = 1 415 for i := 0; i < b.N; i++ { 416 u2.Mul(u1) 417 } 418 } 419 420 func BenchmarkMulMapSmallSame(b *testing.B) { 421 u1 := &UnitMap{value: 10} 422 u1.dimension = make(map[dimensionMap]int) 423 u1.dimension[LengthB] = 1 424 425 u2 := &UnitMap{value: 10} 426 u2.dimension = make(map[dimensionMap]int) 427 u2.dimension[LengthB] = 2 428 for i := 0; i < b.N; i++ { 429 u2.Mul(u1) 430 } 431 } 432 433 func BenchmarkMulMapLargeDiff(b *testing.B) { 434 u1 := &UnitMap{value: 10} 435 u1.dimension = make(map[dimensionMap]int) 436 u1.dimension[LengthB] = 1 437 u1.dimension[MassB] = 1 438 u1.dimension[ChemAmtB] = 1 439 u1.dimension[TemperatureB] = 1 440 u1.dimension[LuminosityB] = 1 441 u1.dimension[TimeB] = 1 442 u1.dimension[CurrentB] = 1 443 444 u2 := &UnitMap{value: 10} 445 u2.dimension = make(map[dimensionMap]int) 446 u2.dimension[MassB] = 1 447 for i := 0; i < b.N; i++ { 448 u2.Mul(u1) 449 } 450 } 451 452 func BenchmarkMulMapLargeSame(b *testing.B) { 453 u1 := &UnitMap{value: 10} 454 u1.dimension = make(map[dimensionMap]int) 455 u1.dimension[LengthB] = 2 456 u1.dimension[MassB] = 2 457 u1.dimension[ChemAmtB] = 2 458 u1.dimension[TemperatureB] = 2 459 u1.dimension[LuminosityB] = 2 460 u1.dimension[TimeB] = 2 461 u1.dimension[CurrentB] = 2 462 463 u2 := &UnitMap{value: 10} 464 u2.dimension = make(map[dimensionMap]int) 465 u2.dimension[LengthB] = 3 466 u2.dimension[MassB] = 3 467 u2.dimension[ChemAmtB] = 3 468 u2.dimension[TemperatureB] = 3 469 u2.dimension[LuminosityB] = 3 470 u2.dimension[TimeB] = 3 471 u2.dimension[CurrentB] = 3 472 for i := 0; i < b.N; i++ { 473 u2.Mul(u1) 474 } 475 }