Coverage for pyrc \ tools \ science.py: 36%

73 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-13 16:59 +0200

1# ------------------------------------------------------------------------------- 

2# Copyright (C) 2026 Joel Kimmich, Tim Jourdan 

3# ------------------------------------------------------------------------------ 

4# License 

5# This file is part of PyRC, distributed under GPL-3.0-or-later. 

6# ------------------------------------------------------------------------------ 

7 

8import numpy as np 

9from sympy import diff, SparseMatrix 

10import multiprocessing 

11import os 

12from psutil import virtual_memory 

13 

14os.environ["SYMPY_CACHE_SIZE"] = "10000000000" 

15 

16 

17def get_free_ram_gb(): 

18 return get_free_ram() / (1024**3) 

19 

20 

21def get_free_ram(): 

22 return virtual_memory().available 

23 

24 

25def kelvin_to_celsius(kelvin): 

26 return kelvin - 273.15 

27 

28 

29def celsius_to_kelvin(celsius): 

30 return celsius + 273.15 

31 

32 

33def build_jacobian(terms: list, variables: list, involved_symbols: list, num_cores: int = None) -> SparseMatrix: 

34 """ 

35 creates the jacobian matrix using all terms and their involved symbols. 

36 

37 Parameters 

38 ---------- 

39 terms : list 

40 All terms in a list. 

41 variables : list 

42 All variables of all terms in a list. 

43 involved_symbols : list 

44 Lists with the symbols the derivative must be created for each term in a list. 

45 So for term[0] there are only the variables involved_symbols[0] relevant. 

46 It is used to make the calculation faster and set 0 to all other places in the jacobian matrix where the 

47 symbols are not involved. 

48 num_cores : int, optional 

49 The number of cores that should be used to calculate the jacobian matrix. 

50 If None, the maximum number of cores except one is used. 

51 If 1 or smaller, no parallel computing is used. 

52 

53 Returns 

54 ------- 

55 Any : 

56 The jacobian matrix with symbols. 

57 """ 

58 

59 # Define batch size for efficiency 

60 if num_cores is None: 

61 num_cores = multiprocessing.cpu_count() - 1 

62 

63 if num_cores <= 1: 

64 jacobian_rows = compute_jacobian_batch(list(range(0, len(terms))), terms, involved_symbols, variables) 

65 else: 

66 batch_size = max(1, len(terms) // (num_cores * 3)) # Adjust batch size based on problem size 

67 batches = [list(range(i, min(i + batch_size, len(terms)))) for i in range(0, len(terms), batch_size)] 

68 

69 # Prepare arguments for starmap 

70 batch_args = [(batch, terms, involved_symbols, variables) for batch in batches] 

71 

72 # Parallel execution with batching using starmap 

73 with multiprocessing.Pool(processes=num_cores) as pool: 

74 jacobian_batches = pool.starmap(compute_jacobian_batch, batch_args) 

75 

76 # Flatten the result and convert to SparseMatrix 

77 jacobian_rows = [row for batch in jacobian_batches for row in batch] 

78 jacobian = SparseMatrix(jacobian_rows) 

79 

80 return jacobian 

81 

82 

83def is_numeric(value): 

84 """ 

85 Checks if value is a numeric value. 

86 

87 Parameters 

88 ---------- 

89 value : Any 

90 Value to check. 

91 

92 Returns 

93 ------- 

94 bool 

95 True if value is numeric, False otherwise 

96 """ 

97 list_of_numerics = [float, int, np.number] # add numeric data types here if necessary 

98 for numeric in list_of_numerics: 

99 if isinstance(value, numeric): 

100 return True 

101 return False 

102 

103 

104def round_valid(number: float | int, valid_digits: int, return_str: bool = False): 

105 if np.isnan(number): 

106 if return_str: 

107 return "NaN" 

108 return np.nan 

109 if number == np.inf: 

110 if return_str: 

111 return "Infinity" 

112 return np.inf 

113 if number == -np.inf: 

114 if return_str: 

115 return "-Infinity" 

116 return -np.inf 

117 if int(number) == 0: 

118 # number < 1 

119 if number == 0: 

120 if return_str: 

121 return "0" 

122 return 0 

123 counter = 0 

124 while int(number) == 0: 

125 number = number * 10 

126 counter += 1 

127 if return_str: 

128 format_str = f"{{:{counter + 1 + valid_digits}.{valid_digits + counter - 1}f}}" 

129 return format_str.format(np.round(number, valid_digits) / 10**counter) 

130 return np.round(np.round(number, valid_digits) / 10**counter, counter + valid_digits - 1) 

131 else: 

132 ten_digits = int(np.log10(abs(number))) + 1 

133 if ten_digits >= valid_digits: 

134 number = int(number) 

135 if return_str: 

136 return f"{np.round(number, valid_digits - ten_digits)}" 

137 return np.round(number, valid_digits - ten_digits) 

138 

139 

140# Function to compute a batch of Jacobian rows 

141def compute_jacobian_batch(_batch, _terms, _dependent_variables, _all_variables): 

142 batch_result = [] 

143 for i in _batch: 

144 row = [diff(_terms[i], v) if v in _dependent_variables[i] else 0 for v in _all_variables] 

145 batch_result.append(row) 

146 return batch_result 

147 

148 

149def cm_to_inch(value): 

150 return value / 2.54