gonum.org/v1/gonum@v0.14.0/unit/unittype.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  	"bytes"
     9  	"fmt"
    10  	"sort"
    11  	"sync"
    12  	"unicode/utf8"
    13  )
    14  
    15  // Uniter is a type that can be converted to a Unit.
    16  type Uniter interface {
    17  	Unit() *Unit
    18  }
    19  
    20  // Dimension is a type representing an SI base dimension or a distinct
    21  // orthogonal dimension. Non-SI dimensions can be created using the NewDimension
    22  // function, typically within an init function.
    23  type Dimension int
    24  
    25  // NewDimension creates a new orthogonal dimension with the given symbol, and
    26  // returns the value of that dimension. The input symbol must not overlap with
    27  // any of the any of the SI base units or other symbols of common use in SI ("kg",
    28  // "J", etc.), and must not overlap with any other dimensions created by calls
    29  // to NewDimension. The SymbolExists function can check if the symbol exists.
    30  // NewDimension will panic if the input symbol matches an existing symbol.
    31  //
    32  // NewDimension should only be called for unit types that are actually orthogonal
    33  // to the base dimensions defined in this package. See the package-level
    34  // documentation for further explanation.
    35  func NewDimension(symbol string) Dimension {
    36  	defer mu.Unlock()
    37  	mu.Lock()
    38  	_, ok := dimensions[symbol]
    39  	if ok {
    40  		panic("unit: dimension string \"" + symbol + "\" already used")
    41  	}
    42  	d := Dimension(len(symbols))
    43  	symbols = append(symbols, symbol)
    44  	dimensions[symbol] = d
    45  	return d
    46  }
    47  
    48  // String returns the string for the dimension.
    49  func (d Dimension) String() string {
    50  	if d == reserved {
    51  		return "reserved"
    52  	}
    53  	defer mu.RUnlock()
    54  	mu.RLock()
    55  	if int(d) < len(symbols) {
    56  		return symbols[d]
    57  	}
    58  	panic("unit: illegal dimension")
    59  }
    60  
    61  // SymbolExists returns whether the given symbol is already in use.
    62  func SymbolExists(symbol string) bool {
    63  	mu.RLock()
    64  	_, ok := dimensions[symbol]
    65  	mu.RUnlock()
    66  	return ok
    67  }
    68  
    69  const (
    70  	// SI Base Units
    71  	reserved Dimension = iota
    72  	CurrentDim
    73  	LengthDim
    74  	LuminousIntensityDim
    75  	MassDim
    76  	MoleDim
    77  	TemperatureDim
    78  	TimeDim
    79  	// Other common SI Dimensions
    80  	AngleDim // e.g. radians
    81  )
    82  
    83  var (
    84  	// mu protects symbols and dimensions for concurrent use.
    85  	mu      sync.RWMutex
    86  	symbols = []string{
    87  		CurrentDim:           "A",
    88  		LengthDim:            "m",
    89  		LuminousIntensityDim: "cd",
    90  		MassDim:              "kg",
    91  		MoleDim:              "mol",
    92  		TemperatureDim:       "K",
    93  		TimeDim:              "s",
    94  		AngleDim:             "rad",
    95  	}
    96  
    97  	// dimensions guarantees there aren't two identical symbols
    98  	// SI symbol list from http://lamar.colostate.edu/~hillger/basic.htm
    99  	dimensions = map[string]Dimension{
   100  		"A":   CurrentDim,
   101  		"m":   LengthDim,
   102  		"cd":  LuminousIntensityDim,
   103  		"kg":  MassDim,
   104  		"mol": MoleDim,
   105  		"K":   TemperatureDim,
   106  		"s":   TimeDim,
   107  		"rad": AngleDim,
   108  
   109  		// Reserve common SI symbols
   110  		// prefixes
   111  		"Y":  reserved,
   112  		"Z":  reserved,
   113  		"E":  reserved,
   114  		"P":  reserved,
   115  		"T":  reserved,
   116  		"G":  reserved,
   117  		"M":  reserved,
   118  		"k":  reserved,
   119  		"h":  reserved,
   120  		"da": reserved,
   121  		"d":  reserved,
   122  		"c":  reserved,
   123  		"μ":  reserved,
   124  		"n":  reserved,
   125  		"p":  reserved,
   126  		"f":  reserved,
   127  		"a":  reserved,
   128  		"z":  reserved,
   129  		"y":  reserved,
   130  		// SI Derived units with special symbols
   131  		"sr":  reserved,
   132  		"F":   reserved,
   133  		"C":   reserved,
   134  		"S":   reserved,
   135  		"H":   reserved,
   136  		"V":   reserved,
   137  		"Ω":   reserved,
   138  		"J":   reserved,
   139  		"N":   reserved,
   140  		"Hz":  reserved,
   141  		"lx":  reserved,
   142  		"lm":  reserved,
   143  		"Wb":  reserved,
   144  		"W":   reserved,
   145  		"Pa":  reserved,
   146  		"Bq":  reserved,
   147  		"Gy":  reserved,
   148  		"Sv":  reserved,
   149  		"kat": reserved,
   150  		// Units in use with SI
   151  		"ha": reserved,
   152  		"L":  reserved,
   153  		"l":  reserved,
   154  		// Units in Use Temporarily with SI
   155  		"bar": reserved,
   156  		"b":   reserved,
   157  		"Ci":  reserved,
   158  		"R":   reserved,
   159  		"rd":  reserved,
   160  		"rem": reserved,
   161  	}
   162  )
   163  
   164  // Dimensions represent the dimensionality of the unit in powers
   165  // of that dimension. If a key is not present, the power of that
   166  // dimension is zero. Dimensions is used in conjunction with New.
   167  type Dimensions map[Dimension]int
   168  
   169  func (d Dimensions) clone() Dimensions {
   170  	if d == nil {
   171  		return nil
   172  	}
   173  	c := make(Dimensions, len(d))
   174  	for dim, pow := range d {
   175  		if pow != 0 {
   176  			c[dim] = pow
   177  		}
   178  	}
   179  	return c
   180  }
   181  
   182  // matches reports whether the dimensions of d and o match. Zero power
   183  // dimensions in d an o must be removed, otherwise matches may incorrectly
   184  // report a mismatch.
   185  func (d Dimensions) matches(o Dimensions) bool {
   186  	if len(d) != len(o) {
   187  		return false
   188  	}
   189  	for dim, pow := range d {
   190  		if o[dim] != pow {
   191  			return false
   192  		}
   193  	}
   194  	return true
   195  }
   196  
   197  func (d Dimensions) String() string {
   198  	// Map iterates randomly, but print should be in a fixed order. Can't use
   199  	// dimension number, because for user-defined dimension that number may
   200  	// not be fixed from run to run.
   201  	atoms := make(unitPrinters, 0, len(d))
   202  	for dimension, power := range d {
   203  		if power != 0 {
   204  			atoms = append(atoms, atom{dimension, power})
   205  		}
   206  	}
   207  	sort.Sort(atoms)
   208  	var b bytes.Buffer
   209  	for i, a := range atoms {
   210  		if i > 0 {
   211  			b.WriteByte(' ')
   212  		}
   213  		fmt.Fprintf(&b, "%s", a.Dimension)
   214  		if a.pow != 1 {
   215  			fmt.Fprintf(&b, "^%d", a.pow)
   216  		}
   217  	}
   218  
   219  	return b.String()
   220  }
   221  
   222  type atom struct {
   223  	Dimension
   224  	pow int
   225  }
   226  
   227  type unitPrinters []atom
   228  
   229  func (u unitPrinters) Len() int {
   230  	return len(u)
   231  }
   232  
   233  func (u unitPrinters) Less(i, j int) bool {
   234  	// Order first by positive powers, then by name.
   235  	if u[i].pow*u[j].pow < 0 {
   236  		return u[i].pow > 0
   237  	}
   238  	return u[i].String() < u[j].String()
   239  }
   240  
   241  func (u unitPrinters) Swap(i, j int) {
   242  	u[i], u[j] = u[j], u[i]
   243  }
   244  
   245  // Unit represents a dimensional value. The dimensions will typically be in SI
   246  // units, but can also include dimensions created with NewDimension. The Unit type
   247  // is most useful for ensuring dimensional consistency when manipulating types
   248  // with different units, for example, by multiplying an acceleration with a
   249  // mass to get a force. See the package documentation for further explanation.
   250  type Unit struct {
   251  	dimensions Dimensions
   252  	value      float64
   253  }
   254  
   255  // New creates a new variable of type Unit which has the value and dimensions
   256  // specified by the inputs. The built-in dimensions are always in SI units
   257  // (metres, kilograms, etc.).
   258  func New(value float64, d Dimensions) *Unit {
   259  	return &Unit{
   260  		dimensions: d.clone(),
   261  		value:      value,
   262  	}
   263  }
   264  
   265  // DimensionsMatch checks if the dimensions of two Uniters are the same.
   266  func DimensionsMatch(a, b Uniter) bool {
   267  	return a.Unit().dimensions.matches(b.Unit().dimensions)
   268  }
   269  
   270  // Dimensions returns a copy of the dimensions of the unit.
   271  func (u *Unit) Dimensions() Dimensions {
   272  	return u.dimensions.clone()
   273  }
   274  
   275  // Add adds the function argument to the receiver. Panics if the units of
   276  // the receiver and the argument don't match.
   277  func (u *Unit) Add(uniter Uniter) *Unit {
   278  	a := uniter.Unit()
   279  	if !DimensionsMatch(u, a) {
   280  		panic("unit: mismatched dimensions in addition")
   281  	}
   282  	u.value += a.value
   283  	return u
   284  }
   285  
   286  // Unit implements the Uniter interface, returning the receiver. If a
   287  // copy of the receiver is required, use the Copy method.
   288  func (u *Unit) Unit() *Unit {
   289  	return u
   290  }
   291  
   292  // Copy returns a copy of the Unit that can be mutated without the change
   293  // being reflected in the original value.
   294  func (u *Unit) Copy() *Unit {
   295  	return &Unit{
   296  		dimensions: u.dimensions.clone(),
   297  		value:      u.value,
   298  	}
   299  }
   300  
   301  // Mul multiply the receiver by the input changing the dimensions
   302  // of the receiver as appropriate. The input is not changed.
   303  func (u *Unit) Mul(uniter Uniter) *Unit {
   304  	a := uniter.Unit()
   305  	for key, val := range a.dimensions {
   306  		if d := u.dimensions[key]; d == -val {
   307  			delete(u.dimensions, key)
   308  		} else {
   309  			u.dimensions[key] = d + val
   310  		}
   311  	}
   312  	u.value *= a.value
   313  	return u
   314  }
   315  
   316  // Div divides the receiver by the argument changing the
   317  // dimensions of the receiver as appropriate.
   318  func (u *Unit) Div(uniter Uniter) *Unit {
   319  	a := uniter.Unit()
   320  	u.value /= a.value
   321  	for key, val := range a.dimensions {
   322  		if d := u.dimensions[key]; d == val {
   323  			delete(u.dimensions, key)
   324  		} else {
   325  			u.dimensions[key] = d - val
   326  		}
   327  	}
   328  	return u
   329  }
   330  
   331  // Value return the raw value of the unit as a float64. Use of this
   332  // method is, in general, not recommended, though it can be useful
   333  // for printing. Instead, the From method of a specific dimension
   334  // should be used to guarantee dimension consistency.
   335  func (u *Unit) Value() float64 {
   336  	return u.value
   337  }
   338  
   339  // SetValue sets the value of the unit.
   340  func (u *Unit) SetValue(v float64) {
   341  	u.value = v
   342  }
   343  
   344  // Format makes Unit satisfy the fmt.Formatter interface. The unit is formatted
   345  // with dimensions appended. If the power of the dimension is not zero or one,
   346  // symbol^power is appended, if the power is one, just the symbol is appended
   347  // and if the power is zero, nothing is appended. Dimensions are appended
   348  // in order by symbol name with positive powers ahead of negative powers.
   349  func (u *Unit) Format(fs fmt.State, c rune) {
   350  	if u == nil {
   351  		fmt.Fprint(fs, "<nil>")
   352  	}
   353  	switch c {
   354  	case 'v':
   355  		if fs.Flag('#') {
   356  			fmt.Fprintf(fs, "&%#v", *u)
   357  			return
   358  		}
   359  		fallthrough
   360  	case 'e', 'E', 'f', 'F', 'g', 'G':
   361  		p, pOk := fs.Precision()
   362  		w, wOk := fs.Width()
   363  		units := u.dimensions.String()
   364  		switch {
   365  		case pOk && wOk:
   366  			fmt.Fprintf(fs, "%*.*"+string(c), pos(w-utf8.RuneCount([]byte(units))-1), p, u.value)
   367  		case pOk:
   368  			fmt.Fprintf(fs, "%.*"+string(c), p, u.value)
   369  		case wOk:
   370  			fmt.Fprintf(fs, "%*"+string(c), pos(w-utf8.RuneCount([]byte(units))-1), u.value)
   371  		default:
   372  			fmt.Fprintf(fs, "%"+string(c), u.value)
   373  		}
   374  		fmt.Fprintf(fs, " %s", units)
   375  	default:
   376  		fmt.Fprintf(fs, "%%!%c(*Unit=%g)", c, u)
   377  	}
   378  }
   379  
   380  func pos(a int) int {
   381  	if a < 0 {
   382  		return 0
   383  	}
   384  	return a
   385  }