From: Robert D. <rob...@gm...> - 2023-07-16 17:40:54
|
Jonas, thanks for your interest in Maxima. As others have mentioned, Maxima does not directly implement step by step operations. It is possible to implement stuff like that, by disabling built-in simplifications (i.e., application of identities) and defining new ones. Here's something I came up with as a proof of concept which is based on Maxima's pattern matching rules. Note that pattern matching in this context is applied to expressions, not strings. %i44 is an example equation and we will try to solve for x. The sequence of operations r2a, r2b, r7a, etc. was determined by trial and error in this case. The input comes from a text file which contains the input and all the operations. As shown in the PS, built-in simplifications are disabled, so only the ones I defined are applied. Note that the notation *(x) which appears in a few places is Maxima's display for a multiplication expression with one argument; similar to +x, it is equivalent to x itself. (%i44) 3+4*(x-2) = 5+6*(3-x) (%o44) 3 + 4 (x - 2) = 5 + 6 (3 - x) (%i45) apply1(%,r2a,r2b) (%o45) 3 + (4 x + 4 (- 2)) = 5 + (6 3 + 6 (- x)) (%i46) apply1(%,r7a) (%o46) 3 + 4 x + 4 (- 2) = 5 + 6 3 + 6 (- x) (%i47) apply1(%,r5a) (%o47) 3 + 4 x + 4 (- 2) = 5 + 6 3 + 6 (- x) (%i48) apply1(%,r3b) (%o48) 3 + 4 x + (- 8) = 5 + 18 + 6 (- x) (%i49) applyb1(%,r4a,r4b) (%o49) 4 x + (- 5) = 6 (- x) + 23 (%i50) apply1(%,r6a,r6b) (%o50) 4 x - 6 (- x) = - (- 5) + 23 (%i51) apply1(%,r5b) (%o51) 4 x + (- 1) (6 (- 1 x)) = - 1 (- 5) + 23 (%i52) apply1(%,r7b) (%o52) 4 x + (- 1) 6 (- 1) x = - 1 (- 5) + 23 (%i53) applyb1(%,r3a) (%o53) 4 x + (- 1 6 (- 1)) x = *(- 1 (- 5)) + 23 (%i54) applyb1(%,r3b) (%o54) 4 x + 6 x = 5 + 23 (%i55) apply1(%,r9) (%o55) (*(4) + *(6)) x = 5 + 23 (%i56) apply1(%,r10,r11) (%o56) (4 + 6) x = 5 + 23 (%i57) apply1(%,r4b) (%o57) 10 x = 28 (%i58) apply1(%,r12) 28 (%o58) *(x) = ----- *(10) (%i59) apply1(%,r10,r11) 28 (%o59) x = -- 10 A more widely useful user interface might be a clickable GUI in which one could select a rule to apply and see how it changes everything following. In that case the user is doing all of the thinking, which might or might not be the goal. A different kind of UI would be to state the goal and then have the sequence of operations determined automatically -- I suppose that is pretty much the same as the existing 'solve', except with a display of the intermediate steps. Note that it takes some doing to work through this simple example, if one is restricted to the simplest operations. I suppose that in itself is a learning experience; I am reminded of the example of Russell & Whitehead, whose proof of 1 + 1 = 2 is absurdly long. PS. /* step_by_step.mac * copyright 2023 by Robert Dodier * I release this work under terms of the GNU General Public License */ /* disable built-in identities */ simp: false; plusp (e) := not atom(e) and op(e) = "+"; timesp (e) := not atom(e) and op(e) = "*"; partition_list (l, p) := [sublist (l, p), sublist (l, lambda ([e], not p(e)))]; /* distribute times over plus */ matchdeclare (aa, lambda ([e], e # 1)); matchdeclare (pp, plusp); defrule (r2a, aa*pp, map (lambda ([x], aa*x), pp)); /* distribute minus over plus */ matchdeclare (pp, plusp); defrule (r2b, - pp, map (lambda ([x], - x), pp)); /* group integers in times expression */ matchdeclare (mm, lambda ([e], timesp (e) and length (sublist (args (e), integerp)) >= 2)); defrule (r3a, mm, block ([a, b], [a, b]: partition_list (args (mm), integerp), apply ("*", append ([apply ("*", a)], b)))); /* evaluate product of integers */ matchdeclare (mm, lambda ([e], timesp (e) and every (integerp, args (e)))); defrule (r3b, mm, apply ('?\*, args (mm))); /* group integers in plus expression */ matchdeclare (pp, lambda ([e], plusp (e) and length (sublist (args (e), integerp)) >= 2)); defrule (r4a, pp, block ([a, b], [a, b]: partition_list (args (pp), integerp), apply ("+", append (b, [apply ("+", a)])))); /* evaluate sum of integers */ matchdeclare (pp, lambda ([e], plusp (e) and every (integerp, args (e)))); defrule (r4b, pp, apply ('?\+, args (pp))); /* (- integer) --> negative integer */ matchdeclare (nn, integerp); defrule (r5a, - nn, ?\- (nn)); /* (- something) --> (-1) * something */ matchdeclare (aa, all); defrule (r5b, - aa, (?\- (1))*aa); /* partition terms in equation, x versus free of x */ /* case a: left-hand side contains terms free of x */ matchdeclare (pp, lambda ([e], plusp (e) and some (lambda ([e1], freeof (x, e1)), args (e)))); matchdeclare (qq, plusp); defrule (r6a, pp = qq, block ([p1, px, q1, qx], [p1, px]: partition (pp, x), [q1, qx]: partition (qq, x), px - qx = - p1 + q1)); /* case b: right-hand side contains terms not free of x */ matchdeclare (pp, plusp); matchdeclare (qq, lambda ([e], plusp (e) and some (lambda ([e1], not freeof (x, e1)), args (e)))); defrule (r6b, pp = qq, block ([p1, px, q1, qx], [p1, px]: partition (pp, x), [q1, qx]: partition (qq, x), px - qx = - p1 + q1)); /* flatten plus expressions (associativity) */ matchdeclare (pp, lambda ([e], plusp (e) and some (plusp, args (e)))); defrule (r7a, pp, apply ("+", flatten (map (lambda ([e1], if plusp (e1) then args (e1) else e1), args (pp))))); /* flatten times expressions (associativity) */ matchdeclare (mm, lambda ([e], timesp (e) and some (timesp, args (e)))); defrule (r7b, mm, apply ("*", flatten (map (lambda ([e1], if timesp (e1) then args (e1) else e1), args (mm))))); /* group x terms in plus expression */ matchdeclare (pp, lambda ([e], plusp (e) and length (sublist (args (e), lambda ([e1], not freeof (x, e1)))) >= 2)); defrule (r8, pp, block ([a, b], [a, b]: partition_list (args (pp), lambda ([e1], not freeof (x, e1))), apply ("+", cons (apply ("+", a), b)))); /* factor out x */ matchdeclare (pp, lambda ([e], plusp (e) and every (lambda ([e1], not freeof (x, e1)), args (e)))); defrule (r9, pp, map (lambda ([e], subtimes (e, lambda ([e1], freeof (x, e1)))), pp) * x); subtimes (e, p) := if not atom(e) then apply ("*", sublist (args (e), p)) else 1; /* trivial multiplication identities */ defrule (r10, "*"(), 1); /* r11 implements "*"(aa) --> aa * working around bug in defrule here ... */ matchdeclare (aa, all); :lisp (defun $r11 (e) (if (and (consp e) (eq (caar e) 'mtimes) (= (length (cdr e)) 1)) (values (msetq $aa (cadr e)) t))) /* foo*x = bar --> x = bar/foo */ matchdeclare (mm, lambda ([e], not atom(e) and op(e) = "*" and length (e) > 1 and not freeof (x, e))); matchdeclare (aa, freeof (x)); defrule (r12, mm = aa, block ([a, b], [a, b]: partition_list (args (mm), lambda ([e1], freeof (x, e1))), apply ("*", b) = aa / apply ("*", a))); |