mirror of
https://github.com/gonum/gonum.git
synced 2025-10-11 18:10:10 +08:00
289 lines
9.0 KiB
Go
289 lines
9.0 KiB
Go
package unit
|
|
|
|
import (
|
|
"sort"
|
|
"strconv"
|
|
"sync"
|
|
)
|
|
|
|
// Uniter is an interface representing a type that can be converted
|
|
// to a unit.
|
|
type Uniter interface {
|
|
Unit() *Unit
|
|
}
|
|
|
|
// Dimension is a type representing an SI base dimension or other
|
|
// orthogonal dimension. If a new dimension is desired for a
|
|
// domain-specific problem, NewDimension should be used. Integers
|
|
// should never be cast as type dimension
|
|
// // Good: Create a package constant with an init function
|
|
// var MyDimension unit.Dimension
|
|
// init(){
|
|
// MyDimension = NewDimension("my")
|
|
// }
|
|
// main(){
|
|
// var := MyDimension(28.2)
|
|
// }
|
|
type Dimension int
|
|
|
|
const (
|
|
reserved Dimension = iota
|
|
// SI Base Units
|
|
CurrentDim Dimension
|
|
LengthDim
|
|
LuminousIntensityDim
|
|
MassDim
|
|
TemperatureDim
|
|
TimeDim
|
|
// Start of other SI Units
|
|
AngleDim // e.g. radians
|
|
lastPackageDimension // Used in create dimension
|
|
)
|
|
|
|
var lastCreatedDimension Dimension = lastPackageDimension
|
|
var dimensionToSymbol map[Dimension]string = make(map[Dimension]string) // for printing
|
|
var symbolToDimension map[string]Dimension = make(map[string]Dimension) // for guaranteeing there aren't two identical symbols
|
|
|
|
// TODO: Should we actually reserve "common" SI unit symbols ("N", "J", etc.) so there isn't confusion
|
|
// TODO: If we have a fancier ParseUnit, maybe the 'reserved' symbols should be a different map
|
|
// map[string]string which says how they go?
|
|
func init() {
|
|
dimensionToSymbol[CurrentDim] = "A"
|
|
symbolToDimension["A"] = CurrentDim
|
|
dimensionToSymbol[LengthDim] = "m"
|
|
symbolToDimension["m"] = LengthDim
|
|
dimensionToSymbol[LuminousIntensityDim] = "cd"
|
|
symbolToDimension["cd"] = LuminousIntensityDim
|
|
dimensionToSymbol[MassDim] = "kg"
|
|
symbolToDimension["kg"] = MassDim
|
|
dimensionToSymbol[TemperatureDim] = "K"
|
|
symbolToDimension["K"] = TemperatureDim
|
|
dimensionToSymbol[TimeDim] = "s"
|
|
symbolToDimension["s"] = TimeDim
|
|
dimensionToSymbol[AngleDim] = "rad"
|
|
symbolToDimension["rad"] = AngleDim
|
|
|
|
// Reserve common SI symbols
|
|
// base units
|
|
symbolToDimension["mol"] = reserved
|
|
// prefixes
|
|
symbolToDimension["Y"] = reserved
|
|
symbolToDimension["Z"] = reserved
|
|
symbolToDimension["E"] = reserved
|
|
symbolToDimension["P"] = reserved
|
|
symbolToDimension["T"] = reserved
|
|
symbolToDimension["G"] = reserved
|
|
symbolToDimension["M"] = reserved
|
|
symbolToDimension["k"] = reserved
|
|
symbolToDimension["h"] = reserved
|
|
symbolToDimension["da"] = reserved
|
|
symbolToDimension["d"] = reserved
|
|
symbolToDimension["c"] = reserved
|
|
symbolToDimension["m"] = reserved
|
|
symbolToDimension["μ"] = reserved
|
|
symbolToDimension["n"] = reserved
|
|
symbolToDimension["p"] = reserved
|
|
symbolToDimension["f"] = reserved
|
|
symbolToDimension["a"] = reserved
|
|
symbolToDimension["z"] = reserved
|
|
symbolToDimension["y"] = reserved
|
|
// SI Derived units with special symbols
|
|
symbolToDimension["sr"] = reserved
|
|
symbolToDimension["F"] = reserved
|
|
symbolToDimension["C"] = reserved
|
|
symbolToDimension["S"] = reserved
|
|
symbolToDimension["H"] = reserved
|
|
symbolToDimension["V"] = reserved
|
|
symbolToDimension["Ω"] = reserved
|
|
symbolToDimension["J"] = reserved
|
|
symbolToDimension["N"] = reserved
|
|
symbolToDimension["Hz"] = reserved
|
|
symbolToDimension["lx"] = reserved
|
|
symbolToDimension["lm"] = reserved
|
|
symbolToDimension["Wb"] = reserved
|
|
symbolToDimension["T"] = reserved
|
|
symbolToDimension["W"] = reserved
|
|
symbolToDimension["Pa"] = reserved
|
|
symbolToDimension["Bq"] = reserved
|
|
symbolToDimension["Gy"] = reserved
|
|
symbolToDimension["Sv"] = reserved
|
|
symbolToDimension["kat"] = reserved
|
|
// Units in use with SI
|
|
symbolToDimension["ha"] = reserved
|
|
symbolToDimension["L"] = reserved
|
|
symbolToDimension["l"] = reserved
|
|
// Units in Use Temporarily with SI
|
|
symbolToDimension["bar"] = reserved
|
|
symbolToDimension["b"] = reserved
|
|
symbolToDimension["Ci"] = reserved
|
|
symbolToDimension["R"] = reserved
|
|
symbolToDimension["rd"] = reserved
|
|
symbolToDimension["rem"] = reserved
|
|
}
|
|
|
|
// Dimensions represent the dimensionality of the unit in powers
|
|
// of that dimension. If a key is not present, the power of that
|
|
// dimension is zero. Dimensions is used in conjuction with NewUnit
|
|
type Dimensions map[Dimension]int
|
|
|
|
var newUnitMutex *sync.Mutex = &sync.Mutex{} // so there is no race condition for dimension
|
|
|
|
// NewDimension returns a new dimension variable which will have a
|
|
// unique representation across packages to prevent accidental overlap.
|
|
// The input string represents a symbol name which will be used for printing
|
|
// Unit types. This symbol may not overlap with any of the SI base units
|
|
// or other symbols of common use in SI ("kg", "J", "μ", etc.). A list of
|
|
// such symbols can be found at http://lamar.colostate.edu/~hillger/basic.htm or
|
|
// by consulting the package source. Furthermore, the provided symbol is also
|
|
// forbidden from overlapping with other packages calling NewDimension. NewDimension
|
|
// is expecting to be used only during initialization, and as such it will panic
|
|
// if the symbol matching an existing symbol
|
|
// NewDimension should only be called for unit types that are actually orthogonal
|
|
// to the base dimensions defined in this package. Please see the package-level
|
|
// documentation for further explanation
|
|
func NewDimension(symbol string) Dimension {
|
|
newUnitMutex.Lock()
|
|
defer newUnitMutex.Unlock()
|
|
lastCreatedDimension++
|
|
_, ok := symbolToDimension[symbol]
|
|
if ok {
|
|
panic("unit: dimension string " + symbol + " already used")
|
|
}
|
|
dimensionToSymbol[lastCreatedDimension] = symbol
|
|
symbolToDimension[symbol] = lastCreatedDimension
|
|
return lastCreatedDimension
|
|
}
|
|
|
|
// Unit is a type a value with generic SI units. Most useful for
|
|
// translating between dimensions, for example, by multiplying
|
|
// an acceleration with a mass to get a force. Please see the
|
|
// package documentation for further explanation.
|
|
type Unit struct {
|
|
dimensions map[Dimension]int // Map for custom dimensions
|
|
value float64
|
|
}
|
|
|
|
// NewUnit creates a new variable of type Unit which has the value
|
|
// specified by value and the dimensions specified by the
|
|
// base units struct. The value is always in SI Units.
|
|
//
|
|
// Example: To create an acceleration of 3 m/s^2, one could do
|
|
// myvar := CreateUnit(3.0, &Dimensions{unit.LengthDim: 1, unit.TimeDim: -2})
|
|
func NewUnit(value float64, d Dimensions) *Unit {
|
|
u := &Unit{
|
|
dimensions: make(map[Dimension]int),
|
|
}
|
|
for key, val := range d {
|
|
u.dimensions[key] = val
|
|
}
|
|
u.value = value
|
|
return u
|
|
}
|
|
|
|
// DimensionsMatch checks if the dimensions of two Uniters are the same
|
|
func DimensionsMatch(a, b Uniter) bool {
|
|
aUnit := a.Unit()
|
|
bUnit := b.Unit()
|
|
if len(aUnit.dimensions) != len(bUnit.dimensions) {
|
|
return false
|
|
}
|
|
for key, val := range aUnit.dimensions {
|
|
if bUnit.dimensions[key] != val {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Add adds the function argument to the reciever. Panics if the units of
|
|
// the receiver and the argument don't match.
|
|
func (u *Unit) Add(uniter Uniter) *Unit {
|
|
a := uniter.Unit()
|
|
if !DimensionsMatch(u, a) {
|
|
panic("Attempted to add the values of two units whose dimensions do not match.")
|
|
}
|
|
u.value += a.value
|
|
return u
|
|
}
|
|
|
|
// Unit implements the Uniter interface
|
|
func (u *Unit) Unit() *Unit {
|
|
return u
|
|
}
|
|
|
|
// Mul multiply the receiver by the input changing the dimensions
|
|
// of the receiver as appropriate. The input is not changed
|
|
func (u *Unit) Mul(uniter Uniter) *Unit {
|
|
a := uniter.Unit()
|
|
for key, val := range a.dimensions {
|
|
u.dimensions[key] += val
|
|
}
|
|
u.value *= a.value
|
|
return u
|
|
}
|
|
|
|
// Div divides the receiver by the argument changing the
|
|
// dimensions of the receiver as appropriate
|
|
func (u *Unit) Div(uniter Uniter) *Unit {
|
|
a := uniter.Unit()
|
|
u.value /= a.value
|
|
for key, val := range a.dimensions {
|
|
u.dimensions[key] -= val
|
|
}
|
|
return u
|
|
}
|
|
|
|
// Value return the raw value of the unit as a float64. Use of this
|
|
// method is, in general, not recommended, though it can be useful
|
|
// for printing. Instead, the FromUnit type of a specific
|
|
// dimension should be used to guarantee dimension consistency
|
|
func (u *Unit) Value() float64 {
|
|
return u.value
|
|
}
|
|
|
|
type symbolString struct {
|
|
symbol string
|
|
pow int
|
|
}
|
|
|
|
type unitPrinters []symbolString
|
|
|
|
func (u unitPrinters) Len() int {
|
|
return len(u)
|
|
}
|
|
|
|
func (u unitPrinters) Less(i, j int) bool {
|
|
return u[i].symbol < u[j].symbol
|
|
}
|
|
|
|
func (u unitPrinters) Swap(i, j int) {
|
|
u[i], u[j] = u[j], u[i]
|
|
}
|
|
|
|
// String makes Unit satisfy the stringer interface. The unit is printed
|
|
// using strconv.FormatFloat(unit.value, 'e', -1, 64), with dimensions
|
|
// appended. If the power if the dimension is not zero or one,
|
|
// symbol^power is appended, if the power is one, just the symbol is appended
|
|
// and if the power is zero, nothing is appended. Dimensions are appended
|
|
// in order by symbol name.
|
|
func (u Unit) String() string {
|
|
str := strconv.FormatFloat(u.value, 'e', -1, 64)
|
|
// Map iterates randomly, but print should be in a fixed order. Can't use
|
|
// dimension number, because for user-defined dimension that number may
|
|
// not be fixed from run to run.
|
|
data := make(unitPrinters, 0, 10)
|
|
for dimension, power := range u.dimensions {
|
|
if power != 0 {
|
|
data = append(data, symbolString{dimensionToSymbol[dimension], power})
|
|
}
|
|
}
|
|
sort.Sort(data)
|
|
for _, s := range data {
|
|
str += " " + s.symbol
|
|
if s.pow != 1 {
|
|
str += "^" + strconv.Itoa(s.pow)
|
|
}
|
|
}
|
|
return str
|
|
}
|