Skip to content

property

This module defines the Property class and property arithmetics.

AbsoluteTemperatureUnitConverter

Bases: AbsoluteUnitConverter

Convert absolute temperature with this converter.

Examples:

>>> AbsoluteTemperatureUnitConverter.convert(10, AbsoluteTemperatureUnit.KELVIN, AbsoluteTemperatureUnit.RANKINE)
18.0
Source code in src/property_utils/units/converters.py
@register_converter(AbsoluteTemperatureUnit)
class AbsoluteTemperatureUnitConverter(AbsoluteUnitConverter):
    """
    Convert absolute temperature with this converter.

    Examples:
        >>> AbsoluteTemperatureUnitConverter.convert(10, AbsoluteTemperatureUnit.KELVIN, AbsoluteTemperatureUnit.RANKINE)
        18.0
    """

    reference_unit = AbsoluteTemperatureUnit.KELVIN
    conversion_map = {
        AbsoluteTemperatureUnit.KELVIN: 1,
        AbsoluteTemperatureUnit.RANKINE: 1.8,
    }

    @override
    @classmethod
    def convert(
        cls,
        value: float,
        from_descriptor: UnitDescriptor,
        to_descriptor: UnitDescriptor,
    ) -> float:
        if not isinstance(value, (float, int)):
            raise UnitConversionError(f"invalid 'value': {value}; expected numeric. ")
        if from_descriptor.isinstance(
            RelativeTemperatureUnit
        ) or to_descriptor.isinstance(RelativeTemperatureUnit):
            return RelativeTemperatureUnitConverter.convert(
                value, from_descriptor, to_descriptor
            )
        return value * cls.get_factor(from_descriptor, to_descriptor)

AliasEnergyUnitConverter

Bases: AbsoluteUnitConverter

Convert energy units with this converter.

Examples:

>>> AliasEnergyUnitConverter.convert(2500, EnergyUnit.JOULE, EnergyUnit.KILO_JOULE)
2.5
Source code in src/property_utils/units/converters.py
@register_converter(EnergyUnit)
class AliasEnergyUnitConverter(AbsoluteUnitConverter):
    """
    Convert energy units with this converter.

    Examples:
        >>> AliasEnergyUnitConverter.convert(2500, EnergyUnit.JOULE, EnergyUnit.KILO_JOULE)
        2.5
    """

    reference_unit = EnergyUnit.JOULE
    conversion_map = {
        EnergyUnit.JOULE: 1,
        EnergyUnit.KILO_JOULE: UnitPrefix.KILO.inverse(),
        EnergyUnit.MEGA_JOULE: UnitPrefix.MEGA.inverse(),
        EnergyUnit.GIGA_JOULE: UnitPrefix.GIGA.inverse(),
        EnergyUnit.CALORIE: 1 / 4.184,
        EnergyUnit.KILO_CALORIE: (1 / 4.184) * UnitPrefix.KILO.inverse(),
        EnergyUnit.BTU: 1 / 1055.0,
        EnergyUnit.ELECTRONVOLT: 6.242e18,
        EnergyUnit.WATTHOUR: 1 / 3600,
        EnergyUnit.KILO_WATTHOUR: (1 / 3600) * UnitPrefix.KILO.inverse(),
    }

AliasForceUnitConverter

Bases: AbsoluteUnitConverter

Convert force units with this converter.

Examples:

>>> AliasForceUnitConverter.convert(2, ForceUnit.NEWTON, ForceUnit.DYNE)
200000.0
Source code in src/property_utils/units/converters.py
@register_converter(ForceUnit)
class AliasForceUnitConverter(AbsoluteUnitConverter):
    """
    Convert force units with this converter.

    Examples:
        >>> AliasForceUnitConverter.convert(2, ForceUnit.NEWTON, ForceUnit.DYNE)
        200000.0
    """

    reference_unit = ForceUnit.NEWTON
    conversion_map = {ForceUnit.NEWTON: 1, ForceUnit.DYNE: 100_000}

AliasPowerUnitConverter

Bases: AbsoluteUnitConverter

Convert power units with this converter.

Examples:

>>> AliasPowerUnitConverter.convert(5, PowerUnit.KILO_WATT, PowerUnit.WATT)
5000.0
Source code in src/property_utils/units/converters.py
@register_converter(PowerUnit)
class AliasPowerUnitConverter(AbsoluteUnitConverter):
    """
    Convert power units with this converter.

    Examples:
        >>> AliasPowerUnitConverter.convert(5, PowerUnit.KILO_WATT, PowerUnit.WATT)
        5000.0
    """

    reference_unit = PowerUnit.WATT
    conversion_map = {
        PowerUnit.WATT: 1,
        PowerUnit.KILO_WATT: UnitPrefix.KILO.inverse(),
        PowerUnit.MEGA_WATT: UnitPrefix.MEGA.inverse(),
        PowerUnit.GIGA_WATT: UnitPrefix.GIGA.inverse(),
    }

AliasPressureUnitConverter

Bases: AbsoluteUnitConverter

Convert pressure units with this converter.

Examples:

>>> AliasPressureUnitConverter.convert(2, PressureUnit.BAR, PressureUnit.KILO_PASCAL)
200.0
Source code in src/property_utils/units/converters.py
@register_converter(PressureUnit)
class AliasPressureUnitConverter(AbsoluteUnitConverter):
    """
    Convert pressure units with this converter.

    Examples:
        >>> AliasPressureUnitConverter.convert(2, PressureUnit.BAR, PressureUnit.KILO_PASCAL)
        200.0
    """

    reference_unit = PressureUnit.BAR
    conversion_map = {
        PressureUnit.MILLI_BAR: UnitPrefix.MILLI.inverse(),
        PressureUnit.BAR: 1,
        PressureUnit.PSI: 14.5038,
        PressureUnit.PASCAL: 100_000,
        PressureUnit.KILO_PASCAL: 100,
        PressureUnit.MEGA_PASCAL: 0.1,
    }

AmountUnitConverter

Bases: AbsoluteUnitConverter

Convert amount units with this converter.

Examples:

>>> AmountUnitConverter.convert(2000, AmountUnit.MOL, AmountUnit.KILO_MOL)
2.0
Source code in src/property_utils/units/converters.py
@register_converter(AmountUnit)
class AmountUnitConverter(AbsoluteUnitConverter):
    """
    Convert amount units with this converter.

    Examples:
        >>> AmountUnitConverter.convert(2000, AmountUnit.MOL, AmountUnit.KILO_MOL)
        2.0
    """

    reference_unit = AmountUnit.MOL
    conversion_map = {AmountUnit.MOL: 1, AmountUnit.KILO_MOL: UnitPrefix.KILO.inverse()}

AreaUnitConverter

Bases: ExponentiatedUnitConverter

Convert area units with this converter.

Examples:

>>> AreaUnitConverter.convert(1, LengthUnit.METER**2, LengthUnit.CENTI_METER**2)
10000.0
Source code in src/property_utils/units/converters.py
@register_converter(LengthUnit**2)
class AreaUnitConverter(ExponentiatedUnitConverter):
    """
    Convert area units with this converter.

    Examples:
        >>> AreaUnitConverter.convert(1, LengthUnit.METER**2, LengthUnit.CENTI_METER**2)
        10000.0
    """

ElectricCurrentUnitConverter

Bases: AbsoluteUnitConverter

Convert electric current units with this converter.

Examples:

>>> ElectricCurrentUnitConverter.convert(1000, ElectricCurrentUnit.MILLI_AMPERE, ElectricCurrentUnit.AMPERE)
1.0
Source code in src/property_utils/units/converters.py
@register_converter(ElectricCurrentUnit)
class ElectricCurrentUnitConverter(AbsoluteUnitConverter):
    """
    Convert electric current units with this converter.

    Examples:
        >>> ElectricCurrentUnitConverter.convert(1000, ElectricCurrentUnit.MILLI_AMPERE, ElectricCurrentUnit.AMPERE)
        1.0
    """

    reference_unit = ElectricCurrentUnit.AMPERE
    conversion_map = {
        ElectricCurrentUnit.MILLI_AMPERE: UnitPrefix.MILLI.inverse(),
        ElectricCurrentUnit.AMPERE: 1,
        ElectricCurrentUnit.KILO_AMPERE: UnitPrefix.KILO.inverse(),
    }

EnergyUnitConverter

