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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (73.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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())