Coverage for src/beamme/utils/data_structures.py: 95%

21 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-08 11:03 +0000

1# The MIT License (MIT) 

2# 

3# Copyright (c) 2018-2026 BeamMe Authors 

4# 

5# Permission is hereby granted, free of charge, to any person obtaining a copy 

6# of this software and associated documentation files (the "Software"), to deal 

7# in the Software without restriction, including without limitation the rights 

8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 

9# copies of the Software, and to permit persons to whom the Software is 

10# furnished to do so, subject to the following conditions: 

11# 

12# The above copyright notice and this permission notice shall be included in 

13# all copies or substantial portions of the Software. 

14# 

15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 

21# THE SOFTWARE. 

22"""Helper functions for data structure related functionality.""" 

23 

24from typing import Any as _Any 

25from typing import Callable as _Callable 

26 

27from fourcipp.utils.dict_utils import ( 

28 compare_nested_dicts_or_lists as _compare_nested_dicts_or_lists, 

29) 

30 

31 

32def create_inverse_mapping(mapping: dict[_Any, _Any]) -> dict[_Any, _Any]: 

33 """Create an inverse mapping from a given mapping. 

34 

35 Args: 

36 mapping: The mapping to create the inverse mapping from. 

37 

38 Returns: 

39 The inverse mapping. 

40 """ 

41 # Check that the mapping is invertible, i.e., that all values are unique. 

42 unique_values = set(mapping.values()) 

43 if len(unique_values) != len(mapping): 

44 unique_values = set() 

45 duplicate_values = set() 

46 for value in mapping.values(): 

47 if value in unique_values: 

48 duplicate_values.add(value) 

49 else: 

50 unique_values.add(value) 

51 raise ValueError( 

52 "The mapping is not invertible, values are not unique. " 

53 f"Non-unique values: {duplicate_values}" 

54 ) 

55 return {value: key for key, value in mapping.items()} 

56 

57 

58def compare_nested_dicts_or_lists( 

59 obj: _Any, 

60 reference_obj: _Any, 

61 allow_int_vs_float_comparison: bool = False, 

62 rtol: float = 1.0e-5, 

63 atol: float = 1.0e-8, 

64 equal_nan: bool = False, 

65 custom_compare: _Callable | None = None, 

66 raise_on_mismatch: bool = False, 

67) -> bool: 

68 """Recursively compare two nested dictionaries or lists. 

69 

70 This function is taken from FourCIPP and modified such that raising 

71 an assertion error is optional. 

72 

73 To compare custom python objects, a `custom_compare` callable can be provided which: 

74 - Returns nothing/`None` if the objects where not compared within `custom_compare` 

75 - Returns `True` if the objects are seen as equal 

76 - Optionally raises an AssertionError if the objects are not equal 

77 

78 Args: 

79 obj: Object for comparison. 

80 reference_obj: Reference object. 

81 allow_int_vs_float_comparison: Allow a tolerance based comparison between `int` and 

82 `float`. 

83 rtol: The relative tolerance parameter for `numpy.isclose`. 

84 atol: The absolute tolerance parameter for `numpy.isclose`. 

85 equal_nan: Whether to compare NaN's as equal for `numpy.isclose`. 

86 custom_compare: Callable to compare objects within this nested framework. 

87 raise_on_mismatch: Whether to raise an `AssertionError` if the objects are not equal. 

88 

89 Returns: 

90 `True` if the dictionaries are equal, `False` otherwise. 

91 """ 

92 try: 

93 return _compare_nested_dicts_or_lists( 

94 obj, 

95 reference_obj, 

96 allow_int_vs_float_comparison, 

97 rtol, 

98 atol, 

99 equal_nan, 

100 custom_compare, 

101 ) 

102 except AssertionError: 

103 if raise_on_mismatch: 

104 raise 

105 return False