| 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): |
| 839d16 | 71 | |
| b5b680 | 72 | if idx1 == idx2: |
| SP | 73 | continue |
| 839d16 | 74 | |
| SP | 75 | if (val1.get_formatted_value()==val2.get_formatted_value()): |
| 76 | return True | |
| b5b680 | 77 | if not val1.is_float() or not val2.is_float(): |
| SP | 78 | if val1.get_formatted_value() == val2.get_formatted_value(): |
| 79 | return True | |
| 80 | else: | |
| 81 | if ( | |
| 82 | abs(val1.get_formatted_value() - val2.get_formatted_value()) | |
| 83 | < self.AnsDiff * val1.get_formatted_value() | |
| 84 | ): | |
| 85 | return True | |
| 08b2a2 | 86 | return False |
| 6c2245 | 87 | |
| SP | 88 | |
| 89 | class ProblemSource: | |
| 7c2a8f | 90 | def __init__(self, parser=None): |
| SP | 91 | self.introduction = None |
| 92 | self.subproblems = None | |
| 7029ac | 93 | self.picture= None |
| 7c2a8f | 94 | self.parsedSolutions = None |
| SP | 95 | self.parsedVariables = None |
| d89217 | 96 | self.variableGenerator = None |
| SP | 97 | self.varDict = {} |
| 7c2a8f | 98 | if parser is not None: |
| 7029ac | 99 | self.introduction, self.picture, self.subproblems, self.parsedVariables, self.parsedSolutions, self.variableGenerator = ( |
| d89217 | 100 | parser.get_parsed_sections() |
| SP | 101 | ) |
| 78d798 | 102 | # self.generateVariables() |
| 7c2a8f | 103 | |
| SP | 104 | def generateVariables(self): |
| 105 | for key in self.parsedVariables: | |
| ca061f | 106 | self.varDict[key] = Variable( |
| SP | 107 | next(self.variableGenerator[key]), self.parsedVariables[key]["type"] |
| 108 | ) | |
| 7c2a8f | 109 | |
| SP | 110 | def generateSolutions(self): |
| 6c2245 | 111 | # a dirty one but it has to be like this ;) |
| SP | 112 | __retsol = [] |
| 113 | ||
| 114 | # define variables | |
| 115 | for __varname, __var in self.varDict.items(): | |
| 116 | exec(__varname + "=" + str(__var.get_formatted_value())) | |
| 117 | for __s in self.parsedSolutions: | |
| 118 | __ps = {} | |
| 119 | __ps["correct"] = [] | |
| 120 | __ps["wrong"] = [] | |
| 121 | for __corr in __s["correct"]: | |
| 122 | for __corrsplit in __corr.split(";"): | |
| b5b680 | 123 | __result = None |
| 6c2245 | 124 | if __corrsplit.find("=") >= 0: |
| 839d16 | 125 | try: |
| SP | 126 | exec(self.substitute_octave(__corrsplit)) |
| 127 | except: | |
| 128 | print("Error while evaluating {}".format(__corrsplit)) | |
| 6c2245 | 129 | else: |
| SP | 130 | __result = eval(self.substitute_octave(__corrsplit)) |
| b5b680 | 131 | if __result is None: |
| SP | 132 | raise Exceptions.NoResult( |
| 133 | "Result cannot be calculated. Be sure to specify last term without = sign!" | |
| 134 | ) | |
| 6c2245 | 135 | __ps["correct"].append(Variable(__result, formatting=__s["type"])) |
| SP | 136 | for __corr in __s["wrong"]: |
| 137 | for __corrsplit in __corr.split(";"): | |
| 993a25 | 138 | __result = None |
| 6c2245 | 139 | if __corrsplit.find("=") >= 0: |
| SP | 140 | exec(self.substitute_octave(__corrsplit)) |
| 141 | else: | |
| 142 | __result = eval(self.substitute_octave(__corrsplit)) | |
| b5b680 | 143 | if __result is None: |
| SP | 144 | raise Exceptions.NoResult( |
| 145 | "Result cannot be calculated. Be sure to specify last term without = sign!" | |
| 146 | ) | |
| 6c2245 | 147 | __ps["wrong"].append(Variable(__result, formatting=__s["type"])) |
| 78d798 | 148 | __ps["glyph"] = __s["glyph"] |
| SP | 149 | __ps["unit"] = __s["unit"] |
| 6c2245 | 150 | __retsol.append(__ps) |
| SP | 151 | return __retsol |
| 7c2a8f | 152 | |
| SP | 153 | def isMultiProblem(self): |
| 154 | if len(self.subproblems) > 0: | |
| 155 | return True | |
| 156 | else: | |
| 157 | return False | |
| ef19a9 | 158 | |
| SP | 159 | def substitute_variables(self, text): |
| ca061f | 160 | for key, var in self.varDict.items(): |
| da1010 | 161 | text = re.sub("\/\*\/" + key + "\/\*\/", var.format_as_tex(dollar=""), text) |
| ef19a9 | 162 | return text |
| SP | 163 | |
| ca061f | 164 | def substitute_octave(self, text): |
| SP | 165 | text = re.sub("\^", "**", text) |
| 166 | text = re.sub(";", "\n", text) | |
| 167 | return text | |
| ef19a9 | 168 | |
| 08b2a2 | 169 | def generateProblem(self): |
| ca061f | 170 | intro = self.substitute_variables(self.introduction) |
| SP | 171 | sp = [] |
| ef19a9 | 172 | for p in self.subproblems: |
| SP | 173 | sp.append(self.substitute_variables(p)) |
| 6c2245 | 174 | sol = self.generateSolutions() |
| 7029ac | 175 | return {"introduction": intro, "subproblems": sp, "picture": self.picture, "solutions": sol} |