branch: externals/org-mathsheet
commit 29648b4824c8a60e4cef779921c5562957d319dc
Author: Ian Martins <ia...@jhu.edu>
Commit: Ian Martins <ia...@jhu.edu>

    Documented, prefer / for fractions
---
 example.org   |  78 +++++++++---------
 mathsheet.org | 252 +++++++++++++++++++++++++++++++++++-----------------------
 2 files changed, 193 insertions(+), 137 deletions(-)

diff --git a/example.org b/example.org
index 19ea901393..5aae6f8a69 100644
--- a/example.org
+++ b/example.org
@@ -1,14 +1,6 @@
 * add and subtract
 
 #+name: add-sub-1
-| weight | order | template              | descr                               
      |
-|--------+-------+-----------------------+-------------------------------------------|
-|      3 |     1 | [1..10] + [0..10]     | simple                              
      |
-|      2 |     2 | [1..10] + [8..12]     | second number bigger                
      |
-|      2 |     2 | [1..12] + [0..10]     | first number bigger                 
      |
-|      1 |     3 | [a=2..10] - [1..$a]   | small number subtraction, positive 
result |
-
-#+name: add-sub-2
 | weight | order | template                             | descr                
                   |
 
|--------+-------+--------------------------------------+-----------------------------------------|
 |      3 |     1 | [1..10] + [0..10]                    | simple               
                   |
@@ -19,21 +11,33 @@
 |      0 |     0 | [$a*[1..5]] / [a=1..10]              | division             
                   |
 |      5 |     5 | [a=1..10] + [b=0..10] - [0..($a+$b)] | three with 
subtraction, positive result |
 
-#+BEGIN: problem-set :templates "add-sub-2" :count 12 :instruction "Compute 
the answer"
-| problem   | answer |
-|-----------+--------|
-| 2 + 6     |      8 |
-| 6 + 4     |     10 |
-| 9 + 5     |     14 |
-| 4 + 9     |     13 |
-| 6 - 4     |      2 |
-| 6 + 12    |     18 |
-| 5 + 3 + 1 |      9 |
-| 4 + 1 - 3 |      2 |
-| 2 + 7 - 5 |      4 |
-| 1 + 7 - 5 |      3 |
-| 9 + 6 - 7 |      8 |
-| 2 + 0 - 0 |      2 |
+#+BEGIN: problem-set :templates "add-sub-1" :count 24 :prob-cols 3 
:instruction "Compute the answer"
+| problem    | answer |
+|------------+--------|
+| 5 + 6      |     11 |
+| 1 + 5      |      6 |
+| 4 + 7      |     11 |
+| 7 + 1      |      8 |
+| 5 + 4      |      9 |
+| 9 + 1      |     10 |
+| 5 - 4      |      1 |
+| 3 + 13     |     16 |
+| 7 + 8      |     15 |
+| 9 + 12     |     21 |
+| 4 + 9      |     13 |
+| 8 - 5      |      3 |
+| 8 + 5 + 4  |     17 |
+| 6 + 6 + 3  |     15 |
+| 7 + 9 - 1  |     15 |
+| 3 + 2 - 0  |      5 |
+| 8 + 7 - 4  |     11 |
+| 5 + 4 - 7  |      2 |
+| 2 + 9 - 6  |      5 |
+| 1 + 3 - 0  |      4 |
+| 9 + 5 - 3  |     11 |
+| 7 + 8 - 12 |      3 |
+| 8 + 4 - 7  |      5 |
+| 8 + 2 - 8  |      2 |
 #+END:
 
 * algebra
@@ -41,20 +45,20 @@
 | weight | order | template                              | descr               
  |
 
|--------+-------+---------------------------------------+-----------------------|
 |      3 |     1 | x / ([2..4] + [a=0..5]) = [$a..10]    | simple              
  |
-|      3 |     2 | [$a*[2..10]] / x = [a=1,2,4]          | simple              
  |
+|      2 |     2 | [$a*[2..10]] / x = [a=1,2,4]          | simple              
  |
