Code Monkey home page Code Monkey logo

numericalunits's Introduction

numericalunits: Units and dimensional analysis compatible with everything

Package homepage at PyPI -- Source code at github -- Written by Steve Byrnes

This package implements units and dimensional analysis in an unconventional way that has the following unique advantages:

  • Compatible with everything: Compatible with virtually any numerical calculation routine, including numpy and scipy, and even including routines not written in Python! That means, for example, if you have a decades-old closed-source C routine for numerical integration, you can pass it a quantity with units of velocity and an integration range with units of time, and the final answer will magically have units of distance. This extreme compatibility is possible because if the variable x represents a quantity with dimensions (like "3.3 kg"), x is actually stored internally as an ordinary floating-point number. The dimension is encoded in the value as a multiplicative factor. When two numbers are multiplied, their dimensions are automatically multiplied, and so on.
  • Modular and non-intrusive: When you input data, you say what units they are in. When you display results, you say what units you want to display them in. These steps are very little trouble, and in fact help you create nice, self-documenting code. Other than that, you have to do nothing at all to pass dimensionful quantities into and out of any already-written programs or routines.
  • Powerful tool for debugging: Not all calculation mistakes cause violations of dimensional analysis, but most do--for example, if you accidentally multiply two lengths instead of adding them, the result will have the wrong dimension. If you use this package, it will alert you to these sorts of mistakes.
  • Zero storage overhead
  • Zero calculation overhead

These great features come with the disadvantage that the interface is less slick than other unit packages. If you have a quantity with units, you cannot directly see what the units are. You are supposed to already know what the units are, and then the package will tell you whether you made a mistake. Even worse, you only get alerted to the mistake after running a calculation all the way through twice.

Therefore the package is not suggested for students exploring how units work. It is suggested for engineering and science professionals who want to make their code more self-documenting and self-debugging.

Installation

You can install from PyPI:

pip install numericalunits

Alternatively---since it's a single module that requires no setup or compilation---you can download numericalunits.py from PyPI or github and use it directly.

Usage and examples

To assign a unit to a quantity, multiply by the unit, e.g. my_length = 100 * mm. (In normal text you would write "100 mm", but unfortunately Python does not have "implied multiplication".)

To express a dimensionful quantity in a certain unit, divide by that unit, e.g. when you see my_length / cm, you pronounce it "my_length expressed in cm".

Unit errors, like trying to add a length to a mass, will not immediately announce themselves as unit errors. Instead, you need to run the whole calculation twice (in a new Python session each time). If you get the same final answers both times, then congratulations, all your calculations are almost guaranteed to pass dimensional analysis! If you get different answers every time you run, then you made a unit error! It is up to you to figure out where and what the error is.

Example 1: What is 5 mL expressed in cubic nanometers?:

from numericalunits import mL, nm
x = 5 * mL  # "Read: x is equal to 5 milliliters"
print(x / nm**3)   # "Read: x expressed in cubic nanometers is..." --> 5e21

Example 2: An electron is in a 1e5 V/cm electric field. What is its acceleration? (Express the answer in m/s².)

from numericalunits import V, cm, e, me, m, s
Efield = 1e5 * (V / cm)
force = e * Efield # (e is the elementary charge)
accel = force / me # (me is the electron mass)
print(accel / (m / s**2)) # Answer --> 1.7588e18

Example 3: You measured a voltage as a function of the position of dial: 10 volts when the dial is at 1cm, 11 volts when the dial is at 2cm, etc. etc. Interpolate from this data to get the expected voltage when the dial is at 41mm, and express the answer in mV.

from numericalunits import cm, V, mm, mV
from numpy import array
from scipy.interpolate import interp1d
voltage_data = array([[1 * cm, 10 * V],
                      [2 * cm, 11 * V],
                      [3 * cm, 13 * V],
                      [4 * cm, 16 * V],
                      [5 * cm, 18 * V]])
