7. Number Lines booklet python

The python below requires 3 .tex files:

  1. number_lines_booklet_template.tex

  2. number_lines_booklet_ans_template.tex

  3. number_lines_worksheet_diagram_template.tex

7.1. Python to create a 2 page booklet of number lines worksheets

The python file, number_lines_booklet_maker.py, when run, will ask for these inputs:
Choose the arithmetic process: "Enter 1,2,3,4,5 or 6 for plus,minus_neg,minus,minus_pos,plus_neg,random".
Choose the number of questions from 1 to 100: "Enter the number of questions from 1 to 80"
Choose the file name base: ""Enter the base filename to be added to the prefix nlBk:".
The filename will have β€œ_q” added for the question booklet and β€œ_ans” for the answer booklet.

number_lines_booklet_maker

  1from pathlib import Path
  2import subprocess
  3import time
  4import random
  5import magick_pdf_to_png
  6
  7currfile_dir = Path(__file__).parent
  8tex_template_path = currfile_dir / "number_lines_booklet_template.tex"
  9texans_template_path = currfile_dir / "number_lines_booklet_ans_template.tex"
 10tex_diagram_template_path = currfile_dir / "number_lines_booklet_diagram_template.tex"
 11
 12
 13def convert_to_pdf(tex_path, currfile_dir, aux_path):
 14    """
 15    Converts a TeX file to PDF format using pdfLaTeX.
 16
 17    Args:
 18        tex_path (str): The path to the TeX file.
 19        currfile_dir (str): The path to the directory where the TeX file is located.
 20        aux_path (str): The path to the directory where auxiliary files will be stored.
 21
 22    Returns:
 23        subprocess.CompletedProcess: A subprocess.CompletedProcess object containing information about the completed process.
 24
 25    Raises:
 26        FileNotFoundError: If the TeX file does not exist.
 27        subprocess.CalledProcessError: If pdfLaTeX returns a non-zero exit code.
 28    """
 29    result = subprocess.run(
 30        [
 31            "pdfLaTeX",
 32            tex_path,
 33            "-output-directory",
 34            currfile_dir,
 35            "-aux-directory",
 36            aux_path,
 37        ],
 38        stdout=subprocess.PIPE,
 39    )
 40
 41
 42def getprocess_dict(num):
 43    if num is None or num == 6:
 44        num = random.randint(1, 5)
 45    match num:
 46        case 1:
 47            return go_right_dict("plus")
 48        case 2:
 49            return go_right_dict("minus_neg")
 50        case 3:
 51            return go_left_dict("minus")
 52        case 4:
 53            return go_left_dict("minus_pos")
 54        case 5:
 55            return go_left_dict("plus_neg")
 56
 57
 58def val_in_list_exclude_zero(low, high):
 59    vals = list(range(low, high + 1))
 60    if 0 in vals:
 61        vals.remove(0)
 62    return random.choice(vals)
 63
 64
 65def go_right_dict(add_style):
 66    # set points
 67    endval = val_in_list_exclude_zero(-7, 9)
 68    startval = val_in_list_exclude_zero(-9, endval - 2)
 69    changevaltxt = endval - startval
 70    kv = dict()
 71    kv["endval"] = f"{endval}"
 72    kv["startval"] = f"{startval}"
 73    # answers
 74    kv["endvaltxt"] = f"{endval}"
 75    kv["startvaltxt"] = f"{startval}"
 76    if add_style == "plus":
 77        kv["changevaltxt"] = r"+" + str(changevaltxt)
 78    else:  # minus_neg
 79        kv["changevaltxt"] = r"-(" + str(-changevaltxt) + ")"
 80    kv["equtxt"] = f"{startval}{kv['changevaltxt']} = {endval}"
 81    # _question
 82    kv["endvaltxt_q"] = f"\qgap"
 83    kv["startvaltxt_q"] = f"\qgap"
 84    if add_style == "plus":
 85        kv["changevaltxt_q"] = f"+\qgap"
 86        kv["equtxt_q"] = f"\qgap + \qgap = \qgap"
 87    else:  # minus_neg
 88        kv["changevaltxt_q"] = r"-(\qgap)"
 89        kv["equtxt_q"] = r"\qgap - (\qgap) = \qgap"
 90    return kv
 91
 92
 93def go_left_dict(sub_style):
 94    # set points
 95    endval = val_in_list_exclude_zero(-9, 7)
 96    startval = val_in_list_exclude_zero(endval + 2, 9)
 97    changevaltxt = endval - startval
 98    kv = dict()
 99    kv["endval"] = f"{endval}"
