Package: unitexpr
Unit Expressions For PythonAttaching units to numerical quantities is a convenient way to check if
an expression is valid or an equation is consistent.
For example, it makes little sense to add a quantity
representing weight and one representing distance, or to
add seconds and pico-seconds.The packageunitexprprovides classes and meta-classes that
make it trivial to define custom unit systems andnumpyarrays
with support for physical units.A search onpypishows that there are a few packages available
for doing unit analysis. The most notable I found isscimath,
which supports unit conversion and working with united numpy arrays.
For the purpose of optimizationscimathcomputes and stores unit
expressions in terms of base units.The packageunitexprstores unit expressions in terms of
base unitsandderived units. The advantage is that unit expressions
retain their form. The cost (in terms of computational time) of keeping
track of derived unit terms is of the order of few microsecond, depending
on the complexity of the unit expression. For more details seebenchmarks.For example, the constantm_e*c/h_bar(wherem_eis
the electron mass,cis the velocity of light,h_baris the
reduced Planck constant) is displayed asm_e*c*h_bar**-1.0. In
terms of SI base units the same constant is given by
the less obvious expression:2589605074819.227*m**-1.0.InstallationTo install the packageunitexpruse the command:$pipinstallunitexprUsageThe following sections demonstrate how to createunit expressions,
work withquantity arrays,
definescalar quantities,
and constructcustom unit systems.1. Unit ExpressionsUnit expressions are objects with base classUnitExprBase.
Each unit system defines auniqueunit expression type
that is available as a class attribute
(.expr_type). Valid unit expressiontermsfor a given unit system are:base units,derived units,unit expressions, andreal numbers.The package includes two predefined unit systems with a comprehensive list of
derived units and physical constants:unitexpr.si_units: SI Units based on meter, second, kilogram,
Ampere, Kelvin, mol, and candela,unitexpr.sc_units: Semiconductor Units based on nanometer, picosecond,
electron mass, Ampere, Kelvin, mol, and candela.fromunitexpr.si_unitsimportm,s,c,SiUnit# Accessing the unit expression type of the units system:SiUnitExpr=SiUnit.expr_typeasserttype(m/s)==SiUnitExpr# Examples of unit expressions:v=10.0*m/sw=v+20.0*v# When adding or subtracting units and unit expression the term on the left# side determines the form of the expression. This is best shown in the example# below.## Note: c is defined as:# c = SiUnit('c', 'speed of light', 'velocity', expr=299792458*m/s)# Defining a derived unit:c_sound=SiUnit('c_sound','speed of sound','velocity',expr=343*m/s)v1=c+c_soundv2=c_sound+cassertv1==v2print(v1)# Prints:  1.0000011441248464*cprint(v2)# Prints:  874031.4897959183*c_soundTip: The methodsproportional_toandscaling_factorcan be used to
determined if a unit or unit expression is a scaled
version of another unit or unit expression:fromunitexpr.si_unitsimportm,s,SiUnit# Define a derived unitcm=SiUnit('cm',name='centimeter',quantity='length',expr=m/100.0)# Check if units are proportionalassertcm.proportional_to(m)==Trueassertcm.proportional_to(s)==False# Get the scaling factor that converts cm to m.assertcm.scaling_factor(m)==100.0# Get the scaling factor that converts m to cm.assertm.scaling_factor(cm)==0.01# Get the scaling factor that converts m to s.assertm.scaling_factor(s)==None2. Quantity ArraysTo support scientific calculation
the package includesqarrayan extension of numpy'sndarray.The entries of aqarrayrepresent
the value of a physicalquantitythat can be expressed in terms of a
numerical value and a unit.  The constructor ofqarrayaccepts the same parameters as the constructor ofndarraywith
the additional optional parametersunit(default value 1.0).
andinfowhich can be used to store object documentation.To construct aqarrayfrom a numerical value or
an existing array one can use the convenience functionquantityor the class methodqarray.from_input.frommathimportpifromunitexprimportqarrayfromunitexpr.si_unitsimportm,s,h_bar,m_e,c,SiUnit# Constructing a qarray with a given shape.q=qarray(shape=(2,2))q.fill(10.0)print("q = ")print(q)print()a=q*mprint("a = q*m = ")print(a)print()# Constructing a qarray from another array.b=qarray.from_input(q,unit=s)# Using the convenience method quantity.b=quantity(q,unit=s)b.fill(2.0)print("b =")print(b)print()print("a / b =")print(a/b)print()print("(a / b)**2 =")print((a/b)**2)print()Pi=SiUnit("Pi","Pi","circle constant",pi*SiUnit.expr_type.one)print("Pi*a*9.81*m/s**2 =")print(Pi*a*9.81*m/s**2)Running the script above produces the following output:Click to show the console output.(unitexpr)$pythonexample/qarray_example.pyq =[[10. 10.][10. 10.]] unit: 1.0a = q*m =[[10. 10.][10. 10.]] unit: mb =[[2. 2.][2. 2.]] unit: sa / b =[[5. 5.][5. 5.]] unit: m*s**-1.0(a / b)**2 =[[25. 25.][25. 25.]] unit: m**2.0*s**-2.0Pi*a*9.81*m/s**2 =[[98.1 98.1][98.1 98.1]] unit: Pi*m**2.0*s**-2.0Tip: United arrays can be multiplied with unit expressions.
Any numerical factor will be multiplied with the array using scalar
multiplication. The remaining part of the unit expression will be
multiplied with the unit attribute of the array.United array can be added to unit expressions as long as the
base units match.To retain a numerical factor, for examplepias term of the
unit expression it must be declared as a unit (see the example
above).Note: Units and unit expressions with zero magnitude
maynotbe assigned as the unit attribute of qarrays (
normalization will fail with aDivisionByZeroerror).3. Scalar QuantitiesTo represent ascalarquantity one can use a zero-dimensionalqarray.
The functionquanityprovides a convenient way to create scalar quantities.fromunitexprimportquanityfromunitexpr.sc_unitsimportps,nmdt=quantity(5.0,unit=ps,info='Time-integration step size.')cavity_length=quantity(1.25e6,unit=nm,info='Optical cavity length.')# Accessing the quantity value:print(dt.item())# Prints: 5.0print(dt)# Prints: 5.0 psprint(dt.__repr__())# qarray(5.0, unit=ps, info='Time-integration step size.')# quantity expressions:print(dt*cavity_length)# Prints: 6250000.0 ps*nmTip:  Quantities can be used together with (compatible) units to form
mathematical expressions.Custom Unit SystemsDefining custom unit systems using the packageunitexpris a simple task consisting of two steps:
definingbase unit symbolsand
defining theunit systemby sub-classingUnitBase.1. Defining Base Unit SymbolsIn order to define a unit system, one must first specify the
base unit symbols. In the context of this package this is done
by constructing a tuple with entries of typeUnitSymbol(an immutable class with
instance attributes:symbol,name, andquantity):fromunitexprimportUnitSymbol# Defining unit symbolsunit_symbols=(UnitSymbol(symbol='m',name='meter',quantity='length'),UnitSymbol(symbol='s',name='second',quantity='time'),UnitSymbol(symbol='kg',name='kilogram',quantity='weight'))Note: The attributesymbolmust be a valid Python identifier.2. Defining a Unit SystemA custom unit system can be defined by sub-classingUnitBasespecifying the meta-classUnitMetaand the
custom base unit symbols as class constructor parameters:fromunitexprimportUnitBase,UnitMeta# Defining a unit system using the base unit symbols specified above.# Note the use of the metaclass `UnitMeta`.classMetricUnit(UnitBase,metaclass=UnitMeta,unit_symbols=unit_symbols):pass# Base units are now available as class attributes.# For example:m=MetricUnit.ms=MetricUnit.skg=MetricUnit.kgasserttype(m)==MetricUnit# Declaring derived unitsc=MetricUnit('c','speed of light','velocity',expr=299792458*m/s)The base units are constructed during the instantiation of the meta-class
and are available as class attributes. In the example above the
base units arem,s, andkg.Derived units and unit expressions can be constructed using the operations:multiplication:J = MetricUnit('J', 'joule', 'energy', expr=N*m)division:W = MetricUnit('W', 'watt', 'power', expr=J/s)scalar multiplication:c = MetricUnit('c', 'speed of light', 'velocity', expr=299792458*m/s)exponentiation:N = MetricUnit('N', 'newton', 'force', expr=kg*m*s**-2).It is advisable to choose the unit variable name as the unit symbol. For example,
the constantc(defined above) represents
the speed of light and its unit symbol was set to 'c'.Note: Units and unit expressions extend Python'snamedtupleand as such are immutable.Features and bugsPlease file feature requests and bugs at theissue tracker.
Contributions are welcome.