Let's create a short test for the Pythagorean theorem!
The command line is:
mathmaker pythagorean-short-test
After checking everything's fine, mathmaker will use the write() method of the (LaTeX) Machine to write the sheet to the output:
M.write(str(sheet.AVAILABLE[args[0]][0](M)))
sheet.AVAILABLE[args[0]][0](M) is, in this case, PythagoreanTheoremShortTest.
The __str__() method of S_Structure turns the sheet into a string.
The Machine is given in argument in order to call its methods from inside the sheets', exercises' and questions' methods.
The global defined at the beginning of PythagoreanTheoremShortTest will be used:
FONT_SIZE_OFFSET = -1 SHEET_LAYOUT_TYPE = 'short_test' SHEET_LAYOUT_UNIT = "cm" #EXAMPLE OF A SHEET NOT USING ANY LAYOUT # ----------------------- lines_nb col_widths exercises SHEET_LAYOUT = { 'exc' : [ None, 'all' ], 'ans' : [ None, 'all' ] }
If you look into PythagoreanTheoremShortTest.__init__(), you'll see some fields redefined (the title etc.) and also which Exercise is created and added to the object's exercises_list field:
ex1 = exercise.X_RightTriangle(self.machine, x_kind='short_test', x_subkind='pythagorean_theorem_one_of_each') self.exercises_list.append(ex1)
X_RightTriangle defines some globals:
AVAILABLE_X_KIND_VALUES = \ {'short_test' : ['pythagorean_theorem_one_of_each', 'converse_of_pythagorean_theorem', 'contrapositive_of_pythagorean_theorem'] #'preformatted' : [''], #'bypass' : [''] } X_LAYOUT_UNIT = "cm" #[1, 9, 9], (1, 1) # ---------------------- lines_nb col_widths questions X_LAYOUTS = {'default' : { 'exc' : [ None, 'all' ], 'ans' : [ None, 'all' ] } }
Note that the x_kind and x_subkind options written in the PythagoreanTheoremShortTest sheet match the possible kinds of Exercise to create.
Then in X_RightTriangle.__init__(), the section matching our request is:
if self.x_kind == 'short_test': if self.x_subkind == 'pythagorean_theorem_one_of_each': q_subkinds = ['calculate_hypotenuse', 'calculate_one_leg'] if randomly.heads_or_tails(): self.questions_list.append(default_question( self.machine, q_kind='pythagorean_theorem', q_subkind=randomly.pop(q_subkinds), use_pythagorean_triples=True, use_decimals=True, final_unit=randomly.pop(units), number_of_the_question='a', figure_in_the_text='no', rotate_around_barycenter=\ randomly.pop(angles) + random_signs[0]*randomly.integer(0, 20) ) ) self.questions_list.append(default_question( self.machine, q_kind='pythagorean_theorem', q_subkind=randomly.pop(q_subkinds), use_pythagorean_triples=False, round_to=randomly.pop([TENTH, HUNDREDTH]), final_unit=randomly.pop(units), number_of_the_question='b', figure_in_the_text='no', rotate_around_barycenter=\ randomly.pop(angles) + random_signs[1]*randomly.integer(0, 20) ) ) else: etc. (same but reversed order)
It defines randomly the order of the two questions: one will be to calculate one leg and the other one, to calculate the hypotenuse. It is also randomly decided if the result should be rounded or not (in one of the questions, the result must be rounded, in the other one, the result is an exact decimal result).
Each call to the matching question is made with several options defining more precisely which kind of question to create.
What happens in Q_RightTriangle? First, AVAILABLE_Q_KIND_VALUES is defined and you can check that the given argument belongs to the possible kinds:
AVAILABLE_Q_KIND_VALUES = {'pythagorean_theorem' : ['calculate_hypotenuse', 'calculate_one_leg'], 'converse_of_pythagorean_theorem' : ['default'], 'contrapositive_of_pythagorean_theorem': ['default'], 'cosinus' : ['calculate_hypotenuse', 'calculate_one_leg', 'calculate_angle'], 'sinus' : ['calculate_hypotenuse', 'calculate_one_leg', 'calculate_angle'], 'tangente' : ['calculate_hypotenuse', 'calculate_one_leg', 'calculate_angle'], }
In Q_RightTriangle.__init__(), many different variables are set according to the values of the options given in argument (or by default).
Then it will set some variables according to the type of question and according to the values of the options, again:
if self.q_kind == 'pythagorean_theorem': sides_values = [None, None, None] if use_pythagorean_triples: etc. . . .
Once this is done, at the end, it creates the RightTriangle that will be used in the question, and also other useful fields for this case (self.unknown_side and self.known_sides):
. . . self.right_triangle = \ RightTriangle((vertices_names, 'sketch' ), rotate_around_isobarycenter=rotation_option ) self.right_triangle.leg0.set_label(Value(sides_values[0], unit=sides_units[0]) ) self.right_triangle.leg1.set_label(Value(sides_values[1], unit=sides_units[1]) ) self.right_triangle.hypotenuse.set_label(Value(sides_values[2], unit=sides_units[2]) ) for side in self.right_triangle.sides: if side.label.raw_value == "": self.unknown_side = side.clone() else: self.known_sides += [side.clone()]
That's all about the creation of the objects.
Now, let's check how the output gets written.
The call of S_Structure.__str__() in mathmaker:
M.write(str(sheet.AVAILABLE[args[0]][0](M)))
will call many different methods (to write the headers, the title etc. according to the sheet's layout) and in particular will call S_Structure.texts_to_str(), which will call X_Structure.to_str() on each Exercise object it contains (and also make it differently according to layout settings), which itself will call Q_Structure.to_str() on each question it contains, which itself calls Q_RightTriangle.text_to_str() and Q_RightTriangle.answer_to_str(). These two last methods will actually write the texts and the answers, using the core objects created and according to the different settings (that all got stored in the Q_RightTriangle object when it was created).