Bases: CompositeUnitConverter

Convert energy units (mass * length^2 / time^2) with this converter.

Examples:

>>> from_unit = MassUnit.KILO_GRAM * (LengthUnit.METER**2) / (TimeUnit.MINUTE**2)
>>> to_unit = MassUnit.METRIC_TONNE * (LengthUnit.CENTI_METER**2) / (TimeUnit.MINUTE**2)
>>> EnergyUnitConverter.convert(25, from_unit, to_unit)
250.0
Source code in src/property_utils/units/converters.py
@register_converter(EnergyUnit.aliased_generic_descriptor())
class EnergyUnitConverter(CompositeUnitConverter):
    """
    Convert energy units (mass * length^2 / time^2) with this converter.

    Examples:
        >>> from_unit = MassUnit.KILO_GRAM * (LengthUnit.METER**2) / (TimeUnit.MINUTE**2)
        >>> to_unit = MassUnit.METRIC_TONNE * (LengthUnit.CENTI_METER**2) / (TimeUnit.MINUTE**2)
        >>> EnergyUnitConverter.convert(25, from_unit, to_unit)
        250.0
    """

ForceUnitConverter

Bases: CompositeUnitConverter

Convert force units (mass * length / time^2) with this converter.

Examples:

>>> from_unit = MassUnit.KILO_GRAM * LengthUnit.CENTI_METER / (TimeUnit.SECOND**2)
>>> to_unit = MassUnit.GRAM * LengthUnit.METER / (TimeUnit.SECOND**2)
>>> ForceUnitConverter.convert(100, from_unit, to_unit)
1000.0
Source code in src/property_utils/units/converters.py
@register_converter(ForceUnit.aliased_generic_descriptor())
class ForceUnitConverter(CompositeUnitConverter):
    """
    Convert force units (mass * length / time^2) with this converter.

    Examples:
        >>> from_unit = MassUnit.KILO_GRAM * LengthUnit.CENTI_METER / (TimeUnit.SECOND**2)
        >>> to_unit = MassUnit.GRAM * LengthUnit.METER / (TimeUnit.SECOND**2)
        >>> ForceUnitConverter.convert(100, from_unit, to_unit)
        1000.0
    """

LengthUnitConverter

Bases: AbsoluteUnitConverter

Convert length units with this converter.

Examples:

>>> LengthUnitConverter.convert(2000, LengthUnit.MILLI_METER, LengthUnit.METER)
2.0
Source code in src/property_utils/units/converters.py
@register_converter(LengthUnit)
class LengthUnitConverter(AbsoluteUnitConverter):
    """
    Convert length units with this converter.

    Examples:
        >>> LengthUnitConverter.convert(2000, LengthUnit.MILLI_METER, LengthUnit.METER)
        2.0
    """

    reference_unit = LengthUnit.METER
    conversion_map = {
        LengthUnit.MILLI_METER: UnitPrefix.MILLI.inverse(),
        LengthUnit.CENTI_METER: UnitPrefix.CENTI.inverse(),
        LengthUnit.METER: 1,
        LengthUnit.KILO_METER: UnitPrefix.KILO.inverse(),
        LengthUnit.INCH: 39.37,
        LengthUnit.FOOT: 3.281,
        LengthUnit.YARD: 1.094,
        LengthUnit.MILE: 1 / 1609,
        LengthUnit.NAUTICAL_MILE: 1 / 1852,
    }

MassUnitConverter

Bases: AbsoluteUnitConverter

Convert mass units with this converter.

Examples:

>>> MassUnitConverter.convert(10, MassUnit.KILO_GRAM, MassUnit.GRAM)
10000.0
Source code in src/property_utils/units/converters.py
@register_converter(MassUnit)
class MassUnitConverter(AbsoluteUnitConverter):
    """
    Convert mass units with this converter.

    Examples:
        >>> MassUnitConverter.convert(10, MassUnit.KILO_GRAM, MassUnit.GRAM)
        10000.0
    """

    reference_unit = MassUnit.KILO_GRAM
    conversion_map = {
        MassUnit.MILLI_GRAM: UnitPrefix.KILO * UnitPrefix.MILLI.inverse(),
        MassUnit.GRAM: UnitPrefix.KILO,
        MassUnit.KILO_GRAM: 1,
        MassUnit.METRIC_TONNE: 1 / 1_000.0,
        MassUnit.POUND: 2.205,
    }

PowerUnitConverter

Bases: CompositeUnitConverter

Convert power units (mass * length^2 / time^3) with this converter.

Examples:

>>> from_unit = MassUnit.KILO_GRAM * (LengthUnit.METER**2) / (TimeUnit.MINUTE**3)
>>> to_unit = MassUnit.METRIC_TONNE * (LengthUnit.CENTI_METER**2) / (TimeUnit.MINUTE**3)
>>> PowerUnitConverter.convert(15, from_unit, to_unit)
150.0
Source code in src/property_utils/units/converters.py
@register_converter(PowerUnit.aliased_generic_descriptor())
class PowerUnitConverter(CompositeUnitConverter):
    """
    Convert power units (mass * length^2 / time^3) with this converter.

    Examples:
        >>> from_unit = MassUnit.KILO_GRAM * (LengthUnit.METER**2) / (TimeUnit.MINUTE**3)
        >>> to_unit = MassUnit.METRIC_TONNE * (LengthUnit.CENTI_METER**2) / (TimeUnit.MINUTE**3)
        >>> PowerUnitConverter.convert(15, from_unit, to_unit)
        150.0
    """

PressureUnitConverter

Bases: CompositeUnitConverter

Convert pressure units (mass / length / time^2) with this converter.

Examples:

>>> from_unit = MassUnit.GRAM / LengthUnit.CENTI_METER / (TimeUnit.HOUR**2)
>>> to_unit = MassUnit.KILO_GRAM / LengthUnit.METER / (TimeUnit.HOUR**2)
>>> PressureUnitConverter.convert(50, from_unit, to_unit)
5.0
Source code in src/property_utils/units/converters.py
@register_converter(PressureUnit.aliased_generic_descriptor())
class PressureUnitConverter(CompositeUnitConverter):
    """
    Convert pressure units (mass / length / time^2) with this converter.

    Examples:
        >>> from_unit = MassUnit.GRAM / LengthUnit.CENTI_METER / (TimeUnit.HOUR**2)
        >>> to_unit = MassUnit.KILO_GRAM / LengthUnit.METER / (TimeUnit.HOUR**2)
        >>> PressureUnitConverter.convert(50, from_unit, to_unit)
        5.0
    """

Property dataclass

A Property describes a value with a unit of measurement.

A Property can have any 'value' or 'unit'; validations are not applied to it. For example, a Property with length units and negative value is valid.