+|      2 |     3 | x/[-5..-1,1..5] + [1..10] = [-10..10] | double range 
sequence |
+|      1 |     3 | x = x/[a=2..6] + [round([1..20]/$a)]  | var twice           
  |
 |      1 |     3 | x^2 = sqrt([16 - $a] + [a=1..5])      | sqrt                
  |
-|      3 |     4 | [-5..-1,1..5] x + [0..10] = [-10..10] | double range 
sequence |
-|      3 |     4 | x = 2/x + sin([1..10])                | trig, two vars      
  |
 
-#+BEGIN: problem-set :templates "algebra-1" :count 8 :instruction "Solve for x"
-| problem            | answer            |
-|--------------------+-------------------|
-| x / (2 + 3) = 9    | x = 45            |
-| x / (3 + 3) = 9    | x = 54            |
-| 8 / x = 2          | x = 4             |
-| 36 / x = 4         | x = 9             |
-| x^2 = sqrt(12 + 4) | x = 2             |
-| -5 x + 8 = 0       | x = 8:5           |
-| -2 x + 3 = 0       | x = 3:2           |
-| x = 2/x + sin(2)   | x = 1.43177096141 |
+#+BEGIN: problem-set :templates "algebra-1" :count 8 :prob-cols 2 :instruction 
"Solve for x"
+| problem            | answer  |
+|--------------------+---------|
+| x / (3 + 0) = 4    | x = 12  |
+| x / (2 + 2) = 5    | x = 20  |
+| 6 / x = 2          | x = 3   |
+| 7 / x = 1          | x = 7   |
+| x = x/3 + 3        | x = 9/2 |
+| x/-2 + 1 = 7       | x = -12 |
+| x^2 = sqrt(15 + 1) | x = 2   |
+| x/-5 + 6 = 6       | x = 0   |
 #+END:
diff --git a/mathsheet.org b/mathsheet.org
index 73279900a5..12634096c8 100644
--- a/mathsheet.org
+++ b/mathsheet.org
@@ -11,10 +11,15 @@ someone else. It could be useful for a teacher, tutor, 
homeschool
 parent, or any parent.
 ** Examples
 Here are some example worksheets generated by this tool:
