Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request] Visualize future review distribution #100

Open
cashpw opened this issue Sep 19, 2022 · 1 comment
Open

[Feature request] Visualize future review distribution #100

cashpw opened this issue Sep 19, 2022 · 1 comment

Comments

@cashpw
Copy link
Contributor

cashpw commented Sep 19, 2022

I'm thinking of something like the graph generated by the Anki Simulator add-on.

@l3kn
Copy link
Owner

l3kn commented Jun 19, 2023

This seems very difficult to do right and I'm not sure how useful an inaccurate variant would be.

The simulation loop would need to compute the probabilities of each of the four ratings for each card.
If we had such an algorithm, we could easily use it to predict the recall probability (like ebisu) for each card and use it to schedule reviews.

A simpler version could use the same probabilities for each card, computed from the past reviews.
Below is some code that does this with some hard-coded numbers.

At the moment I'm not diligent enough to review all due cards each day (or even multiple times each day) to judge how my real review counts differ from the predicted ones.

(require 'time-date)

(defun off-flatten (card)
  (unless (plist-get card :suspended)
    (mapcar
     (lambda (pos)
       (list
        :due (plist-get pos :due)
        :ease (plist-get pos :ease)
        :box (plist-get pos :box)
        :interval (plist-get pos :interval)))
     (plist-get card :positions))))

(defun off-insert (pos buckets)
  (let* ((due (plist-get pos :due))
         (bucket (-find (lambda (b) (time-less-p due (plist-get b :end))) buckets)))
    (when bucket
      (push pos (plist-get bucket :positions)))))

(defun off-rate-pos (pos rating)
  (cl-destructuring-bind (ease box interval)
      (org-fc-algo-sm2-next-parameters
       (plist-get pos :ease)
       (plist-get pos :box)
       (plist-get pos :interval)
       rating)
    (plist-put pos :ease ease)
    (plist-put pos :box box)
    (plist-put pos :interval interval)
    (plist-put pos :due
               (encode-time
                (decoded-time-add
                 (decode-time
                  (plist-get pos :due))
                 (make-decoded-time :second
                                    (*
                                     interval
                                     60 60 24)))))))

(defun off-rating ()
  (let ((r (cl-random 1.0)))
    (cond
     ((< r 0.10) 'again)
     ((< r 0.15) 'hard)
     ((< r 0.60) 'good)
     (t 'easy))))

(defun off-step (buckets)
  (let ((first-non-empty
         (-find (lambda (b) (not (null (plist-get b :positions))))
                buckets)))
    (when first-non-empty
      (let* ((poss (plist-get first-non-empty :positions))
             (poss_
              (mapcar (lambda (pos)
                        (off-rate-pos pos (off-rating))) poss)))
        (cl-incf (plist-get first-non-empty :reviews)
                 (length poss))
        (plist-put first-non-empty :positions '())
        (dolist (pos poss_)
          (off-insert pos buckets)))
      buckets)))

(defun off-iterate (buckets)
  (while (off-step buckets))
  buckets)

(defun off-forecast (context)
  (interactive (list (org-fc-select-context)))
  (let* ((now (encode-time (decode-time)))
         (poss
          ;; To simplify the code, we'll assume cards are reviewed on
          ;; exactly when they become due. For cards that are already due,
          ;; we'll rewrite the due date to the current time.
          ;; (mapcan #'off-flatten (org-fc-index context))
          (mapcar
           (lambda (pos)
             (if (time-less-p (plist-get pos :due) now)
                 (plist-put pos :due now)
               pos))
           (mapcan #'off-flatten (org-fc-index context))))
         (buckets (off-buckets 90 0)))
    (dolist (pos poss) (off-insert pos buckets))
    (off-iterate buckets)))

(defun off-show (context)
  (interactive (list (org-fc-select-context)))
  (with-current-buffer (get-buffer-create "*org-fc Forecast*")
    (let* ((org-fc-algorithm 'sm2-v2)
           (buckets (off-forecast context)))

      (goto-char (point-min))
      (erase-buffer)
      (dolist (buk buckets)
        (insert (format "%s - %s\n"
                        (plist-get buk :end)
                        (plist-get buk :reviews))))

      (insert
       (format "Total reviews: %s\n"
               (cl-loop for buk in buckets summing
                        (plist-get buk :reviews))))
      (switch-to-buffer (current-buffer)))))

(defun off-buckets (n &optional new-per-day)
  (setq new-per-day (or new-per-day 0))
  (let* ((cur (decode-time (current-time)))
         (day-end
          (make-decoded-time
           :second 59
           :minute 59
           :hour 23
           :day (nth 3 cur)
           :month (nth 4 cur)
           :year (nth 5 cur))))
    (cl-loop
     for d from 0 to (1- n) collecting
     (let ((time
            (encode-time
             (decoded-time-add day-end (make-decoded-time :day d))))
           (start
            (encode-time
             (decoded-time-add day-end (make-decoded-time :day (1- d))))))
       (list :end time
             :reviews 0
             :positions
             (cl-loop for i from 0 below new-per-day collecting
                      (list
                       :due start
                       :ease 2.5
                       :box 0
                       :interval 0)))))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants