Skip to content

Unit References

units

UnitNotFound

Bases: Exception

Raised when a requested unit with specific order of magnitude is not found in the registry.

Source code in submodules/mesqual/mesqual/units.py
 9
10
11
class UnitNotFound(Exception):
    """Raised when a requested unit with specific order of magnitude is not found in the registry."""
    pass

UnitRegistryNotComplete

Bases: Exception

Raised when the Units class is missing expected unit definitions for a dimensionality.

Source code in submodules/mesqual/mesqual/units.py
14
15
16
17
18
19
20
class UnitRegistryNotComplete(Exception):
    """Raised when the Units class is missing expected unit definitions for a dimensionality."""

    def __init__(self, message: str = None):
        base = f'You should never end up here. Your units are not properly registered in the {Units.__name__} class.'
        message = message or ''
        super().__init__(base + message)

Units

Central registry for energy system units with utilities for unit conversion and formatting.

Provides a comprehensive collection of energy, power, currency, time, and derived units commonly used in energy systems analysis. Built on the pint library, this class extends the standard unit registry with energy-specific units and intelligent formatting capabilities.

Unit Categories

Energy: Wh, kWh, MWh, GWh, TWh Power: W, kW, MW, GW, TW Ramping: W_per_min, MW_per_min, MW_per_hour Currency: EUR, kEUR, MEUR, BEUR, TEUR Energy Prices: EUR_per_Wh, EUR_per_MWh Capacity Prices: EUR_per_W, EUR_per_MW Ramping Prices: EUR_per_W_per_min, EUR_per_MW_per_min, EUR_per_MW_per_hour Time: minute, hour, day, week, year Dimensionless: per_unit, percent, percent_base, MTU, NaU, MissingUnit

Key Features
  • Automatic "pretty" unit selection for optimal readability
  • Common unit finding across collections of quantities
  • Configurable text formatting with thousand separators and decimal control
  • Intensive/extensive quantity classification
  • Unit family iteration and base unit resolution

Examples:

Basic usage:

>>> energy = 5432.1 * Units.kWh
>>> Units.get_quantity_in_pretty_unit(energy)
5.4321 MWh

Formatting:

>>> price = 45.678 * Units.EUR_per_MWh
>>> Units.get_pretty_text_for_quantity(price, decimals=2)
'45.68 €/MWh'

Finding common units for collections:

>>> quantities = [1_500_000 * Units.EUR, 2_300_000 * Units.EUR]
>>> common_unit = Units.get_common_pretty_unit_for_quantities(quantities)
>>> common_unit
MEUR
See Also

QuantityToTextConverter: Reusable formatter for consistent quantity display

Source code in submodules/mesqual/mesqual/units.py
 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
