Coverage for nodimo / _internal.py: 100%

71 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-26 03:10 +0000

1# ┓• Licensed under the MIT License 

2# ┏┓┏┓┏┫┓┏┳┓┏┓ Copyright (c) 2024 Rodrigo Castro 

3# ┛┗┗┛┗┻┗┛┗┗┗┛ https://nodimo.readthedocs.io 

4 

5""" 

6Internal 

7======== 

8 

9This module contains variables and functions for internal use. 

10 

11Variables 

12--------- 

13_is_running_on_jupyter : bool 

14 If ``True``, the package in running on IPython/Jupyter. 

15 

16Functions 

17--------- 

18_custom_display(obj) 

19 Displays object using a custom CSS style. 

20_show_object(obj, use_custom_css=True) 

21 Prints object in shell. 

22_print_horizontal_line() 

23 Prints a horizontal line. 

24_sympify_number(number) 

25 Converts a number representation into a Sympy number. 

26_unsympify_number(number) 

27 Does the inverse of _sympify_number. 

28_prettify_name(name, bold=True) 

29 Wrapper for the Sympy function pretty_symbol. 

30_show_nodimo_warning(message) 

31 Displays a NodimoWarning message with custom format. 

32 

33Classes 

34------- 

35NodimoWarning 

36 (Custom) Nodimo warning 

37""" 

38 

39from sympy import pretty, Number, Rational, nsimplify, sympify 1abcdef

40from sympy.printing.pretty.pretty_symbology import pretty_symbol 1abcdef

41from typing import Union 1abcdef

42import warnings 1abcdef

43 

44 

45# Determine if Nodimo is running on IPython/Jupyter. 

46try: 1abcdef

47 from IPython import get_ipython 1abcdef

48 

49 _is_running_on_jupyter = True if get_ipython() is not None else False 1abcdef

50 if not _is_running_on_jupyter: 1abcdef

51 raise 1abcdef

52 from IPython.display import display, Markdown, HTML 1abcdef

53except: 1abcdef

54 _is_running_on_jupyter = False 1abcdef

55 

56 

57def _custom_display(obj): 1abcdef

58 """Displays object using a custom CSS style. 

59 

60 This custom display function was created to avoid vertical scrolling 

61 bars on the right of cells outputs, specially when running jupyter 

62 notebook in Google Chrome (issue #10 on github repository). 

63 

64 Parameters 

65 ---------- 

66 obj : Any 

67 The object to print. 

68 """ 

69 

70 css_style = '<style>.jp-OutputArea-output{overflow-y: hidden;}</style>' 1abcdef

71 

72 display(HTML(css_style + f'${obj._repr_latex_()}$')) 1abcdef

73 

74 

75def _show_object(obj, use_custom_css=True, use_unicode=True): 1abcdef

76 """Prints object in shell. 

77 

78 Parameters 

79 ---------- 

80 obj : Any 

81 The object to print. 

82 use_custom_css : bool, default=True 

83 If ``True``, the object is displayed using custom css. Only 

84 available in IPython/Jupyter. 

85 use_unicode : bool, default=False 

86 If ``True``, the object is displayed using unicode characters. 

87 Only available for pretty printing. 

88 """ 

89 

90 if _is_running_on_jupyter: 1abcdef

91 if use_custom_css: 1abcdef

92 _custom_display(obj) 1abcdef

93 else: 

94 display(obj) 1abcdef

95 else: 

96 print('\n' + pretty(obj, use_unicode=use_unicode) + '\n') 1abcdef

97 

98 

99def _print_horizontal_line(): 1abcdef

100 """Prints a horizontal line in shell.""" 

101 

102 if _is_running_on_jupyter: 1abcdef

103 display(Markdown('<hr>')) 1abcdef

104 else: 

105 print(78 * '-') 1abcdef

106 

107 

108def _sympify_number(number: Union[int, float, str, tuple, Number]) -> Number: 1abcdef

109 """Converts a number representation into a Sympy number. 

110 

111 This method is roughly a wrapper for Sympy.sympify, but it only 

112 accepts objects that can be transformed into a Sympy number. A 

113 workaround was implemented to allow the creation of rational numbers 

114 from tuples. 

115 

116 Parameters 

117 ---------- 

118 number : Union[int, float, str, tuple, Number] 

119 Anything that represents a number. 

120 

121 Returns 

122 ------- 

123 number_sp : Number 

124 The input number converted to a Sympy number. 

125 

126 Raises 

127 ------ 

128 ValueError 

129 If the input could not be converted into a Sympy number. 

130 

131 Examples 

132 -------- 

133 

134 >>> from nodimo._internal import _sympify_number as spnum 

135 >>> from sympy import Integer, Rational 

136 >>> spnum(5) == spnum(5.0) == spnum('5') == Integer(5) 

137 >>> spnum((2,3)) == spnum(2/3) == spnum('2/3') == Rational(2,3) 

138 """ 

