3. Number Lines - automatedο
3.1. A number line diagram with answersο
3.2. Splitting the LaTeX and modifying it to be built by pythonο
3.2.1. document templateο
1\documentclass[preview,12pt]{standalone}
2\usepackage{tikz}
3\usetikzlibrary{positioning}
4\usetikzlibrary {arrows.meta}
5\usetikzlibrary{bending}
6\usepackage[a4paper, portrait, margin=1cm]{geometry}
7
8\def\jumpheight{10}
9\def\qgap{\rule[-1pt]{1.0em}{.25pt}}
10
11\begin{document}
12 <<diagram>>
13\end{document}
<<diagram>>
is placeholder text for the text that python will use to add the LaTeX for the number line diagram.3.2.2. diagram templateο
1\begin{tikzpicture}[scale=0.75]
2 % axis, arrow style to-to
3 \draw[{To[scale=1.3]}-{To[scale=1.3]}, line width=1pt] (-10.4, 0) -- (10.4, 0);
4 % tick marks
5 \foreach \x in {-10,-9,...,10}
6 \draw[shift={(\x,0)},color=black, line width=1pt] (0pt,-14pt) -- (0pt,0pt);
7 % numbers along each axis
8 \foreach \x in {-10,0,10}
9 \draw[shift={(\x,-0.8)},color=black] node[font=\large] {$\x$};
10 \draw[shift={(<<startval>>,-0.8)},color=black] node[font=\large,text height=12pt] {$<<startvaltxt>>$};
11 \draw[shift={(<<endval>>,-0.8)},color=black] node[font=\large,text height=12pt] {$<<endvaltxt>>$};
12 % dots
13 \filldraw[black] (<<startval>>,0) circle (4pt) node[above,yshift=-2pt] (a) {};
14 \filldraw[black] (<<endval>>,0) circle (4pt) node[above,yshift=-2pt] (b) {};
15 % arrow
16 \draw[-{To[scale=1.3, bend]},line width=1pt, color=black] (a.north) .. controls +(north:\jumpheight mm) and +(north:\jumpheight mm) .. node[above=2pt,font=\large,text height=10pt] {$<<changevaltxt>>$} (b.north); % for addition
17 % equation at right end
18 \node [font=\large, minimum width=30mm] at (11.0,1.2) {$<<equtxt>>$};
19\end{tikzpicture}
\draw[shift={(<<startval>>,-0.8)},color=black] node[font=\large,text height=12pt] {$<<startvaltxt>>$};
has a placeholder <<startval>>
which is replaced by python.<< >>
.3.2.3. Python to create a number line diagramο
It opens three template files for creating a tex file with a number line diagram and reads their contents.
It calls a function called
make1_diagram
that takes the diagram template text as an argument and returns two strings: one for the question and one for the answer. The function calls thebtf.get_1step_process_dict
function to get a dictionary with some keys and values for creating the diagram. The function then replaces the placeholders in the template text with the corresponding values from the dictionary, depending on whether it is for the answer or question version. The function uses a loop to iterate over the keys and values of the dictionary and perform the replacement.It replaces a placeholder in the LaTeX template texts with the diagram texts and writes them to two output files: one for the question and one for the answer.
It waits for two seconds and then calls another function called
convert_to_pdf
that takes the output tex files and converts them to PDF files using thepdfLaTeX
command.It waits for another second and then calls another function called
convert_pdf_to_png
from themagick_pdf_to_png
module that takes the output PDF files and converts them to PNG files using themagick
command.It prints βstartingβ before calling the main function and βfinishedβ after it.
The code imports the modules
pathlib
,subprocess
,time
,random
, and the file in the currectn directorymagick_pdf_to_png
.The code defines the paths for the template and output files using the
Path
class frompathlib
.The code defines the function called
btf.get_1step_process_dict
that returns a dictionary with some keys and values for creating the number line diagram. The function uses a random integer to choose one of five possible cases, each corresponding to a different style of adding or subtracting numbers on the number line. The function calls another helper function calledval_in_list_exclude_zero
that returns a random integer from a range excluding zero.The code defines the function called
go_right_dict
that takes an argument calledadd_style
and returns a dictionary with some keys and values for creating a number line diagram where the arrow goes to the right. The function sets the end value, start value, and change value based on the argument and uses some formatting to display them as text.The code defines a function called
go_left_dict
that takes an argument calledsub_style
and returns a dictionary with some keys and values for creating a number line diagram where the arrow goes to the left. The function sets the end value, start value, and change value based on the argument and uses some formatting to display them as text.The code defines two lists called
kv_keys_ans
andkv_keys_q
that contain the keys of the dictionary that are relevant for the answer and question versions of the diagram, respectively.The code defines the function called
trimkey
that takes a key and removes the_q
suffix if it has one.
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_template.tex"
9texans_template_path = currfile_dir / "number_lines_template.tex"
10tex_diagram_template_path = currfile_dir / "number_lines_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 tex_diagram_template_txt_ans = tex_diagram_template_txt
151 kv = getprocess_dict(num)
152 for key, value in kv.items():
153 # show answers
154 if key in kv_keys_ans:
155 tex_diagram_template_txt_ans = tex_diagram_template_txt_ans.replace(
156 "<<" + key + ">>", value
157 )
158 for key, value in kv.items():
159 # don't show answers, use ___ for gaps
160 if key in kv_keys_q:
161 tex_diagram_template_txt = tex_diagram_template_txt.replace(
162 "<<" + trimkey(key) + ">>", value
163 )
164 return tex_diagram_template_txt, tex_diagram_template_txt_ans
165
166
167def main():
168 num = input("Enter 1,2,3,4,5 or 6 for plus,minus_neg,minus,minus_pos,plus_neg,random \n")
169 if num.strip().isdigit():
170 num = int(num)
171 if not num in [1, 2, 3, 4, 5, 6]:
172 num = 6 # random by default
173 else:
174 num = 6 # random by default
175 filename = input("Enter the base filename to be added to the prefix nl_: \n")
176 if not filename:
177 filename = "1" # "nl_1_q and nl_1_ans as default file"
178 # set names of files that are made
179 # questions
180 # questions
181 tex_output_path = currfile_dir / f"nl_{filename}_q.tex"
182 pdf_path = currfile_dir / f"nl_{filename}_q.pdf"
183 png_path = currfile_dir / f"nl_{filename}_q.png"
184 aux_path = currfile_dir / "temp"
185 # answers
186 tex_output_path_ans = currfile_dir / f"nl_{filename}_ans.tex"
187 pdf_path_ans = currfile_dir / f"nl_{filename}_ans.pdf"
188 png_path_ans = currfile_dir / f"nl_{filename}_ans.png"
189 # Read in the LaTeX template file
190 with open(tex_template_path, "r") as infile:
191 tex_template_txt = infile.read()
192 # Read in the LaTeX template file for answers
193 with open(texans_template_path, "r") as infile:
194 tex_template_txt_ans = infile.read()
195 # Read in the LaTeX diagram template file
196 with open(tex_diagram_template_path, "r") as infile:
197 tex_diagram_template_txt = infile.read()
198
199 # Generate the <<diagram>> replacement tex
200 diagram_text, diagram_text_ans = make1_diagram(tex_diagram_template_txt, num)
201 # Replace the <<diagram>> placeholder in the LaTeX template
202 tex_template_txt = tex_template_txt.replace("<<diagram>>", diagram_text)
203 tex_template_txt_ans = tex_template_txt_ans.replace("<<diagram>>", diagram_text_ans)
204 # Write the question diagram tex to an output file
205 with open(tex_output_path, "w") as outfile:
206 outfile.write(tex_template_txt)
207 # Write the answer diagram tex to an output file
208 with open(tex_output_path_ans, "w") as outfile:
209 outfile.write(tex_template_txt_ans)
210
211 # # Wait for the file to be created
212 time.sleep(2)
213
214 # convert to pdf
215 convert_to_pdf(tex_output_path_ans, currfile_dir, aux_path)
216 convert_to_pdf(tex_output_path, currfile_dir, aux_path)
217
218 time.sleep(1)
219 # convert to png
220 magick_pdf_to_png.convert_pdf_to_png(pdf_path, png_path)
221 magick_pdf_to_png.convert_pdf_to_png(pdf_path_ans, png_path_ans)
222
223
224if __name__ == "__main__":
225 print("starting")
226 main()
227 print("finished")