Source code in src/property_utils/properties/property.py
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
@dataclass
class Property:
    """
    A Property describes a value with a unit of measurement.

    A Property can have any 'value' or 'unit'; validations are not applied to it.
    For example, a Property with length units and negative value is valid.
    """

    value: float
    unit: UnitDescriptor
    unit_converter: Optional[Type[UnitConverter]] = None
    default_units: ClassVar[Optional[UnitDescriptor]] = None

    def __init__(self, value: float, unit: Optional[UnitDescriptor] = None) -> None:
        if not isinstance(value, (float, int)):
            raise PropertyValidationError(
                f"cannot create Property; invalid 'value': {value}; expected numeric. "
            )

        if unit is None and self.default_units is not None:
            unit = self.default_units

        if not isinstance(unit, (MeasurementUnit, Dimension, CompositeDimension)):
            raise PropertyValidationError(
                f"cannot create Property; invalid 'unit': {unit}. Expected an instance"
                " of one of: MeasurementUnit, Dimension, CompositeDimension. "
            )

        self.value = value
        self.unit = unit

    def eq(self, other: "Property", *, rel_tol=1e-9, abs_tol=0) -> bool:
        """
        Perform equality comparison between this and some other Property. This method
        of testing equality is preferable to the equality operator '==' because float
        point tolerance is taken into account.

        rel_tol is the maximum difference for being considered "close", relative
        to the magnitude of the input values.
        abs_tol is the maximum difference for being considered "close", regardless of
        the magnitude of the input values.
        For the values to be considered close, the difference between them must be
        smaller than at least one of the tolerances.

        Raises `PropertyBinaryOperationError` if an error occurs during conversion
        of other's units.

        Examples:
            >>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
            >>> T1 = Property(33.333333, AbsoluteTemperatureUnit.KELVIN)
            >>> T2 = Property(100/3, AbsoluteTemperatureUnit.KELVIN)
            >>> T1 == T2
            False
            >>> T1.eq(T2)
            False
            >>> T1.eq(T2, rel_tol=0.1)
            True
        """
        if not isinstance(other, Property):
            return False
        if not self.unit.isinstance_equivalent(other.unit.to_generic()):
            return False
        try:
            prop = other.to_unit(self.unit) if self.unit != other.unit else other
        except PropertyUtilsException as exc:
            raise PropertyBinaryOperationError(
                f"during conversion of {other} to ({self.unit}) units an error occured: ",
                exc,
            ) from None
        return isclose(self.value, prop.value, rel_tol=rel_tol, abs_tol=abs_tol)

    def to_si(self) -> Self:
        """
        Create a new property with SI units.

        Raises `ImpossiblePropertyUnitConverion` if there is no converter registered
        for the unit.

        Raises `InvalidUnitConversion` if any error occurs in the unit conversion.

        Examples:
            >>> from property_utils.units.units import RelativeTemperatureUnit
            >>> T = Property(100, RelativeTemperatureUnit.CELCIUS)
            >>> T.to_si()
            <Property: 373.15 K>
        """
        if isinstance(self.unit, MeasurementUnit):
            return self.to_unit(self.unit.si())
        if isinstance(self.unit, Dimension):
            return self.to_unit(self.unit.unit.si() ** self.unit.power)
        if isinstance(self.unit, CompositeDimension):
            return self.to_unit(self.unit.to_generic().to_si())
        raise PropertyValidationError(
            f"cannot convert Property to SI; 'unit' is invalid: {self.unit}. "
        )

    def to_unit(self, unit: UnitDescriptor) -> Self:
        """
        Create a new property with specified unit.

        Raises `PropertyUnitConversionError` if the unit is not of the same type.

        Raises `ImpossiblePropertyUnitConverion` if there is no converter registered
        for the unit.

        Raises `UnitConversionError` if any error occurs in the unit conversion.

        Examples:
            >>> from property_utils.units.units import RelativeTemperatureUnit
            >>> T = Property(100, RelativeTemperatureUnit.CELCIUS)
            >>> T.to_unit(RelativeTemperatureUnit.FAHRENHEIT)
            <Property: 212.0 °F>
        """
        if self.unit == unit:
            return self.__class__(unit=self.unit, value=self.value)

        if not unit.isinstance_equivalent(self.unit.to_generic()):
            raise PropertyUnitConversionError(
                f"cannot convert {self} to ({unit}) units; 'unit' should be an instance"
                f" of {self.unit.to_generic()}. "
            )
        try:
            converter = self._converter()
        except UndefinedConverterError:
            raise PropertyUnitConversionError(
                f"cannot convert property {self} to units: {unit}; no unit converter "
                f" found for {unit.to_generic()}. "
                "Did you forget to @register_converter? "
            ) from None
        try:
            value = converter.convert(self.value, self.unit, unit)
        except UnitConversionError as exc:
            raise exc from None
        return self.__class__(value=value, unit=unit)

    def __neg__(self) -> Self:
        """
        Defines negation of properties.

        Examples:
            >>> from property_utils.units.units import RelativeTemperatureUnit
            >>> T = Property(3, RelativeTemperatureUnit.CELCIUS)
            >>> -T
            <Property: -3 °C>
        """
        return self.__class__(-self.value, self.unit)

    def __mul__(self, other) -> "Property":
        """
        Defines multiplication between properties and numerics.

        Examples:
            >>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
            >>> T = Property(300, AbsoluteTemperatureUnit.KELVIN)
            >>> 2*T
            <Property: 600 K>
            >>> A = Property(10, LengthUnit.METER**2)
            >>> T * A
            <Property: 3000 (m^2) * K>
        """
        if isinstance(other, (float, int)):
            return self.__class__(self.value * other, self.unit)
        if isinstance(other, Property):
            _other = self._unit_preconversion(other)
            return Property(
                self.value * _other.value, self._simplify_units(self.unit * _other.unit)
            )
        raise PropertyBinaryOperationError(
            f"cannot multiply {self} with {other}; "
            "second operand must be numeric or Property. "
        )

    def __rmul__(self, other) -> "Property":
        """
        Defines multiplication between properties and numerics.

        Examples:
            >>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
            >>> T = Property(300, AbsoluteTemperatureUnit.KELVIN)
            >>> 2*T
            <Property: 600 K>
            >>> A = Property(10, LengthUnit.METER**2)
            >>> T * A
            <Property: 3000 (m^2) * K>
        """
        return self.__mul__(other)

    def __truediv__(self, other) -> "Property":
        """
        Defines division between properties and numerics.

        Examples:
            >>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
            >>> T = Property(500, AbsoluteTemperatureUnit.KELVIN)
            >>> T/2
            <Property: 250.0 K>
            >>> A = Property(10, LengthUnit.METER**2)
            >>> T / A
            <Property: 50.0 K / (m^2)>
        """
        if isinstance(other, (float, int)):
            try:
                value = self.value / other
            except ZeroDivisionError:
                raise PropertyBinaryOperationError(
                    f"cannot divide {self} with {other}; denominator is zero. "
                ) from None
            return Property(value, self.unit)
        if isinstance(other, Property):
            _other = self._unit_preconversion(other)
            try:
                value = self.value / _other.value
            except ZeroDivisionError:
                raise PropertyBinaryOperationError(
                    f"cannot divide {self} with {other}; denominator's value is zero. "
                ) from None
            return Property(value, self._simplify_units(self.unit / _other.unit))
        raise PropertyBinaryOperationError(
            f"cannot divide {self} with {other}; "
            "denominator must be numeric or Property. "
        )

    def __rtruediv__(self, other) -> "Property":
        """
        Defines right division between properties and numerics.

        Examples:
            >>> from property_utils.units.units import AbsoluteTemperatureUnit
            >>> T = Property(500, AbsoluteTemperatureUnit.KELVIN)
            >>> 100/T
            <Property: 0.2  / K>
        """
        if isinstance(other, (float, int)):
            try:
                value = other / self.value
            except ZeroDivisionError:
                raise PropertyBinaryOperationError(
                    f"cannot divide {self} with {other}; denominator is zero. "
                ) from None
            return Property(value, self.unit.inverse())
        if isinstance(other, Property):
            try:
                value = other.value / self.value
            except ZeroDivisionError:
                raise PropertyBinaryOperationError(
                    f"cannot divide {self} with {other}; denominator's value is zero. "
                ) from None
            return Property(value, self._simplify_units(other.unit / self.unit))
        raise PropertyBinaryOperationError(
            f"cannot divide {self} with {other}; "
            "numerator must be numeric or Property. "
        )

    def __add__(self, other) -> Self:
        """
        Defines addition between properties.

        Examples:
            >>> from property_utils.units.units import LengthUnit
            >>> x1 = Property(15, LengthUnit.METER)
            >>> x2 = Property(5, LengthUnit.METER)
            >>> x1 + x2
            <Property: 20 m>
        """
        if not isinstance(other, Property):
            raise PropertyBinaryOperationError(
                f"cannot add {other} to ({self}); {other} is not a Property; "
                "only properties can be added to properties. "
            )
        if not self.unit.isinstance_equivalent(other.unit.to_generic()):
            raise PropertyBinaryOperationError(
                f"cannot add ({other}) to ({self}); "
                f"({other}) must have ({self.unit.to_generic()}) units. "
            )
        try:
            prop = other.to_unit(self.unit) if self.unit != other.unit else other
        except PropertyUnitConversionError:
            raise PropertyBinaryOperationError(
                f"cannot add ({other}) to ({self}); ({other}) does not have the same "
                f"units as ({self}) and there is no unit converter registered for "
                f"({self.unit.to_generic()}). "
            ) from None
        except UnitConversionError as exc:
            raise PropertyBinaryOperationError(
                f"cannot add ({other}) to ({self});", exc
            ) from None
        return self.__class__(self.value + prop.value, self.unit)

    def __radd__(self, other) -> Self:
        """
        Defines right addition between properties.

        Examples:
            >>> from property_utils.units.units import LengthUnit
            >>> x1 = Property(15, LengthUnit.METER)
            >>> x2 = Property(5, LengthUnit.METER)
            >>> x1 + x2
            <Property: 20 m>
        """
        return self.__add__(other)

    def __sub__(self, other) -> Self:
        """
        Defines subtraction between properties.

        Examples:
            >>> from property_utils.units.units import TimeUnit
            >>> t1 = Property(2, TimeUnit.MINUTE)
            >>> t2 = Property(60, TimeUnit.SECOND)
            >>> t1 - t2
            <Property: 1.0 min>
        """
        if not isinstance(other, Property):
            raise PropertyBinaryOperationError(
                f"cannot subtract {other} from ({self}); {other} is not a "
                "Property; only properties can be subtracted from properties. "
            )
        if not self.unit.isinstance_equivalent(other.unit.to_generic()):
            raise PropertyBinaryOperationError(
                f"cannot subtract ({other}) from ({self}); "
                f"({other}) must have ({self.unit.to_generic()}) units. "
            )
        try:
            prop = other.to_unit(self.unit) if self.unit != other.unit else other
        except PropertyUnitConversionError:
            raise PropertyBinaryOperationError(
                f"cannot subtract ({other}) from ({self}); ({other}) does not have the "
                f"same units as ({self}) and there is no unit converter registered for "
                f"({self.unit.to_generic()}). "
            ) from None
        except UnitConversionError as exc:
            raise PropertyBinaryOperationError(
                f"cannot subtract ({other}) from ({self});", exc
            ) from None
        return self.__class__(self.value - prop.value, self.unit)

    def __rsub__(self, other) -> "Property":
        """
        Defines right subtraction between properties.

        Examples:
            >>> from property_utils.units.units import TimeUnit
            >>> t1 = Property(2, TimeUnit.MINUTE)
            >>> t2 = Property(60, TimeUnit.SECOND)
            >>> t1 - t2
            <Property: 1.0 min>
        """
        if not isinstance(other, Property):
            raise PropertyBinaryOperationError(
                f"cannot subtract {self} from ({other}); {other} is not a "
                f"{self.__class__}; only same properties can be subtracted from each "
                "other. "
            )
        if not self.unit.isinstance_equivalent(other.unit.to_generic()):
            raise PropertyBinaryOperationError(
                f"cannot subtract ({self}) from ({other}); "
                f"({other}) must have ({self.unit.to_generic()}) units. "
            )
        return other.__add__(-self)

    def __pow__(self, power) -> "Property":
        """
        Defines exponentiation for properties.

        Examples:
            >>> from property_utils.units.units import LengthUnit
            >>> L = Property(5, LengthUnit.METER)
            >>> L**3
            <Property: 125 (m^3)>
        """
        if not isinstance(power, (float, int)):
            raise PropertyExponentError(
                f"invalid exponent: {power}; expected numeric. "
            )
        return Property(self.value**power, self.unit**power)

    def __eq__(self, other) -> bool:
        """
        Defines equality between properties.
        Prefer Property.eq instead.The equality operator returns False even for very
        small differences between floating point values.

        Examples:
            >>> from property_utils.units.units import LengthUnit
            >>> L1 = Property(500, LengthUnit.CENTI_METER)
            >>> L2 = Property(5, LengthUnit.METER)
            >>> L1 == L2
            True

            >>> L3 = Property(6, LengthUnit.METER)
            >>> L2 == L3
            False
        """
        if not isinstance(other, Property):
            return False
        if not self.unit.isinstance_equivalent(other.unit.to_generic()):
            return False
        try:
            prop = other.to_unit(self.unit) if self.unit != other.unit else other
        except PropertyUtilsException as exc:
            raise PropertyBinaryOperationError(
                f"during conversion of {other} to ({self.unit}) units an error occured: ",
                exc,
            ) from None
        return self.value == prop.value

    def __ne__(self, other) -> bool:
        """
        Defines inequality between properties.
        Prefer Property.eq instead.The inequality operator returns True even for very
        small differences between floating point values.

        Examples:
            >>> from property_utils.units.units import LengthUnit
            >>> L1 = Property(500, LengthUnit.CENTI_METER)
            >>> L2 = Property(5, LengthUnit.METER)
            >>> L1 != L2
            False

            >>> L3 = Property(6, LengthUnit.METER)
            >>> L2 != L3
            True
        """
        return not self.__eq__(other)

    def __gt__(self, other) -> bool:
        """
        Defines comparison between properties.

        Examples:
            >>> from property_utils.units.units import RelativeTemperatureUnit
            >>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
            >>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
            >>> T1 > T2
            False
        """
        self._validate_comparison_input(other)
        try:
            prop = other.to_unit(self.unit) if self.unit != other.unit else other
        except PropertyUnitConversionError:
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self}); ({other}) does not have the "
                f"same units as ({self}) and there is no unit converter registered for "
                f"({self.unit.to_generic()}). "
            ) from None
        except UnitConversionError as exc:
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self});", exc
            ) from None
        return self.value > prop.value

    def __ge__(self, other) -> bool:
        """
        Defines comparison between properties.

        Examples:
            >>> from property_utils.units.units import RelativeTemperatureUnit
            >>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
            >>> T2 = Property(212, RelativeTemperatureUnit.FAHRENHEIT)
            >>> T1 >= T2
            True
        """
        self._validate_comparison_input(other)
        try:
            prop = other.to_unit(self.unit) if self.unit != other.unit else other
        except PropertyUnitConversionError:
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self}); ({other}) does not have the "
                f"same units as ({self}) and there is no unit converter registered for "
                f"({self.unit.to_generic()}). "
            ) from None
        except UnitConversionError as exc:
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self});", exc
            ) from None
        return self.value >= prop.value

    def __lt__(self, other) -> bool:
        """
        Defines comparison between properties.

        Examples:
            >>> from property_utils.units.units import RelativeTemperatureUnit
            >>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
            >>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
            >>> T1 < T2
            True
        """
        self._validate_comparison_input(other)
        try:
            prop = other.to_unit(self.unit) if self.unit != other.unit else other
        except PropertyUnitConversionError:
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self}); ({other}) does not have the "
                f"same units as ({self}) and there is no unit converter registered for "
                f"({self.unit.to_generic()}). "
            ) from None
        except UnitConversionError as exc:
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self});", exc
            ) from None
        return self.value < prop.value

    def __le__(self, other) -> bool:
        """
        Defines comparison between properties.

        Examples:
            >>> from property_utils.units.units import RelativeTemperatureUnit
            >>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
            >>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
            >>> T1 <= T2
            True
        """
        self._validate_comparison_input(other)
        try:
            prop = other.to_unit(self.unit) if self.unit != other.unit else other
        except PropertyUnitConversionError:
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self}); ({other}) does not have the "
                f"same units as ({self}) and there is no unit converter registered for "
                f"({self.unit.to_generic()}). "
            ) from None
        except UnitConversionError as exc:
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self});", exc
            ) from None
        return self.value <= prop.value

    def __str__(self) -> str:
        return f"{self.value} {self.unit}"

    def __repr__(self) -> str:
        return f"<Property: {str(self)}>"

    def _converter(self) -> Type[UnitConverter]:
        """
        Raises `UndefinedConverter` if a converter is not defined.
        """
        if self.unit_converter is None:
            self.unit_converter = self._get_converter()
        return self.unit_converter

    def _get_converter(self) -> Type[UnitConverter]:
        """
        Raises `UndefinedConverter` if a converter is not defined.
        """
        return get_converter(self.unit.to_generic())

    def _validate_comparison_input(self, other) -> None:
        """
        Raises `PropertyBinaryOperationError` if other is not a Property or if it does
        not have the same unit type as this Property.
        """
        if not isinstance(other, Property):
            raise PropertyBinaryOperationError(
                f"cannot compare {other} to ({self}); {other} is not a Property; "
                "only properties can be compared to properties. "
            )
        if not self.unit.isinstance_equivalent(other.unit.to_generic()):
            raise PropertyBinaryOperationError(
                f"cannot compare ({other}) to ({self}); "
                f"({other}) must have ({self.unit.to_generic()}) units. "
            )

    def _unit_preconversion(self, prop: "Property") -> "Property":
        """
        Applies a conversion to the given property's units before it is multiplied or
        divided with this unit.

        The preconversion is needed to produce simplified units from the multiplication/
        division.
        For example, if you multiply 5 cm with 2.02 m you don't want to get the result
        in cm * m; in order to get the result in cm^2, 2.02 m (the right operand) is
        converted to cm first.
        """
        if isinstance(prop.unit, CompositeDimension):
            return prop.to_unit(self._composite_unit_preconversion(prop.unit))

        if isinstance(prop.unit, Dimension):
            return prop.to_unit(self._dimension_unit_preconversion(prop.unit))

        if isinstance(prop.unit, MeasurementUnit):
            return prop.to_unit(self._simple_unit_preconversion(prop.unit))

        return prop

    # pylint: disable=too-many-branches
    def _composite_unit_preconversion(
        self, unit: CompositeDimension
    ) -> CompositeDimension:
        """
        Returns the composite dimension that the given dimension should be converted to
        before multiplication or division with this property.
        """
        other = replace(unit).simplified()

        if isinstance(self.unit, CompositeDimension):
            self.unit.simplify()

            for i, num in enumerate(other.numerator):
                _n = self.unit.get_numerator(num.to_generic(), None)
                if _n is not None:
                    other.numerator[i] = replace(_n)

                d = self.unit.get_denominator(num.to_generic(), None)
                if d is not None:
                    other.numerator[i] = replace(d)

            for i, d in enumerate(other.denominator):
                _d = self.unit.get_denominator(d.to_generic(), None)
                if _d is not None:
                    other.denominator[i] = replace(_d)

                n = self.unit.get_numerator(d.to_generic(), None)
                if n is not None:
                    other.denominator[i] = replace(n)

            return other

        _self: UnitDescriptor
        if isinstance(self.unit, MeasurementUnit):
            _self = self.unit**1
        elif isinstance(self.unit, Dimension):
            _self = replace(self.unit)
        else:
            _self = self.unit

        if isinstance(_self, Dimension):
            for i, n in enumerate(other.numerator):
                if n.unit.isinstance(_self.unit.to_generic()):
                    other.numerator[i] = _self.unit ** other.numerator[i].power
                    return other

            for i, d in enumerate(other.denominator):
                if d.unit.isinstance(_self.unit.to_generic()):
                    other.denominator[i] = _self.unit ** other.denominator[i].power
                    return other

        return unit

    def _dimension_unit_preconversion(self, unit: Dimension) -> Dimension:
        """
        Returns the dimension that the given dimension should be converted to before
        multiplication or division with this property.
        """
        if isinstance(self.unit, CompositeDimension):
            self.unit.simplify()

            for d in self.unit.denominator:
                if d.unit.isinstance(unit.unit.to_generic()):
                    return d.unit**unit.power

            for n in self.unit.numerator:
                if n.unit.isinstance(unit.unit.to_generic()):
                    return n.unit**unit.power

        _self: UnitDescriptor
        if isinstance(self.unit, Dimension):
            _self = self.unit.unit
        else:
            _self = self.unit

        if isinstance(_self, MeasurementUnit):
            if _self.isinstance(unit.unit.to_generic()):
                return _self**unit.power

        return unit

    def _simple_unit_preconversion(self, unit: MeasurementUnit) -> MeasurementUnit:
        """
        Returns the unit that the given unit should be converted to before
        multiplication or division with this property.
        """
        return self._dimension_unit_preconversion(unit**1).unit

    def _simplify_units(self, unit: CompositeDimension) -> UnitDescriptor:
        """
        Simplifies the composite dimension and returns NON_DIMENSIONAL if the simplified
        composite does not have units.
        """
        unit = unit.simplified()

        if unit.has_no_units():
            return NonDimensionalUnit.NON_DIMENSIONAL

        return unit

