├── __init__.py ├── README.md └── prettyPy.py /__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['pretty','prettyPrint','calc'] 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | prettyPy 2 | ======== 3 | 4 | This python module can be used with ipython notebook to write equations and expressions, as a string, in simple math syntax. This program wraps the string in LaTeX to beautify the result. There are two methods of implementation: (I) printing the resulting tag and (II) displaying the equation using the Ipython.display module. I look forward to integrating this module with LaTeX as well.
5 | 6 | -------------------------------------------------- 7 | 8 | Example: 9 | ```Python 10 | from prettyPy import prettyPy as pp 11 | pp.pretty('dy/dx = (y_2 - y_1)/(x_2 - x_1)') 12 | pp.prettyPrint('dy/dx = (y_2 - y_1)/(x_2 - x_1)') 13 | ``` 14 | -------------------------------------------------- 15 | 16 | Developer: Charlie Kawczynski
17 | Email: charliekawczynski@gmail.com
18 | Available @: https://github.com/charliekawczynski/prettyPy
19 | -------------------------------------------------------------------------------- /prettyPy.py: -------------------------------------------------------------------------------- 1 | from __future__ import division # Turn off integer division 2 | import re 3 | import numpy as np 4 | from itertools import * 5 | from operator import * 6 | from IPython.display import display, Math, Latex 7 | 8 | #------------------------------------------------- 9 | # Program: prettyPy 10 | # This program receives a string of some 11 | # equation and wraps it in LaTeX code, and 12 | # outputs the result to reduce the verbose 13 | # implementation of writing LaTeX equations. 14 | # Developer: Charlie Kawczynski 15 | # Email: charliekawczynski@gmail.com 16 | # Available @: charliekawczynski.com 17 | #------------------------------------------------- 18 | # Example: 19 | # from prettyPy import prettyPy as pp 20 | # a = 'b*cos(theta) + c**(d*sin(gamma))' 21 | # pp.prettyPrint(a) 22 | # print pp.pretty(a) + ' = ' + str(eval(a)) 23 | #------------------------------------------------- 24 | # Improvements to come: 25 | 26 | # -Remove replacement of * symbol to allow convolution expressions 27 | # and t^* superscripts.. 28 | 29 | # - allow for defining variables in a local file for later use 30 | # ex: setVars({ 31 | # "v_vec":"{\bf v_vec}", 32 | # "B":"{\bf B}", 33 | # }) 34 | 35 | # -Reorganize greek letters, symbols, and user defined variables, 36 | # make them into a list and replace by decreasing string size 37 | 38 | #------------------------------------------------- 39 | # General things to fix: 40 | # -try moving exponent of sin()**2 to sin**2() and delta(t)^2 to delta^2(t) 41 | 42 | def pretty(s): 43 | return markup(s) 44 | 45 | def prettyPrint(s): 46 | return display(Math(markup(s))) 47 | 48 | def defineVars(s): 49 | # Replace Greek Letters: 50 | s = replaceGreekLetter(s,'alpha') 51 | s = replaceGreekLetter(s,'Alpha') 52 | s = replaceGreekLetter(s,'beta') 53 | s = replaceGreekLetter(s,'Beta') 54 | s = replaceGreekLetter(s,'Delta') 55 | s = replaceGreekLetter(s,'delta') 56 | s = replaceGreekLetter(s,'eta') 57 | s = replaceGreekLetter(s,'epsilon') 58 | s = replaceGreekLetter(s,'gamma') 59 | s = replaceGreekLetter(s,'Gamma') 60 | s = replaceGreekLetter(s,'kappa') 61 | s = replaceGreekLetter(s,'lambda') 62 | s = replaceGreekLetter(s,'nabla') 63 | s = replaceGreekLetter(s,'mu') 64 | s = replaceGreekLetter(s,'nu') 65 | s = replaceGreekLetter(s,'omega') 66 | s = replaceGreekLetter(s,'Omega') 67 | s = replaceGreekLetter(s,'partial') 68 | s = replaceGreekLetter(s,'pi') 69 | s = replaceGreekLetter(s,'Pi') 70 | s = replaceGreekLetter(s,'psi') 71 | s = replaceGreekLetter(s,'Psi') 72 | s = replaceGreekLetter(s,'Phi') 73 | s = replaceGreekLetter(s,'sigma') 74 | s = replaceGreekLetter(s,'Sigma') 75 | s = replaceGreekLetter(s,'tau') 76 | s = replaceGreekLetter(s,'Tau') 77 | s = replaceGreekLetter(s,'theta') 78 | s = replaceGreekLetter(s,'Theta') 79 | s = replaceGreekLetter(s,'varphi') 80 | s = replaceGreekLetter(s,'varepsilon') 81 | s = replaceGreekLetter(s,'xi') 82 | s = replaceGreekLetter(s,'zeta') 83 | # Define Symbols and Characters: 84 | s = replaceGreekLetter(s,'max') 85 | s = replaceGreekLetter(s,'min') 86 | s = replaceGreekLetter(s,'sum') 87 | s = replaceGreekLetter(s,'det') 88 | s = replaceGreekLetter(s,'iiint') 89 | s = replaceGreekLetter(s,'iint') 90 | s = replaceGreekLetter(s,'int') 91 | s = replaceGreekLetter(s,'infty') 92 | s = userDefinedVars(s,'cross','\\times') 93 | s = userDefinedVars(s,'dot','\\bullet') 94 | s = userDefinedVars(s,'infinity','\\infty') 95 | s = userDefinedVars(s,'order','\\cal O') # This is the script 'order' symbol 96 | # Define Truncated Symbols and Characters (if truncation can be found in original): 97 | s = userDefinedRecursiveVars(s,'approx','\\approx') 98 | s = userDefinedRecursiveVars(s,'eps','\\epsilon') 99 | s = userDefinedRecursiveVars(s,'sig','\\sigma') 100 | s = userDefinedRecursiveVars(s,'ne','\\ne') 101 | s = userDefinedRecursiveVars(s,'Sig','\\Sigma') 102 | # Define Personal Variables: 103 | s = userDefinedVars(s,'pd','\\partial') 104 | return s 105 | 106 | def functions(s): 107 | s = g_of_x(s,'sqrt(') 108 | 109 | s = g_of_x(s,'sin(') 110 | s = g_of_x(s,'cos(') 111 | s = g_of_x(s,'tan(') 112 | 113 | s = g_of_x(s,'sinh(') 114 | s = g_of_x(s,'cosh(') 115 | s = g_of_x(s,'tanh(') 116 | 117 | s = g_of_x(s,'asin(') 118 | s = g_of_x(s,'sin^{-1}(') 119 | s = g_of_x(s,'acos(') 120 | s = g_of_x(s,'cos^{-1}(') 121 | s = g_of_x(s,'atan(') 122 | s = g_of_x(s,'tan^{-1}(') 123 | 124 | s = g_of_x(s,'csc(') 125 | s = g_of_x(s,'sec(') 126 | s = g_of_x(s,'cot(') 127 | return s 128 | 129 | def userDefinedVars(s,L,Lnew): 130 | r = 0 131 | for k in range(0,s.count(L)): 132 | loc = find_nth(s,L,k+1+r) 133 | if loc == 0: 134 | TF = not re.match(r'[A-Za-z0-9]',s[loc+len(L)]) 135 | elif loc == len(s) - len(L): 136 | TF = not re.match(r'[A-Za-z0-9]',s[loc-1]) 137 | else: 138 | TF = not re.match(r'[A-Za-z0-9]',s[loc+len(L)]) and not re.match(r'[A-Za-z0-9]',s[loc-1]) 139 | if TF: 140 | s_before = s[0:loc] 141 | s_after = s[loc+len(L):] 142 | s = s_before + Lnew + s_after 143 | r = r-1 144 | return s 145 | 146 | def userDefinedRecursiveVars(s,L,Lnew): 147 | for k in range(0,s.count(L)): 148 | loc = find_nth(s,L,k+1) 149 | if loc == 0: 150 | TF = not re.match(r'[A-Za-z0-9]',s[loc+len(L)]) 151 | elif loc == len(s) - len(L): 152 | TF = not re.match(r'[A-Za-z0-9]',s[loc-1]) 153 | else: 154 | TF = not re.match(r'[A-Za-z0-9]',s[loc+len(L)]) and not re.match(r'[A-Za-z0-9]',s[loc-1]) 155 | if TF: 156 | s_before = s[0:loc] 157 | s_after = s[loc+len(L):] 158 | s = s_before + Lnew + s_after 159 | return s 160 | 161 | def replaceGreekLetter(s,L): 162 | Lnew = '\\' + L 163 | for k in range(0,s.count(L)): 164 | loc = find_nth(s,L,k+1) 165 | if loc == 0: 166 | TF = not re.match(r'[A-Za-z0-9]',s[loc+len(L)]) 167 | elif loc == len(s) - len(L): 168 | TF = not re.match(r'[A-Za-z0-9]',s[loc-1]) 169 | else: 170 | TF = not re.match(r'[A-Za-z0-9]',s[loc+len(L)]) and not re.match(r'[A-Za-z0-9]',s[loc-1]) 171 | if TF: 172 | s_before = s[0:loc] 173 | s_after = s[loc+len(L):] 174 | s = s_before + Lnew + s_after 175 | return s 176 | 177 | def format_res(n,units): 178 | if abs(n) < 1000 and abs(n) > 1/1000: 179 | if units.replace(' ','') == '': 180 | res = str('%.3f' % n) 181 | else: 182 | res = str('%.3f' % n) + ' [' + units + ']' 183 | return res 184 | else: 185 | a = '%.3E' % n 186 | n_formatted = a.split('E')[0].rstrip('0').rstrip('.') + 'E' + a.split('E')[1] 187 | if units.replace(' ','') == '': 188 | res = n_formatted 189 | else: 190 | res = n_formatted + ' [' + units + ']' 191 | return res 192 | 193 | def markup(s): 194 | s = impliedMultiplication(s) 195 | s = s.replace(' ','') 196 | s = s.replace("**","^") 197 | s = superscriptsWithParen(s) 198 | s = subscripts(s) 199 | s = superscriptsWithoutParen(s) 200 | s = fractions(s) 201 | s = functions(s) 202 | s = defineVars(s) 203 | s = removeExcessParenthesis(s) 204 | if not s.count('{') == s.count('}'): 205 | print 'UNEQUAL NUMBER OF CURLY BRACES' 206 | if not s.count('[') == s.count(']'): 207 | print 'UNEQUAL NUMBER OF BRACKETS' 208 | if not s.count('(') == s.count(')'): 209 | print 'UNEQUAL NUMBER OF PARENTHASES' 210 | 211 | s = s.replace('(',' \\left( ') 212 | s = s.replace(')',' \\right) ') 213 | s = s.replace('[',' \\left[ ') 214 | s = s.replace(']',' \\right] ') 215 | s = s.replace('*',' ') 216 | s = s.replace('&','&&, \\qquad \\qquad') 217 | s = '\\begin{align}' + s + '\\end{align}' 218 | s = '$$' + s + '$$' 219 | return s 220 | 221 | 222 | def consecutiveLists(data): 223 | d = {}; i = 0 224 | for k, g in groupby(enumerate(data), lambda (i,x):i-x): 225 | d[i] = map(itemgetter(1), g) 226 | i = i + 1 227 | return d 228 | 229 | def step(s,openDelim,closeDelim): 230 | p = np.zeros(len(s)) 231 | for i in range(len(s)-1): 232 | if s[i] == openDelim: 233 | p[i:] = p[i:] + 1 234 | elif s[i] == closeDelim: 235 | p[i+1:] = p[i+1:] - 1 236 | return p 237 | 238 | def replaceLevelJ(s,p): 239 | try: 240 | if p == 0: 241 | p = step(s,'(',')') 242 | except:pass 243 | t = np.where(p == max(p)) 244 | cl = consecutiveLists(t[0]) 245 | s_new = s 246 | for i in cl: 247 | s_prefix = s_new[cl[i][0]-1] 248 | s_before = s_new[0:cl[i][0]] 249 | s_after = s_new[cl[i][-1]+1:] 250 | s_cut = s_new[cl[i][0]:cl[i][-1]+1] 251 | if s_prefix == '^': 252 | s_mid = '{' + s_cut[1:-1] + '}' 253 | s_new = s_before + s_mid + s_after 254 | for j in cl[i]: 255 | p[j] = p[j] - 1 256 | 257 | return (p,s_new) 258 | 259 | def superscriptsWithParen(s): 260 | n = s.count('(') + s.count('_') 261 | (p,s_new) = replaceLevelJ(s,0) 262 | for j in range(n-1,0,-1): 263 | s_temp = s_new 264 | (p,s_new) = replaceLevelJ(s_temp,p) 265 | if max(p) == 0: 266 | break 267 | return s_new 268 | 269 | def subscripts(s): 270 | r = re.compile(r'_[^\}\(\)\]\=\+\-\^\/\*]*') 271 | m = list(set(r.findall(s))) 272 | m.sort(lambda x,y: cmp(len(x), len(y)), reverse=True) 273 | for j in m: 274 | s_mid = j[0] + '{' + j[1:] + '}' 275 | s = s.replace(j,s_mid) 276 | return s 277 | 278 | def superscriptsWithoutParen(s): 279 | r = re.compile(r'\^[^\{]{1}[^\*\)\+\=\-\/]+') 280 | la = list(set(r.findall(s))) 281 | for i in la: 282 | if i[0:1] == '^{': # .startswith('^{') 283 | la.remove(i) 284 | for j in range(0,len(la)): 285 | if not la[j].count('{') == la[j].count('}'): 286 | temp_la = la[j][0:-1] 287 | la.pop(j) 288 | la.insert(j,temp_la) 289 | for j in la: 290 | k = s.index(j) 291 | s_before = s[0:k+1] 292 | s_mid = '{' + j[1:] + '}' 293 | s_after = s[k+len(j):] 294 | s = s_before + s_mid + s_after 295 | return s 296 | 297 | def findnth(haystack, needle, n): 298 | parts= haystack.split(needle, n+1) 299 | if len(parts)<=n+1: 300 | return -1 301 | return len(haystack)-len(parts[-1])-len(needle) 302 | 303 | def find_nth(haystack, needle, n): 304 | start = haystack.find(needle) 305 | while start >= 0 and n > 1: 306 | start = haystack.find(needle, start+len(needle)) 307 | n -= 1 308 | return start 309 | 310 | def impliedMultiplication(s): 311 | for k in range(0,s.count(' ')+1): 312 | f = find_nth(s,' ',k) 313 | t_left = ('+','/','-','(','[','=','*') 314 | t_right = ('+','/','-',')',']','=','*') 315 | TFL = not s[0:f].strip(' ').endswith(t_left) 316 | TFR = not s[f+1:].strip(' ').startswith(t_right) 317 | if all([TFL,TFR]): 318 | s = s[0:f] + '* ' + s[f+1:] 319 | return s 320 | 321 | def fractions(s): 322 | # any case 323 | t_left = ['+','-','/','*','\n','[','{','(','='] 324 | t_right = ['+','-','/','*','\n',']','}',')','='] 325 | r = 0 326 | for j in range(0,s.count('/')): 327 | L = find_nth(s,'/',j+1+r) 328 | p = step(s,'(',')') 329 | pcb = step(s,'{','}') 330 | p_0 = p[L] 331 | pcb_0 = pcb[L] 332 | # find ending to left 333 | for k in range(L-1,0,-1): 334 | if p[k] == p_0 and pcb[k] == pcb_0 and s[k] in t_left: 335 | l0 = k+1 336 | break 337 | try:l0 338 | except:l0 = 0 339 | # find ending to right 340 | for k in range(L+1,len(s)): 341 | if p[k] == p_0 and pcb[k] == pcb_0 and s[k] in t_right: 342 | r0 = k 343 | break 344 | try:r0 345 | except:r0 = len(s) 346 | if r0 == None: 347 | r0 = len(s) 348 | s_num = s[l0:L] 349 | s_denom = s[L+1:r0] 350 | s_mid = '\\frac{' + s_num + '}{' + s_denom + '}' 351 | p_any = step(s,'(',')') + step(s,'[',']') + step(s,'{','}') 352 | # The exponent in the numerator and denominator 353 | # will be of the form, e.g, 2/3 when the length 354 | # of the numerator and denominator are less than n. 355 | # One exception to this is when the fraction is not 356 | # contained in parentheses 357 | n = 1 358 | if len(s_num) > n or len(s_denom) > n or max(p_any[l0:r0]) == 0: 359 | if s_denom.startswith('(') and s_denom.endswith(')') and s_num.startswith('(') and s_num.endswith(')'): 360 | s_numNew = s_num[1:-1] 361 | s_denomNew = s_denom[1:-1] 362 | s_mid = '\\frac{' + s_numNew + '}{' + s_denomNew + '}' 363 | elif s_denom.startswith('(') and s_denom.endswith(')'): 364 | s_denomNew = s_denom[1:-1] 365 | s_mid = '\\frac{' + s_num + '}{' + s_denomNew + '}' 366 | elif s_num.startswith('(') and s_num.endswith(')'): 367 | s_numNew = s_num[1:-1] 368 | s_mid = '\\frac{' + s_numNew + '}{' + s_denom + '}' 369 | s = s[0:l0] + s_mid + s[r0:] 370 | r = r-1 371 | r0 = None;l0 = None; 372 | return s 373 | 374 | def g_of_x(s,needle): 375 | if needle == 'sqrt(': 376 | removeParen = True 377 | else: 378 | removeParen = False 379 | t_right = ['+','-','/','*','\n','}',')','='] 380 | r = 0 381 | for j in range(0,s.count(needle)): 382 | L = find_nth(s,needle,j+1+r) 383 | n_len = len(needle) 384 | p = step(s,'(',')') 385 | pcb = step(s,'{','}') 386 | p_0 = p[L] 387 | pcb_0 = pcb[L] 388 | # find ending to right 389 | for k in range(L+n_len,len(s)): 390 | if p[k] == p_0 and pcb[k] == pcb_0 and s[k] in t_right: 391 | r0 = k 392 | break 393 | try:r0 394 | except:r0 = len(s) 395 | if removeParen: 396 | s_termInside = s[n_len + L:r0-1] 397 | s_old = needle[0:-1] + '(' + s_termInside + ')' 398 | r = r - 1 399 | else: 400 | s_termInside = s[n_len + L-1:r0] 401 | s_old = needle[0:-1] + s_termInside 402 | s_new = '\\' + needle[0:-1] + '{' + s_termInside + '}' 403 | s = s.replace(s_old,s_new) 404 | r0 = None 405 | return s 406 | 407 | def removeExcessParenthesis(s): 408 | return s 409 | def notInsideFrac(s): 410 | p = step(s,'\\frac{','}') 411 | TF = max(p) == 0 412 | return TF 413 | --------------------------------------------------------------------------------