f = interp1d(voltage_data[:,0], voltage_data[:,1])
print(f(41 * mm) / mV) # Answer --> 16200

Example 4: A unit mistake ... what is 1 cm expressed in atmospheres?

from numericalunits import cm, atm
print((1 * cm) / atm) # --> a randomly-varying number
# The answer randomly varies every time you run this (in a new Python
# session), indicating that you are violating dimensional analysis.

How it works

A complete set of independent base units (meters, kilograms, seconds, coulombs, kelvins) are defined as randomly-chosen positive floating-point numbers. All other units and constants are defined in terms of those. In a dimensionally-correct calculation, the units all cancel out, so the final answer is deterministic, not random. In a dimensionally-incorrect calculations, there will be random factors causing a randomly-varying final answer.

Included units and constants

Includes a variety of common units, both SI and non-SI, everything from frequency to magnetic flux. Also includes common physical constants like Planck's constant and the speed of light. Browse the source code to see a complete list. It is very easy to add in any extra units and constants that were left out.

Notes

Notes on implementation and use

  • What does it mean to "run the calculation again in a new Python session?" You know that you've started a new Python session if all the variable definitions have been forgotten. Three examples: In Spyder, each "Console" tab is its own session. In Jupyter, make a new Python session by selecting "Restart kernel". From the command line, each time you type python blah.py, you are opening a new Python session.
  • For little, self-contained calculations (a few lines that are all within a single module), it is possible to check the units without opening a new Python session: Run the function numericalunits.reset_units() at the beginning of the calculation before any variables are defined; then check for dimensional errors by re-running the whole calculation (including the reset_units() part). Note that if you are using from-style imports, like from numericalunits import cm, you need to put them after reset_units() in the code.
  • While debugging a program, it may be annoying to have intermediate values in the calculation that randomly vary every time you run the program. In this case, you can use reset_units('SI') instead of the normal reset_units(). This puts all dimensionful variables in standard (MKS) SI units: All times are in seconds, all lengths are in meters, all forces are in newtons, etc. Alternatively, reset_units(123) uses 123 as the seed for the random-number generator. Obviously, in these modes, you will not get any indication of dimensional-analysis errors. As above, if you are going to use any version of reset_units(), make sure you do it before any dimensionful variable is defined in any module.
  • If you have a quantity you want to plot, store, pass between different parallel processes, etc., make sure you first express it in a known unit. (e.g. "energy expressed in joules" can be passed between processses, but "energy" cannot.) For parallel processing in particular, see README appendix for different ways of using the package, with example code.
  • There are very rare, strange cases where the final answer does not seem to randomly vary even though there was a dimensional-analysis violation: For example, the expression (1 + 1e-50 * cm / atm) fails dimensional analysis, so if you calculate it the answer is randomly-varying. But, it is only randomly varying around the 50th decimal point, so the variation is hidden from view. You would not notice it as an error.
  • Since units are normal Python float-type numbers, they follow the normal casting rules. For example, 2 * cm is a python float, not an int. This is usually what you would want and expect.
  • You can give a dimension to complex numbers in the same way as real numbers--for example (2.1e3 + 3.9e4j) * ohm.
  • Requires Python 3. (For Python 2 compatibility, install numericalunits version 1.23 or earlier.)
  • If you find bugs, please tell me by email or github issue board.
  • If you get overflows or underflows, you can edit the unit initializations. For example, the package sets the meter to a random numerical value between 0.1 and 10. Therefore, if you're doing molecular simulation, most lengths you use will be tiny numbers. You should probably set the meter instead to be between, say, a random numerical value between 1e8 and 1e10.
  • Some numerical routines use a default absolute tolerance, rather than relative tolerance, to decide convergence. This can cause the calculation result to randomly vary even though there is no dimensional analysis error. When this happens, you should set the absolute tolerance to a value with the appropriate units. Alternatively, you can scale the data before running the algorithm and scale it back afterwards. Maybe this sounds like a hassle, but it's actually a benefit: If your final result is very sensitive to some numerical tolerance setting, then you really want to be aware of that.