__add__(other)

Defines addition between properties.

Examples:

>>> from property_utils.units.units import LengthUnit
>>> x1 = Property(15, LengthUnit.METER)
>>> x2 = Property(5, LengthUnit.METER)
>>> x1 + x2
<Property: 20 m>
Source code in src/property_utils/properties/property.py
def __add__(self, other) -> Self:
    """
    Defines addition between properties.

    Examples:
        >>> from property_utils.units.units import LengthUnit
        >>> x1 = Property(15, LengthUnit.METER)
        >>> x2 = Property(5, LengthUnit.METER)
        >>> x1 + x2
        <Property: 20 m>
    """
    if not isinstance(other, Property):
        raise PropertyBinaryOperationError(
            f"cannot add {other} to ({self}); {other} is not a Property; "
            "only properties can be added to properties. "
        )
    if not self.unit.isinstance_equivalent(other.unit.to_generic()):
        raise PropertyBinaryOperationError(
            f"cannot add ({other}) to ({self}); "
            f"({other}) must have ({self.unit.to_generic()}) units. "
        )
    try:
        prop = other.to_unit(self.unit) if self.unit != other.unit else other
    except PropertyUnitConversionError:
        raise PropertyBinaryOperationError(
            f"cannot add ({other}) to ({self}); ({other}) does not have the same "
            f"units as ({self}) and there is no unit converter registered for "
            f"({self.unit.to_generic()}). "
        ) from None
    except UnitConversionError as exc:
        raise PropertyBinaryOperationError(
            f"cannot add ({other}) to ({self});", exc
        ) from None
    return self.__class__(self.value + prop.value, self.unit)