class Units(metaclass=_IterableUnitsMeta):
    """
    Central registry for energy system units with utilities for unit conversion and formatting.

    Provides a comprehensive collection of energy, power, currency, time, and derived units
    commonly used in energy systems analysis. Built on the pint library, this class extends
    the standard unit registry with energy-specific units and intelligent formatting capabilities.

    Unit Categories:
        Energy: Wh, kWh, MWh, GWh, TWh
        Power: W, kW, MW, GW, TW
        Ramping: W_per_min, MW_per_min, MW_per_hour
        Currency: EUR, kEUR, MEUR, BEUR, TEUR
        Energy Prices: EUR_per_Wh, EUR_per_MWh
        Capacity Prices: EUR_per_W, EUR_per_MW
        Ramping Prices: EUR_per_W_per_min, EUR_per_MW_per_min, EUR_per_MW_per_hour
        Time: minute, hour, day, week, year
        Dimensionless: per_unit, percent, percent_base, MTU, NaU, MissingUnit

    Key Features:
        - Automatic "pretty" unit selection for optimal readability
        - Common unit finding across collections of quantities
        - Configurable text formatting with thousand separators and decimal control
        - Intensive/extensive quantity classification
        - Unit family iteration and base unit resolution

    Examples:
        Basic usage:
        >>> energy = 5432.1 * Units.kWh
        >>> Units.get_quantity_in_pretty_unit(energy)
        5.4321 MWh

        Formatting:
        >>> price = 45.678 * Units.EUR_per_MWh
        >>> Units.get_pretty_text_for_quantity(price, decimals=2)
        '45.68 €/MWh'

        Finding common units for collections:
        >>> quantities = [1_500_000 * Units.EUR, 2_300_000 * Units.EUR]
        >>> common_unit = Units.get_common_pretty_unit_for_quantities(quantities)
        >>> common_unit
        MEUR

    See Also:
        QuantityToTextConverter: Reusable formatter for consistent quantity display
    """
    _ureg = ureg
    Unit = Unit
    Quantity = Quantity

    Wh = _ureg.Wh
    kWh = _ureg.kWh
    MWh = _ureg.MWh
    GWh = _ureg.GWh
    TWh = _ureg.TWh

    W = _ureg.W
    kW = _ureg.kW
    MW = _ureg.MW
    GW = _ureg.GW
    TW = _ureg.TW

    W_per_min = _ureg.W_per_min
    MW_per_min = _ureg.MW_per_min
    MW_per_hour = _ureg.MW_per_hour

    W_per_period = _ureg.W_per_period
    MW_per_period = _ureg.MW_per_period

    EUR = _ureg.EUR
    kEUR = _ureg.kEUR
    MEUR = _ureg.MEUR
    BEUR = _ureg.BEUR
    TEUR = _ureg.TEUR

    EUR_per_W = _ureg.EUR_per_W
    EUR_per_MW = _ureg.EUR_per_MW

    EUR_per_Wh = _ureg.EUR_per_Wh
    EUR_per_MWh = _ureg.EUR_per_MWh

    EUR_per_W_per_min = _ureg.EUR_per_W_per_min
    EUR_per_MW_per_min = _ureg.EUR_per_MW_per_min
    EUR_per_MW_per_hour = _ureg.EUR_per_MW_per_hour

    percent_base = _ureg.percent_base
    percent = _ureg.perc
    ratio = _ureg.ratio
    per_unit = _ureg.per_unit
    MTU = _ureg.MTU
    NaU = _ureg.NaU
    MissingUnit = _ureg.MissingUnit

    _STRING_REPLACEMENTS = {
        '_per_': '/',
        'EUR': '€',
        'per_unit': 'pu',
        'perc': '%',
        'inf': '∞',
        'nan': 'N/A',
    }

    _INTENSIVE_QUANTITIES = [W, EUR_per_Wh, percent_base, per_unit, ratio, W_per_period, W_per_min, EUR_per_W]
    _EXTENSIVE_QUANTITIES = [Wh, EUR, MTU]

    @classmethod
    def get_quantity_type_enum(cls, unit: Unit) -> QuantityTypeEnum:
        """
        Classify a unit as intensive or extensive quantity.

        Intensive quantities (e.g., power, prices) are independent of system size,
        while extensive quantities (e.g., energy, cost) scale with system size.

        Args:
            unit: The unit to classify

        Returns:
            QuantityTypeEnum indicating INTENSIVE or EXTENSIVE

        Raises:
            KeyError: If the unit's base unit is not registered in the classification lists

        Examples:
            >>> Units.get_quantity_type_enum(Units.MW)
            QuantityTypeEnum.INTENSIVE
            >>> Units.get_quantity_type_enum(Units.MWh)
            QuantityTypeEnum.EXTENSIVE
        """
        base_unit = cls.get_base_unit_for_unit(unit)
        if base_unit in cls._INTENSIVE_QUANTITIES:
            return QuantityTypeEnum.INTENSIVE
        elif base_unit in cls._EXTENSIVE_QUANTITIES:
            return QuantityTypeEnum.EXTENSIVE
        raise KeyError(f'QuantityTypeEnum for {unit} not registered')

    @classmethod
    def units_have_same_base(cls, unit_1: Unit, unit_2: Unit) -> bool:
        """
        Check if two units have the same dimensionality (are convertible).

        Args:
            unit_1: First unit to compare
            unit_2: Second unit to compare

        Returns:
            True if units have same dimensionality, False otherwise

        Examples:
            >>> Units.units_have_same_base(Units.kWh, Units.MWh)
            True
            >>> Units.units_have_same_base(Units.kWh, Units.MW)
            False
        """
        return unit_1.dimensionality == unit_2.dimensionality

    @classmethod
    def get_base_unit_for_unit(cls, unit: Unit) -> Unit:
        """
        Get the base unit (order of magnitude 1) for a given unit's dimensionality.

        Args:
            unit: Unit to find base unit for

        Returns:
            The base unit with order of magnitude 1 (e.g., W for power, Wh for energy)

        Examples:
            >>> Units.get_base_unit_for_unit(Units.MW)
            W
            >>> Units.get_base_unit_for_unit(Units.GWh)
            Wh
        """
        return cls.get_target_unit_for_oom(unit, 1)

    @classmethod
    def get_oom_of_unit(cls, unit: Unit) -> float:
        """
        Get the order of magnitude of a unit relative to its base unit.

        Args:
            unit: Unit to determine order of magnitude for

        Returns:
            Order of magnitude as float (e.g., 1e6 for MW, 1e9 for GW)

        Examples:
            >>> Units.get_oom_of_unit(Units.MW)
            1000000.0
            >>> Units.get_oom_of_unit(Units.kWh)
            1000.0
        """
        return (1 * unit).to_base_units().magnitude

    @classmethod
    def get_target_unit_for_oom(cls, reference_unit: Unit, target_oom: float) -> Quantity:
        """
        Find a unit with exact order of magnitude within the same dimensionality.

        Args:
            reference_unit: Unit defining the dimensionality
            target_oom: Target order of magnitude (e.g., 1e6 for mega, 1e9 for giga)

        Returns:
            Unit with the exact target order of magnitude

        Raises:
            UnitNotFound: If no unit with exact target order of magnitude exists

        Examples:
            >>> Units.get_target_unit_for_oom(Units.W, 1e6)
            MW
            >>> Units.get_target_unit_for_oom(Units.EUR, 1e9)
            BEUR
        """
        units = cls.get_all_units_with_equal_base(reference_unit)
        for u in units:
            if cls.get_oom_of_unit(u) == target_oom:
                return u
        raise UnitNotFound(f'No unit with order of mag {target_oom:.0e} for {reference_unit}')

    @classmethod
    def get_closest_unit_for_oom(cls, reference_unit: Unit, target_oom: float) -> Quantity:
        """
        Find the closest unit for a target order of magnitude (doesn't require exact match).

        Selects the largest unit whose order of magnitude is less than or equal to
        the target order of magnitude.

        Args:
            reference_unit: Unit defining the dimensionality
            target_oom: Target order of magnitude

        Returns:
            Closest unit with order of magnitude <= target_oom

        Raises:
            UnitRegistryNotComplete: If no units found for the dimensionality

        Examples:
            >>> Units.get_closest_unit_for_oom(Units.W, 5e5)  # Between kW and MW
            kW
            >>> Units.get_closest_unit_for_oom(Units.EUR, 7.5e6)  # Between MEUR and BEUR
            MEUR
        """
        units_with_same_dimension = cls.get_all_units_with_equal_base(reference_unit)
        if len(units_with_same_dimension) == 0:
            raise UnitRegistryNotComplete
        base_unit = cls.get_base_unit_for_unit(reference_unit)
        sorted_units = sorted(units_with_same_dimension, key=lambda x: (1 * x).to(base_unit).magnitude, reverse=True)
        for u in sorted_units:
            if (1 * u).to(base_unit).magnitude <= target_oom:
                return u
        return sorted_units[0]

    @classmethod
    def get_quantity_in_target_oom(cls, quantity: Quantity, target_oom: float) -> Quantity:
        """
        Convert quantity to unit with specific order of magnitude.

        Args:
            quantity: Quantity to convert
            target_oom: Target order of magnitude

        Returns:
            Quantity converted to target order of magnitude, or original if not found

        Examples:
            >>> energy = 5000 * Units.kWh
            >>> Units.get_quantity_in_target_oom(energy, 1e6)
            5.0 MWh
        """
        try:
            target_unit = cls.get_target_unit_for_oom(quantity.units, target_oom)
            return quantity.to(target_unit)
        except UnitNotFound:
            RuntimeWarning(f'# TODO:')
            return quantity

    @classmethod
    def get_quantity_in_target_unit(cls, quantity: Quantity, target_unit: Unit) -> Quantity:
        """
        Convert quantity to a specific target unit.

        Simple wrapper around pint's to() method for consistency with other unit conversion methods.

        Args:
            quantity: Quantity to convert
            target_unit: Target unit

        Returns:
            Quantity converted to target unit

        Examples:
            >>> energy = 5000 * Units.kWh
            >>> Units.get_quantity_in_target_unit(energy, Units.MWh)
            5.0 MWh
        """
        return quantity.to(target_unit)

    @classmethod
    def get_quantity_in_pretty_unit(cls, quantity: Quantity) -> Quantity:
        """
        Convert quantity to the most readable unit (magnitude between 1 and 10,000).

        Automatically selects a unit where the magnitude is less than 10,000,
        making the value easy to read and comprehend.

        Args:
            quantity: Quantity to convert

        Returns:
            Quantity in "pretty" readable unit

        Examples:
            >>> energy = 5432100 * Units.Wh
            >>> Units.get_quantity_in_pretty_unit(energy)
            5.4321 MWh

            >>> cost = 0.045 * Units.EUR
            >>> Units.get_quantity_in_pretty_unit(cost)
            4.5 EUR_cent
        """
        base_unit = cls.get_base_unit_for_unit(quantity.units)
        units = cls.get_all_units_with_equal_base(base_unit)
        units = sorted(units, key=lambda x: (1 * x).to(base_unit).magnitude, reverse=False)
        for u in units:
            if abs(quantity.to(u).magnitude) < 10_000:
                return quantity.to(u)
        return quantity.to(units[-1])

    @classmethod
    def get_common_pretty_unit_for_quantities(cls, quantities: list[Quantity]) -> Unit:
        """
        Find common "pretty" unit for a collection of quantities.

        Strategy:
        1. Verify all quantities have same dimensionality
        2. Convert all quantities to all available units
        3. Select unit with most values having abs(magnitude) < 10,000
        4. If tie, select unit giving largest magnitudes (avoid tiny decimals)

        Args:
            quantities: List of quantities with same dimensionality

        Returns:
            Pretty unit that works well for the collection

        Raises:
            ValueError: If quantities have different dimensionalities or list is empty

        Examples:

            >>> quantities = [1_000_000 * Units.EUR, 5_000_000 * Units.EUR]
            >>> Units.get_common_pretty_unit_for_quantities(quantities)
                Units.MEUR

            >>> quantities = [0.03 * Units.EUR_per_MWh, -0.02 * Units.EUR_per_MWh, 0.01 * Units.EUR_per_MWh]
            >>> Units.get_common_pretty_unit_for_quantities(quantities)
                Units.EUR/Units.MWh  # Not EUR/Wh which would give tiny values
        """
        if not quantities:
            raise ValueError("Cannot find common unit for empty list of quantities")

        # Verify all quantities have same dimensionality
        base_unit = cls.get_base_unit_for_unit(quantities[0].units)
        for q in quantities[1:]:
            if not cls.units_have_same_base(q.units, base_unit):
                raise ValueError(
                    f"All quantities must have same dimensionality. "
                    f"Found {q.units} which differs from {base_unit}"
                )

        # Handle all-zero case
        non_zero_quantities = [q for q in quantities if abs(q.magnitude) > 0]
        if not non_zero_quantities:
            # All values are zero, return common unit if all same, else base unit
            if len(set([q.units for q in quantities])) == 1:
                return quantities[0].units
            return base_unit

        # Get all available units for this dimensionality
        available_units = cls.get_all_units_with_equal_base(base_unit)
        if not available_units:
            # return common unit if all same, else base unit
            if len(set([q.units for q in quantities])) == 1:
                return quantities[0].units
            return base_unit

        # Evaluate each unit
        best_unit = base_unit
        best_count_under_10k = -1
        best_median_magnitude = -1

        for unit in available_units:
            # Convert all quantities to this unit
            magnitudes = [abs(q.to(unit).magnitude) for q in non_zero_quantities]

            # Count how many values are under 10,000
            count_under_10k = sum(1 for m in magnitudes if m < 10_000)

            # Get median magnitude for tie-breaking
            median_magnitude = np.median(magnitudes)

            # Update best if this unit is better
            if (count_under_10k > best_count_under_10k or
                (count_under_10k == best_count_under_10k and median_magnitude > best_median_magnitude)):
                best_unit = unit
                best_count_under_10k = count_under_10k
                best_median_magnitude = median_magnitude

        return best_unit

    @classmethod
    def get_all_units_with_equal_base(cls, unit: Unit) -> list[Unit]:
        """
        Get all registered units with the same dimensionality as the input unit.

        Args:
            unit: Reference unit to match dimensionality

        Returns:
            List of all units with same dimensionality (e.g., all energy units, all power units)

        Examples:
            >>> Units.get_all_units_with_equal_base(Units.MW)
            [W, kW, MW, GW, TW]
            >>> Units.get_all_units_with_equal_base(Units.EUR)
            [EUR_cent, EUR, kEUR, MEUR, BEUR, TEUR]
        """
        return [u for u in Units if cls.units_have_same_base(unit, u)]

    @classmethod
    def get_pretty_text_for_quantity(
            cls,
            quantity: Quantity,
            decimals: int = None,
            thousands_separator: str = None,
            include_unit: bool = True,
            include_oom: bool = True,
            include_sign: bool = None,
    ) -> str:
        """
        Format a quantity as human-readable text with customizable formatting.

        Applies string replacements for better readability (e.g., '_per_' → '/', 'EUR' → '€').

        Args:
            quantity: Quantity to format
            decimals: Number of decimal places (auto-determined if None)
            thousands_separator: Separator for thousands (default: '')
            include_unit: Whether to include unit in output (default: True)
            include_oom: Whether to include order of magnitude prefix (default: True)
            include_sign: Whether to include '+' for positive values (default: None/auto)

        Returns:
            Formatted text string representation of the quantity

        Examples:
            >>> price = 45.678 * Units.EUR_per_MWh
            >>> Units.get_pretty_text_for_quantity(price, decimals=2)
            '45.68 €/MWh'

            >>> cost = 1234567 * Units.EUR
            >>> Units.get_pretty_text_for_quantity(cost, thousands_separator=' ')
            '1 234 567 €'

            >>> power = 5.2 * Units.MW
            >>> Units.get_pretty_text_for_quantity(power, include_sign=True)
            '+5.2 MW'
        """
        if decimals is None:
            decimals = cls.get_pretty_decimals(quantity)
        if thousands_separator is None:
            thousands_separator = ''

        sign_str = cls._get_sign_str_for_quantity(quantity, include_sign)
        value_str = f'{abs(quantity.magnitude):,.{decimals}f}'
        value_str = value_str.replace(',', thousands_separator)

        if include_unit:
            if not include_oom:
                raise NotImplementedError('Why would you do that?')
            unit_str = str(quantity.units)
        elif include_oom:
            unit_str = cls._get_units_oom_prefix(quantity.units)
        else:
            unit_str = ''

        components = []
        if sign_str:
            components.append(sign_str)

        components.append(value_str)

        if unit_str:
            components.append(' ' + unit_str)

        pretty_text = ''.join(components)

        for r, v in cls._STRING_REPLACEMENTS.items():
            pretty_text = pretty_text.replace(r, v)

        return pretty_text

    @classmethod
    def _get_sign_str_for_quantity(cls, quantity: Quantity, include_sign: bool = None) -> str:
        """
        Get the sign string for a quantity value.

        Internal helper method for formatting quantities with appropriate sign representation.

        Args:
            quantity: Quantity to get sign for
            include_sign: Whether to include '+' for positive values

        Returns:
            Sign string: '+', '-', or '' (empty)

        Note:
            Returns empty string for NaN, zero, or when include_sign is False.
            For positive values, only returns '+' if include_sign is explicitly True.
        """
        if include_sign is False:
            return ''

        value = quantity.magnitude
        if np.isnan(value):
            return ''
        if value == 0:
            return ''
        if value < 0:
            return '-'
        if value > 0:
            if include_sign:
                return '+'
            else:
                return ''
        raise Exception(f'How did you end up here for value {quantity}')

    @classmethod
    def get_pretty_decimals(cls, quantity: Quantity) -> int:
        """
        Determine appropriate number of decimal places for a quantity's magnitude.

        Automatically selects decimal places based on the magnitude to ensure readability:
        - Integer values: 0 decimals
        - >100: 0 decimals
        - >10: 1 decimal
        - >0.1: 2 decimals
        - >0.01: 3 decimals
        - <0.01: 5 decimals

        Args:
            quantity: Quantity to determine decimal places for

        Returns:
            Number of decimal places (0-5)

        Examples:
            >>> Units.get_pretty_decimals(1234.5 * Units.MW)
            0
            >>> Units.get_pretty_decimals(12.34 * Units.MW)
            1
            >>> Units.get_pretty_decimals(0.0123 * Units.MW)
            5
        """
        # if quantity.units == Units.per_unit:
        #     return 3

        if isinstance(quantity.magnitude, int):
            return 0

        abs_value = abs(quantity.magnitude)
        if abs_value > 100:
            return 0
        elif abs_value > 10:
            return 1
        elif abs_value > 0.1:
            return 2
        elif abs_value > 0.01:
            return 3
        elif abs_value == 0:
            return 0
        else:
            return 5

    @classmethod
    def _get_units_oom_prefix(cls, unit: Unit) -> str:
        """
        Extract the order of magnitude prefix from a unit.

        Internal helper that returns the prefix (k, M, G, T) by removing the base unit.

        Args:
            unit: Unit to extract prefix from

        Returns:
            Order of magnitude prefix string (e.g., 'k', 'M', 'G')

        Examples:
            >>> Units._get_units_oom_prefix(Units.MW)
            'M'
            >>> Units._get_units_oom_prefix(Units.kWh)
            'k'
        """
        base_unit = cls.get_base_unit_for_unit(unit)
        return str(unit).replace(str(base_unit), '')

