tfw math homework is too much work. this only took me about twice the time it would take to do the homework by hand
---
an automated nonhomogenous linear ODE solver which uses the method of undetermined coefficients. the best part is that it outputs a latex file showing each step. includes batch processing
attached is the script, an example latex output, and the latex rendered as a pdf
from sympy import *
from copy import deepcopy
def all_terms(polyn, rcoeff = False):
polyn = deepcopy(polyn)
coeffs = []
terms = []
while polyn:
fam, coeff = polyn.LT()
famexpr = fam.as_expr()
if famexpr == 1:
fam = poly(x+1)-poly(x) #1
else:
fam = poly(fam.as_expr())
terms.append(fam)
coeffs.append(coeff)
polyn = polyn - fam*coeff
if rcoeff:
return zip(terms, coeffs)
else:
return terms
def csub(pexp):
pexp = deepcopy(pexp)
for i in range(mc*2):
pexp = pexp.subs('C'+str(i), 1)
return pexp
def MOUC(problem, rlatex = True):
latextext = []
#print the problem
latextext.append(latex(problem))
latextext.append(latex("\\line(1,0){450}"))
yc = Eq(problem.lhs, 0)
latextext.append('y_c : ' + latex(yc))
#print yc
cp = yc
for i, ndy in reversed(list(enumerate(dys))):
cp = cp.subs(ndy, r**i)
cp = cp.lhs
latextext.append(latex(cp))
cproots = []
cpr = roots(Poly(cp))
for k, v in cpr.items():
cproots.append([k]*v)
#print roots (imaginary!?)
latextext.append('r : ' + ', '.join([latex(cproot) for cproot in cproots]))
latextext.append(latex("\\line(1,0){450}"))
ycsol = dsolve(yc.subs(y, y(x)))
latextext.append(latex(ycsol))
#print ycsol
yfams = []
ypf = poly(problem.rhs)
yfams = all_terms(ypf)
dyfams = [yfam.as_expr() for yfam in yfams]
for yfam in dyfams:
done = True
while done:
if type(yfam) in [type(S(val)) for val in xrange(-1,3)]:
break
dyfam = poly(diff(yfam, x))
dyfammons = all_terms(dyfam)
for dyfammon in dyfammons:
dyfammon = dyfammon.as_expr()
if dyfammon in dyfams:
done = False
else:
dyfams.append(dyfammon)
dyfam = dyfammon
dyfams1 = dyfams[:]
yre = ycsol.rhs.expand()
yre = csub(yre)
rhsfams = [term.as_expr() for term in all_terms(poly(yre))]
for i, dyfam in enumerate(dyfams):
while dyfams[i] in rhsfams:
dyfams[i] *= x
while dyfams.count(dyfams[i]) > 1:
dyfams[i] *= x
#print promoted dyfams... if different
for i, dyfam in enumerate(dyfams):
if dyfam == dyfams1[i]:
latextext.append(latex(dyfam))
else:
latextext.append(latex(dyfams1[i]) + ' \\rightarrow ' + latex(dyfam))
Cs = [symbols('C'+str(i)) for i in range(3, 3+mc2)]
yp = 0
for i, term in enumerate(dyfams):
yp += Cs[i]*term
dyps = [yp]
for i in xrange(1, md+1):
dyps.append(diff(dyps[-1], x))
#print each of the dyps
latextext.append(latex("\\line(1,0){450}"))
name = "y_p"
for i in xrange(poly(cp).degree()+1):
latextext.append(name + '=' + latex(dyps[i]))
name += "'"
adds = []
TCs = all_terms(poly(cp), True)
for T, C in TCs:
adds.append(C*dyps[T.degree()])
problemlhs = Add(*adds, evaluate=False)
latextext.append(latex("\\line(1,0){450}"))
ypbs = Eq(problemlhs, problem.rhs)
ypas = Eq(problemlhs.collect(x), problem.rhs)
#print unsimplified and simplified particular
latextext.append(latex(ypbs))
latextext.append(latex(ypas))
eqs = {key: Eq(0,0, evaluate = False) for key in rhsfams+dyfams}
for term, coeff in ypas.lhs.as_coefficients_dict().items():
key = csub(term)
cl, cr = eqs[key].lhs, eqs[key].rhs
eqs[key] = Eq(cl+coeff*term, cr)
for term, coeff in ypas.rhs.as_coefficients_dict().items():
key = csub(term)
cl, cr = eqs[key].lhs, eqs[key].rhs
eqs[key] = Eq(cl, coeff*term+cr)
for k, v in eqs.items():
cl, cr = v.lhs, v.rhs
eqs[k] = Eq((cl/k).simplify(), (cr/k).simplify())
tosolve = []
for k, v in eqs.items():
if v is not S(True):
tosolve.append(v)
#print tosolve
latextext.append(latex("\\line(1,0){450}"))
for eq in tosolve:
latextext.append(latex(eq))
ucs = Cs[:len(tosolve)]
Cps = {key:val for (key, val) in zip(ucs, solve_poly_system(tosolve, ucs)[0])}
#print Cps
for k, v in Cps.items():
latextext.append(latex(k) + ' = ' + latex(v))
ypsol = yp.xreplace(Cps)
#print ypsol
latextext.append(latex("\\line(1,0){450}"))
latextext.append('y_p = ' +latex(ypsol))
gsol = Eq(y, ycsol.rhs + ypsol)
#print gsol
latextext.append(latex(gsol))
if rlatex:
return gsol, latextext
else:
return gsol
def format(latexlines):
start = "\\begin{dmath*}"
end = "\\end{dmath*}"
nl = "\n"
return nl.join([start + nl + line + nl + end for line in latexlines])
def batchMOUC(problemdict, output):
fulltext = ["\\documentclass[fleqn, 12pt]{article}\n\\usepackage{breqn}\n\\usepackage[cm]{fullpage}\n\\setlength{\\mathindent}{0pt}\n\\begin{document}"]
#head, tail, and section
for i, v in problemdict.items():
_, latext = MOUC(v)
fulltext.append('\\section*{'+str(i)+'}')
fulltext.append(format(latext))
fulltext.append("\\end{document}")
with open(output, 'w') as ouf:
ouf.write('\n'.join(fulltext))
init_printing()
md = mc = mc2 = 5
x, y, r = symbols('x y r')
dys = [y]
for i in xrange(1, md+1):
dys.append(Derivative(dys[-1], x))
problems = {
9: Eq(dys[2] + 2*dys[1] - 3*y, 1+x*exp(x)),
10: Eq(dys[2] + 9*y, 2*cos(3*x) + 3*sin(3*x)),
13: Eq(dys[2] + 2*dys[1] + 5*y, exp(x)*sin(x))
}
batchMOUC(problems, 'output.latex')