100    kv["startval"] = f"{startval}"
101    # answers
102    kv["endvaltxt"] = f"{endval}"
103    kv["startvaltxt"] = f"{startval}"
104    if sub_style == "minus":
105        kv["changevaltxt"] = r"-" + str(-changevaltxt)
106    elif sub_style == "minus_pos":
107        kv["changevaltxt"] = r"-(+" + str(-changevaltxt) + ")"
108    else:  # plus_neg
109        kv["changevaltxt"] = r"+(" + str(changevaltxt) + ")"
110    kv["equtxt"] = f"{startval}{kv['changevaltxt']} = {endval}"
111    # _question
112    kv["endvaltxt_q"] = f"\qgap"
113    kv["startvaltxt_q"] = f"\qgap"
114    if sub_style == "minus":
115        kv["changevaltxt_q"] = r"-\qgap"
116        kv["equtxt_q"] = r"\qgap - \qgap = \qgap"
117    elif sub_style == "minus_pos":
118        kv["changevaltxt_q"] = r"-(+\qgap)"
119        kv["equtxt_q"] = r"\qgap - (+\qgap) = \qgap"
120    else:  # plus_neg
121        kv["changevaltxt_q"] = r"+(\qgap)"
122        kv["equtxt_q"] = r"\qgap + (\qgap) = \qgap"
123    return kv
124
125
126kv_keys_ans = [
127    "startval",
128    "endval",
129    "startvaltxt",
130    "endvaltxt",
131    "changevaltxt",
132    "equtxt",
133]
134kv_keys_q = [
135    "startval",
136    "endval",
137    "startvaltxt_q",
138    "endvaltxt_q",
139    "changevaltxt_q",
140    "equtxt_q",
141]
142
143
144def trimkey(key):
145    key = key.replace("_q", "")
146    return key
147
148
149def make1_diagram(tex_diagram_template_txt, num):
150    posttext = r"\vspace{-2pt}"
151    tex_diagram_template_txt_ans = tex_diagram_template_txt
152    kv = getprocess_dict(num)
153    for key, value in kv.items():
154        # show answers
155        if key in kv_keys_ans:
156            tex_diagram_template_txt_ans = tex_diagram_template_txt_ans.replace(
157                "<<" + key + ">>", value
158            )
159    for key, value in kv.items():
160        # don't show answers, use ___ for gaps
161        if key in kv_keys_q:
162            tex_diagram_template_txt = tex_diagram_template_txt.replace(
163                "<<" + trimkey(key) + ">>", value
164            )
165    return tex_diagram_template_txt + posttext, tex_diagram_template_txt_ans + posttext
166
167
168def main():
169    num = input(
170        "Enter 1,2,3,4,5 or 6 for plus,minus_neg,minus,minus_pos,plus_neg,random \n"
171    )
172    if num.strip().isdigit():
173        num = int(num)
174        if not num in [1, 2, 3, 4, 5, 6]:
175            num = 6  # random by default
176    else:
177        num = 6  # random by default
178    #
179    numq = input("Enter the number of questions from 1 to 80 \n")
180    if numq.strip().isdigit():
181        numq = int(numq)
182        if not numq in range(1,81):
183            numq = 16  # random by default
184    else:
185        numq = 16  # random by default
186    #
187    filename = input("Enter the base filename to be added to the prefix nlBk_: \n")
188    if not filename:
189        filename = "1"  # "nlBk_1_q and nlBk_1_ans as default file"
190    # set names of files that are made
191    tex_output_path = currfile_dir / f"nlBk_{filename}_q.tex"
192    pdf_path = currfile_dir / f"nlBk_{filename}_q.pdf"
193    aux_path = currfile_dir / "temp"
194    # answers
195    tex_output_path_ans = currfile_dir / f"nlBk_{filename}_ans.tex"
196    pdf_path_ans = currfile_dir / f"nlBk_{filename}_ans.pdf"
197
198    # Read in the LaTeX template file
199    with open(tex_template_path, "r") as infile:
200        tex_template_txt = infile.read()
201    # Read in the LaTeX template file for answers
202    with open(texans_template_path, "r") as infile:
203        tex_template_txt_ans = infile.read()
204    # Read in the LaTeX diagram template file
205    with open(tex_diagram_template_path, "r") as infile:
206        tex_diagram_template_txt = infile.read()
207
208    # <<diagrams>>
209    # generate diagrams text and text for answers
210    diagrams_text = ""
211    diagrams_text_ans = ""
212    # add the headtext
213    headtext = r"\pagebreak ~ \newline ~ \newline"
214    rmax = numq + 1
215    for i in range(1, rmax):
216        img_tex, img_tex_ans = make1_diagram(tex_diagram_template_txt, num)
217        if i > 8 and i % 8 == 1:
218            diagrams_text += headtext
219            diagrams_text_ans += headtext
220        diagrams_text += img_tex
221        diagrams_text_ans += img_tex_ans
222
223    # Replace the <<diagrams>> placeholder in the LaTeX template with the generated diagrams
224    tex_template_txt = tex_template_txt.replace("<<diagrams>>", diagrams_text)
225    tex_template_txt_ans = tex_template_txt_ans.replace(
226        "<<diagrams>>", diagrams_text_ans
227    )
228    # Write the question tex to an output file
229    with open(tex_output_path, "w") as outfile:
230        outfile.write(tex_template_txt)
231
232    # Write the answer tex to an output file
233    with open(tex_output_path_ans, "w") as outfile:
234        outfile.write(tex_template_txt_ans)
235
236    # Wait for the file to be created
237    time.sleep(2)
238    # Convert the LaTeX files to PDFs
239    convert_to_pdf(tex_output_path, currfile_dir, aux_path)
240    convert_to_pdf(tex_output_path_ans, currfile_dir, aux_path)
241
242    # # don't convert to images
243    # time.sleep(1)
244    # # Convert the PDFs to PNGs
245    # magick_pdf_to_png.convert_pdf_to_png(pdf_path,png_path)
246    # magick_pdf_to_png.convert_pdf_to_png(pdf_path_ans,png_path_ans)
247
248
249if __name__ == "__main__":
250    print("starting")
251    main()
252    print("finished")