get_quantity_type_enum classmethod

get_quantity_type_enum(unit: Unit) -> QuantityTypeEnum

Classify a unit as intensive or extensive quantity.

Intensive quantities (e.g., power, prices) are independent of system size, while extensive quantities (e.g., energy, cost) scale with system size.

Parameters:

Name Type Description Default
unit Unit

The unit to classify

required

Returns:

Type Description
QuantityTypeEnum

QuantityTypeEnum indicating INTENSIVE or EXTENSIVE

Raises:

Type Description
KeyError

If the unit's base unit is not registered in the classification lists

Examples:

>>> Units.get_quantity_type_enum(Units.MW)
QuantityTypeEnum.INTENSIVE
>>> Units.get_quantity_type_enum(Units.MWh)
QuantityTypeEnum.EXTENSIVE
Source code in submodules/mesqual/mesqual/units.py
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
@classmethod
def get_quantity_type_enum(cls, unit: Unit) -> QuantityTypeEnum:
    """
    Classify a unit as intensive or extensive quantity.

    Intensive quantities (e.g., power, prices) are independent of system size,
    while extensive quantities (e.g., energy, cost) scale with system size.

    Args:
        unit: The unit to classify

    Returns:
        QuantityTypeEnum indicating INTENSIVE or EXTENSIVE

    Raises:
        KeyError: If the unit's base unit is not registered in the classification lists

    Examples:
        >>> Units.get_quantity_type_enum(Units.MW)
        QuantityTypeEnum.INTENSIVE
        >>> Units.get_quantity_type_enum(Units.MWh)
        QuantityTypeEnum.EXTENSIVE
    """
    base_unit = cls.get_base_unit_for_unit(unit)
    if base_unit in cls._INTENSIVE_QUANTITIES:
        return QuantityTypeEnum.INTENSIVE
    elif base_unit in cls._EXTENSIVE_QUANTITIES:
        return QuantityTypeEnum.EXTENSIVE
    raise KeyError(f'QuantityTypeEnum for {unit} not registered')

units_have_same_base classmethod

units_have_same_base(unit_1: Unit, unit_2: Unit) -> bool

Check if two units have the same dimensionality (are convertible).

Parameters:

Name Type Description Default
unit_1 Unit

First unit to compare

required
unit_2 Unit

Second unit to compare

required

Returns:

Type Description
bool

True if units have same dimensionality, False otherwise

Examples:

>>> Units.units_have_same_base(Units.kWh, Units.MWh)
True
>>> Units.units_have_same_base(Units.kWh, Units.MW)
False
Source code in submodules/mesqual/mesqual/units.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
@classmethod
def units_have_same_base(cls, unit_1: Unit, unit_2: Unit) -> bool:
    """
    Check if two units have the same dimensionality (are convertible).

    Args:
        unit_1: First unit to compare
        unit_2: Second unit to compare

    Returns:
        True if units have same dimensionality, False otherwise

    Examples:
        >>> Units.units_have_same_base(Units.kWh, Units.MWh)
        True
        >>> Units.units_have_same_base(Units.kWh, Units.MW)
        False
    """
    return unit_1.dimensionality == unit_2.dimensionality

get_base_unit_for_unit classmethod

get_base_unit_for_unit(unit: Unit) -> Unit

Get the base unit (order of magnitude 1) for a given unit's dimensionality.

Parameters:

Name Type Description Default
unit Unit

Unit to find base unit for

required

Returns:

Type Description
Unit

The base unit with order of magnitude 1 (e.g., W for power, Wh for energy)

Examples:

>>> Units.get_base_unit_for_unit(Units.MW)
W
>>> Units.get_base_unit_for_unit(Units.GWh)
Wh
Source code in submodules/mesqual/mesqual/units.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
@classmethod
def get_base_unit_for_unit(cls, unit: Unit) -> Unit:
    """
    Get the base unit (order of magnitude 1) for a given unit's dimensionality.

    Args:
        unit: Unit to find base unit for

    Returns:
        The base unit with order of magnitude 1 (e.g., W for power, Wh for energy)

    Examples:
        >>> Units.get_base_unit_for_unit(Units.MW)
        W
        >>> Units.get_base_unit_for_unit(Units.GWh)
        Wh
    """
    return cls.get_target_unit_for_oom(unit, 1)

get_oom_of_unit classmethod

get_oom_of_unit(unit: Unit) -> float

Get the order of magnitude of a unit relative to its base unit.

Parameters:

Name Type Description Default
unit Unit

Unit to determine order of magnitude for

required

Returns:

Type Description
float

Order of magnitude as float (e.g., 1e6 for MW, 1e9 for GW)

Examples:

>>> Units.get_oom_of_unit(Units.MW)
1000000.0
>>> Units.get_oom_of_unit(Units.kWh)
1000.0
Source code in submodules/mesqual/mesqual/units.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
@classmethod
def get_oom_of_unit(cls, unit: Unit) -> float:
    """
    Get the order of magnitude of a unit relative to its base unit.

    Args:
        unit: Unit to determine order of magnitude for

    Returns:
        Order of magnitude as float (e.g., 1e6 for MW, 1e9 for GW)

    Examples:
        >>> Units.get_oom_of_unit(Units.MW)
        1000000.0
        >>> Units.get_oom_of_unit(Units.kWh)
        1000.0
    """
    return (1 * unit).to_base_units().magnitude

get_target_unit_for_oom classmethod

get_target_unit_for_oom(reference_unit: Unit, target_oom: float) -> Quantity

Find a unit with exact order of magnitude within the same dimensionality.

Parameters:

Name Type Description Default
reference_unit Unit

Unit defining the dimensionality

required
target_oom float

Target order of magnitude (e.g., 1e6 for mega, 1e9 for giga)

required

Returns:

Type Description
Quantity

Unit with the exact target order of magnitude

Raises:

Type Description
UnitNotFound

If no unit with exact target order of magnitude exists

Examples:

