pyquantity 0.1.15__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.
pyquantity/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """
2
+ pyquantity - A modern Python package for quantity calculations.
3
+
4
+ This package provides tools for working with physical quantities, units,
5
+ and dimensional analysis.
6
+ """
7
+
8
+ __version__ = "0.1.15"
9
+ __author__ = "odysseu"
10
+ __email__ = "uboucherie1@gmail.com"
11
+ __license__ = "MIT"
12
+
13
+ from .context import (
14
+ MeasurementDatabase,
15
+ UnitParser,
16
+ extract_quantities,
17
+ find_units_in_text,
18
+ get_measurement,
19
+ parse_quantity,
20
+ )
21
+ from .core import Dimension, Quantity, UnitSystem
22
+ from .parser import QuantityParser, parse_quantities
23
+
24
+ __all__ = [
25
+ "Quantity", "Dimension", "UnitSystem",
26
+ "QuantityParser", "parse_quantities",
27
+ "MeasurementDatabase", "UnitParser",
28
+ "get_measurement", "parse_quantity", "extract_quantities", "find_units_in_text"
29
+ ]
pyquantity/context.py ADDED
@@ -0,0 +1,513 @@
1
+ """
2
+ Contextual measurements and real-world object database for pyquantity.
3
+
4
+ This module provides a database of common real-world objects with their
5
+ associated measurements, as well as natural language parsing capabilities
6
+ for extracting units and measurements from text.
7
+ """
8
+
9
+ import re
10
+
11
+ from .core import Quantity, UnitSystem
12
+
13
+
14
+ class MeasurementDatabase:
15
+ """
16
+ Database of real-world objects and their associated measurements.
17
+
18
+ This class provides a collection of common objects with standard
19
+ measurements (e.g., "normal bath" = 150 liters, "standard cup" = 250 ml).
20
+ """
21
+
22
+ def __init__(self) -> None:
23
+ # Initialize with standard measurements
24
+ self.measurements = {
25
+ # Volume measurements
26
+ "teaspoon": Quantity(5.0, "milliliter"),
27
+ "tablespoon": Quantity(15.0, "milliliter"),
28
+ "cup": Quantity(250.0, "milliliter"),
29
+ "glass": Quantity(200.0, "milliliter"),
30
+ "bottle": Quantity(500.0, "milliliter"),
31
+ "can": Quantity(330.0, "milliliter"),
32
+ "jug": Quantity(1.0, "liter"),
33
+ "bucket": Quantity(10.0, "liter"),
34
+ "bathtub": Quantity(150.0, "liter"),
35
+ "normal bath": Quantity(150.0, "liter"),
36
+ "swimming pool": Quantity(50000.0, "liter"),
37
+ "ocean": Quantity(1.332e21, "liter"), # Approximate volume of Earth's oceans
38
+
39
+ # Mass measurements
40
+ "grain of salt": Quantity(0.06, "milligram"),
41
+ "paperclip": Quantity(1.0, "gram"),
42
+ "apple": Quantity(150.0, "gram"),
43
+ "loaf of bread": Quantity(500.0, "gram"),
44
+ "bag of sugar": Quantity(1.0, "kilogram"),
45
+ "average person": Quantity(70.0, "kilogram"),
46
+ "car mass": Quantity(1500.0, "kilogram"),
47
+ "elephant": Quantity(5000.0, "kilogram"),
48
+ "blue whale": Quantity(150000.0, "kilogram"),
49
+
50
+ # Length measurements
51
+ "grain of sand": Quantity(0.5, "millimeter"),
52
+ "credit card": Quantity(85.6, "millimeter"),
53
+ "smartphone": Quantity(150.0, "millimeter"),
54
+ "pizza": Quantity(30.0, "centimeter"),
55
+ "door": Quantity(2.0, "meter"),
56
+ "room": Quantity(5.0, "meter"),
57
+ "football field length": Quantity(100.0, "meter"),
58
+ "marathon": Quantity(42.195, "kilometer"),
59
+ "mount everest": Quantity(8848.0, "meter"),
60
+
61
+ # Time measurements
62
+ "blink": Quantity(0.3, "second"),
63
+ "breath": Quantity(4.0, "second"),
64
+ "minute": Quantity(60.0, "second"),
65
+ "hour": Quantity(3600.0, "second"),
66
+ "day": Quantity(86400.0, "second"),
67
+ "week": Quantity(604800.0, "second"),
68
+ "month": Quantity(2.628e6, "second"), # Average month
69
+ "year": Quantity(3.154e7, "second"), # Average year
70
+
71
+ # Speed measurements
72
+ "snail": Quantity(0.05, "meter/second"),
73
+ "walking": Quantity(1.4, "meter/second"),
74
+ "running": Quantity(5.0, "meter/second"),
75
+ "cycling": Quantity(7.0, "meter/second"),
76
+ "car speed": Quantity(25.0, "meter/second"),
77
+ "high speed train": Quantity(83.0, "meter/second"),
78
+ "airplane": Quantity(250.0, "meter/second"),
79
+ "speed of sound": Quantity(343.0, "meter/second"),
80
+ "speed of light": Quantity(299792458.0, "meter/second"),
81
+
82
+ # Temperature measurements
83
+ "freezing point of water": Quantity(0.0, "celsius"),
84
+ "room temperature": Quantity(20.0, "celsius"),
85
+ "body temperature": Quantity(37.0, "celsius"),
86
+ "boiling point of water": Quantity(100.0, "celsius"),
87
+ "absolute zero": Quantity(-273.15, "celsius"),
88
+ "surface of the sun": Quantity(5500.0, "celsius"),
89
+
90
+ # Energy measurements
91
+ "calorie": Quantity(4.184, "joule"),
92
+ "food calorie": Quantity(4184.0, "joule"),
93
+ "battery": Quantity(10000.0, "joule"),
94
+ "car battery": Quantity(1.0e6, "joule"),
95
+ "gasoline liter": Quantity(3.42e7, "joule"),
96
+ "ton of tnt": Quantity(4.184e9, "joule"),
97
+ "atomic bomb": Quantity(8.4e13, "joule"), # Little Boy
98
+
99
+ # Power measurements
100
+ "light bulb": Quantity(60.0, "watt"),
101
+ "human": Quantity(100.0, "watt"),
102
+ "car engine": Quantity(100000.0, "watt"),
103
+ "jet engine": Quantity(1.0e8, "watt"),
104
+ "power plant": Quantity(1.0e9, "watt"),
105
+ "sun": Quantity(3.828e26, "watt"),
106
+
107
+ # Pressure measurements
108
+ "atmospheric pressure": Quantity(101325.0, "pascal"),
109
+ "car tire": Quantity(200000.0, "pascal"),
110
+ "bicycle tire": Quantity(400000.0, "pascal"),
111
+ "deep ocean": Quantity(1.0e7, "pascal"),
112
+ "marianas trench": Quantity(1.1e8, "pascal"),
113
+
114
+ # Area measurements
115
+ "postage stamp": Quantity(4.0, "square_centimeter"),
116
+ "a4 paper": Quantity(0.0625, "square_meter"),
117
+ "parking space": Quantity(12.0, "square_meter"),
118
+ "tennis court": Quantity(260.0, "square_meter"),
119
+ "football field area": Quantity(7140.0, "square_meter"),
120
+ "central park": Quantity(3.41e6, "square_meter"),
121
+
122
+ # Volume flow measurements
123
+ "faucet": Quantity(0.1, "liter/second"),
124
+ "shower": Quantity(0.2, "liter/second"),
125
+ "garden hose": Quantity(0.5, "liter/second"),
126
+ "fire hose": Quantity(10.0, "liter/second"),
127
+ "river": Quantity(1000.0, "cubic_meter/second"), # Amazon river
128
+ "niagara falls": Quantity(2400.0, "cubic_meter/second"),
129
+ }
130
+
131
+ def get_measurement(self, object_name: str) -> Quantity | None:
132
+ """
133
+ Get the measurement for a given object name.
134
+
135
+ Args:
136
+ object_name: The name of the object to look up
137
+
138
+ Returns:
139
+ The Quantity object if found, None otherwise
140
+
141
+ Examples:
142
+ >>> db = MeasurementDatabase()
143
+ >>> bath = db.get_measurement("normal bath")
144
+ >>> print(bath)
145
+ 150.0 liter
146
+ """
147
+ object_name = object_name.lower().strip()
148
+ return self.measurements.get(object_name)
149
+
150
+ def add_measurement(self, object_name: str, quantity: Quantity) -> None:
151
+ """
152
+ Add a new measurement to the database.
153
+
154
+ Args:
155
+ object_name: The name of the object
156
+ quantity: The Quantity object representing the measurement
157
+
158
+ Examples:
159
+ >>> db = MeasurementDatabase()
160
+ >>> db.add_measurement("my cup", Quantity(300.0, "milliliter"))
161
+ """
162
+ self.measurements[object_name.lower().strip()] = quantity
163
+
164
+ def find_measurements(self, search_term: str) -> list[tuple[str, Quantity]]:
165
+ """
166
+ Find measurements matching a search term.
167
+
168
+ Args:
169
+ search_term: The term to search for
170
+
171
+ Returns:
172
+ List of (object_name, quantity) tuples that match the search
173
+
174
+ Examples:
175
+ >>> db = MeasurementDatabase()
176
+ >>> results = db.find_measurements("bath")
177
+ >>> for name, qty in results:
178
+ ... print(f"{name}: {qty}")
179
+ """
180
+ search_term = search_term.lower().strip()
181
+ return [(name, qty) for name, qty in self.measurements.items()
182
+ if search_term in name]
183
+
184
+
185
+ class UnitParser:
186
+ """
187
+ Natural language parser for units and measurements.
188
+
189
+ This class can extract units and quantities from natural language text
190
+ and convert them to Quantity objects.
191
+ """
192
+
193
+ def __init__(self, measurement_db: MeasurementDatabase | None = None) -> None:
194
+ self.measurement_db = measurement_db or MeasurementDatabase()
195
+
196
+ # Common unit patterns
197
+ self.unit_patterns = {
198
+ # Volume patterns
199
+ r"liters?|l|ml|milliliters?|cl|centiliters?|dl|deciliters?|hl|hectoliters?",
200
+ r"gallons?|gal|quarts?|pts?|pints?|cups?|tablespoons?|tbsp|teaspoons?|tsp",
201
+
202
+ # Mass patterns
203
+ r"kilograms?|kg|grams?|g|milligrams?|mg|micrograms?|µg|tonnes?|tons?",
204
+
205
+ # Length patterns
206
+ r"meters?|m|centimeters?|cm|millimeters?|mm|kilometers?|km|miles?|mi|feet|ft|inches?|in",
207
+
208
+ # Time patterns
209
+ r"seconds?|s|minutes?|min|hours?|h|days?|weeks?|months?|years?",
210
+
211
+ # Temperature patterns
212
+ r"celsius|c|fahrenheit|f|kelvin|k",
213
+
214
+ # Speed patterns
215
+ r"meters? per second|m/s|km/h|mph|knots?",
216
+
217
+ # Energy patterns
218
+ r"joules?|j|calories?|cal|kilocalories?|kcal|watt hours?|wh|kilowatt hours?|kwh",
219
+
220
+ # Power patterns
221
+ r"watts?|w|kilowatts?|kw|megawatts?|mw|horsepower|hp",
222
+
223
+ # Pressure patterns
224
+ r"pascals?|pa|bars?|atm|atmospheres?|torr|psi",
225
+ }
226
+
227
+ def parse_quantity(self, text: str) -> Quantity | None:
228
+ """
229
+ Parse a quantity from natural language text.
230
+
231
+ Args:
232
+ text: The text to parse
233
+
234
+ Returns:
235
+ A Quantity object if a valid quantity is found, None otherwise
236
+
237
+ Examples:
238
+ >>> parser = UnitParser()
239
+ >>> qty = parser.parse_quantity("5 meters")
240
+ >>> print(qty)
241
+ 5.0 meter
242
+
243
+ >>> qty = parser.parse_quantity("100 km/h")
244
+ >>> print(qty)
245
+ 100.0 kilometer/hour
246
+ """
247
+ text = text.strip().lower()
248
+
249
+ # Try to match number + unit pattern (allow spaces in unit names for compound units and scientific notation)
250
+ quantity_pattern = r"([0-9]+\.?[0-9]*[eE][+-]?[0-9]+|[0-9]+\.?[0-9]*)\s*([a-zA-Z/°µ\s]+)"
251
+ match = re.search(quantity_pattern, text)
252
+
253
+ if match:
254
+ value = float(match.group(1))
255
+ unit_str = match.group(2)
256
+
257
+ # Clean up the unit string - preserve slashes for compound units
258
+ unit_str = unit_str.replace("°", " degree ")
259
+
260
+ # Convert common plural units to singular and handle compound units
261
+ unit_mapping = {
262
+ "meters": "meter",
263
+ "kilometers": "kilometer",
264
+ "centimeters": "centimeter",
265
+ "millimeters": "millimeter",
266
+ "grams": "gram",
267
+ "kilograms": "kilogram",
268
+ "milligrams": "milligram",
269
+ "seconds": "second",
270
+ "minutes": "minute",
271
+ "hours": "hour",
272
+ "liters": "liter",
273
+ "milliliters": "milliliter",
274
+ "watts": "watt",
275
+ "kilowatts": "kilowatt",
276
+ "volts": "volt",
277
+ "amperes": "ampere",
278
+ "ohms": "ohm",
279
+ "hertz": "hertz",
280
+ "newtons": "newton",
281
+ "pascals": "pascal",
282
+ "joules": "joule",
283
+ "coulombs": "coulomb",
284
+ "farads": "farad",
285
+ "henrys": "henry",
286
+ "teslas": "tesla",
287
+ "webers": "weber",
288
+ "lumens": "lumen",
289
+ "luxes": "lux",
290
+ "becquerels": "becquerel",
291
+ "grays": "gray",
292
+ "sieverts": "sievert",
293
+ "katals": "katal",
294
+ "miles": "mile",
295
+ "feet": "foot",
296
+ "inches": "inch",
297
+ "yards": "yard",
298
+ "gallons": "gallon",
299
+ "pounds": "pound",
300
+ "ounces": "ounce",
301
+ # Compound units
302
+ "km/h": "kilometer/hour",
303
+ "kmh": "kilometer/hour",
304
+ "m/s": "meter/second",
305
+ "ms": "meter/second",
306
+ "mph": "mile/hour",
307
+ "knots": "knot",
308
+ "km": "kilometer", # Handle km without /h
309
+ "hrs": "hour", # Alternative for hours
310
+ "l": "liter", # Alternative for liter
311
+ "hr": "hour", # Abbreviation for hour
312
+ "meters per second squared": "meter_per_second_squared",
313
+ "meters/second squared": "meter_per_second_squared",
314
+ "meters per second^2": "meter_per_second_squared",
315
+ "meters/second^2": "meter_per_second_squared",
316
+ }
317
+
318
+ # Store original unit string for display purposes
319
+ original_unit_str = unit_str
320
+
321
+ # Apply unit mapping for internal processing
322
+ if unit_str in unit_mapping:
323
+ unit_str = unit_mapping[unit_str]
324
+
325
+ try:
326
+ # Create quantity with singular unit for internal processing
327
+ quantity = Quantity(value, unit_str)
328
+ # Preserve original unit string for display
329
+ quantity.unit = original_unit_str
330
+ return quantity
331
+ except ValueError:
332
+ # Unit not recognized, try to find it in our patterns
333
+ pass
334
+
335
+ # Try to find known objects
336
+ for object_name, quantity in self.measurement_db.measurements.items():
337
+ if object_name in text.lower():
338
+ return quantity
339
+
340
+ return None
341
+
342
+ def extract_quantities(self, text: str) -> list[Quantity]:
343
+ """
344
+ Extract all quantities from a text.
345
+
346
+ Args:
347
+ text: The text to analyze
348
+
349
+ Returns:
350
+ List of Quantity objects found in the text
351
+
352
+ Examples:
353
+ >>> parser = UnitParser()
354
+ >>> quantities = parser.extract_quantities("A car traveling at 100 km/h for 2 hours")
355
+ >>> for qty in quantities:
356
+ ... print(qty)
357
+ """
358
+ quantities = []
359
+
360
+ # Use regex to find all quantity patterns in the text
361
+ # This pattern matches numbers followed by units (with optional whitespace)
362
+ quantity_pattern = re.compile(r'(\d+\.?\d*)\s*([a-zA-Z/]+)')
363
+
364
+ for match in quantity_pattern.finditer(text):
365
+ value_str, unit_str = match.groups()
366
+ try:
367
+ value = float(value_str)
368
+ # Try to parse this specific quantity
369
+ quantity = self.parse_quantity(f"{value} {unit_str}")
370
+ if quantity:
371
+ quantities.append(quantity)
372
+ except (ValueError, AttributeError):
373
+ # Skip invalid quantities
374
+ continue
375
+
376
+ return quantities
377
+
378
+ def find_units_in_text(self, text: str) -> list[str]:
379
+ """
380
+ Find all unit references in text.
381
+
382
+ Args:
383
+ text: The text to analyze
384
+
385
+ Returns:
386
+ List of unit strings found in the text
387
+
388
+ Examples:
389
+ >>> parser = UnitParser()
390
+ >>> units = parser.find_units_in_text("The speed is 5 m/s and pressure is 1013 hPa")
391
+ >>> print(units)
392
+ ['meter/second', 'hectopascal']
393
+ """
394
+ units_found = []
395
+ text_lower = text.lower()
396
+
397
+ # Check for known units from our database
398
+ all_units: set[str] = set()
399
+ all_units.update(UnitSystem.BASE_UNITS.keys())
400
+ all_units.update(UnitSystem.DERIVED_UNITS.keys())
401
+
402
+ # Also include common unit abbreviations
403
+ unit_abbreviations = {
404
+ 'hpa': 'hectopascal',
405
+ 'c': 'celsius',
406
+ 'f': 'fahrenheit',
407
+ 'k': 'kelvin',
408
+ 'm': 'meter',
409
+ 's': 'second',
410
+ 'kg': 'kilogram',
411
+ 'g': 'gram',
412
+ 'l': 'liter',
413
+ 'ml': 'milliliter',
414
+ 'km': 'kilometer',
415
+ 'cm': 'centimeter',
416
+ 'mm': 'millimeter',
417
+ 'h': 'hour',
418
+ 'min': 'minute',
419
+ 'pa': 'pascal',
420
+ 'kpa': 'kilopascal',
421
+ 'mpa': 'megapascal',
422
+ 'bar': 'bar',
423
+ 'psi': 'psi',
424
+ 'atm': 'atmosphere',
425
+ 'w': 'watt',
426
+ 'kw': 'kilowatt',
427
+ 'j': 'joule',
428
+ 'kj': 'kilojoule',
429
+ 'v': 'volt',
430
+ 'a': 'ampere',
431
+ 'ohm': 'ohm',
432
+ 'hz': 'hertz',
433
+ 'khz': 'kilohertz',
434
+ 'mhz': 'megahertz',
435
+ 'ghz': 'gigahertz',
436
+ 'nm': 'nanometer',
437
+ 'um': 'micrometer',
438
+ 'm/s': 'meter/second',
439
+ 'km/h': 'kilometer/hour',
440
+ 'mph': 'mile/hour',
441
+ 'rpm': 'revolution/minute'
442
+ }
443
+
444
+ # Check for full unit names first (using word boundaries)
445
+ import re
446
+ for unit in all_units:
447
+ # Use word boundary regex to avoid substring matches
448
+ if re.search(r'\b' + re.escape(unit) + r'\b', text_lower):
449
+ units_found.append(unit)
450
+
451
+ # Check for unit abbreviations (using word boundaries)
452
+ for abbr, full_unit in unit_abbreviations.items():
453
+ if re.search(r'\b' + re.escape(abbr) + r'\b', text_lower) and full_unit not in units_found:
454
+ units_found.append(full_unit)
455
+
456
+ return units_found
457
+
458
+
459
+ # Global instance for convenience
460
+ default_measurement_db = MeasurementDatabase()
461
+ default_parser = UnitParser(default_measurement_db)
462
+
463
+
464
+ def get_measurement(object_name: str) -> Quantity | None:
465
+ """
466
+ Convenience function to get a measurement from the default database.
467
+
468
+ Args:
469
+ object_name: The name of the object to look up
470
+
471
+ Returns:
472
+ The Quantity object if found, None otherwise
473
+ """
474
+ return default_measurement_db.get_measurement(object_name)
475
+
476
+
477
+ def parse_quantity(text: str) -> Quantity | None:
478
+ """
479
+ Convenience function to parse a quantity from text.
480
+
481
+ Args:
482
+ text: The text to parse
483
+
484
+ Returns:
485
+ A Quantity object if a valid quantity is found, None otherwise
486
+ """
487
+ return default_parser.parse_quantity(text)
488
+
489
+
490
+ def extract_quantities(text: str) -> list[Quantity]:
491
+ """
492
+ Convenience function to extract quantities from text.
493
+
494
+ Args:
495
+ text: The text to analyze
496
+
497
+ Returns:
498
+ List of Quantity objects found in the text
499
+ """
500
+ return default_parser.extract_quantities(text)
501
+
502
+
503
+ def find_units_in_text(text: str) -> list[str]:
504
+ """
505
+ Convenience function to find units in text.
506
+
507
+ Args:
508
+ text: The text to analyze
509
+
510
+ Returns:
511
+ List of unit strings found in the text
512
+ """
513
+ return default_parser.find_units_in_text(text)