diff --git a/packages/units/LICENSE b/packages/units/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/units/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/units/README.md b/packages/units/README.md new file mode 100644 index 0000000000..35d0a3d8b8 --- /dev/null +++ b/packages/units/README.md @@ -0,0 +1,409 @@ + + +# ![@thi.ng/units](https://media.thi.ng/umbrella/banners-20220914/thing-units.svg?576826a0) + +[![npm version](https://img.shields.io/npm/v/@thi.ng/units.svg)](https://www.npmjs.com/package/@thi.ng/units) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/units.svg) +[![Mastodon Follow](https://img.shields.io/mastodon/follow/109331703950160316?domain=https%3A%2F%2Fmastodon.thi.ng&style=social)](https://mastodon.thi.ng/@toxi) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +- [About](#about) + - [Unit definitions](#unit-definitions) + - [Predefined units](#predefined-units) + - [Acceleration](#acceleration) + - [Angle](#angle) + - [Area](#area) + - [Data](#data) + - [Electric current](#electric-current) + - [Energy](#energy) + - [Force](#force) + - [Frequency](#frequency) + - [Length](#length) + - [Luminous intensity](#luminous-intensity) + - [Mass](#mass) + - [Power](#power) + - [Pressure](#pressure) + - [Speed](#speed) + - [Substance](#substance) + - [Temperature](#temperature) + - [Time](#time) + - [Volume](#volume) + - [Creating & deriving units](#creating--deriving-units) + - [Using standard metric prefixes](#using-standard-metric-prefixes) + - [Unit conversions](#unit-conversions) +- [Status](#status) +- [Installation](#installation) +- [Dependencies](#dependencies) +- [API](#api) +- [Authors](#authors) +- [License](#license) + +## About + +Extensible SI unit creation & conversions (130+ units predefined). + +All unit definitions & conversions are based on the SI units described here: + +- https://en.wikipedia.org/wiki/International_System_of_Units + +### Unit definitions + +Each unit is defined via a 7-dimensional vector representing individual exponents +for each of the base SI dimensions, in order: + +| SI dimension | Base unit | Base unit symbol | +|---------------------|-----------|------------------| +| mass | kilogram | kg | +| length | meter | m | +| time | second | s | +| current | ampere | A | +| temperature | kelvin | K | +| amount of substance | mole | mol | +| luminous intensity | candela | cd | + +Dimensionless units are supported too, and for those all dimensions are set to +zero. + +Additionally, we also define a scale factor and zero offset for each unit, with +most dimensions' base units usually using a factor of 1 and no offset. + +For example, here's how we can define kilograms and meters: + +```ts +const KG = unit(1, 1); // SI dimension 0 +// { dim: [ 1, 0, 0, 0, 0, 0, 0 ], scale: 1, offset: 0, prefix: false } + +const M = unit(1, 1); // SI dimension 1 +// { dim: [ 0, 1, 0, 0, 0, 0, 0 ], scale: 1, offset: 0, prefix: false } +``` + +More complex units like electrical resistance (e.g. kΩ) are based on more than a +single dimension: + +```ts +// { dim: [ 1, 2, -3, -2, 0, 0, 0 ], scale: 1000, offset: 0, prefix: true } +``` + +This dimension vector represents the unit definition for (see [SI derived +units](https://en.wikipedia.org/wiki/SI_derived_unit)): + +> 1 Ohm = kg⋅m2⋅s−3⋅A−2 + +### Predefined units + +The following units are provided as "builtins", here grouped by dimension: + +#### Acceleration + +| Unit name | JS name | Description | +|-----------|----------|---------------------------| +| `m/s2` | `M_S2` | meter per second squared | +| `ft/s2` | `FT_S2` | foot per second squared | +| `rad/s2` | `RAD_S2` | radian per second squared | +| `g0` | `G0` | standard gravity | + +#### Angle + +| Unit name | JS name | Description | +|-----------|----------|-------------| +| `arcmin` | `ARCMIN` | arc minute | +| `arcsec` | `ARCSEC` | arc second | +| `deg` | `DEG` | degree | +| `gon` | `GON` | gradian | +| `rad` | `RAD` | radian | +| `sr` | `SR` | steradian | +| `turn` | `TURN` | turn | + +#### Area + +| Unit name | JS name | Description | +|-----------|---------|-------------------| +| `m2` | `M2` | square meter | +| `cm2` | `CM2` | square centimeter | +| `mm2` | `MM2` | square millimeter | +| `km2` | `KM2` | square kilometer | +| `ha` | `HA` | hectar | +| `ac` | `AC` | acre | +| `sqin` | `SQIN` | square inch | +| `sqft` | `SQFT` | square foot | +| `sqmi` | `SQMI` | square mile | + +#### Data + +| Unit name | JS name | Description | +|-----------|---------|-----------------| +| `bit` | `BIT` | bit | +| `kbit` | `KBIT` | kilobit | +| `Mbit` | `MBIT` | megabit | +| `Gbit` | `GBIT` | gigabit | +| `Tbit` | `TBIT` | terabit | +| `B` | `BYTE` | byte (8 bit) | +| `KB` | `KBYTE` | kilobyte (1024) | +| `MB` | `MBYTE` | megabyte (1024) | +| `GB` | `GBYTE` | gigabyte (1024) | +| `TB` | `TBYTE` | terabyte (1024) | +| `PB` | `PBYTE` | petabyte (1024) | +| `EB` | `EBYTE` | exabyte (1024) | + +#### Electric current + +| Unit | JS name | Description | +|------|-----------|-------------------| +| A | `A` | ampere | +| mA | `MA` | milliampere | +| mAh | `MA_H` | milliampere-hours | +| C | `C` | coulomb | +| V | `V` | volt | +| mV | `MV` | millivolt | +| kV | `KV` | kilovolt | +| F | `F` | farad | +| pF | `PF` | picofarad | +| µF | `µF` | microfarad | +| Ω | `OHM` | ohm | +| kΩ | `KOHM` | kiloohm | +| MΩ | `MOHM` | megaohm | +| GΩ | `GOHM` | gigaohm | +| S | `SIEMENS` | siemens | +| Wb | `WB` | weber | +| T | `TESLA` | tesla | +| H | `HENRY` | henry | + +#### Energy + +| Unit | JS name | Description | +|------|---------|-------------| +| J | `J` | joule | +| kJ | `KJ` | kilojoule | +| MJ | `MJ` | megajoule | +| GJ | `GJ` | gigajoule | +| cal | `CAL` | calorie | +| kcal | `KCAL` | kilocalorie | + +#### Force + +| Unit | JS name | Description | +|------|---------|-------------| +| `N` | `N` | newton | + +#### Frequency + +| Unit | JS name | Description | +|-------|---------|---------------------| +| `Hz` | `HZ` | hertz | +| `kHz` | `KHZ` | kilohertz | +| `MHz` | `MHZ` | megahertz | +| `GHz` | `GHZ` | gigahertz | +| `THz` | `THZ` | terahertz | +| `rpm` | `RPM` | rotation per minute | +| `ω` | `OMEGA` | radian per second | + +#### Length + +| Unit name | JS name | Description | +|-----------|---------|-------------------| +| `m` | `M` | meter | +| `cm` | `CM` | centimeter | +| `mm` | `MM` | millimeter | +| `µm` | `µM` | micrometer | +| `nm` | `NM` | nanometer | +| `km` | `KM` | kilometer | +| `au` | `AU` | astronomical unit | +| `in` | `IN` | inch | +| `ft` | `FT` | foot | +| `yd` | `YD` | yard | +| `mi` | `MI` | mile | +| `nmi` | `NMI` | nautical mile | +| `pica` | `PICA` | pica | +| `point` | `POINT` | point | + +#### Luminous intensity + +| Unit | JS name | Description | +|------|---------|-------------| +| `cd` | `CD` | candela | +| `lm` | `LM` | lumen | +| `lx` | `LX` | lux | + +#### Mass + +| Unit name | JS name | Description | +|-----------|---------|----------------| +| `µg` | `µG` | microgram | +| `mg` | `mG` | milligram | +| `g` | `G` | gram | +| `kg` | `KG` | kilogram | +| `t` | `T` | tonne | +| `kt` | `KT` | kilotonne | +| `Mt` | `MT` | megatonne | +| `Gt` | `GT` | gigatonne | +| `lb` | `LB` | imperial pound | +| `st` | `ST` | stone | + +#### Power + +| Unit name | JS name | Description | +|-----------|---------|---------------| +| `W` | `W` | watt | +| `mW` | `MW` | milliwatt | +| `kW` | `KW` | kilowatt | +| `MW` | `MW` | megawatt | +| `GW` | `GW` | gigawatt | +| `TW` | `TW` | terawatt | +| `Wh` | `WH` | watt-hour | +| `kWh` | `KWH` | kilowatt-hour | + +#### Pressure + +| Unit name | JS name | Description | +|-----------|---------|-----------------------| +| `Pa` | `PA` | pascal | +| `kPa` | `KPA` | kilopascal | +| `MPa` | `MPA` | megapascal | +| `GPa` | `GPA` | gigapascal | +| `at` | `AT` | technical atmosphere | +| `atm` | `ATM` | atmosphere | +| `bar` | `BAR` | bar | +| `psi` | `PSI` | pound per square inch | + +#### Speed + +| Unit | JS name | Description | +|--------|---------|--------------------| +| `m/s` | `M_S` | meter per second | +| `km/h` | `KM_H` | kilometer per hour | +| `mph` | `MPH` | mile per hour | +| `kn` | `KN` | knot | + +#### Substance + +| Unit | JS name | Description | +|-------|---------|-------------| +| `mol` | `MOL` | mole | + +#### Temperature + +| Unit | JS name | Description | +|------|---------|-------------------| +| `K` | `K` | kelvin | +| `℃` | `DEG_C` | degree celsius | +| `℉` | `DEG_F` | degree fahrenheit | + +#### Time + +| Unit | JS name | Description | +|-------|---------|--------------------| +| s | `S` | second | +| ms | `MS` | millisecond | +| µs | `µS` | microsecond | +| ns | `NS` | nanosecond | +| min | `MIN` | minute | +| h | `H` | hour | +| day | `DAY` | day | +| week | `WEEK` | week | +| month | `MONTH` | month (30 days) | +| year | `YEAR` | year (365.25 days) | + +#### Volume + +| Unit | JS name | Description | +|-------------|-----------|----------------------| +| `m3` | `M3` | cubic meter | +| `mm3` | `MM3` | cubic millimeter | +| `cm3` | `CM3` | cubic centimeter | +| `km3` | `KM3` | cubic kilometer | +| `l` | `L` | liter | +| `cl` | `CL` | centiliter | +| `ml` | `ML` | milliliter | +| `imp gal` | `GAL` | imperial gallon | +| `imp pt` | `PT` | imperial pint | +| `imp fl oz` | `FLOZ` | imperial fluid ounce | +| `us gal` | `US_GAL` | US gallon | +| `us pt` | `US_PT` | US pint | +| `us cup` | `US_CUP` | US cup | +| `us fl oz` | `US_FLOZ` | US fluid ounce | + +### Creating & deriving units + +#### Using standard metric prefixes + +Existing coherent + +### Unit conversions + +Only units with compatible (incl. reciprocal) dimensions can be converted, +otherwise an error will be thrown. Units can be given as + +```ts +// convert from km/h to mph using unit names +convert(100, "km/h", "mph"); +// 62.13711922373341 + +// using predefined unit constants directly +convert(60, MPH, KM_H); +// 96.56063999999998 + +// or using anonymous units (meter/second ⇒ yard/hour) +convert(1, "m/s", div(YD, H)) +// 3937.007874015749 +``` + +## Status + +**BETA** - possibly breaking changes forthcoming + +[Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bunits%5D+in%3Atitle) + +## Installation + +```bash +yarn add @thi.ng/units +``` + +ES module import: + +```html + +``` + +[Skypack documentation](https://docs.skypack.dev/) + +For Node.js REPL: + +```js +const units = await import("@thi.ng/units"); +``` + +Package sizes (brotli'd, pre-treeshake): ESM: 3.17 KB + +## Dependencies + +- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks) +- [@thi.ng/equiv](https://github.com/thi-ng/umbrella/tree/develop/packages/equiv) +- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors) + +## API + +[Generated API docs](https://docs.thi.ng/umbrella/units/) + +TODO + +## Authors + +- [Karsten Schmidt](https://thi.ng) + +If this project contributes to an academic publication, please cite it as: + +```bibtex +@misc{thing-units, + title = "@thi.ng/units", + author = "Karsten Schmidt", + note = "https://thi.ng/units", + year = 2021 +} +``` + +## License + +© 2021 - 2023 Karsten Schmidt // Apache License 2.0 diff --git a/packages/units/api-extractor.json b/packages/units/api-extractor.json new file mode 100644 index 0000000000..bc73f2cc02 --- /dev/null +++ b/packages/units/api-extractor.json @@ -0,0 +1,3 @@ +{ + "extends": "../../api-extractor.json" +} diff --git a/packages/units/package.json b/packages/units/package.json new file mode 100644 index 0000000000..563b6b2fe0 --- /dev/null +++ b/packages/units/package.json @@ -0,0 +1,157 @@ +{ + "name": "@thi.ng/units", + "version": "0.0.1", + "description": "Extensible SI unit creation & conversions (130+ units predefined)", + "type": "module", + "module": "./index.js", + "typings": "./index.d.ts", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/develop/packages/units#readme", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/postspectacular" + }, + { + "type": "patreon", + "url": "https://patreon.com/thing_umbrella" + } + ], + "author": "Karsten Schmidt (https://thi.ng)", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && tsc --declaration", + "clean": "rimraf '*.js' '*.d.ts' '*.map' doc", + "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts", + "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose", + "doc:readme": "yarn doc:stats && tools:readme", + "doc:stats": "tools:module-stats", + "pub": "yarn npm publish --access public", + "test": "testament test" + }, + "dependencies": { + "@thi.ng/checks": "^3.3.9", + "@thi.ng/equiv": "^2.1.19", + "@thi.ng/errors": "^2.2.12" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.34.4", + "@thi.ng/testament": "^0.3.12", + "rimraf": "^4.4.0", + "tools": "workspace:^", + "typedoc": "^0.23.26", + "typescript": "^4.9.5" + }, + "keywords": [ + "acceleration", + "angle", + "area", + "bits", + "bytes", + "capacitance", + "current", + "voltage", + "resistance", + "converter", + "energy", + "force", + "frequency", + "length", + "mass", + "power", + "si", + "speed", + "temperature", + "time", + "typescript", + "units", + "volume" + ], + "publishConfig": { + "access": "public" + }, + "browser": { + "process": false, + "setTimeout": false + }, + "engines": { + "node": ">=14" + }, + "files": [ + "./*.js", + "./*.d.ts" + ], + "exports": { + ".": { + "default": "./index.js" + }, + "./accel": { + "default": "./accel.js" + }, + "./angle": { + "default": "./angle.js" + }, + "./api": { + "default": "./api.js" + }, + "./area": { + "default": "./area.js" + }, + "./data": { + "default": "./data.js" + }, + "./electric": { + "default": "./electric.js" + }, + "./energy": { + "default": "./energy.js" + }, + "./force": { + "default": "./force.js" + }, + "./frequency": { + "default": "./frequency.js" + }, + "./length": { + "default": "./length.js" + }, + "./luminous": { + "default": "./luminous.js" + }, + "./mass": { + "default": "./mass.js" + }, + "./power": { + "default": "./power.js" + }, + "./pressure": { + "default": "./pressure.js" + }, + "./speed": { + "default": "./speed.js" + }, + "./substance": { + "default": "./substance.js" + }, + "./temperature": { + "default": "./temperature.js" + }, + "./time": { + "default": "./time.js" + }, + "./unit": { + "default": "./unit.js" + }, + "./volume": { + "default": "./volume.js" + } + }, + "thi.ng": { + "status": "beta", + "year": 2021 + } +} diff --git a/packages/units/src/accel.ts b/packages/units/src/accel.ts new file mode 100644 index 0000000000..3198a3419f --- /dev/null +++ b/packages/units/src/accel.ts @@ -0,0 +1,24 @@ +import { RAD } from "./angle.js"; +import { FT, M } from "./length.js"; +import { S } from "./time.js"; +import { defUnit, div, mul, pow } from "./unit.js"; + +export const M_S2 = defUnit( + "m/s2", + "meter per second squared", + div(M, pow(S, 2)) +); + +export const FT_S2 = defUnit( + "ft/s2", + "foot per second squared", + div(FT, pow(S, 2)) +); + +export const RAD_S2 = defUnit( + "rad/s2", + "radian per second squared", + div(RAD, pow(S, 2)) +); + +export const G0 = defUnit("g0", "standard gravity", mul(M_S2, 9.80665)); diff --git a/packages/units/src/angle.ts b/packages/units/src/angle.ts new file mode 100644 index 0000000000..4f65b6d66b --- /dev/null +++ b/packages/units/src/angle.ts @@ -0,0 +1,12 @@ +import { defUnit, dimensionless, mul } from "./unit.js"; + +const PI = Math.PI; + +export const RAD = defUnit("rad", "radian", dimensionless(1, 0, true)); +export const DEG = defUnit("deg", "degree", mul(RAD, PI / 180)); +export const GON = defUnit("gon", "gradian", mul(RAD, PI / 200)); +export const TURN = defUnit("turn", "turn", mul(RAD, 2 * PI)); +export const ARCMIN = defUnit("arcmin", "arc minute", mul(RAD, PI / 10800)); +export const ARCSEC = defUnit("arcsec", "arc second", mul(RAD, PI / 648000)); + +export const SR = defUnit("sr", "steradian", dimensionless(1, 0, true)); diff --git a/packages/units/src/api.ts b/packages/units/src/api.ts new file mode 100644 index 0000000000..b58621f030 --- /dev/null +++ b/packages/units/src/api.ts @@ -0,0 +1,99 @@ +export interface Unit { + /** + * SI dimension vector ({@link Dimensions}) + */ + dim: Dimensions; + /** + * Scaling factor relative to the coherent unit + */ + scale: number; + /** + * Zero offset value + */ + offset: number; + /** + * True, if this unit is coherent. + * + * @remarks + * Reference: + * - https://en.wikipedia.org/wiki/Coherence_(units_of_measurement) + */ + coherent: boolean; +} + +export interface NamedUnit extends Unit { + /** + * Symbol under which this unit can be looked up with via {@link asUnit} and + * others. + */ + sym: string; + /** + * This unit's human readable description/name. + */ + name: string; +} + +/** + * Vector of the 7 basic SI unit dimensions. + * + * @remarks + * In order: + * + * - 0 = mass + * - 1 = length + * - 2 = time + * - 3 = current + * - 4 = temperature + * - 5 = amount of substance + * - 6 = luminous intensity + * + * Note: For dimensionless units, all dimensions are zero. + * + * Reference: + * - https://en.wikipedia.org/wiki/International_System_of_Units + */ +export type Dimensions = [ + number, + number, + number, + number, + number, + number, + number +]; + +/** + * A known metric prefix. + */ +export type Prefix = keyof typeof PREFIXES; + +/** + * @remarks + * Reference: + * - https://en.wikipedia.org/wiki/Metric_prefix + */ +export const PREFIXES = { + Q: 1e30, + R: 1e27, + Y: 1e24, + Z: 1e21, + E: 1e18, + P: 1e15, + T: 1e12, + G: 1e9, + M: 1e6, + k: 1e3, + h: 1e2, + d: 1e-1, + c: 1e-2, + m: 1e-3, + µ: 1e-6, + n: 1e-9, + p: 1e-12, + f: 1e-15, + a: 1e-18, + z: 1e-21, + y: 1e-24, + r: 1e-27, + q: 1e-30, +}; diff --git a/packages/units/src/area.ts b/packages/units/src/area.ts new file mode 100644 index 0000000000..c064377b3c --- /dev/null +++ b/packages/units/src/area.ts @@ -0,0 +1,13 @@ +import { CM, FT, IN, KM, M, MI, MM } from "./length.js"; +import { defUnit, mul, pow } from "./unit.js"; + +export const M2 = defUnit("m2", "square meter", pow(M, 2)); +export const MM2 = defUnit("mm2", "square millimeter", pow(MM, 2)); +export const CM2 = defUnit("cm2", "square centimeter", pow(CM, 2)); +export const KM2 = defUnit("km2", "square kilometer", pow(KM, 2)); +export const HA = defUnit("ha", "hectar", mul(M2, 1e4)); + +export const AC = defUnit("ac", "acre", mul(M2, 4046.86)); +export const SQIN = defUnit("sqin", "square inch", pow(IN, 2)); +export const SQFT = defUnit("sqft", "square foot", pow(FT, 2)); +export const SQMI = defUnit("sqmi", "square mile", pow(MI, 2)); diff --git a/packages/units/src/data.ts b/packages/units/src/data.ts new file mode 100644 index 0000000000..9c8868c091 --- /dev/null +++ b/packages/units/src/data.ts @@ -0,0 +1,15 @@ +import { defUnit, dimensionless, mul, prefix } from "./unit.js"; + +export const BIT = defUnit("bit", "bit", dimensionless(1, 0, true)); +export const KBIT = defUnit("kbit", "kilobit", prefix("k", BIT)); +export const MBIT = defUnit("Mbit", "megabit", prefix("M", BIT)); +export const GBIT = defUnit("Gbit", "gigabit", prefix("G", BIT)); +export const TBIT = defUnit("Tbit", "terabit", prefix("T", BIT)); + +export const BYTE = defUnit("B", "byte", mul(BIT, 8, true)); +export const KBYTE = defUnit("KB", "kilobyte", mul(BYTE, 1024)); +export const MBYTE = defUnit("MB", "megabyte", mul(KBYTE, 1024)); +export const GBYTE = defUnit("GB", "gigabyte", mul(MBYTE, 1024)); +export const TBYTE = defUnit("TB", "terabyte", mul(GBYTE, 1024)); +export const PBYTE = defUnit("PB", "petabyte", mul(TBYTE, 1024)); +export const EBYTE = defUnit("EB", "exabyte", mul(PBYTE, 1024)); diff --git a/packages/units/src/electric.ts b/packages/units/src/electric.ts new file mode 100644 index 0000000000..d055c8ed2d --- /dev/null +++ b/packages/units/src/electric.ts @@ -0,0 +1,28 @@ +import { M2 } from "./area.js"; +import { J } from "./energy.js"; +import { H, S } from "./time.js"; +import { defUnit, div, mul, prefix, unit } from "./unit.js"; + +export const A = defUnit("A", "ampere", unit(3, 1, 0, true)); +export const MA = defUnit("mA", "milliampere", prefix("m", A)); +export const MA_H = defUnit("mAh", "milliampere-hour", mul(MA, H)); + +export const C = defUnit("C", "coulomb", mul(A, S, true)); + +export const V = defUnit("V", "volt", div(J, C, true)); +export const MV = defUnit("mV", "millivolt", prefix("m", V)); +export const KV = defUnit("kV", "kilovolt", prefix("k", V)); + +export const F = defUnit("F", "farad", div(C, V, true)); +export const PF = defUnit("pF", "picofarad", prefix("p", F)); +export const µF = defUnit("µF", "microfarad", prefix("µ", F)); + +export const OHM = defUnit("Ω", "ohm", div(V, A, true)); +export const KOHM = defUnit("kΩ", "kiloohm", prefix("k", OHM)); +export const MOHM = defUnit("MΩ", "megaohm", prefix("M", OHM)); +export const GOHM = defUnit("GΩ", "gigaohm", prefix("G", OHM)); + +export const SIEMENS = defUnit("S", "siemens", div(A, V, true)); +export const WB = defUnit("Wb", "weber", mul(V, S, true)); +export const TESLA = defUnit("T", "tesla", div(WB, M2, true)); +export const HENRY = defUnit("H", "henry", div(WB, A, true)); diff --git a/packages/units/src/energy.ts b/packages/units/src/energy.ts new file mode 100644 index 0000000000..b47235991e --- /dev/null +++ b/packages/units/src/energy.ts @@ -0,0 +1,10 @@ +import { N } from "./force.js"; +import { M } from "./length.js"; +import { defUnit, mul, prefix } from "./unit.js"; + +export const J = defUnit("J", "joule", mul(N, M, true)); +export const KJ = defUnit("kJ", "kilojoule", prefix("k", J)); +export const MJ = defUnit("MJ", "megajoule", prefix("M", J)); +export const GJ = defUnit("GJ", "gigajoule", prefix("G", J)); +export const CAL = defUnit("cal", "calorie", mul(J, 4.184, true)); +export const KCAL = defUnit("kcal", "kilocalorie", prefix("k", CAL)); diff --git a/packages/units/src/force.ts b/packages/units/src/force.ts new file mode 100644 index 0000000000..919acf172a --- /dev/null +++ b/packages/units/src/force.ts @@ -0,0 +1,5 @@ +import { M_S2 } from "./accel.js"; +import { KG } from "./mass.js"; +import { defUnit, mul } from "./unit.js"; + +export const N = defUnit("N", "newton", mul(KG, M_S2, true)); diff --git a/packages/units/src/frequency.ts b/packages/units/src/frequency.ts new file mode 100644 index 0000000000..aef97d587f --- /dev/null +++ b/packages/units/src/frequency.ts @@ -0,0 +1,11 @@ +import { S } from "./time.js"; +import { defUnit, div, mul, prefix, reciprocal } from "./unit.js"; + +export const HZ = defUnit("Hz", "hertz", reciprocal(S, true)); +export const KHZ = defUnit("kHz", "kilohertz", prefix("k", HZ)); +export const MHZ = defUnit("MHz", "megahertz", prefix("M", HZ)); +export const GHZ = defUnit("GHz", "gigahertz", prefix("G", HZ)); +export const THZ = defUnit("THz", "terahertz", prefix("T", HZ)); +export const RPM = defUnit("rpm", "rotation per minute", mul(HZ, 1 / 60)); + +export const OMEGA = defUnit("ω", "radian per second", div(HZ, 2 * Math.PI)); diff --git a/packages/units/src/index.ts b/packages/units/src/index.ts new file mode 100644 index 0000000000..8163e41792 --- /dev/null +++ b/packages/units/src/index.ts @@ -0,0 +1,21 @@ +export * from "./api.js"; +export * from "./unit.js"; + +export * from "./accel.js"; +export * from "./angle.js"; +export * from "./area.js"; +export * from "./data.js"; +export * from "./electric.js"; +export * from "./energy.js"; +export * from "./force.js"; +export * from "./frequency.js"; +export * from "./length.js"; +export * from "./luminous.js"; +export * from "./mass.js"; +export * from "./power.js"; +export * from "./pressure.js"; +export * from "./speed.js"; +export * from "./substance.js"; +export * from "./temperature.js"; +export * from "./time.js"; +export * from "./volume.js"; diff --git a/packages/units/src/length.ts b/packages/units/src/length.ts new file mode 100644 index 0000000000..c7135351cf --- /dev/null +++ b/packages/units/src/length.ts @@ -0,0 +1,18 @@ +import { defUnit, mul, prefix, unit } from "./unit.js"; + +export const M = defUnit("m", "meter", unit(1, 1, 0, true)); +export const CM = defUnit("cm", "centimeter", prefix("c", M)); +export const MM = defUnit("mm", "millimeter", prefix("m", M)); +export const µM = defUnit("µm", "micrometer", prefix("µ", M)); +export const NM = defUnit("nm", "nanometer", prefix("n", M)); +export const KM = defUnit("km", "kilometer", prefix("k", M)); +export const AU = defUnit("au", "astronomical unit", mul(M, 149597870700)); + +export const IN = defUnit("in", "inch", mul(M, 0.0254)); +export const FT = defUnit("ft", "foot", mul(IN, 12)); +export const YD = defUnit("yd", "yard", mul(FT, 3)); +export const MI = defUnit("mi", "mile", mul(YD, 1760)); +export const NMI = defUnit("nmi", "nautical mile", mul(M, 1852)); + +export const PICA = defUnit("pica", "pica", mul(IN, 1 / 6)); +export const POINT = defUnit("point", "point", mul(IN, 1 / 72)); diff --git a/packages/units/src/luminous.ts b/packages/units/src/luminous.ts new file mode 100644 index 0000000000..cc90c84ac2 --- /dev/null +++ b/packages/units/src/luminous.ts @@ -0,0 +1,8 @@ +import { SR } from "./angle.js"; +import { M2 } from "./area.js"; +import { defUnit, div, mul, unit } from "./unit.js"; + +export const CD = defUnit("cd", "candela", unit(6, 1, 0, true)); + +export const LM = defUnit("lm", "lumen", mul(CD, SR)); +export const LX = defUnit("lx", "lux", div(LM, M2)); diff --git a/packages/units/src/mass.ts b/packages/units/src/mass.ts new file mode 100644 index 0000000000..cdb77121c0 --- /dev/null +++ b/packages/units/src/mass.ts @@ -0,0 +1,14 @@ +import { defUnit, mul, prefix, unit } from "./unit.js"; + +export const G = defUnit("g", "gram", unit(0, 1e-3, 0, true)); +export const KG = defUnit("kg", "kilogram", prefix("k", G)); +export const MG = defUnit("mg", "milligram", prefix("m", G)); +export const µG = defUnit("µg", "microgram", prefix("µ", G)); + +export const T = defUnit("t", "tonne", prefix("M", G, true)); +export const KT = defUnit("kt", "kilotonne", prefix("k", T)); +export const MT = defUnit("Mt", "megatonne", prefix("M", T)); +export const GT = defUnit("Gt", "gigatonne", prefix("G", T)); + +export const LB = defUnit("lb", "imperial pound", mul(KG, 0.45359237)); +export const ST = defUnit("st", "stone", mul(LB, 14)); diff --git a/packages/units/src/power.ts b/packages/units/src/power.ts new file mode 100644 index 0000000000..fb48215f25 --- /dev/null +++ b/packages/units/src/power.ts @@ -0,0 +1,13 @@ +import { J } from "./energy.js"; +import { H, S } from "./time.js"; +import { defUnit, div, mul, prefix } from "./unit.js"; + +export const W = defUnit("W", "watt", div(J, S, true)); +export const MILLI_W = defUnit("mW", "milliwatt", prefix("m", W)); +export const KW = defUnit("kW", "kilowatt", prefix("k", W)); +export const MW = defUnit("MW", "megawatt", prefix("M", W)); +export const GW = defUnit("GW", "gigawatt", prefix("G", W)); +export const TW = defUnit("TW", "terawatt", prefix("T", W)); + +export const W_H = defUnit("Wh", "watt-hour", mul(W, H, true)); +export const KW_H = defUnit("kWh", "kilowatt-hour", prefix("k", W_H)); diff --git a/packages/units/src/pressure.ts b/packages/units/src/pressure.ts new file mode 100644 index 0000000000..eec018ea55 --- /dev/null +++ b/packages/units/src/pressure.ts @@ -0,0 +1,13 @@ +import { M2 } from "./area.js"; +import { N } from "./force.js"; +import { defUnit, div, mul, prefix } from "./unit.js"; + +export const PA = defUnit("Pa", "pascal", div(N, M2, true)); +export const KPA = defUnit("kPa", "kilopascal", prefix("k", PA)); +export const MPA = defUnit("MPa", "megapascal", prefix("M", PA)); +export const GPA = defUnit("GPa", "gigapascal", prefix("G", PA)); + +export const AT = defUnit("at", "technical atmosphere", mul(PA, 98066.5)); +export const ATM = defUnit("atm", "atmosphere", mul(PA, 101325)); +export const BAR = defUnit("bar", "bar", mul(PA, 1e5, true)); +export const PSI = defUnit("psi", "pound per square inch", mul(PA, 6894.757)); diff --git a/packages/units/src/speed.ts b/packages/units/src/speed.ts new file mode 100644 index 0000000000..487ce82382 --- /dev/null +++ b/packages/units/src/speed.ts @@ -0,0 +1,9 @@ +import { defUnit, div } from "./unit.js"; +import { FT, KM, M, MI, NMI } from "./length.js"; +import { H, S } from "./time.js"; + +export const M_S = defUnit("m/s", "meter per second", div(M, S)); +export const KM_H = defUnit("km/h", "kilometer per hour", div(KM, H)); +export const FT_S = defUnit("ft/s", "foot per second", div(FT, S)); +export const MPH = defUnit("mph", "mile per hour", div(MI, H)); +export const KNOT = defUnit("kn", "knot", div(NMI, H)); diff --git a/packages/units/src/substance.ts b/packages/units/src/substance.ts new file mode 100644 index 0000000000..80fecbda4a --- /dev/null +++ b/packages/units/src/substance.ts @@ -0,0 +1,3 @@ +import { defUnit, unit } from "./unit.js"; + +export const MOL = defUnit("mol", "mole", unit(5, 1, 0, true)); diff --git a/packages/units/src/temperature.ts b/packages/units/src/temperature.ts new file mode 100644 index 0000000000..198963a5a3 --- /dev/null +++ b/packages/units/src/temperature.ts @@ -0,0 +1,9 @@ +import { defUnit, unit } from "./unit.js"; + +export const K = defUnit("K", "kelvin", unit(4, 1)); +export const DEG_C = defUnit("℃", "degree celsius", unit(4, 1, 273.15)); +export const DEG_F = defUnit( + "℉", + "degree fahrenheit", + unit(4, 1 / 1.8, 459.67 / 1.8) +); diff --git a/packages/units/src/time.ts b/packages/units/src/time.ts new file mode 100644 index 0000000000..20bd612f06 --- /dev/null +++ b/packages/units/src/time.ts @@ -0,0 +1,12 @@ +import { defUnit, mul, prefix, unit } from "./unit.js"; + +export const S = defUnit("s", "second", unit(2, 1, 0, true)); +export const MS = defUnit("ms", "millisecond", prefix("m", S)); +export const µS = defUnit("µs", "microsecond", prefix("µ", S)); +export const NS = defUnit("ns", "nanosecond", prefix("n", S)); +export const MIN = defUnit("min", "minute", mul(S, 60)); +export const H = defUnit("h", "hour", mul(MIN, 60)); +export const DAY = defUnit("day", "day", mul(H, 24)); +export const WEEK = defUnit("week", "week", mul(DAY, 7)); +export const MONTH = defUnit("month", "month", mul(DAY, 30)); +export const YEAR = defUnit("year", "year", mul(DAY, 365.25)); diff --git a/packages/units/src/unit.ts b/packages/units/src/unit.ts new file mode 100644 index 0000000000..14168e8d7d --- /dev/null +++ b/packages/units/src/unit.ts @@ -0,0 +1,254 @@ +import { isNumber } from "@thi.ng/checks/is-number"; +import { isString } from "@thi.ng/checks/is-string"; +import { equivArrayLike } from "@thi.ng/equiv"; +import { assert } from "@thi.ng/errors/assert"; +import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; +import { Dimensions, NamedUnit, Prefix, PREFIXES, Unit } from "./api.js"; + +export const UNITS: Record = {}; + +/** + * Defines a "raw" (anonymous) unit using given dimension(s), scale factor, zero + * offset and `coherent` flag indicating if the unit is the coherent one for + * given dimensions and can later be used for deriving prefixed versions (see + * {@link coherent}). + * + * @param dim + * @param scale + * @param offset + * @param coherent + */ +export const unit = ( + dim: Dimensions | number, + scale: number, + offset = 0, + coherent = false +): Unit => ({ + dim: isNumber(dim) ? __oneHot(dim) : dim, + scale, + offset, + coherent, +}); + +/** + * Returns a new dimensionless unit (i.e. all SI dimensions are zero) with given + * `scale` factor. + * + * @param scale + * @param offset + * @param coherent + */ +export const dimensionless = (scale: number, offset = 0, coherent = false) => + unit([0, 0, 0, 0, 0, 0, 0], scale, offset, coherent); + +/** + * Takes a unit symbol, full unit name and pre-defined {@link Unit} impl and + * registers it in the {@link UNITS} cache for further lookups by symbol name. + * + * @remarks + * By default throws an error if attempting to register a unit with an existing + * symbol. If `force` is true, the existing unit will be overwritten. + * + * @param sym + * @param name + * @param unit + * @param force + */ +export const defUnit = ( + sym: string, + name: string, + unit: Unit, + force = false +): NamedUnit => { + if (UNITS[sym] && !force) illegalArgs(`attempt to override unit: ${sym}`); + return (UNITS[sym] = { ...unit, sym, name }); +}; + +/** + * Attempts to find a unit by given symbol ID/name. Throws error if unit is + * unknown. + * + * @param id + */ +export const asUnit = (id: string) => { + for (let i = 0; i < id.length; i++) { + const pre = id.substring(0, i); + const unit = UNITS[id.substring(i)]; + if (unit) { + return PREFIXES[pre] !== undefined + ? prefix(pre, unit) + : unit; + } + } + for (let u in UNITS) { + if (UNITS[u].name === id) return UNITS[u]; + } + illegalArgs(`unknown unit: ${id}`); +}; + +/** + * Creates a new re-scaled version of given unit (only coherent ones are + * allowed), using the scale factor associated with given standard metric prefix + * (see {@link PREFIXES}). If `coherent` is true (default: false), the new unit + * itself is considered coherent and can be prefixed later. + * + * @example + * ```ts + * // create kilometer unit from (builtin) meter + * const KM = prefix("k", M); + * ``` + * + * @param id + * @param unit + * @param coherent + */ +export const prefix = (id: Prefix, unit: Unit, coherent = false) => + unit.coherent + ? mul(unit, PREFIXES[id], coherent) + : illegalArgs(`unit isn't coherent: ${id}`); + +/** + * Derives a new unit as the product of the given units. If `coherent` is true + * (default: false), the new unit itself is considered coherent and can be + * prefixed later. + * + * @param a + * @param b + * @param coherent + */ +export const mul = (a: Unit, b: Unit | number, coherent = false) => + isNumber(b) + ? unit(a.dim, a.scale * b, a.offset, coherent) + : unit( + a.dim.map((x, i) => x + b.dim[i]), + a.scale * b.scale, + 0, + coherent + ); + +/** + * Derives a new unit via the division of the given units. If `coherent` is true + * (default: false), the new unit itself is considered coherent and can be + * prefixed later. + * + * @param a + * @param b + * @param coherent + */ +export const div = (a: Unit, b: Unit | number, coherent = false) => + isNumber(b) + ? unit(a.dim, a.scale / b, a.offset, coherent) + : unit( + a.dim.map((x, i) => x - b.dim[i]), + a.scale / b.scale, + 0, + coherent + ); + +/** + * Creates the reciprocal version of given unit (i.e. all SI dimensions will + * flip sign) and the scale factor of the new unit will be `1/scale`. + * + * @example + * ```ts + * const HZ = reciprocal(S, true); + * ``` + * + * @param u + * @param coherent + */ +export const reciprocal = (u: Unit, coherent = false) => + div(dimensionless(1), u, coherent); + +/** + * Raises given unit to power `k`. + * + * ```ts + * // create kilometer unit from (builtin) meter + * const SQ_METER = pow(M, 2); + * + * // acceleration aka m/s^2 + * const M_S2 = div(M, pow(S, 2)); + * ``` + * + * @param u + * @param k + * @param coherent + */ +export const pow = (u: Unit, k: number, coherent = false) => + unit(u.dim.map((x) => x * k), u.scale ** k, 0, coherent); + +/** + * Attempts to convert `x` from `src` unit into `dest` unit. Throws an error if + * units are incompatible. + * + * @remarks + * Units can only be converted if their SI dimensions are compatible. See + * {@link isConvertible}. + * + * @param x + * @param src + * @param dest + */ +export const convert = (x: number, src: Unit | string, dest: Unit | string) => { + const $src = __ensureUnit(src); + const $dest = __ensureUnit(dest); + const xnorm = x * $src.scale + $src.offset; + if (isReciprocal($src, $dest)) + return (1 / xnorm - $dest.offset) / $dest.scale; + assert(equivArrayLike($src.dim, $dest.dim), "incompatible dimensions"); + return (xnorm - $dest.offset) / $dest.scale; +}; + +/** + * Returns true if the two given units are reciprocal to each other (and + * therefore can be used for conversion). + * + * @param a + * @param b + */ +export const isReciprocal = (src: Unit | string, dest: Unit | string) => { + const { dim: a } = __ensureUnit(src); + const { dim: b } = __ensureUnit(dest); + let ok = false; + for (let i = 0; i < 7; i++) { + const xa = a[i]; + const xb = b[i]; + if (xa === 0 && xb === 0) continue; + if (xa !== -xb) return false; + ok = true; + } + return ok; +}; + +/** + * Returns true if `src` unit is convertible to `dest`. + * + * @param src + * @param dest + */ +export const isConvertible = (src: Unit | string, dest: Unit | string) => { + const $src = __ensureUnit(src); + const $dest = __ensureUnit(dest); + return isReciprocal($src, $dest) || equivArrayLike($src.dim, $dest.dim); +}; + +export const formatSI = ({ dim }: Unit) => { + const SI = ["kg", "m", "s", "A", "K", "mol", "cd"]; + const acc: string[] = []; + for (let i = 0; i < 7; i++) { + const x = dim[i]; + if (x !== 0) acc.push(SI[i] + (x !== 1 ? x : "")); + } + return acc.length ? acc.join("·") : ""; +}; + +/** @internal */ +const __ensureUnit = (x: Unit | string) => (isString(x) ? asUnit(x) : x); + +/** @internal */ +const __oneHot = (x: number) => { + const dims = new Array(7).fill(0); + dims[x] = 1; + return dims; +}; diff --git a/packages/units/src/volume.ts b/packages/units/src/volume.ts new file mode 100644 index 0000000000..b4e4fb7165 --- /dev/null +++ b/packages/units/src/volume.ts @@ -0,0 +1,27 @@ +import { CM, KM, M, MM } from "./length.js"; +import { defUnit, mul, pow, prefix } from "./unit.js"; + +export const M3 = defUnit("m3", "cubic meter", pow(M, 3)); +export const MM3 = defUnit("mm3", "cubic millimeter", pow(MM, 3)); +export const CM3 = defUnit("cm3", "cubic centimeter", pow(CM, 3)); +export const KM3 = defUnit("km3", "cubic kilometer", pow(KM, 3)); +export const L = defUnit("l", "liter", mul(M3, 1e-3, true)); +export const CL = defUnit("cl", "centiliter", prefix("c", L)); +export const ML = defUnit("ml", "milliliter", prefix("m", L)); + +export const GAL = defUnit("imp gal", "imperial gallon", mul(L, 4.54609)); +export const PT = defUnit("imp pt", "imperial pint", mul(GAL, 1 / 8)); +export const FLOZ = defUnit( + "imp fl oz", + "imperial fluid ounce", + mul(GAL, 1 / 160) +); + +export const US_GAL = defUnit("us gal", "us gallon", mul(L, 3.785411784)); +export const US_PT = defUnit("us pt", "us pint", mul(US_GAL, 1 / 8)); +export const US_CUP = defUnit("us cup", "us cup", mul(US_GAL, 1 / 16)); +export const US_FLOZ = defUnit( + "us fl oz", + "us fluid ounce", + mul(US_GAL, 1 / 128) +); diff --git a/packages/units/test/index.ts b/packages/units/test/index.ts new file mode 100644 index 0000000000..d894436529 --- /dev/null +++ b/packages/units/test/index.ts @@ -0,0 +1,50 @@ +import { eqDelta } from "@thi.ng/math"; +import { group } from "@thi.ng/testament"; +import * as assert from "assert"; +import { + ARCMIN, + CM, + convert, + DEG, + FT, + GON, + IN, + KM, + M, + MI, + MM, + NM, + RAD, + TURN, + Unit, + YD, +} from "../src/index.js"; + +const PI = Math.PI; +const TAU = 2 * PI; + +const check = (x: number, src: Unit, y: number, dest: Unit) => { + const res = convert(x, src, dest); + assert.ok(eqDelta(res, y), `${x} => ${res} (expected: ${y})`); +}; + +group("units", { + angle: () => { + check(1, RAD, 180 / PI, DEG); + check(TAU, RAD, 1, TURN); + check(360, DEG, 400, GON); + check(1 / 60, DEG, 1, ARCMIN); + }, + + length: () => { + check(1, M, 1000, MM); + check(1000, MM, 1, M); + check(1, M, 100, CM); + check(1, KM, 1000, M); + check(25.4e6, NM, 1, IN); + check(25.4, MM, 1, IN); + check(12, IN, 1, FT); + check(36, IN, 1, YD); + check(1760 * 36, IN, 1, MI); + }, +}); diff --git a/packages/units/tpl.readme.md b/packages/units/tpl.readme.md new file mode 100644 index 0000000000..976c06d6f7 --- /dev/null +++ b/packages/units/tpl.readme.md @@ -0,0 +1,340 @@ + + + + +## About + +{{pkg.description}} + +All unit definitions & conversions are based on the SI units described here: + +- https://en.wikipedia.org/wiki/International_System_of_Units + +### Unit definitions + +Each unit is defined via a 7-dimensional vector representing individual exponents +for each of the base SI dimensions, in order: + +| SI dimension | Base unit | Base unit symbol | +|---------------------|-----------|------------------| +| mass | kilogram | kg | +| length | meter | m | +| time | second | s | +| current | ampere | A | +| temperature | kelvin | K | +| amount of substance | mole | mol | +| luminous intensity | candela | cd | + +Dimensionless units are supported too, and for those all dimensions are set to +zero. + +Additionally, we also define a scale factor and zero offset for each unit, with +most dimensions' base units usually using a factor of 1 and no offset. + +For example, here's how we can define kilograms and meters: + +```ts +const KG = unit(1, 1); // SI dimension 0 +// { dim: [ 1, 0, 0, 0, 0, 0, 0 ], scale: 1, offset: 0, prefix: false } + +const M = unit(1, 1); // SI dimension 1 +// { dim: [ 0, 1, 0, 0, 0, 0, 0 ], scale: 1, offset: 0, prefix: false } +``` + +More complex units like electrical resistance (e.g. kΩ) are based on more than a +single dimension: + +```ts +// { dim: [ 1, 2, -3, -2, 0, 0, 0 ], scale: 1000, offset: 0, prefix: true } +``` + +This dimension vector represents the unit definition for (see [SI derived +units](https://en.wikipedia.org/wiki/SI_derived_unit)): + +> 1 Ohm = kg⋅m2⋅s−3⋅A−2 + +### Predefined units + +The following units are provided as "builtins", here grouped by dimension: + +#### Acceleration + +| Unit name | JS name | Description | +|-----------|----------|---------------------------| +| `m/s2` | `M_S2` | meter per second squared | +| `ft/s2` | `FT_S2` | foot per second squared | +| `rad/s2` | `RAD_S2` | radian per second squared | +| `g0` | `G0` | standard gravity | + +#### Angle + +| Unit name | JS name | Description | +|-----------|----------|-------------| +| `arcmin` | `ARCMIN` | arc minute | +| `arcsec` | `ARCSEC` | arc second | +| `deg` | `DEG` | degree | +| `gon` | `GON` | gradian | +| `rad` | `RAD` | radian | +| `sr` | `SR` | steradian | +| `turn` | `TURN` | turn | + +#### Area + +| Unit name | JS name | Description | +|-----------|---------|-------------------| +| `m2` | `M2` | square meter | +| `cm2` | `CM2` | square centimeter | +| `mm2` | `MM2` | square millimeter | +| `km2` | `KM2` | square kilometer | +| `ha` | `HA` | hectar | +| `ac` | `AC` | acre | +| `sqin` | `SQIN` | square inch | +| `sqft` | `SQFT` | square foot | +| `sqmi` | `SQMI` | square mile | + +#### Data + +| Unit name | JS name | Description | +|-----------|---------|-----------------| +| `bit` | `BIT` | bit | +| `kbit` | `KBIT` | kilobit | +| `Mbit` | `MBIT` | megabit | +| `Gbit` | `GBIT` | gigabit | +| `Tbit` | `TBIT` | terabit | +| `B` | `BYTE` | byte (8 bit) | +| `KB` | `KBYTE` | kilobyte (1024) | +| `MB` | `MBYTE` | megabyte (1024) | +| `GB` | `GBYTE` | gigabyte (1024) | +| `TB` | `TBYTE` | terabyte (1024) | +| `PB` | `PBYTE` | petabyte (1024) | +| `EB` | `EBYTE` | exabyte (1024) | + +#### Electric current + +| Unit | JS name | Description | +|------|-----------|-------------------| +| A | `A` | ampere | +| mA | `MA` | milliampere | +| mAh | `MA_H` | milliampere-hours | +| C | `C` | coulomb | +| V | `V` | volt | +| mV | `MV` | millivolt | +| kV | `KV` | kilovolt | +| F | `F` | farad | +| pF | `PF` | picofarad | +| µF | `µF` | microfarad | +| Ω | `OHM` | ohm | +| kΩ | `KOHM` | kiloohm | +| MΩ | `MOHM` | megaohm | +| GΩ | `GOHM` | gigaohm | +| S | `SIEMENS` | siemens | +| Wb | `WB` | weber | +| T | `TESLA` | tesla | +| H | `HENRY` | henry | + +#### Energy + +| Unit | JS name | Description | +|------|---------|-------------| +| J | `J` | joule | +| kJ | `KJ` | kilojoule | +| MJ | `MJ` | megajoule | +| GJ | `GJ` | gigajoule | +| cal | `CAL` | calorie | +| kcal | `KCAL` | kilocalorie | + +#### Force + +| Unit | JS name | Description | +|------|---------|-------------| +| `N` | `N` | newton | + +#### Frequency + +| Unit | JS name | Description | +|-------|---------|---------------------| +| `Hz` | `HZ` | hertz | +| `kHz` | `KHZ` | kilohertz | +| `MHz` | `MHZ` | megahertz | +| `GHz` | `GHZ` | gigahertz | +| `THz` | `THZ` | terahertz | +| `rpm` | `RPM` | rotation per minute | +| `ω` | `OMEGA` | radian per second | + +#### Length + +| Unit name | JS name | Description | +|-----------|---------|-------------------| +| `m` | `M` | meter | +| `cm` | `CM` | centimeter | +| `mm` | `MM` | millimeter | +| `µm` | `µM` | micrometer | +| `nm` | `NM` | nanometer | +| `km` | `KM` | kilometer | +| `au` | `AU` | astronomical unit | +| `in` | `IN` | inch | +| `ft` | `FT` | foot | +| `yd` | `YD` | yard | +| `mi` | `MI` | mile | +| `nmi` | `NMI` | nautical mile | +| `pica` | `PICA` | pica | +| `point` | `POINT` | point | + +#### Luminous intensity + +| Unit | JS name | Description | +|------|---------|-------------| +| `cd` | `CD` | candela | +| `lm` | `LM` | lumen | +| `lx` | `LX` | lux | + +#### Mass + +| Unit name | JS name | Description | +|-----------|---------|----------------| +| `µg` | `µG` | microgram | +| `mg` | `mG` | milligram | +| `g` | `G` | gram | +| `kg` | `KG` | kilogram | +| `t` | `T` | tonne | +| `kt` | `KT` | kilotonne | +| `Mt` | `MT` | megatonne | +| `Gt` | `GT` | gigatonne | +| `lb` | `LB` | imperial pound | +| `st` | `ST` | stone | + +#### Power + +| Unit name | JS name | Description | +|-----------|---------|---------------| +| `W` | `W` | watt | +| `mW` | `MW` | milliwatt | +| `kW` | `KW` | kilowatt | +| `MW` | `MW` | megawatt | +| `GW` | `GW` | gigawatt | +| `TW` | `TW` | terawatt | +| `Wh` | `WH` | watt-hour | +| `kWh` | `KWH` | kilowatt-hour | + +#### Pressure + +| Unit name | JS name | Description | +|-----------|---------|-----------------------| +| `Pa` | `PA` | pascal | +| `kPa` | `KPA` | kilopascal | +| `MPa` | `MPA` | megapascal | +| `GPa` | `GPA` | gigapascal | +| `at` | `AT` | technical atmosphere | +| `atm` | `ATM` | atmosphere | +| `bar` | `BAR` | bar | +| `psi` | `PSI` | pound per square inch | + +#### Speed + +| Unit | JS name | Description | +|--------|---------|--------------------| +| `m/s` | `M_S` | meter per second | +| `km/h` | `KM_H` | kilometer per hour | +| `mph` | `MPH` | mile per hour | +| `kn` | `KN` | knot | + +#### Substance + +| Unit | JS name | Description | +|-------|---------|-------------| +| `mol` | `MOL` | mole | + +#### Temperature + +| Unit | JS name | Description | +|------|---------|-------------------| +| `K` | `K` | kelvin | +| `℃` | `DEG_C` | degree celsius | +| `℉` | `DEG_F` | degree fahrenheit | + +#### Time + +| Unit | JS name | Description | +|-------|---------|--------------------| +| s | `S` | second | +| ms | `MS` | millisecond | +| µs | `µS` | microsecond | +| ns | `NS` | nanosecond | +| min | `MIN` | minute | +| h | `H` | hour | +| day | `DAY` | day | +| week | `WEEK` | week | +| month | `MONTH` | month (30 days) | +| year | `YEAR` | year (365.25 days) | + +#### Volume + +| Unit | JS name | Description | +|-------------|-----------|----------------------| +| `m3` | `M3` | cubic meter | +| `mm3` | `MM3` | cubic millimeter | +| `cm3` | `CM3` | cubic centimeter | +| `km3` | `KM3` | cubic kilometer | +| `l` | `L` | liter | +| `cl` | `CL` | centiliter | +| `ml` | `ML` | milliliter | +| `imp gal` | `GAL` | imperial gallon | +| `imp pt` | `PT` | imperial pint | +| `imp fl oz` | `FLOZ` | imperial fluid ounce | +| `us gal` | `US_GAL` | US gallon | +| `us pt` | `US_PT` | US pint | +| `us cup` | `US_CUP` | US cup | +| `us fl oz` | `US_FLOZ` | US fluid ounce | + +### Creating & deriving units + +#### Using standard metric prefixes + +Existing coherent + +### Unit conversions + +Only units with compatible (incl. reciprocal) dimensions can be converted, +otherwise an error will be thrown. Units can be given as + +```ts +// convert from km/h to mph using unit names +convert(100, "km/h", "mph"); +// 62.13711922373341 + +// using predefined unit constants directly +convert(60, MPH, KM_H); +// 96.56063999999998 + +// or using anonymous units (meter/second ⇒ yard/hour) +convert(1, "m/s", div(YD, H)) +// 3937.007874015749 +``` + +{{meta.status}} + +{{repo.supportPackages}} + +{{repo.relatedPackages}} + +{{meta.blogPosts}} + +## Installation + +{{pkg.install}} + +{{pkg.size}} + +## Dependencies + +{{pkg.deps}} + +{{repo.examples}} + +## API + +{{pkg.docs}} + +TODO + + diff --git a/packages/units/tsconfig.json b/packages/units/tsconfig.json new file mode 100644 index 0000000000..e19642bf9a --- /dev/null +++ b/packages/units/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": ["./src/**/*.ts"] +} diff --git a/yarn.lock b/yarn.lock index ad66bdc3cb..6cc847b4ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5419,6 +5419,22 @@ __metadata: languageName: unknown linkType: soft +"@thi.ng/units@workspace:packages/units": + version: 0.0.0-use.local + resolution: "@thi.ng/units@workspace:packages/units" + dependencies: + "@microsoft/api-extractor": ^7.34.4 + "@thi.ng/checks": ^3.3.9 + "@thi.ng/equiv": ^2.1.19 + "@thi.ng/errors": ^2.2.12 + "@thi.ng/testament": ^0.3.12 + rimraf: ^4.4.0 + tools: "workspace:^" + typedoc: ^0.23.26 + typescript: ^4.9.5 + languageName: unknown + linkType: soft + "@thi.ng/vclock@workspace:packages/vclock": version: 0.0.0-use.local resolution: "@thi.ng/vclock@workspace:packages/vclock"