__eq__(other)

Defines equality between properties. Prefer Property.eq instead.The equality operator returns False even for very small differences between floating point values.

Examples:

>>> from property_utils.units.units import LengthUnit
>>> L1 = Property(500, LengthUnit.CENTI_METER)
>>> L2 = Property(5, LengthUnit.METER)
>>> L1 == L2
True
>>> L3 = Property(6, LengthUnit.METER)
>>> L2 == L3
False
Source code in src/property_utils/properties/property.py
def __eq__(self, other) -> bool:
    """
    Defines equality between properties.
    Prefer Property.eq instead.The equality operator returns False even for very
    small differences between floating point values.

    Examples:
        >>> from property_utils.units.units import LengthUnit
        >>> L1 = Property(500, LengthUnit.CENTI_METER)
        >>> L2 = Property(5, LengthUnit.METER)
        >>> L1 == L2
        True

        >>> L3 = Property(6, LengthUnit.METER)
        >>> L2 == L3
        False
    """
    if not isinstance(other, Property):
        return False
    if not self.unit.isinstance_equivalent(other.unit.to_generic()):
        return False
    try:
        prop = other.to_unit(self.unit) if self.unit != other.unit else other
    except PropertyUtilsException as exc:
        raise PropertyBinaryOperationError(
            f"during conversion of {other} to ({self.unit}) units an error occured: ",
            exc,
        ) from None
    return self.value == prop.value

__ge__(other)

Defines comparison between properties.

Examples:

>>> from property_utils.units.units import RelativeTemperatureUnit
>>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
>>> T2 = Property(212, RelativeTemperatureUnit.FAHRENHEIT)
>>> T1 >= T2
True
Source code in src/property_utils/properties/property.py
def __ge__(self, other) -> bool:
    """
    Defines comparison between properties.

    Examples:
        >>> from property_utils.units.units import RelativeTemperatureUnit
        >>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
        >>> T2 = Property(212, RelativeTemperatureUnit.FAHRENHEIT)
        >>> T1 >= T2
        True
    """
    self._validate_comparison_input(other)
    try:
        prop = other.to_unit(self.unit) if self.unit != other.unit else other
    except PropertyUnitConversionError:
        raise PropertyBinaryOperationError(
            f"cannot compare ({other}) to ({self}); ({other}) does not have the "
            f"same units as ({self}) and there is no unit converter registered for "
            f"({self.unit.to_generic()}). "
        ) from None
    except UnitConversionError as exc:
        raise PropertyBinaryOperationError(
            f"cannot compare ({other}) to ({self});", exc
        ) from None
    return self.value >= prop.value

__gt__(other)

Defines comparison between properties.

Examples:

>>> from property_utils.units.units import RelativeTemperatureUnit
>>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
>>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
>>> T1 > T2
False
Source code in src/property_utils/properties/property.py
def __gt__(self, other) -> bool:
    """
    Defines comparison between properties.

    Examples:
        >>> from property_utils.units.units import RelativeTemperatureUnit
        >>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
        >>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
        >>> T1 > T2
        False
    """
    self._validate_comparison_input(other)
    try:
        prop = other.to_unit(self.unit) if self.unit != other.unit else other
    except PropertyUnitConversionError:
        raise PropertyBinaryOperationError(
            f"cannot compare ({other}) to ({self}); ({other}) does not have the "
            f"same units as ({self}) and there is no unit converter registered for "
            f"({self.unit.to_generic()}). "
        ) from None
    except UnitConversionError as exc:
        raise PropertyBinaryOperationError(
            f"cannot compare ({other}) to ({self});", exc
        ) from None
    return self.value > prop.value

