{ "cells": [ { "cell_type": "markdown", "id": "716737c3-0f74-4fc8-ad0f-2b1605ff602d", "metadata": {}, "source": [ "# Background and Approach" ] }, { "cell_type": "markdown", "id": "f67f6c2a-9450-4638-ace6-92da52f38ab6", "metadata": {}, "source": [ "`forallpeople` implements the SI unit system in a single Python class called, `Physical`, as in \"a physical quantity\".\n", "\n", "It is intended for folks who routinely perform calculations within a single, typical, unit context and with limited precision requirements. This is common among engineers working within a single discipline (e.g. electrical, mechanical, or civil engineering, etc.) but may be less common to scientists who may be working between different unit contexts with precisions that may or may not carry uncertainties (other units packages, such as pint, may be more suitable for those applications). \n", "\n", "`forallpeople` assumes the following:\n", "\n", "* Floating point numbers are going to be used for calculation and their precision is generally adequate\n", "* A physical quantity with a given set of dimensions will always represent the same phenomenon\n", " * e.g. One context may be that a `Physical` with `Dimensions(kg=1, m=2, s=-2, A=0, cd=0, K=0, mol=0)` will always represent a \"torque\" (say, in `N*m`) but will never be considered as \"energy\" (say, in `J`). However, another context could consider a `Physical` with the same dimensions always as \"energy\" in joules and never as a \"torque\". `forallpeople` would never consider the dimensions to represent the phenomena of both \"energy\" and \"torque\" at the same time.\n", " * This is consistent with the application of derived units as described in the [SI brochure](https://www.bipm.org/en/publications/si-brochure) (pg. 140, 9th ed.)\n" ] }, { "cell_type": "markdown", "id": "6cb35050-a32e-4a06-9ee4-ff1235c5a183", "metadata": {}, "source": [ "## Data Model" ] }, { "cell_type": "markdown", "id": "fb72b845-f073-4f74-8204-99486d8eb54b", "metadata": {}, "source": [ "A `Physical` instance has the following two _primary_ attributes to model an SI unit:\n", "\n", "1. `.value`: The magnitude of the quantity in unscaled SI base units\n", "2. `.dimensions`: A vector, implemented as a `NamedTuple`, that describes the dimension of the physical quantity within the seven dimensional space where the SI units are defined: kg, m, s, A, cd, K, mol.\n", "\n", "Additionally, it has the following three _secondary_ attributes which affect how the physical quantity is _represented_:\n", "\n", "3. `.precision: int = 3`: The default number of decimal places to display in the physical quantity's representation\n", "4. `.factor: Decimal = Decimal(\"1\")`: An arbitrary factor applied to the `.value` attribute to scale it to another unit system based upon the SI units. This is most commonly used for representing units in the US Customary system.\n", "5. `.prefixed: str = \"\"`: A single-character string representing one of the scaling prefixes used in the SI unit system, e.g. \"k\" for \"kilo\", as in \"kilometer\" or \"u\" for \"micro\" as in \"micronewton\"." ] }, { "cell_type": "markdown", "id": "149792d0-ec46-44ef-b208-5c21c2749237", "metadata": {}, "source": [ "## Unit Representation\n", "\n", "All `Physical` instances model a physical quantity in SI base units. Each `Physical` instance is immutable representing that the physical quantity in real life is also \"immutable\": it simply is as it is and we have the SI units to describe it.\n", "\n", "However, we can change the _representation_ of the physical quantity to whatever we want. `forallpeople` can instantiate a singleton `Environment` which globally affects how the representation of all physical quantities in the namespace are displayed to you, the calculator.\n", "\n", "In this example, a force is calculated. The representation of the resulting force is the default one: an unscaled magnitude in SI base units.\n", "\n", "```python\n", "import forallpeople as si\n", "\n", "G = 9.81 * (si.m/si.s**2)\n", "m = 3200.5*si.kg\n", "force = m*G\n", "print(force) # 318.825 kg·m·s⁻²\n", "```\n", "\n", "The representation of this physical quantity can be altered by loading an environment. This example will use the `'default'` environment which defines the \"derived\" units of the SI unit system (i.e. Pa, N, V, W, etc.).\n", "\n", "```python\n", "import forallpeople as si\n", "\n", "G = 9.81 * (si.m/si.s**2)\n", "m = 3200.5*si.kg\n", "force = m*G\n", "print(force) # 318.825 kg·m·s⁻²\n", "si.environment('default')\n", "print(force) # 318.825 N\n", "```\n", "\n", "The immutable physical quantity of `force` is unchanged. One way to think of the unit environment is as a kind of \"lens\" with which we \"view\" the resulting physical quantity. Depending on the environment that is loaded, the view of the physical quantity will change. \n", "\n", "As an example, let us now load the `'us_customary'` environment.\n", "\n", "```python\n", "import forallpeople as si\n", "\n", "G = 9.81 * (si.m/si.s**2)\n", "m = 3200.5*si.kg\n", "force = m*G\n", "print(force) # 318.825 kg·m·s⁻²\n", "si.environment('default')\n", "print(force) # 318.825 N\n", "si.environment('us_customary')\n", "print(force) # 70573.126 lb\n", "```\n", "\n", "All of these are different representations of the same _immutable_ physical quantity of `318.825 kg·m·s⁻²`." ] }, { "cell_type": "markdown", "id": "269a683c-a288-4314-880c-72c55ad0c521", "metadata": {}, "source": [ "## No dimensionless definitions\n", "\n", "Some unit libraries define dimensionless units such as degree, radian, or steradian.\n", "\n", "`forallpeople` does not allow for the definition of dimensionless units. If a `Physical` ever results in a quantity that has `.dimensions` of `0` in all components then a `float` is returned.\n", "\n", "The intended behaviour is to be similar to that of a hand calculation: when the units cancel out, they cancel out and you are left with a number.\n", "\n", "This makes trigonometry easy and intuitive.\n", "\n", "e.g. \n", "\n", "```python\n", "from math import cos\n", "import forallpeople as si\n", "x1 = 32.3 * si.m\n", "x2 = 49.1 * si.m\n", "print(x1 / x2) # 0.6578411405295315\n", "print(cos(x1 / x2)) # 0.7913140226440856\n", "```\n", "\n", "This approach has the negative consequence of introducing ambiguity between the physical quantities of hertz (cycles per second), angular velocity (radians per second), and angular frequency (radians per second) which all have SI dimensions of `s⁻¹`. The [SI units brochure](https://www.bipm.org/en/publications/si-brochure) recommends \"that the quantities called \"frequency\", \"angular frequency\", and \"angular velocity\" always be given explicit units of Hz or rad/s and not `s⁻¹`.\" However, this can be _partially_ managed within `forallpeople` through the unit environment definitions. See [Customizing Unit Environments](environments.ipynb).\n" ] }, { "cell_type": "markdown", "id": "ff69a600-83b1-4c1c-9553-30a9576863c1", "metadata": {}, "source": [ "## Auto-scaling\n", "\n", "By convention, the [derived](environments.ipynb) quantities are often represented with prefix that scales the quantity to use a minimum number of digits, e.g. `103000.0 N` would be more commonly written as `103.0 kN` and `0.0000000000234 m` would be more commonly written as `23.4 pm`. `forallpeople` automatically represents base units, powers of base units, and recognized derived units with \"auto-scaling\" to follow a \"convention over configuration\" approach. However, a custom prefix can be set on individual instances, see [Assigning Prefixes](basic_usage.ipynb) for more information.\n", "\n", "The following units would be auto-scaled (and would also be eligible for providing an over-riding prefix):\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "forallpeople", "language": "python", "name": "forallpeople" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.4" } }, "nbformat": 4, "nbformat_minor": 5 }