139 

140 number_sp = sympify(number) 1abcdef

141 

142 if number_sp.is_Rational: 1abcdef

143 return number_sp 1abcdef

144 elif number_sp.is_Float and not isinstance(number, Number): 1abcdef

145 number_sp_rational = nsimplify(number_sp, rational=True) 1abcdef

146 denattr = number_sp_rational.denominator 1abcdef

147 # In older versions of sympy, denominator and numerator are 

148 # methods, while in current versions they are attributes. 

149 number_denominator = denattr if not callable(denattr) else denattr() 1abcdef

150 if number_denominator <= 100: 1abcdef

151 return number_sp_rational 1abcdef

152 else: 

153 return number_sp 1abcdef

154 elif isinstance(number, tuple) and len(number) in {1, 2}: 1abcdef

155 if all(obj.is_Number for obj in number_sp): 1abcdef

156 return Number(*number_sp) 1abcdef

157 elif number_sp.is_number: 1abcdef

158 return number_sp 1abcdef

159 

160 raise ValueError(f"{repr(number)} could not be converted to a Sympy number") 1abcdef

161 

162 

163def _unsympify_number(number_sp: Number) -> Union[int, float, str, tuple]: 1abcdef

164 """Does the inverse of _sympify_number. 

165 

166 Parameters 

167 ---------- 

168 number_sp : Number 

169 The Sympy number. 

170 

171 Returns 

172 ------- 

173 number : Union[int, float, str, tuple] 

174 Any expression that represents a number. 

175 

176 Raises 

177 ------ 

178 ValueError 

179 If the input is not a Sympy Number. 

180 """ 

181 

182 if not hasattr(number_sp, 'is_number') or not number_sp.is_number: 1abcdef

183 raise ValueError(f"{repr(number_sp)} is not a Sympy number") 1abcdef

184 elif number_sp.is_Integer: 1abcdef

185 return int(number_sp) 1abcdef

186 elif number_sp.is_Rational: 1abcdef

187 number_rat: Rational = Rational(number_sp) 1abcdef

188 numattr = number_rat.numerator 1abcdef

189 denattr = number_rat.denominator 1abcdef

190 # In older versions of sympy, denominator and numerator are 

191 # methods, while in current versions they are attributes. 

192 number_numerator = numattr if not callable(numattr) else numattr() 1abcdef

193 number_denominator = denattr if not callable(denattr) else denattr() 1abcdef

194 return (int(number_numerator), int(number_denominator)) 1abcdef

195 elif number_sp.is_Float: 1abcdef

196 return float(number_sp) 1abcdef

197 else: 

198 return str(number_sp) 1abcdef

199 

200 

201def _prettify_name(name: str, bold: bool = False): 1abcdef

202 """Wrapper for the Sympy function pretty_symbol. 

203 

204 This function was created to provide a better exception context for 

205 the Sympy function pretty_symbol. 

206 

207 Parameters 

208 ---------- 

209 name : str 

210 String to be prettified. 

211 bold : bool, default=False 

212 If ``True``, the string is converted to boldface. Only availabe 

213 for non-numeric values. 

214 

215 Returns 

216 ------- 

217 pretty_name : str 

218 Prettified string 

219 

220 Raises 

221 ------ 

222 ValueError 

223 If the input name is invalid. 

224 """ 

225 

226 try: 1abcdef

227 return pretty_symbol(name, bold_name=bold) 1abcdef

228 except: 1abcdef

229 raise ValueError(f"{repr(name)} is an invalid name") 1abcdef

230 

231 

232class NodimoWarning(Warning): 1abcdef

233 """(Custom) Nodimo warning. 

234 

235 Issued anytime Nodimo finds an inconsistency in the user's input, 

236 but one that can be handled without halting the execution. 

237 """ 

238 

239 pass 1abcdef

240 

241 

242def _nodimo_formatwarning(message, category, filename, lineno, line=None): 1abcdef

243 return f'\033[93m{category.__name__}\033[0m: {message}\n' 1abcdef

244 

245 

246def _show_nodimo_warning(message: str): 1abcdef

247 """Displays a NodimoWarning message with custom formattting. 

248 

249 Parameters 

250 ---------- 

251 message : str 

252 The message to be displayed in the warning. 

253 """ 

254 

255 original_formatwarning = warnings.formatwarning 1abcdef

256 warnings.formatwarning = _nodimo_formatwarning 1abcdef

257 warnings.warn(message, NodimoWarning) 1abcdef

258 warnings.formatwarning = original_formatwarning 1abcdef