import {BoxedExpression, BoxedRule, BoxedSubstitution} from "@holgerengels/compute-engine"; import {assert, renderBoxed, latexOptions, renderLatex} from "./util"; import {html, TemplateResult} from "lit"; import {boxed_0, ce, Equation, Operation} from "./model"; import katex from "katex"; window.katex = katex; const ADD: Operation = { name: "add", title: "`+`", help: "Äquivalenzumformung: Auf beiden Seiten den Ausdruck addieren", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); return [{ variable: e.variable, left: ce.box(["Add", e.left, arg!]).simplify(), right: ce.box(["Add", e.right, arg!]).simplify() }] }, render: (arg?: BoxedExpression): TemplateResult => { return arg && arg.isNegative ? html`|  ${renderBoxed(arg!)}` : html`|  + ${renderBoxed(arg!)}` } }; const SUBTRACT: Operation = { name: "subtract", title: "`−`", help: "Äquivalenzumformung: Auf beiden Seiten den Ausdruck subtrahieren", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); return [{ variable: e.variable, left: ce.box(["Subtract", e.left, arg!]).simplify(), right: ce.box(["Subtract", e.right, arg!]).simplify() }] }, render: (arg?: BoxedExpression): TemplateResult => { return arg && arg.isNegative ? html`|  + ${renderBoxed(ce.box(["Negate", arg!]))}` : html`|  − ${renderBoxed(arg!)}` } }; const MULTIPLY: Operation = { name: "multiply", title: "`\\cdot`", help: "Äquivalenzumformung: Beide Seiten mit dem Ausdruck multiplizieren", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); if (arg!.isZero) return error(e, "Beide Seiten mit 0 multiplizieren ist nicht erlaubt!"); let sols = ce.box(["Equal", arg!, 0]).solve("x"); if (sols && sols?.length != 0) return error(e, "Multiplizieren mit einen Term, der 0 werden kann, ist nicht erlaubt!"); else return [{ variable: e.variable, left: ce.box(["Multiply", e.left, arg!]).simplify(), right: ce.box(["Multiply", e.right, arg!]).simplify() }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  · ${renderBoxed(arg!)}` } }; const DIVIDE: Operation = { name: "divide", title: "`:`", help: "Äquivalenzumformung: Auf beiden Seiten durch den Ausdruck dividieren", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); if (arg!.isZero) return error(e, "Durch 0 teilen ist nicht definiert!"); let sols = ce.box(["Equal", arg!, 0]).solve("x"); if (sols && sols?.length != 0) return error(e, "Teilen durch einen Term, der 0 werden kann, ist nicht erlaubt!"); else return [{ variable: e.variable, left: ce.box(["Divide", e.left, arg!]).simplify(), right: ce.box(["Divide", e.right, arg!]).simplify() }]; }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  : ${renderBoxed(arg!)}` } }; const SQRT: Operation = { name: "sqrt", title: "`\\sqrt{\\text{ }}`", help: "Äquivalenzumformung: Auf beiden Seiten die Wurzelfunktion anwenden", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); const left = ce.box(["Sqrt", e.left]).simplify(); const right = ce.box(["Sqrt", e.right]).simplify(); if (left.N().isImaginary || right.N().isImaginary) return error(e, "In ℝ ist die Wurzel einer negativen Zahl nicht definiert!"); let result: Equation[] = [{ variable: e.variable, left: left, right: right.simplify(), }]; if (!e.right.isZero) result.push({ variable: e.variable, left: left, right: ce.box(["Negate", right]).simplify(), }) return result; }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  ${renderLatex("\\sqrt{}")}` } }; const ROOT: Operation = { name: "root", title: "`\\sqrt[n]{\\text{ }}`", help: "Äquivalenzumformung: Auf beiden Seiten die n-te Wurzel ziehen", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); const left = ce.box(["Root", e.left, arg!]).simplify(); const right = ce.box(["Root", e.right, arg!]).simplify(); if (left.N().isImaginary || right.N().isImaginary) return error(e, "In ℝ ist die gerade Wurzel einer negativen Zahl nicht definiert!"); let result: Equation[] = [{ variable: e.variable, left: left, right: right.simplify(), }]; if (Math.round(arg!.numericValue as number / 2) === arg!.numericValue as number / 2) result.push({ variable: e.variable, left: left, right: ce.box(["Negate", right]).simplify(), }); return result; }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  ${renderBoxed(ce.box(["Root", ce.parse("\\text{}"), arg!]))}` } }; const SQUARE: Operation = { name: "square", title: "`^2`", help: "Äquivalenzumformung: Beide Seiten quadrieren", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); const left = ce.box(["SQUARE", e.left]).simplify(); const right = ce.box(["SQUARE", e.right]).simplify(); return [{ variable: e.variable, left: left, right: right, }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  ${renderLatex("^2")}` } }; const LN: Operation = { name: "ln", title: "ln", help: "Äquivalenzumformung: Auf beiden Seiten die Logarithmusfunktion anwenden", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); const left = ce.box(["Ln", e.left]).simplify(); const right = ce.box(["Ln", e.right]).simplify(); return left.N().isImaginary || right.N().isImaginary || left.isNaN || right.isNaN ? error(e, "In ℝ ist der Logarithmus nur für positive Zahlen definiert!") : [{ variable: e.variable, left: left, right: right, }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  ${renderLatex("\\ln")}` } }; async function lazyJsxGraph() { if (!customElements.get('kmap-jsxgraph')) { customElements.define('kmap-jsxgraph', (await import('kmap-jsxgraph')).KmapJsxGraph); } } function graph(f: string, y: number, sol1: BoxedExpression, sol2: BoxedExpression, p: BoxedExpression) { const l1 = sol1.toLatex(latexOptions).replace(/\\/g, '\\\\'); const l2 = sol2.toLatex(latexOptions).replace(/\\/g, '\\\\'); return ` `; } const ARCSIN: Operation = { name: "arcsin", title: "`\sin^{-1}`", help: "Äquivalenzumformung: Auf beiden Seiten die Arkussinusfunktion anwenden", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); await lazyJsxGraph(); if ("Sin" !== e.left.operator) return error(e, "Auf der linken Seite muss die Sinusfunktion zu oberst stehen!"); const left = e.left.ops![0]; const sol1 = ce.box(["Arcsin", e.right]).simplify(); let message; if (left.operator !== "Symbol") { message = "Es empfiehlt sich, zunächst das Argument des Sinus mit u zu substituieren. Dann lässt sich die zweite Lösung der Periode einfacher bestimmen."; return [{ variable: e.variable, left: left, right: sol1, message: message, }] } else { let sol2 = sol1.N().isNegative ? ce.box(["Subtract", ["Negate", ce.parse("\\pi")], sol1]).simplify() : ce.box(["Subtract", ce.parse("\\pi"), sol1]).simplify(); let f = "Math.sin(x)" let p = sol1.N().isNegative ? ce.parse("-\\pi") : ce.parse("\\pi"); message="
" + graph(f, e.right.N().value as number, sol1, sol2, p) + "Pro Periode gibt es zwei Lösungen. Die erste liefert der WTR. Die zweite kann man mittels Symmetrie­betrachtungen aus dem Schaubild der Standard Sinusfunktion ableiten.
" return [{ variable: e.variable, left: left, right: sol1, }, { variable: e.variable, left: left, right: sol2, message: message }] } }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  ${renderLatex("\\sin^{-1}")}` } }; const ARCCOS: Operation = { name: "arccos", title: "`\cos^{-1}`", help: "Äquivalenzumformung: Auf beiden Seiten die Arkuskosinusfunktion anwenden", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); await lazyJsxGraph(); if ("Cos" !== e.left.operator) return error(e, "Auf der linken Seite muss die Kosinusfunktion zu oberst stehen!"); const left = e.left.ops![0]; const sol1 = ce.box(["Arccos", e.right]).simplify(); let message; if (left.operator !== "Symbol") { message = "Es empfiehlt sich, zunächst das Argument des Kosinus mit u zu substituieren. Dann lässt sich die zweite Lösung der Periode einfacher bestimmen."; return [{ variable: e.variable, left: left, right: sol1, message: message, }] } else { let sol2 = ce.box(["Negate", sol1]).simplify(); let f = "Math.cos(x)" let p = ce.box(0); message="
" + graph(f, e.right.N().value as number, sol1, sol2, p) + "Pro Periode gibt es zwei Lösungen. Die erste liefert der WTR. Die zweite kann man mittels Symmetrie­betrachtungen aus dem Schaubild der Standard Kosinusfunktion ableiten.
" return [{ variable: e.variable, left: left, right: sol1, }, { variable: e.variable, left: left, right: sol2, message: message }] } }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  ${renderLatex("\\cos^{-1}")}` } }; const PERIODIZE: Operation = { name: "periodize", title: "Periodisieren", help: "Die ein oder zwei Lösungen wiederholen sich periodisch.", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); if ("Symbol" !== e.left.operator) return error(e, "Muss zunächst nach x aufgelöst werden!"); if (e.variable !== "x") return error(e, "Zuerst muss die Resubstitution erfolgen!"); const right = ce.box(["Add", e.right, ["Multiply", "k", arg!]]); return [{ variable: e.variable, left: e.left, right: right, }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  periodisieren` } }; const EXP: Operation = { name: "exp", title: "`e^{}`", help: "Äquivalenzumformung: Auf beiden Seiten die Exponentialfunktion", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); const left = ce.box(["Exp", e.left]).simplify(); const right = ce.box(["Exp", e.right]).simplify(); return [{ variable: e.variable, left: left, right: right, }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  ${renderLatex("e^{}")}` } }; const FACTORIZE: Operation = { name: "factorize", title: "Ausklammern", help: "Auf der linken Seite den Ausdruck ausklammern", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); return [{ variable: e.variable, left: ce.box(["Multiply", arg!, ce.box(["Divide", e.left, arg!]).simplify()]), right: e.right }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  ${renderBoxed(arg!)} ausklammern` } }; const EXPAND: Operation = { name: "expand", title: "Ausmultiplizieren", help: "Linke Seite ausmultiplizieren", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); return [{ variable: e.variable, left: ce.box(["ExpandAll", e.left]).evaluate(), right: e.right }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  ausmultiplizieren` } }; const SUBSTITUTE_POLY: Operation = { name: "substitute_poly", title: "Subst", help: "Alle Vorkommen des Ausdrucks werden durch u ersetzt", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); //const exp: BoxedRule = { _tag: 'boxed-rule', id: "exp", condition: undefined, match: ce.box(["Power", "e", "_a"]), replace: ce.box(["Exp", "_a"]) }; const replaceOpts = { canonical: false, iterationLimit: 0, once: false, useVariations: false, recursive: true}; const subst: BoxedRule = { _tag: 'boxed-rule', id: "subst", condition: undefined, match: arg!, replace: ce.box("u")}; const subst2: BoxedRule = { _tag: 'boxed-rule', id: "subst2", condition: undefined, match: ce.box(["Power", arg!, 2]).simplify(), replace: ce.box(["Power", "u", 2]) }; return [{ variable: "u", left: e.left.simplify().replace([subst, subst2], replaceOpts)?.simplify() || e.left, right: e.right.simplify().replace([subst, subst2], replaceOpts)?.simplify() || e.right, }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  ${renderBoxed(arg!)} := u` } }; const SUBSTITUTE_TRIG: Operation = { name: "substitute_trig", title: "Subst", help: "Alle Vorkommen des Ausdrucks werden durch u ersetzt", arg: true, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(arg !== undefined); const subst: BoxedRule = {_tag: 'boxed-rule', id: "subst", condition: undefined, match: arg!, replace: ce.box("u")}; return [{ variable: "u", left: e.left.operator === "Sin" ? ce.box(["Sin", "u"]) : ce.box(["Cos", "u"]), right: e.right, }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  ${renderBoxed(arg!)} := u` } }; const RESUBSTITUTE: Operation = { name: "resubstitute", title: "Resubst", help: "u wird in den Ausdruck zurück ersetzt", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); let ee: Equation | undefined = e; while (ee && !arg) { if (ee.operation === SUBSTITUTE_POLY || ee.operation === SUBSTITUTE_TRIG) arg = ee.arg; ee = ee.former; } if (arg === undefined) return error(e, "Es ist keine Substitution vorausgegangen!"); e.arg = arg; const subst: BoxedRule = {_tag: 'boxed-rule', id: "subst", condition: undefined, match: ce.box("u"), replace: arg!}; return [{ variable: "x", left: e.left.replace(subst)?.simplify() || e.left, right: e.right.replace(subst)?.simplify() || e.right, }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  u := ${renderBoxed(arg!)}` } }; const QUADRATIC_FORMULA: Operation = { name: "quadratic_formula", title: "MNF", help: "Mitternachtsformel für Gleichungen der Form ax²+bx+c=0", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); let max = 0; e.left.getSubexpressions("Power").forEach(s => max = Math.max(max, s.ops![1].numericValue as number)); if (max > 2) return error(e, "Die Mitternachtsformel kann nur auf Polynomgleichungen vom Grad 2 angewandt werden"); const quadraticForm = ce.box(["Add", ["Multiply", ["Power", e.variable, "2"], "_a"], ["Multiply", e.variable, "_b"], "_c"]); const quadraticForm2 = ce.box(["Add", ["Multiply", ["Power", e.variable, "2"], "_a"], ["Multiply", e.variable, "_b"]]); const quadraticForm3 = ce.box(["Add", ["Multiply", ["Power", e.variable, "2"], "_a"], ["Multiply", "_c"]]); let match: BoxedSubstitution | null = null; try { match = e.left.match(quadraticForm); } catch (err) { console.log(err) } let message; if (match === null) { match = e.left.match(quadraticForm2); message = "Kann man mit MNF lösen, schneller geht's mit x Ausklammern und SVNP"; } if (match === null) { match = e.left.match(quadraticForm3); message = "Kann man mit MNF lösen, schneller geht's mit Wurzel ziehen"; } if (match === null || e.right.isNotZero) return error(e, "Die Mitternachtsformel kann nur auf Gleichungen der Form ax²+bx+c=0 angewandt werden!"); const a = match!._a; const b = match!._b || 0; const c = match!._c || 0; let minus = ce.box(["Divide", ["Subtract", ["Negate", b], ["Sqrt", ["Subtract", ["Power", b, 2], ["Multiply", 4, a, c]]]], ["Multiply", 2, a]]); let plus = ce.box(["Divide", ["Add", ["Negate", b], ["Sqrt", ["Subtract", ["Power", b, 2], ["Multiply", 4, a, c]]]], ["Multiply", 2, a]]); const term = ce.box(["Subtract", ["Power", b, 2], ["Multiply", 4, a, c]]); const n = term.N(); if (n.isNegative) return error({variable: 'x', left: term, right: boxed_0}, "`D = " + term.toLatex(latexOptions) + " < 0`"); else if (n.isZero) return [{ variable: e.variable, left: ce.box(e.variable), right: minus.evaluate().simplify(), message: message }]; else return [{ variable: e.variable, left: ce.box(e.variable), right: minus.evaluate().simplify(), message: message }, { variable: e.variable, left: ce.box(e.variable), right: plus.evaluate().simplify(), message: message }]; }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  MNF` } }; const ZERO_PRODUCT: Operation = { name: "zero_product", title: "SVNP", help: "Satz vom Nullprodukt. Eine Seite muss ein Produkt, die andere Null sein", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); if (!e.left.isEqual(boxed_0) && !e.right.isEqual(boxed_0)) return error(e, "Auf einer Seite muss null stehen!") if (e.left.json[0] !== "Multiply" && e.right.json[0] !== "Multiply") return error(e, "Eine Seite muss ein Produkt sein!") let product = e.left.operator === "Multiply" ? e.left : e.right; let equations: Equation[] = []; for (let factor of product.ops!) { //if ("Multiply" === factor.operator) // continue; if (!factor.has("x")) continue if (factor.operator === "Power") factor = factor.ops![0]; equations.push({ variable: "x", left: ce.box(factor), right: boxed_0 }); } return equations; }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  SVNP` } }; const NULL_FORM: Operation = { name: "null_form", title: "Nullform", help: "In Nullform bringen", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); e.arg = ce.box(["Negate", e.right]).simplify(); return [{ variable: e.variable, left: ce.box(["Subtract", e.left, e.right]).simplify(), right: boxed_0 }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`|  ${renderBoxed(arg!)}` } }; const SIMPLIFY: Operation = { name: "simplify", title: "Vereinfache", help: "Vereinfachen", arg: false, func: async (e: Equation, arg?: BoxedExpression): Promise => { assert(!arg); return [{ variable: e.variable, left: e.left.simplify(), right: e.right.simplify() }] }, render: (arg?: BoxedExpression): TemplateResult => { return html`||  vereinfachen` } }; export const error = (e: Equation, message: string) => [{ variable: e.variable, left: e.left, right: e.right, error: message, }]; export const operations: Operation[] = [ ADD, SUBTRACT, MULTIPLY, DIVIDE, SQRT, ROOT, SQUARE, LN, ARCSIN, ARCCOS, EXP, EXPAND, FACTORIZE, ZERO_PRODUCT, QUADRATIC_FORMULA, SUBSTITUTE_POLY, SUBSTITUTE_TRIG, RESUBSTITUTE, PERIODIZE, NULL_FORM, SIMPLIFY ]; export function operation(name: string) { return operations.find(o => o.name === name)!; } export const sets: Map = new Map([ ['exponential', ["add", "subtract", "multiply", "divide", "ln", "factorize", "expand", "zero_product", "quadratic_formula", "substitute_poly", "resubstitute"]], ['polynomial', ["add", "subtract", "multiply", "divide", "sqrt", "factorize", "expand", "zero_product", "quadratic_formula", "substitute_poly", "resubstitute"]], ['polynomial_root', ["add", "subtract", "multiply", "divide", "root", "factorize", "expand", "zero_product", "quadratic_formula", "substitute_poly", "resubstitute"]], ['trigonometrical', ["add", "subtract", "multiply", "divide", "arcsin", "arccos", "factorize", "expand", "zero_product", "substitute_trig", "resubstitute", "periodize"]] ]);