| commit | author | age | ||
| 6c2245 | 1 | from .Variable import Variable |
| ef19a9 | 2 | import re |
| 993a25 | 3 | from . import Exceptions |
| ef19a9 | 4 | from math import * |
| 08b2a2 | 5 | from random import shuffle |
| d89217 | 6 | |
| b5b680 | 7 | |
| 7c2a8f | 8 | class Problem: |
| b5b680 | 9 | def __init__( |
| SP | 10 | self, |
| 11 | source=None, | |
| 12 | shuffle=True, | |
| 13 | MaxShuffleAttempts=40, | |
| 14 | MaxRegenerateAttempts=100, | |
| 15 | AnsDiff=0.05, | |
| 16 | ): | |
| 78d798 | 17 | self.source = source |
| b5b680 | 18 | self.MaxShuffleAttempts = MaxShuffleAttempts |
| SP | 19 | self.MaxRegenerateAttempts = MaxRegenerateAttempts |
| 08b2a2 | 20 | self.AnsDiff = AnsDiff |
| 6c2245 | 21 | self.source.generateVariables() |
| SP | 22 | self.source.generateSolutions() |
| 78d798 | 23 | self.problem = self.source.generateProblem() |
| 08b2a2 | 24 | if shuffle: |
| b5b680 | 25 | for i in range(0, self.MaxRegenerateAttempts): |
| SP | 26 | try: |
| 27 | self.problem["solutions"] = self.shuffleAnswers( | |
| 28 | self.problem["solutions"] | |
| 29 | ) | |
| 30 | except Exceptions.AnswerProximityError: | |
| 7224af | 31 | self.regenerate() |
| b5b680 | 32 | continue |
| SP | 33 | else: |
| 34 | break | |
| 35 | if i == self.MaxRegenerateAttempts - 1: | |
| 36 | raise Exceptions.AnswerProximityError( | |
| 37 | "Could not shuffle answers and get answers that are not too close together :: " | |
| 38 | + self.problem["introduction"] | |
| 39 | ) | |
| 6c2245 | 40 | |
| 7224af | 41 | def regenerate(self): |
| SP | 42 | self.source.generateVariables() |
| 43 | self.source.generateSolutions() | |
| 44 | self.problem = self.source.generateProblem() | |
| 45 | ||
| b5b680 | 46 | def shuffleAnswers(self, solList): |
| SP | 47 | shuffList = [] |
| 08b2a2 | 48 | for cont in solList: |
| SP | 49 | for i in range(0, self.MaxShuffleAttempts): |
| b5b680 | 50 | wrong = cont["wrong"] |
| 08b2a2 | 51 | shuffle(wrong) |
| b5b680 | 52 | ans = [ |
| SP | 53 | *zip(cont["correct"], list("1" * len(cont["correct"]))), |
| 54 | *zip(wrong, list("0" * len(wrong))), | |
| 55 | ] | |
| 56 | ans = ans[0:4] | |
| 57 | if self.checkAnsProximity(ans) == False: | |
| 08b2a2 | 58 | break |
| b5b680 | 59 | if i == self.MaxShuffleAttempts - 1: |
| SP | 60 | raise Exceptions.AnswerProximityError( |
| 61 | "Could not shuffle answers and get answers that are not too close together" | |
| 62 | ) | |
| 08b2a2 | 63 | shuffle(ans) |
| b5b680 | 64 | cont["shuffled"] = ans |
| 08b2a2 | 65 | shuffList.append(cont) |
| SP | 66 | return solList |
| 67 | ||
| 68 | def checkAnsProximity(self, ans): | |
| b5b680 | 69 | for idx1, (val1, cor) in enumerate(ans): |
| SP | 70 | for idx2, (val2, cor) in enumerate(ans): |
| 71 | if idx1 == idx2: | |
| 72 | continue | |
| 73 | if not val1.is_float() or not val2.is_float(): | |
| 74 | if val1.get_formatted_value() == val2.get_formatted_value(): | |
| 75 | return True | |
| 76 | else: | |
| 77 | if ( | |
| 78 | abs(val1.get_formatted_value() - val2.get_formatted_value()) | |
| 79 | < self.AnsDiff * val1.get_formatted_value() | |
| 80 | ): | |
| 81 | return True | |
| 08b2a2 | 82 | return False |
| 6c2245 | 83 | |
| SP | 84 | |
| 85 | class ProblemSource: | |
| 7c2a8f | 86 | def __init__(self, parser=None): |
| SP | 87 | self.introduction = None |
| 88 | self.subproblems = None | |
| 7029ac | 89 | self.picture= None |
| 7c2a8f | 90 | self.parsedSolutions = None |
| SP | 91 | self.parsedVariables = None |
| d89217 | 92 | self.variableGenerator = None |
| SP | 93 | self.varDict = {} |
| 7c2a8f | 94 | if parser is not None: |
| 7029ac | 95 | self.introduction, self.picture, self.subproblems, self.parsedVariables, self.parsedSolutions, self.variableGenerator = ( |
| d89217 | 96 | parser.get_parsed_sections() |
| SP | 97 | ) |
| 78d798 | 98 | # self.generateVariables() |
| 7c2a8f | 99 | |
| SP | 100 | def generateVariables(self): |
| 101 | for key in self.parsedVariables: | |
| ca061f | 102 | self.varDict[key] = Variable( |
| SP | 103 | next(self.variableGenerator[key]), self.parsedVariables[key]["type"] |
| 104 | ) | |
| 7c2a8f | 105 | |
| SP | 106 | def generateSolutions(self): |
| 6c2245 | 107 | # a dirty one but it has to be like this ;) |
| SP | 108 | __retsol = [] |
| 109 | ||
| 110 | # define variables | |
| 111 | for __varname, __var in self.varDict.items(): | |
| 112 | exec(__varname + "=" + str(__var.get_formatted_value())) | |
| 113 | for __s in self.parsedSolutions: | |
| 114 | __ps = {} | |
| 115 | __ps["correct"] = [] | |
| 116 | __ps["wrong"] = [] | |
| 117 | for __corr in __s["correct"]: | |
| 118 | for __corrsplit in __corr.split(";"): | |
| b5b680 | 119 | __result = None |
| 6c2245 | 120 | if __corrsplit.find("=") >= 0: |
| SP | 121 | exec(self.substitute_octave(__corrsplit)) |
| 122 | else: | |
| 123 | __result = eval(self.substitute_octave(__corrsplit)) | |
| b5b680 | 124 | if __result is None: |
| SP | 125 | raise Exceptions.NoResult( |
| 126 | "Result cannot be calculated. Be sure to specify last term without = sign!" | |
| 127 | ) | |
| 6c2245 | 128 | __ps["correct"].append(Variable(__result, formatting=__s["type"])) |
| SP | 129 | for __corr in __s["wrong"]: |
| 130 | for __corrsplit in __corr.split(";"): | |
| 993a25 | 131 | __result = None |
| 6c2245 | 132 | if __corrsplit.find("=") >= 0: |
| SP | 133 | exec(self.substitute_octave(__corrsplit)) |
| 134 | else: | |
| 135 | __result = eval(self.substitute_octave(__corrsplit)) | |
| b5b680 | 136 | if __result is None: |
| SP | 137 | raise Exceptions.NoResult( |
| 138 | "Result cannot be calculated. Be sure to specify last term without = sign!" | |
| 139 | ) | |
| 6c2245 | 140 | __ps["wrong"].append(Variable(__result, formatting=__s["type"])) |
| 78d798 | 141 | __ps["glyph"] = __s["glyph"] |
| SP | 142 | __ps["unit"] = __s["unit"] |
| 6c2245 | 143 | __retsol.append(__ps) |
| SP | 144 | return __retsol |
| 7c2a8f | 145 | |
| SP | 146 | def isMultiProblem(self): |
| 147 | if len(self.subproblems) > 0: | |
| 148 | return True | |
| 149 | else: | |
| 150 | return False | |
| ef19a9 | 151 | |
| SP | 152 | def substitute_variables(self, text): |
| ca061f | 153 | for key, var in self.varDict.items(): |
| da1010 | 154 | text = re.sub("\/\*\/" + key + "\/\*\/", var.format_as_tex(dollar=""), text) |
| ef19a9 | 155 | return text |
| SP | 156 | |
| ca061f | 157 | def substitute_octave(self, text): |
| SP | 158 | text = re.sub("\^", "**", text) |
| 159 | text = re.sub(";", "\n", text) | |
| 160 | return text | |
| ef19a9 | 161 | |
| 08b2a2 | 162 | def generateProblem(self): |
| ca061f | 163 | intro = self.substitute_variables(self.introduction) |
| SP | 164 | sp = [] |
| ef19a9 | 165 | for p in self.subproblems: |
| SP | 166 | sp.append(self.substitute_variables(p)) |
| 6c2245 | 167 | sol = self.generateSolutions() |
| 7029ac | 168 | return {"introduction": intro, "subproblems": sp, "picture": self.picture, "solutions": sol} |