class ABTesting: _algorithms = [ArtsRT(), ArtsRT(0.1, 2, 1.1, 2, 20), ArtsDiffSlow()] @classmethod def get_algorithm_for_id(cls, id): """Returns an algorithm from the_algorithms based on the modulo of the ID of the object and the number of algorithms :param id: An Integer, for which the algorithm should be returned :return: An AlgorithmWrapper containing an ArtsRT object with the parameters specified in WORD_SCHEDULING_ALGORITHM_CONFIG """ idx = cls.__get_algorithm_index_for_id(id) return cls._algorithms[idx] @classmethod def get_algorithm_wrapper_for_id(cls, id): algorithm = cls.get_algorithm_for_id(id) return AlgorithmWrapper(algorithm) @classmethod def split_bookmarks_based_on_algorithm(cls, bookmarks): groups = [] for i in range(0, len(cls._algorithms)): groups.append([]) for i in range(0, len(bookmarks)): group_idx = cls.__get_algorithm_index_for_id(bookmarks[i].id) groups[group_idx].append(bookmarks[i]) return groups @classmethod def __get_algorithm_index_for_id(cls, id): count_algorithms = len(cls._algorithms) return divmod(id, count_algorithms)[1]
def __init__(self, user_id, algorithm=None): """ Create a new algorithm simulation for :param user_id: The user id of a user :param algorithm: The used word scheduling algorithm (not the wrapper) """ self.user_id = user_id self.__create_database() if algorithm is None: algorithm = ArtsRT() self.algo_wrapper = AlgorithmWrapper(algorithm) self.bookmarks = self.__get_bookmarks_for_user(self.user_id)
class BookmarkPriorityUpdater: """ Handles the related tasks for using word scheduling algorithms and also acts as a wrapper that calls the specific algorithm update_bookmark_priority """ algorithm_wrapper = AlgorithmWrapper(ArtsRT()) @classmethod @time_this def update_bookmark_priority(cls, db, user): """ Update all bookmark priorities of one user :param db: The connection to the database :param user: The user object """ try: bookmarks_for_user = user.all_bookmarks_fit_for_study() fit_for_study_count = len(bookmarks_for_user) zeeguu_core.log(f"{fit_for_study_count} bookmarks fit for study") if fit_for_study_count == 0: return # tuple(0=bookmark, 1=exercise) bookmark_exercise_of_user = map(cls._get_exercise_of_bookmark, bookmarks_for_user) b1, b2 = itertools.tee(bookmark_exercise_of_user, 2) max_iterations = max( pair.exercise.id if pair.exercise is not None else 0 for pair in b1) exercises_and_priorities = [ cls._calculate_bookmark_priority(x, max_iterations) for x in b2 ] with db.session.no_autoflush: # might not be needed, but just to be safe for each in exercises_and_priorities: entry = BookmarkPriorityARTS.find_or_create( each.bookmark, each.priority) entry.priority = each.priority db.session.add(entry) # print(entry) db.session.commit() except Exception as e: db.session.rollback() print('Error during updating bookmark priority') print(e) print(traceback.format_exc()) @classmethod def _update_exercise_source_stats(cls): """Update the ExerciseStats for the ArtsDiffSlow and ArtsDiffFast algorithm to provide normalization information between the different exercise sources""" exercise_sources = list(ExerciseSource.query.all()) for source in exercise_sources: exercises = Exercise.query.filter_by(source_id=source.id).filter( Exercise.solving_speed <= 30000).all() reaction_times = list(map(lambda x: x.solving_speed, exercises)) if len(reaction_times) == 0: # magic values for the reaction, if no data exists reaction_times = [5000, 6000] print('This exercise source has no data yet. ID: ' + str(source.id)) mean, sd = NormalDistribution.calc_normal_distribution( reaction_times) if sd is None: sd = 1000 exercise_stats = ExerciseStats.find_or_create( db.session, ExerciseStats(source, mean, sd)) db.session.merge(exercise_stats) db.session.commit() @classmethod def _calculate_bookmark_priority(cls, x, max_iterations): """Calculate the (new) priority of a bookmark-exercise pair :param x: An instance of the bookmark-exercise information (PriorityInfo) :param max_iterations: The current amount of iterations/learning sessions (in the ArtsRT algorithm known as D) :return: The bookmark-exercise information (PriorityInfo) with a updated priority """ if x.exercise is not None: if x.exercise.solving_speed > 0: x.priority = cls.algorithm_wrapper.calculate( x.exercise, max_iterations) else: # solving speed is -1 for the cases where there was some feedback # from the user (either that it's too easy, or that there's something # wrong with it. we shouldn't schedule the bookmark in this case. # moreover, even if we wanted we can't since there's a log of reaction # time somewhere and it won't work with -1! x.priority = PriorityInfo.NO_PRIORITY else: x.priority = PriorityInfo.MAX_PRIORITY return x @staticmethod def _get_exercise_of_bookmark(bookmark): if 0 < len(bookmark.exercise_log): return PriorityInfo(bookmark=bookmark, exercise=bookmark.exercise_log[-1]) return PriorityInfo(bookmark=bookmark, exercise=None)
words_in_parallel_factor=3, repetition_correct_factor=0, repetition_incorrect_factor=0) # update exercise source stats BookmarkPriorityUpdater._update_exercise_source_stats() # optimize for algorithm for these users users = User.find_all() start = timer() user_ids = [user.id for user in users] results = [] for user_id in user_ids: algorithm = ArtsRT() evaluator = AlgorithmEvaluator(user_id, algorithm, change_limit=1.0) variables_to_set = [['d', getattr(algorithm, 'd'), +5], ['b', getattr(algorithm, 'b'), +10], ['w', getattr(algorithm, 'w'), +10]] result = evaluator.fit_parameters(variables_to_set, optimization_goals) if result is not None: count_bookmarks = len(evaluator.fancy.bookmarks) count_exercises = sum( map(lambda x: len(x.exercise_log), evaluator.fancy.bookmarks)) result = [ user_id, list(map(lambda x: [x[0], x[1]], result)), count_bookmarks, count_exercises ] results.append(result)