Coverage for pyrc \ tools \ functions.py: 54%

71 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 

8from __future__ import annotations 

9 

10from datetime import datetime, timedelta 

11from typing import Any 

12 

13from sympy import latex, Basic, sympify, diag, Matrix 

14import numpy as np 

15 

16 

17def add_leading_underscore(string: str = "") -> str: 

18 """ 

19 Adds a leading underscore if not already existing and string is not None or empty. 

20 

21 Parameters 

22 ---------- 

23 string : str 

24 The string to check. 

25 

26 Returns 

27 ------- 

28 str : 

29 Checked string. 

30 """ 

31 if string is not None and string != "": 

32 if not string.startswith("_"): 

33 string = f"_{string}" 

34 else: 

35 string = "" 

36 return string 

37 

38 

39def sympy_matrix_to_latex(matrix): 

40 """ 

41 Return a LaTeX representation of a sympy expression. 

42 

43 Parameters 

44 ---------- 

45 matrix : sympy.Matrix 

46 Expression or matrix to convert to LaTeX. 

47 

48 Returns 

49 ------- 

50 str 

51 LaTeX representation of expr. 

52 """ 

53 

54 diag_elements = matrix.diagonal() 

55 matrix_no_diag = matrix - diag(*diag_elements) 

56 

57 matrix_latex = latex(matrix_no_diag) 

58 diag_latex = latex(Matrix(diag_elements).reshape(len(diag_elements), 1)) 

59 

60 return f"{matrix_latex} + \\mathrm{{diag}}\\left({diag_latex}\\right)" 

61 

62 

63def save_as_tex(filename, latex_str): 

64 if isinstance(latex_str, list): 

65 latex_str = "\n\n".join(latex_str) 

66 with open(filename, "w") as f: 

67 f.write( 

68 rf"""\documentclass[border=3mm]{{standalone}} 

69\usepackage{{amsmath}} 

70\begin{{document}} 

71$ {latex_str} $ 

72\end{{document}}""" 

73 ) 

74 

75def sympy_to_tex(matrix, file): 

76 save_as_tex(file, sympy_matrix_to_latex(matrix)) 

77 

78 

79def sympy_sparse_matrix_to_latex(M, filename, fontsize="20pt"): 

80 latex_str = sparse_matrix_to_nicematrix_latex(M) 

81 doc = rf"""\documentclass[border=3mm]{{standalone}} 

82\usepackage{{amsmath}} 

83\usepackage{{nicematrix}} 

84\usepackage{{lmodern}} 

85\usepackage{{anyfontsize}} 

86 

87\NiceMatrixOptions{{cell-space-limits=2pt}} 

88 

89\begin{{document}} 

90\fontsize{{{fontsize}}}{{{fontsize}}}\selectfont 

91\[ 

92{latex_str} 

93\] 

94\end{{document}} 

95""" 

96 with open(filename, "w") as f: 

97 f.write(doc) 

98 

99 

100def is_set(value): 

101 """ 

102 Returns True if the value is already set (by value or symbol) and False if it is np.nan. 

103 

104 Parameters 

105 ---------- 

106 value : any 

107 The value to check. 

108 

109 Returns 

110 ------- 

111 bool : 

112 True if the resistance value is already set and False if it is np.nan. 

113 """ 

114 if isinstance(value, Basic): 

115 return True 

116 if value is None: 

117 return False 

118 if isinstance(value, np.ndarray): 

119 return value.size > 0 

120 if isinstance(value, (list, tuple)): 

121 return len(value) > 0 

122 if np.isnan(value): 

123 return False 

124 return True 

125 

126 

127def get_nested_attribute(obj, attribute_path: str): 

128 """ 

129 Get nested attribute value from object using dot notation. 

130 

131 Parameters 

132 ---------- 

133 obj : object 

134 Object to get attribute from 

135 attribute_path : str 

136 Dot-separated attribute path (e.g., 'material.name') 

137 

138 Returns 

139 ------- 

140 Any 

141 Value of the attribute 

142 """ 

143 attributes = attribute_path.split(".") 

144 value = obj 

145 for attr in attributes: 

146 value = getattr(value, attr) 

147 return value 

148 

149 

150def contains_symbol(expr: float | int | np.ndarray | np.number | Any) -> bool: 

151 """Check whether any sympy symbol appears in an expression, array, or scalar. 

152 

153 Parameters 

154 ---------- 

155 expr : float | int | np.ndarray | np.number | Any 

156 Expression, numpy array, list, scalar, or np.nan to check. 

157 

158 Returns 

159 ------- 

160 bool : 

161 True if any sympy symbol is found anywhere in expr. 

162 """ 

163 if isinstance(expr, np.ndarray): 

164 return any(contains_symbol(e) for e in expr.flat) 

165 if isinstance(expr, (list, tuple)): 

166 return any(contains_symbol(e) for e in expr) 

167 try: 

168 return bool(sympify(expr).free_symbols) 

169 except Exception: 

170 return False 

171 

172 

173def check_type(nodes, type1, type2) -> bool: 

174 return (isinstance(nodes[0], type1) and isinstance(nodes[1], type2)) or ( 

175 isinstance(nodes[1], type1) and isinstance(nodes[0], type2) 

176 ) 

177 

178 

179def return_type(nodes: list, type1, check_value: list | Any = None): 

180 if check_value is None: 

181 check_value = nodes 

182 assert len(check_value) == len(nodes) 

183 if isinstance(check_value[0], type1): 

184 return tuple(nodes) 

185 else: 

186 return nodes[1], nodes[0] 

187 

188 

189def subtract_seconds_from_string(date_string: str, seconds: int | float) -> str: 

190 dt = datetime.fromisoformat(date_string) 

191 result_dt = dt - timedelta(seconds=seconds) 

192 return result_dt.isoformat() 

193 

194 

195def fill_none(list1, list2): 

196 return [b if a is None else a for a, b in zip(list1, list2)]