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

73 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-29 15:57 +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 multiprocessing 

9import os 

10 

11import numpy as np 

12from psutil import virtual_memory 

13from sympy import SparseMatrix, diff 

14 

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

16 

17 

18def get_free_ram_gb(): 

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

20 

21 

22def get_free_ram(): 

23 return virtual_memory().available 

24 

25 

26def kelvin_to_celsius(kelvin): 

27 return kelvin - 273.15 

28 

29 

30def celsius_to_kelvin(celsius): 

31 return celsius + 273.15 

32 

33 

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

35 """ 

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

37 

38 Parameters 

39 ---------- 

40 terms : list 

41 All terms in a list. 

42 variables : list 

43 All variables of all terms in a list. 

44 involved_symbols : list 

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

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

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

48 symbols are not involved. 

49 num_cores : int, optional 

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

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

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

53 

54 Returns 

55 ------- 

56 Any : 

57 The jacobian matrix with symbols. 

58 """ 

59 

60 # Define batch size for efficiency 

61 if num_cores is None: 

62 num_cores = multiprocessing.cpu_count() - 1 

63 

64 if num_cores <= 1: 

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

66 else: 

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

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

69 

70 # Prepare arguments for starmap 

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

72 

73 # Parallel execution with batching using starmap 

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

75 jacobian_batches = pool.starmap(compute_jacobian_batch, batch_args) 

76 

77 # Flatten the result and convert to SparseMatrix 

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

79 jacobian = SparseMatrix(jacobian_rows) 

80 

81 return jacobian 

82 

83 

84def is_numeric(value): 

85 """ 

86 Checks if value is a numeric value. 

87 

88 Parameters 

89 ---------- 

90 value : Any 

91 Value to check. 

92 

93 Returns 

94 ------- 

95 bool 

96 True if value is numeric, False otherwise 

97 """ 

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

99 for numeric in list_of_numerics: 

100 if isinstance(value, numeric): 

101 return True 

102 return False 

103 

104 

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

106 if np.isnan(number): 

107 if return_str: 

108 return "NaN" 

109 return np.nan 

110 if number == np.inf: 

111 if return_str: 

112 return "Infinity" 

113 return np.inf 

114 if number == -np.inf: 

115 if return_str: 

116 return "-Infinity" 

117 return -np.inf 

118 if int(number) == 0: 

119 # number < 1 

120 if number == 0: 

121 if return_str: 

122 return "0" 

123 return 0 

124 counter = 0 

125 while int(number) == 0: 

126 number = number * 10 

127 counter += 1 

128 if return_str: 

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

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

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

132 else: 

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

134 if ten_digits >= valid_digits: 

135 number = int(number) 

136 if return_str: 

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

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

139 

140 

141# Function to compute a batch of Jacobian rows 

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

143 batch_result = [] 

144 for i in _batch: 

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

146 batch_result.append(row) 

147 return batch_result 

148 

149 

150def cm_to_inch(value): 

151 return value / 2.54