>>> Units.get_target_unit_for_oom(Units.W, 1e6)
MW
>>> Units.get_target_unit_for_oom(Units.EUR, 1e9)
BEUR
Source code in submodules/mesqual/mesqual/units.py
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
@classmethod
def get_target_unit_for_oom(cls, reference_unit: Unit, target_oom: float) -> Quantity:
    """
    Find a unit with exact order of magnitude within the same dimensionality.

    Args:
        reference_unit: Unit defining the dimensionality
        target_oom: Target order of magnitude (e.g., 1e6 for mega, 1e9 for giga)

    Returns:
        Unit with the exact target order of magnitude

    Raises:
        UnitNotFound: If no unit with exact target order of magnitude exists

    Examples:
        >>> Units.get_target_unit_for_oom(Units.W, 1e6)
        MW
        >>> Units.get_target_unit_for_oom(Units.EUR, 1e9)
        BEUR
    """
    units = cls.get_all_units_with_equal_base(reference_unit)
    for u in units:
        if cls.get_oom_of_unit(u) == target_oom:
            return u
    raise UnitNotFound(f'No unit with order of mag {target_oom:.0e} for {reference_unit}')

get_closest_unit_for_oom classmethod

get_closest_unit_for_oom(reference_unit: Unit, target_oom: float) -> Quantity