__le__(other)

Defines comparison between properties.

Examples:

>>> from property_utils.units.units import RelativeTemperatureUnit
>>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
>>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
>>> T1 <= T2
True
Source code in src/property_utils/properties/property.py
def __le__(self, other) -> bool:
    """
    Defines comparison between properties.

    Examples:
        >>> from property_utils.units.units import RelativeTemperatureUnit
        >>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
        >>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
        >>> T1 <= T2
        True
    """
    self._validate_comparison_input(other)
    try:
        prop = other.to_unit(self.unit) if self.unit != other.unit else other
    except PropertyUnitConversionError:
        raise PropertyBinaryOperationError(
            f"cannot compare ({other}) to ({self}); ({other}) does not have the "
            f"same units as ({self}) and there is no unit converter registered for "
            f"({self.unit.to_generic()}). "
        ) from None
    except UnitConversionError as exc:
        raise PropertyBinaryOperationError(
            f"cannot compare ({other}) to ({self});", exc
        ) from None
    return self.value <= prop.value

__lt__(other)

Defines comparison between properties.

Examples:

>>> from property_utils.units.units import RelativeTemperatureUnit
>>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
>>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
>>> T1 < T2
True
Source code in src/property_utils/properties/property.py
def __lt__(self, other) -> bool:
    """
    Defines comparison between properties.

    Examples:
        >>> from property_utils.units.units import RelativeTemperatureUnit
        >>> T1 = Property(100, RelativeTemperatureUnit.CELCIUS)
        >>> T2 = Property(213, RelativeTemperatureUnit.FAHRENHEIT)
        >>> T1 < T2
        True
    """
    self._validate_comparison_input(other)
    try:
        prop = other.to_unit(self.unit) if self.unit != other.unit else other
    except PropertyUnitConversionError:
        raise PropertyBinaryOperationError(
            f"cannot compare ({other}) to ({self}); ({other}) does not have the "
            f"same units as ({self}) and there is no unit converter registered for "
            f"({self.unit.to_generic()}). "
        ) from None
    except UnitConversionError as exc:
        raise PropertyBinaryOperationError(
            f"cannot compare ({other}) to ({self});", exc
        ) from None
    return self.value < prop.value

__mul__(other)

Defines multiplication between properties and numerics.

Examples:

>>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
>>> T = Property(300, AbsoluteTemperatureUnit.KELVIN)
>>> 2*T
<Property: 600 K>
>>> A = Property(10, LengthUnit.METER**2)
>>> T * A
<Property: 3000 (m^2) * K>
Source code in src/property_utils/properties/property.py
def __mul__(self, other) -> "Property":
    """
    Defines multiplication between properties and numerics.

    Examples:
        >>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
        >>> T = Property(300, AbsoluteTemperatureUnit.KELVIN)
        >>> 2*T
        <Property: 600 K>
        >>> A = Property(10, LengthUnit.METER**2)
        >>> T * A
        <Property: 3000 (m^2) * K>
    """
    if isinstance(other, (float, int)):
        return self.__class__(self.value * other, self.unit)
    if isinstance(other, Property):
        _other = self._unit_preconversion(other)
        return Property(
            self.value * _other.value, self._simplify_units(self.unit * _other.unit)
        )
    raise PropertyBinaryOperationError(
        f"cannot multiply {self} with {other}; "
        "second operand must be numeric or Property. "
    )

__ne__(other)

Defines inequality between properties. Prefer Property.eq instead.The inequality operator returns True even for very small differences between floating point values.

Examples:

>>> from property_utils.units.units import LengthUnit
>>> L1 = Property(500, LengthUnit.CENTI_METER)
>>> L2 = Property(5, LengthUnit.METER)
>>> L1 != L2
False
>>> L3 = Property(6, LengthUnit.METER)
>>> L2 != L3
True
Source code in src/property_utils/properties/property.py
def __ne__(self, other) -> bool:
    """
    Defines inequality between properties.
    Prefer Property.eq instead.The inequality operator returns True even for very
    small differences between floating point values.

    Examples:
        >>> from property_utils.units.units import LengthUnit
        >>> L1 = Property(500, LengthUnit.CENTI_METER)
        >>> L2 = Property(5, LengthUnit.METER)
        >>> L1 != L2
        False

        >>> L3 = Property(6, LengthUnit.METER)
        >>> L2 != L3
        True
    """
    return not self.__eq__(other)

__neg__()

Defines negation of properties.

Examples:

>>> from property_utils.units.units import RelativeTemperatureUnit
>>> T = Property(3, RelativeTemperatureUnit.CELCIUS)
>>> -T
<Property: -3 °C>
Source code in src/property_utils/properties/property.py
def __neg__(self) -> Self:
    """
    Defines negation of properties.

    Examples:
        >>> from property_utils.units.units import RelativeTemperatureUnit
        >>> T = Property(3, RelativeTemperatureUnit.CELCIUS)
        >>> -T
        <Property: -3 °C>
    """
    return self.__class__(-self.value, self.unit)

__pow__(power)

Defines exponentiation for properties.

Examples:

>>> from property_utils.units.units import LengthUnit
>>> L = Property(5, LengthUnit.METER)
>>> L**3
<Property: 125 (m^3)>
Source code in src/property_utils/properties/property.py
def __pow__(self, power) -> "Property":
    """
    Defines exponentiation for properties.

    Examples:
        >>> from property_utils.units.units import LengthUnit
        >>> L = Property(5, LengthUnit.METER)
        >>> L**3
        <Property: 125 (m^3)>
    """
    if not isinstance(power, (float, int)):
        raise PropertyExponentError(
            f"invalid exponent: {power}; expected numeric. "
        )
    return Property(self.value**power, self.unit**power)

__radd__(other)

Defines right addition between properties.

Examples:

>>> from property_utils.units.units import LengthUnit
>>> x1 = Property(15, LengthUnit.METER)
>>> x2 = Property(5, LengthUnit.METER)
>>> x1 + x2
<Property: 20 m>
Source code in src/property_utils/properties/property.py
def __radd__(self, other) -> Self:
    """
    Defines right addition between properties.

    Examples:
        >>> from property_utils.units.units import LengthUnit
        >>> x1 = Property(15, LengthUnit.METER)
        >>> x2 = Property(5, LengthUnit.METER)
        >>> x1 + x2
        <Property: 20 m>
    """
    return self.__add__(other)

__rmul__(other)

Defines multiplication between properties and numerics.

Examples:

>>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
>>> T = Property(300, AbsoluteTemperatureUnit.KELVIN)
>>> 2*T
<Property: 600 K>
>>> A = Property(10, LengthUnit.METER**2)
>>> T * A
<Property: 3000 (m^2) * K>
Source code in src/property_utils/properties/property.py
def __rmul__(self, other) -> "Property":
    """
    Defines multiplication between properties and numerics.

    Examples:
        >>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
        >>> T = Property(300, AbsoluteTemperatureUnit.KELVIN)
        >>> 2*T
        <Property: 600 K>
        >>> A = Property(10, LengthUnit.METER**2)
        >>> T * A
        <Property: 3000 (m^2) * K>
    """
    return self.__mul__(other)

__rsub__(other)

Defines right subtraction between properties.

Examples:

>>> from property_utils.units.units import TimeUnit
>>> t1 = Property(2, TimeUnit.MINUTE)
>>> t2 = Property(60, TimeUnit.SECOND)
>>> t1 - t2
<Property: 1.0 min>
Source code in src/property_utils/properties/property.py
def __rsub__(self, other) -> "Property":
    """
    Defines right subtraction between properties.

    Examples:
        >>> from property_utils.units.units import TimeUnit
        >>> t1 = Property(2, TimeUnit.MINUTE)
        >>> t2 = Property(60, TimeUnit.SECOND)
        >>> t1 - t2
        <Property: 1.0 min>
    """
    if not isinstance(other, Property):
        raise PropertyBinaryOperationError(
            f"cannot subtract {self} from ({other}); {other} is not a "
            f"{self.__class__}; only same properties can be subtracted from each "
            "other. "
        )
    if not self.unit.isinstance_equivalent(other.unit.to_generic()):
        raise PropertyBinaryOperationError(
            f"cannot subtract ({self}) from ({other}); "
            f"({other}) must have ({self.unit.to_generic()}) units. "
        )
    return other.__add__(-self)

