screamer icon indicating copy to clipboard operation
screamer copied to clipboard

Real variables are not bound

Open swapneils opened this issue 2 years ago • 2 comments

In all my test cases, real variables converge to a range-size of 0 (and have equivalent upper and lower bounds when printed) but still aren't actually bound to a value, leading to a (fail) condition.

Test case (using ratios to avoid some issues with CL float arithmetic that lead to (fail) results):

(let ((v (a-real-betweenv -10 10))
      (a 1/10)
      (b 23/10))
  (assert! (=v a (-v b v)))
  (one-value (solution v
                       (static-ordering #'divide-and-conquer-force))))
;; => (fail)

There's a quickfix where we manually check range-size in restrict-bounds! and setf variable-value if it's 0, but I assume this issue is produced by a noticer not working correctly on reals, and I don't yet understand Screamer's noticer system enough to pinpoint the problem.

Quickfix:

(in-package :screamer)
(defun restrict-bounds! (x lower-bound upper-bound)
  ;; note: X must be a variable.
  ;; note: LOWER-BOUND and UPPER-BOUND must be real constants.
  (when (variable-integer? x)
    (if lower-bound (setf lower-bound (ceiling lower-bound)))
    (if upper-bound (setf upper-bound (floor upper-bound))))
  (if (or (eq (variable-value x) x) (not (variable? (variable-value x))))
      (let ((run? nil))
        (when (and lower-bound
                   (or (not (variable-lower-bound x))
                       (> lower-bound (variable-lower-bound x))))
          (when (and (variable-upper-bound x)
                   (< (variable-upper-bound x) lower-bound))
              (fail))
          (when (or (not (variable-lower-bound x))
                    (not (variable-upper-bound x))
                    (>= (/ (- lower-bound (variable-lower-bound x))
                           (- (variable-upper-bound x) (variable-lower-bound x)))
                        *minimum-shrink-ratio*))
            (local (setf (variable-lower-bound x) lower-bound))
            (setf run? t)))
        (when (and upper-bound
                   (or (not (variable-upper-bound x))
                       (< upper-bound (variable-upper-bound x))))
          (when (and (variable-lower-bound x)
                   (> (variable-lower-bound x) upper-bound))
            (fail))
          (when (or (not (variable-lower-bound x))
                    (not (variable-upper-bound x))
                    (>= (/ (- (variable-upper-bound x) upper-bound)
                           (- (variable-upper-bound x) (variable-lower-bound x)))
                        *minimum-shrink-ratio*))
            (local (setf (variable-upper-bound x) upper-bound))
            (setf run? t)))
        (when run?
          (cond ((eq (variable-enumerated-domain x) t)
                 (if (and (variable-lower-bound x)
                          (variable-upper-bound x)
                          (variable-integer? x)
                          (or (null *maximum-discretization-range*)
                              (<= (- (variable-upper-bound x)
                                     (variable-lower-bound x))
                                  *maximum-discretization-range*)))
                     (set-enumerated-domain!
                      x (integers-between
                         (variable-lower-bound x)
                         (variable-upper-bound x)))))
                ((or (and lower-bound
                          (some #'(lambda (element) (< element lower-bound))
                                (variable-enumerated-domain x)))
                     (and upper-bound
                          (some #'(lambda (element) (> element upper-bound))
                                (variable-enumerated-domain x))))
                 ;; note: Could do less consing if had LOCAL DELETE-IF.
                 ;;       This would also allow checking list only once.
                 (set-enumerated-domain!
                  x (remove-if #'(lambda (element)
                                   (or (and lower-bound (< element lower-bound))
                                       (and upper-bound (> element upper-bound))))
                               (variable-enumerated-domain x)))))


          ;; When the range-size of x is 0, set (variable-value x)
          (let ((range (range-size x)))
            (when (and range
                       (zerop range))
              (setf (variable-value x) (variable-lower-bound x))))


          (run-noticers x)))))

;; Test
(let ((v (a-real-betweenv -10 10))
      (a 1/10)
      (b 23/10))
  (assert! (=v a (-v b v)))
  (one-value (solution v
                       (static-ordering #'divide-and-conquer-force))))
;; => 11/5 (2.2)

swapneils avatar Jun 13 '23 01:06 swapneils

Hi, just letting you know that I'm not actively maintaining Screamer these days. If someone wants to pick it up and run, they should feel free to do so.

nikodemus avatar Oct 24 '23 17:10 nikodemus

@nikodemus Maybe move this into sharplispers and see if someone will take it over?

rpgoldman avatar Oct 24 '23 19:10 rpgoldman