UnitValues 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Brody Howard
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: UnitValues
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A small package for using units in Python. Allows for computation with dimensioned qauntities and allows you to convert between different units. Do anything you would do with regular floats or integers in Python except now with units. I think you'll find this implementation more elegant then most other unit packages.
|
|
5
|
+
Author-email: Brody Howard <brody.howard@mail.utoronto.ca>
|
|
6
|
+
Project-URL: Homepage, https://github.com/BroRocket/unitvalue/
|
|
7
|
+
Project-URL: Issues, https://github.com/BroRocket/unitvalue/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
|
|
15
|
+
# UnitValue Package
|
|
16
|
+
## Introduction
|
|
17
|
+
The UnitValue package is designed to allow easy manipulation and conversion of numerical values associated with units. It currently supports both Imperial and Metric systems and includes commonly used units, with the flexibility to add more units as needed. It sworks with numpy as well.
|
|
18
|
+
|
|
19
|
+
This package is ideal for applications that require precise unit conversions and tracking of units alongside numerical values.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
To install the UnitValue package, you can use the following command:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install UnitValue
|
|
26
|
+
```
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Create `UnitValue` objects with specified units and values.
|
|
30
|
+
- Convert between different units within the same dimension (e.g., meters to kilometers).
|
|
31
|
+
- Support for different measurement systems (e.g., metric and imperial).
|
|
32
|
+
- Arithmetic operations with unit handling (addition, subtraction, multiplication, division).
|
|
33
|
+
- Error handling for unsupported units and invalid operations.
|
|
34
|
+
|
|
35
|
+
## Class and Methods Overview
|
|
36
|
+
|
|
37
|
+
`UnitValue`
|
|
38
|
+
Attributes:
|
|
39
|
+
- value (float): The numerical value.
|
|
40
|
+
- unit (str): The unit associated with the value.
|
|
41
|
+
- system (str): The measurement system (e.g., "METRIC", "IMPERIAL").
|
|
42
|
+
- dimension (str): The dimension of the unit (e.g., "LENGTH", "MASS").
|
|
43
|
+
Methods:
|
|
44
|
+
- __init__(self, value: float, unit: str): Initializes a UnitValue object.
|
|
45
|
+
- create_unit(cls, unit: str, value: float=0) -> 'UnitValue': Creates unit object.
|
|
46
|
+
- to(self, target_unit: str) -> 'UnitValue': Converts the current value to the specified unit.
|
|
47
|
+
- convert_base_metric(self) -> 'UnitValue': Convert the current value to it's base SI/Metric unit.
|
|
48
|
+
- add_custom_unitadd_custom_unit(cls, system: str, dimension:str, unit:str, conversion_factor: float) -> None: Allows you to add your own units to your instance of the package.
|
|
49
|
+
- available_units(cls, system: str="", dimension: str="") -> str: Get available units for the specified measurement system and dimension.
|
|
50
|
+
- unit_from_string(cls, unit_str:str) -> 'UnitValue': Create a UnitValue object from a formatted string (e.g., '10 m').
|
|
51
|
+
- from_dict(cls, data: dict) -> 'UnitValue': Create a UnitValue object from a dictionary.
|
|
52
|
+
- to_dict(self) -> dict: Convert the UnitValue object to a dictionary.
|
|
53
|
+
Properties:
|
|
54
|
+
- get_unit: returns unit string.
|
|
55
|
+
- get_dimension: return dimension string.
|
|
56
|
+
- get_system: returns system string.
|
|
57
|
+
|
|
58
|
+
## Supported Units and Conversions
|
|
59
|
+
|
|
60
|
+
The package supports a wide range of units, including but not limited to:
|
|
61
|
+
|
|
62
|
+
### METRIC:
|
|
63
|
+
DISTANCE: ['m', 'km', 'cm', 'mm']
|
|
64
|
+
PRESSURE: ['kg/ms^2', 'MPa', 'bar', 'kPa', 'hPa', 'Pa']
|
|
65
|
+
MASS: ['kg', 'tonne', 'g']
|
|
66
|
+
VELOCITY: ['m/s', 'km/s', 'km/h', 'cm/s', 'mm/s']
|
|
67
|
+
DENSITY: ['kg/m^3', 't/m^3', 'g/m^3']
|
|
68
|
+
VOLUME: ['m^3', 'L', 'cm^3', 'mL', 'mm^3']
|
|
69
|
+
AREA: ['m^2', 'km^2', 'cm^2', 'mm^2']
|
|
70
|
+
TEMPERATURE: ['K', 'c']
|
|
71
|
+
MASS FLOW RATE: ['kg/s', 't/s', 'kg/min', 'g/s']
|
|
72
|
+
ENERGY: ['kgm^2/s^2', 'MJ', 'kJ', 'Nm', 'J', 'eV']
|
|
73
|
+
TIME: ['s', 'h', 'min', 'ms']
|
|
74
|
+
MOMENTUM: ['kgm/s', 'Ns']
|
|
75
|
+
FREQUENCY: ['/s', 'Hz']
|
|
76
|
+
ACCELERATION: ['m/s^2', 'g']
|
|
77
|
+
FORCE: ['kgm/s^2', 'N', 'gcm/s^2']
|
|
78
|
+
ENERGY PER UNIT MASS: ['m^2/s^2']
|
|
79
|
+
MASS PER LENGTH: ['kg/m', 'kg/cm', 'g/cm']
|
|
80
|
+
MASS PER AREA: ['kg/m^2', 'g/cm^2']
|
|
81
|
+
VOLUMETRIC FLOW RATE: ['m^3/s', 'cm^3/s']
|
|
82
|
+
DYNAMIC VISCOCITY: ['kg/ms', 'g/cms']
|
|
83
|
+
KINEMATIC VISCOCITY: ['m^2/s', 'cm^2/s']
|
|
84
|
+
MASS FLUX: ['kg/m^2s']
|
|
85
|
+
|
|
86
|
+
### IMPERIAL:
|
|
87
|
+
DISTANCE: ['in', 'mi', 'yd', 'ft']
|
|
88
|
+
PRESSURE: ['psi', 'psf']
|
|
89
|
+
MASS: ['lb', 'ton', 'slug', 'st', 'oz']
|
|
90
|
+
VELOCITY: ['ft/s', 'mi/s', 'mph', 'in/s']
|
|
91
|
+
DENSITY: ['lb/in^3', 'lb/ft^3', 'lb/yd^3']
|
|
92
|
+
VOLUME: ['gal', 'yd^3', 'ft^3', 'in^3']
|
|
93
|
+
AREA: ['in^2', 'mi^2', 'yd^2', 'ft^2']
|
|
94
|
+
TEMPERATURE: ['f', 'R']
|
|
95
|
+
MASS FLOW RATE: ['lb/s', 'ton/s', 'st/s', 'oz', 'lb/min']
|
|
96
|
+
ENERGY: ['ftlb', 'kcal', 'cal']
|
|
97
|
+
TIME: ['s', 'h', 'min', 'ms']
|
|
98
|
+
MOMENTUM: ['slugft/s', 'lbft/s']
|
|
99
|
+
FREQUENCY: ['rpm']
|
|
100
|
+
ACCELERATION: ['ft/s^2']
|
|
101
|
+
FORCE: ['lbf']
|
|
102
|
+
ENERGY PER UNIT MASS: ['ft^2/s^2']
|
|
103
|
+
MASS PER UNIT LENGTH: ['lb/ft', 'oz/in']
|
|
104
|
+
MASS PER AREA: ['lb/ft^2']
|
|
105
|
+
VOLUMETRIC FLOW RATE: ['ft^3/s', 'gal/s']
|
|
106
|
+
DYNAMIC VISCOCITY: ['lb/fts']
|
|
107
|
+
KINEMATIC VISCOCITY: ['ft^2/s']
|
|
108
|
+
MASS FLUX: ['lb/ft^2s']
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## Example Usage
|
|
112
|
+
|
|
113
|
+
### Creating a UnitValue
|
|
114
|
+
|
|
115
|
+
To create a `UnitValue` object, use the `create_dimensioned_quantity` function:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from unitvalue import UnitValue
|
|
119
|
+
|
|
120
|
+
# Create a UnitValue object with a specified unit and value
|
|
121
|
+
distance = UnitValue.create_unit('meter', 100)
|
|
122
|
+
# Or initialize instance yourself
|
|
123
|
+
distance = UnitValue("METRIC", "DISTANCE", "m", 100)
|
|
124
|
+
# or initialize a unit from a string
|
|
125
|
+
distance = UnitValue.unit_from_string('100 m')
|
|
126
|
+
# or initialize from a dictionary
|
|
127
|
+
data = {'system':'METRIC', 'dimension':'DISTANCE', 'unit':'m', 'value':100}
|
|
128
|
+
distance = UnitValue.from_dict(data)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Arithmetic Operations
|
|
132
|
+
|
|
133
|
+
`UnitValue` objects support arithmetic operations, maintaining unit consistency (The units do not even need to be in the same system or magnitude for you to perform arithmetic on them as the package will handle this). It is important to know all arithmetic operations return a value in the base metric units
|
|
134
|
+
|
|
135
|
+
### Converting Units
|
|
136
|
+
|
|
137
|
+
You can convert the unit of a `UnitValue` object using the `to` method:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# Convert the distance to kilometers
|
|
141
|
+
distance.to(unit='kilometer')
|
|
142
|
+
print(distance) # Output: 0.1 kilometer
|
|
143
|
+
|
|
144
|
+
# Convert to base metric unit (Useful for Scinetifc calculations)
|
|
145
|
+
distance.convert_base_metric()
|
|
146
|
+
print(distance)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Basic Example
|
|
150
|
+
```python
|
|
151
|
+
from unitvalue import UnitValue
|
|
152
|
+
|
|
153
|
+
length1 = UnitValue.create_unit('meter', 50)
|
|
154
|
+
length2 = UnitValue.create_unit('meter', 30)
|
|
155
|
+
|
|
156
|
+
# Addition
|
|
157
|
+
total_length = length1 + length2
|
|
158
|
+
print(total_length) # Output: 80 m
|
|
159
|
+
|
|
160
|
+
# Subtraction
|
|
161
|
+
remaining_length = length1 - length2
|
|
162
|
+
print(remaining_length) # Output: 20 m
|
|
163
|
+
|
|
164
|
+
# Multiplication by a scalar
|
|
165
|
+
double_length = length1 * 2
|
|
166
|
+
print(double_length) # Output: 100 m
|
|
167
|
+
|
|
168
|
+
# Division by a scalar
|
|
169
|
+
half_length = length1 / 2
|
|
170
|
+
print(half_length) # Output: 25 m
|
|
171
|
+
|
|
172
|
+
# Multiplication between UnitValue Objects
|
|
173
|
+
area = lenght1 * length2
|
|
174
|
+
print(area) # Output: 150 m^2
|
|
175
|
+
|
|
176
|
+
# Divivsion between UnitValue Objects
|
|
177
|
+
l = area / length2
|
|
178
|
+
print(l) # Output: 50 m
|
|
179
|
+
|
|
180
|
+
# UnitValue object to the power
|
|
181
|
+
werid unit = lenght1**3.5
|
|
182
|
+
print(weird_unit) # Output: 125000 m^3
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Contributing
|
|
186
|
+
Contributions are welcome! If you find any issues or have ideas for enhancements, feel free to open an issue or submit a pull request on GitHub.
|
|
187
|
+
1. Fork the repository.
|
|
188
|
+
2. Create a new branch (git checkout -b feature-branch).
|
|
189
|
+
3. Make your changes.
|
|
190
|
+
4. Commit your changes (git commit -am 'Add new feature').
|
|
191
|
+
5. Push to the branch (git push origin feature-branch).
|
|
192
|
+
6. Open a Pull Request.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
This package is distributed under the MIT License. See LICENSE for more information.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
unitvalue/__init__.py,sha256=DI1WASYpVFo9rG6svmHnOxyetgTyzoUQmvCyXHi-PzA,22
|
|
2
|
+
unitvalue/units.py,sha256=WnAIBqiihvbVrETXaox5n2Lur13R0d7Pa39E4d2IA4c,38336
|
|
3
|
+
UnitValues-0.1.0.dist-info/LICENSE,sha256=jJBt92gwJCJ_MZ0MWcIghXEPEAKlu38V0BIE0ZomvkQ,1091
|
|
4
|
+
UnitValues-0.1.0.dist-info/METADATA,sha256=8o1to9vX5wXnmWIVoVP3A-G1uGfPySQCADPKSJD06NI,7750
|
|
5
|
+
UnitValues-0.1.0.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
|
6
|
+
UnitValues-0.1.0.dist-info/top_level.txt,sha256=U7s7jA9rQlt2zcXlcBLuLHTyaYWJ2ehGqf2sDFoN82U,10
|
|
7
|
+
UnitValues-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
unitvalue
|
unitvalue/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .units import *
|
unitvalue/units.py
ADDED
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
'''
|
|
2
|
+
This module allows you to easly store numbers with units and easily convert the units and value of the numbers.
|
|
3
|
+
The two supported systems currently are Imperial and Metric.
|
|
4
|
+
Typically used units are supported and more can be added upon request or in your instance.
|
|
5
|
+
'''
|
|
6
|
+
import warnings
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
class UnitValue:
|
|
10
|
+
"""
|
|
11
|
+
Represents a value with dimension/units.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
__system (str): The measurement system (e.g., METRIC, IMPERIAL).
|
|
15
|
+
__dimension (str): The dimension of the unit (e.g., LENGTH, MASS).
|
|
16
|
+
__unit (str): The unit of the value (e.g., m, kg).
|
|
17
|
+
value (float): The numerical value.
|
|
18
|
+
"""
|
|
19
|
+
UNITS = {"IMPERIAL": {
|
|
20
|
+
"DISTANCE": {"in": 0.0254, "mi": 1609.34, "yd": 0.9144, "ft": 0.3048},
|
|
21
|
+
"PRESSURE": {"psi": 6894.76, "psf": 47.8803},
|
|
22
|
+
"MASS": {"lb": 0.453592, "ton": 907.1847, "slug": 14.59390, "st": 6.35029, "oz": 0.0283495},
|
|
23
|
+
"VELOCITY": {"ft/s": 0.3048, "mi/s": 1609.34, "mph": 0.44704, "in/s": 0.0254},
|
|
24
|
+
"DENSITY": {"lb/in^3": 27679.9, "lb/ft^3": 16.0185, "lb/yd^3": 0.593276},
|
|
25
|
+
"VOLUME": {"gal": 0.00378541, "yd^3": 0.764555, "ft^3": 0.0283168, "in^3": 0.0000163871},
|
|
26
|
+
"AREA": {"in^2": 0.00064516, "mi^2": 2590000, "yd^2": 0.836127, "ft^2": 0.092903},
|
|
27
|
+
"TEMPERATURE": {"f": None, "R":None},
|
|
28
|
+
"MASS FLOW RATE": {"lb/s": 0.453592, "ton/s": 907.1847, "st/s": 6.35029, "oz": 0.0283495, "lb/min": 0.00755987},
|
|
29
|
+
"ENERGY": {"ftlb": 1.35582, "kcal": 4184, "cal": 4.184},
|
|
30
|
+
"TIME": {"s": 1, "h": 3600, "min": 60, "ms": 0.001},
|
|
31
|
+
"MOMENTUM": {"slugft/s": 4.449670915354, "lbft/s": 0.1383},
|
|
32
|
+
"FREQUENCY": {"rpm": 0.016667},
|
|
33
|
+
"ACCELERATION": {"ft/s^2": 0.3048},
|
|
34
|
+
"FORCE": {"lbf": 4.44822},
|
|
35
|
+
"ENERGY PER UNIT MASS": {"ft^2/s^2": 0.09290304},
|
|
36
|
+
"MASS PER UNIT LENGTH": {"lb/ft": 1.48816, "oz/in": 1.11612},
|
|
37
|
+
"MASS PER AREA": {"lb/ft^2": 4.88243},
|
|
38
|
+
"VOLUMETRIC FLOW RATE": {"ft^3/s": 0.0283168, "gal/s": 0.00378541},
|
|
39
|
+
"DYNAMIC VISCOCITY": {"lb/fts": 1.488163943568},
|
|
40
|
+
"KINEMATIC VISCOCITY": {"ft^2/s": 0.092903},
|
|
41
|
+
"MASS FLUX": {"lb/ft^2s": 4.88243}
|
|
42
|
+
},
|
|
43
|
+
"METRIC": {
|
|
44
|
+
"DISTANCE": {"m": 1, "km": 1000, "cm": 0.01, "mm": 0.001},
|
|
45
|
+
"PRESSURE": {"kg/ms^2": 1, "MPa": 1000000, "bar": 100000, "kPa": 1000, "hPa": 100, "Pa": 1},
|
|
46
|
+
"MASS": {"kg": 1, "tonne": 1000, "g": 0.001},
|
|
47
|
+
"VELOCITY": {"m/s": 1, "km/s": 1000, "km/h": 0.277778, "cm/s": 0.01, "mm/s": 0.001},
|
|
48
|
+
"DENSITY": {"kg/m^3": 1, "t/m^3": 1000, "g/m^3": 0.001},
|
|
49
|
+
"VOLUME": {"m^3": 1, "L": 0.001, "cm^3": 0.000001, "mL": 0.000001, "mm^3": 0.000000001},
|
|
50
|
+
"AREA": {"m^2": 1, "km^2": 1000000, "cm^2": 0.0001, "mm^2": 0.000001},
|
|
51
|
+
"TEMPERATURE": {"K": None, "c": None},
|
|
52
|
+
"MASS FLOW RATE": {"kg/s": 1, "t/s": 1000, "kg/min": 0.0166667, "g/s": 0.001},
|
|
53
|
+
"ENERGY": {"kgm^2/s^2": 1, "MJ": 1000000, "kJ": 1000, "Nm": 1, "J": 1, "eV": 1.602177e-19},
|
|
54
|
+
"TIME": {"s": 1, "h": 3600, "min": 60, "ms": 0.001},
|
|
55
|
+
"MOMENTUM": {"kgm/s": 1, "Ns": 1},
|
|
56
|
+
"FREQUENCY": {"/s": 1, "Hz": 1},
|
|
57
|
+
"ACCELERATION": {"m/s^2": 1, "g": 9.80665},
|
|
58
|
+
"FORCE": {"kgm/s^2": 1, "N": 1, "gcm/s^2": 0.00001},
|
|
59
|
+
"ENERGY PER UNIT MASS": {"m^2/s^2": 1},
|
|
60
|
+
"MASS PER LENGTH": {"kg/m": 1, "kg/cm": 100, "g/cm": 0.1},
|
|
61
|
+
"MASS PER AREA": {"kg/m^2": 1, "g/cm^2": 10},
|
|
62
|
+
"VOLUMETRIC FLOW RATE": {"m^3/s": 1, "cm^3/s": 0.000001},
|
|
63
|
+
"DYNAMIC VISCOCITY": {"kg/ms": 1, "g/cms":0.1},
|
|
64
|
+
"KINEMATIC VISCOCITY": {"m^2/s": 1, "cm^2/s": 0.0001},
|
|
65
|
+
"MASS FLUX": {"kg/m^2s": 1}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
SPELLING_MAP = {'mtr': 'm', 'meters': 'm', 'mtr.': 'm', 'metres': 'm', 'metre': 'm', 'm ': 'm', 'meter': 'm', 'kilometer': 'km', 'kilometre': 'km', 'kilo meter': 'km', 'k meter': 'km', 'klometer': 'km', 'km ': 'km', 'kmeter': 'km', 'kmtr': 'km', 'centemeter': 'cm', 'centimetre': 'cm', 'cn': 'cm', 'c meter': 'cm', 'cm ': 'cm', 'centi meter': 'cm', 'centimeter': 'cm', 'cmeter': 'cm', 'cmtr': 'cm', 'mmeter': 'mm', 'millimeter': 'mm', 'milimeter': 'mm', 'milli meter': 'mm', 'millimetre': 'mm', 'm meter': 'mm', 'mn': 'mm', 'mmtr': 'mm', 'mm ': 'mm', 'g ': 'g', 'gramme': 'g', 'grm': 'g', 'gms': 'g', 'gram': 'g', 'gm': 'g', 'gramm': 'g', 'gram ': 'g', 'kilograms': 'kg', 'k gram': 'kg', 'kilogramme': 'kg', 'kilogram ': 'kg', 'kilogramme ': 'kg', 'kilogrammes': 'kg', 'kg ': 'kg', 'kilogram': 'kg', 'kilo gram': 'kg', 'kgrm': 'kg', 'liters': 'L', 'l': 'L', 'litres': 'L', 'ltrs': 'L', 'liter': 'L', 'litre ': 'L', 'litre': 'L', 'liters ': 'L', 'liter ': 'L', 'litter': 'L', 'ltr': 'L', 'ml': 'mL', 'milliliter': 'mL', 'mlitres': 'mL', 'millilitre': 'mL', 'milli litre': 'mL', 'mliters': 'mL', 'milli liter': 'mL', 'mltr': 'mL', 'sec': 's', 'seconds': 's', 'sedonds': 's', 'sconds': 's', 'second': 's', 'secs': 's', 'secnd': 's', 'minute': 'min', 'minuts': 'min', 'mins': 'min', 'minutes': 'min', 'min': 'min', 'minut': 'min', 'hours': 'h', 'hr': 'h', 'howr': 'h', 'houer': 'h', 'hour': 'h', 'hou': 'h', 'hrs': 'h', 'square meters': 'm^2', 'square meter': 'm^2', 'sqr meter': 'm^2', 'square metre': 'm^2', 'sq meter': 'm^2', 'sqmetre': 'm^2', 'sq metres': 'm^2', 'sqm': 'm^2', 'cube meter': 'm^3', 'cu meter': 'm^3', 'cbm': 'm^3', 'cubic meters': 'm^3', 'cubic metre': 'm^3', 'cubic meter': 'm^3', 'cu m': 'm^3', 'degree centigrade': 'c', 'degree celsius': 'c', 'celcius': 'c', 'deg celsius': 'c', 'celsius degree': 'c', 'celsus': 'c', 'celsius': 'c', 'deg c': 'c', 'fahrenheit degree': 'f', 'deg f': 'f', 'deg fahrenheit': 'f', 'fahrenhiet': 'f', 'degree fahrenheit': 'f', 'farhenheit': 'f', 'force': 'kgm/s^2', 'Newton': 'kgm/s^2', 'newtn': 'kgm/s^2', 'nwton': 'kgm/s^2', 'nwt': 'kgm/s^2', 'newton': 'kgm/s^2', 'joule': 'kgm^2/s^2', 'energy': 'kgm^2/s^2', 'juole': 'kgm^2/s^2', 'jl': 'kgm^2/s^2', 'joules': 'kgm^2/s^2', 'juul': 'kgm^2/s^2', 'inck': 'in', 'inche': 'in', 'inch': 'in', 'inc': 'in', 'inch ': 'in', 'inchh': 'in', 'foot': 'ft', 'foott': 'ft', 'fot': 'ft', 'foor': 'ft', 'foot ': 'ft', 'feet': 'ft', 'yardd': 'yd', 'yad': 'yd', 'yarrd': 'yd', 'yard ': 'yd', 'yard': 'yd', 'mille': 'mi', 'mile ': 'mi', 'milee': 'mi', 'mil': 'mi', 'mile': 'mi', 'pounds': 'lb', 'pounnd': 'lb', 'pound ': 'lb', 'pound': 'lb', 'lbs': 'lb', 'poundd': 'lb', 'galln': 'gal', 'gllon': 'gal', 'gallon': 'gal', 'galllon': 'gal', 'gallon ': 'gal', 'ounze': 'oz', 'ouncce': 'oz', 'ounce ': 'oz', 'ozs': 'oz', 'ounce': 'oz', 'ounc': 'oz', 'ounc ': 'oz', 'stn': 'st', 'stone ': 'st', 'sts': 'st', 'stone': 'st', 'st ': 'st', 'ft2': 'ft^2', 'square foot': 'ft^2', 'sqft': 'ft^2', 'square feet': 'ft^2', 'sq foot': 'ft^2', 'ft3': 'ft^3', 'cft': 'ft^3', 'cubic foot': 'ft^3', 'cu ft': 'ft^3', 'cubic feet': 'ft^3'}
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def add_custom_unit(cls, system: str, dimension:str, unit:str, conversion_factor: float) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Add your own units to the package instance whihc you can then use in conversion and arithmatic. Note there is a small chance new SI/Metric units will mess with unit conversion during arithmatic.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
system (str): The measurement system ('IMPERIAL' or 'METRIC').
|
|
78
|
+
dimension (str): The dimension of the unit (e.g., 'DISTANCE', 'PRESSURE', 'MASS').
|
|
79
|
+
unit (str): The string representing the new unit e.g., 'kg'
|
|
80
|
+
conversion_factor (float): The conversion factor to convert from this unit to the SI base for this units dimension. Must be > 0 or throws error.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
Exception: If the provided system or dimension is invalid, if the unit already exists or if the conversion factor is less than 0.
|
|
84
|
+
"""
|
|
85
|
+
if system == "METRIC" or system == "IMPERIAL":
|
|
86
|
+
if dimension not in cls.UNITS[system]:
|
|
87
|
+
raise ValueError(f"Dimension {dimension} is invalid must be one of {list(cls.UNITS[system].keys())}")
|
|
88
|
+
elif dimension == "TEMPERATURE":
|
|
89
|
+
raise NotImplementedError("Currently their is no support for adding custom temperature units")
|
|
90
|
+
elif unit in cls.UNITS[system][dimension]:
|
|
91
|
+
raise ValueError(f"Unit {unit} already exists")
|
|
92
|
+
elif conversion_factor <= 0:
|
|
93
|
+
raise ValueError(f"Conversion Factor {conversion_factor} invalid")
|
|
94
|
+
cls.UNITS[system][dimension][unit] = conversion_factor
|
|
95
|
+
raise ValueError(f"System {system} is invalid must be one of ('METRIC', 'IMPERIAL')")
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def available_units(cls, system: str="", dimension: str="") -> str:
|
|
99
|
+
"""
|
|
100
|
+
Get available units for the specified measurement system and dimension.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
system (str, optional): The measurement system ('IMPERIAL' or 'METRIC'). Defaults to an empty string, which returns units for all systems.
|
|
104
|
+
dimension (str, optional): The dimension of the unit (e.g., 'DISTANCE', 'PRESSURE', 'MASS'). Defaults to an empty string, which returns units for all dimensions.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
str: A string listing available units for the specified system and dimension.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
Exception: If the provided system or dimension is invalid.
|
|
111
|
+
"""
|
|
112
|
+
if system:
|
|
113
|
+
if system not in UnitValue.UNITS.keys():
|
|
114
|
+
raise Exception(f"Unit system {system} invalid: Must be 'IMPERIAL' or 'METRIC'")
|
|
115
|
+
if dimension:
|
|
116
|
+
if dimension not in UnitValue.UNITS[system].keys():
|
|
117
|
+
raise Exception(f"Measurement Type {dimension} invalid: Must be 'DISTANCE', 'PRESSURE', 'MASS', 'VELOCITY', 'DENSITY', 'VOLUME', 'AREA', 'TEMPERATURE', 'MASS FLOW RATE', 'ENERGY'")
|
|
118
|
+
return f"{system} {dimension}: {list(UnitValue.UNITS[system][dimension].keys())}"
|
|
119
|
+
else:
|
|
120
|
+
temp = ""
|
|
121
|
+
for key in UnitValue.UNITS[system].keys():
|
|
122
|
+
temp += f"{key}: {list(UnitValue.UNITS[system][key].keys())}\n"
|
|
123
|
+
return f"{system}:\n{temp}"
|
|
124
|
+
elif dimension:
|
|
125
|
+
if dimension not in UnitValue.UNITS["METRIC"].keys():
|
|
126
|
+
raise Exception(f"Measurement Type {dimension} invalid: Must be 'DISTANCE', 'PRESSURE', 'MASS', 'VELOCITY', 'DENSITY', 'VOLUME', 'AREA', 'TEMPERATURE', 'MASS FLOW RATE', 'ENERGY'")
|
|
127
|
+
return f"METRIC {dimension}: {list(UnitValue.UNITS['METRIC'][dimension].keys())}\nIMPERIAL {dimension}: {list(UnitValue.UNITS['IMPERIAL'][dimension].keys())}"
|
|
128
|
+
else:
|
|
129
|
+
temp = ""
|
|
130
|
+
for key in UnitValue.UNITS["METRIC"].keys():
|
|
131
|
+
temp += f"{key}: {list(UnitValue.UNITS['METRIC'][key].keys())}\n"
|
|
132
|
+
res = f"METRIC:\n{temp}IMPERIAL:\n"
|
|
133
|
+
temp = ""
|
|
134
|
+
for key in UnitValue.UNITS["IMPERIAL"].keys():
|
|
135
|
+
temp += f"{key}: {list(UnitValue.UNITS['IMPERIAL'][key].keys())}\n"
|
|
136
|
+
res += temp
|
|
137
|
+
return res
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def create_unit(cls, unit: str, value: float=0) -> 'UnitValue':
|
|
141
|
+
"""
|
|
142
|
+
Create a UnitValue object based on the unit and value. Will also account for speeling mistakes/alternate names of units.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
unit (str): The unit of the value.
|
|
146
|
+
value (float): The numerical value.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
UnitValue: A UnitValue object with the specified unit and value.
|
|
150
|
+
"""
|
|
151
|
+
u = cls.SPELLING_MAP.get(unit)
|
|
152
|
+
if u: unit = u
|
|
153
|
+
|
|
154
|
+
for system, dimensions in cls.UNITS.items():
|
|
155
|
+
for dimension, units in dimensions.items():
|
|
156
|
+
if unit in units:
|
|
157
|
+
return UnitValue(system, dimension, unit, value)
|
|
158
|
+
warnings.warn("Creating unit not recongized by package")
|
|
159
|
+
return UnitValue(None, None, unit, value)
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def unit_from_string(cls, unit_str:str) -> 'UnitValue':
|
|
163
|
+
"""
|
|
164
|
+
Create a UnitValue object from a formatted string (e.g., '10 m').
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
unit_str (str): The string containing the value and unit.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
UnitValue: A UnitValue object.
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
ValueError: If the string is not in the correct format.
|
|
174
|
+
"""
|
|
175
|
+
res = unit_str.split()
|
|
176
|
+
if len(res) > 2:
|
|
177
|
+
raise ValueError(f"String {unit_str} is not in right format 'value unit' e.g,. '10 kg")
|
|
178
|
+
try:
|
|
179
|
+
val = float(res[0])
|
|
180
|
+
return UnitValue.create_unit(res[1], val)
|
|
181
|
+
except:
|
|
182
|
+
raise ValueError("First element of string could not be converted to number, ensure string of the form e.g., '10 kg'")
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def from_dict(cls, data: dict) -> 'UnitValue':
|
|
186
|
+
"""
|
|
187
|
+
Create a UnitValue object from a dictionary.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
data (dict): A dictionary containing the system, dimension, unit, and value.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
UnitValue: A UnitValue object.
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
return cls(data['system'], data['dimension'], data['unit'], data['value'])
|
|
197
|
+
except:
|
|
198
|
+
raise ValueError(f"Dictionary {data}, doesn't contain the correct keys 'system', 'dimension', unit, and value")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def __init__(self, system: str|None, dimension: str|None, unit: str, value: float=0) -> None:
|
|
202
|
+
"""
|
|
203
|
+
Initialize a UnitValue object.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
system (str): The measurement system.
|
|
207
|
+
dimension (str): The dimension of the unit.
|
|
208
|
+
unit (str): The unit of the value.
|
|
209
|
+
value (float): The numerical value.
|
|
210
|
+
"""
|
|
211
|
+
self._conversion_cache = {}
|
|
212
|
+
self.value = value
|
|
213
|
+
if system is None:
|
|
214
|
+
self.__system = None
|
|
215
|
+
self.__dimension = None
|
|
216
|
+
self.__unit = unit
|
|
217
|
+
return
|
|
218
|
+
if system.lower() == "imperial":
|
|
219
|
+
self.__system = "IMPERIAL"
|
|
220
|
+
elif system.lower() == "metric":
|
|
221
|
+
self.__system = "METRIC"
|
|
222
|
+
else:
|
|
223
|
+
raise Exception(f"Unit system {system} invalid: Must be 'IMPERIAL' or 'METRIC'")
|
|
224
|
+
|
|
225
|
+
if dimension not in UnitValue.UNITS[self.__system]:
|
|
226
|
+
raise Exception(f"Measurement Type {dimension} invalid: Must be 'DISTANCE', 'PRESSURE', 'MASS', 'VELOCITY', 'DENSITY', 'VOLUME', 'AREA', 'TEMPERATURE', 'MASS FLOW RATE', 'ENERGY'")
|
|
227
|
+
self.__dimension = dimension
|
|
228
|
+
|
|
229
|
+
if unit not in UnitValue.UNITS[self.__system][self.__dimension]:
|
|
230
|
+
raise Exception(f"Unit {unit} invalid: Unit must be {list(UnitValue.UNITS[self.__system][self.__dimension].keys())} for {self.__system} {self.__dimension}")
|
|
231
|
+
self.__unit = unit
|
|
232
|
+
|
|
233
|
+
def __call__(self) -> int|float:
|
|
234
|
+
"""
|
|
235
|
+
Get the value of the unit.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
int|float: The magnitude of the dimensioned quatity
|
|
239
|
+
"""
|
|
240
|
+
return self.value
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def __process_unit(unit_str: str, units: str, opp: int) -> None:
|
|
244
|
+
"""
|
|
245
|
+
Processes a unit string and updates the units dictionary with the appropriate values.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
unit_str (str): The unit string to be processed.
|
|
249
|
+
units (dict): A dictionary to store the processed units and their values.
|
|
250
|
+
opp (int): An integer used to determine the operation (1 for multiplication, -1 for division).
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
None: This function updates the units dictionary in place.
|
|
254
|
+
"""
|
|
255
|
+
i = 0
|
|
256
|
+
while i < len(unit_str):
|
|
257
|
+
if unit_str[i] == "/":
|
|
258
|
+
opp *= -1
|
|
259
|
+
i += 1
|
|
260
|
+
continue
|
|
261
|
+
elif unit_str[i] == "k":
|
|
262
|
+
key = unit_str[i:i+2]
|
|
263
|
+
i += 1
|
|
264
|
+
else:
|
|
265
|
+
key = unit_str[i]
|
|
266
|
+
|
|
267
|
+
if key not in units:
|
|
268
|
+
units[key] = 0
|
|
269
|
+
|
|
270
|
+
if i + 1 < len(unit_str) and unit_str[i+1] == "^":
|
|
271
|
+
index = 2
|
|
272
|
+
is_float = False
|
|
273
|
+
for j in range(i+2, len(unit_str)):
|
|
274
|
+
if unit_str[j] == ".":
|
|
275
|
+
is_float = True
|
|
276
|
+
index += 1
|
|
277
|
+
else:
|
|
278
|
+
try:
|
|
279
|
+
int(unit_str[j])
|
|
280
|
+
index += 1
|
|
281
|
+
except:
|
|
282
|
+
break
|
|
283
|
+
if is_float:
|
|
284
|
+
units[key] += opp * float(unit_str[i+2:i+index])
|
|
285
|
+
else:
|
|
286
|
+
units[key] += opp * int(unit_str[i+2:i+index])
|
|
287
|
+
i += index
|
|
288
|
+
else:
|
|
289
|
+
units[key] += opp
|
|
290
|
+
i += 1
|
|
291
|
+
|
|
292
|
+
def __mul__(self, m) -> 'UnitValue':
|
|
293
|
+
"""
|
|
294
|
+
Multiplies UnitValue object by another Unitvlue or dimensionless value.
|
|
295
|
+
"""
|
|
296
|
+
if isinstance(m, UnitValue):
|
|
297
|
+
self.convert_base_metric()
|
|
298
|
+
m.convert_base_metric()
|
|
299
|
+
units = {}
|
|
300
|
+
self.__process_unit(self.__unit, units, 1)
|
|
301
|
+
self.__process_unit(m.__unit, units, 1)
|
|
302
|
+
|
|
303
|
+
for dimension, units_dict in UnitValue.UNITS["METRIC"].items():
|
|
304
|
+
temp = {}
|
|
305
|
+
self.__process_unit(list(units_dict)[0], temp, 1)
|
|
306
|
+
if temp == units:
|
|
307
|
+
return UnitValue("METRIC", dimension, list(units_dict)[0], self.value * m.value)
|
|
308
|
+
|
|
309
|
+
numer = ""
|
|
310
|
+
denom = "/"
|
|
311
|
+
for key in units.keys():
|
|
312
|
+
if units[key] > 1:
|
|
313
|
+
numer += f"{key}^{units[key]}" if units[key] % 1 != 0 else f"{key}^{int(units[key])}"
|
|
314
|
+
elif units[key] == 1:
|
|
315
|
+
numer += key
|
|
316
|
+
elif units[key] == -1:
|
|
317
|
+
denom += key
|
|
318
|
+
elif units[key] < -1:
|
|
319
|
+
denom += f"{key}^{-1*units[key]}" if units[key] % 1 != 0 else f"{key}^{-1*int(units[key])}"
|
|
320
|
+
|
|
321
|
+
new_unit = numer + denom if denom != "/" else numer
|
|
322
|
+
if new_unit == "":
|
|
323
|
+
return self.value * m.value
|
|
324
|
+
|
|
325
|
+
return UnitValue(None, None, new_unit, self.value * m.value)
|
|
326
|
+
elif isinstance(m, (int, float, np.ndarray)):
|
|
327
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value * m)
|
|
328
|
+
else:
|
|
329
|
+
return NotImplemented
|
|
330
|
+
|
|
331
|
+
def __rmul__(self, m) -> 'UnitValue':
|
|
332
|
+
return self.__mul__(m)
|
|
333
|
+
|
|
334
|
+
def __truediv__(self, d) -> 'UnitValue':
|
|
335
|
+
"""
|
|
336
|
+
Divides UnitValue object by another Unitvlue or dimensionless value.
|
|
337
|
+
"""
|
|
338
|
+
if isinstance(d, UnitValue):
|
|
339
|
+
self.convert_base_metric()
|
|
340
|
+
d.convert_base_metric()
|
|
341
|
+
units = {}
|
|
342
|
+
self.__process_unit(self.__unit, units, 1)
|
|
343
|
+
self.__process_unit(d.__unit, units, -1)
|
|
344
|
+
|
|
345
|
+
for dimension, units_dict in UnitValue.UNITS["METRIC"].items():
|
|
346
|
+
temp = {}
|
|
347
|
+
self.__process_unit(list(units_dict)[0], temp, 1)
|
|
348
|
+
if temp == units:
|
|
349
|
+
return UnitValue("METRIC", dimension, list(units_dict)[0], self.value / d.value)
|
|
350
|
+
|
|
351
|
+
numer = ""
|
|
352
|
+
denom = "/"
|
|
353
|
+
for key in units.keys():
|
|
354
|
+
if units[key] > 1:
|
|
355
|
+
numer += f"{key}^{units[key]}" if units[key] % 1 != 0 else f"{key}^{int(units[key])}"
|
|
356
|
+
elif units[key] == 1:
|
|
357
|
+
numer += key
|
|
358
|
+
elif units[key] == -1:
|
|
359
|
+
denom += key
|
|
360
|
+
elif units[key] < -1:
|
|
361
|
+
denom += f"{key}^{-1*units[key]}" if units[key] % 1 != 0 else f"{key}^{-1*int(units[key])}"
|
|
362
|
+
|
|
363
|
+
new_unit = numer + denom if denom != "/" else numer
|
|
364
|
+
if new_unit == "":
|
|
365
|
+
return self.value / d.value
|
|
366
|
+
|
|
367
|
+
return UnitValue(None, None, new_unit, self.value / d.value)
|
|
368
|
+
elif isinstance(d, (int, float, np.ndarray)):
|
|
369
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value / d)
|
|
370
|
+
else:
|
|
371
|
+
return NotImplemented
|
|
372
|
+
|
|
373
|
+
def __rtruediv__(self, d) -> 'UnitValue':
|
|
374
|
+
"""
|
|
375
|
+
Divides another Unitvlue or dimensionless value by UnitValue object.
|
|
376
|
+
"""
|
|
377
|
+
if isinstance(d, UnitValue):
|
|
378
|
+
self.convert_base_metric()
|
|
379
|
+
d.convert_base_metric()
|
|
380
|
+
units = {}
|
|
381
|
+
self.__process_unit(self.__unit, units, -1)
|
|
382
|
+
self.__process_unit(d.__unit, units, 1)
|
|
383
|
+
|
|
384
|
+
for dimension, units_dict in UnitValue.UNITS["METRIC"].items():
|
|
385
|
+
temp = {}
|
|
386
|
+
self.__process_unit(list(units_dict)[0], temp, 1)
|
|
387
|
+
if temp == units:
|
|
388
|
+
return UnitValue("METRIC", dimension, list(units_dict)[0], d.value / self.value)
|
|
389
|
+
|
|
390
|
+
numer = ""
|
|
391
|
+
denom = "/"
|
|
392
|
+
for key in units.keys():
|
|
393
|
+
if units[key] > 1:
|
|
394
|
+
numer += f"{key}^{units[key]}" if units[key] % 1 != 0 else f"{key}^{int(units[key])}"
|
|
395
|
+
elif units[key] == 1:
|
|
396
|
+
numer += key
|
|
397
|
+
elif units[key] == -1:
|
|
398
|
+
denom += key
|
|
399
|
+
elif units[key] < -1:
|
|
400
|
+
denom += f"{key}^{-1*units[key]}" if units[key] % 1 != 0 else f"{key}^{int(units[key])}"
|
|
401
|
+
|
|
402
|
+
new_unit = numer + denom if denom != "/" else numer
|
|
403
|
+
if new_unit == "":
|
|
404
|
+
return d.value / self.value
|
|
405
|
+
|
|
406
|
+
return UnitValue(None, None, new_unit, d.value / self.value)
|
|
407
|
+
elif isinstance(d, (int, float, np.ndarray)):
|
|
408
|
+
|
|
409
|
+
units = {}
|
|
410
|
+
self.__process_unit(self.__unit, units, -1)
|
|
411
|
+
for dimension, units_dict in UnitValue.UNITS["METRIC"].items():
|
|
412
|
+
temp = {}
|
|
413
|
+
self.__process_unit(list(units_dict)[0], temp, 1)
|
|
414
|
+
if temp == units:
|
|
415
|
+
return UnitValue("METRIC", dimension, list(units_dict)[0], d / self.value)
|
|
416
|
+
|
|
417
|
+
temp = self.__unit.split("/")
|
|
418
|
+
b = True
|
|
419
|
+
denom = "/"
|
|
420
|
+
numer = ""
|
|
421
|
+
for units in temp:
|
|
422
|
+
if b:
|
|
423
|
+
denom += units
|
|
424
|
+
b = False
|
|
425
|
+
else:
|
|
426
|
+
numer += units
|
|
427
|
+
b = True
|
|
428
|
+
|
|
429
|
+
new_unit = numer + denom
|
|
430
|
+
return UnitValue(None, None, new_unit, d / self.value)
|
|
431
|
+
else:
|
|
432
|
+
return NotImplemented
|
|
433
|
+
|
|
434
|
+
def __pow__(self, p) -> 'UnitValue':
|
|
435
|
+
"""
|
|
436
|
+
Raises UnitValue object to the power of a float or interger.
|
|
437
|
+
"""
|
|
438
|
+
if isinstance(p, (int, float)):
|
|
439
|
+
if p == 1:
|
|
440
|
+
return self
|
|
441
|
+
|
|
442
|
+
self.convert_base_metric()
|
|
443
|
+
units = {}
|
|
444
|
+
self.__process_unit(self.__unit, units, 1)
|
|
445
|
+
for unit in units.keys():
|
|
446
|
+
units[unit] *= p
|
|
447
|
+
for dimension, units_dict in UnitValue.UNITS["METRIC"].items():
|
|
448
|
+
temp = {}
|
|
449
|
+
self.__process_unit(list(units_dict)[0], temp, 1)
|
|
450
|
+
if temp == units:
|
|
451
|
+
return UnitValue("METRIC", dimension, list(units_dict)[0], self.value ** p)
|
|
452
|
+
|
|
453
|
+
numer = ""
|
|
454
|
+
denom = "/"
|
|
455
|
+
for key in units.keys():
|
|
456
|
+
if units[key] == 1:
|
|
457
|
+
numer += key
|
|
458
|
+
elif units[key] == -1:
|
|
459
|
+
denom += key
|
|
460
|
+
elif units[key] > 0:
|
|
461
|
+
numer += f"{key}^{units[key]}" if units[key] % 1 != 0 else f"{key}^{int(units[key])}"
|
|
462
|
+
elif units[key] < 0:
|
|
463
|
+
denom += f"{key}^{-1*units[key]}" if units[key] % 1 != 0 else f"{key}^{-1*int(units[key])}"
|
|
464
|
+
|
|
465
|
+
new_unit = numer + denom if denom != "/" else numer
|
|
466
|
+
return UnitValue(None, None, new_unit, self.value ** p)
|
|
467
|
+
|
|
468
|
+
else:
|
|
469
|
+
raise NotImplementedError(f"Raising to the power with type {type(p)} not supported")
|
|
470
|
+
|
|
471
|
+
def __add__(self, a) -> 'UnitValue':
|
|
472
|
+
"""
|
|
473
|
+
Adds UnitValue object by another Unitvlue or dimensionless value.
|
|
474
|
+
"""
|
|
475
|
+
if isinstance(a, UnitValue):
|
|
476
|
+
if self.__dimension != a.__dimension:
|
|
477
|
+
if self.__unit != a.__unit:
|
|
478
|
+
raise TypeError(f"Cannot add unit {self.__unit} and unit {a.__unit}")
|
|
479
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value + a.value)
|
|
480
|
+
self.convert_base_metric()
|
|
481
|
+
a.convert_base_metric()
|
|
482
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value + a.value)
|
|
483
|
+
|
|
484
|
+
elif isinstance(a, (int, float, np.ndarray)):
|
|
485
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value + a)
|
|
486
|
+
else:
|
|
487
|
+
return NotImplemented
|
|
488
|
+
|
|
489
|
+
def __radd__(self, a) -> 'UnitValue':
|
|
490
|
+
return self.__add__(a)
|
|
491
|
+
|
|
492
|
+
def __sub__(self, s) -> 'UnitValue':
|
|
493
|
+
"""
|
|
494
|
+
Subtracts UnitValue object by another Unitvlue or dimensionless value.
|
|
495
|
+
"""
|
|
496
|
+
if isinstance(s, UnitValue):
|
|
497
|
+
if self.__dimension != s.__dimension:
|
|
498
|
+
if self.__unit != s.__unit:
|
|
499
|
+
raise TypeError(f"Cannot subtract unit {self.__unit} and unit {s.__unit}")
|
|
500
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value - s.value)
|
|
501
|
+
self.convert_base_metric()
|
|
502
|
+
s.convert_base_metric()
|
|
503
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value - s.value)
|
|
504
|
+
elif isinstance(s, (int, float, np.ndarray)):
|
|
505
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value - s)
|
|
506
|
+
else:
|
|
507
|
+
return NotImplemented
|
|
508
|
+
|
|
509
|
+
def __rsub__(self, s) -> 'UnitValue':
|
|
510
|
+
return -self.__sub__(s)
|
|
511
|
+
|
|
512
|
+
def __neg__(self):
|
|
513
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, -self.value)
|
|
514
|
+
|
|
515
|
+
def __abs__(self):
|
|
516
|
+
"""
|
|
517
|
+
Returns a Unitvalue object with the absolute value of the original UnitValue value.
|
|
518
|
+
"""
|
|
519
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, abs(self.value))
|
|
520
|
+
|
|
521
|
+
def __eq__(self, other) -> bool:
|
|
522
|
+
"""
|
|
523
|
+
Checks if UnitValue object is equal to other.
|
|
524
|
+
"""
|
|
525
|
+
if isinstance(other, UnitValue):
|
|
526
|
+
a = self.__cpy().convert_base_metric()
|
|
527
|
+
b = other.__cpy().convert_base_metric()
|
|
528
|
+
return a.value == b.value and a.__unit == b.__unit
|
|
529
|
+
elif isinstance(other, (int, float)):
|
|
530
|
+
return self.value == other
|
|
531
|
+
return False
|
|
532
|
+
|
|
533
|
+
def __ne__(self, other) -> bool:
|
|
534
|
+
if isinstance(other, UnitValue):
|
|
535
|
+
a = self.__cpy().convert_base_metric()
|
|
536
|
+
b = other.__cpy().convert_base_metric()
|
|
537
|
+
return a.value != b.value and a.__unit != b.__unit
|
|
538
|
+
elif isinstance(other, (int, float)):
|
|
539
|
+
return self.value != other
|
|
540
|
+
return True
|
|
541
|
+
|
|
542
|
+
def __lt__(self, other) -> bool:
|
|
543
|
+
if isinstance(other, UnitValue):
|
|
544
|
+
a = self.__cpy().convert_base_metric()
|
|
545
|
+
b = other.__cpy().convert_base_metric()
|
|
546
|
+
return a.value < b.value
|
|
547
|
+
elif isinstance(other, (int, float)):
|
|
548
|
+
return self.value < other
|
|
549
|
+
raise TypeError(f"Cannot compare type UnitValue and type {type(other)}")
|
|
550
|
+
|
|
551
|
+
def __le__(self, other) -> bool:
|
|
552
|
+
if isinstance(other, UnitValue):
|
|
553
|
+
a = self.__cpy().convert_base_metric()
|
|
554
|
+
b = other.__cpy().convert_base_metric()
|
|
555
|
+
return a.value <= b.value
|
|
556
|
+
elif isinstance(other, (int, float)):
|
|
557
|
+
return self.value <= other
|
|
558
|
+
raise TypeError(f"Cannot compare type UnitValue and type {type(other)}")
|
|
559
|
+
|
|
560
|
+
def __gt__(self, other) -> bool:
|
|
561
|
+
if isinstance(other, UnitValue):
|
|
562
|
+
a = self.__cpy().convert_base_metric()
|
|
563
|
+
b = other.__cpy().convert_base_metric()
|
|
564
|
+
return a.value > b.value
|
|
565
|
+
elif isinstance(other, (int, float)):
|
|
566
|
+
return self.value > other
|
|
567
|
+
raise TypeError(f"Cannot compare type UnitValue and type {type(other)}")
|
|
568
|
+
|
|
569
|
+
def __ge__(self, other) -> bool:
|
|
570
|
+
if isinstance(other, UnitValue):
|
|
571
|
+
a = self.__cpy().convert_base_metric()
|
|
572
|
+
b = other.__cpy().convert_base_metric()
|
|
573
|
+
return a.value >= b.value
|
|
574
|
+
elif isinstance(other, (int, float)):
|
|
575
|
+
return self.value >= other
|
|
576
|
+
raise TypeError(f"Cannot compare type UnitValue and type {type(other)}")
|
|
577
|
+
|
|
578
|
+
def __float__(self) -> float:
|
|
579
|
+
return float(self.value)
|
|
580
|
+
|
|
581
|
+
def __int__(self) -> int:
|
|
582
|
+
return int(self.value)
|
|
583
|
+
|
|
584
|
+
def __round__(self, ndigits: int=0) -> 'UnitValue':
|
|
585
|
+
"""
|
|
586
|
+
Rounds UnitValue to ndigts returns rounded Unitvale
|
|
587
|
+
"""
|
|
588
|
+
self.value = round(self.value, ndigits)
|
|
589
|
+
return self
|
|
590
|
+
|
|
591
|
+
def __repr__(self) -> str:
|
|
592
|
+
return f"{self.value} {self.__unit}"
|
|
593
|
+
|
|
594
|
+
def __str__(self) -> str:
|
|
595
|
+
return f"{self.value} {self.__unit}"
|
|
596
|
+
|
|
597
|
+
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
|
|
598
|
+
"""
|
|
599
|
+
Handle NumPy ufunc (universal function) operations for UnitValue objects.
|
|
600
|
+
"""
|
|
601
|
+
out = kwargs.get('out', ())
|
|
602
|
+
inputs = [i.value if isinstance(i, UnitValue) else i for i in inputs]
|
|
603
|
+
|
|
604
|
+
result = getattr(ufunc, method)(*inputs, **kwargs)
|
|
605
|
+
|
|
606
|
+
if method == 'reduce':
|
|
607
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, result)
|
|
608
|
+
elif method == 'outer':
|
|
609
|
+
return np.vectorize(lambda x: UnitValue(self.__system, self.__dimension, self.__unit, x))(result)
|
|
610
|
+
else:
|
|
611
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, result)
|
|
612
|
+
|
|
613
|
+
def __convert_system(self, unit: str = "") -> None:
|
|
614
|
+
self.value *= UnitValue.UNITS[self.__system][self.__dimension][self.__unit]
|
|
615
|
+
self.__system = "IMPERIAL" if self.__system == "METRIC" else "METRIC"
|
|
616
|
+
|
|
617
|
+
if unit:
|
|
618
|
+
if unit in UnitValue.UNITS[self.__system][self.__dimension].keys():
|
|
619
|
+
self.value /= UnitValue.UNITS[self.__system][self.__dimension][unit]
|
|
620
|
+
self.__unit = unit
|
|
621
|
+
else:
|
|
622
|
+
sys = self.__system
|
|
623
|
+
self.__system = "IMPERIAL" if self.__system == "METRIC" else "METRIC"
|
|
624
|
+
self.value /= UnitValue.UNITS[self.__system][self.__dimension][self.__unit]
|
|
625
|
+
raise Exception(f"Unit {unit} invalid: Unit must be {list(UnitValue.UNITS[sys][self.__dimension].keys())} for {sys} {self.__dimension}")
|
|
626
|
+
else:
|
|
627
|
+
if self.__system != "METRIC":
|
|
628
|
+
self.value /= UnitValue.UNITS[self.__system][self.__dimension][list(UnitValue.UNITS[self.__system][self.__dimension].keys())[0]]
|
|
629
|
+
self.__unit = list(UnitValue.UNITS[self.__system][self.__dimension].keys())[0]
|
|
630
|
+
|
|
631
|
+
def __convert_unit(self, unit: str) -> None:
|
|
632
|
+
if unit in UnitValue.UNITS[self.__system][self.__dimension].keys():
|
|
633
|
+
self.value *= UnitValue.UNITS[self.__system][self.__dimension][self.__unit]
|
|
634
|
+
self.value /= UnitValue.UNITS[self.__system][self.__dimension][unit]
|
|
635
|
+
self.__unit = unit
|
|
636
|
+
else:
|
|
637
|
+
raise Exception(f"Unit {unit} invalid: Unit must be {list(UnitValue.UNITS[self.__system][self.__dimension].keys())} for {self.__system} {self.__dimension}")
|
|
638
|
+
|
|
639
|
+
def __cpy(self) -> 'UnitValue':
|
|
640
|
+
return UnitValue(self.__system, self.__dimension, self.__unit, self.value)
|
|
641
|
+
|
|
642
|
+
def __temperature_handler(self, old_unit: str, new_unit: str): # add Rankine
|
|
643
|
+
if new_unit == "K":
|
|
644
|
+
if old_unit == "c":
|
|
645
|
+
self.value += 273.15
|
|
646
|
+
elif old_unit == "f":
|
|
647
|
+
self.value = (self.value - 32) * (5/9) + 273.15
|
|
648
|
+
elif old_unit == "R":
|
|
649
|
+
self.value *= 5/9
|
|
650
|
+
elif new_unit == "c":
|
|
651
|
+
if old_unit == "K":
|
|
652
|
+
self.value -= 273.15
|
|
653
|
+
elif old_unit == "f":
|
|
654
|
+
self.value = (self.value - 32) * (5/9)
|
|
655
|
+
elif old_unit == "R":
|
|
656
|
+
self.value = (self.value - 491.67) * (5/9)
|
|
657
|
+
elif new_unit == "f":
|
|
658
|
+
if old_unit == "K":
|
|
659
|
+
self.value = ((self.value -273.15) / (5/9)) + 32
|
|
660
|
+
elif old_unit == "c":
|
|
661
|
+
self.value = (self.value / (5/9)) + 32
|
|
662
|
+
elif old_unit == "R":
|
|
663
|
+
self.value -= 459.67
|
|
664
|
+
elif new_unit == "R":
|
|
665
|
+
if old_unit == "c":
|
|
666
|
+
self.value = self.value * (9/5) + 491.67
|
|
667
|
+
elif old_unit == "f":
|
|
668
|
+
self.value += 459.67
|
|
669
|
+
elif old_unit == "K":
|
|
670
|
+
self.value *= 1.8
|
|
671
|
+
|
|
672
|
+
def __convert_temp(self, change_system: bool, unit: str=""):
|
|
673
|
+
if change_system:
|
|
674
|
+
self.__system = "IMPERIAL" if self.__system == "METRIC" else "METRIC"
|
|
675
|
+
if unit in UnitValue.UNITS[self.__system][self.__dimension].keys():
|
|
676
|
+
self.__temperature_handler(self.__unit, unit)
|
|
677
|
+
self.__unit = unit
|
|
678
|
+
else:
|
|
679
|
+
raise Exception(f"Unit {unit} invalid: Unit must be {list(UnitValue.UNITS[self.__system][self.__dimension].keys())} for {self.__system} {self.__dimension}")
|
|
680
|
+
elif unit:
|
|
681
|
+
if unit in UnitValue.UNITS[self.__system][self.__dimension].keys():
|
|
682
|
+
self.__temperature_handler(self.__unit, unit)
|
|
683
|
+
self.__unit = unit
|
|
684
|
+
else:
|
|
685
|
+
raise Exception(f"Unit {unit} invalid: Unit must be {list(UnitValue.UNITS[self.__system][self.__dimension].keys())} for {self.__system} {self.__dimension}")
|
|
686
|
+
else:
|
|
687
|
+
raise Exception("No conversion performed as change_system was false and unit was empty")
|
|
688
|
+
|
|
689
|
+
def convert_base_metric(self) -> 'UnitValue':
|
|
690
|
+
"""
|
|
691
|
+
Convert the current value to it's base SI/Metric unit.
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
UnitValue: The UnitValue object with the converted units.
|
|
695
|
+
"""
|
|
696
|
+
if self.__system is None:
|
|
697
|
+
return
|
|
698
|
+
elif self.__dimension != "TEMPERATURE":
|
|
699
|
+
self.value *= UnitValue.UNITS[self.__system][self.__dimension][self.__unit]
|
|
700
|
+
self.__system = "METRIC"
|
|
701
|
+
self.__unit = list(UnitValue.UNITS[self.__system][self.__dimension].keys())[0]
|
|
702
|
+
else:
|
|
703
|
+
self.__temperature_handler(self.__unit, "K")
|
|
704
|
+
self.__system = "METRIC"
|
|
705
|
+
self.__unit = "K"
|
|
706
|
+
return self
|
|
707
|
+
|
|
708
|
+
def to(self, unit:str) -> 'UnitValue':
|
|
709
|
+
"""
|
|
710
|
+
Convert the current value to a new unit.
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
unit (str): The unit to convert to.
|
|
714
|
+
|
|
715
|
+
Returns:
|
|
716
|
+
UnitValue: The UnitValue object with the converted value.
|
|
717
|
+
|
|
718
|
+
Raises:
|
|
719
|
+
Exception: If the unit is not recognized or conversion is not possible.
|
|
720
|
+
"""
|
|
721
|
+
u = UnitValue.SPELLING_MAP.get(unit)
|
|
722
|
+
if u: unit = u
|
|
723
|
+
|
|
724
|
+
cache_key = (self.__system, self.__dimension, self.__unit, unit)
|
|
725
|
+
if cache_key in self._conversion_cache:
|
|
726
|
+
cached_value = self._conversion_cache[cache_key]
|
|
727
|
+
return UnitValue(self.__system, self.__dimension, unit, cached_value)
|
|
728
|
+
|
|
729
|
+
other_system = "IMPERIAL" if self.__system == "METRIC" else "METRIC"
|
|
730
|
+
if self.__system is None:
|
|
731
|
+
raise Exception(f"Invalid unit {self.__unit}: this unit is not currently supported by the module")
|
|
732
|
+
elif self.__dimension == "TEMPERATURE":
|
|
733
|
+
if unit in UnitValue.UNITS[self.__system][self.__dimension]:
|
|
734
|
+
self.__convert_temp(False, unit)
|
|
735
|
+
elif unit in UnitValue.UNITS[other_system][self.__dimension]:
|
|
736
|
+
self.__convert_temp(True, unit)
|
|
737
|
+
else:
|
|
738
|
+
raise TypeError(f"Cannot convert unit of dimension {self.__dimension} to {unit}")
|
|
739
|
+
else:
|
|
740
|
+
if unit in UnitValue.UNITS[self.__system][self.__dimension]:
|
|
741
|
+
self.__convert_unit(unit)
|
|
742
|
+
elif unit in UnitValue.UNITS[other_system][self.__dimension]:
|
|
743
|
+
self.__convert_system(unit)
|
|
744
|
+
else:
|
|
745
|
+
raise TypeError(f"Cannot convert unit of dimension {self.__dimension} to {unit}")
|
|
746
|
+
|
|
747
|
+
self._conversion_cache[cache_key] = self.value
|
|
748
|
+
return self
|
|
749
|
+
|
|
750
|
+
def to_dict(self) -> dict:
|
|
751
|
+
"""
|
|
752
|
+
Convert the UnitValue object to a dictionary.
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
dict: A dictionary representation of the UnitValue object.
|
|
756
|
+
"""
|
|
757
|
+
return {
|
|
758
|
+
'system': self.__system,
|
|
759
|
+
'dimension': self.__dimension,
|
|
760
|
+
'unit': self.__unit,
|
|
761
|
+
'value': self.value
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
@property
|
|
765
|
+
def get_unit(self) -> str:
|
|
766
|
+
return self.__unit
|
|
767
|
+
|
|
768
|
+
@property
|
|
769
|
+
def get_dimension(self) -> str:
|
|
770
|
+
return self.__dimension
|
|
771
|
+
|
|
772
|
+
@property
|
|
773
|
+
def get_system(self) -> str:
|
|
774
|
+
return self.__system
|
|
775
|
+
|
|
776
|
+
print(UnitValue.available_units())
|