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
« 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# ------------------------------------------------------------------------------
8from __future__ import annotations
10from datetime import datetime, timedelta
11from typing import Any
13from sympy import latex, Basic, sympify, diag, Matrix
14import numpy as np
17def add_leading_underscore(string: str = "") -> str:
18 """
19 Adds a leading underscore if not already existing and string is not None or empty.
21 Parameters
22 ----------
23 string : str
24 The string to check.
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
39def sympy_matrix_to_latex(matrix):
40 """
41 Return a LaTeX representation of a sympy expression.
43 Parameters
44 ----------
45 matrix : sympy.Matrix
46 Expression or matrix to convert to LaTeX.
48 Returns
49 -------
50 str
51 LaTeX representation of expr.
52 """
54 diag_elements = matrix.diagonal()
55 matrix_no_diag = matrix - diag(*diag_elements)
57 matrix_latex = latex(matrix_no_diag)
58 diag_latex = latex(Matrix(diag_elements).reshape(len(diag_elements), 1))
60 return f"{matrix_latex} + \\mathrm{{diag}}\\left({diag_latex}\\right)"
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 )
75def sympy_to_tex(matrix, file):
76 save_as_tex(file, sympy_matrix_to_latex(matrix))
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}}
87\NiceMatrixOptions{{cell-space-limits=2pt}}
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)
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.
104 Parameters
105 ----------
106 value : any
107 The value to check.
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
127def get_nested_attribute(obj, attribute_path: str):
128 """
129 Get nested attribute value from object using dot notation.
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')
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
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.
153 Parameters
154 ----------
155 expr : float | int | np.ndarray | np.number | Any
156 Expression, numpy array, list, scalar, or np.nan to check.
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
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 )
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]
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()
195def fill_none(list1, list2):
196 return [b if a is None else a for a, b in zip(list1, list2)]