-1. arithmatic
-2. algebra
-** Parts
-There are two main components involved in generating a worksheet:
+1. [[file:add-sub-1.pdf][arithmatic]]
+2. [[file:algebra-1.pdf][algebra]]
+
+They were generated using [[file:example.org][this configuration]].
+** Requirements
+[[https://www.gnu.org/software/texinfo/manual/texinfo/html_node/Format-with-texi2dvi-or-texi2pdf.html][texi2pdf]]
 is required to generate the PDF worksheet. Without it you can
+still generate the table of problems and solutions.
+** Usage
+There are two main components involved in defining a worksheet:
 1. the problem templates
 2. the problem-set block
 *** Problem Templates
@@ -41,7 +46,7 @@ In additon to expressions where the answer is a number, 
templates can
 be equations where the solution is found by solving for the
 variable. For example, consider this template:
 #+begin_example
-[1..5] x + [0..10] = [-10..10]
+[1..5] x + 3 = [-10..10]
 #+end_example
 This can produce the following problems:
 #+begin_example
@@ -55,6 +60,7 @@ These are the field rules:
 - [1,3,5] :: choose randomly from 1, 3 or 5
 - [-3..-1,1..3] :: choose a random number from -3 to -1 or 1 to 3
 - [10/(2-1)] :: evaluate the expression
+- [round(sin(0.3))] :: expressions can use math functions
 - [a=...] :: assign the variable a to the number chosen for this field
 - [-2..$a] :: any number from -2 to the value assigned to ~a~ in another
   field
@@ -63,6 +69,10 @@ These are the field rules:
 The ability to keep track of the random number chosen in one field and
 use it to influence another allows the template to be written to avoid
 answers that are negative or don't divide evenly.
+
+These math functions are allowed: sqrt, sin, cos, tan, asin, acos,
+atan, floor, ceil, round. Find more details about each of these
+functions in the emacs calc manual.
 **** Template Examples
 Here are a few more examples:
 
@@ -71,33 +81,47 @@ Division problem that divides evenly
 [$a*[1..5]] / [a=1..10]
 #+end_example
 
-Addition and subtraction, still with a positive result
+Addition and subtraction, but ensure a positive result
 #+begin_example
 [a=1..10] + [b=0..10] - [0..($a+$b)]
 #+end_example
 
-*** The Problem Template Table
+Division and ensure we don't divide by zero
+#+begin_example
+[-10..10] / [-5..-1,1..5]
+#+end_example
 
-You may want to have more than one type of problem on a worksheet, so
+*** The Problem Template Table
+**** Overview
+In order to make it possible to have more than one problem template on
+a worksheet, each worksheet is configured with a set of templates in a
+templates table. For example
 
-Each table defines a worksheet. Each time the worksheet is generated
-the problems are re-randomized.
+#+name: first-sheet
+| weight | order | template            | descr                  |
+|--------+-------+---------------------+------------------------|
+|      3 |     1 | [1..10] + [1..20]   | addition               |
+|      1 |     2 | [a=1..10] - [0..$a] | subtraction above zero |
 
 The table contains the following columns:
-- weight :: the relative number of this type of problem to include on
-  the worksheet.
-- order :: problems are ordered on the sheet in ascending order. two
-  problems with the same order will be intermingled.
+- weight :: The relative number of this type of problem to include on
+  the worksheet. A weight of zero means the template will not be
+  used. For ~first-sheet~ three out of four of the worksheet problems
+  will be addition.
+- order :: Troblems are ordered on the sheet in ascending order. Two
+  problems with the same order will be intermingled. For ~first-sheet~
+  all of the addition problems will come first.
 - template :: this is the template used to generate problems of this
-  type. Templates are described in more detail below.
-- descr :: just your notes, not used in worksheet generation.
+  type.
+- descr :: This column is just for your notes. It is not used in
+  worksheet generation.
 
+Also notice that the table is assigned a name. That name will be used
+to refer to it later.
 **** Examples
+Here is another example template table.
 
-We label the table so that we can refer to it from the dynamic block
-that generates the worksheet. Only the first three columns are used.
-
-#+name: firstset
+#+name: second-sheet
 | weight | order | template                      | descr                       
 |
 
|--------+-------+-------------------------------+------------------------------|
 |      3 |     1 | [1..10] + [0..10]             | simple                      
 |
@@ -108,7 +132,28 @@ that generates the worksheet. Only the first three columns 
are used.
 |      0 |     0 | [$a*[1..5]] / [a=1..10]       | division                    
 |
 *** Problem-Set Block
 **** Overview
-**** Examples
+The second thing needed to generate a mathsheet is an 
[[https://orgmode.org/manual/Dynamic-Blocks.html][org dynamic
+block]]. Here is an example:
+
+#+BEGIN: problem-set :templates "first-set" :count 20 :instruction "Compute 
the solution"
+#+END:
+
+The block name must be ~problem-set~ and it must specify the following 
parameters
+- ~:templates~ :: The name of the templates table to use
+- ~:count~ :: the total number of problems to put on the sheet
+- ~:prob-cols~ :: the number of columns in which to lay out the problems
+- ~:instruction~ :: a brief instruction that will be included at the top
+  of the sheet to guide the student
+
+@@html:<kbd>@@C-c@@html:</kbd>@@ @@html:<kbd>@@C-c@@html:</kbd>@@ on
+the block ~BEGIN~ line or ~END~ line will trigger mathsheet to generate a
+new set of problems. The new problems and answers will be written to a
+table in the body of the dynamic block, and you will have the option
+(via a yes or no prompt in the mini bar) to write those problems to a
+PDF. On "yes", mathsheet will write a PDF to a file named by the
+template table name. If an existing file exists it will be
+overwritten. On "no", nothing will be written.
+
 * Code walkthrough
 ** Problem generation
 *** Header
@@ -300,7 +345,9 @@ be ambiguous:
          (sequence (list (or range value) (* "," space (or range value)))
                    `(vals -- (seq-random-elt vals)))
          (range value ".." value
-                `(min max -- (+ (random (- max min)) min)))
+                `(min max -- (if (>= min max)
+                                 (error "Range bounds must be increasing")
+                               (+ (random (- max min)) min))))
          (value (or (substring (opt "-") (+ digit)) var-rhs parenthetical)
                 `(v -- (if (stringp v) (string-to-number v) v)))
          (parenthetical "(" (or expression value) ")")
@@ -334,7 +381,7 @@ test with
 #+end_src
 
 #+RESULTS:
-: 8
+: 3
 
 *** Replace field
 
@@ -461,21 +508,19 @@ other examples
 
 *** Generate problem set from templates
 
-1. load table
-2. determine how many of each
-   1. sort by weight, low to high
-   2. for each row
-      1. calculate number, round with min 1, but 0->0
-   3. for last entry (highest weight) just take however many are left.
-   4. produce '(order template nil) for each problem
-   5. convert to '(order problem answer)
-3. sort
-4. loop through list, replacing entry with '(problem . solution)
+This reads in the templates, figures out how many of each based on
+weights and the number of problems needed, generates the problem set,
+figures out the answers, then reorders.
+
+The reordering is done because if multiple templates are assigned the
+same ~order~, they should be intermingled, but we add all problems for
+each template sequentially. In order to mix them up we shuffle the
+whole set and then reorder by ~order~.
 
 #+name: generate-problems
 #+begin_src elisp :tangle mathsheet.el
   (defun mathsheet--generate-problems (template-name count)
-    "Generate COUNT problems based on TEMPLATE-NAME
+    "Use templates from TEMPLATE-NAME to generate COUNT problems
 
   Generate problems and answers based on what is defined in the
   given template table. The template table defines problem
@@ -485,12 +530,14 @@ other examples
       (save-excursion
         (goto-char (point-min))
         (search-forward-regexp (org-babel-named-data-regexp-for-name 
template-name) nil t)
+
         ;; read table from buffer, drop header, convert fields to numbers or 
strings
         (setq templates (mapcar
                          (lambda (row) (list (string-to-number (nth 0 row))
                                              (string-to-number (nth 1 row))
                                              (substring-no-properties (nth 2 
row))))
-                         (seq-drop (org-table-to-lisp) 2))))
+                         (seq-drop (org-table-to-lisp) 2)))) ; load the table, 
drop the header
+
       ;; sort by weight (low to high)
       (setq templates (sort templates (lambda (a b) (< (car a) (car b))))
             ;; calc total weight
@@ -498,6 +545,7 @@ other examples
                           (seq-reduce (lambda (total item) (+ total (car 
item)))
                                       templates
                                       0)))