Notes on unit definitions

  • For electromagnetism, all units are intended for use in SI formulas. If you plug them into cgs-gaussian electromagnetism formulas, or cgs-esu electromagnetism formulas, etc., you will get nonsense results.

  • The package does not keep track of "radians" as an independent unit assigned a random number. The reason is that the "radians" factor does not always neatly cancel out of formulas.

  • The package does not keep track of "moles" as an independent unit assigned a random number; instead mol is just a pure number (~6e23), like you would say "dozen"=12. That means: (1) gram/mol is exactly the same as amu, and Boltzmann constant is exactly the same as the ideal gas constant, and so on. (2) You should rarely need to use Avogadro's number NA -- it is just a synonym of mol (NA = mol ~ 6e23). Here are a few examples using moles:

    from numericalunits import um, uM, kcal, mol, fmol, J
    
    # There are eight copies of a protein inside a yeast nucleus of volume
    # 3 cubic microns. What is the concentration of the protein, in micromolar (uM)?
    print((8 / (3 * um**3)) / uM)   # Answer --> 0.0044
    
    # 5 kcal / mol is how many joules?
    print((5 * kcal / mol) / J)   # Answer --> 3.47e-20
    
    # How many molecules are in 2.3 femtomoles?
    print(2.3 * fmol)   # Answer --> 1.39e9
    
  • The package cannot convert temperatures between Fahrenheit, Celsius, and kelvin. The reason is that these scales have different zeros, so the units cannot be treated as multiplicative factors. It is, however, possible to convert temperature intervals, via the units degCinterval (which is a synonym of kelvin, K) and degFinterval.

numericalunits's People

Contributors

jfeist avatar pulkin avatar sbyrnes321 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

numericalunits's Issues

weird interaction with native round() function

I am doing some calculations using numericalunits, and these work OK, but if I try to round() the results, I get 0.0 as a result. Is this a known bug?

I can reproduce this both in a local environment (python v3.9.7) and in Google Colab (Python 3.9.16).

!pip install numericalunits

Output:

Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting numericalunits
  Downloading numericalunits-1.25-py3-none-any.whl (13 kB)
Installing collected packages: numericalunits
Successfully installed numericalunits-1.25
from numericalunits import mg, ng, pg, uL, mL

cf = 10 * (ng/uL)
vf = 10 * (uL)
ci = 0.5375 * (mg/uL)

vi = ((cf * vf) / ci)
print(vi, "uL")

vi = round(((cf * vf) / ci),3)
print(vi, "uL")

Output is:

1.5168342577649152e-10 uL
0.0 uL

If I change the units in the input, still wrong:

cf = 10 * (ng/uL)
vf = 10 * (uL)
ci = 537.5 * (ng/uL)

vi = ((cf * vf) / ci)
print(vi, "uL")

vi = round(((cf * vf) / ci),3)
print(vi, "uL")

Output now is:

1.5168342577649152e-07 uL
0.0 uL

Add rpm definition

Hi

I like your package and it would be great if you could add the definition for rpm (rounds per minute):

rpm = 2*pi/minute

Thanks,
Jonas

Problem converting pressure

Hi, this is very nice project!

I was trying it out and I got the following:

In [1]: from numericalunits import m, kg, Pa, atm

In [2]: p = 1 * atm

In [3]: p / Pa
Out[3]: 101325.0

In [4]: p / (kg/m**2)
Out[4]: 3086314.0471400716

Should not the last number be ~1033?

Missing unit for radiation dose

Hi, thanks for the great package.

I would like to add the unit for radiation dose deposited in material.

The name of the unit is Gray, its symbol is Gy:
1 Gy = 1 J/Kg.

Greater range for random values?

