diff --git a/CognitiveModel/-x 6eq15.wme b/CognitiveModel/-x 6eq15.wme new file mode 100644 index 0000000..c36d93e --- /dev/null +++ b/CognitiveModel/-x 6eq15.wme @@ -0,0 +1,48 @@ +(require* wmeTypes "wmeTypes.clp") + +(assert (MAIN::initial-fact)) ; probably don't need this, but does not hurt +(bind ?*n* 1) ; used to number facts so that a newly asserted fact is never a duplicate + +;;------------------------------------------------------------------------------------- +;; +;; Template for equations of the form ax + b = c + +;;------------------------------------------------------------------------------------- +;; +;; To create new problem of the form solve ax + b = c, only edit this part +;; + +(bind ?a -1) +(bind ?b 6) +(bind ?c 15) +(bind ?var "x") + +(bind ?start-left-str "-x + 6") ; this should be literally what is in the interface in the start state +(bind ?start-right-str "15") ; ditto + + +;;------------------------------------------------------------------------------------- +;; +;; Create facts representing the equation +;; + +(bind ?ax-term (assert (simple-term (coeff ?a)(var ?var)))) +(bind ?b-term (assert (simple-term (coeff ?b)))) +(bind ?c-term (assert (simple-term (coeff ?c)))) +(bind ?left-expr (assert (expr (terms ?ax-term ?b-term)))) +(bind ?right-expr (assert (expr (terms ?c-term)))) +(bind ?eq (assert (equation (sides ?left-expr ?right-expr)))) + +;;------------------------------------------------------------------------------------- +;; +;; Now store equation in problem fact, which is created in productionRules.pr (because +;; the interface facts are the same for every problem) +;; + +(modify ?this-problem (cur-equation ?eq)) + +(bind ?start-left (assert (interface-element (name startLeft)(value ?start-left-str)))) +(bind ?start-right (assert (interface-element (name startRight)(value ?start-right-str)))) +(bind ?start-line (assert (line (solution-steps ?start-left ?start-right)))) + +(modify ?this-problem (closed-lines ?start-line)) ; this is for the reorder rule diff --git a/CognitiveModel/-x+6eq15.wme b/CognitiveModel/-x+6eq15.wme new file mode 100644 index 0000000..c36d93e --- /dev/null +++ b/CognitiveModel/-x+6eq15.wme @@ -0,0 +1,48 @@ +(require* wmeTypes "wmeTypes.clp") + +(assert (MAIN::initial-fact)) ; probably don't need this, but does not hurt +(bind ?*n* 1) ; used to number facts so that a newly asserted fact is never a duplicate + +;;------------------------------------------------------------------------------------- +;; +;; Template for equations of the form ax + b = c + +;;------------------------------------------------------------------------------------- +;; +;; To create new problem of the form solve ax + b = c, only edit this part +;; + +(bind ?a -1) +(bind ?b 6) +(bind ?c 15) +(bind ?var "x") + +(bind ?start-left-str "-x + 6") ; this should be literally what is in the interface in the start state +(bind ?start-right-str "15") ; ditto + + +;;------------------------------------------------------------------------------------- +;; +;; Create facts representing the equation +;; + +(bind ?ax-term (assert (simple-term (coeff ?a)(var ?var)))) +(bind ?b-term (assert (simple-term (coeff ?b)))) +(bind ?c-term (assert (simple-term (coeff ?c)))) +(bind ?left-expr (assert (expr (terms ?ax-term ?b-term)))) +(bind ?right-expr (assert (expr (terms ?c-term)))) +(bind ?eq (assert (equation (sides ?left-expr ?right-expr)))) + +;;------------------------------------------------------------------------------------- +;; +;; Now store equation in problem fact, which is created in productionRules.pr (because +;; the interface facts are the same for every problem) +;; + +(modify ?this-problem (cur-equation ?eq)) + +(bind ?start-left (assert (interface-element (name startLeft)(value ?start-left-str)))) +(bind ?start-right (assert (interface-element (name startRight)(value ?start-right-str)))) +(bind ?start-line (assert (line (solution-steps ?start-left ?start-right)))) + +(modify ?this-problem (closed-lines ?start-line)) ; this is for the reorder rule diff --git a/CognitiveModel/productionRules.pr b/CognitiveModel/productionRules.pr new file mode 100644 index 0000000..90df2d2 --- /dev/null +++ b/CognitiveModel/productionRules.pr @@ -0,0 +1,5089 @@ +;; tell Eclipse, etc. to read the templates from the wmeTypes.clp file +(require* wmeTypes "wmeTypes.clp") + +(defglobal ?*debug* = FALSE) +(defglobal ?*trace* = FALSE) + +;; Issues +;; - 4x-3=9 looks like cur-transformation and cur-equation are not updated after second transformation is complete? + +;; Started to implement the strict condition but then wondered whether that is a good next step. +;; Other possible to do's: +;; Generalize to more problem types. E.g., c = ax + b, ax + b = cx + d +;; Generalize to even more problem types. +;; Make the model useful for tutoring +;; - define conditions of rules better? +;; - deal with the intermediate steps (what is the desired tutor behavior?) +;; - does having rules for the strict standard condition for logging really work? + +;; More to do items +;; - figure out a general solution for fact numbering (or is it possible in a newer Jess to have +;; multiple copies of facts with the same slot values?) +;; - generalize across quotient and product terms? +;; - change name of factors slot in quotient term template? +;; - split move-simple-term-left-to-right into rules for constant term and var term? +;; - avoid creating simple terms with coefficient 0? (see condition of move rule) +;; - now prints 2*(x + 4) + 3x - 3 = 0, make it print this as 2(x + 4) + 3x - 3 = 0 +;; - test move-simple-term-left-to-right when there is more than just a constant on the right +;; - make sure cannot divide by 0? +;; - deal with other cases that involve 0? + +;; Ideas +;; - for efficiency, create a layered model? where more general versions of a strategy are matched +;; first, followed by more specific versions (and all are written in the logs, to help analysis) +;; --> should be the other way around, no? +;; - generalize move rules so they work l->r and r->l +;; - same for other rules? +;; - implement general rules first? that define the space of allowable solutions, though may not be good for hints +;; - add constant to both sides +;; - add var term to both sides +;; - same for subtract? +;; - add complex term to both sides? +;; - combine like terms (could the existing rule work?) +;; - divide (could the existing rule work?) +;; - distribute (could the existing rule work?) +;; +;; + + +;;---------------------------------------------------------------------------------------- +;; +;; Functions for printing out expressions and equations - for hints, development, etc. +;; + +(deffunction term->string (?term ?parentheses-p ?op-minus-p ?op-p) ; perhaps this should not be re-computed each time? + ; ?parentheses-p: wrap terms with negative coefficients in parentheses (probably should test that ?op-minus = FALSE ??) + ; ?op-minus-p: whether terms with negative coeff are written as ... + -7 ... (?op-minus-p = FALSE) or ... - 7 ... (?op-minus-p = TRUE) + ; ?op-p: include op or not, i.e., whether you get " + 7x" or "7x" - should maybe be separated out + ; these controls interact to some degree: + ; - don't want parentheses when there is no op? maybe that is OK (so will get parentheses for now) + ; - cannot drop negative coefficient for first term, so when ?op-p equals TRUE, need to keep the negative coeff + + (if (= TRUE ?*debug*) then + (printout t "term->string: ?term = " ?term ", ?parentheses-p = " ?parentheses-p ", ?op-minus-p = " ?op-minus-p ", ?op-p = " ?op-p crlf ) + ) + ; could test for jess-type is fact, but not for specific type of fact? how bizarre + (bind ?type (fact-slot-value ?term type)) + (if (= simple-term ?type) ; is there a way to match a given fact against a pattern? + then + (bind ?coeff (fact-slot-value ?term coeff)) + (bind ?var (fact-slot-value ?term var)) + (bind ?op (if (and (= TRUE ?op-minus-p)(< ?coeff 0)) then " - " else " + ")) + (bind ?op (if (= TRUE ?op-p) then ?op else "")) + (bind ?par1 (if (and (= ?parentheses-p TRUE)(< ?coeff 0)) then "(" else "")) + (bind ?par2 (if (and (= ?parentheses-p TRUE)(< ?coeff 0)) then ")" else "")) + (bind ?coeff (if (and (= TRUE ?op-minus-p)(= ?op-p TRUE)(< ?coeff 0)) + then (* -1 ?coeff) + else ?coeff)) + + (if (= TRUE ?*debug*) then + (printout t "term->string: ?coeff = " ?coeff ", ?var = " ?var ", ?op = " ?op + ", ?par1 = " ?par1 ", ?par2 = " ?par2 crlf ) + ) + + + (bind ?return-value + (str-cat ?op + ?par1 + (if (and (= 1 ?coeff)(not (= nil ?var))) + then "" + else (if (and (= -1 ?coeff)(not (= nil ?var))) ; this case does not occur, + ; given we multiply by -1 above + then "-" + else ?coeff)) + (if (= nil ?var) then "" else ?var) + ?par2)) + (if (= TRUE ?*debug*) then + (printout t "term->string ?return-value = " ?return-value crlf crlf)) + (return ?return-value) + else (if (or (= quotient-term ?type)(= product-term ?type)) + then + (bind ?factors (fact-slot-value ?term factors)) + (bind ?f1 (nth$ 1 ?factors)) ; would it be better to make this a recursive call to terms-insert-op$ ? + (bind ?f2 (nth$ 2 ?factors)) + ; should a factor in a quotient/product term that is not a simple term + ; (i.e., an expression with multiple terms) + ; always be in parentheses? probably + (bind ?terms1 (fact-slot-value ?f1 terms)) ; ?f1 and ?f2 are expr so accessing the terms slot is safe + (bind ?terms2 (fact-slot-value ?f2 terms)) + (bind ?par1a (if (> (length$ ?terms1) 1) then "(" else "")) + (bind ?par1b (if (> (length$ ?terms1) 1) then ")" else "")) + (bind ?par2a (if (or (> (length$ ?terms2) 1)(= product-term ?type)) then "(" else "")) + (bind ?par2b (if (or (> (length$ ?terms2) 1)(= product-term ?type)) then ")" else "")) + + (bind ?op (if (= TRUE ?op-p) then " + " else "")) + + ; how can we print -3*2 as "- 3(2)" and not "+ -3(2)" + ; same for -3*(x + 2) -- would like "- 3(x + 2)" not "+ -3(x + 2)" + ; make this a special case: first factor is a simple term and the type is product? + ; this function is getting to be really messy .... + + (bind ?first-term (nth$ 1 ?terms1)) + (bind ?first-term-type (fact-slot-value ?first-term type)) + (if (and (= (length$ ?terms1) 1)(= ?first-term-type simple-term)(= ?type product-term)) + then + + (bind ?return-value + (str-cat ; hmmm, no ?op, ?par1a, ?par1b ? + (term->string ?first-term FALSE TRUE ?op-p) + (if (eq ?par2a "(" ) ; so we get 2(x + 2) not 2*(x + 2) + then "" + else "*") + ?par2a + (expr->str3 ?f2) + ?par2b + )) + else + (bind ?return-value + (str-cat ?op + ?par1a + (expr->str3 ?f1) + ?par1b + (if (= quotient-term ?type) + then "/" + else ; product term + (if (eq ?par2a "(" ) ; so we get 2(x + 2) not 2*(x + 2) + then "" + else "*")) + ?par2a + (expr->str3 ?f2) + ?par2b + ))) + (if (= TRUE ?*debug*) then + (printout t "term->string ?return-value = " ?return-value crlf crlf)) + (return ?return-value) + else + (return "<< Not a simple term, quotient term, or product term >>") + ))) + +(deffunction terms-insert-op$ (?list ?parentheses-p ?op-minus-p) ; + (if (= TRUE ?*debug*) then + (printout t "terms-insert-op$, ?list = " ?list ", ?parentheses-p = " ?parentheses-p ", ?op-minus-p = " ?op-minus-p crlf)) + (if (> (length$ ?list) 0) ; should be easier test whether a list is empty in Jess?? + then + (bind ?return-value + (str-cat (term->string (nth$ 1 ?list) ?parentheses-p ?op-minus-p TRUE) + (terms-insert-op$ (rest$ ?list) ?parentheses-p ?op-minus-p))) + (if (= TRUE ?*debug*) then + (printout t "terms-insert-op$ ?return-value = " ?return-value crlf crlf)) + (return ?return-value) + else + (return "") ; empty list + )) + +; insert the new term (which should be a simple term) after a like term, if there is one; +; otherwise, append at the end +(deffunction insert-term-after-like-term$ (?new ?terms) + (if (= 0 (length$ ?terms)) + then (return (create$ ?new))) ; reached the end of the list of terms; put ?new at the end + (bind ?t1 (fact-slot-value ?new type)) + (if (not (= ?t1 simple-term)) + then (return (create$ ?terms ?new))) ; ?new should be a simple term + (bind ?first (nth$ 1 ?terms)) + (bind ?t2 (fact-slot-value ?first type)) + (if (not (= ?t2 simple-term)) ; not a like term, so keep going + then (return (create$ ?first (insert-term-after-like-term$ ?new (rest$ ?terms))))) + (bind ?v1 (fact-slot-value ?new var)) + (bind ?v2 (fact-slot-value ?first var)) + (if (eq ?v1 ?v2) ; like terms + then (return (create$ ?first ?new (rest$ ?terms))) ; insert ?new here + else (return (create$ ?first (insert-term-after-like-term$ ?new (rest$ ?terms))))) + ; otherwise, not a like term, so keep going + ) + +(deffunction expr->str1 (?expr) ; insert + in between terms, return string + (if (= TRUE ?*debug*) then + (printout t "expr->str1" ?expr crlf)) + (bind $?terms (fact-slot-value ?expr terms)) + (bind ?tmp (terms-insert-op$ (rest$ $?terms) FALSE FALSE)) + (return (str-cat (term->string (nth$ 1 $?terms) FALSE FALSE FALSE) ?tmp)) + ; is the call to return necessary?? + ) + +(deffunction expr->str2 (?expr) ; insert + in between terms, with negative terms in parentheses, return string + (bind $?terms (fact-slot-value ?expr terms)) + (bind ?tmp (terms-insert-op$ (rest$ $?terms) TRUE FALSE)) + (return (str-cat (term->string (nth$ 1 $?terms) TRUE FALSE FALSE) ?tmp)) + ; is the call to return necessary?? + ) + +(deffunction expr->str3 (?expr) ; insert + or - in between terms, return string + (bind $?terms (fact-slot-value ?expr terms)) + (bind ?tmp (terms-insert-op$ (rest$ $?terms) FALSE TRUE)) + (bind ?result (str-cat (term->string (nth$ 1 $?terms) FALSE TRUE FALSE) ?tmp)) +; (printout t crlf "expr->str3(" ?expr ") = " ?result crlf) + (return ?result) + ; is the call to return necessary?? + ) + +(deffunction copy-term (?term) ; Jess should provide this kind of function + (bind ?type (fact-slot-value ?term type)) + (if (= simple-term ?type) + then + (bind ?c (fact-slot-value ?term coeff)) + (bind ?v (fact-slot-value ?term var)) + (return (assert (simple-term (coeff ?c)(var ?v)))) ; type and fact-nr slots filled in by default + else (if (= product-term ?type) + then + (bind ?factors (map copy-expr (fact-slot-value ?term factors))) + (return (assert (product-term (factors ?factors)))) + else (= quotient-term ?type) + then + (bind ?factors (map copy-expr (fact-slot-value ?term factors))) + (return (assert (quotient-term (factors ?factors)))) + else ; something is wrong + (return FALSE) + ))) + +(deffunction copy-expr (?expr) + ; slots are terms, type, and fact-nr + (bind $?new-terms (map copy-term (fact-slot-value ?expr terms))) + (return (assert (expr (terms $?new-terms)))) ; type and fact-nr slots filled in by default + ) + +(deffunction copy-transf (?transf) + (bind ?new-transf + (assert (transformation ; don't want to use duplicate because I want a new fact number (so the fact is unique) + (equation (copy-eq (fact-slot-value ?transf equation))) + (to-be-simplified (fact-slot-value ?transf to-be-simplified)) + (written (fact-slot-value ?transf written)) + (focus (fact-slot-value ?transf focus)) + (prev-left-val (fact-slot-value ?transf prev-left-val)) + (prev-right-val (fact-slot-value ?transf prev-right-val)) + (description (fact-slot-value ?transf description)) + (pre-explained (fact-slot-value ?transf pre-explained)) + (post-explained (fact-slot-value ?transf post-explained)) + (skip-expl-sel2 (fact-slot-value ?transf skip-expl-sel2)) + (no-hints-p (fact-slot-value ?transf no-hints-p)) ; since used within chains only, + ; copying should not be necessary, but just in case + (strategic-p (fact-slot-value ?transf strategic-p)) +; (check-for-circle (fact-slot-value ?transf check-for-circle)) +; (gave-circle-feedback (fact-slot-value ?transf gave-circle-feedback)) + ))) + (return ?new-transf) + ) + +(deffunction copy-eq (?eq) ; no type checking? + (return + (assert + (equation + (sides + (create$ + (copy-expr (nth$ 1 (fact-slot-value ?eq sides))) + (copy-expr (nth$ 2 (fact-slot-value ?eq sides))))))))) + +(deffunction print-eq (?eq) + (bind ?sides (fact-slot-value ?eq sides)) + (bind ?left (nth$ 1 ?sides)) + (bind ?right (nth$ 2 ?sides)) + (printout t (expr->str1 ?left) " = " (expr->str1 ?right) crlf) + (printout t (expr->str2 ?left) " = " (expr->str2 ?right) crlf) + (printout t (expr->str3 ?left) " = " (expr->str3 ?right) crlf) + ) + +(deffunction eq->str (?eq) + (bind ?sides (fact-slot-value ?eq sides)) + (bind ?left (nth$ 1 ?sides)) + (bind ?right (nth$ 2 ?sides)) + (return (str-cat (expr->str3 ?left) " = " (expr->str3 ?right))) + ) + +(deffunction simple-terms-only-p$ (?terms) + (if (= 0 (length$ ?terms)) then + (return TRUE) + else + (bind ?first (nth$ 1 ?terms)) + (if (eq simple-term (fact-slot-value ?first type)) then + (return (simple-terms-only-p$ (rest$ ?terms))) + else + (return FALSE) + ))) + +(deffunction remove-whitespace (?s) + (bind ?result "") + (for (bind ?i 1) (<= ?i (str-length ?s)) (++ ?i) + (bind ?c (sub-string ?i ?i ?s)) + (if (neq ?c " ") then (bind ?result (str-cat ?result ?c)))) + (return ?result) + ) + +; some test cases +; (binary-op-within-parentheses-p " (( -4 ))") +; (binary-op-within-parentheses-p " (( x-4 ))") +; (binary-op-within-parentheses-p "(() + 4)" ) +; (binary-op-within-parentheses-p "((x) + 4)" ) +; (binary-op-within-parentheses-p "((x) + (4)" ) +; (binary-op-within-parentheses-p "4 + ( - (4)" ) +; (binary-op-within-parentheses-p "(-4x)" ) +; (binary-op-within-parentheses-p "(-4x) + 2" ) +; (binary-op-within-parentheses-p "(-4x + 2)" ) +; (binary-op-within-parentheses-p "((-4x) + 2)" ) +; (binary-op-within-parentheses-p "( 4 + (-4x) - 2)" ) + +; split into separate, parentheses-balanced substrings [simplification: assume 1?] +; for each substring +; find lowest level op(s) +; split +; see if you have stuff other than pars and whitespace before and after + +;; UNFINISHED +;(deffunction binary-op-within-parentheses-p (?s) ; is " ... ( x + 4 ) ... " +; (bind ?level 0) +; (bind ?pre FALSE) +; (bind ?pre-stuff FALSE) +; (bind ?op FALSE) +; (bind ?post FALSE) +; (bind ?post-stuff FALSE) +; (bind ?s (remove-whitespace ?s)) +; (for (bind ?i 1) (<= ?i (str-length ?s)) (++ ?i) +; (bind ?c (sub-string ?i ?i ?s)) +; (if (= ?c "(") then (bind ?level (+ 1 ?level))(bind ?pre TRUE)) +; else +; (if (= ?c ")") ; not "(" +; then (bind ?level (- ?level 1)) +; (if (= 0 ?level) ; either we are done are we reset entirely +; then if (and ?pre-stuff ?post-stuff) then (return TRUE) +; else (bind ?pre FALSE)(bind ?pre-stuff FALSE)(bind ?op FALSE) +; (bind ?post FALSE)(bind ?post-stuff FALSE)) +; else ; not "(" and not ")" +; (if ) +; +; ) +; (return FALSE) +; )) + +;;---------------------------------------------------------------------------------------- +;; +;; Some functions used in calls to predict +;; + +(defglobal ?*add-or-subtract-list* = (create$ "Add" "Added" "Subtract" "Subtracted")) + +(deffunction add-or-subtract-p (?model ?student) + ; can ignore ?model +; (printout t crlf crlf "add-or-subtract-p, ?model = " ?model ", student = " ?student) + (bind ?result (member$ ?student ?*add-or-subtract-list*)) +; (printout t crlf "?result = " ?result crlf crlf) + (return (if (numberp ?result) then TRUE else (if (eq FALSE ?result) then FALSE else FALSE))) + ; weird, I know + ) + +(deffunction num-equal-p (?model ?student) ; could use "=" in call to predict but then we + ; get an error when the two things are not numbers +; (printout t crlf "num-equal-p ?model = " ?model ", ?student = " ?student crlf) +; (printout t crlf "(numberp ?model) = " (numberp ?model) crlf) +; (printout t crlf "(numberp ?student) = " (numberp ?student) crlf) + (or (eq ?model ?student) ; this is crazy + (and (stringp ?model) + (stringp ?student) + (algEquivTerms ?model ?student) ; maybe should have used this in the first place ... + ) + (and (numberp ?model) + (numberp ?student) + (= ?model ?student))) + ) + +(deffunction sides-equal-p (?model ?student) +; (printout t crlf "sides-equal-p, ?model = " ?model ", ?student = " ?student crlf) +; (printout t crlf "sides-equal-p, (stringp ?model) = " (stringp ?model) +; ", (stringp ?student) = " (stringp ?student) crlf) +; (printout t crlf "sides-equal-p, (symbolp ?model) = " (symbolp ?model) +; ", (symbolp ?student) = " (symbolp ?student) crlf) + + (or (and (= ?model "left") ; equal sign can deal with string versus symbol + (= ?student "to the left side")) + (and (= ?model "right") + (= ?student "to the right side")) + )) + +(deffunction algEquivTerms-ext (?expr1 ?expr2) + + ) + +(deffunction stuExpressionNotValid (?ign ?inp) + (or (not (isAlgValid ?inp)) + (not (valid-chars-p ?inp)))) + +(deffunction valid-chars-p (?s) ; does ?s contain only characters + ; x ( ) / * + - 0 1 2 3 4 5 6 7 8 9 + ; assumes x as the variable but it is not easy to + (bind ?valid-chars "()/*+-0123456789x " ) + (for (bind ?i 1) (<= ?i (str-length ?s)) (++ ?i) + (bind ?c (sub-string ?i ?i ?s)) + (if (not (str-index ?c ?valid-chars)) then (return FALSE))) + (return TRUE) + ) + +;;---------------------------------------------------------------------------------------- +;; +;; Some keyboard functions for development +;; + +(deffunction prpreq (?prob-fact-id) ; print the current equation in the problem fact + ; could probably figure out id automatically, but this is fine for now + (print-eq (fact-slot-value (fact-id ?prob-fact-id) cur-equation)) + ) + +(deffunction preq (?eq-fact-id) + (print-eq (fact-id ?eq-fact-id) ) + ) + +;;---------------------------------------------------------------------------------------- +;; +;; Functions used in hints +;; +(deffunction like-term-or-zero-p (?var ?terms) + "test if there is a like term or a zero term among ?terms" + (foreach ?t ?terms + (if (eq simple-term (fact-slot-value ?t type)) + then + (bind ?c (fact-slot-value ?t coeff)) + (bind ?v (fact-slot-value ?t var)) + (if (or (eq ?v ?var)(eq 0 ?c)) + then + (return TRUE)))) + (return FALSE)) + +(deffunction find-var-term (?terms) + (find-a-term ?terms FALSE TRUE TRUE TRUE)) + +(deffunction find-a-term (?terms ?const ?var ?pos ?neg) + (foreach ?t ?terms + (if (eq simple-term (fact-slot-value ?t type)) + then + (bind ?c (fact-slot-value ?t coeff)) + (bind ?v (fact-slot-value ?t var)) + (if (and (or (and ?const (eq nil ?v)) + (and ?var (neq nil ?v))) + (or (and ?pos (> ?c 0)) + (and ?neg (< ?c 0)))) + then + (return ?t)))) + (return FALSE)) + +(deffunction find-neg-var-term (?terms) + (find-a-term ?terms FALSE TRUE FALSE TRUE)) + +(deffunction find-const-term (?terms) ; 0 not considered a constant term + (find-a-term ?terms TRUE FALSE TRUE TRUE)) + +(deffunction find-neg-const-term (?terms) + (find-a-term ?terms TRUE FALSE FALSE TRUE)) + +(deffunction find-prod-term (?terms) + (foreach ?t ?terms + (if (eq product-term (fact-slot-value ?t type)) + then + (return ?t))) + (return FALSE)) + +; no Jess built-in function for reversing a list, unfortunately +(deffunction reverse$ (?l) + (if (= 0 (length$ ?l)) + then (return ?l)) + (return (create$ (reverse$ (rest$ ?l)) (nth$ 1 ?l)))) + +(deffunction find-inverse-transf (?steps ?term) + (if ?*debug* + then (printout t crlf "find-inverse-transf ?steps = " ?steps + " ?term = " ?term crlf )) + (bind ?var (fact-slot-value ?term var)) + (bind ?coeff (fact-slot-value ?term coeff)) + (foreach ?s (reverse$ ?steps) + (bind ?tr (fact-slot-value ?s pre-transformation)) + (if (neq nil ?tr) + then + (bind ?descr (fact-slot-value ?tr description)) + (if (eq add (nth$ 1 ?descr)) + then + (bind ?mt (nth$ 2 ?descr)) + (if (and (eq ?var (fact-slot-value ?mt var)) + (eq (* -1 ?coeff) (fact-slot-value ?mt coeff))) + then + (if ?*debug* + then (printout t crlf "find-inverse-transf returns = " ?tr crlf )) + (return ?tr))))) + (if ?*debug* + then (printout t crlf "find-inverse-transf returns = " ?tr crlf )) + (return FALSE)) + + +(deffunction find-same-eq-prior-to-inv-transf (?steps ?stop-at-transf ?cur-l ?cur-r) + (bind ?cur-l-str (expr->str3 ?cur-l)) + (bind ?cur-r-str (expr->str3 ?cur-r)) + (if ?*debug* + then (printout t crlf "find-same-eq-prior-to-inv-transf ?steps = " ?steps crlf + " ?stop-at-transf = " ?stop-at-transf " ?cur-l = " ?cur-l + " ?cur-r = " ?cur-r crlf + " ?cur-l-str = " ?cur-l-str + " ?cur-r-str = " ?cur-r-str crlf "-----" + )) + (foreach ?s (reverse$ ?steps) ; search steps in the opposite order (i.e., in the order they occurred) + (bind ?tr (fact-slot-value ?s pre-transformation)) + (if (eq ?tr ?stop-at-transf) + then (return FALSE)) +; (if (neq nil ?tr) +; then +; (bind ?eq (fact-slot-value ?tr equation)) + (bind ?eq (fact-slot-value ?s pre-equation)) + (if (neq nil ?eq) ; probably does not happen; defensive programming + then + (bind ?sides (fact-slot-value ?eq sides)) + (bind ?l (nth$ 1 ?sides)) + (bind ?r (nth$ 2 ?sides)) + (bind ?l-str (expr->str3 ?l)) + (bind ?r-str (expr->str3 ?r)) + + (if ?*debug* + then + (printout t crlf "?l-str " ?l-str " ?r-str " ?r-str) + (printout t crlf "(algStrictEquivTerms ?l-str ?cur-l-str) = " + (algStrictEquivTerms ?l-str ?cur-l-str)) + (printout t crlf "(algStrictEquivTerms ?r-str ?cur-r-str) = " + (algStrictEquivTerms ?r-str ?cur-r-str))) + + (if (and (algStrictEquivTerms ?l-str ?cur-l-str) + (algStrictEquivTerms ?r-str ?cur-r-str)) + then + (return ?eq)))) + ;) + (return FALSE)) + +(deffunction is-zero-term (?terms) + (if (not (= 1 (length$ ?terms))) + then (return FALSE)) + (bind ?t (nth$ 1 ?terms)) + (if (eq simple-term (fact-slot-value ?t type)) + then + (if (and (eq nil (fact-slot-value ?t var)) + (= 0 (fact-slot-value ?t coeff))) + then + (return TRUE))) + (return FALSE)) + +(deffunction like-terms-p (?terms) ; are there like terms? we just consider var and const terms + ; assuming all var terms are like terms + (bind ?v FALSE) + (bind ?c FALSE) + (foreach ?t ?terms + (if (eq simple-term (fact-slot-value ?t type)) + then + (if (eq nil (fact-slot-value ?t var)) ; const term + then + (if ?c + then (return TRUE) + else (bind ?c TRUE)) + else ; var term + (if ?v + then (return TRUE) + else (bind ?v TRUE))))) + (return FALSE)) + +(deffunction nmember$ (?item ?list) ; how often does ?item occur in the list? + (bind ?n 0) + (foreach ?x ?list + (if (= ?x ?item) then (bind ?n (+ 1 ?n)))) + (return ?n) + ) + +(deffunction recommend-move-simple-term-p (?left-terms ?right-terms ?move-term) + ;; --------- Hints ----------- + ;; If (C)V = CV, recommend moving the var to the left. + ;; If CV = V, recommend moving the var to the right. + ;; If C = CV, recommend moving the constant to the left + ;; If CV = C, recommend moving the constant to the right + ;; If CV = 0, recommend moving the constant to the right. + ;; If 0 = CV, recommend moving the var to the left. + + ;; Could analyze if there are any other possible matches besides the ones listed above ... + + ;; Possibilities: + ;; CV = CV | + ;; C = CV | + ;; V = CV | + ;; CV = V | + ;; CV = C | + ;; CV = 0 | + ;; 0 = CV | + + (bind ?lv (find-var-term ?left-terms)) + (bind ?lc (find-const-term ?left-terms)) + (bind ?rv (find-var-term ?right-terms)) + (bind ?rc (find-const-term ?right-terms)) + +; (printout t crlf "move-simple-term: ?lv = " ?lv ", ?lc = " ?lc ", ?rv = " ?rv ", ?rc = " ?rc crlf) + + (return + (and (not (like-terms-p ?left-terms)) ; combining like terms should be done first + (not (like-terms-p ?right-terms)) + (or (and ?lv ?rv ?rc (eq ?rv ?move-term)) ; If (C)V = CV, move var to the left. + (and ?lc ?lv ?rv (not ?rc)(eq ?lv ?move-term)) ; If CV = V, move var to the right + (and ?lc (not ?lv) ?rc ?rv (eq ?rc ?move-term)) ; If C = CV, move const to the left + (and ?lc ?lv ?rc (not ?rv)(eq ?lc ?move-term)) ; If CV = C, move const to the right + (and ?lc ?lv (not ?rc)(not ?rv)(eq ?lc ?move-term)) ; If CV = 0, move const to the right + (and (not ?lc)(not ?lv) ?rc ?rv (eq ?rv ?move-term)) ; If 0 = CV, move var to the left + )) + )) + +(deffunction transf->str (?transf) +; (return "")) + + (bind $?descr (fact-slot-value ?transf description)) + (bind ?num (nth$ 2 $?descr)) + (bind ?result + (str-cat (nth$ 1 $?descr) + " " + (if (or (lexemep ?num)(numberp ?num)) + then "" + else (str-cat (term->string ?num FALSE FALSE FALSE) " ")) + ; else "" ) ; ?num) + ; " " + (nth$ 3 $?descr) ; "|" + ; (if (fact-slot-value ?transf strategic-p) then 1 else 0) + )) + (if ?*debug* then (printout t crlf "transf->str, ?result = " ?result crlf)) + (return ?result) + ) + +; encoding (meaningful only for moves): var/const, to left/to right, neg/pos coefficient, strategic or not +(deffunction transf-props->str (?transf) +; (return "")) + + (bind ?descr (fact-slot-value ?transf description)) + (if (not (= (nth$ 1 $?descr) add)) then (return "")) ; don't bother with transformations other than add + (bind ?move-term (nth$ 2 $?descr)) + (bind ?c (fact-slot-value ?move-term coeff)) + (bind ?v (fact-slot-value ?move-term var)) + (bind ?s (fact-slot-value ?transf to-side)) + (return (str-cat + (if (eq nil ?v) then "c" else "v") "|" + (if (> ?c 0) then "-" else "+") "|" ; if move term has pos coeff we are moving a term + ; with neg coeff + (if (eq ?s left) then "l" else (if (eq ?s right) then "r" else "?")) "|" + (if (fact-slot-value ?transf strategic-p) then 1 else 0) + )) + ) + +(deffunction line->str (?line ?pre-expl ?post-expl) +; (return "")) + + (str-cat (if (neq nil ?pre-expl) + then (ies->str (fact-slot-value ?line pre-explanations) "|") + else "") + (if (neq nil ?pre-expl) then "|" else "") + (ies->str (fact-slot-value ?line solution-steps) " = ") + (if (neq nil ?post-expl) then "|" else "") + (if (neq nil ?post-expl) + then (ies->str (fact-slot-value ?line post-explanations) "|") + else "")) + ) + +(deffunction ies->str (?ies ?sep) +; (return "")) + + (if ?*debug* then (printout t crlf "ies->str: ?ies = " ?ies crlf)) + (bind ?i 1) + (bind ?result "") + (foreach ?ie ?ies + (if ?*debug* then (printout t crlf "ies->str: ?ie = " ?ie ", i = " ?i crlf)) + (if (> ?i 1) then (bind ?result (str-cat ?result ?sep))) + (bind ?result (str-cat ?result (ie->str ?ie))) + (bind ?i (+ 1 ?i))) + (return ?result) + ) + +(deffunction ie->str (?ie) +; (return "")) + + (bind ?val (fact-slot-value ?ie value)) + (if (eq nil ?val) then "_____" else ?val)) + +;;---------------------------------------------------------------------------------------- +;; +;; Rules +;; + +;; Anatomy of a chain +;; +;; 1. record-pre-state; needed so that successful steps can be undone +;; 2. when there is no transformation, a rule that creates one (i.e., a transformation rule) +;; 3. a focus rule (sets the focus to left or right) or an explanation rule +;; (focus rules appear to be necessary so that in the chain that writes the left side no simplifications +;; of the right side are done, and vice versa ... ) +;; TO DO: should we have a rule that sets the focus to explanation? might trim the conflict tree on solving +;; steps - also, would mean we do not need to worry about simplication rules inadvertently showing +;; up in the chains for the explanation steps (although since the explanation rules don't change the +;; transformation fact that would not create erroneous) +;; 4. when the focus is left or right, zero or more simplification rules followed by a write rule (could be +;; write-intermediate or write; the latter removes the transformation) +;; TO DO: transformation cannot be removed until the explanation has been written +;; TO DO: the write rules do a lot of stuff on their rhs (and there will be more still); any chance that +;; that they could be refactored so that work can be done in a single rule? +;; TO DO: do we really need separate write-left and write-right rules? same for the write-intermediate rules? +;; +;; Quick high level gloss of the model: the main operations/transformation are modeled as rules that create +;; a transformation fact, whereas the rules that take care of the simplifications respond to these +;; facts ... (but do not create a transformation fact). +;; + +;;----------------------------------------------------------------------------------------------------------- +;; +;; Transformation rules - Standard Strategy +;; +;; +;; These rules capture the main operations but not the details of wether intermediate steps are written out, etc. +;; They all have in common that they create a transformation fact and that they can only fire when there is no such +;; fact. +;; +;; Nan Li in her EDM talk said that student performance goes down when the variable is on the right. So we should have +;; separate rules. +;; Also need separate rules for when variable coefficient is 1 or -1. +;; Separate for positive and negative more generally? +;; + +;; Standard strategy (see Waalkens et al., 2013) +;; 1. use the distributive law to expand any term in parentheses +;; 2. combine constant terms and variable terms on each side of the equation +;; 3. move variable terms to one side of the equation and constant terms to the other side +;; 4. divide both sides by the coefficient of the variable term. + +;; IDEA: Use "edit distance" between input strings to prune the search? + + + +; MOVE Rules that conform to standard strategy ... come after multiplying through and combining like terms +; +; Analysis of when a move is allowed under the standard strategy +; Steps 1 and 2 must be done, so no complex terms on either side and no like terms on any given side +; So can have 0, 1, or 2 terms on either side; however, moving makes sense only if the side being moved +; from has 2 terms +; +; C = constant term +; V = variable term +; +; Side to move from - Possible move term - Side to move to - Strategic? +; CV C 0 Yes +; CV C C Yes +; CV C V No (CV = C --> V = CV means no progress) +; CV C CV Yes (CV = CV --> V = CV after cleanup) +; CV V 0 Yes +; CV V C No (CV = C --> C = CV means no progress) +; CV V V Yes +; CV V CV Yes (CV = CV --> V = CV after cleanup) + +; Conditions: +; Sides have only simple terms (or one side could be 0) +; Each side only has unlike terms + +; So: +; Side from which to move has exactly 2 simple terms that are unlike terms +; The move term is one of these terms (obviously) +; The other side has only simple terms that are unlike terms (meaning 0, 1, or 2 terms) +; The other side is not a single term that is unlike the one being moved - in other words, the either side +; is either 0 or has a simple term that is like the one being moved (and no other ) + + +; TO DO: split into rules for moving constant and moving var? +; TO DO: split into rules for moving terms with negative and positive coefficients? +; TO DO: split into rules for terms with coefficient = 1 or -1? +; would fit with the strategy of making a maximally granular model and would not explode the +; conflict tree because the total number of rule activations would not +; change (i.e., one for each moveable term) + +;(defrule move-simple-term-to-right +; (declare (salience 1000)) ; so it is used before the more flexible rules +; ; moves a simple term from left to right, +; ; but only if there is a like term on the right (i.e., like the term being moved), +; ; (note: this criterion is too strong if you consider -3x+3=0; moving -3x is +; ; helpful, even though there is no like term on the right; right side that is 0 covered by separate rule) +; ; and only if there are two simple, unlike terms on the left +; ; (note: minimum criterion for the move being minimally strategic is that there +; ; is more than one term on the left; but standard strategy requires stronger condition) +; ; --> or does it? check C&E article ... +; ; and only if there are at most two simple, unlike terms on the right +; ; (so this captures the standard strategy - getting rid of parentheses, combining like terms has been done) +; ; with intermediate step (i.e., no canceling on the left, combining like terms on the right) +; ; +; ?prob <- (problem (cur-equation ?eq)(cur-transformation nil) +; (cur-step ~nil)) ; so that record-pre-state goes first +; ?eq <- (equation (sides ?left ?right)) +; ?left <- (expr (terms ? ?)) ; total of two terms +; ?left <- (expr (terms $?before ?move-term $?after)) +; ?left <- (expr (terms $? ?other-term $?)) +; (test (neq ?move-term ?other-term)) +; ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var)) ; no condition that ?var is non-nil +; ; (rule covers moving constant term or variable term) +; ?other-term <- (simple-term (var ~?var)) ; other term is an unlike term +; ; now check the right has either one term (i.e., the one that is like the term being moved) +; ; or has two simple terms, one that is like the one being moved, the other a simple term +; ; and unlike term +; ?right <- (expr (terms $?right-terms)) +; ?right <- (expr (terms $? ?like-term $?)) +; ?like-term <- (simple-term (var ?var)(coeff ~0)) ; exclude zero +; ?right <- (expr (terms $? ?possible-unlike-term $?)) +; ; note: there may not be a match with ?possible-unlike-term bound to a different term +; ; (i.e., different from ?like-term) +; ; but there is always a match with ?possible-unlike-term = ?like-term so this pattern never fails +; ?possible-unlike-term <- (simple-term (var ?var2)) +; (test (or (= 1 (length$ $?right-terms)) +; (and (= 2 (length$ $?right-terms)) +; (neq ?var ?var2) ; ensure that ?possible-unlike-term is indeed an unlike term +; ))) +;=> +; (bind ?new-coeff (* -1 ?coeff)) +; (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) +; (bind ?new-term-string (term->string ?new-term FALSE FALSE FALSE)) +; (bind ?new-left (assert (expr (terms $?before ?move-term ?new-term $?after)))) +; ; inserts the new term immediately following the term being moved +; (bind ?new-right (assert (expr (terms $?right-terms ?new-term)))) +; ; inserts the new term at the end (not that it matters much) +; (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) +; (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left right) +; (description add ?new-term both) +; ; add ?new-term to both sides - used for explanation +; ; assume it is a term but not always a simple term (though this rule only deals with moving +; ; simple terms, obviously) +; ; TO DO: or generalize this even further, to moving entire expressions? maybe not ... +; ))) +; (modify ?prob (cur-transformation ?transf)) +; +; (if (= TRUE ?*trace*) then +; (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) " to the right" crlf) +; (print-eq ?new-eq) +; (printout t crlf)) +; ) + +;(defrule move-simple-term-to-left ; analogous to move-simple-term-to-right +; (declare (salience 1000)) ; so it is used before the more flexible rules +; ?prob <- (problem (cur-equation ?eq)(cur-transformation nil) +; (cur-step ~nil)) ; so that record-pre-state goes first +; ?eq <- (equation (sides ?left ?right)) +; ?right <- (expr (terms ? ?)) ; total of two terms +; ?right <- (expr (terms $?before ?move-term $?after)) +; ?right <- (expr (terms $? ?other-term $?)) +; (test (neq ?move-term ?other-term)) +; ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var)) +; ?other-term <- (simple-term (var ~?var)) +; ?left <- (expr (terms $?left-terms)) +; ?left <- (expr (terms $? ?like-term $?)) +; ?like-term <- (simple-term (var ?var)(coeff ~0)) ; exclude zero +; ?left <- (expr (terms $? ?possible-unlike-term $?)) +; ; note: there may not be a match with ?possible-unlike-term bound to a different term +; ; (i.e., different from ?like-term) +; ; but there is always a match with ?possible-unlike-term = ?like-term so this pattern never fails +; ?possible-unlike-term <- (simple-term (var ?var2)) +; (test (or (= 1 (length$ $?left-terms)) +; (and (= 2 (length$ $?left-terms)) +; (neq ?var ?var2) ; ensure that ?possible-unlike-term is indeed an unlike term +; ))) +;=> +; (bind ?new-coeff (* -1 ?coeff)) +; (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) +; (bind ?new-right (assert (expr (terms $?before ?move-term ?new-term $?after)))) +; ; inserts the new term immediately following the term being moved +; (bind ?new-left (assert (expr (terms $?left-terms ?new-term)))) +; ; inserts the new term at the end (not that it matters much) +; (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) +; (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left right) +; (description add ?new-term both) +; ; add ?new-term to both sides - used for explanation +; ))) +; (modify ?prob (cur-transformation ?transf)) +; +; (if (= TRUE ?*trace*) then +; (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) " to the left" crlf) +; (print-eq ?new-eq) +; (printout t crlf)) +; ) + +; when are transformations consistent? is having the same operator the only requirement? +; this rule is needed when dealing with pre-explanations, +(defrule same-op-as-prov-transformation + ?prob <- (problem (cur-transformation ?cur)(prov-transformation ?prov)) + ?cur <- (transformation (description ?op ? ?)) + ?prov <- (transformation (description ?op ? ?)) + => + (modify ?prob (prov-transformation nil)) + ) + +; TO DO: bump up salience to 1000 (but low salience good for testing move-simple-term) +;(defrule move-simple-term-to-zero-right ; when there is a 0 only on one side, makes sense to move +; ; either constant or var term +; ; TO DO: or select the move so you end up with var term with positive coeff? +; (declare (salience 1000)) +; ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) +; ?eq <- (equation (sides ?left ?right)) +; ?left <- (expr (terms ? ?)) ; total of two terms +; ?left <- (expr (terms $?before ?move-term $?after)) +; ?left <- (expr (terms $? ?other-term $?)) +; (test (neq ?move-term ?other-term)) +; ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var)) ; no condition that ?var is non-nil +; ; (rule covers moving constant term or variable term) +; ?other-term <- (simple-term (var ~?var)) ; other term is an unlike term +; ; now check the right has a 0 terms only +; ?right <- (expr (terms ?right-term)) ; only a single +; ?right-term <- (simple-term (coeff 0)) ; coefficient is zero +;=> +; (bind ?new-coeff (* -1 ?coeff)) +; (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) +; (bind ?new-left (assert (expr (terms $?before ?move-term ?new-term $?after)))) +; ; inserts the new term immediately following the term being moved +; (bind ?new-right (assert (expr (terms ?right-term ?new-term)))) ; so student can write 0-5 or 0-5x +; ; inserts the new term at the end (not that it matters much) +; (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) +; (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left right) +; (description add ?new-term both)))) +; (modify ?prob (cur-transformation ?transf)) +; +; (if (= TRUE ?*trace*) then +; (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) " to the right" crlf) +; (print-eq ?new-eq) +; (printout t crlf)) +; ) + +; TO DO: bump up salience to 1000 (but low salience good for testing move-simple-term) +;(defrule move-simple-term-to-zero-left ; when there is a 0 only on one side, makes sense to move +; ; either constant or var term - or select the move so you end up with var term with positive coeff? +; (declare (salience 1000)) +; ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) +; ?eq <- (equation (sides ?left ?right)) +; ?right <- (expr (terms ? ?)) ; total of two terms +; ?right <- (expr (terms $?before ?move-term $?after)) +; ?right <- (expr (terms $? ?other-term $?)) +; (test (neq ?move-term ?other-term)) +; ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var)) ; no condition that ?var is non-nil +; ; (rule covers moving constant term or variable term) +; ?other-term <- (simple-term (var ~?var)) ; other term is an unlike term +; ; now check the left is a zero term +; ?left <- (expr (terms ?left-term)) ; only a single +; ?left-term <- (simple-term (coeff 0)) ; coefficient is zero +;=> +; (bind ?new-coeff (* -1 ?coeff)) +; (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) +; (bind ?new-right (assert (expr (terms $?before ?move-term ?new-term $?after)))) +; ; inserts the new term immediately following the term being moved +; (bind ?new-left (assert (expr (terms ?left-term ?new-term)))) ; so student can write 0-5 or 0-5x +; ; inserts the new term at the end (not that it matters much) +; (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) +; (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left right) +; (description add ?new-term both)))) +; (modify ?prob (cur-transformation ?transf)) +; +; (if (= TRUE ?*trace*) then +; (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) " to the left" crlf) +; (print-eq ?new-eq) +; (printout t crlf)) +; ) + + + +; conforms to standard strategy: only when terms in parentheses have been eliminated +(defrule combine-variable-terms + "Skills:Combine_variable_terms LDashb" + (declare (salience 299)) ; so it goes before move-constant and move-variable + ; but after combine-constant-terms which is probably more frequent + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + + ; we only have simple terms -- to enforce the standard strategy + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?left-terms)) + ?right <- (expr (terms $?right-terms)) + (test (simple-terms-only-p$ $?left-terms)) + (test (simple-terms-only-p$ $?right-terms)) + + ?eq <- (equation (sides $? ?s $?)) + ?s <- (expr (terms $?before ?t1 $?between ?t2 $?after)) + ?t1 <- (simple-term (coeff ?c1)(var ?var&~nil)) ; variable term + ?t2 <- (simple-term (coeff ?c2)(var ?var)) +=> + (bind ?new-term (assert (simple-term (coeff (+ ?c1 ?c2))(var ?var)))) + (bind ?new-expr (assert (expr (terms $?before ?new-term $?between $?after)))) + + (bind ?side (if (eq ?s ?left) then left else right)) + (if (eq ?side left) + then (bind ?new-eq (assert (equation (sides ?new-expr ?right)))) + else (bind ?new-eq (assert (equation (sides ?left ?new-expr))))) + (bind ?transf (assert (transformation (equation ?new-eq) + (description combine 0 ?side)(skip-expl-sel2 TRUE)))) ; 0 just a place holder + ; no intermediate step for combine like terms? + (modify ?prob (cur-transformation ?transf)) + + ;; --- Hints --- + (bind $?other-terms (create$ $?before $?between $?after)) + (bind ?other-terms-expr (assert (expr (terms $?other-terms)))) + (bind ?keep (if (> (length$ $?other-terms) 0) + then (str-cat You " " also " " need " " to " " keep " " (expr->str3 ?other-terms-expr) . ) + else "" )) + (retract ?other-terms-expr) ; not needed any more + + (construct-message + [ You have two variable terms on the (str-cat ?side ,) + (term->string ?t1 FALSE FALSE FALSE) + and (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + When you have two variable terms on one + side, it is good to add them, so you have only one. + This is called "\"combining like terms.\"" + ; How can you do so? + ] + [ On the (str-cat ?side ,) combine like terms by adding (term->string ?t1 FALSE FALSE FALSE) + and (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + This gives (str-cat (term->string ?new-term FALSE FALSE FALSE) .) + ?keep ] + ) +; (construct-message [ Combine variable terms on the ?side side. ] +; [ Combine (term->string ?t1 FALSE FALSE FALSE) +; and (term->string ?t2 FALSE FALSE FALSE) on the ?side side. ]) + + (if (= TRUE ?*trace*) then + (printout t "Combine like terms " (term->string ?t1 FALSE FALSE FALSE) + " and " (term->string ?t2 FALSE FALSE FALSE)" on the " ?side crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + + +; conforms to standard strategy: only when terms in parentheses have been eliminated +(defrule combine-constant-terms + "Skills:Combine_constant_terms LDashb" + (declare (salience 300)) ; so it goes before move-constant and move-variable + ; and before combine-variable-terms which is probably less frequent + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + + ; we only have simple terms -- to strictly enforce the standard strategy + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?left-terms)) + ?right <- (expr (terms $?right-terms)) + (test (simple-terms-only-p$ $?left-terms)) + (test (simple-terms-only-p$ $?right-terms)) + + ?eq <- (equation (sides $? ?s $?)) + ?s <- (expr (terms $?before ?t1 $?between ?t2 $?after)) + ?t1 <- (simple-term (coeff ?c1)(var nil)) ; constant terms + ?t2 <- (simple-term (coeff ?c2)(var nil)) +=> + (bind ?new-term (assert (simple-term (coeff (+ ?c1 ?c2))))) + (bind ?new-expr (assert (expr (terms $?before ?new-term $?between $?after)))) + + + (bind ?side (if (eq ?s ?left) then left else right)) + (if (eq ?side left) + then (bind ?new-eq (assert (equation (sides ?new-expr ?right)))) + else (bind ?new-eq (assert (equation (sides ?left ?new-expr))))) + (bind ?transf (assert (transformation (equation ?new-eq) + (description combine 0 ?side)(skip-expl-sel2 TRUE)))) ; 0 just a place holder + ; no intermediate step for combine like terms? + (modify ?prob (cur-transformation ?transf)) + + ;; --- Hints --- + ; Example: 8x + 4 - 5 = 11 + ; 1. You have two constants on the left, 4 and -5. When you have two constants on one side, it is + ; good to add them so you have a single constant. This is called "combining like terms." + ; How can you do so? + ; 2. On the left, combine like terms by adding 4 and -5. You need to keep the 8x. + ; 3. Write 8x - 1 on the left. (Taken care of by the write intermediate rule.) + (bind $?other-terms (create$ $?before $?between $?after)) + (bind ?other-terms-expr (assert (expr (terms $?other-terms)))) + (bind ?keep (if (> (length$ $?other-terms) 0) + then (str-cat You " " also " " need " " to " " keep " " (expr->str3 ?other-terms-expr) . ) + else "" )) + (retract ?other-terms-expr) ; not needed any more + + (construct-message ; [ Combine constant terms on the ?side side. ] + ; [ Combine (term->string ?t1 FALSE FALSE FALSE) + ; and (term->string ?t2 FALSE FALSE FALSE) on the ?side side. ] + [ You have two constants on the (str-cat ?side ,) (term->string ?t1 FALSE FALSE FALSE) + and (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + If you add them, you will have only one constant, which is better. + This is called "\"combining like terms.\"" + ; How can you do so? + ] + [ On the (str-cat ?side ,) combine like terms by adding (term->string ?t1 FALSE FALSE FALSE) + and (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + This gives (str-cat (term->string ?new-term FALSE FALSE FALSE) .) + ?keep ] + ) + + (if (= TRUE ?*trace*) then + (printout t "Combine constant terms " (term->string ?t1 FALSE FALSE FALSE) + " and " (term->string ?t2 FALSE FALSE FALSE)" on the " ?side crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +; TO DO: consolidate left and right rules? +; in standard strategy, division comes last ... only a var term left on the left and a constant term on the right +(defrule divide-both-sides-by-left-var-coeff ; what are the conditions? only when one term left? + "Skills:Divide_both_sides_by_the_variable_coefficient LDashb" + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms ?t1)) ; only one term + ?t1 <- (simple-term (coeff ?c1&~1)(var ?var&~nil)) ; divide only by the coefficient of the variable term + ?right <- (expr (terms ?t2)) + ?t2 <- (simple-term (coeff ?c2)(var nil)) ; ?c2 needed for hints +=> + (bind ?divisor-term (assert (simple-term (coeff ?c1)))) + (bind ?divisor-expr (assert (expr (terms ?divisor-term)))) + (bind ?new-term-left (assert (quotient-term (factors ?left ?divisor-expr)))) + (bind ?new-left (assert (expr (terms ?new-term-left)))) + (bind ?new-term-right (assert (quotient-term (factors ?right ?divisor-expr)))) + (bind ?new-right (assert (expr (terms ?new-term-right)))) + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left right) + (description divide ?divisor-term both)))) + (modify ?prob (cur-transformation ?transf)) + + ;; ---- Hints ---- +; (construct-message [ Divide both sides by (term->string ?divisor-term FALSE FALSE FALSE) . ] +; ) + (construct-message +;Level 1 +;- highlight key feature (You have a product of a coefficient and a variable on the left: ax is a times x. And you have a constant b on the other side.) +;- state general principle in simple terms (When you only have a product of a coefficient and a variable on one side, and a constant on the other, you can get the variable by itself by dividing both sides by the coefficient.) +;- state the term for the general principle (This is called "Divide both sides by the variable coefficient (this is consistent with the skill name in the Skillometer).") + [ You have only a variable term on the left: (str-cat (term->string ?t1 FALSE FALSE FALSE) .) + ; which is ?c1 times (str-cat ?var .) + Also, you have only a constant on the right: (str-cat ?c2 .) + ; When you have only a variable term on one side, and only a constant on the other, + You can get the variable by itself by dividing both sides by the coefficient of the variable. ] +;- state the term for the general principle (This is called "Divide both sides by the variable coefficient (this is consistent with the skill name in the Skillometer).") + +; Level 2 +; - state general transaction (Divide both sides by the coefficient of the variable.) +; - state how transaction is to be implemented (that is, divide both sides by a) + [ Divide both sides by the coefficient of the variable. That is, divide both sides + by (str-cat (term->string ?divisor-term FALSE FALSE FALSE) . ) ] +;Level 3 +;- bottom-out hint (Write ax/a on the left) + ) + + (if (= TRUE ?*trace*) then + (printout t "Divide both sides by " (term->string ?divisor-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule divide-both-sides-by-right-var-coeff ; what are the conditions? only when one term left? + "Skills:Divide_both_sides_by_the_variable_coefficient LDashb" + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms ?t1)) ; only one term + ?t1 <- (simple-term (coeff ?c1&~1)(var ?var&~nil)) ; divide only by the coefficient of the variable term + ?left <- (expr (terms ?t2)) + ?t2 <- (simple-term (coeff ?c2)(var nil)) ; ?c2 needed for hints +=> + (bind ?divisor-term (assert (simple-term (coeff ?c1)))) + (bind ?divisor-expr (assert (expr (terms ?divisor-term)))) + (bind ?new-term-right (assert (quotient-term (factors ?right ?divisor-expr)))) + (bind ?new-right (assert (expr (terms ?new-term-right)))) + (bind ?new-term-left (assert (quotient-term (factors ?left ?divisor-expr)))) + (bind ?new-left (assert (expr (terms ?new-term-left)))) + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left right) + (description divide ?divisor-term both)))) + (modify ?prob (cur-transformation ?transf)) + + ;; ---- Hints ---- +; (construct-message [ Divide both sides by (term->string ?divisor-term FALSE FALSE FALSE) . ] +; ) + (construct-message + [ You have only a variable term on the right: (str-cat (term->string ?t1 FALSE FALSE FALSE) .) + ; which is ?c1 times (str-cat ?var .) + Also, you have only a constant on the left: (str-cat ?c2 .) + ; When you have only a variable term on one side, and only a constant on the other, + You can get the variable by itself by dividing both sides by the coefficient of the variable. ] + [ Divide both sides by the coefficient of the variable. That is, divide both sides + by (str-cat (term->string ?divisor-term FALSE FALSE FALSE) . ) ] + ) + + (if (= TRUE ?*trace*) then + (printout t "Divide both sides by " (term->string ?divisor-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + + + +;; to model 4(-2x + 3) = -4 --> -2x + 3 = -1 +;; how do we represent the intermediate steps? +;; 4/4(-2x + 3) --> 1(-2x + 3) --> -2x + 3 +;; but how about 4(-2x + 3) --> (4(-2x + 3)/4) that would be a separate rule but equally correct ... + +;; so we need one divide rule and possibly two simplification rules? + +(defrule divide-product-by-constant ; what are the conditions? only when one term left? + ; (test (not (is-hint))) + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides $? ?s1 $?)) + ?s1 <- (expr (terms ?t1)) ; only a constant term on one side + ?t1 <- (simple-term (coeff ?c1)(var nil)) + + ?eq <- (equation (sides $? ?s2 $?)) + ?s2 <- (expr (terms ?t2)) ; only a product term on the other side + ?t2 <- (product-term (factors ? ?)) ; may not be necessary + ?t2 <- (product-term (factors $? ?const-factor $?)) + ?const-factor <- (expr (terms ?const-term)) + ?const-term <- (simple-term (coeff ?c2)(var nil)) + ?t2 <- (product-term (factors $? ?linear-factor $?)) + ?linear-factor <- (expr (terms ? ?)) ; need more conditions? + + (test (= 0 (mod ?c1 ?c2))) ; don't want fractions (just yet!) + + ?eq <- (equation (sides ?left ?)) + => + ;; create divisor + (bind ?divisor-term (assert (simple-term (coeff ?c2)))) + (bind ?divisor-expr (assert (expr (terms ?divisor-term)))) + + ;; create a quotient expr for the side with the constant term + (bind ?new-t1 (assert (quotient-term (factors ?s1 ?divisor-expr)))) + (bind ?new-s1 (assert (expr (terms ?new-t1)))) + + ;; create a quotient expr for the side with the product + (bind ?new-t2 (assert (quotient-term (factors ?s2 ?divisor-expr)))) + (bind ?new-s2 (assert (expr (terms ?new-t2)))) + + ;; create a new equation and transformation + (if (eq ?s1 ?left) + then (bind ?new-eq (assert (equation (sides ?new-s1 ?new-s2)))) + else (bind ?new-eq (assert (equation (sides ?new-s2 ?new-s1))))) + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left right) + (description divide ?divisor-term both)))) + (modify ?prob (cur-transformation ?transf)) + +; (construct-message [ Divide both sides by (term->string ?divisor-term FALSE FALSE FALSE) . ] +; ) + +; (if (= TRUE ?*trace*) then +; (printout t "Divide both sides by " (term->string ?divisor-term FALSE FALSE FALSE) crlf) +; (print-eq ?new-eq) +; (printout t crlf)) + ) + + +;; TO DO: do we need to model distributing on both sides at once? +;; TO DO: as a little speed optimization, could inspect student input - when distributing, there +;; cannot be a + or - sign with "stuff" on both sides of it +;; e.g., 3(x - 3) could be written as 3(x) + 3(-3) but that still satisfies the rule above +;; +(defrule distribute-left + "Skills:Distribute LDashb" + (declare (salience 501)) ; useful for hints + +; (studentValues (input ?stu-inp)) +; (test (or (is-hint)(not (binary-op-within-parentheses-p ?stu-inp)))) + + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?before ?left-product $?after)) + ?left-product <- (product-term (factors $? ?a-expr $?)) + ?left-product <- (product-term (factors $? ?bx+c-expr $?)) + (test (not (eq ?a-expr ?bx+c-expr))) + ?a-expr <- (expr (terms ?a-term)) + ?a-term <- (simple-term (coeff ?a)(var nil)) ; does it matter that for some probs there is a var of the same name in the wme file? + ?bx+c-expr <- (expr (terms $? ?bx-term $?)) + ?bx+c-expr <- (expr (terms $? ?c-term $?)) + (test (not (eq ?bx-term ?c-term))) ; simplify by assuming the first one is bx and the second c? + ?bx-term <- (simple-term (coeff ?b)(var ?var&~nil)) + ?c-term <- (simple-term (coeff ?c)(var nil)) + + ?bx+c-expr <- (expr (terms ?first-term $?)) ; so we can keep the order of terms when distributing +=> + (bind ?bx-expr (assert (expr (terms ?bx-term)))) + (bind ?c-expr (assert (expr (terms ?c-term)))) + (bind ?abx-term (assert (product-term (factors ?a-expr ?bx-expr)))) + (bind ?ac-term (assert (product-term (factors ?a-expr ?c-expr)))) + ; cleaner would be to keep the existing order, but it looks like the constant term is always the first one + + ; however, we do need to worry about how to order the two products + (if (eq ?bx-term ?first-term) + then + (bind ?new-left-side (assert (expr (terms $?before ?abx-term ?ac-term $?after)))) + else + (bind ?new-left-side (assert (expr (terms $?before ?ac-term ?abx-term $?after))))) + (bind ?new-eq (assert (equation (sides ?new-left-side ?right)))) + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left left) + (description distribute * left)(skip-expl-sel2 TRUE)))) + (modify ?prob (cur-transformation ?transf)) + + ;; ----- Hints ----- +; (construct-message [ First, get rid of the parentheses on the left. ] +; [ Distribute (term->string ?a-term FALSE FALSE FALSE) across +; (str-cat "(" (expr->str3 ?bx+c-expr ) ")" ) . ] +; ) + +; Example: 4(2x + 1) - 5 = 11 + + ; Level 1 +; - highlight key feature: On the left, you have 4 times an expression in parentheses (2x + 1) . +; - state general principle in simple terms: When you have a number times an expression in parentheses, +; you can get rid of the parentheses by writing separate, smaller, products. +; - state the term for the general principle: This is called "distributing across parentheses." +; - ask short question: How can you do so? +; Note the principle is really: To multiply an expression in parentheses that has more than one term, +; you can multiply each term separately. Or even more completely: When you have a product of a +; constant term times an expression in parenthesis that has more than one term, you can transform it +; by writing a separate multiplication for each term. But this language is too complicated. + + ; Level 2 +; - state general transaction: On the left, distribute across parentheses," +; - state how transaction is to be implemented: "by writing 4 times 2x and 4 times 1 separately" +; - add slight clarification to avoid errors (You need to keep the -5.) + +; Level 3 +; - bottom-out hint: Write 4(2x) + 4(1) - 5 + (bind $?other-terms (create$ $?before $?after)) + (bind ?other-terms-expr (assert (expr (terms $?other-terms)))) + (bind ?keep (if (> (length$ $?other-terms) 0) + then (str-cat Remember " " you " " also " " need " " to " " keep " " + the " " (expr->str3 ?other-terms-expr) . ) + else "" )) + (retract ?other-terms-expr) ; not needed any more + + (construct-message + [ On the left, you have + (str-cat (term->string ?left-product FALSE FALSE FALSE ) "." ) + ; That is, you have a number times an expression in parentheses. + You can get rid of the parentheses by writing two smaller products. + ; How can you do so? + ] + On the left, you may replace + (term->string ?left-product FALSE FALSE FALSE) by two products, + (term->string ?abx-term FALSE FALSE FALSE) + and + (str-cat (term->string ?ac-term FALSE FALSE FALSE) .) + This is called "\"distributing across parentheses.\"" + ?keep + ] + ) + + + (if (= TRUE ?*trace*) then + (printout t "Distribute product on the left" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule distribute-right + "Skills:Distribute LDashb" + (declare (salience 500)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms $?before ?right-product $?after)) + ?right-product <- (product-term (factors $? ?a-expr $?)) + ?right-product <- (product-term (factors $? ?bx+c-expr $?)) + (test (not (eq ?a-expr ?bx+c-expr))) + ?a-expr <- (expr (terms ?a-term)) + ?a-term <- (simple-term (coeff ?a)(var nil)) ; does it matter that for some probs there is a var of the same name in the wme file? + ?bx+c-expr <- (expr (terms $? ?bx-term $?)) + ?bx+c-expr <- (expr (terms $? ?c-term $?)) + (test (not (eq ?bx-term ?c-term))) ; simplify by assuming the first one is bx and the second c? + ?bx-term <- (simple-term (coeff ?b)(var ?var&~nil)) + ?c-term <- (simple-term (coeff ?c)(var nil)) + ?bx+c-expr <- (expr (terms ?first-term $?)) ; so we can keep the order of terms when distributing + => + (bind ?bx-expr (assert (expr (terms ?bx-term)))) + (bind ?c-expr (assert (expr (terms ?c-term)))) + (bind ?abx-term (assert (product-term (factors ?a-expr ?bx-expr)))) ; cleaner would be to keep the existing order + (bind ?ac-term (assert (product-term (factors ?a-expr ?c-expr)))) + (if (eq ?bx-term ?first-term) + then + (bind ?new-right-side (assert (expr (terms $?before ?abx-term ?ac-term $?after)))) + else + (bind ?new-right-side (assert (expr (terms $?before ?ac-term ?abx-term $?after))))) + (bind ?new-eq (assert (equation (sides ?left ?new-right-side)))) + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified right right) + (description distribute * right)(skip-expl-sel2 TRUE)))) + (modify ?prob (cur-transformation ?transf)) + + ;; ----- Hints ----- + (bind $?other-terms (create$ $?before $?after)) + (bind ?other-terms-expr (assert (expr (terms $?other-terms)))) + (bind ?keep (if (> (length$ $?other-terms) 0) + then (str-cat Remember " " you " " also " " need " " to " " keep " " the " " (expr->str3 ?other-terms-expr) . ) + else "" )) + (retract ?other-terms-expr) ; not needed any more + + (construct-message + [ On the right, you have + (str-cat (term->string ?right-product FALSE FALSE FALSE ) "." ) + ; That is, you have a number times an expression in parentheses. + You can get rid of the parentheses by writing two smaller products. + ; How can you do so? + ] + On the right, you may replace + (term->string ?right-product FALSE FALSE FALSE) by two products, + (term->string ?abx-term FALSE FALSE FALSE) + and + (str-cat (term->string ?ac-term FALSE FALSE FALSE) .) + This is called "\"distributing across parentheses.\"" + ?keep + ] + ) + +; (construct-message [ First, get rid of the parentheses on the right. ] +; [ Distribute (term->string ?a-term FALSE FALSE FALSE) across +; (str-cat "(" (expr->str3 ?bx+c-expr ) ")" ) . ] ) + + (if (= TRUE ?*trace*) then + (printout t "Distribute product on the right" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +;(defrule swap +; ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) +; ?eq <- (equation (sides ?left ?right)) +;=> +; (bind ?new-eq (assert (equation (sides ?right ?left)))) +; (bind ?transf (assert (transformation (equation ?new-eq)(description swap * *)))) ; no simplification needed +; (modify ?prob (cur-transformation ?transf)) +; +; (if (= TRUE ?*trace*) then +; (printout t "Swap sides" crlf) +; (print-eq ?new-eq) +; (printout t crlf)) +; ) + +;(defrule reorder-terms-left ; issue: what if the student wants to reorder the side written last? +; ; when first side written is the same as previous line, could also be combining like +; ; terms on the other side +; ; also, what if we do this in the context of simplifying ?? +; ; maybe we can use the prov-transformation mechanism? +; ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil) +; (open-lines ?cur-line $?)(closed-lines ?prev-line $?)) ; pre-explanations nil? +; ?prev-line <- (line (solution-steps ?prev-ie-left ?)) +; ?prev-ie-left <- (interface-element (value ?prev-input)) +; ?cur-line <- (line (solution-steps ?cur-ie-left ?)) +; ?cur-ie-left <- (interface-element (name ?s)) +; (studentValues (selection ?sel)(input ?input)) +; (test (and (eq ?s (sym-cat ?sel))(algEquivTerms ?prev-input ?input) +; (not (algEquivTermsSameOrder ?prev-input ?input)))) ; reordered +;=> +; (bind ?transf (assert (transformation (equation ?eq)(description reorder * *)))) ; no simplification needed +; (modify ?prob (cur-transformation ?transf)) +; +; (if (= TRUE ?*trace*) then +; (printout t "Swap sides" crlf) +; (print-eq ?new-eq) +; (printout t crlf)) +; ) + +(defrule done + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i done ButtonPressed -1)) + + (problem (cur-transformation nil)(cur-equation ?eq)) + ?eq <- (equation (sides $? ?s1 $?)) + ?eq <- (equation (sides $? ?s2 $?)) + (test (not (eq ?s1 ?s2))) + ?s1 <- (expr (terms ?t1)) + ?t1 <- (simple-term (coeff 1)(var ~nil)) + ?s2 <- (expr (terms ?t2)) + ?t2 <- (simple-term (var nil)) + +=> + (predict done ButtonPressed -1) + (if (= TRUE ?*trace*) then + (printout t "Done!" crlf) + (printout t crlf)) + + (construct-message [ You have isolated x. ] + [ You are done with this problem. ] + [ Click the Done button. ] + ) + ) + +; not done: either one side has a complex term, or multiple simple terms, or there is no term +; with with x isolated --- could write separate rules for these cases to avoid the or +; --> though how to avoid multiple activations ?? + +; one of the sides has multiple terms +(defrule bug-not-done-1 + (declare (salience 10000000)) + (hint (now FALSE)) + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i done ButtonPressed -1)) + + (problem (cur-equation ?eq)(cur-step nil)) + ?eq <- (equation (sides ?e1 ?e2)) + ?e1 <- (expr (terms $?t1)) + ?e2 <- (expr (terms $?t2)) + (test (or (> (length$ $?t1) 1)(> (length$ $?t2) 1))) + +=> + (predict done ButtonPressed -1) + + ;; TO DO: assumes x as variable ... + (construct-message [ You are not done yet with this problem. + You need to have x by itself. ] ) + ) +; +;; there is a variable term whose constant is not 1 ---- how to avoid multiple activations? +;; is it even important to avoid multiple activations? +(defrule bug-not-done-2 + (declare (salience 10000000)) + (hint (now FALSE)) + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i done ButtonPressed -1)) + + (problem (cur-equation ?eq)(cur-step nil)) + ?eq <- (equation (sides ?e1 ?e2)) + ?e1 <- (expr (terms ?)) ; one term only + ?e2 <- (expr (terms ?)) ; one term only + + ?eq <- (equation (sides $? ?expr $?)) + ?expr <- (expr (terms ?t)) + + ?t <- (simple-term (var ~nil)(coeff ~1)) +=> + (predict done ButtonPressed -1) + + ;; TO DO: assumes x as variable ... + (construct-message [ You are not done yet with this problem. + You need to have x by itself. ] ) + ) + +(defrule bug-not-done-3 + (declare (salience 10000000)) + (hint (now FALSE)) + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i done ButtonPressed -1)) + + (problem (cur-equation ?eq)(cur-step nil)) + ?eq <- (equation (sides ?e1 ?e2)) + ?e1 <- (expr (terms ?)) + ?e2 <- (expr (terms ?)) + + ?eq <- (equation (sides $? ?expr $?)) + ?expr <- (expr (terms ?t)) + ?t <- (product-term) + +=> + (predict done ButtonPressed -1) + + ;; TO DO: assumes x as variable ... + (construct-message [ You are not done yet with this problem. + You need to have x by itself. ] ) + ) + +(defrule bug-not-done-4 + (declare (salience 10000000)) + (hint (now FALSE)) + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i done ButtonPressed -1)) + + (problem (cur-equation ?eq)(cur-step nil)) + ?eq <- (equation (sides ?e1 ?e2)) + ?e1 <- (expr (terms ?)) + ?e2 <- (expr (terms ?)) + + ?eq <- (equation (sides $? ?expr $?)) + ?expr <- (expr (terms ?t)) + ?t <- (quotient-term) + +=> + (predict done ButtonPressed -1) + + ;; TO DO: assumes x as variable ... + (construct-message [ You are not done yet with this problem. + You need to have x by itself. ] ) + ) + +(defrule bug-invalid-expression + (declare (salience 10000000)) + (hint (now FALSE)) + (studentValues (selection ?s)(action ?a)(input ?i)) + + (problem (open-lines ?l $?)) + ?l <- (line (solution-steps $? ?ie $?)) + ?ie <- (interface-element (name ?sel)(value nil)) + + (test (lhs-predict-oa ?s ?a ?i ?sel UpdateTextField "whatever" stuExpressionNotValid)) + + => + (predict ?sel UpdateTextField "whatever" stuExpressionNotValid) + + (construct-message [ This is not a valid algebra expression. ] ) + ) + +;;----------------------------------------------------------------------------------------------------------- +;; +;; Transformation rules - Flexible strategy +;; + +(defrule hint-move-simple-term ; move a simple term regardless of whether there is a like + ; term on the other side - not necessarily strategic; however, cannot move 0 + (declare (salience 200)) +; (test (is-hint)) + (hint (now TRUE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil) + (hint-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides $? ?side $?)) + ?side <- (expr (terms $?before ?move-term $?after)) + ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var)) ; cannot move 0 (perhaps 0 should not be a term ...) + ?side <- (expr (terms $? ?unlike-term $?)) + ?unlike-term <- (simple-term (var ?var2&~?var)) ; there is an unlike term on the side being moved from + ?eq <- (equation (sides $? ?other-side $?)) + (test (neq ?side ?other-side)) +; ?other-side <- (expr (terms $? ?like-term-or-zero $?)) +; ?like-term-or-zero <- (simple-term (coeff ?coeff2)(var ?var3)) +; (test (or (= ?coeff2 0)(eq ?var3 ?var))) ; other side has like term or is 0 + ; how can we prevent multiple matches for ?like-term-or-zero + ?other-side <- (expr (terms $?other-terms)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms $?right-terms)) + ?left <- (expr (terms $?left-terms)) + + ;; --- this is for efficiency in help cycles --- prune the activations that are not preferred + (test (recommend-move-simple-term-p $?left-terms $?right-terms ?move-term)) + +=> + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) + (bind ?new-term-string (term->string ?new-term FALSE FALSE FALSE)) + (bind ?new-side (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved +; (bind ?new-other-side (assert (expr (terms $?other-terms ?new-term)))) + ; inserts the new term after the like term ??? now it is inserted at the end + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?other-terms)) + (bind ?new-other-side (assert (expr (terms $?new-terms)))) + + ; now figure out on which side simplification is needed since we are moving a term, simplification is + ; needed on the side from which we are moving the term + ; now, which side is which? could avoid this by having two rules; would usually not increase + ; the conflict tree; + (if (eq ?side ?left) + then (bind ?new-eq (assert (equation (sides ?new-side ?new-other-side)))) + (bind $?simplify (create$ left)) + (bind ?side-symbol left) + else (bind ?new-eq (assert (equation (sides ?new-other-side ?new-side)))) + (bind $?simplify (create$ right)) + (bind ?side-symbol right) + ) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) ?var)))) + ; TO DO: define simple Jess function var-term-p + $?other-terms))) + then (bind $?simplify (create$ left right))) ; otherwise, $?simplify stays what it was + ; could be done more smoothly on LHS with simple pattern, see above (at least or the + ; for the case where there is a like term) - would result in two rules but not in a + ; bigger conflict tree because at any time at most one would match + ; --> however, the rule that tests that there is NOT a like term will probably look + ; like the RHS function call here ... nonetheless, would allow for more + ; fine-grained student modeling, so preferable in that sense? + ; --> so could see splitting into 4 or even 5, depending on which direction and whether + ; or not the other side has a like term, does not have a like term, or is 0 + ; --> and of course could split even further, depending on whether a constant or a + ; variable term is moved + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + ))) + (modify ?prob (hint-transformation ?transf)) ; this slot is only used here - so we can have a + ; rule for knowledge tracing to follow (hint-move-constant-term and hint-move-variable-term) + +; ;; --------- Hints ----------- +; ;; If (C)V = CV, recommend moving the var to the left. +; ;; If CV = V, recommend moving the var to the right. +; ;; If C = CV, recommend moving the constant to the left +; ;; If CV = C, recommend moving the constant to the right +; ;; If CV = 0, recommend moving the constant to the right. +; ;; If 0 = CV, recommend moving the var to the left. +; +; ;; Could analyze if there are any other possible matches besides the ones listed above ... +; +; ;; Possibilities: +; ;; CV = CV | +; ;; C = CV | +; ;; V = CV | +; ;; CV = V | +; ;; CV = C | +; ;; CV = 0 | +; ;; 0 = CV | +; +; (bind ?lv (find-var-term $?left-terms)) +; (bind ?lc (find-const-term $?left-terms)) +; (bind ?rv (find-var-term $?right-terms)) +; (bind ?rc (find-const-term $?right-terms)) +; +; (printout t crlf "move-simple-term: ?lv = " ?lv ", ?lc = " ?lc ", ?rv = " ?rv ", ?rc = " ?rc crlf) +; +; (if ; (like-term-or-zero-p ?var $?other-terms) +; (and (not (like-terms-p $?left-terms)) ; combining like terms should be done first +; (not (like-terms-p $?right-terms)) +; (or (and ?lv ?rv ?rc (eq ?rv ?move-term)) ; If (C)V = CV, move var to the left. +; (and ?lc ?lv ?rv (not ?rc)(eq ?lv ?move-term)) ; If CV = V, move var to the right +; (and ?lc (not ?lv) ?rc ?rv (eq ?rc ?move-term)) ; If C = CV, move const to the left +; (and ?lc ?lv ?rc (not ?rv)(eq ?lc ?move-term)) ; If CV = C, move const to the right +; (and ?lc ?lv (not ?rc)(not ?rv)(eq ?lc ?move-term)) ; If CV = 0, move const to the right +; (and (not ?lc)(not ?lv) ?rc ?rv (eq ?rv ?move-term)) ; If 0 = CV, move var to the left +; )) + + ;; the logic above has been moved into function recommend-move-simple-term-p + ;; + +; For ax + b = cx + d, how about: +; "To solve for x, you have to put all the variables on one side, and the constants on the other. +; So you want to have ax and cx on the left side first." +; - "What do you need to do to have both ax and cx on the left side?" +; - "Subtract cx from both sides." - "Write ax+b-cx" + + ; find unlike term on the same side + ; find like term on the other side + + (if (recommend-move-simple-term-p $?left-terms $?right-terms ?move-term) ; this determines whether + ; we give a hint at all (although we have already tested this on the lhs, so + ; redundant .... ) + then + (bind ?the-var (if (eq nil ?var) then ?var2 else ?var)) ; the var we are solving for + (bind ?move-var-p (if (eq nil ?var) then FALSE else TRUE)) ; are we moving a var or const? + (bind ?like-term-other-side ; is there a like term on the other side? + (if ?move-var-p + then (find-var-term $?other-terms) + else (find-const-term $?other-terms))) + (bind ?move-term-str (term->string ?move-term FALSE FALSE FALSE)) + (if (> ?coeff 0) + then + (bind ?move-verb Subtract) + (bind ?add/subtr-term-str ?move-term-str) + else + (bind ?add/subtr-term-str (term->string ?new-term FALSE FALSE FALSE)) + (bind ?move-verb Add)) + + (if ?like-term-other-side + then + (bind ?like-term-other-side-str (term->string ?like-term-other-side FALSE FALSE FALSE)) + (construct-message ; [ To solve for (str-cat ?the-var ,) you need to have only variable terms on + ; one side and only constant terms on the other. ] + [ You have (if ?move-var-p then variables else constants) + on both sides: (if (eq ?side-symbol left) + then ?move-term-str + else ?like-term-other-side-str) on the left + and (if (eq ?side-symbol left) + then ?like-term-other-side-str + else ?move-term-str) on the right. + How can you have all (if ?move-var-p then variables else constants) + on the (if (eq ?side-symbol left) then right else left) + and no (if ?move-var-p then variables else constants) + on the (if (eq ?side-symbol left) then left? else right?) + ] + [ How can you get rid of ?move-term-str on the ?side-symbol side? ] + [ ?move-verb ?add/subtr-term-str + (if (eq ?move-verb Add) then to else from) both sides. ] ) + else + ; otherwise we have a zero term on the other side, I believe. + ; because if there was an unlike term on the other side (and no like term), as well as an + ; unlike term on this side, we should not recommend to move the given term + (bind ?unlike-term-str (term->string ?unlike-term FALSE FALSE FALSE)) + (construct-message ; [ To solve for (str-cat ?the-var ,) you need to have only variable terms on + ; one side and only constant terms on the other. ] + [ You have both a variable term and a constant on the + (str-cat ?side-symbol :) + (if ?move-var-p then ?move-term-str else ?unlike-term-str) + and (str-cat (if ?move-var-p then ?unlike-term-str else ?move-term-str ) .) + How can you have only a + (if (or (and (eq ?side-symbol right) ?move-var-p) + (and (eq ?side-symbol left)(not ?move-var-p))) + then "variable term" else constant) + on the left and only a + (if (or (and (eq ?side-symbol right) ?move-var-p) + (and (eq ?side-symbol left)(not ?move-var-p))) + then constant else "variable term") + on the right? ] + [ How can you get rid of ?move-term-str on the ?side-symbol side? ] + [ ?move-verb ?add/subtr-term-str + (if (eq ?move-verb Add) then to else from) both sides. ] + ; Otherwise: however, you have both a variable and constant on + ; the ... side. + ; how can you make sure the variable is by itself + ; on the left/right side? + )) + else + (modify ?transf (no-hints-p TRUE))) ; so the write rules know + + ;; bottom-out hint will need to be done in a subsequent rule + + + (if (= TRUE ?*trace*) then + (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) " to the right" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +;; these two rules just serve to mark the skill, while keeping the logic for deciding what move to +;; recommend in a single rule +(defrule hint-move-constant-term + "Skills:Add/subtract_constant_from_both_sides LDashb" + (hint (now TRUE)) + ?p <- (problem (hint-transformation ?transf)) + ?transf <- (transformation (description add ?move-term ?)) + ?move-term <- (simple-term (var nil)) + => + (modify ?p (hint-transformation nil)(cur-transformation ?transf)) + ) + +(defrule hint-move-variable-term + "Skills:Add/subtract_variable_from_both_sides LDashb" + (hint (now TRUE)) + ?p <- (problem (hint-transformation ?transf)) + ?transf <- (transformation (description add ?move-term ?)) + ?move-term <- (simple-term (var ~nil)) + => + (modify ?p (hint-transformation nil)(cur-transformation ?transf)) + ) + +; assume students are inclined to move constants right and variables left +; so we make the move-constant-right rule have higher priority than the move-constant-left rule, so +; as to speed up the model tracer +; moving a simple term is considered strategic if afterwards you have unlike terms on one fewer side than +; before; i.e., there must be a like term on the side being moved to +(defrule move-constant-right-strategically ; move a constant term to the right + "Skills:Add/subtract_constant_from_both_sides LDashb" + (declare (salience 201)) + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?before ?move-term $?after)) + + ; --- term to be moved has to be a constant term --- + ?move-term <- (simple-term (coeff ?coeff&~0)(var nil)) ; cannot move 0 (perhaps 0 should not be a term ...) + + ; if the constant is the only thing on the left, cannot move it (would be un-strategic) + ; now outlow cases such as -4 = 4x --> 0 = 4x + 4 + ; and 6 = x + 13 --> 0 = x + 7 + ; i.e., the most egregious examples of non-strategic moves + ?left <- (expr (terms $?left-terms)) + (test (> (length$ $?left-terms) 1)) + + ; also, there has to be a constant term on the right for the move to be strategic + ?right <- (expr (terms $?right-terms)) + ; to avoid multiple activations, use function call rather than matching + (test (or (find-const-term $?right-terms)(is-zero-term $?right-terms))) + +=> + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)))) + (bind ?new-left (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved + + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?right-terms)) + (bind ?new-right (assert (expr (terms $?new-terms)))) + + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind $?simplify (create$ left)) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) nil)))) + $?right-terms))) + then (bind $?simplify (create$ left right))) + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + (to-side right) + ))) + (modify ?prob (cur-transformation ?transf)) + + (if (= TRUE ?*trace*) then + (printout t "Move constant right: " (term->string ?move-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule move-constant-right-unstrategically ; move a constant term to the right +; "Skills:Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb" + (declare (salience 101)) + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?before ?move-term $?after)) + + ; --- term to be moved has to be a constant term --- + ?move-term <- (simple-term (coeff ?coeff&~0)(var nil)) ; cannot move 0 (perhaps 0 should not be a term ...) + + ; if the constant is the only thing on the left, cannot move it (would be un-strategic) + ; outlaws cases such as -4 = 4x --> 0 = 4x + 4 + ; and 6 = x + 13 --> 0 = x + 7 + ; i.e., the most egregious examples of non-strategic moves (we keep this condition even though + ; the rule is meant to capture some unstrategic moves) + ?left <- (expr (terms $?left-terms)) + (test (> (length$ $?left-terms) 1)) + + ; here we negate the condition for the move to be strategic + ?right <- (expr (terms $?right-terms)) + ; to avoid multiple activations, use function call rather than matching + (test (not (or (find-const-term $?right-terms)(is-zero-term $?right-terms)))) + => + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)))) + (bind ?new-left (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved + + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?right-terms)) + (bind ?new-right (assert (expr (terms $?new-terms)))) + + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind $?simplify (create$ left)) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) nil)))) + $?right-terms))) + then (bind $?simplify (create$ left right))) + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + (strategic-p FALSE) + (to-side right) + ))) + (modify ?prob (cur-transformation ?transf)) + +; (construct-success-message [ What you did is OK but there is a better thing you can do. +; To find out, first back up by clicking Undo. Then click Hint. ]) + + (if (= TRUE ?*trace*) then + (printout t "Move constant right: " (term->string ?move-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule move-constant-left-strategically ; move a constant term to the left - lower priority than move-constant-right + "Skills:Add/subtract_constant_from_both_sides LDashb" + (declare (salience 21)) ; assumption: students tend to want to move constants to the right + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms $?before ?move-term $?after)) + + ; --- term to be moved has to be a constant term --- + ?move-term <- (simple-term (coeff ?coeff&~0)(var nil)) ; cannot move 0 + + ; outlaw some egregiously unstrategic cases such as 4x = -4 --> 4x + 4 = 0 + ; and x + 13 = 6 --> x + 7 = 0 + ?right <- (expr (terms $?right-terms)) + (test (> (length$ $?right-terms) 1)) + + ; also, require that there is a like term (i.e., a constant term) on the left already + ; (for the move to be strategic) + ?left <- (expr (terms $?left-terms)) + (test (or (find-const-term $?left-terms)(is-zero-term $?left-terms))) + + ; TO DO? this rule also takes care of moving a constant if there is a product term + +=> + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)))) + (bind ?new-right (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved + + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?left-terms)) + (bind ?new-left (assert (expr (terms $?new-terms)))) + + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind $?simplify (create$ right)) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) nil)))) + $?left-terms))) + then (bind $?simplify (create$ left right))) + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + (to-side left) + ))) + (modify ?prob (cur-transformation ?transf)) + + (if (= TRUE ?*trace*) then + (printout t "Move constant left: " (term->string ?move-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule move-constant-left-unstrategically ; move a constant term to the left - lower priority than move-constant-right +; "Skills:Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb;Add/subtract_constant_from_both_sides LDashb" + (declare (salience 20)) ; assumption: students tend to be somewhat strategic (maybe) + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms $?before ?move-term $?after)) + + ; --- term to be moved has to be a constant term --- + ?move-term <- (simple-term (coeff ?coeff&~0)(var nil)) ; cannot move 0 + + ; outlaw some egregiously unstrategic cases such as 4x = -4 --> 4x + 4 = 0 + ; and x + 13 = 6 --> x + 7 = 0 + ?right <- (expr (terms $?right-terms)) + (test (> (length$ $?right-terms) 1)) + + ; there is no like term (i.e., a constant term) on the left already + ; (that's why the move is not strategic) + ?left <- (expr (terms $?left-terms)) + (test (not (or (find-const-term $?left-terms)(is-zero-term $?left-terms)))) + => + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)))) + (bind ?new-right (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved + + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?left-terms)) + (bind ?new-left (assert (expr (terms $?new-terms)))) + + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind $?simplify (create$ right)) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) nil)))) + $?left-terms))) + then (bind $?simplify (create$ left right))) + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + (strategic-p FALSE) + (to-side left) + ))) + (modify ?prob (cur-transformation ?transf)) + +; (construct-success-message [ What you did is OK but there is a better thing you can do. +; To find out, first back up by clicking Undo. Then click Hint. ]) + + (if (= TRUE ?*trace*) then + (printout t "Move constant left: " (term->string ?move-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule move-variable-left-strategically + "Skills:Add/subtract_variable_from_both_sides LDashb" + (declare (salience 200)) ; assume students like to move variables to the left + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms $?before ?move-term $?after)) + ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var&~nil)) ; cannot move 0 (perhaps 0 should not be a term ...) + + ; outlaw egregious unstrategive moves such as 4x = -4 --> 0 = -4 - 4x + ; or 4 = -4x --> 4x + 4 = 0 + ?right <- (expr (terms $?right-terms)) + ?left <- (expr (terms $?left-terms)) + (test (not (and (= 1 (length$ $?left-terms))(= 1 (length$ $?right-terms))))) + + ; additionally, require that there is a variable term on the other side + (test (or (find-var-term $?left-terms)(is-zero-term $?left-terms))) + +=> + ;; ---- update the side we are moving from - could become 0 + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) + (bind ?new-right (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved + ; TO DO: follow what the student does + + ;; ---- update the side we are moving to + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?left-terms)) + (bind ?new-left (assert (expr (terms $?new-terms)))) + ; inserts the new term after a like term if there is one + ; TO DO: follow what the student does + + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind $?simplify (create$ right)) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) ?var)))) + ; TO DO: define simple Jess function var-term-p + $?left-terms))) + then (bind $?simplify (create$ left right))) ; otherwise, $?simplify stays what it was + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + (to-side left) + ))) + (modify ?prob (cur-transformation ?transf)) + + (if (= TRUE ?*trace*) then + (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule move-variable-left-unstrategically +; "Skills:Add/subtract_variable_from_both_sides LDashb;Add/subtract_variable_from_both_sides LDashb;Add/subtract_variable_from_both_sides LDashb" + (declare (salience 100)) ; assume students like to move variables to the left + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms $?before ?move-term $?after)) + ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var&~nil)) ; cannot move 0 (perhaps 0 should not be a term ...) + + ; outlaw egregious unstrategive moves such as 4x = -4 --> 0 = -4 - 4x + ; or 4 = -4x --> 4x + 4 = 0 + ?right <- (expr (terms $?right-terms)) + ?left <- (expr (terms $?left-terms)) + (test (not (and (= 1 (length$ $?left-terms))(= 1 (length$ $?right-terms))))) + + ; unstrategic because there is no variable term on the other side + (test (not (or (find-var-term $?left-terms)(is-zero-term $?left-terms)))) + + => + ;; ---- update the side we are moving from - could become 0 + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) + (bind ?new-right (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved + ; TO DO: follow what the student does + + ;; ---- update the side we are moving to + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?left-terms)) + (bind ?new-left (assert (expr (terms $?new-terms)))) + ; inserts the new term after a like term if there is one + ; TO DO: follow what the student does + + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind $?simplify (create$ right)) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) ?var)))) + ; TO DO: define simple Jess function var-term-p + $?left-terms))) + then (bind $?simplify (create$ left right))) ; otherwise, $?simplify stays what it was + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + (strategic-p FALSE) + (to-side left) + ))) + (modify ?prob (cur-transformation ?transf)) + +; (construct-success-message [ What you did is OK but there is a better thing you can do. +; To find out, first back up by clicking Undo. Then click Hint. ]) + + (if (= TRUE ?*trace*) then + (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + + +(defrule move-variable-right-strategically + "Skills:Add/subtract_variable_from_both_sides LDashb" + (declare (salience 11)) + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?before ?move-term $?after)) + ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var&~nil)) ; cannot move 0 (perhaps 0 should not be a term ...) + + ?right <- (expr (terms $?right-terms)) + ?left <- (expr (terms $?left-terms)) + + ; this outlaws the case 4x = -4 --> 0 = -4 - 4x + ; or 4 = -4x --> 4x + 4 = 0 + (test (not (and (= 1 (length$ $?left-terms))(= 1 (length$ $?right-terms))))) + + (test (or (find-var-term $?right-terms)(is-zero-term $?right-terms))) +=> + ;; ---- update the side we are moving from - could become 0 + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) + (bind ?new-left (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved + ; TO DO: follow what the student does + + ;; ---- update the side we are moving to + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?right-terms)) + (bind ?new-right (assert (expr (terms $?new-terms)))) + ; inserts the new term after a like term if there is one + ; TO DO: follow what the student does + + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind $?simplify (create$ left)) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) ?var)))) + ; TO DO: define simple Jess function var-term-p + $?right-terms))) + then (bind $?simplify (create$ left right))) ; otherwise, $?simplify stays what it was + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + (to-side right) + ))) + (modify ?prob (cur-transformation ?transf)) + + (if (= TRUE ?*trace*) then + (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule move-variable-right-unstrategically ; unlikely move by students so low salience +; "Skills:Add/subtract_variable_from_both_sides LDashb;Add/subtract_variable_from_both_sides LDashb;Add/subtract_variable_from_both_sides LDashb" + (declare (salience 10)) + (hint (now FALSE)) + ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?before ?move-term $?after)) + ?move-term <- (simple-term (coeff ?coeff&~0)(var ?var&~nil)) ; cannot move 0 (perhaps 0 should not be a term ...) + + ?right <- (expr (terms $?right-terms)) + ?left <- (expr (terms $?left-terms)) + + ; this outlaws the case 4x = -4 --> 0 = -4 - 4x + ; or 4 = -4x --> 4x + 4 = 0 + (test (not (and (= 1 (length$ $?left-terms))(= 1 (length$ $?right-terms))))) + + ; move is not strategic because there is not variable term on the right + (test (not (or (find-var-term $?right-terms)(is-zero-term $?right-terms)))) + => + ;; ---- update the side we are moving from - could become 0 + (bind ?new-coeff (* -1 ?coeff)) + (bind ?new-term (assert (simple-term (coeff ?new-coeff)(var ?var)))) + (bind ?new-left (assert (expr (terms $?before ?move-term ?new-term $?after)))) + ; inserts the new term immediately following the term being moved + ; TO DO: follow what the student does + + ;; ---- update the side we are moving to + (bind $?new-terms (insert-term-after-like-term$ ?new-term $?right-terms)) + (bind ?new-right (assert (expr (terms $?new-terms)))) + ; inserts the new term after a like term if there is one + ; TO DO: follow what the student does + + (bind ?new-eq (assert (equation (sides ?new-left ?new-right)))) + (bind $?simplify (create$ left)) + + ; does the other side have a like term? (if so, simplification is needed on that side as well ... hmmm ... + ; what if there were multiple like terms on the other side?) + ; if other side has zero term, simplification is needed as well, even if it is an unlike term + (if (< 0 (length$ (filter (lambda (?x) (and (eq (fact-slot-value ?x type) simple-term) + (or (eq (fact-slot-value ?x coeff) 0) + (eq (fact-slot-value ?x var) ?var)))) + ; TO DO: define simple Jess function var-term-p + $?right-terms))) + then (bind $?simplify (create$ left right))) ; otherwise, $?simplify stays what it was + + (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified $?simplify) + (description add ?new-term both) + (strategic-p FALSE) + (to-side right) + ))) + (modify ?prob (cur-transformation ?transf)) + +; (construct-success-message [ What you did is OK but there is a better thing you can do. +; To find out, first back up by clicking Undo. Then click Hint. ]) + + (if (= TRUE ?*trace*) then + (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + + + +;(defrule move-constant-term-to-isolate-product ; move a constant when one side is a constant plus +; ; product of constant times linear expression and the other side is constant +; ; and the division that would follow does not yield a fraction +;; (declare (salience 200000000000)) ; low priority? students may not do this +; +; ;; --- this is for efficiency in help cycles --- we will not recommend this action so no need +; ;; for the rule to apply in help cycles +;; (test (not (is-hint))) +; (hint (now FALSE)) +; +; ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) +; +; ;; one side has a constant term only +; ?eq <- (equation (sides $? ?s2 $?)) +; ?s2 <- (expr (terms ?constant-term)) +; ?constant-term <- (simple-term (coeff ?c2)(var nil)) ; ?c2 can be 0 +; +; ;; the other side has a constant term plus a product term +; ?eq <- (equation (sides $? ?s1 $?)) +; ?s1 <- (expr (terms ? ?)) +; ?s1 <- (expr (terms $? ?move-term $?)) +; ?move-term <- (simple-term (coeff ?c1&~0)(var nil)) ; cannot move 0 (perhaps 0 should not be a term ...) +; ?s1 <- (expr (terms $? ?prod-term $?)) +; ?prod-term <- (product-term (factors ? ?)) ; may not happen that there are more than 2 but just in case +; ?prod-term <- (product-term (factors $? ?f1 $?)) +;; ?f1 <- (expr (terms ? ?)) +; ?f1 <- (expr (terms ?constant-prod-term)) +; ?constant-prod-term <- (simple-term (coeff ?c3&~0)(var nil)) +; (test (= 0 (mod (- ?c2 ?c1) ?c3))) ; checking we do not get a fraction +; +; ?prod-term <- (product-term (factors $? ?f2 $?)) +; ?f2 <- (expr (terms ? ?)) ; could test this some more, but may not be necessary +; ; TO DO: do we really want this condition? +; +; ; just binding some more vars needed on the rhs +; ?eq <- (equation (sides ?left ?)) +; ?s1 <- (expr (terms ?first-term $?)) +; +;=> +; (bind ?new-coeff (* -1 ?c1)) +; (bind ?new-term (assert (simple-term (coeff ?new-coeff)))) +; (bind ?new-move-to-side (assert (expr (terms ?constant-term ?new-term )))) +; +; (if (eq ?move-term ?first-term) +; then (bind ?new-move-from-side (assert (expr (terms ?move-term ?new-term ?prod-term)))) +; else (bind ?new-move-from-side (assert (expr (terms ?prod-term ?move-term ?new-term ))))) +; +; ;; now figure out which side is left and which side is right +; ;; (alternatively, could have specialized rules) +; (if (eq ?s1 ?left) +; then (bind ?new-eq (assert (equation (sides ?new-move-from-side ?new-move-to-side)))) +; else (bind ?new-eq (assert (equation (sides ?new-move-to-side ?new-move-from-side))))) +; +; (bind ?transf (assert (transformation (equation ?new-eq)(to-be-simplified left right) +; (description add ?new-term both) +; ))) +; (modify ?prob (cur-transformation ?transf)) +; +; (modify ?transf (no-hints-p TRUE)) ; so the write rules know -- probably not necessary since +; ; the rule will not apply in hint cycles +; +; (if (= TRUE ?*trace*) then +; (printout t "Move " (term->string ?move-term FALSE FALSE FALSE) crlf) +; (print-eq ?new-eq) +; (printout t crlf)) +; ) + + +;(defrule combine-like-terms-right-flexible +; ; TO DO: should we have separate rules for combining constants and combining variables? +; ; this rule works for constant terms and variable terms +; ; would it be better to have one cancel rule that works for both sides? or start with a maximally granular model? +; ?prob <- (problem (cur-equation ?eq)(cur-transformation nil)(cur-step ~nil)) +; ?eq <- (equation (sides ?left ?right)) ; probably won't work in the long run ... +;; ?left <- (expr (terms $?left-terms)) +;; ?right <- (expr (terms $?right-terms)) +;; (test (simple-terms-only-p$ $?left-terms)) +;; (test (simple-terms-only-p$ $?right-terms)) ; to strictly enforce the standard strategy +; ?right <- (expr (terms $?before ?t1 $?between ?t2 $?after)) +; ?t1 <- (simple-term (coeff ?c1)(var ?var)) +; ?t2 <- (simple-term (coeff ?c2)(var ?var)) +;=> +; (bind ?new-term (assert (simple-term (coeff (+ ?c1 ?c2))(var ?var)))) +; (bind ?new-right (assert (expr (terms $?before ?new-term $?between $?after)))) +; (bind ?new-eq (assert (equation (sides ?left ?new-right)))) ; this step would be more difficult in a rule that works on both sides ... +; (bind ?transf (assert (transformation (equation ?new-eq) +; (description combine 0 right)(skip-expl-sel2 TRUE)))) ; 0 just a place holder +; ; no intermediate step for combine like terms? +; (modify ?prob (cur-transformation ?transf)) +; +; (if (= TRUE ?*trace*) then +; (printout t "Combine like terms " (term->string ?t1 FALSE FALSE FALSE) +; " and " (term->string ?t2 FALSE FALSE FALSE)" on the right" crlf) +; (print-eq ?new-eq) +; (printout t crlf)) +; ) + + +;;----------------------------------------------------------------------------------------------------------- +;; +;; Simplification rules +;; +;; These rules capture the simplification steps needed to fully "implement" the main operations. +;; They have in common that they require a transformation fact and modify it. +;; They need to ensure that they only simplify the side of the equation that is in focus (as specified by +;; the focus slot of the transformation fact). + +;; make sure the simplify rules work with the to-be-simplified slot + +;; TO DO: why have separate simplify rules for simplifying a var term v. constant term? (answer: fine-grained modeling) +;; Alternatively, could have separate rules, but move the shared body into a single rule, to avoid the duplication we +;; have now. +;; +(defrule simplify-eliminate-simple-quotient-var-term-left + ; if the left side is a single quotient term of the form (d var / d), drop the d's + ; analogous to the cancel-terms-left + "Skills:Compute_quotient_for_variable_coefficient LDashb" + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(focus left)(to-be-simplified $?bef left $?aft)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms ?left-quotient)) + ?left-quotient <- (quotient-term (factors ?dividend-expr ?divisor-expr)) + ?dividend-expr <- (expr (terms ?dividend-term)) + ?dividend-term <- (simple-term (coeff ?d)(var ?var)) + ?divisor-expr <- (expr (terms ?divisor-term)) + ?divisor-term <- (simple-term (coeff ?d)(var nil)) + +=> + (bind ?new-term-left (assert (simple-term (coeff 1)(var ?var)))) + (bind ?new-left (assert (expr (terms ?new-term-left)))) + (bind ?new-eq (assert (equation (sides ?new-left ?right)))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?bef $?aft)) + + (construct-message [ Now simplify the variable term on the left. ] + [ What is (term->string ?left-quotient FALSE FALSE FALSE) "?" ] ) + + (if (= TRUE ?*trace*) then + (printout t "Eliminate quotient on the left side" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +; could potentially generalize to a(bx + c)/d with a mod d = 0 +(defrule simplify-eliminate-quotient-left + ; if is a quotient of the form a(bx + c)/a then drop the a's + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(focus left)(to-be-simplified $?bef left $?aft)) + ?eq <- (equation (sides ?s1 ?s2)) + ?s1 <- (expr (terms ?q)) + ?q <- (quotient-term (factors ?dividend-expr ?divisor-expr)) + ?dividend-expr <- (expr (terms ?dividend-term)) + ?dividend-term <- (product-term (factors $? ?f1 $?)) + ?f1 <- (expr (terms ?t1)) + ?t1 <- (simple-term (coeff ?d&~nil)(var nil)) + + ?dividend-term <- (product-term (factors $? ?f2 $?)) + ?f2 <- (expr (terms ? ?)) ; minimal test - we are not messing with ?f2 + + ?divisor-expr <- (expr (terms ?divisor-term)) + ?divisor-term <- (simple-term (coeff ?d)(var nil)) ; same coefficient + +=> + (bind ?new-eq (assert (equation (sides ?f2 ?s2)))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?bef $?aft)) + + (construct-message [ Now simplify the term on the left. ] + ; [ What is (term->string ?q FALSE FALSE FALSE) "?" ] + [ You multiply by ?d and you divide by (str-cat ?d ".") + The two (str-cat ?d "s") cancel each other out. You may remove them. ] + ) + + (if (= TRUE ?*trace*) then + (printout t "Eliminate quotient on the left side" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule simplify-eliminate-quotient-right + ; if is a quotient of the form a(bx + c)/a then drop the a's + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(focus right)(to-be-simplified $?bef right $?aft)) + ?eq <- (equation (sides ?s2 ?s1)) + ?s1 <- (expr (terms ?q)) + ?q <- (quotient-term (factors ?dividend-expr ?divisor-expr)) + ?dividend-expr <- (expr (terms ?dividend-term)) + ?dividend-term <- (product-term (factors $? ?f1 $?)) + ?f1 <- (expr (terms ?t1)) + ?t1 <- (simple-term (coeff ?d&~nil)(var nil)) + + ?dividend-term <- (product-term (factors $? ?f2 $?)) + ?f2 <- (expr (terms ? ?)) ; minimal test - we are not messing with ?f2 + + ?divisor-expr <- (expr (terms ?divisor-term)) + ?divisor-term <- (simple-term (coeff ?d)(var nil)) ; same coefficient + +=> + (bind ?new-eq (assert (equation (sides ?s2 ?f2 )))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?bef $?aft)) + + (construct-message [ Now simplify the term on the right. ] + [ You multiply by ?d and you divide by (str-cat ?d ".") + The two (str-cat ?d "s") cancel each other out. You may remove them. ]) + + (if (= TRUE ?*trace*) then + (printout t "Eliminate quotient on the right side" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule simplify-eliminate-simple-quotient-var-term-right + ; if the left side is a single quotient term of the form (d var / d), drop the d's + ; analogous to the cancel-terms-left + "Skills:Compute_quotient_for_variable_coefficient LDashb" + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(focus right)(to-be-simplified $?bef right $?aft)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms ?right-quotient)) + ?right-quotient <- (quotient-term (factors ?dividend-expr ?divisor-expr)) + ?dividend-expr <- (expr (terms ?dividend-term)) + ?dividend-term <- (simple-term (coeff ?d)(var ?var)) + ?divisor-expr <- (expr (terms ?divisor-term)) + ?divisor-term <- (simple-term (coeff ?d)(var nil)) + +=> + (bind ?new-term-right (assert (simple-term (coeff 1)(var ?var)))) + (bind ?new-right (assert (expr (terms ?new-term-right)))) + (bind ?new-eq (assert (equation (sides ?left ?new-right)))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?bef $?aft)) + + (construct-message [ Now simplify the variable term on the right. ] + [ What is (term->string ?right-quotient FALSE FALSE FALSE) "?" ] ) + + (if (= TRUE ?*trace*) then + (printout t "Eliminate quotient on the right side" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + + +;; NOTE: although simplify-divide-left was derived from simplify-divide-right, +;; its condition is broader +(defrule simplify-divide-constant-term-left ; if the left side has a quotient term of the form (a / b), + ; replace it by the quotient + ; assume integer quotient for now + "Skills:Compute_quotient_for_constant LDashb" + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(focus left) + (to-be-simplified $?before left $?after)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms ?left-quotient)) + ?left-quotient <- (quotient-term (factors ?dividend-expr ?divisor-expr)) + ?dividend-expr <- (expr (terms ?dividend-term)) + ?dividend-term <- (simple-term (coeff ?a)(var nil)) + ?divisor-expr <- (expr (terms ?divisor-term)) + ?divisor-term <- (simple-term (coeff ?b)(var nil)) + (test (= (div ?a ?b)(/ ?a ?b))) ; checks whether quotient is integer + ; just so we don't have to deal with the case where it is not +=> + (bind ?new-coeff (div ?a ?b)) + (bind ?new-term-left (assert (simple-term (coeff ?new-coeff)))) + (bind ?new-left (assert (expr (terms ?new-term-left)))) + (bind ?new-eq (assert (equation (sides ?new-left ?right)))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before $?after)) + + (construct-message [ Now simplify the left side by doing the division. ] + [ What is (term->string ?left-quotient FALSE FALSE FALSE) "?" ] ) + + (if (= TRUE ?*trace*) then + (printout t "Do the division on the left side" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +;; TO DO: broaden this rule so it deal with any simple quotient term on? +;; Or do we need narrow simplify rules for the standard strategy but broader simplify rules for the more +;; general case? +;; +(defrule simplify-divide-constant-term-right ; if the right side is a single quotient term of the form (a / b), replace by quotient + ; assume integer quotient for now + "Skills:Compute_quotient_for_constant LDashb" + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(focus right) + (to-be-simplified $?before right $?after)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms ?right-quotient)) + ?right-quotient <- (quotient-term (factors ?dividend-expr ?divisor-expr)) + ?dividend-expr <- (expr (terms ?dividend-term)) + ?dividend-term <- (simple-term (coeff ?a)(var nil)) + ?divisor-expr <- (expr (terms ?divisor-term)) + ?divisor-term <- (simple-term (coeff ?b)(var nil)) + (test (= (div ?a ?b)(/ ?a ?b))) ; checks whether quotient is integer + ; just so we don't have to deal with the case where it is not +=> + (bind ?new-coeff (div ?a ?b)) + (bind ?new-term-right (assert (simple-term (coeff ?new-coeff)))) + (bind ?new-right (assert (expr (terms ?new-term-right)))) + (bind ?new-eq (assert (equation (sides ?left ?new-right)))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before $?after)) + + (construct-message [ Now simplify the right side by doing the division. ] + [ What is (term->string ?right-quotient FALSE FALSE FALSE) "?" ] ) + + + (if (= TRUE ?*trace*) then + (printout t "Do the division on the right side" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +;; can apply twice in one chain (e.g., 2(x)+2(4)+3x-3=0) +;; how can we avoid generating two chains that only differ in the order in which the two products are dealt with? +(defrule simplify-eliminate-simple-product-left ; covers both a*b and ax*b (and commutative variants) + "Skills:Multiply_simple_terms LDashb" + (declare (salience 2000)) ; needs to go before write-intermediate-left in hint cycles + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(focus left)(to-be-simplified $?before1 left $?after1)) + (test (not (member$ left $?before1))) ; just to avoid matches that are essential duplicates + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?before2 ?simple-prod-term $?after2)) + ?simple-prod-term <- (product-term (factors ?expr1 ?expr2)) ; wonder if this level of structure is needed? + ?expr1 <- (expr (terms ?t1)) + ?t1 <- (simple-term (coeff ?c1)(var ?v1)) + ?expr2 <- (expr (terms ?t2)) + ?t2 <- (simple-term (coeff ?c2)(var ?v2)) + (test (or (eq nil ?v1)(eq nil ?v2))) ; rule does not deal with quadratic equations + + ?transf <- (transformation (to-be-simplified $?to-be-simplified)) +=> + (bind ?coeff (* ?c1 ?c2)) + (bind ?var (if (eq nil ?v1) then ?v2 else ?v1)) ; note this expression could be nil + (bind ?new-term (assert (simple-term (coeff ?coeff)(var ?var)))) + (bind ?new-left (assert (expr (terms $?before2 ?new-term $?after2)))) + (bind ?new-eq (assert (equation (sides ?new-left ?right)))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before1 $?after1)) + + ; we only give this hint when there are two multiplications to be simplified, so that the hint + ; level does not appear twice in the hint sequence + (if (> (nmember$ left $?to-be-simplified) 1) then + (construct-message ; [ Now simplify the expression on the left by doing the multiplications. ] + [ Now do the multiplications on the left. ] + )) + + (if (= TRUE ?*trace*) then + (printout t "Eliminate simple product on the left side" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule simplify-eliminate-simple-product-right ; covers both a*b and ax*b (and commutative variants) + "Skills:Multiply_simple_terms LDashb" + (declare (salience 2000)) ; needs to go before write-intermediate-right in hint cycles + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(focus right)(to-be-simplified $?before1 right $?after1)) + (test (not (member$ right $?before1))) ; just to avoid matches that are essential duplicates + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms $?before2 ?simple-prod-term $?after2)) + ?simple-prod-term <- (product-term (factors ?expr1 ?expr2)) ; wonder if this level of structure is needed? + ?expr1 <- (expr (terms ?t1)) + ?t1 <- (simple-term (coeff ?c1)(var ?v1)) + ?expr2 <- (expr (terms ?t2)) + ?t2 <- (simple-term (coeff ?c2)(var ?v2)) + (test (or (eq nil ?v1)(eq nil ?v2))) ; rule does not deal with quadratic equations + ?transf <- (transformation (to-be-simplified $?to-be-simplified)) + => + (bind ?coeff (* ?c1 ?c2)) + (bind ?var (if (eq nil ?v1) then ?v2 else ?v1)) ; note this expression could be nil + (bind ?new-term (assert (simple-term (coeff ?coeff)(var ?var)))) + (bind ?new-right (assert (expr (terms $?before2 ?new-term $?after2)))) + (bind ?new-eq (assert (equation (sides ?left ?new-right)))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before1 $?after1)) + + (if (> (nmember$ right $?to-be-simplified) 1) then + (construct-message ; [ Now simplify the expression on the right by doing the multiplications. ] + [ Now do the multiplications on the right. ] + )) + + (if (= TRUE ?*trace*) then + (printout t "Eliminate simple product on the right side" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + + + +(defrule simplify-combine-constant-terms + "Skills:Combine_constant_terms LDashb" + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(to-be-simplified $?before1 ?side $?after1)(focus ?side) + (description ~distribute ? ?)) ; to outlaw 2+3(-x + 2) --> 8+3(-x) etc. + + ?eq <- (equation (sides $? ?s $?)) + ?s <- (expr (terms $?before ?t1 $?between ?t2 $?after)) + ?t1 <- (simple-term (coeff ?c1)(var nil)) ; constant terms + ?t2 <- (simple-term (coeff ?c2)(var nil)) + (test (not (= ?c1 (* -1 ?c2)))) ; terms do not cancel each other out + + ?eq <- (equation (sides ?left ?right)) + (test (or (and (eq ?side left)(eq ?s ?left))(and (eq ?side right)(eq ?s ?right)))) +=> + (bind ?new-term (assert (simple-term (coeff (+ ?c1 ?c2))))) + (bind ?new-expr (assert (expr (terms $?before ?new-term $?between $?after)))) + + (if (eq ?side left) + then (bind ?new-eq (assert (equation (sides ?new-expr ?right)))) + else (bind ?new-eq (assert (equation (sides ?left ?new-expr))))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before1 $?after1)) + + + ;; --- Hints --- + (bind $?other-terms (create$ $?before $?between $?after)) + (bind ?other-terms-expr (assert (expr (terms $?other-terms)))) + (bind ?keep (if (> (length$ $?other-terms) 0) + then (str-cat You " " also " " need " " to " " keep " " (expr->str3 ?other-terms-expr) . ) + else "" )) + (retract ?other-terms-expr) ; not needed any more + + (construct-message + [ You have two constants on the (str-cat ?side ,) (term->string ?t1 FALSE FALSE FALSE) + and (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + If you add them, you will have only one constant, which is better. + This is called "\"combining like terms.\"" + ; How can you do so? + ] + [ On the (str-cat ?side ,) combine like terms by adding (term->string ?t1 FALSE FALSE FALSE) + and (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + This gives (str-cat (term->string ?new-term FALSE FALSE FALSE) .) + ?keep ] + ) +; (construct-message [ Simplify by combining like terms +; (term->string ?t1 FALSE FALSE FALSE) and (term->string ?t2 FALSE FALSE FALSE) +; on the ?side side. ] +; [ On the ?side side, add (term->string ?t1 FALSE FALSE FALSE) and +; (term->string ?t2 FALSE FALSE FALSE) . ] +; ) + + + (if (= TRUE ?*trace*) then + (printout t "Combine constant terms " (term->string ?t1 FALSE FALSE FALSE) + " and " (term->string ?t2 FALSE FALSE FALSE)" on the " ?side crlf) + (print-eq ?new-eq) + (printout t crlf)) + + ) + +(defrule simplify-cancel-constant-terms + "Skills:Combine_constant_terms LDashb" + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(to-be-simplified $?before1 ?side $?after1)(focus ?side) + (description ~distribute ? ?)) ; to outlaw -6+3(-x + 2) --> 3(-x) etc. + + ?eq <- (equation (sides $? ?s $?)) + ?s <- (expr (terms $?before ?t1 $?between ?t2 $?after)) + ?t1 <- (simple-term (coeff ?c1)(var nil)) ; constant terms + ?t2 <- (simple-term (coeff ?c2)(var nil)) + (test (= ?c1 (* -1 ?c2))) ; terms cancel each other out + + ?eq <- (equation (sides ?left ?right)) + (test (or (and (eq ?side left)(eq ?s ?left))(and (eq ?side right)(eq ?s ?right)))) + + ?s <- (expr (terms $?all-terms)) +=> + (if (= 2 (length$ $?all-terms)) + then + (bind ?zero-term (assert (simple-term (coeff 0)))) + (bind ?new-expr (assert (expr (terms ?zero-term)))) + else + (bind ?new-expr (assert (expr (terms $?before $?between $?after))))) + + (if (eq ?side left) + then (bind ?new-eq (assert (equation (sides ?new-expr ?right)))) + else (bind ?new-eq (assert (equation (sides ?left ?new-expr))))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before1 $?after1)) + + ;; --- Hints --- + (bind $?other-terms (create$ $?before $?between $?after)) + (bind ?other-terms-expr (assert (expr (terms $?other-terms)))) + (bind ?keep (if (> (length$ $?other-terms) 0) + then (str-cat You " " need " " to " " keep " " (expr->str3 ?other-terms-expr) . ) + else "" )) + (retract ?other-terms-expr) ; not needed any more + + (construct-message + [ On the ?side side, you have (term->string ?t1 FALSE FALSE FALSE) and + (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + These two numbers cancel each other out. ] + [ On the ?side side, you may delete terms (term->string ?t1 FALSE FALSE FALSE) and + (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + ?keep ] + ) + + (if (= TRUE ?*trace*) then + (printout t "Cancel out constant terms " (term->string ?t1 FALSE FALSE FALSE) + " and " (term->string ?t2 FALSE FALSE FALSE)" on the " ?side crlf) + (print-eq ?new-eq) + (printout t crlf)) + + ) + +(defrule simplify-combine-variable-terms + "Skills:Combine_variable_terms LDashb" + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(to-be-simplified $?before1 ?side $?after1)(focus ?side) + (description ~distribute ? ?)) ; to outlaw 2x+3(-x + 2) --> -x+3(2) etc. + + ?eq <- (equation (sides $? ?s $?)) + ?s <- (expr (terms $?before ?t1 $?between ?t2 $?after)) + ?t1 <- (simple-term (coeff ?c1)(var ?var&~nil)) ; variable terms + ?t2 <- (simple-term (coeff ?c2)(var ?var)) + (test (not (= ?c1 (* -1 ?c2)))) ; terms do not cancel each other out + + ?eq <- (equation (sides ?left ?right)) + (test (or (and (eq ?side left)(eq ?s ?left))(and (eq ?side right)(eq ?s ?right)))) +=> + (bind ?new-term (assert (simple-term (coeff (+ ?c1 ?c2))(var ?var)))) + (bind ?new-expr (assert (expr (terms $?before ?new-term $?between $?after)))) + + (if (eq ?side left) + then (bind ?new-eq (assert (equation (sides ?new-expr ?right)))) + else (bind ?new-eq (assert (equation (sides ?left ?new-expr))))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before1 $?after1)) + + ;; --- Hints --- + (bind $?other-terms (create$ $?before $?between $?after)) + (bind ?other-terms-expr (assert (expr (terms $?other-terms)))) + (bind ?keep (if (> (length$ $?other-terms) 0) + then (str-cat You " " also " " need " " to " " keep " " (expr->str3 ?other-terms-expr) . ) + else "" )) + (retract ?other-terms-expr) ; not needed any more + + (construct-message + [ You have two variable terms on the (str-cat ?side ,) + (term->string ?t1 FALSE FALSE FALSE) + and (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + If you add them, you will have only one variable term, which is better. + This is called "\"combining like terms.\"" + ; How can you do so? + ] + [ On the (str-cat ?side ,) combine like terms by adding (term->string ?t1 FALSE FALSE FALSE) + and (str-cat (term->string ?t2 FALSE FALSE FALSE) .) + This gives (str-cat (term->string ?new-term FALSE FALSE FALSE) .) + ?keep ] + ) +; (construct-message [ Simplify by combining like terms +; (term->string ?t1 FALSE FALSE FALSE) and (term->string ?t2 FALSE FALSE FALSE) +; on the ?side side. ] +; [ On the ?side side, add (term->string ?t1 FALSE FALSE FALSE) and +; (term->string ?t2 FALSE FALSE FALSE) . ] +; ) + + (if (= TRUE ?*trace*) then + (printout t "Combine variable terms " (term->string ?t1 FALSE FALSE FALSE) + " and " (term->string ?t2 FALSE FALSE FALSE)" on the " ?side crlf) + (print-eq ?new-eq) + (printout t crlf)) + + ) + +(defrule simplify-cancel-variable-terms + "Skills:Combine_variable_terms LDashb" + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(to-be-simplified $?before1 ?side $?after1)(focus ?side) + (description ~distribute ? ?)) ; to outlaw 3x+3(-x + 2) --> 3(2) etc. + + ?eq <- (equation (sides $? ?s $?)) + ?s <- (expr (terms $?before ?t1 $?between ?t2 $?after)) + ?t1 <- (simple-term (coeff ?c1)(var ?var&~nil)) ; variable terms + ?t2 <- (simple-term (coeff ?c2)(var ?var)) + (test (= ?c1 (* -1 ?c2))) ; terms cancel each other out + + ?eq <- (equation (sides ?left ?right)) + (test (or (and (eq ?side left)(eq ?s ?left))(and (eq ?side right)(eq ?s ?right)))) + + ?s <- (expr (terms $?all-terms)) +=> + (if (= 2 (length$ $?all-terms)) + then + (bind ?zero-term (assert (simple-term (coeff 0)))) + (bind ?new-expr (assert (expr (terms ?zero-term)))) + else + (bind ?new-expr (assert (expr (terms $?before $?between $?after))))) + + (if (eq ?side left) + then (bind ?new-eq (assert (equation (sides ?new-expr ?right)))) + else (bind ?new-eq (assert (equation (sides ?left ?new-expr))))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before1 $?after1)) + + ;; ----- Hints ----- + (bind $?other-terms (create$ $?before $?between $?after)) + (bind ?other-terms-expr (assert (expr (terms $?other-terms)))) + (bind ?keep (if (> (length$ $?other-terms) 0) + then (str-cat You " " " " need " " to " " keep " " (expr->str3 ?other-terms-expr) . ) + else "" )) + (retract ?other-terms-expr) ; not needed any more + (construct-message + [ On the ?side side, you have terms (term->string ?t1 FALSE FALSE FALSE) and + (term->string ?t2 FALSE FALSE FALSE) . + These two variable terms cancel each other out. ] + [ On the ?side side, you may delete terms (term->string ?t1 FALSE FALSE FALSE) and + (term->string ?t2 FALSE FALSE FALSE) "." + ?keep ] + ) + + (if (= TRUE ?*trace*) then + (printout t "Cancel variable terms " (term->string ?t1 FALSE FALSE FALSE) + " and " (term->string ?t2 FALSE FALSE FALSE)" on the " ?side crlf) + (print-eq ?new-eq) + (printout t crlf)) + + ) + +; do we need these? +(defrule simplify-drop-zero-left + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(to-be-simplified $?before1 left $?after1)(focus left)) + ?eq <- (equation (sides ?left ?right)) ; probably won't work in the long run ... + ?left <- (expr (terms $?before2 ?zero-term $?after2)) + ?zero-term <- (simple-term (coeff 0)) + ; require a variable term - case of 0 - 9 and such is covered by the simplify-combine ... rules + ?left <- (expr (terms $? ?var-term $?)) + ?var-term <- (simple-term (var ~nil)) + => + (bind ?new-left (assert (expr (terms $?before2 $?after2)))) + (bind ?new-eq (assert (equation (sides ?new-left ?right)))) ; this step would be more difficult in a rule that works on both sides ... + (modify ?transf (equation ?new-eq)(to-be-simplified $?before1 $?after1)) + + (construct-message [ Simplify by deleting the zero term on the left. ] ) + + (if (= TRUE ?*trace*) then + (printout t "Drop zero term on the left" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) + +(defrule simplify-drop-zero-right + (problem (cur-transformation ?transf)) + ?transf <- (transformation (equation ?eq)(to-be-simplified $?before1 right $?after1)(focus right)) + ?eq <- (equation (sides ?left ?right)) + ?right <- (expr (terms $?before2 ?zero-term $?after2)) + ?zero-term <- (simple-term (coeff 0)) + ; require a variable term - case of 0 - 9 and such is covered by the simplify-combine ... rules + ?right <- (expr (terms $? ?var-term $?)) + ?var-term <- (simple-term (var ~nil)) + => + (bind ?new-right (assert (expr (terms $?before2 $?after2)))) + (bind ?new-eq (assert (equation (sides ?left ?new-right)))) + (modify ?transf (equation ?new-eq)(to-be-simplified $?before1 $?after1)) + + (construct-message [ Simplify by deleting the zero term on the right. ] ) + + (if (= TRUE ?*trace*) then + (printout t "Drop zero term on the right" crlf) + (print-eq ?new-eq) + (printout t crlf)) + ) +;;----------------------------------------------------------------------------------------------------------- +;; +;; Rules for testing and debugging +;; + +;; oddly, this rule keeps fire every cycle of the prod model (are globals reset after each rule firing?) +;(defrule print-equation ; prints equation at the start of solving, just for development purposes +; (declare (salience 1000000)) ; should be higher than for any other rule +; (test (= ?*trace* TRUE)) ; use rule only when developing +; ?prob <- (problem (cur-equation ?eq)(printed FALSE)) +;=> +; (modify ?prob (printed TRUE)) +; (printout t "Given equation" crlf) +; (print-eq ?eq) +; (printout t crlf)) + +;(defrule enable-pre-explanations +; (declare (salience 100000000)) ; should be higher than for any other rule +; ?p <- (problem (cur-step nil)) ; to prevent proliferation of activations +; (studentValues (input ?student-input)) +; (test (eq ?student-input "Pre-Explanations: true")) ; don't want this in hint cycles, so this is fine +; => +; (predict checkBoxGroup1 DONT-CARE "Pre-Explanations: true") +; (modify ?p (pre-explanations-p TRUE)) +; (perform-tutor-action "InterfaceAction" "preExpl1Group" "SetVisible" TRUE) +; ) +; +;(defrule enable-post-explanations +; (declare (salience 100000000)) ; should be higher than for any other rule +; ?p <- (problem (cur-step nil)) ; to prevent proliferation +; (studentValues (input ?student-input)) +; (test (eq ?student-input "Post-Explanations: true")) ; don't want this in hint cycles, so this is fine +; => +; (predict checkBoxGroup1 DONT-CARE "Post-Explanations: true") +; (modify ?p (post-explanations-p TRUE)) +; (perform-tutor-action "InterfaceAction" "solve1Group" "SetVisible" TRUE) +; ) +; +;(defrule enable-no-explanations +; (declare (salience 100000000)) ; should be higher than for any other rule +; ?p <- (problem (cur-step nil)) ; to prevent proliferation +; (studentValues (input ?student-input)) +; (test (eq ?student-input "No Explanations: true")) ; don't want this in hint cycles, so this is fine +; => +; (predict checkBoxGroup1 DONT-CARE "No Explanations: true") +; (perform-tutor-action "InterfaceAction" "solve1Group" "SetVisible" TRUE) +; ) +; +;(defrule hide-all +; (declare (salience 100000000)) ; should be higher than for any other rule +; ?p <- (problem (cur-step nil) +; (pre-expl-groups $?pre-expl-groups) +; (solve-groups $?solve-groups) +; (post-expl-groups $?post-expl-groups) +; ) +; (studentValues (selection "hideButton")) +; => +; (predict hideButton DONT-CARE DONT-CARE) ; to stop chaining +; +; (foreach ?g $?pre-expl-groups +; (perform-tutor-action "InterfaceAction" ?g "SetVisible" FALSE)) +; (foreach ?g $?solve-groups +; (perform-tutor-action "InterfaceAction" ?g "SetVisible" FALSE)) +; (foreach ?g $?post-expl-groups +; (perform-tutor-action "InterfaceAction" ?g "SetVisible" FALSE)) +; ) +; +;(defrule show-all +; (declare (salience 100000000)) ; should be higher than for any other rule +; ?p <- (problem (cur-step nil) +; (pre-expl-groups $?pre-expl-groups) +; (solve-groups $?solve-groups) +; (post-expl-groups $?post-expl-groups) +; ) +; (studentValues (selection "showButton")) +; => +; (predict showButton DONT-CARE DONT-CARE) ; to stop chaining +; +; (foreach ?g $?pre-expl-groups +; (perform-tutor-action "InterfaceAction" ?g "SetVisible" TRUE)) +; (foreach ?g $?solve-groups +; (perform-tutor-action "InterfaceAction" ?g "SetVisible" TRUE)) +; (foreach ?g $?post-expl-groups +; (perform-tutor-action "InterfaceAction" ?g "SetVisible" TRUE)) +; ) +; +;;------------------------------------------------------------------------------------------------------ +;; +;; Dealing with transformations +;; +;; Explicit representation of transformations is necessary to implement undo and to help with being +;; flexible and/or configurable with respect to step skipping. +;; + + +;; +;; maximally flexible would be that intermediate step can be included on left, right, or both +;; so an operation could require three lines (2 intermediate forms --> one intermediate form --> no intermediate forms) +;; +;; --> but does it really make sense to treat the intermediate forms as different? or is it better to treat +;; them as any other form of the equation? (of course, if we want the tutor to insist that students get +;; rid of the intermediate steps right away, then they should be treated as different) +;; +;; one big advantage of not treating them as different is that we do not need to analyse for each operation +;; whether or not it has intermediate forms +;; +;; argh +;; +;; +;; model this with focus? i.e., place the rules that deal with intermediate steps in a separate module? +;; + +; (defmodule SIMPLIFY) +; this module is entered after a new equation has been created (in the transformation fact in the problem's +; cur-transformation slot) in its un-simplified (i.e., intermediate) form (but before this equation has been +; written by the student) +; the idea is to stay in the SIMPLIFY module until the left side and right side have both been written in +; simplified form, and then to return to the MAIN module so the next operation can be carried out +; --> or should we require only that the simplifying is done, without requiring the writing, so that the model +; can be flexible in how much step skipping it allows? +; maybe that kind of flexibility is an overambitious goal - the hard part is keeping the writing on the left +; and right in sync (or so it seems) +; +; how do we know we are done simplifying and writing (so we can pop the focus stack)? or rely on running +; out of rule activations? seems a bit risky ... + +; and how do we structure the writing in a line-by-line manner? introduce a write-transformation subgoal? +; or interpret the transformation fact as such a subgoal? maybe write-line-subgoal is a better name ... ? +; is the idea that a transformation does not necessarily correspond to a line? + +; do we always have a simplify-left and simplify-right subgoal? no, not e.g., if you are multiplying through +; on one side; so these subgoals have to come from the "main operation rule" (argh) +; we do always have a write-left and a write-right subgoal, but how do we know we are done simplifying? +; (a write-left subgoal will not be removed when there is still a simplify-left subgoal?) + + + +; focus rules appear to be necessary so that in the chain that writes the left side no simplifications +; of the right side are done, and vice versa ... +; has lhs sai matching for efficiency + +(defrule focus-pre-expl + (problem (cur-transformation ?transf)(prov-transformation nil)(hint-transformation nil) + (cur-step ~nil)(pre-explanations-p ~nil)(open-lines ?line $?)) + ?transf <- (transformation (focus nil)(pre-explained nil)(skip-expl-sel2 ?skip-sel2)) + ?line <- (line (pre-explanations ?ie1 ?ie2 ?ie3)) ; these need to go in order ... catch order errors here + ?ie1 <- (interface-element (value ?v1)(name ?s1)) + ?ie2 <- (interface-element (value ?v2)(name ?s2)) + ?ie3 <- (interface-element (value ?v3)(name ?s3)) + + ;; -- lhs sai matching + ;; -- is there any point in doing this? since it comes after figuring out the transformation ... + ;; also, this rule may often come with a singleton agenda ... + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i + (if (eq nil ?v1) ; TO DO: this duplicates the code on the rhs - hard to avoid - + ; or could we define/use a Jess function here? + ; e.g., pre-explain-sel and then some lambda here? + then ?s1 + else (if (and (eq nil ?v2)(eq nil ?skip-sel2)) + then ?s2 + else ?s3)) + UpdateComboBox NotSpecified)) + + => + (bind ?sel (if (eq nil ?v1) + then ?s1 + else (if (and (eq nil ?v2)(eq nil ?skip-sel2)) + then ?s2 + else ?s3))) + (predict ?sel UpdateComboBox NotSpecified) ; this helps with catching out of order errors when there are multiple + ; possible transformations + ; Hmmm.... maybe not, since the transformation is created first ... + (modify ?transf (focus pre-expl)) + ) + + +;; small issue: in a hint cycle, don't want to focus left when all the action is on the right side, +;; and the right side has not been filled in yet +;; this happens when distributing on the right side and combining like terms on the right side +;; so how do we recognize these situations? and do we worry about this only on the first line that +;; corresponds to the given transformation? (yes, because that would be the only time the +;; transformation-level hints are given, for better or for worse) +;; how do we test whether this is the first line related to the given transformation? +;; --> prev-left-val and prev-right-val are nil +;; --> actually seems to work quite nicely without this condition, when the student enters the +;; intermediate form +;; how do we test whether in this given transformation, the action is on the right side? +;; --> could like in the description slot for combine,right or distribute,right or possibly could +;; look in the to-be-simplified slot? latter is more general but perhaps we prefer to be +;; be specific here, so let's go with the first approach +(defrule focus-left + (declare (salience 1000)) ; so it goes before focus-right + (problem (cur-transformation ?transf)(hint-transformation nil)(open-lines ?line $?) + (cur-step ~nil)(pre-explanations-p ?pre-must)) + ?transf <- (transformation (focus nil)(pre-explained ?pre-done)(description ?op ? ?side)) + (test (or (eq nil ?pre-must)(not (eq nil ?pre-done)))) + ?line <- (line (solution-steps ?lsel ?rsel)) + ?lsel <- (interface-element (name ?name) + (value nil) ; seems odd that this was not part of the rule before? + ) + + ?rsel <- (interface-element (value ?r)) + (hint (now ?hint)) + (test (or ; (not (is-hint)) ; this restriction applies to hint cycles only + (not ?hint) + (neq nil ?r) ; and does not apply when there is input in the right solve box already + (not (and (or (eq ?op distribute)(eq ?op combine)) + (eq ?side right))) + )) + + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i (str-cat ?name) UpdateTextField NotSpecified)) + +=> + (predict (str-cat ?name) UpdateTextField NotSpecified) + (modify ?transf (focus left)) + ) + + +; has lhs sai matching for efficiency +(defrule focus-right + (declare (salience 999)) + (problem (cur-transformation ?transf)(hint-transformation nil) + (open-lines ?line $?)(cur-step ~nil)(pre-explanations-p ?pre-must)) + ?transf <- (transformation (focus nil)(pre-explained ?pre-done)) + (test (or (eq nil ?pre-must)(not (eq nil ?pre-done)))) + ?line <- (line (solution-steps ?left-sel ?sel)) + ?sel <- (interface-element (name ?name) + (value nil) ; seems odd this was not part of the rule before? + ) + + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i (str-cat ?name) UpdateTextField NotSpecified)) + + ;; efficiency hack: if we are in a hint cycle and the left box is open, fail here + ;; TO DO: do so if we just made an error in the box on the right - does the problem summary + ;; tell me that? + +; ?left-sel <- (interface-element (value ?v)) +; (test (or (not (is-hint)) ; if we are in a hint cycle then focus right only if left box is done +; (neq ?v nil))) + ;; TO DO: add condition that says: or just made an error on the right - but need to get that info in wm +=> + (predict ?name UpdateTextField NotSpecified) + (modify ?transf (focus right)) + ) + +(defrule focus-post-expl + (problem (cur-transformation ?transf)(hint-transformation nil) + (open-lines ?line $?)(cur-step ~nil)(post-explanations-p TRUE)) + ; could add the condition that this transformation has not been explained before + ; this is now taken care of by the write rules, but arguably it is better to handle that in this rule + ; TO DO: make focus = post-expl stay in wm for the next cycle ? + ?line <- (line (solution-steps ?s1 ?s2)) + ?s1 <- (interface-element (value ~nil)) + ?s2 <- (interface-element (value ~nil)) + ; the following may be a better way of checking than the three lines above + ; hmmm.... maybe we need both + ?transf <- (transformation (focus nil)(post-explained nil)) + ; cannot do early checking of the selection against student selection (as in focus-left/right) because + ; there are three options ... + ; TO DO: is that previous comment based on an assumption that the explanation fields can be filled out + ; in any order? + ; however, not necessary, since only one post-explanation rule will match (or perhaps two) +=> + (modify ?transf (focus post-expl)) + ) +;;------------------------------------------------------------------------------------------------------ +;; +;; Rules for writing equations +;; + +;write-left +;if we're focusing on the left side (focus left checks the left side is open) +;and the left does not still need to be simplified +; --> need condition to avoid duplication of a whole line? +;then +;write the left +;mark the simplified form left as written (just in case it had been simplified) +;remove the focus +;(if the line is full move it over to the list of full lines) +;(if both the right and left have been "written" remove transformation) - takes care of the line duplication +(defrule write-left + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?o-lines) + (closed-lines $?c-lines) ; what if there is no next line? include dummy line? + (cur-step ?step)(steps $?steps) + (pre-explanations-p ?pre-expl-p) + (post-explanations-p ?post-expl-p) + ) + ?transf <- (transformation (focus left)(to-be-simplified $?simplify) + (written $?written)(equation ?eq)(post-explained ?post-explained)(no-hints-p ?nh)) + (test (not (member$ left $?simplify))) + ?line <- (line (solution-steps ?sel ?right)(groups ? ? ?post-expl-group)) + ?sel <- (interface-element (value nil)) + ?right <- (interface-element (value ?right-val)) + ?eq <- (equation (sides ?left ?)) + ?prob <- (problem (open-lines ? ?next-line $?)) ; just to grab the next one + ?next-line <- (line (groups ?next-pre-expl-group ?next-solve-group ?)) + + ;; lhs sai matching -- not sure what efficiency gains to expect ... + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i DONT-CARE UpdateTextField (expr->str3 ?left) algStrictEquivTerms)) + + ?log-fact <- (custom-fields) + ?line <- (line (solution-steps $?solu)) ; needed for logging + + => + (bind ?inp (expr->str3 ?left)) + (predict DONT-CARE UpdateTextField ?inp algStrictEquivTerms) + + (modify ?sel (value ?*sInput*)) + (modify ?transf (written left $?written)(focus nil)) ; meaning the final (possibly simplified) form has been written + + (modify ?step (interface-elements ?sel)) + (modify ?prob (cur-step nil)(steps ?step $?steps)) + + (if (neq nil ?right-val) ; if a full equation (left and right side) has been written + then + (modify ?transf (prev-left-val ?inp)(prev-right-val ?right-val)) + (if (or (eq nil ?post-expl-p)(not (eq nil ?post-explained))) + ; if post-explanations are not required or have been done already (on a previous line for this transf) + then + (modify ?prob (open-lines $?o-lines)(closed-lines ?line $?c-lines)) ; done with the line + (if (member$ right $?written) ; if the right side has also been fully simplified + then + (modify ?prob (cur-transformation nil)(cur-equation ?eq))))) ; done with the transformation + ; TO DO: retract the transformation? + ; TO DO: why is the equation copied from the transformation into the problem fact? + + ; take care of revealing interface elements + ; if equation is fully written (i.e., left and right, simplified or not) ; if not fully written, then we do not reveal anything + ; then (regardless of whether it has been fully simplified) + ; if post-explaining and not post-explained, reveal post-explanations on the same line + ; if fully simplified and pre-explanations, reveal pre-explanation on the next line + ; else reveal solve steps on the next line + ; NOTE: this assumes we are not simultaneously pre-explaining and post-explaining + + (if (neq nil ?right-val) ; if a full equation (left and right side) has been written + then + (if (and (eq TRUE ?post-expl-p)(eq nil ?post-explained)) + then (perform-tutor-action "InterfaceAction" ?post-expl-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?post-expl-group)) + else (if (and (member$ right $?written)(eq TRUE ?pre-expl-p)) ; fully simplified + then (perform-tutor-action "InterfaceAction" ?next-pre-expl-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-pre-expl-group)) + else (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-solve-group)) + ))) + + ;; --- Hints --- + (if (eq nil ?nh) then (construct-message [ Enter ?inp on the left side. ] )) + + ;; --- Log more stuff --- + (modify ?log-fact + (postEqSys (eq->str ?eq)) + (postEqStu (ies->str $?solu " = ")) + (act write-left) + (transf (transf->str ?transf)) + (transfProps (transf-props->str ?transf)) + ) + ) + +;; +;; if we record the (undoable) "state" after these actions in a "state" fact, it would contain +;; - interface component with value (in the first open row) +;; - list of open and closed components +;; - current equation (this one is tricky, perhaps - do we need a copy) +;; - current transformation (potentially nil) +;; +;; - do we still need to record the prev-left-val and the prev-right-val? I believe these are used to check +;; the student input is actually a change from the previous row +;; - we'd need this kind of state also for the (non-undoable) initial state ... + +;; could that same fact be used to "do" the step? +;; i.e., create the state fact first, then do the step by "interpreting" that fact? +;; would be great to have a single do-step rule, would be more maintainable ... + + + +;(defrule write-right +; ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?o-lines)(closed-lines $?c-lines) +; (cur-step ?step)(steps $?steps) +; (post-explanations-p ?post-expl-p) +; (pre-explanations-p ?pre-expl-p) +; ) +; ?transf <- (transformation (focus right)(to-be-simplified $?simplify) +; (written $?written)(equation ?eq)(post-explained ?post-explained)(no-hints-p ?nh)) +; (test (not (member$ right $?simplify))) +; ?line <- (line (solution-steps ?left ?sel)(groups ? ? ?post-expl-group)) +; ?sel <- (interface-element (value nil)) +; ?left <- (interface-element (value ?left-val)) +; ?eq <- (equation (sides ? ?right)) +; ?prob <- (problem (open-lines ? ?next-line $?)) ; just to grab the next one +; ?next-line <- (line (groups ?next-pre-expl-group ?next-solve-group ?)) +; +; ;; lhs sai matching +; (studentValues (selection ?s)(action ?a)(input ?i)) +; (test (lhs-predict-oa ?s ?a ?i DONT-CARE UpdateTextField (expr->str3 ?right) algStrictEquivTerms)) +; +; ?log-fact <- (custom-fields) +; ?line <- (line (solution-steps $?solu)) ; needed for logging +; => +; (bind ?inp (expr->str3 ?right)) +; (predict DONT-CARE UpdateTextField ?inp algStrictEquivTerms ) +; (modify ?sel (value ?*sInput*)) +; (modify ?transf (written right $?written)(focus nil)) ; meaning the final (possibly simplified) form has been written +; +; (modify ?step (interface-elements ?sel)) +; (modify ?prob (cur-step nil)(steps ?step $?steps)) +; +; (if (neq nil ?left-val) ; if a full equation (left and right side) has been written +; then +; (modify ?transf (prev-left-val ?left-val)(prev-right-val ?inp)) +; (if (or (eq nil ?post-expl-p)(not (eq nil ?post-explained))) +; ; if post-explanations are not required or have been done already (on a previous line for this transf) +; then +; (modify ?prob (open-lines $?o-lines)(closed-lines ?line $?c-lines)) ; done with the line +; (if (member$ left $?written) ; if the left side has also been fully simplified +; then +; (modify ?prob (cur-transformation nil)(cur-equation ?eq))))) ; done with the transformation +; +; ;; --- Interface stuff --- +; (if (neq nil ?left-val) ; if a full equation (left and right side) has been written +; then +; (if (and (eq TRUE ?post-expl-p)(eq nil ?post-explained)) +; then (perform-tutor-action "InterfaceAction" ?post-expl-group "SetVisible" TRUE) +; (modify ?step (revealed-interface-group ?post-expl-group)) +; else (if (and (member$ left $?written)(eq TRUE ?pre-expl-p)) ; fully simplified +; then (perform-tutor-action "InterfaceAction" ?next-pre-expl-group "SetVisible" TRUE) +; (modify ?step (revealed-interface-group ?next-pre-expl-group)) +; else (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) +; (modify ?step (revealed-interface-group ?next-solve-group))))) +; ;; --- Hints --- +; (if (eq nil ?nh) then (construct-message [ Enter ?inp on the right side. ] )) +; +; ;; --- Log more stuff --- +; (modify ?log-fact +; (postEqSys (eq->str ?eq)) +; (postEqStu (ies->str $?solu " = ")) +; (act write-right) +; (transf (transf->str ?transf)) +; (transfProps (transf-props->str ?transf)) +; )) + +(defrule write-right-check-circle + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?o-lines)(closed-lines $?c-lines) + (cur-step ?step)(steps $?steps) + (post-explanations-p ?post-expl-p) + (pre-explanations-p ?pre-expl-p) + ) + ?transf <- (transformation (focus right)(to-be-simplified $?simplify) + (written $?written)(equation ?eq)(post-explained ?post-explained)(no-hints-p ?nh)) + (test (not (member$ right $?simplify))) + ?line <- (line (solution-steps ?left-ie ?sel)(groups ? ? ?post-expl-group)) + ?sel <- (interface-element (value nil)) + ?left-ie <- (interface-element (value ?left-val)) + ?eq <- (equation (sides ?left-side ?right-side)) + ?prob <- (problem (open-lines ? ?next-line $?)) ; just to grab the next one + ?next-line <- (line (groups ?next-pre-expl-group ?next-solve-group ?)) + + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i DONT-CARE UpdateTextField (expr->str3 ?right-side) algStrictEquivTerms)) + + ?log-fact <- (custom-fields) + ?line <- (line (solution-steps $?solu)) ; needed for logging + + ;; -- needed for the check on being in a circle -- + ?transf <- (transformation (description ?op ?term ?)) + => + (bind ?inp (expr->str3 ?right-side)) + (predict DONT-CARE UpdateTextField ?inp algStrictEquivTerms ) + (modify ?sel (value ?*sInput*)) + (modify ?transf (written right $?written)(focus nil)) ; meaning the final (possibly simplified) form has been written + + (modify ?step (interface-elements ?sel)) + (modify ?prob (cur-step nil)(steps ?step $?steps)) + + (if (neq nil ?left-val) ; if a full equation (left and right side) has been written + then + (modify ?transf (prev-left-val ?left-val)(prev-right-val ?inp)) + (if (or (eq nil ?post-expl-p)(not (eq nil ?post-explained))) + ; if post-explanations are not required or have been done already (on a previous line for this transf) + then + (modify ?prob (open-lines $?o-lines)(closed-lines ?line $?c-lines)) ; done with the line + (if (member$ left $?written) ; if the left side has also been fully simplified + then + (modify ?prob (cur-transformation nil)(cur-equation ?eq))))) ; done with the transformation + + ;; --- Interface stuff --- + (if (neq nil ?left-val) ; if a full equation (left and right side) has been written + then + (if (and (eq TRUE ?post-expl-p)(eq nil ?post-explained)) + then (perform-tutor-action "InterfaceAction" ?post-expl-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?post-expl-group)) + else (if (and (member$ left $?written)(eq TRUE ?pre-expl-p)) ; fully simplified + then (perform-tutor-action "InterfaceAction" ?next-pre-expl-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-pre-expl-group)) + else (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-solve-group))))) + ;; --- Hints --- + (if (eq nil ?nh) then (construct-message [ Enter ?inp on the right side. ] )) + + ;; --- Success message --- + ;; Check if the student is going around in circles. + ;; + (if (and (eq ?op add) ; only when dealing with an add/subtract operation + (member$ left $?written) ; and only when we are done simplifying + (or (eq nil ?post-expl-p)(neq nil ?post-explained))) ; and not when we still need to post-explain + then + (bind ?inv-transf (find-inverse-transf $?steps ?term)) ; this is a minimum criterion for + ; a circle (e.g., we are adding 12 now and added -12 before) + ; perhaps this is a good enough criterion, but to be safe, we also check that + ; the same equation has been seen before + (if ?inv-transf + then + (bind ?prev-eq (find-same-eq-prior-to-inv-transf $?steps ?inv-transf ?left-side ?right-side)) + (if ?prev-eq + then + (bind ?v (fact-slot-value ?term var)) + (bind ?c (fact-slot-value ?term coeff)) + (bind ?term-str (str-cat ; would be better to make a new term and use term->string but oh well + (if (= 1 (abs ?c)) + then "" + else (abs ?c)) + (if (neq nil ?v) then ?v else "")) + ) + (bind ?eq-str (eq->str ?prev-eq)) + + (construct-success-message [ Mathematically, what you did is correct, but unfortunately, + you are back to (str-cat ?eq-str ,) the same equation as before. + To make progress, add or subtract something other than + (str-cat ?term-str .) ] + )))) + + ;; --- Log more stuff --- + (modify ?log-fact + (postEqSys (eq->str ?eq)) + (postEqStu (ies->str $?solu " = ")) + (act write-right) + (transf (transf->str ?transf)) + (transfProps (transf-props->str ?transf)) + )) + + +;write-intermediate-left +;if we're focusing on the left side (focus left checks the left side is open) +;and we have not written the simplified form yet +;and writing the intermediate form represents progress or leaves the possibility for progress relative to previous step +; (i.e., either there is no previous step, or the right side represents progress - meaning, simplified form whereas +; the previous line was not - or the right side is open and the right side can be simplified) +;then +;write the intermediate form +;remove the focus +; (if the line if full move it over to the list of full lines) +(defrule write-intermediate-left + (declare (salience 10000)) ; may help the model tracer be more efficient? depends on students ... +; (hint (now FALSE)) + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?o-lines)(closed-lines $?c-lines) + (cur-step ?step)(steps $?steps)(post-explanations-p ?post-expl-p)) + ?transf <- (transformation (focus left)(to-be-simplified $?simplify) + (written $?written)(equation ?eq)(post-explained ?post-explained) + (prev-left-val ?prev-left-val)(prev-right-val ?prev-right-val) + (no-hints-p ?nh)) + (test (member$ left $?simplify)) ; left side is to be simplified, but has not been (fully) yet + ?line <- (line (solution-steps ?sel ?right)(groups ? ? ?post-expl-group)) + ?sel <- (interface-element (value nil)) ; redundant because focus rule also checks this, but oh well + ?right <- (interface-element (value ?right-val)) + ?eq <- (equation (sides ?left ?)) +; (test (or (eq nil ?prev-right-val) ; no previous line for this transformation +; (not (algStrictEquivTerms ?prev-left-val (expr->str3 ?left))) ; current left val different from previous +; ; happens only when there are multiple simplification steps on the left +; ; comparing as strings could be risky? +; ; TO DO: should this be algStrictEquivTerms ? +; (and (eq nil ?right-val) ; haven't filled in the value on the right yet, and the right still needs to be simplified +; (member$ right $?simplify)) +; (and (neq nil ?right-val) ; right val has been filled in and is different +; (not (algStrictEquivTerms ?right-val ?prev-right-val))) ; comparing as strings could be risky? YES! +; )) + ?prob <- (problem (open-lines ? ?next-line $?)) ; just to grab the next one + ?next-line <- (line (groups ? ?next-solve-group ?)) + + ;; TO DO: rule should not apply in hint cycles, when there are two terms to be simplified on the + ;; left side (because we want to recommend doing both) (as when distributing) + ;; though maybe that could be done by giving the simplify-eliminat-simple-product rules + ;; higher priority + + ;; lhs sai matching -- not sure what efficiency gains to expect ... + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i DONT-CARE UpdateTextField (expr->str3 ?left) algStrictEquivTerms)) + + ;; we do not want this rule to give a hint when there are multiple simplifications and it + ;; would recommend only one and we have already written the unsimplified form + (hint (now ?hint)) + (test (not (and ?hint (neq nil ?prev-left-val)(> (nmember$ left $?simplify) 0)))) + ?log-fact <- (custom-fields) + ?line <- (line (solution-steps $?solu)) ; needed for logging + => + (bind ?inp (expr->str3 ?left)) + (predict DONT-CARE UpdateTextField ?inp algStrictEquivTerms) + + (modify ?step (interface-elements ?sel)) + (modify ?prob (cur-step nil)(steps ?step $?steps)) + + (modify ?sel (value ?*sInput*)) + (modify ?transf (focus nil)) + (if (neq nil ?right-val) ; if a full equation (left and right side) has been written + then + (modify ?transf (prev-left-val ?inp)(prev-right-val ?right-val)) + (if (or (eq nil ?post-expl-p)(not (eq nil ?post-explained))) + ; if post-explanations are not required or have been done already (on a previous line for this transf) + then + (modify ?prob (open-lines $?o-lines)(closed-lines ?line $?c-lines)) ; done with the line (though not with + ; the transformation) + + )) + + ;; interface stuff - kept separate from the logic above, although somewhat similar + ;; if equation fully written (left and right, though not fully simplified, at least not on the left) + ;; then if post-explaining and not post-explained, reveal post-exaplantion steps on same line + ;; otherwise reveal solve steps on the next line + ;; (do not need to reveal the next pre-explanation steps because we have not fully simplified yet) + + (if (neq nil ?right-val) ; if a full equation (left and right side) has been written + then + (if (and (eq TRUE ?post-expl-p)(eq nil ?post-explained)) + then (perform-tutor-action "InterfaceAction" ?post-expl-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?post-expl-group)) + else (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-solve-group)))) + + ;; --- Hints --- + (if (and (eq nil ?nh) ; don't give this hint if we are in a non-preferred chain (as indicated by + ; the no-hints-p slot in the transformation) + (not (algEquivTerms ?prev-left-val ?inp)) ; don't recommend + ; writing the same as before - in that case, writing the simplified form is better + + (not (and (neq nil ?prev-left-val)(> (nmember$ left $?simplify) 0))) + ; also, don't give this hint if there is a previous left val and two simplifications + ; to make --> want to hint doing both simpifications at the same time + ) + then (construct-message [ Write ?inp on the left. ] )) + + ;; --- Log more stuff --- + (modify ?log-fact + (postEqSys (eq->str ?eq)) + (postEqStu (ies->str $?solu " = ")) + (act write-intermediate-left) + (transf (transf->str ?transf)) + (transfProps (transf-props->str ?transf)) + )) + + +(defrule write-intermediate-right + (declare (salience 9999)) ; may help the model tracer be more efficient? +; (hint (now FALSE)) + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?o-lines)(closed-lines $?c-lines) + (cur-step ?step)(steps $?steps)(post-explanations-p ?post-expl-p)) + ?transf <- (transformation (focus right)(to-be-simplified $?simplify) + (written $?written)(equation ?eq)(post-explained ?post-explained) + (prev-left-val ?prev-left-val)(prev-right-val ?prev-right-val) + (no-hints-p ?nh)) + (test (member$ right $?simplify)) ; right side is to be simplified, but has not been yet + ?line <- (line (solution-steps ?left ?sel)(groups ? ? ?post-expl-group)) + ?sel <- (interface-element (value nil)) ; redundant because focus rule also checks this, but oh well + ?left <- (interface-element (value ?left-val)) + ?eq <- (equation (sides ? ?right)) +; (test (or (eq nil ?prev-left-val) ; no previous line for this transformation +; (not (algStrictEquivTerms ?prev-right-val (expr->str3 ?right))) ; current right val different from previous +; (and (eq nil ?left-val) ; haven't filled in the value on the left yet, and the left still needs to be simplified +; (member$ left $?simplify)) +; (and (neq nil ?left-val) +; (not (algStrictEquivTerms ?left-val ?prev-left-val))) +; ;; TO DO: should this be algStrictEquivTerms +; )) + ?prob <- (problem (open-lines ? ?next-line $?)) ; just to grab the next one + ?next-line <- (line (groups ? ?next-solve-group ?)) + + ;; lhs sai matching -- not sure what efficiency gains to expect ... + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i DONT-CARE UpdateTextField (expr->str3 ?right) algStrictEquivTerms)) + + ;; we do not want this rule to give a hint when there are multiple simplifications and it + ;; would recommend only one and we have already written the unsimplified form + (hint (now ?hint)) + (test (not (and ?hint (neq nil ?prev-right-val)(> (nmember$ right $?simplify) 0)))) + ?log-fact <- (custom-fields) + ?line <- (line (solution-steps $?solu)) ; needed for logging + => + (bind ?inp (expr->str3 ?right)) + (predict DONT-CARE UpdateTextField ?inp algStrictEquivTerms) + + (modify ?step (interface-elements ?sel)) + (modify ?prob (cur-step nil)(steps ?step $?steps)) + + (modify ?sel (value ?*sInput*)) + (modify ?transf (focus nil)) + (if (neq nil ?left-val) ; if a full equation (left and right side) has been written + then + (modify ?transf (prev-left-val ?left-val)(prev-right-val ?inp)) + (if (or (eq nil ?post-expl-p)(not (eq nil ?post-explained))) + ; if post-explanations are not required or have been done already (on a previous line for this transf) + then + (modify ?prob (open-lines $?o-lines)(closed-lines ?line $?c-lines)) ; done with the line + )) + + ;; interface stuff + (if (neq nil ?left-val) ; if a full equation (left and right side) has been written + then + (if (and (eq TRUE ?post-expl-p)(eq nil ?post-explained)) + then (perform-tutor-action "InterfaceAction" ?post-expl-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?post-expl-group)) + else (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-solve-group)))) + + (if (and (eq nil ?nh)(not (algEquivTerms ?prev-right-val ?inp))) ; don't recommend + ; writing the same as before - in that case, writing the simplified form is better + ; also, don't give this hint if we are in a non-preferred chain (as indicated by + ; the no-hints-p slot in the transformation) + then (construct-message [ Write ?inp on the right. ] )) + + ;; --- Log more stuff --- + (modify ?log-fact + (postEqSys (eq->str ?eq)) + (postEqStu (ies->str $?solu " = ")) + (act write-intermediate-right) + (transf (transf->str ?transf)) + (transfProps (transf-props->str ?transf)) + )) + +;; ---------------------------------------------------------------------------------------------------------------- +;; +;; Rules for explanations +;; +;; Main idea: the transformation rules fire first, regardless of whether (in the interface) the explanations +;; come before or after entering the equation. When using pre-explanations, the explanations actually "lock in" +;; the transformation so that it can be used in the equations steps. +;; So the rules for explanations can be conditioned on a transformation fact. +;; +;; Should model use internal names for the operations, independent of what is in the menus? Makes it easier to +;; update the model, if the menus change. +;; So we need explanation facts? +;; +;; Also need to take care of closing lines and of recording enough information so the explanation can be undone. +;; --> Does reset work on the comboboxes? +;; + +;; TO DO: Can we use the same rules for pre-explanations and post-explanations? +;; +;; Separate rules depending on transformation? Yes, this will give more fine-grained data in the logs and +;; although it means there are many more rules, the conflict tree does not grow, because the op is in the lhs +;; of the rules. +;; + +(defrule pre-explain-add-op + (not (explain-op)) ; can have only one at a time + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description add ?add-term ?)(focus pre-expl)) + ?line <- (line (pre-explanations ?ie ? ?)) + ?ie <- (interface-element (name ?sel)(value nil)) + + ?add-term <- (simple-term (coeff ?c)) ; this determines whether hint says "Add" or "Subtract" + ; (though tutor will accept either) + + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel NotSpecified "Add" add-or-subtract-p)) + + => + (predict ?sel NotSpecified "Add" add-or-subtract-p) ; this may not add much? only when student goes out of order + ; TO DO: cutting this off earlier would be better + ; i.e., rule focus-pre-expl could check that the student is looking + ; at the first open explanation element + ; this is now redundant with focus-pre-expl but that's OK + ; might as well check input as well + (assert (explain-op (interface-element ?ie)(input ?*sInput*)(prov-transformation ?transf))) + + (if (> ?c 0) then + (construct-message [ Select "\"Add\"" from the menu. ] ) + else + (construct-message [ Select "\"Subtract\"" from the menu. ] )) + + ;; TO DO: figure out if the transformation could be used for this instead of the explain-op + + ;; Issue: cannot commit to specific transformation yet, because we do not know yet what will be added + ;; and there may be multiple possibilities; cannot disambiguate yet (would be nice if we could have + ;; multiple interpretations) + + ;; Option 1: retract the transformation? + ;; How does one retract a transformation? We should have a rule for this already, no? + ;; Option 2: represent the transformation in partially committed state + ;; But how to avoid duplication in the rules that create the transformations? + ;; How about keeping the partially committed transformation and inserting a rule or rules that check + ;; for consistency, with the focus rules waiting until the partially committee transformation + ;; has been removed? + ;; Would also be nice when re-generating options to only generate ones that are consistent ... + ;; Though do that as a next step? + ;; And how is this going to work with undo? Need to be able to restore a partially committed + ;; transformation. Maybe this is not hard. + + ;; --> Moving the transformation into provisional status should happen here, because the next rule (enter-step) + ;; is a generic rule that does not know anything about when provisional status is appropriate. + ;; Actually .... setting the cur-transformation to nil here enables the transformation rules again + ;; So next step should move the transformation into provisional status, but should not have to + ;; decide whether to do so. + + ; (modify ?prob (cur-transformation nil)(prov-transformation ?transf)) ; how about cur-eq???? + ) + +; Distribute is a provisional transformation because there may be equations where one might distribute on +; both sides. +(defrule pre-explain-distribute-op + (not (explain-op)) ; can have only one at a time + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description distribute ? ?)(focus pre-expl)) + ?line <- (line (pre-explanations ?ie1 ?ie2 ?)) + ?ie1 <- (interface-element (name ?sel)(value nil)) + ?ie2 <- (interface-element (name ?lock-sel)) + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel NotSpecified "Distribute" )) + => + (predict ?sel NotSpecified "Distribute") + (assert (explain-op (interface-element ?ie1)(input ?*sInput*) + (prov-transformation ?transf)(lock ?lock-sel))) + + (construct-message [ Select "\"Distribute\"" from the menu. ] ) +; (construct-message [ Select Distribute from the menu. ] ) + ) + +(defrule pre-explain-combine-op + (not (explain-op)) ; can have only one at a time + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description combine ? ?)(focus pre-expl)) + ?line <- (line (pre-explanations ?ie1 ?ie2 ?)) + ?ie1 <- (interface-element (name ?sel)(value nil)) + ?ie2 <- (interface-element (name ?lock-sel)) + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel NotSpecified "Combine like terms" )) + + => + (predict ?sel NotSpecified "Combine like terms") + (assert (explain-op (interface-element ?ie1)(input ?*sInput*)(prov-transformation ?transf)(lock ?lock-sel))) + (construct-message [ Select "\"Combine like terms\"" from the menu. ] ) + ) + +; tutor allows Add and Subtract to be used interchangeably; +; arguably, "Subtract -4 from both sides" is not a great explanation but accepting it seems fine +; still leaves the question of what the hint should say +(defrule post-explain-add-op + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?)(post-explanations-p TRUE) + (cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description add ?add-term ?)(focus post-expl)) + ?line <- (line (post-explanations ?ie ? ?)) ; could tighten this and check that the equation steps are done + ?ie <- (interface-element (name ?sel)(value nil)) ; only once - not so important to check but keeps rule from + ; being activated unnecessarily + ?add-term <- (simple-term (coeff ?c)) ; determines whether hint says "Add" or "Subtract" + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel UpdateComboBox "Added" add-or-subtract-p)) + ?log-fact <- (custom-fields) + => + (predict ?sel UpdateComboBox "Added" add-or-subtract-p) + (modify ?ie (value ?*sInput*)) ; argh, this seems to be a symbol + ; make step undoable + (modify ?step (interface-elements ?ie)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + (if (> ?c 0) then + (construct-message [ Select "\"Added\"" from the menu. ] ) + else + (construct-message [ Select "\"Subtracted\"" from the menu. ] )) + + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-op)(transf (transf->str ?transf))) + ) + +; TO DO: grow into general enter-step rule? +; Currently, this rule is used only for pre-explanation steps +; Deals with the situation that the choice of operator may not fully constrain the transformation so +; it is marked as provisional. + +; The enter-pre-explanation rule takes care of some of the details +(defrule enter-pre-explanation + ?prob <- (problem (cur-step ?step)(steps $?steps)(cur-transformation ?cur-transf) + (open-lines ?line $?)) + ?cur-transf <- (transformation (pre-explained ?pre-explained)(description ?op ? ?)) + ; not 100% if we can always rely on there being a cur-transformation ? + ; though probably we can + ?expl <- (explain-op (interface-element ?ie)(input ?inp)(prov-transformation ?prov-transf)(lock ?lock-sel)) + ?ie <- (interface-element (name ?sel)(value nil)) ; this may be redundant with a previous rule + ?line <- (line (groups ? ?solve-group ?)) + ; group needed when it is time to show the solve group in the interface + => + (predict DONT-CARE DONT-CARE DONT-CARE) ; this is just to terminate the chain + ; selection and input should be checked in the op, num, and sides rules + (modify ?ie (value ?inp)) + ; make step undoable + (modify ?step (interface-elements ?ie)) + (modify ?prob (cur-step nil)(steps ?step $?steps)) + (modify ?cur-transf (focus nil)) + (if (not (eq nil ?prov-transf)) + then + (modify ?prob (cur-transformation nil)(prov-transformation ?prov-transf)) + ) + ; this is for pre-explanations only - could store only the descriptio of the transformation but this is fine also + ; should we retract the ?cur-transf ? And how is this going to play with undo? + (retract ?expl) + ; when the pre-explanation is complete reveal the problem-solving steps in the interface + (if (not (eq nil ?pre-explained)) + then (perform-tutor-action "InterfaceAction" ?solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?solve-group))) + + ; for distribute steps, lock the "number" slot + ; always lock it, even if we did before? + (if (not (eq nil ?lock-sel)) ; kinda ugly, this special case here + then + (perform-tutor-action "InterfaceAction" ?lock-sel UpdateTextField " ---") + (perform-tutor-action "InterfaceAction" ?lock-sel lock TRUE)) + +; (construct-message [ Another hint level. ] ) + + ) + +;; TO DO: split into add and subtract? +(defrule pre-explain-add-constant + (not (explain-op)) ; can have only one at a time + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description add ?add-term ?)(focus pre-expl)) + ?add-term <- (simple-term (coeff ?num)(var nil)) + ?line <- (line (pre-explanations ?ie1 ?ie2 ?)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (value ?op&~nil)) ; + ?ie2 <- (interface-element (name ?sel)(value nil)) ; enforce order + + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i + ?sel + NotSpecified + (if (numberp (member$ ?op (create$ "Add" "Added"))) ; copying rhs code -- ugly + then ?num else (* -1 ?num)) + algEquivTerms)) + + => + (bind ?inp (if (numberp (member$ ?op (create$ "Add" "Added"))) then ?num else (* -1 ?num))) + (predict ?sel NotSpecified ?inp algEquivTerms ) ; NotSpecificed so we keep chaining + + (assert (explain-op (interface-element ?ie2)(input ?inp))) + ; Note: transformation is not provisional any more! fully determined by "add" op plus term to be added + (construct-message [ Enter ?inp . ] ) + ) + +; TO DO: split into add and subtract? +(defrule pre-explain-add-var-term + (not (explain-op)) ; can have only one at a time + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description add ?var-term ?)(focus pre-expl)) + ?var-term <- (simple-term (coeff ?coeff)(var ?var&~nil)) + ?line <- (line (pre-explanations ?ie1 ?ie2 ?)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (value ?op&~nil)) ; + ?ie2 <- (interface-element (name ?sel)(value nil)) ; enforce order + + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i + ?sel + NotSpecified + (str-cat (if (numberp (member$ ?op (create$ "Add" "Added"))) + then ?coeff else (* -1 ?coeff)) + ?var) ; and we are again copying rhs code -- very ugly + algEquivTerms)) + + => + (bind ?inp-coeff (if (numberp (member$ ?op (create$ "Add" "Added"))) then ?coeff else (* -1 ?coeff))) + (bind ?inp (str-cat ?inp-coeff ?var)) + (predict ?sel NotSpecified ?inp algEquivTerms) ; NotSpecificed so we keep chaining + (assert (explain-op (interface-element ?ie2)(input ?inp))) ; transformation is not provisional + (construct-message [ Enter ?inp . ] ) + ) + +; need to take into account whether the op is "Add" or "Subtract" +; use separate rules? ... so we have more .... :-) +(defrule post-explain-add-constant + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?)(post-explanations-p TRUE) + (cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description add ?add-term ?)(focus post-expl)) + ?add-term <- (simple-term (coeff ?num)(var nil)) + ?line <- (line (post-explanations ?ie1 ?ie2 ?)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (value ?op&~nil)) ; + ?ie2 <- (interface-element (name ?sel)(value nil)) ; enforce order + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i + ?sel + UpdateTextField + (if (numberp (member$ ?op (create$ "Add" "Added"))) ; copying rhs code :-(( + then ?num else (* -1 ?num)) + algEquivTerms)) + ?log-fact <- (custom-fields) + => + (bind ?inp (if (numberp (member$ ?op (create$ "Add" "Added"))) then ?num else (* -1 ?num))) + (predict ?sel UpdateTextField ?inp num-equal-p algEquivTerms) + (modify ?ie2 (value ?inp)) + ; make step undoable + (modify ?step (interface-elements ?ie2)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + (construct-message [ Enter ?inp . ] ) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-num)(transf (transf->str ?transf))) + ) + +(defrule post-explain-add-var-term + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (post-explanations-p TRUE)(cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description add ?var-term ?)(focus post-expl)) + ?var-term <- (simple-term (coeff ?coeff)(var ?var&~nil)) + ?line <- (line (post-explanations ?ie1 ?ie2 ?)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (value ?op&~nil)) ; + ?ie2 <- (interface-element (name ?sel)(value nil)) ; enforce order + + ;; lhs sai matching + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i + ?sel + UpdateTextField + (str-cat (if (numberp (member$ ?op (create$ "Add" "Added"))) + then ?coeff else (* -1 ?coeff)) + ?var) ; and we are again copying rhs code -- very ugly + algEquivTerms)) + + ?log-fact <- (custom-fields) + => + (bind ?inp-coeff (if (numberp (member$ ?op (create$ "Add" "Added"))) then ?coeff else (* -1 ?coeff))) + + (bind ?tmp (assert (simple-term (coeff ?inp-coeff)(var ?var)))) + (bind ?inp (term->string ?tmp FALSE FALSE FALSE)) + (retract ?tmp) + + (predict ?sel UpdateTextField ?inp algEquivTerms) + (modify ?ie2 (value ?inp)) + ; make step undoable + (modify ?step (interface-elements ?ie2)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + (construct-message [ Enter ?inp . ] ) ; probably want to use one of the conversion functions + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-num)(transf (transf->str ?transf))) + ) + +(defrule post-explain-add-sides + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?open)(closed-lines $?closed) + (post-explanations-p TRUE)(cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description add ?move-term ?)(focus post-expl) + (written $?written)(equation ?eq)) + ?line <- (line (post-explanations ? ?ie2 ?ie3)) ; could tighten this and check that the equation steps are done + ?ie2 <- (interface-element (value ~nil)) ; + ?ie3 <- (interface-element (name ?sel)(value nil)) ; enforce order + ?prob <- (problem (open-lines ? ?next-line $?)) + ?next-line <- (line (groups ? ?next-solve-group ?)) + + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel UpdateComboBox "to/from both sides")) + ?log-fact <- (custom-fields) + + ?eq <- (equation (sides ?left-side ?right-side)) + => + (predict ?sel UpdateComboBox "to/from both sides") + (modify ?ie3 (value TRUE)) ; or real student-generated or rule-generated value? + + ; update the transformation + (modify ?transf (post-explained TRUE)(focus nil)) + + ; close the line + (modify ?prob (open-lines $?open)(closed-lines ?line $?closed)) + + + ; if necessary, close the transformation (if the simplified form has been written for both sides) + ; TO DO: refactor + (if (and (member$ left $?written)(member$ right $?written)) + then + (modify ?prob (cur-transformation nil)(cur-equation ?eq))) ; done with the transformation + + ;; interface stuff (assume we do not pre-explain and post-explain in the same problem) + ;; so, done post-explaining means all we need to do is show the next solve group + (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-solve-group)) + + (construct-message [ Select "\"to/from both sides\"" from the menu. ] ) + + ;; --- Success message --- + ;; Give feedback if the student is going around in circles but only if we are fully done with the + ;; transformation. + ;; + (if (and (member$ left $?written)(member$ right $?written)) ; if the fully simplified form has been + ; written + then + (bind ?inv-transf (find-inverse-transf $?steps ?move-term)) ; this is a minimum criterion for + ; a circle (e.g., we are adding 12 now and added -12 before) + ; perhaps this is a good enough criterion, but to be safe, we also check that + ; the same equation has been seen before + (if ?inv-transf + then + (bind ?prev-eq (find-same-eq-prior-to-inv-transf $?steps ?inv-transf ?left-side ?right-side)) + (if ?prev-eq + then + (bind ?v (fact-slot-value ?move-term var)) + (bind ?c (fact-slot-value ?move-term coeff)) + (bind ?term-str (str-cat + (if (= 1 (abs ?c)) + then "" + else (abs ?c)) + (if (neq nil ?v) then ?v else ""))) + (bind ?eq-str (eq->str ?prev-eq)) + + (construct-success-message [ Mathematically, what you did is correct, but unfortunately, + you are back to ?eq-str , the same equation as before. + To make progress, add or subtract something other than + ?term-str .] + )))) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-sides)(transf (transf->str ?transf))) + + ; make step undoable + (modify ?step (interface-elements ?ie3)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + ) + +(defrule pre-explain-add-sides + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description add ? ?)(focus pre-expl)) + ?line <- (line (pre-explanations ? ?ie2 ?ie3)) + ?ie2 <- (interface-element (value ~nil)) + ?ie3 <- (interface-element (name ?sel)(value nil)) ; order has been dealt with by focus-pre-expl + => + (predict ?sel NotSpecified "to/from both sides") + (assert (explain-op (interface-element ?ie3)(input "to/from both sides"))) + + ; update the transformation + (modify ?transf (pre-explained TRUE)) ; relies on order ... does not check that all three pre-expl steps have been done + (construct-message [ Select "\"to/from both sides\"" from the menu. ] ) + ) + +(defrule post-explain-divide-op + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?)(post-explanations-p TRUE) + (cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description divide ? ?)(focus post-expl)) + ?line <- (line (post-explanations ?ie ? ?)) ; could tighten this and check that the equation steps are done + ?ie <- (interface-element (name ?sel)(value nil)) ; only once - not so important to check but keeps rule from + ; being activated unnecessarily + (studentValues (selection ?s)(action ?a)(input ?i)) +; (test (lhs-predict-oa ?s ?a ?i ?sel UpdateComboBox "Divided both sides by" )) + (test (lhs-predict-oa ?s ?a ?i ?sel UpdateComboBox "Divided" )) + ?log-fact <- (custom-fields) + => +; (predict ?sel UpdateComboBox "Divided both sides by" ) ; or real student-generated or rule-generated value? + (predict ?sel UpdateComboBox "Divided" ) ; or real student-generated or rule-generated value? + (modify ?ie (value ?*sInput*)) ; probably does not matter here whether we have actual student input, but + ; just to be sure + ; make step undoable + (modify ?step (interface-elements ?ie)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) +; (construct-message [ Select "\"Divided both sides by\"" from the menu. ] ) + (construct-message [ Select "\"Divided\"" from the menu. ] ) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-op)(transf (transf->str ?transf))) + ) + +(defrule post-explain-distribute-op + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?)(post-explanations-p TRUE) + (cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description distribute ? ?)(focus post-expl)) + ?line <- (line (post-explanations ?ie1 ?ie2 ?)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (name ?sel1)(value nil)) ; only once - not so important to check but keeps rule from + ; being activated unnecessarily + ?ie2 <- (interface-element (name ?sel2)) + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel1 UpdateComboBox "Distributed" )) + ?log-fact <- (custom-fields) + => + (predict ?sel1 UpdateComboBox "Distributed" ) ; or real student-generated or rule-generated value? + (modify ?ie1 (value ?*sInput*)) ; probably does not matter here whether we have actual student input, but + ; just to be sure + ; make step undoable + (modify ?step (interface-elements ?ie1 ?ie2)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + ; hide the "num" interface element + ; (perform-tutor-action "InterfaceAction" ?sel2 "SetVisible" FALSE) + ; this causes some trouble, so leave out for now + + (perform-tutor-action "InterfaceAction" ?sel2 UpdateTextField " ---") + (perform-tutor-action "InterfaceAction" ?sel2 lock TRUE) + ;; argh, how to make this undoable + + (construct-message [ Select "\"Distributed\"" from the menu. ] ) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-op)(transf (transf->str ?transf))) + ) + +(defrule post-explain-combine-op + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?)(post-explanations-p TRUE) + (cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description combine ? ?)(focus post-expl)) + ?line <- (line (post-explanations ?ie1 ?ie2 ?)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (name ?sel1)(value nil)) ; only once - not so important to check but keeps rule from + ; being activated unnecessarily + ?ie2 <- (interface-element (name ?sel2)) + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel1 UpdateComboBox "Combined like terms" )) + ?log-fact <- (custom-fields) + => + (predict ?sel1 UpdateComboBox "Combined like terms" ) ; or real student-generated or rule-generated value? + (modify ?ie1 (value ?*sInput*)) ; probably does not matter here whether we have actual student input, but + ; just to be sure + ; make step undoable + (modify ?step (interface-elements ?ie1 ?ie2)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + ; hide the "num" interface element + ; (perform-tutor-action "InterfaceAction" ?sel2 "SetVisible" FALSE) + ; this causes some trouble, so leave out for now + + (perform-tutor-action "InterfaceAction" ?sel2 UpdateTextField " ---") + (perform-tutor-action "InterfaceAction" ?sel2 lock TRUE) + + (construct-message [ Select "\"Combined like terms\"" from the menu. ] ) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-op)(transf (transf->str ?transf))) + ) + +; TO DO: is dividing ever a provisional transformation? meaning there may be multiple ways to divide? +; If so, should create an explain-op fact instead of handling everything here +; Currently, the divide rule is very strict so we do not have that issue (yet) ... +(defrule pre-explain-divide-op + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?)(pre-explanations-p TRUE) + (cur-step ?step)(steps $?steps)) + ?transf <- (transformation (description divide ? ?)(focus pre-expl)) + ?line <- (line (pre-explanations ?ie ? ?)) ; could tighten this and check that the equation steps are done + ?ie <- (interface-element (name ?sel)(value nil)) ; only once - not so important to check but keeps rule from + ; being activated unnecessarily + => + (predict ?sel NotSpecified "Divide both sides by" ) + (assert (explain-op (interface-element ?ie)(input ?*sInput*))) + (construct-message [ Select "\"Divide both sides by\"" from the menu. ] ) + ) + +(defrule post-explain-divide-by-constant + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?)(post-explanations-p TRUE) + (cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description divide ?div-term ?)(focus post-expl)) + ?div-term <- (simple-term (coeff ?num)(var nil)) + ?line <- (line (post-explanations ?ie1 ?ie2 ?)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (value ~nil)) ; + ?ie2 <- (interface-element (name ?sel)(value nil)) ; enforce order + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel UpdateTextField ?num algEquivTerms )) + ?log-fact <- (custom-fields) + => + (predict ?sel UpdateTextField ?num algEquivTerms) ; use of num-equal-p may not be necessary + (modify ?ie2 (value ?num)) + ; make step undoable + (modify ?step (interface-elements ?ie2)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + + (construct-message [ Enter ?num . ] ) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-num)(transf (transf->str ?transf))) + ) + +;; TO DO: could re-factor further because this rule is very similar to pre-explain-divide-num ... +(defrule pre-explain-divide-by-constant + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?)(pre-explanations-p TRUE) + (cur-step ?step)(steps $?steps)) + ?transf <- (transformation (description divide ?div-term ?)(focus pre-expl)) + ?div-term <- (simple-term (coeff ?num)(var nil)) + ?line <- (line (pre-explanations ?ie1 ?ie2 ?)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (value ~nil)) ; + ?ie2 <- (interface-element (name ?sel)(value nil)) ; enforce order + => + (predict ?sel NotSpecified ?num algEquivTerms) ; NotSpecified so we keep chaining, but the critical test for + ; selection and input is done here and not repeated later in the chain + (assert (explain-op (interface-element ?ie2)(input ?num))) + ; Note: transformation is not provisional! + (construct-message [ Enter ?num . ] ) + ) + +(defrule post-explain-divide-sides + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?open)(closed-lines $?closed) + (post-explanations-p TRUE)(cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description divide ? ?)(focus post-expl)(written $?written)(equation ?eq)) + ?line <- (line (post-explanations ? ?ie2 ?ie3)) ; could tighten this and check that the equation steps are done + ?ie2 <- (interface-element (value ~nil)) ; + ?ie3 <- (interface-element (name ?sel)(value nil)) ; enforce order + ?prob <- (problem (open-lines ? ?next-line $?)) + ?next-line <- (line (groups ? ?next-solve-group ?)) + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel UpdateComboBox "to/from both sides" )) + ?log-fact <- (custom-fields) + => + (predict ?sel UpdateComboBox "to/from both sides") + (modify ?ie3 (value TRUE)) ; or real student-generated or rule-generated value? + + ; update the transformation + (modify ?transf (post-explained TRUE)(focus nil)) + + ; close the line + (modify ?prob (open-lines $?open)(closed-lines ?line $?closed)) + + ; make step undoable + (modify ?step (interface-elements ?ie3)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + + ; and if necessary, close the transformation (if the simplified form has been written for both sides) + ; TO DO: refactor + (if (and (member$ left $?written)(member$ right $?written)) + then + (modify ?prob (cur-transformation nil)(cur-equation ?eq))) ; done with the transformation + + (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-solve-group)) + + (construct-message [ Select "\"to/from both sides\"" from the menu. ] ) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-sides)(transf (transf->str ?transf))) + ) + +(defrule post-explain-distribute-sides + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?open)(closed-lines $?closed) + (post-explanations-p TRUE)(cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description distribute ? ?side)(focus post-expl) + (written $?written)(equation ?eq)) + ?line <- (line (post-explanations ?ie1 ? ?ie3)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (value ~nil)) ; + ?ie3 <- (interface-element (name ?sel)(value nil)) ; enforce order (but skip the "num" field) + ?prob <- (problem (open-lines ? ?next-line $?)) + ?next-line <- (line (groups ? ?next-solve-group ?)) + ;; --- lhs sai matching --- + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel UpdateComboBox ?side sides-equal-p )) + ?log-fact <- (custom-fields) + => + (predict ?sel UpdateComboBox ?side sides-equal-p) + (modify ?ie3 (value TRUE)) ; or real student-generated or rule-generated value? + + ; update the transformation + (modify ?transf (post-explained TRUE)(focus nil)) + + ; close the line + (modify ?prob (open-lines $?open)(closed-lines ?line $?closed)) + + ; make step undoable + (modify ?step (interface-elements ?ie3)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + + ; and if necessary, close the transformation (if the simplified form has been written for both sides) + ; TO DO: refactor + (if (and (member$ left $?written)(member$ right $?written)) + then + (modify ?prob (cur-transformation nil)(cur-equation ?eq))) ; done with the transformation + + (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-solve-group)) + + (if (= ?side "left") then + (construct-message [ Select "\"to the left side\"" from the menu. ] ) + else if (= ?side "right") then + (construct-message [ Select "\"to the right side\"" from the menu. ] ) + ) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-sides)(transf (transf->str ?transf))) + ) + +;; wow this rule is very close to literally the same as the one for distribute +(defrule post-explain-combine-sides + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?open)(closed-lines $?closed) + (post-explanations-p TRUE)(cur-step ?step&~nil)(steps $?steps)) + ?transf <- (transformation (description combine ? ?side)(focus post-expl) + (written $?written)(equation ?eq)) + ?line <- (line (post-explanations ?ie1 ? ?ie3)) ; could tighten this and check that the equation steps are done + ?ie1 <- (interface-element (value ~nil)) ; + ?ie3 <- (interface-element (name ?sel)(value nil)) ; enforce order (but skip the "num" field) + ?prob <- (problem (open-lines ? ?next-line $?)) + ?next-line <- (line (groups ? ?next-solve-group ?)) + ;; --- lhs sai matching --- + (studentValues (selection ?s)(action ?a)(input ?i)) + (test (lhs-predict-oa ?s ?a ?i ?sel UpdateComboBox ?side sides-equal-p )) + ?log-fact <- (custom-fields) + => + (predict ?sel UpdateComboBox ?side sides-equal-p) + (modify ?ie3 (value TRUE)) ; or real student-generated or rule-generated value? + + ; update the transformation + (modify ?transf (post-explained TRUE)(focus nil)) + + ; close the line + (modify ?prob (open-lines $?open)(closed-lines ?line $?closed)) + + ; make step undoable + (modify ?step (interface-elements ?ie3)) ; all rules that write input have this ... TO DO: refactor + (modify ?prob (cur-step nil)(steps ?step $?steps)) + + ; and if necessary, close the transformation (if the simplified form has been written for both sides) + ; TO DO: refactor + (if (and (member$ left $?written)(member$ right $?written)) + then + (modify ?prob (cur-transformation nil)(cur-equation ?eq))) ; done with the transformation + + (perform-tutor-action "InterfaceAction" ?next-solve-group "SetVisible" TRUE) + (modify ?step (revealed-interface-group ?next-solve-group)) + + (if (= ?side "left") then + (construct-message [ Select "\"to the left side\"" from the menu. ] ) + else if (= ?side "right") then + (construct-message [ Select "\"to the right side\"" from the menu. ] ) + ) + ;; --- Log more stuff --- + (modify ?log-fact (act post-expl-sides)(transf (transf->str ?transf))) + ) + + + +(defrule pre-explain-divide-sides + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description divide ? ?)(focus pre-expl)) + ?line <- (line (pre-explanations ? ?ie2 ?ie3)) + ?ie2 <- (interface-element (value ~nil)) + ?ie3 <- (interface-element (name ?sel)(value nil)) ; order has been dealt with by focus-pre-expl + => + (predict ?sel NotSpecified "to/from both sides") + (assert (explain-op (interface-element ?ie3)(input "to/from both sides"))) + + ; update the transformation + (modify ?transf (pre-explained TRUE)) ; relies on order ... does not check that all three pre-expl steps have been done + (construct-message [ Select "\"to/from both sides\"" from the menu. ] ) + ) + +(defrule pre-explain-distribute-sides + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description distribute ? ?side)(focus pre-expl)) + ?line <- (line (pre-explanations ?ie1 ? ?ie3)) + ?ie1 <- (interface-element (value ~nil)) ; since we skip the middle ie + ?ie3 <- (interface-element (name ?sel)(value nil)) ; order has been dealt with by focus-pre-expl + => + (bind ?inp (if (eq ?side left) + then "to the left side" + else (if (eq ?side right) + then "to the right side" + else "??"))) ; ugly to have this here - should be done once? + (predict ?sel NotSpecified ?inp) + (assert (explain-op (interface-element ?ie3)(input ?inp))) + + ; update the transformation + (modify ?transf (pre-explained TRUE)) ; relies on order ... does not check that all three pre-expl steps have been done + + (if (= ?side "left") then + (construct-message [ Select "\"to/from the left side\"" from the menu. ] ) + else if (= ?side "right") then + (construct-message [ Select "\"to/from the right side\"" from the menu. ] ) + ) + + ) + +;; TO DO: this rule is so similar to the one for dividing ... generalize? +;; also, should we allow combining on both sides at the same time? +;; if not, maybe the menu should be changed? +(defrule pre-explain-combine-sides + ?prob <- (problem (cur-transformation ?transf)(open-lines ?line $?) + (pre-explanations-p TRUE)(cur-step ~nil)) + ?transf <- (transformation (description combine ? ?side)(focus pre-expl)) + ?line <- (line (pre-explanations ?ie1 ? ?ie3)) + ?ie1 <- (interface-element (value ~nil)) ; since we skip the middle ie + ?ie3 <- (interface-element (name ?sel)(value nil)) ; order has been dealt with by focus-pre-expl + => + (bind ?inp (if (eq ?side left) + then "to the left side" + else (if (eq ?side right) + then "to the right side" + else "??"))) ; ugly to have this here - should be done once? + (predict ?sel NotSpecified ?inp) + (assert (explain-op (interface-element ?ie3)(input ?inp))) + + ; update the transformation + (modify ?transf (pre-explained TRUE)) ; relies on order ... does not check that all three pre-expl steps have been done + (if (= ?side "left") then + (construct-message [ Select "\"to/from the left side\"" from the menu. ] ) + else if (= ?side "right") then + (construct-message [ Select "\"to/from the right side\"" from the menu. ] ) + ) + + ) + +;; Notes re refactoring +;; +;; After a correct action (not an undo) +;; - update wm including transformation (does not generalize) +;; - potentially, update the post-explained slot of the transformation (if there is a single post-explain sides rule +;; then can be part of that rule) +;; IF the postOp, postNum, postSides elements have been filled out (do you always need all three?) +;; THEN set the post-explained slot of the transformation to TRUE +;; - update the undo stack (always) +;; - potentially, show some interface elements on the current line +;; IF the solveLeft and solveRight elements have been filled in +;; and student needs to post-explain (i.e., the post-explanations-p slot of the problem is not nil) +;; and has not post-explained yet (i.e., the post-explained slot of the transformation is nil) +;; THEN show the postExpl steps +;; - potentially, close the line and show some interface elements on the next line +;; IF the solveLeft and solveRight elements have been filled in +;; and student does not need to post-explain (i.e., the post-explanations-p slot of the problem is nil) +;; or has post-explained (i.e., the post-explained slot of the transformation is not nil) +;; THEN close the line and show the solveLeft and solveRight elements on the next line +;; - potentially, close the transformation +;; IF the left and right simplified forms have been written +;; and student does not need to post-explain (i.e., the post-explanations-p slot of the problem is nil) +;; or has post-explained (i.e., the post-explained slot of the transformation is not nil) +;; THEN close the transformation +;; +;; Control structure +;; do all of this in one rule? mostly on the RHS? +;; or create steps fact and create for each rule a version with the negation of its condition? +;; - mark-post-explained +;; - update-undo-stack +;; - show-elements-cur-line +;; - close-cur-line +;; - close-transformation +;; any other options? +;; could write rules such as: +;; IF next thing to do is mark-post-explained +;; THEN if we are done post-explaining then set post-explained to TRUE +;; remove mark-post-explained from the to do list +;; +;; Gives deep model-tracing depth but only happens on correct input, and always succeeds, so perhaps no prob +;; Could use the ACTION element to terminate the chain. +;; Though could wonder if this is really better then the single rule approach ... ? +;; + + +;; ---------------------------------------------------------------------------------------------------------------- +;; +;; Rules for undoing steps +;; +;; In a step +;; - an interface component is filled in (in the first open row) +;; - a row may be closed (i.e., moved from the open to the closed list) +;; - a new row may be revealed in the interface (or part thereof, e.g., the explanation part only or the equation part only) +;; - a transformation may be created or updated +;; - a transformation may be closed +;; - when a transformation is closed, the cur-equation and cur-transformation slot in the problem is updated +;; - when a transformation is created, the cur-transformation slot in the problem is updated + +;; To undo a step (i.e., re-create the state that existed prior to it, at the end of the previous step), need to know +;; the folowing: +;; - interface component that was filled in +;; - interface components that were revealed (or hidden??) +;; - lists of open and closed rows +;; - the current transformation at the beginning of the step +;; - the current equation at the beginning of the step + +;; What if the "write" rules created a step element that captures all this state information? +;; Then all the undo rule needs to do is re-store the information. +;; + +(defrule record-pre-state + (declare (salience 100000)) ; maybe this is enough to ensure this rule always goes first? + ; safer would be to make all the operations rules have a condition that there is a cur-step + ?prob <- (problem (cur-step nil) + ; (checked-answer-p ~nil) ; these are key conditions + (open-lines $?ol)(closed-lines $?cl)(cur-equation ?eq) ; this is just collecting info + (cur-transformation ?cur-tr)(prov-transformation ?prov-tr)) + + ;; retrieving stuff for the log fact + ?prob <- (problem (open-lines ?line $?)(pre-explanations-p ?pre-expl)(post-explanations-p ?post-expl)) + ?line <- (line (solution-steps $?solu)) + ?eq <- (equation (sides ?left ?right)) + ?left <- (expr (terms $?left-terms)) + ?right <- (expr (terms $?right-terms)) + + ?log-fact <- (custom-fields) + + => + ;; re-setting the no-hints-p slot + (if (neq nil ?cur-tr) then (modify ?cur-tr (no-hints-p nil))) + (if (neq nil ?prov-tr) then (modify ?prov-tr (no-hints-p nil))) + + (bind ?prov-p nil) + (if (eq nil ?prov-tr) ; needed when dealing with pre-explanations that specify the transformation in + ; piecemeal fashion (e.g., adding to both sides is underspecified after the student + ; says "add" until you know what is being added) + then (bind ?tr ?cur-tr) + else (bind ?tr ?prov-tr)(bind ?prov-p TRUE)) + (bind ?tr-copy (if (not (eq nil ?tr)) then (copy-transf ?tr) else nil)) + (bind ?eq-copy (if (not (eq nil ?eq)) then (copy-eq ?eq) else nil)) + ; do I need to copy the open and closed lists? or can they be used as is? copying is probably better ... + (bind ?step (assert (step (pre-transformation ?tr-copy) + (pre-closed-list (create$ $?cl)) ; copy the lists ... + (pre-open-list (create$ $?ol)) + (pre-equation ?eq-copy) + (provisional-p ?prov-p) + ))) + (modify ?prob (cur-step ?step) + ; (checked-answer-p nil) ; for the next cycle + ) + + ;; --- put stuff in the log attributes fact --- + (bind ?pre-eq (if (neq nil ?tr) then (fact-slot-value ?tr equation) else ?eq)) + (bind ?eq-str (if (neq nil ?pre-eq) ; don't think it can happen that pre-eq = nil but just in case + then (eq->str ?pre-eq) else "")) +; (bind ?line-str (line->str ?line ?pre-expl ?post-expl)) + (if (neq nil ?pre-eq) ; same comment as above - ?pre-eq should not be nil + then + (bind $?pre-sides (fact-slot-value ?pre-eq sides)) + (bind ?pre-l (nth$ 1 $?pre-sides)) + (bind ?pre-r (nth$ 2 $?pre-sides)) + (bind $?pre-lterms (fact-slot-value ?pre-l terms)) + (bind $?pre-rterms (fact-slot-value ?pre-r terms)) + ) + (modify ?log-fact (preEqSys ?eq-str) + (preEqStu (ies->str $?solu " = "))) + ; (curLine ?line-str) +; (varLeft (if (find-var-term $?pre-lterms) then 1 else 0)) +; (varRight (if (find-var-term $?pre-rterms) then 1 else 0)) +; (constLeft (if (find-const-term $?pre-lterms) then 1 else 0)) +; (constRight (if (find-const-term $?pre-rterms) then 1 else 0)) +; (zeroLeft (if (is-zero-term $?pre-lterms) then 1 else 0)) +; (zeroRight (if (is-zero-term $?pre-rterms) then 1 else 0)) +; (prodLeft (if (find-prod-term $?pre-lterms) then 1 else 0)) +; (prodRight (if (find-prod-term $?pre-rterms) then 1 else 0)) +; (negVarLeft (if (find-neg-var-term $?pre-lterms) then 1 else 0)) +; (negVarRight (if (find-neg-var-term $?pre-rterms) then 1 else 0)) +; (negConstLeft (if (find-neg-const-term $?pre-lterms) then 1 else 0)) +; (negConstRight (if (find-neg-const-term $?pre-rterms) then 1 else 0)) + (if (neq nil ?pre-eq) ; again, just some defensive programming + then + (modify ?log-fact + (preEqProps + (str-cat + ;varLeft + (if (find-var-term $?pre-lterms) then 1 else 0) + "|" + ;varRight + (if (find-var-term $?pre-rterms) then 1 else 0) + "|" + ;constLeft + (if (find-const-term $?pre-lterms) then 1 else 0) + "|" + ;constRight + (if (find-const-term $?pre-rterms) then 1 else 0) + "|" + ;zeroLeft + (if (is-zero-term $?pre-lterms) then 1 else 0) + "|" + ;zeroRight + (if (is-zero-term $?pre-rterms) then 1 else 0) + "|" + ;prodLeft + (if (find-prod-term $?pre-lterms) then 1 else 0) + "|" + ;prodRight + (if (find-prod-term $?pre-rterms) then 1 else 0) + "|" + ;negVarLeft + (if (find-neg-var-term $?pre-lterms) then 1 else 0) + "|" + ;negVarRight + (if (find-neg-var-term $?pre-rterms) then 1 else 0) + "|" + ;negConstLeft + (if (find-neg-const-term $?pre-lterms) then 1 else 0) + "|" + ;negConstRight + (if (find-neg-const-term $?pre-rterms) then 1 else 0) + ))) + )) +; +;(defrule fireable-bug-log-custom-fields +; (declare (salience -100000)) +; ?prob <- (problem (cur-step nil)) +; +; ;; retrieving stuff for the log fact +; +; ?log-fact <- (custom-fields) +; +; ?c <- (accumulate (bind ?list (new java.util.ArrayList)) ;; initializer +; ; (?list add (str-cat ?descr ";" ?pknow ";")) ;; action +; (?list add ?descr) ;; action +; ?list ;; result +; (Skill (description ?descr) +; (pKnown ?pknow))) +; => +; (predict DONT-CARE DONT-CARE DONT-CARE) +; (printout t crlf "bug-log-custom-fields, ?c = " ?c ; (?c toString) +; crlf) +; +; (modify ?log-fact (pKnowValues (?c toString))) +; +; ) + + +(defrule undo-step ; undo the last correct action + (declare (salience 2000000)) ; should be tried first + + ?sv <- (studentValues (selection "undo") (action "ButtonPressed")) ; match only when the action is undo + + ?prob <- (problem (steps ?step $?steps)) + ?step <- (step (interface-elements $?ies) + (pre-open-list $?open) + (pre-closed-list $?closed) + (pre-transformation ?transf) + (pre-equation ?eq) + (provisional-p ?prov-p) + (revealed-interface-group ?group) + ) + ?log-fact <- (custom-fields) + => + (predict undo DONT-CARE DONT-CARE) + (modify ?prob (cur-equation ?eq) + (open-lines $?open) + (closed-lines $?closed) + (cur-step nil) ; probably redundant but good defensive programming + (steps $?steps) + ) + (if (eq nil ?prov-p) ; need to restore the transformation as either regular or provisional transformation + then (modify ?prob (cur-transformation ?transf)) + else (modify ?prob (prov-transformation ?transf)(cur-transformation nil))) + + (retract ?step) ; not needed anymore ... + + (if ?*trace* + then + (printout t crlf crlf "Undo - $?ies = " $?ies crlf crlf )) + + (foreach ?ie $?ies + (modify ?ie (value nil)) + (bind ?sel (fact-slot-value ?ie name)) + (if ?*trace* + then + (printout t "Undo - ?ie = " ?ie ", ?sel = " ?sel crlf )) + + (perform-tutor-action "InterfaceAction" "root" "resetComponentTPA" ?sel)) + + (if (neq nil ?group) + then + (perform-tutor-action "InterfaceAction" ?group "SetVisible" FALSE)) + + ) + + +;----------------------------------------------------------------------------------------------------- +; detecting lack of strategy +; + +; where in the chain do we want this? before/after record step? how do we avoid multiple chains? +; on the assumption that a repeat step can only happen on an unstrategic move (but that assumption +; needs to be checked) we could make repeat and non-repeat versions of these rules, or the rules +; could set a flag that would prevent the next rule (the focus rule?) from firing until it is unset +; +; relatedly, why will the feedback be given? would be good to wait until the full equation has +; been written or even, simplified? so maybe the repeat step rule only fills in a slot in the +; transformation and then the write-left and write-right rules check if this flag is set and if so, +; and if the other side has been written already, they give the success message? +; alternatively, could give the message earlier ... +; +;(defrule circle +; (problem (cur-transformation ?transf)(steps $?steps)) +; ; would like to reduce the number of matches, given each transformation occurs multiple times +; ; so perhaps there should not be any steps in beween ?st1 and ?st2 ? +; ; ?st1 is a step with an add transformation with var = ?v and coefff = c +; ; ?st2 is a step with an add transformation with var = ?v and coeff = -c +; ; ?st1 could be the current transformation so there is a delay in detecting the cycle +; ; unless we compare the current transformation against on the history stack +; ; +; ; a slightly simpler solution might be simply to match against the transformations in wm, +; ; to see if there is an add and its opposite ... they possibly too general +; +; ?transf <- (transformation (description add ?move-term ?)) +; ?move-term <- (simple-term (var ?v)(coeff ?c)) +; (test (find-inverse-transf $?steps ?v ?c)) +; +; => +; +; ) +; +;(defrule not-a-circle +; (problem (steps $? ?step $?)(cur-transformation ?transf)) +; ?step <- (step (pre-transformation ?t1)) +; ?t1 <- (transformation (description add ?mt1 ?)) +; ?transf <- (transformation (description add ?mt2 ?)) +; ?mt1 <- (simple-term (var ?v)(coeff ?c)) +; ?mt2 <- (simple-term (var ?v)(coeff =(* -1 ?c))) +; +; => +; +; ) + +;;------------------------------------------------------------------------------------- +;; +;; Bug rules - Yeah! +;; + + + + +;;------------------------------------------------------------------------------------- +;; +;; Create facts representing the interface - same for every problem +;; Would be better if these could go in a separate file, but this is the only place for them. +;; + +(bind ?ie11 (assert (interface-element (name preExplOp1)))) +(bind ?ie12 (assert (interface-element (name preExplNum1)))) +(bind ?ie13 (assert (interface-element (name preExplSide1)))) +(bind ?ie14 (assert (interface-element (name solveLeft1)))) +(bind ?ie15 (assert (interface-element (name solveRight1)))) +(bind ?ie16 (assert (interface-element (name postExplOp1)))) +(bind ?ie17 (assert (interface-element (name postExplNum1)))) +(bind ?ie18 (assert (interface-element (name postExplSide1)))) + +(bind ?line1 (assert (line (pre-explanations ?ie11 ?ie12 ?ie13) + (solution-steps ?ie14 ?ie15) + (post-explanations ?ie16 ?ie17 ?ie18) + (groups preExpl1Group solve1Group postExpl1Group) + ))) + +(bind ?ie21 (assert (interface-element (name preExplOp2)))) +(bind ?ie22 (assert (interface-element (name preExplNum2)))) +(bind ?ie23 (assert (interface-element (name preExplSide2)))) +(bind ?ie24 (assert (interface-element (name solveLeft2)))) +(bind ?ie25 (assert (interface-element (name solveRight2)))) +(bind ?ie26 (assert (interface-element (name postExplOp2)))) +(bind ?ie27 (assert (interface-element (name postExplNum2)))) +(bind ?ie28 (assert (interface-element (name postExplSide2)))) + +(bind ?line2 (assert (line (pre-explanations ?ie21 ?ie22 ?ie23) + (solution-steps ?ie24 ?ie25) + (post-explanations ?ie26 ?ie27 ?ie28) + (groups preExpl2Group solve2Group postExpl2Group) + ))) + +(bind ?ie31 (assert (interface-element (name preExplOp3)))) +(bind ?ie32 (assert (interface-element (name preExplNum3)))) +(bind ?ie33 (assert (interface-element (name preExplSide3)))) +(bind ?ie34 (assert (interface-element (name solveLeft3)))) +(bind ?ie35 (assert (interface-element (name solveRight3)))) +(bind ?ie36 (assert (interface-element (name postExplOp3)))) +(bind ?ie37 (assert (interface-element (name postExplNum3)))) +(bind ?ie38 (assert (interface-element (name postExplSide3)))) + +(bind ?line3 (assert (line (pre-explanations ?ie31 ?ie32 ?ie33) + (solution-steps ?ie34 ?ie35) + (post-explanations ?ie36 ?ie37 ?ie38) + (groups preExpl3Group solve3Group postExpl3Group) + ))) + +(bind ?ie41 (assert (interface-element (name preExplOp4)))) +(bind ?ie42 (assert (interface-element (name preExplNum4)))) +(bind ?ie43 (assert (interface-element (name preExplSide4)))) +(bind ?ie44 (assert (interface-element (name solveLeft4)))) +(bind ?ie45 (assert (interface-element (name solveRight4)))) +(bind ?ie46 (assert (interface-element (name postExplOp4)))) +(bind ?ie47 (assert (interface-element (name postExplNum4)))) +(bind ?ie48 (assert (interface-element (name postExplSide4)))) + +(bind ?line4 (assert (line (pre-explanations ?ie41 ?ie42 ?ie43) + (solution-steps ?ie44 ?ie45) + (post-explanations ?ie46 ?ie47 ?ie48) + (groups preExpl4Group solve4Group postExpl4Group) + ))) + +(bind ?ie51 (assert (interface-element (name preExplOp5)))) +(bind ?ie52 (assert (interface-element (name preExplNum5)))) +(bind ?ie53 (assert (interface-element (name preExplSide5)))) +(bind ?ie54 (assert (interface-element (name solveLeft5)))) +(bind ?ie55 (assert (interface-element (name solveRight5)))) +(bind ?ie56 (assert (interface-element (name postExplOp5)))) +(bind ?ie57 (assert (interface-element (name postExplNum5)))) +(bind ?ie58 (assert (interface-element (name postExplSide5)))) + +(bind ?line5 (assert (line (pre-explanations ?ie51 ?ie52 ?ie53) + (solution-steps ?ie54 ?ie55) + (post-explanations ?ie56 ?ie57 ?ie58) + (groups preExpl5Group solve5Group postExpl5Group) + ))) + +(bind ?ie61 (assert (interface-element (name preExplOp6)))) +(bind ?ie62 (assert (interface-element (name preExplNum6)))) +(bind ?ie63 (assert (interface-element (name preExplSide6)))) +(bind ?ie64 (assert (interface-element (name solveLeft6)))) +(bind ?ie65 (assert (interface-element (name solveRight6)))) +(bind ?ie66 (assert (interface-element (name postExplOp6)))) +(bind ?ie67 (assert (interface-element (name postExplNum6)))) +(bind ?ie68 (assert (interface-element (name postExplSide6)))) + +(bind ?line6 (assert (line (pre-explanations ?ie61 ?ie62 ?ie63) + (solution-steps ?ie64 ?ie65) + (post-explanations ?ie66 ?ie67 ?ie68) + (groups preExpl6Group solve6Group postExpl6Group) + ))) + +(bind ?ie71 (assert (interface-element (name preExplOp7)))) +(bind ?ie72 (assert (interface-element (name preExplNum7)))) +(bind ?ie73 (assert (interface-element (name preExplSide7)))) +(bind ?ie74 (assert (interface-element (name solveLeft7)))) +(bind ?ie75 (assert (interface-element (name solveRight7)))) +(bind ?ie76 (assert (interface-element (name postExplOp7)))) +(bind ?ie77 (assert (interface-element (name postExplNum7)))) +(bind ?ie78 (assert (interface-element (name postExplSide7)))) + +(bind ?line7 (assert (line (pre-explanations ?ie71 ?ie72 ?ie73) + (solution-steps ?ie74 ?ie75) + (post-explanations ?ie76 ?ie77 ?ie78) + (groups preExpl7Group solve7Group postExpl7Group) + ))) + +(bind ?ie81 (assert (interface-element (name preExplOp8)))) +(bind ?ie82 (assert (interface-element (name preExplNum8)))) +(bind ?ie83 (assert (interface-element (name preExplSide8)))) +(bind ?ie84 (assert (interface-element (name solveLeft8)))) +(bind ?ie85 (assert (interface-element (name solveRight8)))) +(bind ?ie86 (assert (interface-element (name postExplOp8)))) +(bind ?ie87 (assert (interface-element (name postExplNum8)))) +(bind ?ie88 (assert (interface-element (name postExplSide8)))) + +(bind ?line8 (assert (line (pre-explanations ?ie81 ?ie82 ?ie83) + (solution-steps ?ie84 ?ie85) + (post-explanations ?ie86 ?ie87 ?ie88) + (groups preExpl8Group solve8Group postExpl8Group) + ))) + +(bind ?ie91 (assert (interface-element (name preExplOp9)))) +(bind ?ie92 (assert (interface-element (name preExplNum9)))) +(bind ?ie93 (assert (interface-element (name preExplSide9)))) +(bind ?ie94 (assert (interface-element (name solveLeft9)))) +(bind ?ie95 (assert (interface-element (name solveRight9)))) +(bind ?ie96 (assert (interface-element (name postExplOp9)))) +(bind ?ie97 (assert (interface-element (name postExplNum9)))) +(bind ?ie98 (assert (interface-element (name postExplSide9)))) + +(bind ?line9 (assert (line (pre-explanations ?ie91 ?ie92 ?ie93) + (solution-steps ?ie94 ?ie95) + (post-explanations ?ie96 ?ie97 ?ie98) + (groups preExpl9Group solve9Group postExpl9Group) + ))) + +(bind ?ie101 (assert (interface-element (name preExplOp10)))) +(bind ?ie102 (assert (interface-element (name preExplNum10)))) +(bind ?ie103 (assert (interface-element (name preExplSide10)))) +(bind ?ie104 (assert (interface-element (name solveLeft10)))) +(bind ?ie105 (assert (interface-element (name solveRight10)))) +(bind ?ie106 (assert (interface-element (name postExplOp10)))) +(bind ?ie107 (assert (interface-element (name postExplNum10)))) +(bind ?ie108 (assert (interface-element (name postExplSide10)))) + +(bind ?line10 (assert (line (pre-explanations ?ie101 ?ie102 ?ie103) + (solution-steps ?ie104 ?ie105) + (post-explanations ?ie106 ?ie107 ?ie108) + (groups preExpl10Group solve10Group postExpl10Group) + ))) + +(bind ?ie111 (assert (interface-element (name preExplOp11)))) +(bind ?ie112 (assert (interface-element (name preExplNum11)))) +(bind ?ie113 (assert (interface-element (name preExplSide11)))) +(bind ?ie114 (assert (interface-element (name solveLeft11)))) +(bind ?ie115 (assert (interface-element (name solveRight11)))) +(bind ?ie116 (assert (interface-element (name postExplOp11)))) +(bind ?ie117 (assert (interface-element (name postExplNum11)))) +(bind ?ie118 (assert (interface-element (name postExplSide11)))) + +(bind ?line11 (assert (line (pre-explanations ?ie111 ?ie112 ?ie113) + (solution-steps ?ie114 ?ie115) + (post-explanations ?ie116 ?ie117 ?ie118) + (groups preExpl11Group solve11Group postExpl11Group) + ))) + +(bind ?ie121 (assert (interface-element (name preExplOp12)))) +(bind ?ie122 (assert (interface-element (name preExplNum12)))) +(bind ?ie123 (assert (interface-element (name preExplSide12)))) +(bind ?ie124 (assert (interface-element (name solveLeft12)))) +(bind ?ie125 (assert (interface-element (name solveRight12)))) +(bind ?ie126 (assert (interface-element (name postExplOp12)))) +(bind ?ie127 (assert (interface-element (name postExplNum12)))) +(bind ?ie128 (assert (interface-element (name postExplSide12)))) + +(bind ?line12 (assert (line (pre-explanations ?ie121 ?ie122 ?ie123) + (solution-steps ?ie124 ?ie125) + (post-explanations ?ie126 ?ie127 ?ie128) + (groups preExpl12Group solve12Group postExpl12Group) + ))) + +(bind ?ie131 (assert (interface-element (name preExplOp13)))) +(bind ?ie132 (assert (interface-element (name preExplNum13)))) +(bind ?ie133 (assert (interface-element (name preExplSide13)))) +(bind ?ie134 (assert (interface-element (name solveLeft13)))) +(bind ?ie135 (assert (interface-element (name solveRight13)))) +(bind ?ie136 (assert (interface-element (name postExplOp13)))) +(bind ?ie137 (assert (interface-element (name postExplNum13)))) +(bind ?ie138 (assert (interface-element (name postExplSide13)))) + +(bind ?line13 (assert (line (pre-explanations ?ie131 ?ie132 ?ie133) + (solution-steps ?ie134 ?ie135) + (post-explanations ?ie136 ?ie137 ?ie138) + (groups preExpl13Group solve13Group postExpl13Group) + ))) + +(bind ?ie141 (assert (interface-element (name preExplOp14)))) +(bind ?ie142 (assert (interface-element (name preExplNum14)))) +(bind ?ie143 (assert (interface-element (name preExplSide14)))) +(bind ?ie144 (assert (interface-element (name solveLeft14)))) +(bind ?ie145 (assert (interface-element (name solveRight14)))) +(bind ?ie146 (assert (interface-element (name postExplOp14)))) +(bind ?ie147 (assert (interface-element (name postExplNum14)))) +(bind ?ie148 (assert (interface-element (name postExplSide14)))) + +(bind ?line14 (assert (line (pre-explanations ?ie141 ?ie142 ?ie143) + (solution-steps ?ie144 ?ie145) + (post-explanations ?ie146 ?ie147 ?ie148) + (groups preExpl14Group solve14Group postExpl14Group) + ))) + +(bind ?ie151 (assert (interface-element (name preExplOp15)))) +(bind ?ie152 (assert (interface-element (name preExplNum15)))) +(bind ?ie153 (assert (interface-element (name preExplSide15)))) +(bind ?ie154 (assert (interface-element (name solveLeft15)))) +(bind ?ie155 (assert (interface-element (name solveRight15)))) +(bind ?ie156 (assert (interface-element (name postExplOp15)))) +(bind ?ie157 (assert (interface-element (name postExplNum15)))) +(bind ?ie158 (assert (interface-element (name postExplSide15)))) + +(bind ?line15 (assert (line (pre-explanations ?ie151 ?ie152 ?ie153) + (solution-steps ?ie154 ?ie155) + (post-explanations ?ie156 ?ie157 ?ie158) + (groups preExpl15Group solve15Group postExpl15Group) + ))) + +(bind ?ie161 (assert (interface-element (name preExplOp16)))) +(bind ?ie162 (assert (interface-element (name preExplNum16)))) +(bind ?ie163 (assert (interface-element (name preExplSide16)))) +(bind ?ie164 (assert (interface-element (name solveLeft16)))) +(bind ?ie165 (assert (interface-element (name solveRight16)))) +(bind ?ie166 (assert (interface-element (name postExplOp16)))) +(bind ?ie167 (assert (interface-element (name postExplNum16)))) +(bind ?ie168 (assert (interface-element (name postExplSide16)))) + +(bind ?line16 (assert (line (pre-explanations ?ie161 ?ie162 ?ie163) + (solution-steps ?ie164 ?ie165) + (post-explanations ?ie166 ?ie167 ?ie168) + (groups preExpl16Group solve16Group postExpl16Group) + ))) + +(bind ?ie171 (assert (interface-element (name preExplOp17)))) +(bind ?ie172 (assert (interface-element (name preExplNum17)))) +(bind ?ie173 (assert (interface-element (name preExplSide17)))) +(bind ?ie174 (assert (interface-element (name solveLeft17)))) +(bind ?ie175 (assert (interface-element (name solveRight17)))) +(bind ?ie176 (assert (interface-element (name postExplOp17)))) +(bind ?ie177 (assert (interface-element (name postExplNum17)))) +(bind ?ie178 (assert (interface-element (name postExplSide17)))) + +(bind ?line17 (assert (line (pre-explanations ?ie171 ?ie172 ?ie173) + (solution-steps ?ie174 ?ie175) + (post-explanations ?ie176 ?ie177 ?ie178) + (groups preExpl17Group solve17Group postExpl17Group) + ))) + +(bind ?ie181 (assert (interface-element (name preExplOp18)))) +(bind ?ie182 (assert (interface-element (name preExplNum18)))) +(bind ?ie183 (assert (interface-element (name preExplSide18)))) +(bind ?ie184 (assert (interface-element (name solveLeft18)))) +(bind ?ie185 (assert (interface-element (name solveRight18)))) +(bind ?ie186 (assert (interface-element (name postExplOp18)))) +(bind ?ie187 (assert (interface-element (name postExplNum18)))) +(bind ?ie188 (assert (interface-element (name postExplSide18)))) + +(bind ?line18 (assert (line (pre-explanations ?ie181 ?ie182 ?ie183) + (solution-steps ?ie184 ?ie185) + (post-explanations ?ie186 ?ie187 ?ie188) + (groups preExpl18Group solve18Group postExpl18Group) + ))) + +(bind ?this-problem + (assert (problem (open-lines ?line1 ?line2 ?line3 ?line4 ?line5 ?line6 ?line7 ?line8 ?line9 ?line10 + ?line11 ?line12 ?line13 ?line14 ?line15 ?line16 ?line17 ?line18 ) + ))) + +(modify ?this-problem + (pre-expl-groups ; stores the names of the groups in the interface - used for showing and hiding them all + ; mostly needed for testing with going back to the start state, but + ; this is essentially a workaround + ; TO DO: perhaps work with some custom actionscript? + (create$ preExpl1Group preExpl2Group preExpl3Group + preExpl4Group preExpl5Group preExpl6Group + preExpl7Group preExpl8Group preExpl9Group + preExpl10Group preExpl11Group preExpl12Group + preExpl13Group preExpl14Group preExpl15Group + preExpl16Group preExpl17Group preExpl18Group + )) + (solve-groups + (create$ solve1Group solve2Group solve3Group + solve4Group solve5Group solve6Group + solve7Group solve8Group solve9Group + solve10Group solve11Group solve12Group + solve13Group solve14Group solve15Group + solve16Group solve17Group solve18Group + )) + (post-expl-groups + (create$ postExpl1Group postExpl2Group postExpl3Group + postExpl4Group postExpl5Group postExpl6Group + postExpl7Group postExpl8Group postExpl9Group + postExpl10Group postExpl11Group postExpl12Group + postExpl13Group postExpl14Group postExpl15Group + postExpl16Group postExpl17Group postExpl18Group + ))) + + +;; Turn on explanations -- currently done by rules, since that is more convenient for testing +;(modify ?this-problem + ; (pre-explanations-p TRUE) + ; (post-explanations-p TRUE) +; ) diff --git a/CognitiveModel/wmeTypes.clp b/CognitiveModel/wmeTypes.clp new file mode 100644 index 0000000..c4a11fe --- /dev/null +++ b/CognitiveModel/wmeTypes.clp @@ -0,0 +1,303 @@ +(call java.lang.System setProperty "UseStudentValuesFact" "true") +(defglobal ?*n* = 1) ; or is defglobal for defining a constant? needed so each fact can be unique +(set-maximum-chain-depth 7) +(set-hint-policy "Bias Hints by Prior Error Only") +(call (engine) setStrategyByName "buggy-rules-normal-salience") +;(call (engine) setSkipWhyNotSaves TRUE) + +(deftemplate studentValues + (slot selection) + (slot action) + (slot input)) + +(deftemplate hint (slot now)) + +(deftemplate custom-fields + (slot preEqSys) + (slot preEqStu) + (slot postEqSys) + (slot postEqStu) + ; (slot curLine) + (slot act) ; probably good to avoid "action" as a DataShop column + (slot transf) +; (slot strategic) +; (slot varLeft) +; (slot varRight) +; (slot constLeft) +; (slot constRight) +; (slot zeroLeft) +; (slot zeroRight) +; (slot prodLeft) +; (slot prodRight) +; (slot negVarLeft) +; (slot negVarRight) +; (slot negConstLeft) +; (slot negConstRight) + (slot preEqProps) + (slot transfProps) + (slot pKnowValues) + ) + +;(deftemplate Skill +; (slot name) +; (slot category) +; (slot description) +; (slot label) +; (slot opportunityCount) +; (slot pGuess) +; (slot pKnown) +; (slot pLearn) +; (slot pSlip)) + +;;---------------------------------------------------------------------------------------- +;; +;; Representing equations +;; +;; +;; Equations are represented in working memory as follows: +;; An equation (template: equation) has two sides, which are expressions (template: expr) +;; Expressions are lists of terms; it is implied that the expression is the sum of these terms. +;; (Should we perhaps call them term-lists instead of expressions?) +;; There are 3 kinds of terms: +;; - simple terms (i.e., constant terms or variable terms) (template: simple-term) +;; - product-terms (template: product-term), and +;; - quotient-terms (template: quotient-term) +;; Product-terms and quotient-terms each have factors; factors are always expressions, to make the +;; structure fully recursive. In product-terms, the factors represent the expressions being multiplied, +;; in quotient-terms they represent the dividend expression and the divisor expression, respectively. +;; (Why is it that we have product-term and quotient-term but not additive-term and subtractive-term? Wouldn't +;; one expect symmetry?) +;; TO DO: representing an expression that is 0 is currently done with a simple term that has coefficient 0. +;; But wouldn't it be better to set the term list to the empty list? +;; + +(deftemplate problem + (slot cur-transformation) + (slot hint-transformation) ; used only by hint-move-simple-term + (slot prov-transformation) ; a transformation can be in provisional status, meaning that some of + ; the details are tentative and may change (this happens only when doing pre-explanations) + ; e.g., after entering "Add" the provisional transformation may be + ; "Add 3 to both sides" but depending on later student input this may + ; change to "Add 4x to both sides" (the "Add" part will not change, + ; however, once that has been confirmed as correct input by the + ; student) + (slot cur-equation) ; the equation as it was at the start of the current transformation (i.e., this + ; slot is updated each time a transformation is completed) + (slot printed (default FALSE)) ; for development only + (multislot open-lines) + (multislot closed-lines) + (multislot pre-expl-groups ; stores the names of the groups in the interface - used for showing and hiding them all + ; mostly needed for testing with going back to the start state +; (default preExpl1Group preExpl2Group preExpl3Group +; preExpl4Group preExpl5Group preExpl6Group +; preExpl7Group preExpl8Group preExpl9Group) + ) + (multislot solve-groups + ; commentary +; (default solve1Group solve2Group solve3Group +; solve4Group solve5Group solve6Group +; solve7Group solve8Group solve9Group) + ) + (multislot post-expl-groups +; (default postExpl1Group postExpl2Group postExpl3Group +; postExpl4Group postExpl5Group postExpl6Group +; postExpl7Group postExpl8Group postExpl9Group) + ) + (multislot solve-groups) + (multislot post-expl-groups) + (multislot steps) + (slot checked-answer-p) ; used to do things in a non-model-tracing kind of way, + ; to improve the model's efficiency in recognizing errors + (slot cur-step) + (slot pre-explanations-p) + (slot post-explanations-p) + ) + +(deftemplate equation + (multislot sides) + (slot fact-nr (default-dynamic (bind ?*n* (+ 1 ?*n*)))) ; so every equation is a unique fact + ) + +(deftemplate expr + (multislot terms) + (slot type (default expr)) + (slot fact-nr (default-dynamic (bind ?*n* (+ 1 ?*n*)))) ; so every expr is a unique fact + ) + +(deftemplate simple-term + (slot coeff) + (slot var) + (slot type (default simple-term)) ; bizarre but could not figure out other way to find out a fact's type + (slot fact-nr (default-dynamic (bind ?*n* (+ 1 ?*n*)))) ; so every term is a unique fact + ) + +(deftemplate quotient-term + (multislot factors) ; should be expr-s --- but maybe they should not be called factors? seemed like an appropriate generic term + (slot type (default quotient-term)) + (slot fact-nr (default-dynamic (bind ?*n* (+ 1 ?*n*)))) ; so every term is a unique fact + ) + +(deftemplate product-term ; or generalize across quotient and product term? + (multislot factors) ; should be expr-s + (slot type (default product-term)) + (slot fact-nr (default-dynamic (bind ?*n* (+ 1 ?*n*)))) ; so every term is a unique fact + ) + +;;---------------------------------------------------------------------------------------- +;; +;; Representing transformations +;; +;; Explicit representation of transformations is necessary primarily to help with being +;; flexible and/or configurable with respect to step skipping (i.e., the challenge is to deal flexibly +;; with intermediate steps; but in a way that makes it easy to require them). Representing transformation may also +;; help with the implementation of the undo facility although that is less clear. +;; +;; A transformation represents application of one of the equation-solving operators, with simplification. +;; A transformation can therefore occupy multiple rows in the interface (up to 3). +;; Also, essentially the same transformation can occupy different numbers of rows in the interface, +;; depending on whether the student does the simplification steps implicity, explicitly and at the same +;; time (i.e., in the same row) to both sides, or explicitly and one-by-one. +;; Transformations are created by the rules that capture the main equation-solving operators (which can fire +;; only when there is no current transformation - i.e., when the previous one has been completed). +;; At any point in time, there is a single "current transformation." It is needed to support both implicit +;; and explicit simplification, as described above. The transformation records to what degree the +;; simplification has happened already. +;; There are rules for creating transformations and rules for writing out transformations in the interface, +;; which requires multiple steps (at least 2, at most 6) in the interface. +;; +;; The connection between transformation and rows in the interface is recorded in the lines. +;; --> Or at least, that is the intention. +;; --> The idea seems to be that each time a line is closed, a duplicate of the current transformation is +;; stored with that line. +;; --> However, for a step-wise undo (step in the ITS sense of the terms), it would be necessary to record +;; the steps. E.g., create a stack of steps, copying the current transformation and perhaps other state +;; info when a step is taken. +;; + +;; Reminder: when this template is changed, the function copy-transf needs to change as well. +(deftemplate transformation + (slot equation) ; the target of the transformation, i.e., what the equation will look + ; like once the transformation is complete + (multislot to-be-simplified) ; values can be: left, right; indicares whether simplification still needs to occur + (multislot written) ; values can be: left, right; indicates whether the simplified form + ; has been written + (slot pre-explained) + (slot post-explained) ; indicates whether the post-explanation has happened yet (should happen on the + ; first line for this transformation) + (slot focus) ; focus field is set in each cycle - it serves mainly to prune the + ; search, it seems + (slot prev-left-val) ; when transformation occupies multiple rows in the interface, the value + ; (i.e., the expression represented as string) in the previous row + ; corresponding to the current transformation; nil if the previous row + ; corresponds to a different transformation + ; --> what is this information useful for? + (slot prev-right-val) ; analogous to prev-left-val + (multislot description) ; describes the transformation - needed for pre/post explanations + ; expect three values: operation, number, side(s) + (slot skip-expl-sel2) ; operators that skip the second out of three explanations fields (e.g., + ; distribute, combine like terms) must indicate that here (should be + ; selection name) + (slot no-hints-p) ; indicates we are in a chain that has no hints - within-chain use only + (slot strategic-p (default TRUE)) ; so we can mark transformations that are not strategic + (slot to-side) ; this is for add/subtract transformations only - which side are we + ; moving to? +; (slot check-for-circle (default FALSE)) ; this enables the rule(s) that check(s) for repeated states to +; ; step in before the focus rule(s) +; ; e.g., ax = bx + c --> ax - c = bx --> ax = bx + c +; ; so the tutor can provide strategy feedback +; (slot gave-circle-feedback (default -1)) ; keep track of how many times strategy feedback has been +; ; given (so tutor does not repeat it too many times) +; ; + (slot fact-nr (default-dynamic (bind ?*n* (+ 1 ?*n*)))) + ; so that every transformation is a fact with unique slot values - Jess refuses to create copies + ) + + +;;---------------------------------------------------------------------------------------- +;; +;; Representing steps so they can be undone by the student +;; +;; To undo a step, need to know: +;; - interface component that was filled in +;; - interface components that were revealed (or hidden??) +;; - lists of open and closed rows +;; - the current transformation at the beginning of the step +;; - the current equation at the beginning of the step (actually, could that simply be the one in the problem? no) + +;; The idea is that to undo a step, one needs information only from the corresponding step fact. +;; So it should record the step (so the Flash interface component can be reset and the correspodning value in WM set to NIL) +;; plus the pre-state, i.e., the state of various things just prior to the step. +;; So every chain needs a first rule that records the pre-state. +;; +;; +(deftemplate step + (multislot interface-elements) ; component fact, not name of the component + (multislot pre-open-list) + (multislot pre-closed-list) + (slot pre-transformation) + (slot pre-equation) + (slot provisional-p) ; whether the transformation should be restored as a provisional transformation + ; or as a regular transformation + (slot revealed-interface-group) + ) + +;;---------------------------------------------------------------------------------------- +;; +;; Structures for program control +;; + +;; EXPLAIN-OP +;; +;; Explain-ops are transient facts - they exist temporarily, only within a single chain (i.e., +;; are retracted in the same chain in which they are created and not kept in working memory across +;; cycles). +;; Currently (July 31), they are used only for pre-explanations, to capture things that need to happen +;; on the right-hand-side once the student SAI has been correctly predicted. In particular, it +;; makes the step undoable in the usual manner and stores the transformation as a provisional +;; transformation when needed (i.e., when the explain-op's prov-transformation slot contains a +;; transformation). +;; TO DO: consider whether writing a Jess function for the common rhs actions might not be better. +;; Currently, an add/subtract transformation is provisional until the operand (misnomed as "number") +;; has been determined. Doesn't mean there are always multiple options, just that the model is not +;; structured to collect all possible options and then decide based on what options are collected. +;; (Could be neat to build a "strategic" model that could do just that, i.e., collect the options, +;; reason about which one is best, and then decide.) +;; TO DO: combining like terms is provisional until the sides have been specified (so how to deal with +;; with.) +;; +;; Currently (July 31), pre-explain-add-op creates an explain-op +;; pre-explain-distribute-op also creates an explain-op +;; +(deftemplate explain-op ; TO DO: call this subgoal? but have not been consistent with subgoals + (slot interface-element) + (slot input) + (slot prov-transformation) + (slot lock) ; in distribute ops, want to lock the num field + ) + + +;;---------------------------------------------------------------------------------------- +;; +;; Representing the interface in working memory +;; + +;; Option +;; interface-elements is a list of line facts +;; each line has pre-explanation, equation, and post-explanation slots +;; these slots contain lists of component names? +;; Or is this needlessly complicated? + +(deftemplate line + (multislot pre-explanations) + (multislot solution-steps) + (multislot post-explanations) + (multislot groups) + ) + +(deftemplate interface-element + (slot name) + (slot value) + ) + +; tell productionRules file that templates have been parsed +(provide wmeTypes) \ No newline at end of file diff --git a/FinalBRDs/-x 6eq15.brd b/FinalBRDs/-x 6eq15.brd new file mode 100644 index 0000000..42c1074 --- /dev/null +++ b/FinalBRDs/-x 6eq15.brd @@ -0,0 +1,71 @@ + + + + + NotePropertySet + + StartProblem + -x+6eq15 + + + + NotePropertySet + + InterfaceAction + 6871320589C21B28 + + startLeft + + + UpdateTextField + + + -x + 6 + + + + + NotePropertySet + + InterfaceAction + 9582DC6EDE63B86A + + startRight + + + UpdateTextField + + + 15 + + + + + NotePropertySet + + StartStateEnd + + + + + -x+6eq15 + 1 + + 246 + 30 + + + + Add/subtract_constant_from_both_sides + LDashb + + + Combine_constant_terms + LDashb + + + Divide_both_sides_by_the_variable_coefficient + LDashb + + + diff --git a/FinalBRDs/-x+6eq15.brd b/FinalBRDs/-x+6eq15.brd new file mode 100644 index 0000000..42c1074 --- /dev/null +++ b/FinalBRDs/-x+6eq15.brd @@ -0,0 +1,71 @@ + + + + + NotePropertySet + + StartProblem + -x+6eq15 + + + + NotePropertySet + + InterfaceAction + 6871320589C21B28 + + startLeft + + + UpdateTextField + + + -x + 6 + + + + + NotePropertySet + + InterfaceAction + 9582DC6EDE63B86A + + startRight + + + UpdateTextField + + + 15 + + + + + NotePropertySet + + StartStateEnd + + + + + -x+6eq15 + 1 + + 246 + 30 + + + + Add/subtract_constant_from_both_sides + LDashb + + + Combine_constant_terms + LDashb + + + Divide_both_sides_by_the_variable_coefficient + LDashb + + + diff --git a/FinalBRDs/1416.brd b/FinalBRDs/1416.brd new file mode 100644 index 0000000..4bd76ef --- /dev/null +++ b/FinalBRDs/1416.brd @@ -0,0 +1,1558 @@ + + + + + + SendNoteProperty + + StartProblem + 1416 + + + + NotePropertySet + + InterfaceAction + ECFFA8E5FEDBDD17 + + firstNumGiven + + + UpdateTextArea + + + 1 + + + + + NotePropertySet + + InterfaceAction + 3F45C5A9DBEF00AC + + firstDenGiven + + + UpdateTextArea + + + 4 + + + + + NotePropertySet + + InterfaceAction + BA263816C75B0445 + + secNumGiven + + + UpdateTextArea + + + 1 + + + + + NotePropertySet + + InterfaceAction + 6AB90811187A2DE9 + + secDenGiven + + + UpdateTextArea + + + 6 + + + + + SendNoteProperty + + StartStateEnd + + + + + 1416 + 1 + + 246 + 30 + + + + state1 + 2 + + 242 + 140 + + + + state2 + 3 + + 166 + 135 + + + + state3 + 4 + + 318 + 135 + + + + state4 + 5 + + 119 + 70 + + + + state5 + 6 + + 365 + 70 + + + + state6 + 7 + + 242 + 250 + + + + state7 + 8 + + 242 + 360 + + + + state8 + 9 + + 242 + 470 + + + + state9 + 10 + + 242 + 580 + + + + state10 + 11 + + 238 + 690 + + + + state11 + 12 + + 238 + 800 + + + + state12 + 13 + + 238 + 910 + + + + Done + 14 + + 245 + 1020 + + + + state14 + 15 + + 162 + 245 + + + + state15 + 16 + + 162 + 355 + + + + state16 + 17 + + 162 + 465 + + + + state17 + 18 + + 162 + 575 + + + + state18 + 19 + + 162 + 685 + + + + state19 + 20 + + 162 + 795 + + + + state20 + 21 + + 162 + 905 + + + + Done_2 + 22 + + 161 + 1015 + + + + + + + + 1 + + NotePropertySet + + InterfaceAction + + firstDenConv + + + UpdateTextArea + + + 12 + + + + No, this is not correct. + + What could you do with the fractions so that they have the same denominator? + Choose a denominator both denominators go into. + Enter '12', the least common denominator between both fractions, in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + firstDenConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 12 + + + Student + + + No-Applicable + + determine-lcd fraction-addition + -1 + + 1 + 2 + 0 + + + + + + + 2 + + NotePropertySet + + InterfaceAction + + firstDenConv + + + UpdateTextArea + + + 24 + + + + No, this is not correct. + Good job! + What is the least common multiple of 4 and 6? + What is 4 x 6? + Please enter '24' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + firstDenConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 24 + + + Student + + + No-Applicable + + multiply-denominators fraction-addition + -1 + + 1 + 3 + 0 + + + + + + + 3 + + NotePropertySet + + InterfaceAction + + firstDenConv + + + UpdateTextArea + + + 10 + + + + Instead of trying to add the denominators, choose a denominator both denominators go into. + + Please enter '10' in the highlighted field. + + Buggy Action + Correct Action + Never Checked + + + + ExactMatcher + firstDenConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 10 + + + Student + + + No-Applicable + + unnamed + -1 + + 1 + 4 + 0 + + + + + + + 4 + + NotePropertySet + + InterfaceAction + + secDenConv + + + UpdateTextArea + + + 10 + + + + Instead of trying to add the denominators, choose a denominator both denominators go into. + + Please enter '10' in the highlighted field. + + Buggy Action + Correct Action + Never Checked + + + + ExactMatcher + secDenConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 10 + + + Student + + + No-Applicable + + unnamed + -1 + + 1 + 5 + 0 + + + + + + + 5 + + NotePropertySet + + InterfaceAction + + ansNum1 + + + UpdateTextArea + + + 2 + + + + You can't add the numerators until you've converted the fractions. + + Please enter '2' in the highlighted field. + + Buggy Action + Correct Action + Never Checked + + + + ExactMatcher + ansNum1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 2 + + + Student + + + No-Applicable + + unnamed + -1 + + 1 + 6 + 0 + + + + + + + 6 + + NotePropertySet + + InterfaceAction + + secDenConv + + + UpdateTextArea + + + 12 + + + + No, this is not correct. + + What could you do with the fractions so that they have the same denominator? + Choose a denominator both denominators go into. + Enter '12', the least common denominator between both fractions, in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + secDenConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 12 + + + Student + + + No-Applicable + + determine-lcd fraction-addition + -1 + + 2 + 7 + 0 + + + + + + + 7 + + NotePropertySet + + InterfaceAction + + ansDen1 + + + UpdateTextArea + + + 12 + + + + No, this is not correct. + + Once you've found the least common denominator, you can put in the highlighted field. + Find a common denominator for both fractions. Choose a denominator both denominators go into. + Please enter the least common denominator, '12', in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + ansDen1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 12 + + + Student + + + No-Applicable + + copy-answer-denominator fraction-addition + -1 + + 7 + 8 + 0 + + + + + + + 8 + + NotePropertySet + + InterfaceAction + + firstNumConv + + + UpdateTextArea + + + 3 + + + + No, this is not correct. + + Divide the fraction's denominator into the common denominator. + Multiply the numerator with the number you got. + Multiply 1 and 3. Put this numerator, 3, in the highlighted cell. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + firstNumConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 3 + + + Student + + + No-Applicable + + convert-numerator fraction-addition + -1 + + 8 + 9 + 0 + + + + + + + 9 + + NotePropertySet + + InterfaceAction + + secNumConv + + + UpdateTextArea + + + 2 + + + + No, this is not correct. + + Divide the fraction's denominator into the common denominator. + Multiply the numerator with the number you got. + Multiply 1 and 2. Put this numerator, 2, in the highlighted cell. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + secNumConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 2 + + + Student + + + No-Applicable + + convert-numerator fraction-addition + -1 + + 9 + 10 + 0 + + + + + + + 10 + + NotePropertySet + + InterfaceAction + + ansNum1 + + + UpdateTextArea + + + 5 + + + + No, this is not correct. + + When fractions have the same denominator they can be added. + Go ahead and add the numerator; the denominator stays the same. + Please enter '5' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + ansNum1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 5 + + + Student + + + No-Applicable + + add-numerators fraction-addition + -1 + + 10 + 11 + 0 + + + + + + + 11 + + NotePropertySet + + InterfaceAction + + ansNumFinal1 + + + UpdateTextArea + + + 5 + + + + No, this is not correct. + + Please enter '5' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + ansNumFinal1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 5 + + + Student + + + No-Applicable + + reduce-numerator fraction-addition + -1 + + 11 + 12 + 0 + + + + + + + 12 + + NotePropertySet + + InterfaceAction + + ansDenFinal1 + + + UpdateTextArea + + + 12 + + + + No, this is not correct. + + Please enter '12' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + ansDenFinal1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 12 + + + Student + + + No-Applicable + + reduce-denominator fraction-addition + -1 + + 12 + 13 + 0 + + + + + + + 13 + + NotePropertySet + + InterfaceAction + + done + + + ButtonPressed + + + -1 + + + + No, this is not correct. + + You're done with the problem; your answer is in its simplest form. + Click the highlighted 'Done' button to finish. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + done + + + + + ExactMatcher + ButtonPressed + + + + + ExactMatcher + -1 + + + Student + + + No-Applicable + + unnamed + -1 + + 13 + 14 + 0 + + + + + + + 14 + + NotePropertySet + + InterfaceAction + + secDenConv + + + UpdateTextArea + + + 24 + + + + No, this is not correct. + + What is the a common multiple of 4 and 6? + What is 4 x 6? + Please enter '24' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + secDenConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 24 + + + Student + + + No-Applicable + + multiply-denominators fraction-addition + -1 + + 3 + 15 + 0 + + + + + + + 15 + + NotePropertySet + + InterfaceAction + + secNumConv + + + UpdateTextArea + + + 4 + + + + No, this is not correct. + + Divide each denominator into the common denominator. Then multiply the number you got with the numerator. + 6 goes in to 24 4 times; so multiply 1 and 4. + Enter '4', the converted numerator, in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + secNumConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 4 + + + Student + + + No-Applicable + + convert-numerator fraction-addition + -1 + + 15 + 16 + 0 + + + + + + + 16 + + NotePropertySet + + InterfaceAction + + firstNumConv + + + UpdateTextArea + + + 6 + + + + No, this is not correct. + + Divide the denominator into the common denominator. Then multiply the number you got with the numerator. + 4 goes in to 24 6 times; so multiply 1 and 6. + Enter '6', the converted numerator, in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + firstNumConv + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 6 + + + Student + + + No-Applicable + + convert-numerator fraction-addition + -1 + + 16 + 17 + 0 + + + + + + + 17 + + NotePropertySet + + InterfaceAction + + ansNum1 + + + UpdateTextArea + + + 10 + + + + No, this is not correct. + + If you've already converted the fractions, you can add the converted numerators and put the sum over the common denominator. + The converted numerators are 6 and 4. What is 6 + 4? + Enter '10' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + ansNum1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 10 + + + Student + + + No-Applicable + + add-numerators fraction-addition + -1 + + 17 + 18 + 0 + + + + + + + 18 + + NotePropertySet + + InterfaceAction + + ansDen1 + + + UpdateTextArea + + + 24 + + + + No, this is not correct. + + When you've converted fractions to have an identical denominator, you use that same denominator in the answer. + Just carry the denominator, 24, down to the highlighted field. + Please enter '24' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + ansDen1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 24 + + + Student + + + No-Applicable + + copy-answer-denominator fraction-addition + -1 + + 18 + 19 + 0 + + + + + + + 19 + + NotePropertySet + + InterfaceAction + + ansDenFinal1 + + + UpdateTextArea + + + 12 + + + + No, this is not correct. + + You need to simplify your fraction. + 2 goes into both the numerator and denominator. How many times can 2 go into 24? + Please enter '12' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + ansDenFinal1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 12 + + + Student + + + No-Applicable + + reduce-denominator fraction-addition + -1 + + 19 + 20 + 0 + + + + + + + 20 + + NotePropertySet + + InterfaceAction + + ansNumFinal1 + + + UpdateTextArea + + + 5 + + + + No, this is not correct. + + You need to simplify your fraction. + 2 goes into both the numerator and denominator. How many times can 2 go into 10? + Please enter '5' in the highlighted field. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + ansNumFinal1 + + + + + ExactMatcher + UpdateTextArea + + + + + ExactMatcher + 5 + + + Student + + + No-Applicable + + reduce-numerator fraction-addition + -1 + + 20 + 21 + 0 + + + + + + + 21 + + NotePropertySet + + InterfaceAction + + done + + + ButtonPressed + + + -1 + + + + No, this is not correct. + + You're all done with the problem! + Click the Done button. + + Correct Action + Correct Action + Never Checked + + + + ExactMatcher + done + + + + + ExactMatcher + ButtonPressed + + + + + ExactMatcher + -1 + + + Student + + + No-Applicable + + unnamed + -1 + + 21 + 22 + 0 + + + copy-answer-denominator + fraction-addition + + + reduce-denominator + fraction-addition + + + determine-lcd + fraction-addition + + + convert-numerator + fraction-addition + + + reduce-numerator + fraction-addition + + + add-numerators + fraction-addition + + + multiply-denominators + fraction-addition + + + + diff --git a/HTML/Assets/CTAT.css b/HTML/Assets/CTAT.css new file mode 100644 index 0000000..dc5ba32 --- /dev/null +++ b/HTML/Assets/CTAT.css @@ -0,0 +1,816 @@ +/** +This file defines the default look and feel of CTAT components. + +Table of Contents: +** Global Utility + #unselectable +** CTAT General + #CTAT Generated Components +** Button Based Components + #CTAT Generated button components +** CTAT Text Based Components + #CTAT table components + #CTAT text components + #CTATNumericStepper +** CTAT Stateful Components + #CTAT checkbox + #CTAT radio button + #CTAT combobox + #CTAT jumble + #CTAT drag-n-drop +** CTAT SVG Based Component + #CTAT SVG Component + #CTAT SVG button + #CTAT number line + #CTAT pie chart + #CTAT fraction bar +** Feedback Components + #CTAT Hint Button + #CTAT hint window + #CTAT Skill Window +** Container Components + #CTAT Scroll Pane Component +** Multimedia Components + #CTAT video +** Disabling + #CTAT button disabled +** Grading + #CTAT--incorrect + #CTAT--hint + #CTAT--correct +*/ + + +/******************************* Global Utility ******************************/ + +/*-------------------------------------------------*\ + #unselectable + General class for making entities unselectable. +\*-------------------------------------------------*/ + +.unselectable { + user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; +} + + +/******************************** CTAT General *******************************/ + +/*-----------------------------------------*\ + #CTAT Generated Components + Base class for CTAT generated components +\*-----------------------------------------*/ + +.CTAT-gen-component { + /* assume generated component is always within a div and + that it should occupy the entire div */ + width: 100%; + height: 100%; + padding: 0px; +} +.CTAT-gen-component:focus { + outline: none; /* No focus highlighting */ +} + + +/******************* CTAT Tutor Panel Defaults *******************************/ + +.CTATTutor { width: 900px; height: 600px; display: flex; flex-flow: column; } +.CTATProblemSolving { flex: 1 0 auto; display: flex; } +.CTATProblem, .CTATSolution { border: 1px solid #CCCCCC; border-radius: 5px; padding: 5px; } +.CTATProblem { flex: 1; } +.CTATSolution { flex: 2; } +.CTATTools { height: 140px; display: flex; flex: none; } +.CTATButtons { height: 140px; display: flex; flex-flow: column; justify-content: space-between; } + + +/*********************** CTAT Component Size Defaults ************************/ + +.CTATAudioButton { display: inline-block; } /* size to the content of the button */ +.CTATButton, .CTATSubmitButton { display: inline-block; } /* inline to act like