+
       ;; calculate number for each row
       (dotimes (ii (length templates))
         (let* ((item (nth ii templates))
@@ -518,11 +566,15 @@ other examples
                    (alg-vars (alist-get :alg-vars fill-ret))
                    (calc-string (if (not alg-vars)
                                     problem
-                                  (format "solve(%s,[%s])" problem 
(string-join (seq-uniq alg-vars) ","))))
+                                  (format "solve(%s,[%s])"
+                                          problem
+                                          (string-join (seq-uniq alg-vars) 
","))))
                    (solution
                     (replace-regexp-in-string (rx (or "[" ".]" "]"))
                                               ""
-                                              (calc-eval calc-string))))
+                                              (calc-eval `(,calc-string
+                                                           calc-prefer-frac t
+                                                           calc-frac-format 
("/" nil))))))
               (cond
                ((member problem problem-set) ; dedup problems
                 (setq dup-count (1+ dup-count))
@@ -554,14 +606,13 @@ other examples
 ** Update problem-set block
 
 This generates a problem set and writes it to the dynamic block. This
-is triggered by C-c C-c on the dynamic block header.
+is triggered by ~C-c C-c~ on the dynamic block header or footer.
 
-~params~ is a property list of params on the block header line
-I need to extract the values
+~params~ is a property list of params on the block header line.
 
-- :templates :: templates
-- :count :: 10
-- :instruction :: "Solve for x"
+First we generate the problems and answers, then we write them out to
+a table in the dynamic block, finally, if the user wants it, we
+generate a PDF with these problems.
 
 #+begin_src elisp :tangle mathsheet.el
   (defun org-dblock-write:problem-set (params)
@@ -570,8 +621,9 @@ I need to extract the values
   PARAMS is a plist with the properties set on the dynamic block
   header, which includes `:tempates' which is the name of the
   templates table, `:count' which is the number of problems to put
-  on the worksheet, and `:instruction' which is the content of the
-  instruction line at the top of the page"
+  on the worksheet, `:prob-cols' for the number of columns to use
+  for problems, and `:instruction' which is the content of the
+  instruction line at the top of the page."
 
     ;; write the table header
     (insert "| problem | answer |\n")
@@ -599,48 +651,39 @@ I need to extract the values
         (mathsheet--gen-worksheet
          (plist-get params :templates)
          (plist-get params :instruction)
-         problems))))
+         problems
+         (plist-get params :prob-cols)))))
 #+end_src
 
 ** Generate PDF
 *** Lay out page
