4. Backtracking 2-step booklet python

The python file to make 2-step backtracking booklets is below.
The 2 custom python modules required are:
The python file, backtracking_2step_booklet_maker.py, when run, will ask for these inputs:
Choose the type of diagrams:
"Enter 1, 2, 3, 4, 5 or 6 for standard, 1 row build expression, 1 row inverse operations, 1 row from expression, solve from expression, blank "
Choose the first arithmetic process: "Enter 1, 2, 3, 4 or 5 for +, -, X, /, random for the 1st process".
Choose the second arithmetic process: "Enter 1, 2, 3, 4 or 5 for +, -, X, /, random for the 2nd process"
Choose the number of questions from 1 to 100: "Enter the number of questions from 1 to 100"
Choose the file name base: "Enter the base filename to be added to the prefix bt2Bk_:". The filename will have “_q” added for the question booklet and “_ans” for the answer booklet.
The prefix will be “bt2” for standard; “bt2_build” for 1 row build expression; or “bt2_invop” for 1 row inverse operations.
The prefix will be “bt2_fromexp” for 1 row from expression; “bt2_solvefromexp” for solve from expression; or “bt2_blank” for blank.
The filename will have “_q” added for the question diagram and “_ans” for the answer diagram.

4.1. Sample 2-step backtracking booklet

multiplication_addition_q

multiplication_addition_ans

random_q

random_ans


4.2. 2-step backtracking: 1 row; building the expression

random_q

random_ans


4.3. 2-step backtracking: 1 row; inverse operations

random_q

random_ans


4.4. 2-step backtracking: 1 row; from the expression

random_q

random_ans


4.5. 2-step backtracking: solve from the expression

random_q

random_ans


4.6. 2-step backtracking: python

  1# 10 to page for standard and solve
  2# 20 to page for build
  3# 15 to page for inv and from exp
  4
  5from pathlib import Path
  6import subprocess
  7import time
  8import random
  9import magick_pdf_to_png
 10import backtracking_functions as btf
 11
 12currfile_dir = Path(__file__).parent
 13tex_template_path = currfile_dir / "backtrack_2step_booklet_template.tex"
 14texans_template_path = currfile_dir / "backtrack_2step_booklet_ans_template.tex"
 15
 16tex_diagram_standard_template_path = (
 17    currfile_dir / "backtrack_2step_booklet_diagram_template.tex"
 18)
 19tex_diagram_buildexp_template_path = (
 20    currfile_dir / "backtrack_2step_booklet_diagram_template_1buildexp.tex"
 21)
 22tex_diagram_invop_template_path = (
 23    currfile_dir / "backtrack_2step_booklet_diagram_template_1invop.tex"
 24)
 25tex_diagram_fromexp_template_path = (
 26    currfile_dir / "backtrack_2step_booklet_diagram_template_1invop.tex"
 27)
 28tex_diagram_solvefromexp_template_path = (
 29    currfile_dir / "backtrack_2step_booklet_diagram_template.tex"
 30)
 31tex_diagram_blank_template_path = (
 32    currfile_dir / "backtrack_2step_booklet_diagram_template_blank.tex"
 33)
 34
 35
 36def convert_to_pdf(tex_path, currfile_dir, aux_path):
 37    """
 38    Converts a TeX file to PDF format using pdfLaTeX.
 39
 40    Args:
 41        tex_path (str): The path to the TeX file.
 42        currfile_dir (str): The path to the directory where the TeX file is located.
 43        aux_path (str): The path to the directory where auxiliary files will be stored.
 44
 45    Returns:
 46        subprocess.CompletedProcess: A subprocess.CompletedProcess object containing information about the completed process.
 47
 48    Raises:
 49        FileNotFoundError: If the TeX file does not exist.
 50        subprocess.CalledProcessError: If pdfLaTeX returns a non-zero exit code.
 51    """
 52    result = subprocess.run(
 53        [
 54            "pdfLaTeX",
 55            tex_path,
 56            "-output-directory",
 57            currfile_dir,
 58            "-aux-directory",
 59            aux_path,
 60        ],
 61        stdout=subprocess.PIPE,
 62    )
 63
 64
 65# % end modify values for backtracking
 66# tex_keys = ['stepAB','stepABrev','stepBC', 'stepBCrev', boxA','boxB', 'boxC', 'boxCrev, 'boxBrev', 'boxArev' ]
 67tex_keys_q = ["stepAB", "stepBC", "boxA", "boxCrev"]
 68# used by from expression
 69tex_keys_q_fromexp = ["boxC"]
 70# used by from expression
 71tex_keys_q_solvefromexp = ["boxC", "boxCrev"]
 72
 73
 74def num_for_type(bt_type):
 75    """
 76    1 standard, 10 per page
 77    2 1 row build expression, 20 to page
 78    3 1 row inverse operations, 15 per page
 79    4 1 row from expression, 15 per page
 80    5 solve from expression, 10 per page
 81    6 blank, 10 per page
 82    """
 83    '''Returns the number of pages for a given bt_type.
 84
 85    Args:
 86        bt_type (int): An integer between 1 and 6.
 87
 88    Returns:
 89        int: The number of pages for the given bt_type.
 90    '''
 91    match bt_type:
 92        case 1:
 93            num = 20
 94        case 2:
 95            num = 40
 96        case 3:
 97            num = 30
 98        case 4:
 99            num = 30
