├── __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 |
--------------------------------------------------------------------------------