Coverage for nodimo / _internal.py: 100%
71 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-26 03:10 +0000
« 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
5"""
6Internal
7========
9This module contains variables and functions for internal use.
11Variables
12---------
13_is_running_on_jupyter : bool
14 If ``True``, the package in running on IPython/Jupyter.
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.
33Classes
34-------
35NodimoWarning
36 (Custom) Nodimo warning
37"""
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
45# Determine if Nodimo is running on IPython/Jupyter.
46try: 1abcdef
47 from IPython import get_ipython 1abcdef
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
57def _custom_display(obj): 1abcdef
58 """Displays object using a custom CSS style.
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).
64 Parameters
65 ----------
66 obj : Any
67 The object to print.
68 """
70 css_style = '<style>.jp-OutputArea-output{overflow-y: hidden;}</style>' 1abcdef
72 display(HTML(css_style + f'${obj._repr_latex_()}$')) 1abcdef
75def _show_object(obj, use_custom_css=True, use_unicode=True): 1abcdef
76 """Prints object in shell.
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 """
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
99def _print_horizontal_line(): 1abcdef
100 """Prints a horizontal line in shell."""
102 if _is_running_on_jupyter: 1abcdef
103 display(Markdown('<hr>')) 1abcdef
104 else:
105 print(78 * '-') 1abcdef
108def _sympify_number(number: Union[int, float, str, tuple, Number]) -> Number: 1abcdef
109 """Converts a number representation into a Sympy number.
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.
116 Parameters
117 ----------
118 number : Union[int, float, str, tuple, Number]
119 Anything that represents a number.
121 Returns
122 -------
123 number_sp : Number
124 The input number converted to a Sympy number.
126 Raises
127 ------
128 ValueError
129 If the input could not be converted into a Sympy number.
131 Examples
132 --------
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 """
140 number_sp = sympify(number) 1abcdef
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
160 raise ValueError(f"{repr(number)} could not be converted to a Sympy number") 1abcdef
163def _unsympify_number(number_sp: Number) -> Union[int, float, str, tuple]: 1abcdef
164 """Does the inverse of _sympify_number.
166 Parameters
167 ----------
168 number_sp : Number
169 The Sympy number.
171 Returns
172 -------
173 number : Union[int, float, str, tuple]
174 Any expression that represents a number.
176 Raises
177 ------
178 ValueError
179 If the input is not a Sympy Number.
180 """
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
201def _prettify_name(name: str, bold: bool = False): 1abcdef
202 """Wrapper for the Sympy function pretty_symbol.
204 This function was created to provide a better exception context for
205 the Sympy function pretty_symbol.
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.
215 Returns
216 -------
217 pretty_name : str
218 Prettified string
220 Raises
221 ------
222 ValueError
223 If the input name is invalid.
224 """
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
232class NodimoWarning(Warning): 1abcdef
233 """(Custom) Nodimo warning.
235 Issued anytime Nodimo finds an inconsistency in the user's input,
236 but one that can be handled without halting the execution.
237 """
239 pass 1abcdef
242def _nodimo_formatwarning(message, category, filename, lineno, line=None): 1abcdef
243 return f'\033[93m{category.__name__}\033[0m: {message}\n' 1abcdef
246def _show_nodimo_warning(message: str): 1abcdef
247 """Displays a NodimoWarning message with custom formattting.
249 Parameters
250 ----------
251 message : str
252 The message to be displayed in the warning.
253 """
255 original_formatwarning = warnings.formatwarning 1abcdef
256 warnings.formatwarning = _nodimo_formatwarning 1abcdef
257 warnings.warn(message, NodimoWarning) 1abcdef
258 warnings.formatwarning = original_formatwarning 1abcdef