-this wraps the problems with a tex header and footer.
+This wraps the problems with a tex header and footer.
+
+This template doen't use noweb but it uses noweb syntax (~<<label>>~) to
+mark where mathsheet will insert content. It's not possible actually
+use noweb here since this template must be tangled to mathsheet.el as
+a template.
 
-solution for how to enumerate with circled numbers from 
[[https://latex.org/forum/viewtopic.php?p=40006&sid=d202f756313add2391c3140fbeafe2ff#p40006][here]]
+I found the solution for how to enumerate with circled numbers 
[[https://latex.org/forum/viewtopic.php?p=40006&sid=d202f756313add2391c3140fbeafe2ff#p40006][here]].
 
 #+name: page
 #+begin_src latex :results value silent
-  \documentclass[12pt]{article}
-  \usepackage[top=1in, bottom=0.8in, left=0.8in, right=0.8in]{geometry}
-  \usepackage{fancyhdr}
-  \newsavebox{\myheadbox}% Heading storage box
+  \documentclass[12pt]{exam}
+  \usepackage[top=1in, bottom=0.5in, left=0.8in, right=0.8in]{geometry}
   \usepackage{multicol}
   \usepackage{rotating}
   \usepackage{xcolor}
-  \usepackage{enumitem}
-  \usepackage{tikz}
-  \newcommand*\circled[1]{%
-    \tikz[baseline=(C.base)]\node[draw,circle,inner sep=1.2pt,line 
width=0.2mm,](C) {#1};}
-  \newcommand*\CircledItem{%
-    \stepcounter{enumi}\item[\circled{\theenumi}]}
-
-  \pagestyle{fancy}
-  \lhead{\textmd{\textsf{Name: }}}
-  \rhead{\textmd{\textsf{Date: \today}}}
-  \cfoot{}
 
+  \pagestyle{head}
+  
\header{Name:\enspace\makebox[2.2in]{\hrulefill}}{}{Date:\enspace\makebox[2.2in]{\hrulefill}}
 
   \begin{document}
 
-  \noindent\textbf{<<instruction>>}
-  \vspace{.2cm}
-  \renewcommand{\familydefault}{\ttdefault}
+    \noindent <<instruction>>
 
-    \begin{multicols}{2}
-      \begin{enumerate}[itemsep=0.5cm]
-        <<problems>>
-      \end{enumerate}
-    \end{multicols}
+    \begin{questions}
+      <<problems>>
+    \end{questions}
 
     \vspace*{\fill}
 
@@ -652,18 +695,19 @@ solution for how to enumerate with circled numbers from 
[[https://latex.org/foru
       \begin{minipage}{\linewidth}
         \color{gray}
         \footnotesize
-        \begin{multicols}{4}
-          \begin{enumerate}
-            <<answers>>
-          \end{enumerate}
-        \end{multicols}
+        \begin{questions}
+          <<answers>>
+        \end{questions}
       \end{minipage}
     \end{turn}
 
   \end{document}
 #+end_src
 *** Convert calc to latex
-Convert a calc expression to latex format.
+This converts a calc expression to latex format. The problems and
+answers are generated in standard emacs calc format. If they are to be
+written to a PDF we convert them to latex. emacs calc already knows
+how to convert between formats, so we let it do it.
 
 #+name: convert-to-latex
 #+begin_src elisp :tangle mathsheet.el
@@ -679,19 +723,19 @@ Convert a calc expression to latex format.
       (concat "$" latex-expr-cleaned "$")))
 #+end_src
 *** Write PDF
-
-This writes the generated into a local file and runs ~texi2pdf~ to
-convert it to a pdf. We save it as ~[template-name].tex~ and the final
-worksheet is named ~[template-name].pdf~. Each execution with the same
-template name will overwrite the same file.
-
+This inserts instruction line and generated problems into the page
+template, writes it to a local file, then runs ~texi2pdf~ to build a
+PDF. We save it as ~[template-name].tex~ and the final worksheet is
+named ~[template-name].pdf~. Each execution with the same template name
+will overwrite the same file.
 
 #+begin_src elisp :results silent :tangle mathsheet.el
-  (defun mathsheet--gen-worksheet (file-name instruction problems)
+  (defun mathsheet--gen-worksheet (file-name instruction problems prob-cols)
     "Generate a worksheet with PROBLEMS.
 
   Write a file named FILE-NAME. Include the INSTRUCTION line at the
-  top."
+  top. The problems will be arranged in PROB-COLS columns. The
+  answers will be in 4 columns."
     (with-temp-file (concat file-name ".tex")
       (insert mathsheet--worksheet-template)
 
@@ -700,22 +744,30 @@ template name will overwrite the same file.
       (replace-match "")
       (insert instruction)
 
-      (goto-char (point-min))
-      (search-forward "<<problems>>")
-      (replace-match "")
-      (dolist (row problems)
-        (if (cadddr row)
-            (insert (format"\\CircledItem %s\\vspace{4cm}\n"
-                           (mathsheet--convert-to-latex (car row))))
-          (insert (format"\\CircledItem %s = 
\\rule[-.2\\baselineskip]{2cm}{0.4pt}\n"
-                         (mathsheet--convert-to-latex (car row))))))
+      (let ((answ-cols 5))
+        (goto-char (point-min))
+        (search-forward "<<problems>>")
+        (replace-match "")
+        (dolist (group (seq-partition problems prob-cols))
+          (insert (format "\\begin{multicols}{%d}\n" prob-cols))
+          (dolist (row group)
+            (if (cadddr row)
+                (insert (format"\\question %s\n"
+                               (mathsheet--convert-to-latex (car row))))
+              (insert (format"\\question %s = 
\\rule[-.2\\baselineskip]{2cm}{0.4pt}\n"
+                             (mathsheet--convert-to-latex (car row))))))
+          (insert "\\end{multicols}\n")
+          (insert "\\vspace{\\stretch{1}}\n"))
 
-      (goto-char (point-min))
-      (search-forward "<<answers>>")
-      (replace-match "")
-      (dolist (row problems)
-        (insert (format "\\CircledItem %s\n"
-                        (mathsheet--convert-to-latex (cadr row))))))
+        (goto-char (point-min))
+        (search-forward "<<answers>>")
+        (replace-match "")
+        (dolist (group (seq-partition problems answ-cols))
+          (insert (format "\\begin{multicols}{%s}\n" answ-cols))
+          (dolist (row group)
+            (insert (format "\\question %s\n"
+                            (mathsheet--convert-to-latex (cadr row)))))
+          (insert "\\end{multicols}\n"))))
     (shell-command (concat "texi2pdf " file-name ".tex")
                    (get-buffer-create "*Standard output*")))
 #+end_src

Reply via email to