class Plot(form.Form): """Plot functions""" functions = fields.ExpressionList("Functions", required=True) x = fields.Expression("Variable", default="x") x_min = fields.Expression("x min", default="-10") x_max = fields.Expression("x max", default="10") equal_aspect = fields.Boolean("Equal aspect", default=True) template = """ <div v-if="config.edit"> Plot `functions` for `x` between `x_min` and `x_max` </div> `plot` """ @fields.computed("Plot", field=fields.Html, nohide=True) @fields.figure def plot(self): """Use SymPy/matplotlib to generate an SVG graph""" data = (self.x, self.x_min, self.x_max) graph = sympy.plot(*self.functions, data, show=False, ylabel="y") colors = [False, 'red', 'green', 'orange'] for index in range(len(self.functions)): if index < len(colors) and colors[index]: graph[index].line_color = colors[index] backend = graph.backend(graph) backend.matplotlib.use("agg") backend.process_series() backend.fig.tight_layout() ax = backend.ax[0] if self.equal_aspect: ax.axis("tight") ax.set_aspect("equal") ax.grid(True)
class EulerMethod(form.Form): """Euler method""" template = """ <ul> <li><span class="katex">f(x, y) =</span> `function`</li> <li>Initial condition: `x0`, `y0`</li> <li><span class="katex">h = </span> `h`</li> <li><span class="katex">n = </span> `n`</li> <li v-if="config.edit">`midpoint`</li> </ul> `solution` """ function = fields.Expression("Function", required=True) x0 = fields.Expression("Initial condition (x)", required=True) y0 = fields.Expression("Initial condition (y)", required=True) h = fields.Expression("Step length", default="0.1") n = fields.Expression("Number of iterations", default="3") midpoint = fields.Boolean("Use midpoint method") @fields.computed("Solution") def solution(self): x, y, old_y = self.x0, self.y0, self.y0 for k in range(self.n): grad = self.function.subs({"x": x, "y": y}) if self.midpoint and k > 0: new_y = old_y + grad * 2 * self.h old_y, y = y, new_y else: y += grad * self.h x += self.h return (x.evalf(), y.evalf())
class Taylor(form.Form): """Taylor Expansion""" template = """ Taylor expansion of `expression` of order `order` <span v-if="config.edit || payload.x != 'x'"> with respect to `x` </span> around `x0`. <p v-if="config.edit"> `show_graph` Distance to `x0`: `h` </p> `taylor` <div v-if="payload.show_graph">`graph`</div> """ expression = fields.Expression("Expression") x = fields.Expression("x", default="x") order = fields.Expression("Order", default=6) x0 = fields.Expression("x0", default=0) show_graph = fields.Boolean("Show graph", default=False) h = fields.Expression("h", default="1") @fields.computed("Taylor expansion") def taylor(self): return sympy.series(self.expression, x=self.x, x0=self.x0, n=self.order + 1) @fields.computed("Plot", field=fields.Html, nohide=True) def graph(self): return plot.Plot( functions=[self.expression, self.taylor.removeO()], x=self.x, x_min=self.x0 - self.h, x_max=self.x0 + self.h, ).plot
class Equation(form.Form): """Equation""" name = fields.Field("Survey name") answer = fields.ExpressionList("Your answer", nosave=True, editable=True) equation = fields.Equation("Equation", required=True) x = fields.Expression("Solve for", default="x", required=True) template = """ Solve `equation` <span v-if="config.edit || payload.x !== 'x'">for `x`</span> <div v-if="!payload.marked_question">`solution`</div> <div v-else> <survey :config="config" :name="payload.name" :showStats="config.authState.loggedIn" :correct="computed.correct" :value="payload.answer"> Solution(s): `x` = `answer` </survey> </div> <div v-if="config.edit"> `show_graph` `marked_question` </div> <div v-if="payload.show_graph">`graph`</div> """ show_graph = fields.Boolean("Show graph", default=False) marked_question = fields.Boolean("Marked question", default=False) h = fields.Expression("h", default="3") @fields.computed("Correct", field=fields.Boolean) def correct(self): if not self.answer: return False # Prevent user from just copying the equation for sol in self.answer: if len(sol.atoms(sympy.Symbol)): return False solution = sympy.solveset(self.equation, self.x) sol_count = len(solution.args) return len(solution.intersect( sympy.FiniteSet(*self.answer)).args) >= sol_count @fields.computed("Solution") def solution(self): answer = sympy.solveset(self.equation, self.x) if answer.func == sympy.FiniteSet and len(answer.args) <= 3: answer = [ sympy.Eq(self.x, answer.args[i]) for i in range(len(answer.args)) ] return answer @fields.computed("Plot", field=fields.Html, nohide=True) def graph(self): if not self.show_graph: return "" args = {"functions": [self.equation.args[0], self.equation.args[1]]} if isinstance(self.solution, list) and len(self.solution) in [1, 2]: if len(self.solution) == 1: solution = self.solution[0].args[1] if len(self.solution) == 2: x1, x2 = self.solution[0].args[1], self.solution[1].args[1] solution = (x1 + x2) / 2 self.h = x2 - solution + 2 args.update({ "x_min": solution - self.h, "x_max": solution + self.h }) return plot.Plot(**args).plot def validate(self): if self.marked_question: if self.show_graph: self.show_graph = False if not self.name: self.name = str(uuid.uuid1())