Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
444 changes: 444 additions & 0 deletions fastunits/__init__.py

Large diffs are not rendered by default.

112 changes: 112 additions & 0 deletions fastunits/test_fast_units.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/python

import unittest
import fastunits as U
from fastunits import Value, Unit, Complex, ValueArray, UnitMismatchError
import numpy as np

class FastUnitsTests(unittest.TestCase):
def testConstruction(self):
x = 2*Unit('')
y = Value(5, 'ns')
self.assertIsInstance(x, Value)
self.assertIsInstance(y, Value)
self.assertTrue(x.isDimensionless())
self.assertIsInstance(3j*x, Complex)
self.assertIsInstance(np.arange(5)*U.ns, ValueArray)

def testDimensionless(self):
"""Test that dimensionless values act like floats"""
x = Value(1.5, '')
y = Value(1.5, 'us/ns')
self.assertEqual(x, 1.5)
self.assertEqual(np.ceil(x), 2.)
self.assertEqual(np.floor(x), 1.)
self.assertEqual(y, 1500.)

def testValueArraySlicing(self):
x = np.arange(5)*U.ns
self.assertTrue(np.allclose(x[::2]['ns'], np.array([0., 2., 4.])))

def testAddition(self):
n = Value(2, '')
x = Value(1.0, U.kilometer);
y = Value(3, 'meter');
a = Value(20, 's');
self.assertEqual(x + y, Value(1003, 'meter'))
self.assertNotEqual(x, y)
self.assertNotEqual(x, a)
with self.assertRaises(UnitMismatchError):
_ = y + a
with self.assertRaises(UnitMismatchError):
_ = x + 3.0
_ = x + y
self.assertEqual(x-y, Value(997, 'm'))
self.assertIsInstance(x*1j + y, Complex)
self.assertEqual(n+1, 3)

def testMultiplication(self):
n = Value(2, '')
x = Value(1.0+2j, U.meter)
y = Value(3, U.mm)
z = np.arange(5)*U.ns
a = Value(20, U.second)
self.assertEqual(a*x, x*a)
self.assertTrue((x/y).isDimensionless())
self.assertTrue((z/U.ns).isDimensionless())
self.assertIsInstance(z/U.ns, ValueArray)
self.assertTrue(np.allclose(z*Value(5, 'GHz'), z['ns']*5))

def testPower(self):
x = 2*U.mm
y = 4*U.mm
z = (x*y)**.5
self.assertLess(abs(z**2- Value(8, 'mm^2')), Value(1e-6, U.mm**2))

def testStringification(self):
x = Value(4, U.mm)
self.assertEqual(repr(x), 'Value(4.0, "mm")')
self.assertEqual(str(x), '4.0 mm');

def testDivmod(self):
x = 4.001*U.us
self.assertEquals(x//(4*U.ns), 1000)
self.assertEquals(x % (4*U.ns), x - 1000*4*U.ns)
q,r = divmod(x, 2*U.ns)
self.assertEquals(q, 2000)
self.assertEquals(r, x - q*2*U.ns)

def testConversion(self):
x = Value(3, 'm')
self.assertEquals(x['mm'], 3000.0)
with self.assertRaises(UnitMismatchError):
x['s']
y = Value(1000, 'Mg')
self.assertEquals(y.inBaseUnits().value, 1000000.0)
self.assertEquals(x.inUnitsOf('mm'), 3000*U.mm)

def testFloatRatio(self):
# Years always use floating point ratio
x = Value(1, 'yr')
self.assertIsInstance(x.numer, float)
self.assertEquals(x.denom, 0)

# a mile is exactly 16093440 * 10^-4 meters, this makes mile^3 overflow and forces floating point representation.
x = Value(1, 'mile')
y = x**3

self.assertIsInstance(x.numer, (int, long))
self.assertNotEquals(x.denom, 0)
self.assertIsInstance(y.numer, float)
self.assertEquals(y.denom, 0)

def testHash(self):
x = Value(3, 'ks')
y = Value(3000, 's')
self.assertEquals(hash(x), hash(y))
z = Value(3.1, '')
self.assertEquals(hash(z), hash(3.1))
hash(Value(4j, 'V'))

if __name__ == "__main__":
unittest.main()
19 changes: 19 additions & 0 deletions fastunits/test_unit_array.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/python
import unittest
import unitarray

class UnitsArrayTests(unittest.TestCase):
def testConstruction(self):
x = unitarray.UnitArray('km')
y = unitarray.UnitArray('m')
self.assertEquals(repr(x/y), 'UnitArray("km/m")')

def perf_unit_array(N=10000):
x = unitarray.UnitArray('km')
y = unitarray.UnitArray('m')
for j in range(N):
z = x*y

if __name__ == "__main__":
unittest.main()

32 changes: 32 additions & 0 deletions fastunits/unit_grammar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env python

from pyparsing import (Word, Literal, Group,
Forward, Optional, alphas, nums, alphanums, stringEnd)

toInt = lambda s, l, t: [int(t[0])]

number = Word(nums).setParseAction(toInt)
name = Word(alphas + '%"\'\xE6\xF8', alphanums + '%"\'\xE6\xF8') # degree and mu

power = Literal('^').suppress()
times = Literal('*').suppress()
divide = Literal('/').suppress()
minus = Literal('-').setResultsName('neg')
one = Literal('1').suppress()
lparen = Literal('(').suppress()
rparen = Literal(')').suppress()
exponent = Optional(minus) + number.setResultsName('num') ^ \
Optional(minus) + number.setResultsName('num') + divide \
+ number.setResultsName('denom')

single_unit = name.setResultsName('name') + Optional(power + exponent)
bare_unit = Group(single_unit).setResultsName('posexp', listAllMatches=True)
num_unit = Group(times + single_unit).setResultsName('posexp', listAllMatches=True)
denom_unit = Group(divide + single_unit).setResultsName('negexp', listAllMatches=True)

later_units = Forward()
later_units << (num_unit + Optional(later_units) |
denom_unit + Optional(later_units))

unit = Forward()
unit << ((bare_unit | one + denom_unit) + Optional(later_units) + stringEnd)
Loading