100        case 5:
101            num = 20
102        case 6:
103            num = 20
104    return num
105
106
107def make1_diagram(tex_diagram_template_txt, num1, num2, tex_keys_q):
108    """
109    This function takes in a LaTeX template for a diagram, two numbers `num1` and `num2`, and a list of keys `tex_keys_q`.
110    
111    Args:
112    tex_diagram_template_txt (str): A string representing the LaTeX template for the diagram.
113    num1 (int): An integer representing the first number.
114    num2 (int): An integer representing the second number.
115    tex_keys_q (list): A list of strings representing the keys for the diagram.
116    
117    Returns:
118    tuple: A tuple containing two strings. The first string is the LaTeX template with the keys replaced by their corresponding values from `num1` and `num2`. The second string is the same LaTeX template with all keys replaced by their corresponding values from `num1` and `num2`.
119    """
120
121    tex_diagram_template_txt_ans = tex_diagram_template_txt
122    posttext = r"\vspace{-2pt}"
123    kv = btf.get_2step_process_dict(num1, num2)
124
125    for key, value in kv.items():
126        tex_diagram_template_txt_ans = tex_diagram_template_txt_ans.replace(
127            "<<" + key + ">>", value
128        )
129
130    for key, value in kv.items():
131        if key in tex_keys_q:
132            tex_diagram_template_txt = tex_diagram_template_txt.replace(
133                "<<" + key + ">>", value
134            )
135        else:
136            tex_diagram_template_txt = tex_diagram_template_txt.replace(
137                "<<" + key + ">>", ""
138            )
139
140    # return tex_diagram_template_txt
141    return tex_diagram_template_txt + posttext, tex_diagram_template_txt_ans + posttext
142
143
144def main():
145    input_str = (
146        "Enter 1, 2, 3, 4, 5 or 6 for standard, 1 row build expression, "
147        + "1 row inverse operations, 1 row from expression, solve from expression, blank  \n"
148    )
149    bt_type = input(input_str)
150    if bt_type.strip().isdigit():
151        bt_type = int(bt_type)
152        if not bt_type in [1, 2, 3, 4, 5, 6]:
153            bt_type = 1  # standard by default
154    else:
155        bt_type = 1  # standard by default
156    #
157    if bt_type in [1, 2, 3, 4, 5]:
158        num1 = input("Enter 1, 2, 3, 4 or 5 for +, -, X, /, random for 1st process \n")
159        if num1.strip().isdigit():
160            num1 = int(num1)
161            if num1 not in [1, 2, 3, 4, 5]:
162                num1 = 5  # random by default
163        else:
164            num1 = 5  # random by default
165        #
166        num2 = input("Enter 1, 2, 3, 4 or 5 for +, -, X, /, random for 2nd process \n")
167        if num2.strip().isdigit():
168            num2 = int(num2)
169            if num2 not in [1, 2, 3, 4, 5]:
170                num2 = 5  # random by default
171        else:
172            num2 = 5  # random by default
173        #
174    else:
175        # use as placeholders for sake of call below; will end up returning + + dictionary but not used.
176        num1, num2 = None, None
177
178    numq = input(
179        "Enter the number of questions from 1 to 100; default to 2 pages worth \n"
180    )
181    if numq.strip().isdigit():
182        numq = int(numq)
183        if numq not in range(1, 101):
184            numq = num_for_type(bt_type)
185    else:
186        numq = num_for_type(bt_type)
187
188    match bt_type:
189        case 1:
190            tex_diagram_template_path = tex_diagram_standard_template_path
191            fileprefix = "bt2Bk"
192            q_parts_to_fill = tex_keys_q
193        case 2:
194            tex_diagram_template_path = tex_diagram_buildexp_template_path
195            fileprefix = "bt2Bk_build"
196            q_parts_to_fill = tex_keys_q
197        case 3:
198            tex_diagram_template_path = tex_diagram_invop_template_path
199            fileprefix = "bt2Bk_invop"
200            q_parts_to_fill = tex_keys_q
201        case 4:
202            tex_diagram_template_path = tex_diagram_fromexp_template_path
203            fileprefix = "bt2Bk_fromexp"
204            q_parts_to_fill = tex_keys_q_fromexp
205        case 5:
206            tex_diagram_template_path = tex_diagram_solvefromexp_template_path
207            fileprefix = "bt2Bk_solvefromexp"
208            q_parts_to_fill = tex_keys_q_solvefromexp
209        case 6:
210            tex_diagram_template_path = tex_diagram_blank_template_path
211            fileprefix = "bt2Bk_blank"
212            q_parts_to_fill = tex_keys_q
213
214    #
215    filename = input(
216        f"Enter the base filename to be added to the prefix {fileprefix}_: \n"
217    )
218    if not filename:
219        filename = "1"  # "bt2Bk_1_q and bt2Bk_1_ans as default file"
220    #
221    # set names of files that are made
222    # questions
223    tex_output_path = currfile_dir / f"{fileprefix}_{filename}_q.tex"
224    aux_path = currfile_dir / "temp"
225    # answers
226    tex_output_path_ans = currfile_dir / f"{fileprefix}_{filename}_ans.tex"
227
228    # Read in the LaTeX template file
229    with open(tex_template_path, "r") as infile:
230        tex_template_txt = infile.read()
231    # Read in the LaTeX template file for answers
232    with open(texans_template_path, "r") as infile:
233        tex_template_txt_ans = infile.read()
234    # Read in the LaTeX diagram template file
235    with open(tex_diagram_template_path, "r") as infile:
236        tex_diagram_template_txt = infile.read()
237
238    # <<cols>>
239    # generate column text and column text for answers
240    col1_text = ""
241    col1_text_ans = ""
242    rmax = numq + 1
243    for _ in range(1, rmax):
244        img_tex, img_tex_ans = make1_diagram(
245            tex_diagram_template_txt, num1, num2, q_parts_to_fill
246        )
247        col1_text += img_tex
248        col1_text_ans += img_tex_ans
249
250    # Replace the <<cols>> placeholder in the LaTeX template with the generated diagrams
251    tex_template_txt = tex_template_txt.replace("<<cols>>", col1_text)
252    tex_template_txt_ans = tex_template_txt_ans.replace("<<cols>>", col1_text_ans)
253
254    # Write the question tex to an output file
255    with open(tex_output_path, "w") as outfile:
256        outfile.write(tex_template_txt)
257
258    # Write the answer tex to an output file
259    if bt_type in [1, 2, 3, 4, 5]:
260        with open(tex_output_path_ans, "w") as outfile:
261            outfile.write(tex_template_txt_ans)
262
263    # Wait for the file to be created
264    time.sleep(1)
265    # Convert the LaTeX files to PDFs
266    convert_to_pdf(tex_output_path, currfile_dir, aux_path)
267    if bt_type in [1, 2, 3, 4, 5]:
268        convert_to_pdf(tex_output_path_ans, currfile_dir, aux_path)
269
270
271if __name__ == "__main__":
272    print("starting")
273    main()
274    print("finished")