Files
gonum/unittype.go

193 lines
5.1 KiB
Go

package unit
import "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 called.
type Dimension int
const (
// SI Base Units
CurrentDim Dimension = iota
LengthDim
LuminosityDim
MassDim
TemperatureDim
TimeDim
// Start of other SI Units
AngleDim // e.g. radians
lastPackageDimensionDim // Used in create dimension
)
// 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
//TODO: Should there be some number reserved? We don't want users ever using integer literals
var lastCreatedDimension Dimension = 64 // Reserve first 63 for our use
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.
// NewDimension should only be called for unit types that are orthogonal
// to the base dimensions defined in this package. For example, one unit
// that comes up in blood work is "White blood cells per microscope slide".
// NewDimension is appropriate for "White blood cells", as they are not
// representable in SI base units. However, NewDimension is not appropriate
// for "Slide", as slide is really a unit of area. Slide should instead be
// defined as a constant of type unit.Area
func NewDimension() Dimension {
newUnitMutex.Lock()
defer newUnitMutex.Unlock()
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
type Unit struct {
dimensions map[Dimension]int // Map for custom dimensions
value float64
current int
length int
luminosity int
mass int
temperature int
time int
chemamt int
}
//blah
// 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{length: 1, time: -2})
func NewUnit(value float64, d Dimensions) *Unit {
// TODO: Find most efficient way of doing this
u := &Unit{
current: d[Current],
length: d[Length],
luminosity: d[Luminosity],
mass: d[Mass],
temperature: d[Temperature],
time: d[Time],
chemamt: d[Chemamt],
value: value,
}
// Note that this means all of the keys in u.dimension
// are custom dimensions
for key, val := range d {
if key < lastPackageDimension {
u.dimensions[key] = val
}
}
return u
}
// DimensionsMatch checks if the dimensions of two Uniters are the same
func DimensionsMatch(aU, bU Uniter) bool {
a := aU.Unit()
b := bU.Unit()
if a.length != b.length {
return false
}
if a.time != b.time {
return false
}
if a.mass != b.mass {
return false
}
if a.current != b.current {
return false
}
if a.temperature != b.temperature {
return false
}
if a.luminosity != b.luminosity {
return false
}
if a.chemamt != b.chemamt {
return false
}
if len(a.dimensions) != len(b.dimensions) {
return false
}
for key, val := range a.dimensions {
if b.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(aU Uniter) *Unit {
a := aU.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 allows unit to satisfy the uniter interface
func (u *Unit) Unit() *Unit {
return u
}
// Mul multiply the receiver by the unit changing the dimensions
// of the receiver as appropriate
func (u *Unit) Mul(aU Uniter) *Unit {
a := aU.Unit()
u.length += a.length
u.time += a.time
u.mass += a.mass
u.current += a.current
u.temperature += a.temperature
u.luminosity += a.luminosity
u.value *= a.value
for key, val := range a.dimensions {
u.dimensions[key] += val
}
return u
}
// Div divides the receiver by the argument changing the
// dimensions of the receiver as appropriate
func (u *Unit) Div(aU Uniter) *Unit {
a := aU.Unit()
u.length -= a.length
u.time -= a.time
u.mass -= a.mass
u.current -= a.current
u.temperature -= a.temperature
u.luminosity -= a.luminosity
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 not recommended, instead it is recommended to use a
// FromUnit type of a specific dimension
func (u *Unit) Value() float64 {
return u.value
}