diff --git a/doc/content/DeMorgan1836-induction.jpeg b/doc/content/DeMorgan1836-induction.jpeg new file mode 100644 index 00000000000..2264c8b90bc Binary files /dev/null and b/doc/content/DeMorgan1836-induction.jpeg differ diff --git a/doc/en/Authoring/index.md b/doc/en/Authoring/index.md index 1c75be2f87b..4aff6eb6b9b 100644 --- a/doc/en/Authoring/index.md +++ b/doc/en/Authoring/index.md @@ -49,7 +49,7 @@ The authoring documentation also covers topics on: #### [Mathematics topics](../Topics/index.md) -* [Proof](../Topics/Proof.md) +* [Proof](../Proof/index.md) * [Curve sketching](../Topics/Curve_sketching.md). #### Other diff --git a/doc/en/Developer/Development_history.md b/doc/en/Developer/Development_history.md index 1557e638851..add0ee39bc2 100644 --- a/doc/en/Developer/Development_history.md +++ b/doc/en/Developer/Development_history.md @@ -218,7 +218,7 @@ New features in v4.3: * Add in input option 'nounits'. * Add in input option 'compact' to input "Show the validation" parameter. * Add in a [basic question use report](../Authoring/Reporting.md) page, linked from the question testing page. -* Add in house styles to help typeset [proof](../Topics/Proof.md). +* Add in house styles to help typeset [proof](../Proof/Proof_styles.md). * Add cache to help reduce parsing overhead. diff --git a/doc/en/Proof/Proof_CAS_library.md b/doc/en/Proof/Proof_CAS_library.md new file mode 100644 index 00000000000..b4ff36165c8 --- /dev/null +++ b/doc/en/Proof/Proof_CAS_library.md @@ -0,0 +1,79 @@ +# CAS libraries for representing text-based proofs + +STACK provides libraries to represent and manage lines of a text-based proof in a tree structure. This page is reference documentation for these CAS libraries. For examples of how to use these see the topics page on using [Parson's problems](../Topics/Parsons.md). + +## Proof construction functions, and their manipulation + +Proofs are represented as "proof construction functions" with arguments. For example, an if and only if proof would be represented as `proof_iff(A,B)`, where both `A` and `B` are sub-proofs. Proof construction functions don't typically modify their arguments, but some proof construction functions have simplification properties. For example `proof_iff(A,B)` is normally equivalent to `proof_iff(B,A)`. + +STACK supports the following types of proof construction functions. The following are general proofs + +* `proof()`: general, unspecified proof. +* `proof_c()`: general proof, with commutative arguments. Typically each argument will be another proof block type. + +The following represent particular types of proof. + +* `proof_iff()`. This proof must have two arguments. These arguments are assumed to commute. +* `proof_cases()`. In proof by exhaustive cases, the first element is fixed, and it typically used to describe/justify the cases. E.g. "\(n\) is either odd or even". The remaining cases commute, and typically each argument will be another proof block type. +* `proof_ind()`. A proof by induction must have four arguments. The 1st and 4th are fixed position (defining the statement and conclusion), whereas the 2nd and 3rd commute (for the base case, and induction step). + +It is relatively easy to add in new functions to represent a particular type of proof. + +The variable `proof_types` is a list holding the names of all proof construction functions. E.g. this list is used by the predicate `proofp` to decide if the operator represents a proof. + +`proof_validatep` is a validation function: the argument must be a tree built from proof construction functions as operators (with the right number of arguments in some cases) or atoms which are integer or strings. + +`proof_flatten` turns a proof-tree into a list of steps. E.g. `proof_flatten(proof_iff(A,B))` is just `[A.B]`. This function is useful when the teacher creates a structured tree, but a student's proof is a flat list. + +`proof_normal` returns a normalised proof-tree, e.g. sorting arguments of commutative proof construction functions. Note, this function does not change keys, so will not match proofs using integer keys with a proof using string keys. + +`proof_alternatives` returns a list of all proof trees which are alternatives to its argument. E.g. `proof_iff(A,B))` has two alternatives: `[proof_iff(A,B),proof_iff(B,A)]`. This function recurses over all sub-proofs to generate any tree which might be equivalent. Note, if a student is correct then using `proof_normal` will match their answer with the teacher's. However, when trying to identify possible mistakes we need to find the tree which is _closest_ to one of the acceptable proof trees. Use this function sparingly, as it is computationally expensive on deeply nested trees with many commutative elements. + +## Using a `proof_steps` list + +The design relies on a list of `proof_steps`. Consider this example:"\(n\) is odd if and only if \(n^2\) is odd."; + +```` +proof_steps: [ + ["assodd", "Assume that \\(n\\) is odd."], + ["defn_odd", "Then there exists an \\(m\\in\\mathbb{Z}\\) such that \\(n=2m+1\\)."], + ["alg_odd", "\\[ n^2 = (2m+1)^2 = 2(2m^2+2m)+1.\\]"], + ["def_M_odd", "Define \\(M=2m^2+2m\\in\\mathbb{Z}\\) then \\(n^2=2M+1\\)."], + ["conc_odd", "Hence \\(n^2\\) is odd."], + + ["contrapos", "We reformulate \"\\(n^2\\) is odd \\(\\Rightarrow \\) \\(n\\) is odd \" as the contrapositive."], + ["assnotodd", "Assume that \\(n\\) is not odd."], + ["even", "Then \\(n\\) is even, and so there exists an \\(m\\in\\mathbb{Z}\\) such that \\(n=2m\\)."], + ["alg_even", "\\[ n^2 = (2m)^2 = 2(2m^2).\\]"], + ["def_M_even", "Define \\(M=2m^2\\in\\mathbb{Z}\\) then \\(n^2=2M\\)."], + ["conc_even", "Hence \\(n^2\\) is even."] +]; + +P1:proof_iff(proof("assodd","defn_odd","alg_odd","def_M_odd","conc_odd"),proof("contrapos","assnotodd","even","alg_even","def_M_even","conc_even")); +```` + +Note that the variable `proof_steps` is a _list_ of lists: `[ ["key", "step", ("comment")], ...` + +1. The first element is a `key`. This is a string, or integer, which is used to refer to the `step`. +2. The `key` can be an integer position in the proof_steps, a string `key`, or a string. + * Using integers: `proof_iff(proof(1,2,3,4,5),proof(6,7,8,9,10,11))`; + * Using keys: `proof_iff(proof("assodd","defn_odd","alg_odd","def_M_odd","conc_odd"),proof("contrapos","assnotodd","even","alg_even","def_M_even","conc_even"))` +3. The `proof_steps` list can contain an optional string argument `"comment"`. This string can be used to store justification, explaination and narative. Only some display functions use this argument, when it exists. +4. Note that the backslash must be protected when defining these strings. + +`proof_steps_sub(ex, proof_steps)` takes a proof built from numbered indexes, and translate this into string keys. In the above example, it might be easier to author a proof as `proof(1,2,3,4,5)` rather than type in `proof("assodd","defn_odd","alg_odd","def_M_odd","conc_odd")`. The whole design could be built on numbered keys (and these are valid), but string keys are easier to remember and easier to interpret when looking at students' attempts. Also, string keys can be inserted later without the need to re-number existing numerical keys. + +`proof_getstep(key, proof_steps)` looks for the key `key` in the `proof_steps` list. They key can be either a numerical, or string key. If found then the function returns the full string, otherwise the key is returned without an error. One advantage of this approach is that the teacher can define abreviated, alternative proofs using some of the strings in `proof_steps`, e.g. using the above example the following omits all the detail in the sub-proofs, focusing on the structure and hypothesis/conclusion of each block. + +```` +P2:proof_iff(proof("assodd","\\(\\cdots\\)","conc_odd"), proof("contrapos","assnotodd","\\(\\cdots\\)","conc_even")); +```` + +When displayed, the keys `"\\(\\cdots\\)"` do not occur in `proof_steps`, so are returned unchanged. (Alteratively a teacher could add keys to `proof_steps` for such situations.) + +## Displaying whole proof and proof-step pairs + +To display a whole proof whole proof usinf proof-step lists use `proof_display(P1, proof_steps)`. E.g. add `{@proof_display(P1, proof_steps)@}` to the appropriate castext. This will (1) replace all keys in the proof `P1` with the corrsponding strings in `proof_steps` (if they exist) and (2) display the structure using the nested `
Let P(n) be the statement [...]
+Consider the base case [...]
+Assume P(n) is true [...] hence P(n+1) is true.
+It follows that P(n) is true for all n by the principle of mathematical induction.
+[[input:ans1]] [[validation:ans1]]
```` - Notice the function `stackjson_stringify` turns the variable `proof_steps` into a JSON object. Notice in this example the teacher's proof is nested. This can be seen if we use numerical keys, not string keys and define diff --git a/doc/en/Topics/index.md b/doc/en/Topics/index.md index d5656d02cc8..08156dae79d 100644 --- a/doc/en/Topics/index.md +++ b/doc/en/Topics/index.md @@ -10,7 +10,7 @@ Current topics include: * [Discrete mathematics](Discrete_mathematics.md) * String similarity via the [Levenshtein distance](Levenshtein_distance.md) * [Linear algebra](Linear_algebra.md) -* [Mathematical Proof](Proof.md) +* [Mathematical Proof](../Proof/index.md) * [Propositional Logic](Propositional_Logic.md) * [Scientific Units](Units.md) * [Vector Calculus](Vector_Calculus.md) diff --git a/doc/meta_en.json b/doc/meta_en.json index 8867ae66c8e..d3ad4ad0463 100644 --- a/doc/meta_en.json +++ b/doc/meta_en.json @@ -688,6 +688,25 @@ ] }, { + "Proof":[ + { + "file":"index.md", + "title":"Proof - STACK Documentation", + "description":"General discussion of assessment of proof with STACK." + }, + { + "file":"Proof_CAS_library.md", + "title":"CAS libraries for representing text-based proofs", + "description":"Reference documentation on the libraries in STACK for representing text-based proofs." + }, + { + "file":"Proof_styles.md", + "title":"CSS Styles for displaying proof", + "description":"Reference documentation on the CSS styles in STACK for representing text-based proofs." + } + ] + }, + { "Installation":[ { "file":"index.md", diff --git a/stack/maxima/contrib/prooflib.mac b/stack/maxima/contrib/prooflib.mac index e306aaeb3a8..8ed20b55674 100644 --- a/stack/maxima/contrib/prooflib.mac +++ b/stack/maxima/contrib/prooflib.mac @@ -25,11 +25,12 @@ /* An example of how to use these functions. stack_include("https://raw.githubusercontent.com/maths/moodle-qtype_stack/proof-builder/stack/maxima/contrib/prooflib.mac"); -stack_include("https://raw.githubusercontent.com/maths/moodle-qtype_stack/proof-builder/stack/maxima/proofsamples/odd-squaredodd.mac"); +stack_include("https://raw.githubusercontent.com/maths/moodle-qtype_stack/proof-builder/stack/maxima/contrib/proofsamples/odd-squaredodd.mac"); -(For local testing: stack_include("contribl://prooflib.mac");) +stack_include("contribl://prooflib.mac"); +stack_include("contribl://proofsamples/odd-squaredodd.mac"); -tap:ev(proof_disp_replacesteps(proof_ans, proof_steps), map(lambda([ex], ex=dispproof), proof_types)); +tap:proof_display(proof_ans, proof_steps); {@thm@} {@tap@} @@ -49,6 +50,7 @@ tap:ev(proof_disp_replacesteps(proof_ans, proof_steps), map(lambda([ex], ex=disp /******************************************************************/ /* General proof functions */ +/* Please update Proof/Proof_CAS_library.md with new types. */ /* Note, "proof" is assumed to come first in this list, as we use "rest" below for other types. */ proof_types:[proof, proof_c, proof_iff, proof_cases, proof_ind]; @@ -167,6 +169,7 @@ proof_ensure_list(ex):= if listp(ex) then return(ex) else return([ex]); s_test_case(proof_alternatives(proof(A,B,C,D)), [proof(A,B,C,D)]); s_test_case(proof_alternatives(proof_c(A,B)), [proof_c(A,B),proof_c(B,A)]); +s_test_case(proof_alternatives(proof_iff(A,B)), [proof_iff(A,B),proof_iff(B,A)]); s_test_case(proof_alternatives(proof_ind(A,B,C,D)), [proof_ind(A,B,C,D),proof_ind(A,C,B,D)]); /* @@ -209,7 +212,10 @@ proof_getstep(k, pf) := block([keylist], if integerp(k) then return(second(pf[k])), keylist:sublist(pf, lambda([ex], is(first(ex)=k))), if not(emptyp(keylist)) then return(second(first(keylist))), - return(sconcat("Error: could not identify step: ", k)) + /* If the string is not in the pf list, then just return it unchanged. + Teachers can use this to adapt proofs which use some of the steps. + */ + k ); /* @@ -222,7 +228,8 @@ proof_disp_replacesteps(ex, proof_steps) := block( ); /** - * Take a proof of numbered indexes, and translate this into string keys. + * Take a proof "ex" and a list "pf" of [key, step] pairs and translate this into a proof tree with + * the keys replaced by corresponding strings. */ proof_steps_sub(ex, pf):= block( if integerp(ex) then return(first(proof_steps[ex])), @@ -235,19 +242,33 @@ proof_steps_sub(ex, pf):= block( proof_inline_maths(st) := ssubst("\\)", "\\]", ssubst("\\(", "\\[", st)); s_test_case(proof_inline_maths("\\[ 3 = 2^{\\frac{p}{q}}\\]"), "\\( 3 = 2^{\\frac{p}{q}}\\)"); + +/** + * Take a proof, and any proof steps and display them using proof CSS. + */ +proof_display(proof_ans, proof_steps) := ev(proof_disp_replacesteps(proof_ans, proof_steps), map(lambda([ex], ex=dispproof), proof_types)); + /* Make use of the existing styles. See https://docs.stack-assessment.org/en/Topics/Proof/ */ dispproof([ex]) := block([ex1], apply(sconcat, flatten(append([""], [simplode(ex, " ")], ["
"]))) +); + + /* Use the summary/details HTML tag. dispproof([ex]) := block([ex1], apply(sconcat, flatten(append([""], [simplode(ex, " ")], ["
"]))) -); -*/ diff --git a/stack/maxima/proofsamples/index.md b/stack/maxima/contrib/proofsamples/index.md similarity index 100% rename from stack/maxima/proofsamples/index.md rename to stack/maxima/contrib/proofsamples/index.md diff --git a/stack/maxima/proofsamples/inf-primes.mac b/stack/maxima/contrib/proofsamples/inf-primes.mac similarity index 100% rename from stack/maxima/proofsamples/inf-primes.mac rename to stack/maxima/contrib/proofsamples/inf-primes.mac diff --git a/stack/maxima/proofsamples/log-two-three-irrational.mac b/stack/maxima/contrib/proofsamples/log-two-three-irrational.mac similarity index 100% rename from stack/maxima/proofsamples/log-two-three-irrational.mac rename to stack/maxima/contrib/proofsamples/log-two-three-irrational.mac diff --git a/stack/maxima/proofsamples/odd-squaredodd.mac b/stack/maxima/contrib/proofsamples/odd-squaredodd.mac similarity index 55% rename from stack/maxima/proofsamples/odd-squaredodd.mac rename to stack/maxima/contrib/proofsamples/odd-squaredodd.mac index a5c0672bfcd..be33a40b298 100644 --- a/stack/maxima/proofsamples/odd-squaredodd.mac +++ b/stack/maxima/contrib/proofsamples/odd-squaredodd.mac @@ -3,18 +3,25 @@ thm:"\\(n\\) is odd if and only if \\(n^2\\) is odd."; /****************************************************************/ proof_steps: [ - ["assodd", "Assume that \\(n\\) is odd."], + ["assodd", "Assume that \\(n\\) is odd.", + "This is the hypothesis in the first half of an if any only if proof"], ["defn_odd", "Then there exists an \\(m\\in\\mathbb{Z}\\) such that \\(n=2m+1\\)."], ["alg_odd", "\\[ n^2 = (2m+1)^2 = 2(2m^2+2m)+1.\\]"], - ["def_M_odd", "Define \\(M=2m^2+2m\\in\\mathbb{Z}\\) then \\(n^2=2M+1\\)."], - ["conc_odd", "Hence \\(n^2\\) is odd."], + ["def_M_odd", "Define \\(M=2m^2+2m\\in\\mathbb{Z}\\) then \\(n^2=2M+1\\).", + "Notice we have satisfied the algebraic definition that \\(n^2\\) is an odd number."], + ["conc_odd", "Hence \\(n^2\\) is odd.", + "This is the conclusion in the first half of an if any only if proof"], - ["contrapos", "We reformulate \"\\(n^2\\) is odd \\(\\Rightarrow \\) \\(n\\) is odd \" as the contrapositive."], - ["assnotodd", "Assume that \\(n\\) is not odd."], + ["contrapos", "We reformulate \"\\(n^2\\) is odd \\(\\Rightarrow \\) \\(n\\) is odd \" as the contrapositive.", + "This reformulation enables us to start with \\(n\\) and not start with \\(n^2\\) which is simpler."], + ["assnotodd", "Assume that \\(n\\) is not odd.", + "This is the reformulated hypothesis in the second half of an if any only if proof"], ["even", "Then \\(n\\) is even, and so there exists an \\(m\\in\\mathbb{Z}\\) such that \\(n=2m\\)."], ["alg_even", "\\[ n^2 = (2m)^2 = 2(2m^2).\\]"], ["def_M_even", "Define \\(M=2m^2\\in\\mathbb{Z}\\) then \\(n^2=2M\\)."], - ["conc_even", "Hence \\(n^2\\) is even."] + ["conc_even", "Hence \\(n^2\\) is even.", + "This is the conclusion in the first half of an if any only if proof" + ] ]; /****************************************************************/ diff --git a/stack/maxima/proofsamples/root-two-irrational.mac b/stack/maxima/contrib/proofsamples/root-two-irrational.mac similarity index 100% rename from stack/maxima/proofsamples/root-two-irrational.mac rename to stack/maxima/contrib/proofsamples/root-two-irrational.mac diff --git a/stack/maxima/proofsamples/set-equality.mac b/stack/maxima/contrib/proofsamples/set-equality.mac similarity index 100% rename from stack/maxima/proofsamples/set-equality.mac rename to stack/maxima/contrib/proofsamples/set-equality.mac diff --git a/stack/maxima/proofsamples/sum-odd-int.mac b/stack/maxima/contrib/proofsamples/sum-odd-int.mac similarity index 100% rename from stack/maxima/proofsamples/sum-odd-int.mac rename to stack/maxima/contrib/proofsamples/sum-odd-int.mac