__rtruediv__(other)

Defines right division between properties and numerics.

Examples:

>>> from property_utils.units.units import AbsoluteTemperatureUnit
>>> T = Property(500, AbsoluteTemperatureUnit.KELVIN)
>>> 100/T
<Property: 0.2  / K>
Source code in src/property_utils/properties/property.py
def __rtruediv__(self, other) -> "Property":
    """
    Defines right division between properties and numerics.

    Examples:
        >>> from property_utils.units.units import AbsoluteTemperatureUnit
        >>> T = Property(500, AbsoluteTemperatureUnit.KELVIN)
        >>> 100/T
        <Property: 0.2  / K>
    """
    if isinstance(other, (float, int)):
        try:
            value = other / self.value
        except ZeroDivisionError:
            raise PropertyBinaryOperationError(
                f"cannot divide {self} with {other}; denominator is zero. "
            ) from None
        return Property(value, self.unit.inverse())
    if isinstance(other, Property):
        try:
            value = other.value / self.value
        except ZeroDivisionError:
            raise PropertyBinaryOperationError(
                f"cannot divide {self} with {other}; denominator's value is zero. "
            ) from None
        return Property(value, self._simplify_units(other.unit / self.unit))
    raise PropertyBinaryOperationError(
        f"cannot divide {self} with {other}; "
        "numerator must be numeric or Property. "
    )

__sub__(other)

Defines subtraction between properties.

Examples:

>>> from property_utils.units.units import TimeUnit
>>> t1 = Property(2, TimeUnit.MINUTE)
>>> t2 = Property(60, TimeUnit.SECOND)
>>> t1 - t2
<Property: 1.0 min>
Source code in src/property_utils/properties/property.py
def __sub__(self, other) -> Self:
    """
    Defines subtraction between properties.

    Examples:
        >>> from property_utils.units.units import TimeUnit
        >>> t1 = Property(2, TimeUnit.MINUTE)
        >>> t2 = Property(60, TimeUnit.SECOND)
        >>> t1 - t2
        <Property: 1.0 min>
    """
    if not isinstance(other, Property):
        raise PropertyBinaryOperationError(
            f"cannot subtract {other} from ({self}); {other} is not a "
            "Property; only properties can be subtracted from properties. "
        )
    if not self.unit.isinstance_equivalent(other.unit.to_generic()):
        raise PropertyBinaryOperationError(
            f"cannot subtract ({other}) from ({self}); "
            f"({other}) must have ({self.unit.to_generic()}) units. "
        )
    try:
        prop = other.to_unit(self.unit) if self.unit != other.unit else other
    except PropertyUnitConversionError:
        raise PropertyBinaryOperationError(
            f"cannot subtract ({other}) from ({self}); ({other}) does not have the "
            f"same units as ({self}) and there is no unit converter registered for "
            f"({self.unit.to_generic()}). "
        ) from None
    except UnitConversionError as exc:
        raise PropertyBinaryOperationError(
            f"cannot subtract ({other}) from ({self});", exc
        ) from None
    return self.__class__(self.value - prop.value, self.unit)

__truediv__(other)

Defines division between properties and numerics.

Examples:

>>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
>>> T = Property(500, AbsoluteTemperatureUnit.KELVIN)
>>> T/2
<Property: 250.0 K>
>>> A = Property(10, LengthUnit.METER**2)
>>> T / A
<Property: 50.0 K / (m^2)>
Source code in src/property_utils/properties/property.py
def __truediv__(self, other) -> "Property":
    """
    Defines division between properties and numerics.

    Examples:
        >>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
        >>> T = Property(500, AbsoluteTemperatureUnit.KELVIN)
        >>> T/2
        <Property: 250.0 K>
        >>> A = Property(10, LengthUnit.METER**2)
        >>> T / A
        <Property: 50.0 K / (m^2)>
    """
    if isinstance(other, (float, int)):
        try:
            value = self.value / other
        except ZeroDivisionError:
            raise PropertyBinaryOperationError(
                f"cannot divide {self} with {other}; denominator is zero. "
            ) from None
        return Property(value, self.unit)
    if isinstance(other, Property):
        _other = self._unit_preconversion(other)
        try:
            value = self.value / _other.value
        except ZeroDivisionError:
            raise PropertyBinaryOperationError(
                f"cannot divide {self} with {other}; denominator's value is zero. "
            ) from None
        return Property(value, self._simplify_units(self.unit / _other.unit))
    raise PropertyBinaryOperationError(
        f"cannot divide {self} with {other}; "
        "denominator must be numeric or Property. "
    )

eq(other, *, rel_tol=1e-09, abs_tol=0)

Perform equality comparison between this and some other Property. This method of testing equality is preferable to the equality operator '==' because float point tolerance is taken into account.

rel_tol is the maximum difference for being considered "close", relative to the magnitude of the input values. abs_tol is the maximum difference for being considered "close", regardless of the magnitude of the input values. For the values to be considered close, the difference between them must be smaller than at least one of the tolerances.

Raises PropertyBinaryOperationError if an error occurs during conversion of other's units.

Examples:

>>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
>>> T1 = Property(33.333333, AbsoluteTemperatureUnit.KELVIN)
>>> T2 = Property(100/3, AbsoluteTemperatureUnit.KELVIN)
>>> T1 == T2
False
>>> T1.eq(T2)
False
>>> T1.eq(T2, rel_tol=0.1)
True
Source code in src/property_utils/properties/property.py
def eq(self, other: "Property", *, rel_tol=1e-9, abs_tol=0) -> bool:
    """
    Perform equality comparison between this and some other Property. This method
    of testing equality is preferable to the equality operator '==' because float
    point tolerance is taken into account.

    rel_tol is the maximum difference for being considered "close", relative
    to the magnitude of the input values.
    abs_tol is the maximum difference for being considered "close", regardless of
    the magnitude of the input values.
    For the values to be considered close, the difference between them must be
    smaller than at least one of the tolerances.

    Raises `PropertyBinaryOperationError` if an error occurs during conversion
    of other's units.

    Examples:
        >>> from property_utils.units.units import AbsoluteTemperatureUnit, LengthUnit
        >>> T1 = Property(33.333333, AbsoluteTemperatureUnit.KELVIN)
        >>> T2 = Property(100/3, AbsoluteTemperatureUnit.KELVIN)
        >>> T1 == T2
        False
        >>> T1.eq(T2)
        False
        >>> T1.eq(T2, rel_tol=0.1)
        True
    """
    if not isinstance(other, Property):
        return False
    if not self.unit.isinstance_equivalent(other.unit.to_generic()):
        return False
    try:
        prop = other.to_unit(self.unit) if self.unit != other.unit else other
    except PropertyUtilsException as exc:
        raise PropertyBinaryOperationError(
            f"during conversion of {other} to ({self.unit}) units an error occured: ",
            exc,
        ) from None
    return isclose(self.value, prop.value, rel_tol=rel_tol, abs_tol=abs_tol)

to_si()

Create a new property with SI units.

Raises ImpossiblePropertyUnitConverion if there is no converter registered for the unit.

Raises InvalidUnitConversion if any error occurs in the unit conversion.

Examples:

>>> from property_utils.units.units import RelativeTemperatureUnit
>>> T = Property(100, RelativeTemperatureUnit.CELCIUS)
>>> T.to_si()
<Property: 373.15 K>
Source code in src/property_utils/properties/property.py
def to_si(self) -> Self:
    """
    Create a new property with SI units.

    Raises `ImpossiblePropertyUnitConverion` if there is no converter registered
    for the unit.

    Raises `InvalidUnitConversion` if any error occurs in the unit conversion.

    Examples:
        >>> from property_utils.units.units import RelativeTemperatureUnit
        >>> T = Property(100, RelativeTemperatureUnit.CELCIUS)
        >>> T.to_si()
        <Property: 373.15 K>
    """
    if isinstance(self.unit, MeasurementUnit):
        return self.to_unit(self.unit.si())
    if isinstance(self.unit, Dimension):
        return self.to_unit(self.unit.unit.si() ** self.unit.power)
    if isinstance(self.unit, CompositeDimension):
        return self.to_unit(self.unit.to_generic().to_si())
    raise PropertyValidationError(
        f"cannot convert Property to SI; 'unit' is invalid: {self.unit}. "
    )

