{ "metadata": { "name": "" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "natu Tutorial" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial shows how to use [natu](http://kdavies4.github.io/natu/) to work with physical quantities in [Python](https://www.python.org/)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Table of contents:**\n", "- [Creating and displaying a quantity](#Creating-and-displaying-a-quantity)\n", "- [Conversion factors](#Conversion-factors)\n", "- [Prefixed units](#Prefixed-units)\n", "- [Nonscalar units](#Nonscalar-units)\n", "- [Simplification](#Simplification)\n", "- [Groups of units](#Groups-of-units)\n", "- [Arrays and other data types](#Arrays-and-other-data-types)\n", "- [String formatting](#String-formatting)\n", "- [Changing the display unit of a unit](#Changing-the-display-unit-of-a-unit)\n", "- [Creating units](#Creating-units)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before we get started, we'll establish settings for this IPython notebook." ] }, { "cell_type": "code", "collapsed": false, "input": [ "%matplotlib inline\n", "%precision 4" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 1, "text": [ "u'%.4f'" ] } ], "prompt_number": 1 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Creating and displaying a quantity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we'll import the [metre](http://en.wikipedia.org/wiki/Metre) (m):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.units import m" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's a [scalar unit](http://kdavies4.github.io/natu/natu.types.html#natu.types.ScalarUnit) with a value of 1 (since [SI](http://en.wikipedia.org/wiki/International_System_of_Units) is the default unit system) and dimension of length (`'L'`). Its display unit is itself (`'m'`) and it is prefixable (`prefixable = True`):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "m" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 3, "text": [ "ScalarUnit(1, 'L', 'm', True) (m)" ] } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "When we multiply a number by a unit, we get a [quantity](http://kdavies4.github.io/natu/natu.types.html#natu.types.Quantity):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "length = 0.0254*m\n", "length" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 4, "text": [ "Quantity(0.0254, 'L', 'm') (0.0254 m)" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The value is 0.0254, the dimension is still length (`'L'`), and the display unit is the [metre](http://en.wikipedia.org/wiki/Metre) (`'m'`). The contents of the second parentheses (0.0254 m) is the default string format:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print(length)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "0.0254 m\n" ] } ], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's change the display unit to the [inch](http://en.wikipedia.org/wiki/Inch):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "length.display = 'inch'\n", "length" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 6, "text": [ "Quantity(0.0254, 'L', 'inch') (1.0 inch)" ] } ], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the value did not change, but it will now display as one inch. We could also express the length in inches by dividing it by the inch:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.units import inch\n", "length/inch" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 7, "text": [ "1.0000" ] } ], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nothing all that special has happened here. The value of the `length` quantity was divided by the value of the inch unit, and the dimensions were handled accordingly. The result was dimensionless, so it was returned as a float. Since the result was 1.0, we expect the value of the inch to be 0.0254, and it is: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.units import inch\n", "inch" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 8, "text": [ "ScalarUnit(0.0254, 'L', 'inch', False) (inch)" ] } ], "prompt_number": 8 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Conversion factors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[natu](http://kdavies4.github.io/natu/) doesn't use [conversion factors](https://en.wikipedia.org/wiki/Conversion_factor) internally, but we can use [natu](http://kdavies4.github.io/natu/) to determine them. We would expect to use division:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "inch/m" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 9, "text": [ "0.0254" ] } ], "prompt_number": 9 }, { "cell_type": "markdown", "metadata": {}, "source": [ "but the result may seem counterintuitive at first. We divided the [inch](http://en.wikipedia.org/wiki/Inch) by the [metre](http://en.wikipedia.org/wiki/Metre), but we got the conversion factor from inches to metres. The conversion factor from unit A to unit B is the number of units B in one unit A. Mathematically, this is *x*\\*B = 1\\*A, where *x* is the conversion factor. The solution is *x* = A/B. In this case A is the [inch](http://en.wikipedia.org/wiki/Inch) and B is the [metre](http://en.wikipedia.org/wiki/Metre), so we have the conversion factor from inches to metres (which happens to be the number we used in the previous section)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember that in [natu](http://kdavies4.github.io/natu/) we deal with quantities, not numbers. Numbers are unit-dependent, but quantities are not. A quantity is expressed as the product of a number and a unit (*q* = *n*\\*U). When we say \"in unit,\" we generally mean \"divided by unit.\" So \"quantity in unit\" is *q*/U or *n*, the number." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Prefixed units" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If a unit a prefixable, we can import it with a prefix. For example," ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.units import km\n", "km/m" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 10, "text": [ "1000.0000" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can access units directly from the [units](http://kdavies4.github.io/natu/natu.units.html) module, with or without prefixes:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu import units as U\n", "U.km/U.m" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 11, "text": [ "1000.0000" ] } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, if we use a wildcard import (`from natu.units import *`), we only get the units which are explicitly defined in the [INI files](http://kdavies4.github.io/natu/definitions.html). Typically this does not include prefixed units." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Nonscalar units" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nonscalar units such as [Celsius](http://en.wikipedia.org/wiki/Celsius), [Fahrenheit](http://en.wikipedia.org/wiki/Fahrenheit), and the [decibel](http://en.wikipedia.org/wiki/Decibel) are available: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.units import degC, degF, dB" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 12 }, { "cell_type": "markdown", "metadata": {}, "source": [ "These units are called [lambda units](http://kdavies4.github.io/natu/natu.types.html#natu.types.LambdaUnit) because they involve invertible functions that are not limited to multiplication and division. For convenience, however, [lambda units](http://kdavies4.github.io/natu/natu.types.html#natu.types.LambdaUnit) are overloaded to use the multiplication and division operators (`*` and `/`). The number must always be on the left side of the multiplication operator:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "temperature = 25*degC" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 13 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can convert this temperature to [kelvin](http://en.wikipedia.org/wiki/Kelvin) (a scalar unit):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.units import K\n", "temperature/K" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 14, "text": [ "298.1500" ] } ], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": {}, "source": [ "or to [Fahrenheit](http://en.wikipedia.org/wiki/Fahrenheit) (another [lambda unit](http://kdavies4.github.io/natu/natu.types.html#natu.types.LambdaUnit)):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "temperature/degF" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 15, "text": [ "77.0000" ] } ], "prompt_number": 15 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the quantity (temperature) is on the left side of the operator. This is important." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can add temperatures that have been created from different units:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print(0*degC + 100*K)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "100.0 degC\n" ] } ], "prompt_number": 16 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The display unit of the quantity on the left side of the addition takes precedence. [natu](http://kdavies4.github.io/natu/) checks that the dimensions are compatible when performing arithmetic, so a temperature can only be added to another temperature. Note, however, that temperatures are absolute quantities, regardless of the unit used to express them. The sum of 25 \u2103 and 25 \u2103 is not 50 \u2103, but 323.15 \u2103 (roughly 600 K)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "print(25*degC + 25*degC)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "323.15 degC\n" ] } ], "prompt_number": 17 }, { "cell_type": "markdown", "metadata": {}, "source": [ "To demonstrate the [decibel](http://en.wikipedia.org/wiki/Decibel), we will multiply two numbers by adding their logarithms:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "(10/dB + 10/dB)*dB" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 18, "text": [ "100.0000" ] } ], "prompt_number": 18 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lambda units can also be prefixed if they are marked to allow it (`prefixable = True`). In fact, the [decibel](http://en.wikipedia.org/wiki/Decibel) used above is simply the bel (B) with the [deci (d)](https://en.wikipedia.org/wiki/Deci-) prefix. To retrieve a prefixed unit, simply import it as before. For example," ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.units import mdegC\n", "100*mdegC/K" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 19, "text": [ "273.2500" ] } ], "prompt_number": 19 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Simplification" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, units are simplified using [coherent relations](-7 N A-2 (dimension L M I-2 T-2)\n", "LaTeX format: $1 \\times 10^{-7}\\,\\mathrm{N}\\,\\mathrm{A}^{-2}$ (dimension $\\mathrm{L}\\,\\mathrm{M}\\,\\mathrm{I}^{-2}\\,\\mathrm{T}^{-2}$)\n", "Pretty format: 1\u271510\u207b\u2077 N A\u207b\u00b2 (dimension L M I\u207b\u00b2 T\u207b\u00b2)\n" ] } ], "prompt_number": 25 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The HTML output renders as 1×10-7 N A-2 (dimension L M I-2 T-2), and the LaTeX output renders as $1 \\times 10^{-7}\\,\\mathrm{N}\\,\\mathrm{A}^{-2}$ (dimension $\\mathrm{L}\\,\\mathrm{M}\\,\\mathrm{I}^{-2}\\,\\mathrm{T}^{-2}$). Notice that we explicitly added \\$...\\$ around the LaTeX output because the math mode is required. The pretty format can only be used with integer exponents." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also change the format of the number using Python's built-in formatting. For example," ] }, { "cell_type": "code", "collapsed": false, "input": [ "print(u'Pretty format with 7 decimal places: {0:.7fP}'.format(k_A))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Pretty format with 7 decimal places: 0.0000001 N A\u207b\u00b2\n" ] } ], "prompt_number": 26 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Changing the display unit of a unit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we saw earlier, each unit has a display unit. It defaults to the unit itself, but it can be changed. The display unit is propagated to a [quantity](http://kdavies4.github.io/natu/natu.types.html#natu.types.Quantity) when it is generated from a unit. We can use this feature to create a quantity using one unit that displays in another. For example, to enter length in yards but display it in meters:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.units import yd\n", "yd.display = 'm'\n", "print(1*yd)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "0.9144 m\n" ] } ], "prompt_number": 27 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Besides the display unit, a unit is essentially [immutable](https://en.wikipedia.org/wiki/Immutable_object). Its value is a protected attribute (`_value`) and its dimension and prefixable attributes cannot be changed." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Creating units" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The best way to introduce new units is to swap or add to the [INI files](http://kdavies4.github.io/natu/definitions.html) that define the units. However, we can also create units programmatically." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the new unit is a coherently derived unit, then we can can create it directly from existing units since the product or quotient of two units is generally another unit (as discussed [here](http://kdavies4.github.io/natu/natu.types.html#module-natu.types)). For example, we can create a unit for the [cubic inch](https://en.wikipedia.org/wiki/Cubic_inch) as follows:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "cinch = inch**3\n", "cinch" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 28, "text": [ "ScalarUnit(1.63871e-05, 'L3', 'inch3', False) (inch3)" ] } ], "prompt_number": 28 }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, notice that the display unit is not the new unit but rather inch3:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print(10*cinch)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "10.0 inch3\n" ] } ], "prompt_number": 29 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can update the display unit, but the new unit will not actually be available for display until we insert it into the unit space:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "cinch.display = 'cinch'\n", "from natu import units\n", "units.cinch = cinch" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 30 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The unit will exist in the unit space until the next Python session. Now we get the desired result:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print(10*cinch)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "10.0 cinch\n" ] } ], "prompt_number": 31 }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the expression of the new unit involves a numerical factor (not coherently derived), then the result is a quantity that we need to explicitly cast as a unit:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from natu.types import ScalarUnit\n", "from natu.units import ns\n", "shake = ScalarUnit.fromQuantity(10*ns, 'shake')" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 32 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The display unit has been set, but we still need to insert the unit into the unit space before we can use it:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "units.shake = shake" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 33 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now it is ready for use:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "time = 500*ns\n", "time.display = 'shake'\n", "print(time)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "50.0 shake\n" ] } ], "prompt_number": 34 } ], "metadata": {} } ] }