Skip to content

Commit

Permalink
Implement an 'undef' Units functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
xnx committed Aug 1, 2022
1 parent 612bda9 commit eecd599
Show file tree
Hide file tree
Showing 16 changed files with 787 additions and 585 deletions.
56 changes: 0 additions & 56 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,59 +10,3 @@ transforming physical quantities and their units.
Units are specified as strings using a simple and flexible syntax,
and may be compared, output in different formats and manipulated using a
variety of predefined Python methods.



Installation:
=============

The PyQn package can be installed either from PyPI_ using pip

.. code-block:: bash
python3 -m pip install pyqn
or from the source by running (one of the two) from the project source directory.

.. code-block:: bash
# either
python setup.py install
# or
python3 -m pip install .
Examples:
=========

Units
-----
The units of physical quantities are represented by the ``Units`` class. A
``Units`` object is instantiated from a valid units string and supports ions,
isotopologues, as well as a few special species. This object contains
attributes including the dimensions, HTML and LaTeX representations, and
methods for conversion to different compatible units.

.. code-block:: pycon
>>> from pyqn.units import Units
>>> u1 = Units('km')
>>> u2 = Units('hr')
>>> u3 = u1/u2
>>> print(u3)
km.hr-1
>>> u4 = Units('m/s')
>>> u3.conversion(u4) # OK: can convert from km/hr to m/s
Out[7]: 0.2777777777777778
>>> u3.conversion(u2) # Oops: can't convert from km/hr to m!
...
UnitsError: Failure in units conversion: units km.hr-1[L.T-1] and hr[T] have
different dimensions
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name="pyqn",
version="1.3",
version="1.3.2",
description="A package for managing physical units and quantities",
long_description=long_description,
long_description_content_type="text/x-rst",
Expand Down Expand Up @@ -40,7 +40,7 @@
],
extras_require={"dev": ["black", "pytest-cov", "tox", "ipython"]},
# package_data will include all the resolved globs into both the wheel and sdist
#package_data={},
# package_data={},
# no need for MANIFEST.in, which should be reserved only for build-time files
project_urls={
"Bug Reports": "https://github.com/xnx/pyqn/issues",
Expand Down
54 changes: 29 additions & 25 deletions src/pyqn/atom_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,45 @@
# along with PyQn. If not, see <http://www.gnu.org/licenses/>

import sys
from pyparsing import Word, Group, Literal, Suppress, ParseException, oneOf,\
Optional
from pyparsing import Word, Group, Literal, Suppress, ParseException, oneOf, Optional
from .si import si_prefixes
from .base_unit import BaseUnit, base_unit_stems
from .dimensions import Dimensions

# pyparsing stuff for parsing unit strings:
caps = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lowers = caps.lower()
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0'
digits = '123456789'
exponent = Word(digits + '-')
letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0"
digits = "123456789"
exponent = Word(digits + "-")
prefix = oneOf(list(si_prefixes.keys()))
ustem = Word(letters + 'Å' + 'Ω')
uatom = (Group( '1' | (Optional(prefix) + ustem)) + Optional(exponent))\
| (Group( '1' | ustem) + Optional(exponent))
ustem = Word(letters + "Å" + "Ω")
uatom = (Group("1" | (Optional(prefix) + ustem)) + Optional(exponent)) | (
Group("1" | ustem) + Optional(exponent)
)

# floating point equality and its negation, to some suitable tolerance
def feq(f1, f2, tol=1.e-10):
return abs(f1-f2) <= tol
def fneq(f1, f2, tol=1.e-10):
def feq(f1, f2, tol=1.0e-10):
return abs(f1 - f2) <= tol


def fneq(f1, f2, tol=1.0e-10):
return not feq(f1, f2, tol)


class UnitsError(Exception):
"""
An Exception class for errors that might occur whilst manipulating units.
"""

def __init__(self, error_str):
self.error_str = error_str

def __str__(self):
return self.error_str


class AtomUnit(object):
"""
AtomUnit is a class to represent a single BaseUnit, possibly with an SI
Expand All @@ -64,22 +70,21 @@ class AtomUnit(object):
"""

def __init__(self, prefix, base_unit, exponent=1):
""" Initialize the AtomUnit object. """
"""Initialize the AtomUnit object."""

self.base_unit = base_unit
self.exponent = exponent
self.dims = self.base_unit.dims ** self.exponent
self.dims = self.base_unit.dims**self.exponent

# get the SI prefix (if present), and its 'factor' (10 raised to its
# the power it represents
self.si_fac = 1.
self.si_fac = 1.0
self.prefix = prefix
if prefix is not None:
try:
self.si_prefix = si_prefixes[prefix]
except KeyError:
raise UnitsError('Invalid or unsupported SI prefix: %s'
% prefix)
raise UnitsError("Invalid or unsupported SI prefix: %s" % prefix)
self.si_fac = self.si_prefix.fac
# now calculate the factor relating this AtomUnit to its
# corresponding SI unit:
Expand All @@ -97,7 +102,7 @@ def parse(self, s_unit_atom):
uatom_data = uatom.parseString(s_unit_atom)
except ParseException:
raise
raise UnitsError('Invalid unit atom syntax: %s' % s_unit_atom)
raise UnitsError("Invalid unit atom syntax: %s" % s_unit_atom)

# uatom_data comes back as (([prefix], <stem>), [exponent])
if len(uatom_data[0]) == 1:
Expand All @@ -111,7 +116,7 @@ def parse(self, s_unit_atom):
# properly (ie to mmHg, not to milli-mHg):
if stem not in base_unit_stems:
prefix = None
stem = ''.join(uatom_data[0])
stem = "".join(uatom_data[0])
try:
base_unit = base_unit_stems[stem]
except:
Expand All @@ -124,18 +129,18 @@ def parse(self, s_unit_atom):
return AtomUnit(prefix, base_unit, exponent)

def __pow__(self, power):
""" Return the current AtomUnit raised to a specified power. """
"""Return the current AtomUnit raised to a specified power."""
return AtomUnit(self.prefix, self.base_unit, self.exponent * power)

def __str__(self):
""" String representation of this AtomUnit. """
s = ''
"""String representation of this AtomUnit."""
s = ""
if self.prefix:
s = self.prefix
s_exponent = ''
s_exponent = ""
if self.exponent != 1:
s_exponent = str(self.exponent)
return ''.join([s, str(self.base_unit), s_exponent])
return "".join([s, str(self.base_unit), s_exponent])

def __repr__(self):
return str(self)
Expand All @@ -150,4 +155,3 @@ def prefix_base_eq(self, other):
if self.prefix == other.prefix and self.base_unit == other.base_unit:
return True
return False

Loading

0 comments on commit eecd599

Please sign in to comment.