to_unit(unit)

Create a new property with specified unit.

Raises PropertyUnitConversionError if the unit is not of the same type.

Raises ImpossiblePropertyUnitConverion if there is no converter registered for the unit.

Raises UnitConversionError if any error occurs in the unit conversion.

Examples:

>>> from property_utils.units.units import RelativeTemperatureUnit
>>> T = Property(100, RelativeTemperatureUnit.CELCIUS)
>>> T.to_unit(RelativeTemperatureUnit.FAHRENHEIT)
<Property: 212.0 °F>
Source code in src/property_utils/properties/property.py
def to_unit(self, unit: UnitDescriptor) -> Self:
    """
    Create a new property with specified unit.

    Raises `PropertyUnitConversionError` if the unit is not of the same type.

    Raises `ImpossiblePropertyUnitConverion` if there is no converter registered
    for the unit.

    Raises `UnitConversionError` if any error occurs in the unit conversion.

    Examples:
        >>> from property_utils.units.units import RelativeTemperatureUnit
        >>> T = Property(100, RelativeTemperatureUnit.CELCIUS)
        >>> T.to_unit(RelativeTemperatureUnit.FAHRENHEIT)
        <Property: 212.0 °F>
    """
    if self.unit == unit:
        return self.__class__(unit=self.unit, value=self.value)

    if not unit.isinstance_equivalent(self.unit.to_generic()):
        raise PropertyUnitConversionError(
            f"cannot convert {self} to ({unit}) units; 'unit' should be an instance"
            f" of {self.unit.to_generic()}. "
        )
    try:
        converter = self._converter()
    except UndefinedConverterError:
        raise PropertyUnitConversionError(
            f"cannot convert property {self} to units: {unit}; no unit converter "
            f" found for {unit.to_generic()}. "
            "Did you forget to @register_converter? "
        ) from None
    try:
        value = converter.convert(self.value, self.unit, unit)
    except UnitConversionError as exc:
        raise exc from None
    return self.__class__(value=value, unit=unit)

RelativeTemperatureUnitConverter

Bases: RelativeUnitConverter

Convert temperature units with this converter.

Examples:

>>> RelativeTemperatureUnitConverter.convert(100, RelativeTemperatureUnit.CELCIUS, RelativeTemperatureUnit.FAHRENHEIT)
212.0
Source code in src/property_utils/units/converters.py
@register_converter(RelativeTemperatureUnit)
class RelativeTemperatureUnitConverter(RelativeUnitConverter):  # pylint: disable=too-few-public-methods
    """
    Convert temperature units with this converter.

    Examples:
        >>> RelativeTemperatureUnitConverter.convert(100, RelativeTemperatureUnit.CELCIUS, RelativeTemperatureUnit.FAHRENHEIT)
        212.0
    """

    reference_unit = RelativeTemperatureUnit.CELCIUS
    conversion_map = {
        RelativeTemperatureUnit.CELCIUS: lambda t: t,
        AbsoluteTemperatureUnit.KELVIN: lambda t: t - 273.15,
        RelativeTemperatureUnit.FAHRENHEIT: lambda t: (t - 32) / 1.8,
        AbsoluteTemperatureUnit.RANKINE: lambda t: (t / 1.8) - 273.15,
    }
    reference_conversion_map = {
        RelativeTemperatureUnit.CELCIUS: lambda t: t,
        AbsoluteTemperatureUnit.KELVIN: lambda t: t + 273.15,
        RelativeTemperatureUnit.FAHRENHEIT: lambda t: (t * 1.8) + 32,
        AbsoluteTemperatureUnit.RANKINE: lambda t: (t + 273.15) * 1.8,
    }

TimeUnitConverter

Bases: AbsoluteUnitConverter

Convert time units with this converter.

Examples:

>>> TimeUnitConverter.convert(1, TimeUnit.HOUR, TimeUnit.SECOND)
3600.0
Source code in src/property_utils/units/converters.py
@register_converter(TimeUnit)
class TimeUnitConverter(AbsoluteUnitConverter):
    """
    Convert time units with this converter.

    Examples:
        >>> TimeUnitConverter.convert(1, TimeUnit.HOUR, TimeUnit.SECOND)
        3600.0
    """

    reference_unit = TimeUnit.SECOND
    conversion_map = {
        TimeUnit.MILLI_SECOND: UnitPrefix.MILLI.inverse(),
        TimeUnit.SECOND: 1,
        TimeUnit.MINUTE: 1 / 60.0,
        TimeUnit.HOUR: 1 / 60.0 / 60.0,
        TimeUnit.DAY: 1 / 60.0 / 60.0 / 24.0,
        TimeUnit.WEEK: 1 / 60.0 / 60.0 / 24.0 / 7,
        TimeUnit.MONTH: 1 / 60.0 / 60.0 / 24.0 / (365 / 12),
        TimeUnit.YEAR: 1 / 60.0 / 60.0 / 24.0 / 365,
    }

UnitPrefix

Bases: float, Enum

Enumeration of unit prefixes. Handy when converting to and fro prefixed units.

Examples:

>>> centimeters = 225
>>> meters = centimeters * UnitPrefix.CENTI
>>> meters
2.25
Source code in src/property_utils/units/converters.py
class UnitPrefix(float, Enum):
    """
    Enumeration of unit prefixes.
    Handy when converting to and fro prefixed units.

    Examples:
        >>> centimeters = 225
        >>> meters = centimeters * UnitPrefix.CENTI
        >>> meters
        2.25
    """

    PICO = 1e-12
    NANO = 1e-9
    MICRO = 1e-6
    MILLI = 1e-3
    CENTI = 1e-2
    DECI = 1e-1
    DECA = 1e1
    HECTO = 1e2
    KILO = 1e3
    MEGA = 1e6
    GIGA = 1e9
    TERA = 1e12

    def inverse(self) -> float:
        """
        Return the inverse of the unit prefix. Use when prefixing a unit.

        Examples:
            >>> meters = 50.26
            >>> centimeters = meters * UnitPrefix.CENTI.inverse()
            >>> centimeters
            5026.0
        """
        return 1 / self.value

inverse()

Return the inverse of the unit prefix. Use when prefixing a unit.

Examples:

>>> meters = 50.26
>>> centimeters = meters * UnitPrefix.CENTI.inverse()
>>> centimeters
5026.0
Source code in src/property_utils/units/converters.py
def inverse(self) -> float:
    """
    Return the inverse of the unit prefix. Use when prefixing a unit.

    Examples:
        >>> meters = 50.26
        >>> centimeters = meters * UnitPrefix.CENTI.inverse()
        >>> centimeters
        5026.0
    """
    return 1 / self.value

VolumeUnitConverter

Bases: ExponentiatedUnitConverter

Convert volume units with this converter.

Examples:

>>> VolumeUnitConverter.convert(1, LengthUnit.METER**3, LengthUnit.CENTI_METER**3)
1000000.0
Source code in src/property_utils/units/converters.py
@register_converter(LengthUnit**3)
class VolumeUnitConverter(ExponentiatedUnitConverter):
    """
    Convert volume units with this converter.

    Examples:
        >>> VolumeUnitConverter.convert(1, LengthUnit.METER**3, LengthUnit.CENTI_METER**3)
        1000000.0
    """

p(value, unit=NonDimensionalUnit.NON_DIMENSIONAL)

Create a property with a value and a unit. Default unit is non-dimensional, i.e. no unit.

Examples:

>>> from property_utils.units import KELVIN
>>> p(350, KELVIN)
<Property: 350 K>
>>> p(20.23)
<Property: 20.23 >
Source code in src/property_utils/properties/property.py
def p(
    value: float, unit: UnitDescriptor = NonDimensionalUnit.NON_DIMENSIONAL
) -> "Property":
    """
    Create a property with a value and a unit.
    Default unit is non-dimensional, i.e. no unit.

    Examples:
        >>> from property_utils.units import KELVIN
        >>> p(350, KELVIN)
        <Property: 350 K>

        >>> p(20.23)
        <Property: 20.23 >
    """
    return Property(value, unit)