Find the closest unit for a target order of magnitude (doesn't require exact match).

Selects the largest unit whose order of magnitude is less than or equal to the target order of magnitude.

Parameters:

Name Type Description Default
reference_unit Unit

Unit defining the dimensionality

required
target_oom float

Target order of magnitude

required

Returns:

Type Description
Quantity

Closest unit with order of magnitude <= target_oom

Raises:

Type Description
UnitRegistryNotComplete

If no units found for the dimensionality

Examples:

>>> Units.get_closest_unit_for_oom(Units.W, 5e5)  # Between kW and MW
kW
>>> Units.get_closest_unit_for_oom(Units.EUR, 7.5e6)  # Between MEUR and BEUR
MEUR
Source code in submodules/mesqual/mesqual/units.py
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
@classmethod
def get_closest_unit_for_oom(cls, reference_unit: Unit, target_oom: float) -> Quantity:
    """
    Find the closest unit for a target order of magnitude (doesn't require exact match).

    Selects the largest unit whose order of magnitude is less than or equal to
    the target order of magnitude.

    Args:
        reference_unit: Unit defining the dimensionality
        target_oom: Target order of magnitude

    Returns:
        Closest unit with order of magnitude <= target_oom

    Raises:
        UnitRegistryNotComplete: If no units found for the dimensionality

    Examples:
        >>> Units.get_closest_unit_for_oom(Units.W, 5e5)  # Between kW and MW
        kW
        >>> Units.get_closest_unit_for_oom(Units.EUR, 7.5e6)  # Between MEUR and BEUR
        MEUR
    """
    units_with_same_dimension = cls.get_all_units_with_equal_base(reference_unit)
    if len(units_with_same_dimension) == 0:
        raise UnitRegistryNotComplete
    base_unit = cls.get_base_unit_for_unit(reference_unit)
    sorted_units = sorted(units_with_same_dimension, key=lambda x: (1 * x).to(base_unit).magnitude, reverse=True)
    for u in sorted_units:
        if (1 * u).to(base_unit).magnitude <= target_oom:
            return u
    return sorted_units[0]

get_quantity_in_target_oom classmethod

get_quantity_in_target_oom(quantity: Quantity, target_oom: float) -> Quantity

Convert quantity to unit with specific order of magnitude.

Parameters:

Name Type Description Default
quantity Quantity

Quantity to convert

required
target_oom float

Target order of magnitude

required

Returns:

Type Description
Quantity

Quantity converted to target order of magnitude, or original if not found

Examples:

>>> energy = 5000 * Units.kWh
>>> Units.get_quantity_in_target_oom(energy, 1e6)
5.0 MWh
Source code in submodules/mesqual/mesqual/units.py
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
@classmethod
def get_quantity_in_target_oom(cls, quantity: Quantity, target_oom: float) -> Quantity:
    """
    Convert quantity to unit with specific order of magnitude.

    Args:
        quantity: Quantity to convert
        target_oom: Target order of magnitude

    Returns:
        Quantity converted to target order of magnitude, or original if not found

    Examples:
        >>> energy = 5000 * Units.kWh
        >>> Units.get_quantity_in_target_oom(energy, 1e6)
        5.0 MWh
    """
    try:
        target_unit = cls.get_target_unit_for_oom(quantity.units, target_oom)
        return quantity.to(target_unit)
    except UnitNotFound:
        RuntimeWarning(f'# TODO:')
        return quantity

get_quantity_in_target_unit classmethod

get_quantity_in_target_unit(quantity: Quantity, target_unit: Unit) -> Quantity

Convert quantity to a specific target unit.

Simple wrapper around pint's to() method for consistency with other unit conversion methods.

Parameters:

Name Type Description Default
quantity Quantity

Quantity to convert

required
target_unit Unit

Target unit

required

Returns:

Type Description
Quantity

Quantity converted to target unit

Examples:

>>> energy = 5000 * Units.kWh
>>> Units.get_quantity_in_target_unit(energy, Units.MWh)
5.0 MWh
Source code in submodules/mesqual/mesqual/units.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
@classmethod
def get_quantity_in_target_unit(cls, quantity: Quantity, target_unit: Unit) -> Quantity:
    """
    Convert quantity to a specific target unit.

    Simple wrapper around pint's to() method for consistency with other unit conversion methods.

    Args:
        quantity: Quantity to convert
        target_unit: Target unit

    Returns:
        Quantity converted to target unit

    Examples:
        >>> energy = 5000 * Units.kWh
        >>> Units.get_quantity_in_target_unit(energy, Units.MWh)
        5.0 MWh
    """
    return quantity.to(target_unit)

get_quantity_in_pretty_unit classmethod

get_quantity_in_pretty_unit(quantity: Quantity) -> Quantity

Convert quantity to the most readable unit (magnitude between 1 and 10,000).

Automatically selects a unit where the magnitude is less than 10,000, making the value easy to read and comprehend.

Parameters:

Name Type Description Default
quantity Quantity

Quantity to convert

required

Returns:

Type Description
Quantity

Quantity in "pretty" readable unit

Examples:

>>> energy = 5432100 * Units.Wh
>>> Units.get_quantity_in_pretty_unit(energy)
5.4321 MWh
>>> cost = 0.045 * Units.EUR
>>> Units.get_quantity_in_pretty_unit(cost)
4.5 EUR_cent
Source code in submodules/mesqual/mesqual/units.py
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
@classmethod
def get_quantity_in_pretty_unit(cls, quantity: Quantity) -> Quantity:
    """
    Convert quantity to the most readable unit (magnitude between 1 and 10,000).

    Automatically selects a unit where the magnitude is less than 10,000,
    making the value easy to read and comprehend.

    Args:
        quantity: Quantity to convert

    Returns:
        Quantity in "pretty" readable unit

    Examples:
        >>> energy = 5432100 * Units.Wh
        >>> Units.get_quantity_in_pretty_unit(energy)
        5.4321 MWh

        >>> cost = 0.045 * Units.EUR
        >>> Units.get_quantity_in_pretty_unit(cost)
        4.5 EUR_cent
    """
    base_unit = cls.get_base_unit_for_unit(quantity.units)
    units = cls.get_all_units_with_equal_base(base_unit)
    units = sorted(units, key=lambda x: (1 * x).to(base_unit).magnitude, reverse=False)
    for u in units:
        if abs(quantity.to(u).magnitude) < 10_000:
            return quantity.to(u)
    return quantity.to(units[-1])

get_common_pretty_unit_for_quantities classmethod

get_common_pretty_unit_for_quantities(quantities: list[Quantity]) -> Unit

Find common "pretty" unit for a collection of quantities.

Strategy: 1. Verify all quantities have same dimensionality 2. Convert all quantities to all available units 3. Select unit with most values having abs(magnitude) < 10,000 4. If tie, select unit giving largest magnitudes (avoid tiny decimals)

Parameters:

Name Type Description Default
quantities list[Quantity]

List of quantities with same dimensionality

required

Returns:

Type Description
Unit

Pretty unit that works well for the collection

Raises:

Type Description
ValueError

If quantities have different dimensionalities or list is empty

Examples:

>>> quantities = [1_000_000 * Units.EUR, 5_000_000 * Units.EUR]
>>> Units.get_common_pretty_unit_for_quantities(quantities)
    Units.MEUR

>>> quantities = [0.03 * Units.EUR_per_MWh, -0.02 * Units.EUR_per_MWh, 0.01 * Units.EUR_per_MWh]
>>> Units.get_common_pretty_unit_for_quantities(quantities)
    Units.EUR/Units.MWh  # Not EUR/Wh which would give tiny values
Source code in submodules/mesqual/mesqual/units.py
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
@classmethod
def get_common_pretty_unit_for_quantities(cls, quantities: list[Quantity]) -> Unit:
    """
    Find common "pretty" unit for a collection of quantities.

    Strategy:
    1. Verify all quantities have same dimensionality
    2. Convert all quantities to all available units
    3. Select unit with most values having abs(magnitude) < 10,000
    4. If tie, select unit giving largest magnitudes (avoid tiny decimals)

    Args:
        quantities: List of quantities with same dimensionality

    Returns:
        Pretty unit that works well for the collection

    Raises:
        ValueError: If quantities have different dimensionalities or list is empty

    Examples:

        >>> quantities = [1_000_000 * Units.EUR, 5_000_000 * Units.EUR]
        >>> Units.get_common_pretty_unit_for_quantities(quantities)
            Units.MEUR

        >>> quantities = [0.03 * Units.EUR_per_MWh, -0.02 * Units.EUR_per_MWh, 0.01 * Units.EUR_per_MWh]
        >>> Units.get_common_pretty_unit_for_quantities(quantities)
            Units.EUR/Units.MWh  # Not EUR/Wh which would give tiny values
    """
    if not quantities:
        raise ValueError("Cannot find common unit for empty list of quantities")

    # Verify all quantities have same dimensionality
    base_unit = cls.get_base_unit_for_unit(quantities[0].units)
    for q in quantities[1:]:
        if not cls.units_have_same_base(q.units, base_unit):
            raise ValueError(
                f"All quantities must have same dimensionality. "
                f"Found {q.units} which differs from {base_unit}"
            )

    # Handle all-zero case
    non_zero_quantities = [q for q in quantities if abs(q.magnitude) > 0]
    if not non_zero_quantities:
        # All values are zero, return common unit if all same, else base unit
        if len(set([q.units for q in quantities])) == 1:
            return quantities[0].units
        return base_unit

    # Get all available units for this dimensionality
    available_units = cls.get_all_units_with_equal_base(base_unit)
    if not available_units:
        # return common unit if all same, else base unit
        if len(set([q.units for q in quantities])) == 1:
            return quantities[0].units
        return base_unit

    # Evaluate each unit
    best_unit = base_unit
    best_count_under_10k = -1
    best_median_magnitude = -1

    for unit in available_units:
        # Convert all quantities to this unit
        magnitudes = [abs(q.to(unit).magnitude) for q in non_zero_quantities]

        # Count how many values are under 10,000
        count_under_10k = sum(1 for m in magnitudes if m < 10_000)

        # Get median magnitude for tie-breaking
        median_magnitude = np.median(magnitudes)

        # Update best if this unit is better
        if (count_under_10k > best_count_under_10k or
            (count_under_10k == best_count_under_10k and median_magnitude > best_median_magnitude)):
            best_unit = unit
            best_count_under_10k = count_under_10k
            best_median_magnitude = median_magnitude

    return best_unit

get_all_units_with_equal_base classmethod

get_all_units_with_equal_base(unit: Unit) -> list[Unit]

Get all registered units with the same dimensionality as the input unit.

Parameters:

Name Type Description Default
unit Unit

Reference unit to match dimensionality

required

Returns:

Type Description
list[Unit]

List of all units with same dimensionality (e.g., all energy units, all power units)

Examples:

>>> Units.get_all_units_with_equal_base(Units.MW)
[W, kW, MW, GW, TW]
>>> Units.get_all_units_with_equal_base(Units.EUR)
[EUR_cent, EUR, kEUR, MEUR, BEUR, TEUR]
Source code in submodules/mesqual/mesqual/units.py
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
@classmethod
def get_all_units_with_equal_base(cls, unit: Unit) -> list[Unit]:
    """
    Get all registered units with the same dimensionality as the input unit.

    Args:
        unit: Reference unit to match dimensionality

    Returns:
        List of all units with same dimensionality (e.g., all energy units, all power units)

    Examples:
        >>> Units.get_all_units_with_equal_base(Units.MW)
        [W, kW, MW, GW, TW]
        >>> Units.get_all_units_with_equal_base(Units.EUR)
        [EUR_cent, EUR, kEUR, MEUR, BEUR, TEUR]
    """
    return [u for u in Units if cls.units_have_same_base(unit, u)]

get_pretty_text_for_quantity classmethod

get_pretty_text_for_quantity(quantity: Quantity, decimals: int = None, thousands_separator: str = None, include_unit: bool = True, include_oom: bool = True, include_sign: bool = None) -> str

Format a quantity as human-readable text with customizable formatting.

Applies string replacements for better readability (e.g., 'per' → '/', 'EUR' → '€').

Parameters:

Name Type Description Default
quantity Quantity

Quantity to format

required
decimals int

Number of decimal places (auto-determined if None)

None
thousands_separator str

Separator for thousands (default: '')

None
include_unit bool

Whether to include unit in output (default: True)

True
include_oom bool

Whether to include order of magnitude prefix (default: True)

True
include_sign bool

Whether to include '+' for positive values (default: None/auto)

None

Returns:

Type Description
str

Formatted text string representation of the quantity

Examples:

>>> price = 45.678 * Units.EUR_per_MWh
>>> Units.get_pretty_text_for_quantity(price, decimals=2)
'45.68 €/MWh'
>>> cost = 1234567 * Units.EUR
>>> Units.get_pretty_text_for_quantity(cost, thousands_separator=' ')
'1 234 567 €'
>>> power = 5.2 * Units.MW
>>> Units.get_pretty_text_for_quantity(power, include_sign=True)
'+5.2 MW'
Source code in submodules/mesqual/mesqual/units.py
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
@classmethod
def get_pretty_text_for_quantity(
        cls,
        quantity: Quantity,
        decimals: int = None,
        thousands_separator: str = None,
        include_unit: bool = True,
        include_oom: bool = True,
        include_sign: bool = None,
) -> str:
    """
    Format a quantity as human-readable text with customizable formatting.

    Applies string replacements for better readability (e.g., '_per_' → '/', 'EUR' → '€').

    Args:
        quantity: Quantity to format
        decimals: Number of decimal places (auto-determined if None)
        thousands_separator: Separator for thousands (default: '')
        include_unit: Whether to include unit in output (default: True)
        include_oom: Whether to include order of magnitude prefix (default: True)
        include_sign: Whether to include '+' for positive values (default: None/auto)

    Returns:
        Formatted text string representation of the quantity

    Examples:
        >>> price = 45.678 * Units.EUR_per_MWh
        >>> Units.get_pretty_text_for_quantity(price, decimals=2)
        '45.68 €/MWh'

        >>> cost = 1234567 * Units.EUR
        >>> Units.get_pretty_text_for_quantity(cost, thousands_separator=' ')
        '1 234 567 €'

        >>> power = 5.2 * Units.MW
        >>> Units.get_pretty_text_for_quantity(power, include_sign=True)
        '+5.2 MW'
    """
    if decimals is None:
        decimals = cls.get_pretty_decimals(quantity)
    if thousands_separator is None:
        thousands_separator = ''

    sign_str = cls._get_sign_str_for_quantity(quantity, include_sign)
    value_str = f'{abs(quantity.magnitude):,.{decimals}f}'
    value_str = value_str.replace(',', thousands_separator)

    if include_unit:
        if not include_oom:
            raise NotImplementedError('Why would you do that?')
        unit_str = str(quantity.units)
    elif include_oom:
        unit_str = cls._get_units_oom_prefix(quantity.units)
    else:
        unit_str = ''

    components = []
    if sign_str:
        components.append(sign_str)

    components.append(value_str)

    if unit_str:
        components.append(' ' + unit_str)

    pretty_text = ''.join(components)

    for r, v in cls._STRING_REPLACEMENTS.items():
        pretty_text = pretty_text.replace(r, v)

    return pretty_text

get_pretty_decimals classmethod

get_pretty_decimals(quantity: Quantity) -> int

Determine appropriate number of decimal places for a quantity's magnitude.

Automatically selects decimal places based on the magnitude to ensure readability: - Integer values: 0 decimals - >100: 0 decimals - >10: 1 decimal - >0.1: 2 decimals - >0.01: 3 decimals - <0.01: 5 decimals

Parameters:

Name Type Description Default
quantity Quantity

Quantity to determine decimal places for

required

Returns:

Type Description
int

Number of decimal places (0-5)

Examples:

>>> Units.get_pretty_decimals(1234.5 * Units.MW)
0
>>> Units.get_pretty_decimals(12.34 * Units.MW)
1
>>> Units.get_pretty_decimals(0.0123 * Units.MW)
5
Source code in submodules/mesqual/mesqual/units.py
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
@classmethod
def get_pretty_decimals(cls, quantity: Quantity) -> int:
    """
    Determine appropriate number of decimal places for a quantity's magnitude.

    Automatically selects decimal places based on the magnitude to ensure readability:
    - Integer values: 0 decimals
    - >100: 0 decimals
    - >10: 1 decimal
    - >0.1: 2 decimals
    - >0.01: 3 decimals
    - <0.01: 5 decimals

    Args:
        quantity: Quantity to determine decimal places for

    Returns:
        Number of decimal places (0-5)

    Examples:
        >>> Units.get_pretty_decimals(1234.5 * Units.MW)
        0
        >>> Units.get_pretty_decimals(12.34 * Units.MW)
        1
        >>> Units.get_pretty_decimals(0.0123 * Units.MW)
        5
    """
    # if quantity.units == Units.per_unit:
    #     return 3

    if isinstance(quantity.magnitude, int):
        return 0

    abs_value = abs(quantity.magnitude)
    if abs_value > 100:
        return 0
    elif abs_value > 10:
        return 1
    elif abs_value > 0.1:
        return 2
    elif abs_value > 0.01:
        return 3
    elif abs_value == 0:
        return 0
    else:
        return 5

QuantityToTextConverter

Configurable converter for formatting Quantity objects as text strings.

Stores formatting configuration that can be reused across multiple quantity conversions, enabling consistent formatting across KPI collections and visualizations.

Parameters:

Name Type Description Default
target_unit Unit

Target unit for conversion (if None, uses pretty unit selection)

None
decimals int

Number of decimal places (if None, auto-determined)

None
thousands_separator str

Separator for thousands (default: '')

None
include_unit bool

Whether to include unit in output (default: True)

True
include_oom bool

Whether to include order of magnitude prefix (default: True)

True
include_sign bool

Whether to include + sign for positive values (default: None/auto)

None

Examples:

Basic usage with fixed configuration:

>>> converter = QuantityToTextConverter(
...     target_unit=Units.MWh,
...     decimals=2,
...     thousands_separator=' '
... )
>>> converter.convert(5432.1 * Units.kWh)
'5.43 MWh'

Auto-configure from collection of quantities:

>>> quantities = [1000 * Units.EUR, 5000 * Units.EUR, 10000 * Units.EUR]
>>> converter = QuantityToTextConverter.from_quantities(quantities, decimals=0)
>>> [converter.convert(q) for q in quantities]
['1 kEUR', '5 kEUR', '10 kEUR']
Source code in submodules/mesqual/mesqual/units.py
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
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
class QuantityToTextConverter:
    """
    Configurable converter for formatting Quantity objects as text strings.

    Stores formatting configuration that can be reused across multiple quantity
    conversions, enabling consistent formatting across KPI collections and visualizations.

    Args:
        target_unit: Target unit for conversion (if None, uses pretty unit selection)
        decimals: Number of decimal places (if None, auto-determined)
        thousands_separator: Separator for thousands (default: '')
        include_unit: Whether to include unit in output (default: True)
        include_oom: Whether to include order of magnitude prefix (default: True)
        include_sign: Whether to include + sign for positive values (default: None/auto)

    Examples:
        Basic usage with fixed configuration:
        >>> converter = QuantityToTextConverter(
        ...     target_unit=Units.MWh,
        ...     decimals=2,
        ...     thousands_separator=' '
        ... )
        >>> converter.convert(5432.1 * Units.kWh)
        '5.43 MWh'

        Auto-configure from collection of quantities:
        >>> quantities = [1000 * Units.EUR, 5000 * Units.EUR, 10000 * Units.EUR]
        >>> converter = QuantityToTextConverter.from_quantities(quantities, decimals=0)
        >>> [converter.convert(q) for q in quantities]
        ['1 kEUR', '5 kEUR', '10 kEUR']
    """

    def __init__(
        self,
        target_unit: Unit = None,
        decimals: int = None,
        thousands_separator: str = None,
        include_unit: bool = True,
        include_oom: bool = True,
        include_sign: bool = None,
    ):
        self.target_unit = target_unit
        self.decimals = decimals
        self.thousands_separator = thousands_separator or ''
        self.include_unit = include_unit
        self.include_oom = include_oom
        self.include_sign = include_sign

    def convert(self, quantity: Quantity) -> str:
        """
        Convert a Quantity to formatted text string using stored configuration.

        Args:
            quantity: The quantity to format

        Returns:
            Formatted text representation
        """
        # Apply target unit conversion if specified
        if self.target_unit is not None:
            quantity = Units.get_quantity_in_target_unit(quantity, self.target_unit)
        else:
            quantity = Units.get_quantity_in_pretty_unit(quantity)

        # Use Units.get_pretty_text_for_quantity with stored configuration
        return Units.get_pretty_text_for_quantity(
            quantity,
            decimals=self.decimals,
            thousands_separator=self.thousands_separator,
            include_unit=self.include_unit,
            include_oom=self.include_oom,
            include_sign=self.include_sign,
        )

    @classmethod
    def from_quantities(
        cls,
        quantities: list[Quantity],
        target_unit: Unit = None,
        decimals: int = None,
        thousands_separator: str = None,
        include_unit: bool = True,
        include_oom: bool = True,
        include_sign: bool = None,
    ) -> 'QuantityToTextConverter':
        """
        Create converter auto-configured for a collection of quantities.

        Analyzes the provided quantities to determine an appropriate common unit
        (if target_unit not specified) that works well for all values.

        Args:
            quantities: Collection of quantities to analyze
            target_unit: Override auto-selected unit with specific target
            decimals: Number of decimal places (if None, will be auto-determined per value)
            thousands_separator: Separator for thousands (default: '')
            include_unit: Whether to include unit in output (default: True)
            include_oom: Whether to include order of magnitude prefix (default: True)
            include_sign: Whether to include + sign for positive values (default: None/auto)

        Returns:
            Configured QuantityToTextConverter instance

        Examples:

            Auto-configure for price data:
            >>> prices = [45.2 * Units.EUR_per_MWh, 67.8 * Units.EUR_per_MWh]
            >>> converter = QuantityToTextConverter.from_quantities(prices, thousands_separator=' ')
            >>> converter.convert(prices[0])
                '45.20 €/MWh'
        """
        # Determine common pretty unit if not explicitly provided
        if target_unit is None and quantities:
            target_unit = Units.get_common_pretty_unit_for_quantities(quantities)

        if decimals is None:
            decimals = cls._pretty_decimal_precision([q.magnitude for q in quantities])

        return cls(
            target_unit=target_unit,
            decimals=decimals,
            thousands_separator=thousands_separator,
            include_unit=include_unit,
            include_oom=include_oom,
            include_sign=include_sign,
        )

    @staticmethod
    def _pretty_decimal_precision(values):
        """Determine minimal decimal places to differentiate values,
        with special rule: any |value| < 0.1 -> at least 2 decimals."""
        if len(values) <= 1:
            return None

        sorted_vals = np.sort(values)
        diffs = np.diff(sorted_vals)
        non_zero_diffs = diffs[diffs > 0]

        if len(non_zero_diffs) == 0:
            return None  # all values identical

        median_diff = np.median(non_zero_diffs)
        decimals = max(0, ceil(-log10(median_diff)))

        # Apply special rule for small absolute values
        if np.any(np.abs(values) < 0.1):
            decimals = max(decimals, 2)

        return decimals

convert

convert(quantity: Quantity) -> str

Convert a Quantity to formatted text string using stored configuration.

Parameters:

Name Type Description Default
quantity Quantity

The quantity to format

required

Returns:

Type Description
str

Formatted text representation

Source code in submodules/mesqual/mesqual/units.py
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
def convert(self, quantity: Quantity) -> str:
    """
    Convert a Quantity to formatted text string using stored configuration.

    Args:
        quantity: The quantity to format

    Returns:
        Formatted text representation
    """
    # Apply target unit conversion if specified
    if self.target_unit is not None:
        quantity = Units.get_quantity_in_target_unit(quantity, self.target_unit)
    else:
        quantity = Units.get_quantity_in_pretty_unit(quantity)

    # Use Units.get_pretty_text_for_quantity with stored configuration
    return Units.get_pretty_text_for_quantity(
        quantity,
        decimals=self.decimals,
        thousands_separator=self.thousands_separator,
        include_unit=self.include_unit,
        include_oom=self.include_oom,
        include_sign=self.include_sign,
    )

from_quantities classmethod

from_quantities(quantities: list[Quantity], target_unit: Unit = None, decimals: int = None, thousands_separator: str = None, include_unit: bool = True, include_oom: bool = True, include_sign: bool = None) -> QuantityToTextConverter

Create converter auto-configured for a collection of quantities.

Analyzes the provided quantities to determine an appropriate common unit (if target_unit not specified) that works well for all values.

Parameters:

Name Type Description Default
quantities list[Quantity]

Collection of quantities to analyze

required
target_unit Unit

Override auto-selected unit with specific target

None
decimals int

Number of decimal places (if None, will be auto-determined per value)

None
thousands_separator str

Separator for thousands (default: '')

None
include_unit bool

Whether to include unit in output (default: True)

True
include_oom bool

Whether to include order of magnitude prefix (default: True)

True
include_sign bool

Whether to include + sign for positive values (default: None/auto)

None

Returns:

Type Description
QuantityToTextConverter

Configured QuantityToTextConverter instance

Examples:

Auto-configure for price data:
>>> prices = [45.2 * Units.EUR_per_MWh, 67.8 * Units.EUR_per_MWh]
>>> converter = QuantityToTextConverter.from_quantities(prices, thousands_separator=' ')
>>> converter.convert(prices[0])
    '45.20 €/MWh'
Source code in submodules/mesqual/mesqual/units.py
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
@classmethod
def from_quantities(
    cls,
    quantities: list[Quantity],
    target_unit: Unit = None,
    decimals: int = None,
    thousands_separator: str = None,
    include_unit: bool = True,
    include_oom: bool = True,
    include_sign: bool = None,
) -> 'QuantityToTextConverter':
    """
    Create converter auto-configured for a collection of quantities.

    Analyzes the provided quantities to determine an appropriate common unit
    (if target_unit not specified) that works well for all values.

    Args:
        quantities: Collection of quantities to analyze
        target_unit: Override auto-selected unit with specific target
        decimals: Number of decimal places (if None, will be auto-determined per value)
        thousands_separator: Separator for thousands (default: '')
        include_unit: Whether to include unit in output (default: True)
        include_oom: Whether to include order of magnitude prefix (default: True)
        include_sign: Whether to include + sign for positive values (default: None/auto)

    Returns:
        Configured QuantityToTextConverter instance

    Examples:

        Auto-configure for price data:
        >>> prices = [45.2 * Units.EUR_per_MWh, 67.8 * Units.EUR_per_MWh]
        >>> converter = QuantityToTextConverter.from_quantities(prices, thousands_separator=' ')
        >>> converter.convert(prices[0])
            '45.20 €/MWh'
    """
    # Determine common pretty unit if not explicitly provided
    if target_unit is None and quantities:
        target_unit = Units.get_common_pretty_unit_for_quantities(quantities)

    if decimals is None:
        decimals = cls._pretty_decimal_precision([q.magnitude for q in quantities])

    return cls(
        target_unit=target_unit,
        decimals=decimals,
        thousands_separator=thousands_separator,
        include_unit=include_unit,
        include_oom=include_oom,
        include_sign=include_sign,
    )