I'd love to see a considerably wider range of values so that an erroneous plot will be more likely to look entirely bogus. Say, better 1e10 and 1e-10?

Documentation

Could you provide some documentation, that is which hotkeys match with which unit? (Maybe in the Wiki)
E.g.: If you want 1 nanometer you set 1*u.nm but for other units Ångstrom or so it is less obvious.

Random units if not explitly set.

Thank you very much for providing this package. Everything workes fine and it is super easy to use. What I realized recently though is that if you do not set u.reset_units('SI') explicitly it seems that the set of units are chosen randomly. At least I always got different results for the same calculation.
Maybe a default unit would be practical.

Add Decibel and related units

I miss the units related to the dB ratio like dBm, dBW, dBV, dBA and so on. Does implementing that would work with the current concept?

SyntaxError: invalid syntax

Need help!
mac version: 10.15.1.
Typed: pip install numericalunits
Got error:
ERROR: Command errored out with exit status 1:
command: /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/fm/9zsn7zqs7dl9b24yx2ygn0140000gn/T/pip-install-rJ80y0/numericalunits/setup.py'"'"'; file='"'"'/private/var/folders/fm/9zsn7zqs7dl9b24yx2ygn0140000gn/T/pip-install-rJ80y0/numericalunits/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(file);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, file, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/fm/9zsn7zqs7dl9b24yx2ygn0140000gn/T/pip-install-rJ80y0/numericalunits/pip-egg-info
cwd: /private/var/folders/fm/9zsn7zqs7dl9b24yx2ygn0140000gn/T/pip-install-rJ80y0/numericalunits/
Complete output (8 lines):
Traceback (most recent call last):
File "", line 1, in
File "/private/var/folders/fm/9zsn7zqs7dl9b24yx2ygn0140000gn/T/pip-install-rJ80y0/numericalunits/setup.py", line 5, in
from numericalunits import version
File "numericalunits.py", line 26
cm = mm = um = nm = pm = fm = km = angstrom = Å = lightyear =
^
SyntaxError: invalid syntax
----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output

Install error with Python 3.3

Hi. I got this. If you need more information just ask me.

$ pip install numericalunits
Downloading/unpacking numericalunits
  Downloading numericalunits-1.11.tar.gz
  Running setup.py egg_info for package numericalunits
    Traceback (most recent call last):
      File "<string>", line 16, in <module>
      File "/home/ale/Programs/my-python3-env/build/numericalunits/setup.py", line 1
        # -*- coding: utf-8 -*-
          ^
    SyntaxError: invalid character in identifier
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 16, in <module>

  File "/home/ale/Programs/my-python3-env/build/numericalunits/setup.py", line 1

    # -*- coding: utf-8 -*-

      ^

SyntaxError: invalid character in identifier

----------------------------------------
Command python setup.py egg_info failed with error code 1 in /home/ale/Programs/my-python3-env/build/numericalunits
Storing complete log in /home/ale/.pip/pip.log

Packaging

Hiya, this package is part of the openSUSE collection at https://build.opensuse.org/package/show/devel:languages:python:numeric/python-numericalunits

However currently it can only use the wheel uploaded to PyPI, which is not according to the packaging guidelines at https://en.opensuse.org/openSUSE:Packaging_Python

Would you be able to upload a 'sdist' to PyPI?

Also useful is creating GitHub tags for each release at https://github.com/sbyrnes321/numericalunits/releases , so those tarballs can also be used by packaging systems.

Finally, while I am here, has any thought been given to creating a basic smoke test suite? These are useful for vendors even if they are very basic, as they allow checking compatibility with new python runtimes as they become available.

Add angle unit

I'm wondering if there is any plan for adding angle as a unit of a quantity and be able to convert degree to radian?

add horsepower

It would be great if you had horsepower as unit of power
745.69987158227022 W to 1 hp
This is what wikipedia calls mechanical horsepower.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.