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

66 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 

8from __future__ import annotations 

9 

10from datetime import datetime, timedelta 

11from typing import Any 

12 

13import numpy as np 

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

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 

75 

76def sympy_to_tex(matrix, file): 

77 save_as_tex(file, sympy_matrix_to_latex(matrix)) 

78 

79 

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

81# latex_str = sparse_matrix_to_nicematrix_latex(M) 

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

83# \usepackage{{amsmath}} 

84# \usepackage{{nicematrix}} 

85# \usepackage{{lmodern}} 

86# \usepackage{{anyfontsize}} 

87# 

88# \NiceMatrixOptions{{cell-space-limits=2pt}} 

89# 

90# \begin{{document}} 

91# \fontsize{{{fontsize}}}{{{fontsize}}}\selectfont 

92# \[ 

93# {latex_str} 

94# \] 

95# \end{{document}} 

96# """ 

97# with open(filename, "w") as f: 

98# f.write(doc) 

99 

100 

101def is_set(value): 

102 """ 

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

104 

105 Parameters 

106 ---------- 

107 value : any 

108 The value to check. 

109 

110 Returns 

111 ------- 

112 bool : 

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

114 """ 

115 if isinstance(value, Basic): 

116 return True 

117 if value is None: 

118 return False 

119 if isinstance(value, np.ndarray): 

120 return value.size > 0 

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

122 return len(value) > 0 

123 if np.isnan(value): 

124 return False 

125 return True 

126 

127 

128def get_nested_attribute(obj, attribute_path: str): 

129 """ 

130 Get nested attribute value from object using dot notation. 

131 

132 Parameters 

133 ---------- 

134 obj : object 

135 Object to get attribute from 

136 attribute_path : str 

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

138 

139 Returns 

140 ------- 

141 Any 

142 Value of the attribute 

143 """ 

144 attributes = attribute_path.split(".") 

145 value = obj 

146 for attr in attributes: 

147 value = getattr(value, attr) 

148 return value 

149 

150 

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

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

153 

154 Parameters 

155 ---------- 

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

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

158 

159 Returns 

160 ------- 

161 bool : 

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

163 """ 

164 if isinstance(expr, np.ndarray): 

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

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

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

168 try: 

169 return bool(sympify(expr).free_symbols) 

170 except Exception: 

171 return False 

172 

173 

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

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

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

177 ) 

178 

179 

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

181 if check_value is None: 

182 check_value = nodes 

183 assert len(check_value) == len(nodes) 

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

185 return tuple(nodes) 

186 else: 

187 return nodes[1], nodes[0] 

188 

189 

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

191 dt = datetime.fromisoformat(date_string) 

192 result_dt = dt - timedelta(seconds=seconds) 

193 return result_dt.isoformat() 

194 

195 

196def fill_none(list1, list2): 

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