1## 2# \file cadabra2_defaults.py 3# \ingroup pythoncore 4# Cadabra2 pure Python functionality. 5# 6# This is a pure-python initialisation script to set the path to 7# sympy and setup printing of Cadabra expressions. This script is 8# called both by the command line interface 'cadabra2' as well as by 9# the GUI backend server 'cadabra-server'. 10 11import sys 12import cadabra2 13from cadabra2 import * 14from importlib.machinery import PathFinder, ModuleSpec, SourceFileLoader 15from importlib.abc import MetaPathFinder 16from cdb_appdirs import user_config_dir, user_data_dir 17import datetime 18import atexit 19import rlcompleter 20 21__cdbkernel__=cadabra2.__cdbkernel__ 22__cdbkernel__.completer=rlcompleter.Completer() 23 24import os 25os.environ.setdefault('PATH', '') 26 27PY3 = sys.version_info[0] == 3 28if PY3: 29 unicode = str 30 31if "@ENABLE_JUPYTER@" == "OFF": 32 discr = "\\discretionary{}{}{} " 33else: 34 discr = "" 35 36class PackageCompiler(MetaPathFinder): 37 @classmethod 38 def find_module(cls, fullname, path): 39 return find_spec(fullname, path, None) 40 41 @classmethod 42 def find_spec(cls, fullname, path, target=None): 43 #log_("Finding {}, path=[{}]".format(fullname, ', '.join(f'"{p}"' for p in path) if path is not None else "")) 44 # Top-level import if path=None 45 if path is None or path == "": 46 path = sys.path 47 # Get unqualified package name 48 if '.' in fullname: 49 parents = fullname.split('.') 50 name = parents.pop() 51 else: 52 name = fullname 53 parents = [] 54 # Go through path and try to find a notebook. 55 for entry in path: 56 have_cnb = os.path.isfile(os.path.join(entry, name + ".cnb")) 57 have_cdb = os.path.isfile(os.path.join(entry, name + ".cdb")) 58 have_ipynb = os.path.isfile(os.path.join(entry, name + ".ipynb")) 59 if have_cnb or have_cdb or have_ipynb: 60 # Notebook was found. Create a version of it in the temporary directory, then 61 # return a ModuleSpec object to allow Python to load that file 62 pkg_path = os.path.join(user_config_dir(), "cadabra_packages", *parents) 63 # Create the path if it doesn't exist 64 if not os.path.exists(pkg_path): 65 os.makedirs(pkg_path) 66 if have_cnb: 67 compile_package__(os.path.join(entry, name + ".cnb"), os.path.join(pkg_path, name + ".py")) 68 elif have_cdb: 69 compile_package__(os.path.join(entry, name + ".cdb"), os.path.join(pkg_path, name + ".py")) 70 else: 71 compile_package__(os.path.join(entry, name + ".ipynb"), os.path.join(pkg_path, name + ".py")) 72 return ModuleSpec( 73 fullname, 74 SourceFileLoader(fullname, os.path.join(pkg_path, name + ".py")), 75 origin=os.path.join(pkg_path, name + ".py")) 76 77 # Return none if no notebook was found 78 return None 79 80# Prepend to sys.meta_path, so that all imports will first be checked in 81# case they are notebooks that need compiling 82sys.meta_path.insert(0, PackageCompiler) 83 84# Add current directory to Python module import path. 85sys.path.append(".") 86 87#sys.path.insert(0,'/home/kasper/Development/git.others/sympy') 88 89# Attempt to import sympy; if not, setup logic so that the 90# shell does not fail later. 91 92try: 93 import sympy 94except: 95 class Sympy: 96 """!@brief Stub object for when Sympy itself is not available. 97 98 @long When Sympy is not available, this object contains some basic 99 functionality to prevent things from breaking elsewhere. 100 """ 101 __version__="unavailable" 102 103 sympy = Sympy() 104 105if sympy.__version__ != "unavailable": 106 from sympy import factor 107 from sympy import integrate 108 from sympy import diff 109 from sympy import symbols 110 from sympy import latex 111 from sympy import sin, cos, tan, sqrt, trigsimp 112 from sympy import Matrix as sMatrix 113 114# Whether running in command-line mode or as client-server, there always 115# needs to be a Server object known as 'server' through which interaction 116# with the display routines is handled. The 'display' function will 117# call the 'server.send' method. 118 119if 'server' in globals(): 120 mopen="\\begin{dmath*}{}"; 121 mclose="\\end{dmath*}"; 122else: 123 mopen='' 124 mclose='' 125 class Server: 126 """!@brief Object to handle advanced display in a UI-independent way. 127 128 @long Cadabra makes available to Python a Server object, which 129 contains functions to send output to the user. When running 130 from the command line this simply prints to the screen, but it 131 can talk to a remote client to display images and maths. 132 """ 133 134 def send(self, data, typestr, parent_id, last_in_sequence): 135 """ Send a message to the client; 'typestr' indicates the cell type, 136 'parent_id', if non-null, indicates the serial number of the parent 137 cell. 138 """ 139 print(data) 140 return 0 141 142 def architecture(self): 143 return "terminal" 144 145 def test(self): 146 print("hello there!") 147 148 def handles(self, otype): 149 if(otype=="plain"): 150 return True 151 return False 152 153 def totals(self): 154 return __cdb_progress_monitor__.totals() 155 156 server = Server() 157 158# Import matplotlib and setup functions to prepare its output 159# for sending as base64 to the client. Example use: 160# 161# import matplotlib.pyplot as plt 162# p = plt.plot([1,2,3],[1,2,5],'-o') 163# display(p[0]) 164# 165 166have_matplotlib=True 167try: 168 import matplotlib 169 import matplotlib.artist 170 import matplotlib.figure 171 matplotlib.use('Agg') 172except ImportError: 173 have_matplotlib=False 174 175def save_history(history_path): 176 try: 177 readline.write_history_file(history_path) 178 except: 179 pass 180 181try: 182 import readline 183 history_path = os.path.join(user_data_dir(), "cadabra_history") 184 if os.path.exists(history_path): 185 readline.read_history_file(history_path) 186 readline.set_history_length(1000) 187 atexit.register(save_history, history_path) 188except: 189 pass 190 191import io 192import base64 193 194## @brief Generic display function which handles local as well as remote clients. 195# 196# The 'display' function is a replacement for 'str', in the sense that 197# it will generate human-readable output. However, in contrast to 198# 'str', it knows about what the front-end ('server') can display, and 199# will adapt the output to that. For instance, if 200# server.handles('latex_view') is true, it will generate LaTeX output, 201# while it will generate just plain text otherwise. 202# 203# Once it has figured out which display is accepted by 'server', it 204# will call server.send() with data depending on the object type it is 205# being fed. Data types the server object can support are: 206# 207# - "latex_view": text-mode LaTeX string. 208# - "image_png": base64 encoded png image. 209# - "verbatim": ascii string to be displayed verbatim. 210 211def display(obj, delay_send=False): 212 """ 213 Generalised 'print' function which knows how to display objects in the 214 best possible way on the used interface, be it a console or graphical 215 notebook. In particular, it knows how to display Cadabra expressions 216 in typeset form whenever LaTeX functionality is available. Can also be 217 used to display matplotlib plots. 218 219 When using a Cadabra front-end (command line or notebook), an expression 220 with a trailing semi-colon ';' will automatically be wrapped in a 221 'display' function call so that the expression is displayed immediately. 222 """ 223 if 'matplotlib' in sys.modules and isinstance(obj, matplotlib.figure.Figure): 224 imgstring = io.BytesIO() 225 obj.savefig(imgstring,format='png') 226 imgstring.seek(0) 227 b64 = base64.b64encode(imgstring.getvalue()) 228 server.send(b64, "image_png", 0, False) 229 # FIXME: Use the 'handles' query method on the Server object 230 # to figure out whether it can do something useful 231 # with a particular data type. 232 233 elif 'matplotlib' in sys.modules and isinstance(obj, matplotlib.artist.Artist): 234 f = obj.get_figure() 235 imgstring = io.BytesIO() 236 f.savefig(imgstring,format='png') 237 imgstring.seek(0) 238 b64 = base64.b64encode(imgstring.getvalue()) 239 server.send(b64, "image_png", 0, False) 240 241 elif hasattr(obj,'_backend'): 242 if hasattr(obj._backend,'fig'): 243 f = obj._backend.fig 244 imgstring = io.BytesIO() 245 f.savefig(imgstring,format='png') 246 imgstring.seek(0) 247 b64 = base64.b64encode(imgstring.getvalue()) 248 server.send(b64, "image_png", 0, False) 249 250 elif 'vtk' in sys.modules and isinstance(obj, vtk.vtkRenderer): 251 # Vtk renderer, see http://nbviewer.ipython.org/urls/bitbucket.org/somada141/pyscience/raw/master/20140917_RayTracing/Material/PythonRayTracingEarthSun.ipynb 252 pass 253 254# elif isinstance(obj, numpy.ndarray): 255# server.send("\\begin{dmath*}{}"+str(obj.to_list())+"\\end{dmath*}", "latex") 256 257 elif isinstance(obj, Ex): 258 if server.handles('latex_view'): 259 if delay_send: 260 return obj._latex_() 261 else: 262 ret = mopen+obj._latex_()+mclose 263 id=server.send(ret, "latex_view", 0, False) 264 # print(id) 265 # Make a child cell of the above with input form content. 266 server.send(obj.input_form(), "input_form", id, False) 267 else: 268 server.send(unicode(obj), "plain", 0, False) 269 270 elif isinstance(obj, Property): 271 if server.handles('latex_view'): 272 ret = mopen+obj._latex_()+mclose 273 if delay_send: 274 return ret 275 else: 276 server.send(ret , "latex_view", 0, False) 277 # Not yet available. 278 # server.send(obj.input_form(), "input_form", 0, False) 279 else: 280 server.send(unicode(obj), "plain", 0, False) 281 282 elif type(obj)==list: 283 out="{}$\\big[$" 284 first=True 285 for elm in obj: 286 if first==False: 287 out+=","+discr 288 else: 289 first=False 290 out+= "$"+display(elm, True)+"$" 291 out+="$\\big]$"; 292 server.send(out, "latex_view", 0, False) 293 # FIXME: send input_form version. 294 295 elif hasattr(obj, "__module__") and hasattr(obj.__module__, "find") and obj.__module__.find("sympy")!=-1: 296 if delay_send: 297 return latex(obj) 298 else: 299 server.send("\\begin{dmath*}{}"+latex(obj)+"\\end{dmath*}", "latex_view", 0, False) 300 301 else: 302 # Failing all else, just dump a str representation to the notebook, asking 303 # it to display this verbatim. 304 # server.send("\\begin{dmath*}{}"+str(obj)+"\\end{dmath*}", "latex") 305 if delay_send: 306 return "\\verb|"+str(obj)+"|" 307 else: 308 server.send(unicode(obj), "verbatim", 0, False) 309 310__cdbkernel__.server=server 311__cdbkernel__.display=display 312 313class Console(object): 314 """ 315 The interactive console works in the same Python context as 316 the notebook cells to allow evaluation of expressions for 317 debugging/logging purposes 318 """ 319 def log(self, obj): 320 """ 321 Sends a string representation of obj to the console 322 """ 323 if server.architecture() == "terminal": 324 print(text) 325 elif server.architecture() == "client-server": 326 server.send(unicode(obj), "csl_out", 0, False) 327 328 def clear(self): 329 """ 330 Clears the output of the console window 331 """ 332 if server.architecture() == "client-server": 333 server.send("", "csl_clear", 0, False) 334 335console = Console() 336 337# Set display hooks to catch certain objects and print them 338# differently. Should probably eventually be done cleaner. 339 340def _displayhook(arg): 341 global remember_display_hook 342 if isinstance(arg, Ex): 343 print(unicode(arg)) 344 elif isinstance(arg, Property): 345 print(unicode(arg)) 346 else: 347 remember_display_hook(arg) 348 349remember_display_hook = sys.displayhook 350sys.displayhook = _displayhook 351 352# Default post-processing algorithms. These are not pre-processed 353# so need to have the '__cdbkernel__' argument. 354 355def post_process(__cdbkernel__, ex): 356 collect_terms(ex) 357 358