3D Hensel/INT notation working group

For general discussion about Conway's Game of Life.
User avatar
wirehead
Posts: 253
Joined: June 18th, 2022, 2:37 pm
Location: fish: wirehead: command not found
Contact:

Re: 3D Hensel/INT notation working group

Post by wirehead » November 20th, 2023, 4:55 pm

confocaloid wrote:
November 20th, 2023, 4:21 pm
One thing that I noticed is that entering the empty string causes "IndexError: list index out of range". (Empty string would be valid e.g. in a rule without any survival conditions.)
Fixed that, thanks.

Otherwise, does it look like the Scheme expressions line up with the explanation output?
Langton's ant: Can't play the drums, can be taught.

User avatar
confocaloid
Posts: 3066
Joined: February 8th, 2022, 3:15 pm

Re: 3D Hensel/INT notation working group

Post by confocaloid » November 20th, 2023, 5:12 pm

I think the Scheme output is incorrect in this case:

Code: Select all

>>> ?-u-ch1589ym-049y
Scheme: (not (and (test 'u) (not (or (test 'c) (and (test 'h) (or (test '1) (test '5) (test '8) (test '9) (test 'y))) (not (and (test 'm) (or (test '0) (test '4) (test '9) (test 'y)))))))) 
Explanation:
 NOT (   u AND NOT (   c
                    OR h AND ( 1 OR 5 OR 8 OR 9 OR y )
                    OR m AND NOT ( 0 OR 4 OR 9 OR y ) ) )
The part "(not (and (test 'm) (or (test '0) (test '4) (test '9) (test 'y))))" would appear as "OR NOT (m AND (0 OR 4 OR 9 OR y))" in the explanation, while the actual explanation is "OR m AND NOT ( 0 OR 4 OR 9 OR y )".

I think the correct Scheme output would be "(and (test 'm) (not (test '0) (test '4) (test '9) (test 'y)))" for that part.

User avatar
wirehead
Posts: 253
Joined: June 18th, 2022, 2:37 pm
Location: fish: wirehead: command not found
Contact:

Re: 3D Hensel/INT notation working group

Post by wirehead » November 21st, 2023, 1:24 pm

confocaloid wrote:
November 20th, 2023, 5:12 pm
I think the Scheme output is incorrect in this case:

Code: Select all

>>> ?-u-ch1589ym-049y
Scheme: (not (and (test 'u) (not (or (test 'c) (and (test 'h) (or (test '1) (test '5) (test '8) (test '9) (test 'y))) (not (and (test 'm) (or (test '0) (test '4) (test '9) (test 'y)))))))) 
Explanation:
 NOT (   u AND NOT (   c
                    OR h AND ( 1 OR 5 OR 8 OR 9 OR y )
                    OR m AND NOT ( 0 OR 4 OR 9 OR y ) ) )
The part "(not (and (test 'm) (or (test '0) (test '4) (test '9) (test 'y))))" would appear as "OR NOT (m AND (0 OR 4 OR 9 OR y))" in the explanation, while the actual explanation is "OR m AND NOT ( 0 OR 4 OR 9 OR y )".

I think the correct Scheme output would be "(and (test 'm) (not (test '0) (test '4) (test '9) (test 'y)))" for that part.
Fixed it, thank you. It was inverting the wrong part of the term in to_scheme3(). I updated my post with the fixed version.
Langton's ant: Can't play the drums, can be taught.

User avatar
confocaloid
Posts: 3066
Joined: February 8th, 2022, 3:15 pm

Re: 3D Hensel/INT notation working group

Post by confocaloid » November 21st, 2023, 1:47 pm

wirehead wrote:
November 21st, 2023, 1:24 pm
Fixed it, thank you. It was inverting the wrong part of the term in to_scheme3(). I updated my post with the fixed version.
It does appear to work correctly now. As an example, I manually reformatted a few expressions it printed, to make the structure visible:

Code: Select all

>>> ? -u-ch1589ym-049yn-12356xyw-6-fin8zfhin
Scheme: (not (or (and (test 'u)
                      (not (or (test 'c)
                               (and (test 'h)
                                    (or (test '1) (test '5) (test '8) (test '9) (test 'y)))
                               (and (test 'm)
                                    (not (or (test '0) (test '4) (test '9) (test 'y))))
                               (and (test 'n)
                                    (not (or (test '1) (test '2) (test '3) (test '5) (test '6) (test 'x) (test 'y)))))))
                 (and (test 'w)
                      (not (or (and (test '6)
                                    (not (or (test 'f) (test 'i) (test 'n))))
                               (test '8)
                               (and (test 'z)
                                    (or (test 'f) (test 'h) (test 'i) (test 'n)))))))) 
Explanation:
 NOT (   u AND NOT (   c
                    OR h AND ( 1 OR 5 OR 8 OR 9 OR y )
                    OR m AND NOT ( 0 OR 4 OR 9 OR y )
                    OR n AND NOT ( 1 OR 2 OR 3 OR 5 OR 6 OR x OR y ) )
      OR w AND NOT (   6 AND NOT ( f OR i OR n )
                    OR 8
                    OR z AND ( f OR h OR i OR n ) ) )
>>> ? 5-m8-rcentmv9x-cqrwk-qyfqtugrvi-pqu
Scheme: (or (and (test '5)
                 (not (test 'm)))
            (and (test '8)
                 (not (or (and (test 'r)
                               (or (test 'c) (test 'e) (test 'n)))
                          (and (test 't) (test 'm))
                          (test 'v))))
            (test '9)
            (and (test 'x)
                 (not (or (and (test 'c)
                               (or (test 'q) (test 'r) (test 'w)))
                          (and (test 'k) (not (test 'q))))))
            (and (test 'y)
                 (or (and (test 'f)
                          (or (test 'q) (test 't) (test 'u)))
                     (and (test 'g)
                          (or (test 'r) (test 'v)))
                     (and (test 'i)
                          (not (or (test 'p) (test 'q) (test 'u)))))))
Explanation:
         5 AND NOT (   m )
      OR 8 AND NOT (   r AND ( c OR e OR n )
                    OR t AND ( m )
                    OR v )
      OR 9
      OR x AND NOT (   c AND ( q OR r OR w )
                    OR k AND NOT ( q ) )
      OR y AND     (   f AND ( q OR t OR u )
                    OR g AND ( r OR v )
                    OR i AND NOT ( p OR q OR u ) )
>>> ? -zynmqrtuvw
Scheme: (not (or (test 'z)
                 (and (test 'y)
                      (or (test 'n)
                          (and (test 'm)
                               (or (test 'q) (test 'r) (test 't) (test 'u) (test 'v) (test 'w)))))))
Explanation:
 NOT (   z
      OR y AND     (   n
                    OR m AND ( q OR r OR t OR u OR v OR w ) ) )
127:1 B3/S234c User:Confocal/R (isotropic CA, incomplete)
Unlikely events happen.
My silence does not imply agreement, nor indifference. If I disagreed with something in the past, then please do not construe my silence as something that could change that.

User avatar
wirehead
Posts: 253
Joined: June 18th, 2022, 2:37 pm
Location: fish: wirehead: command not found
Contact:

Re: 3D Hensel/INT notation working group

Post by wirehead » November 21st, 2023, 10:36 pm

I have ported the whole thing to Scheme (specifically LIPS Scheme).

Code: Select all

#! /usr/bin/env lips
;; LIPS-Scheme port of Confocaloid's format parser
;; Untested, incomplete, almost certainly buggy, do not reuse, you have been warned.

(load "python.scm")

(define *edges* "0123456789xyz")
(define *corners* "cefghikmn")
(define *faces* "pqrtuvw")

;; Parser

(define (valid-characters? str)
  (let* ((allowed (concat *edges* *corners* *faces* "-"))
         (used (nodups str))
         (invalid (filter (lambda (x) (not (--> allowed (includes x)))) used)))
    (if (positive? (length invalid))
        (begin
              (display "invalid characters: ")
              (display invalid)
              (newline)
              #f)
        #t)))

(def (read-part2 str lv2 lv1)
    (if (zero? (length str))
        (return '(#f ())))
    (let ((neg #f))
        (when (eq? #\- (string-ref str 0))
            (set! neg #t)
            (set! str (substring str 1))
            (if (zero? (length str))
                (return nil)))
        (for c in (list *edges* *corners* *faces*)
            (if (and (not (eq? c lv1)) (not (eq? c lv2)) (--> c (includes (substring str 0 1))))
                (return (list neg (string->list str)))))
        nil))

(def (read-list2 str lv2 lv1)
    (let* ((poss (fold concat "" (filter (lambda (c) (not (or (eq? c lv1) (eq? c lv2)))) (list *edges* *corners* *faces* "-"))))
           (pat (concat "([" lv2 "])([" poss "]*)"))
           (out (vector))
           (p nil))
        (for m in (array->list (Array.from (--> str (matchAll pat))))
            (set! p (read-part2 (caddr m) lv2 lv1))
            (if (null? p)
                (return nil))
            (--> out (push (list (car (string->list (cadr m))) p))))
        (array->list out)))

(def (read-part1 str lv1)
    (if (zero? (length str))
        (return '(#f ())))
    (let ((neg #f))
        (when (eq? #\- (string-ref str 0))
            (set! neg #t)
            (set! str (substring str 1))
            (if (zero? (length str))
                (return nil)))
        (for c in (list *edges* *corners* *faces*)
            (if (and (not (eq? c lv1)) (--> c (includes (substring str 0 1))))
                (return (let ((ans (read-list2 str c lv1)))
                    (if (null? ans) nil (list neg ans))))))
        nil))

(def (read-list1 str lv1)
    (let* ((poss (fold concat "" (filter (lambda (c) (not (eq? c lv1))) (list *edges* *corners* *faces* "-"))))
           (pat (concat "([" lv1 "])([" poss "]*)"))
           (out (vector))
           (p nil))
        (for m in (array->list (Array.from (--> str (matchAll pat))))
            (set! p (read-part1 (caddr m) lv1))
            (if (null? p)
                (return nil))
            (--> out (push (list (car (string->list (cadr m))) p))))
        (array->list out)))

(def (read-part0 str)
    (if (zero? (length str))
        (return '(#f ())))
    (let ((neg #f))
        (when (eq? #\- (string-ref str 0))
            (set! neg #t)
            (set! str (substring str 1))
            (if (zero? (length str))
                (return nil)))
        (for c in (list *edges* *corners* *faces*)
            (if (--> c (includes (substring str 0 1)))
                (return (let ((ans (read-list1 str c )))
                    (if (null? ans) nil (list neg ans))))))
        nil))

(define (parse-part0 str)
    (set! str (--> str (trim) (toLowerCase)))
    (if (valid-characters? str) (read-part0 str) nil))

;; Printer

(def (print-list2 r)
    (let* ((head (car r))
           (tail (cadr r))
           (neg (car tail))
           (lst (cadr tail)))
        (when (zero? (length lst))
            (display head)
            (return nil))
        (display head)
        (display " AND ")
        (when neg (display "NOT "))
        (display "( ")
        (display (join " OR " lst))
        (display " )")))

(def (print-list1 r)
    (let* ((head (car r))
           (tail (cadr r))
           (neg (car tail))
           (lst (cadr tail)))
        (when (zero? (length lst))
            (display head)
            (return nil))
        (display head)
        (display " AND ")
        (display (if neg "NOT (   " "    (   "))
        (print-list2 (car lst))
        (when (>= (length lst) 2)
            (for l in (cdr lst)
                (newline)
                (display "                    OR ")
                (print-list2 l)))
        (display " )")))

(def (print-part0 r)
    (let* ((neg (car r))
           (lst (cadr r)))
        (if (zero? (length lst)) (return))
        (display (if neg " NOT (   " "         "))
        (print-list1 (car lst))
        (when (>= (length lst) 2)
            (for l in (cdr lst)
                (newline)
                (display "      OR ")
                (print-list1 l)))
        (when neg (display " )"))))

;; Scheme constructor

(def (make-bool-term items name)
    (set! items (filter truthy? items))
    (set! items (fold append nil (map (lambda (x) (if (eq? (car x) name) (cdr x) (list x))) items)))
    (if (> (length items) 1)
        (return `(,name ,@items)))
    (if (null? items)
        (return nil))
    (car items))

(define (make-case x)
    `(test ,x))

(define (to-scheme4 parts)
    (if (not (null? parts))
        (make-bool-term (map make-case parts) 'or)))

(define (to-scheme3 parts)
    (let ((part (car parts))
          (inverted (caadr parts))
          (inner (cadadr parts)))
        (set! inner (to-scheme4 inner))
        (if inverted
            (set! inner `(not ,inner)))
        (make-bool-term (list (make-case part) inner) 'and)))

(def (to-scheme2 parts)
    (let ((inverted (car parts))
          (inner (cadr parts)))
        (if (null? inner)
            (return nil))
        (set! inner (make-bool-term (map to-scheme3 inner) 'or))
        (if inverted
            `(not ,inner)
            inner)))

(define (to-scheme1 part)
    (make-bool-term (list (make-case (car part)) (to-scheme2 (cadr part))) 'and))

(define (to-scheme0 part)
    (let ((inverted (car part))
          (inner (cadr part)))
        (set! inner (make-bool-term (map to-scheme1 inner) 'or))
        (if inverted
            `(not ,inner)
            inner)))

;; interface

(define (help)
    (print
        "Available commands:"
        "   ? <part-0>           Parse a string"
        "   exit, quit           Exit the read-eval-print loop"
        "   help                 Print this help"))

(define (main)
    (print "Confocaloid's 3D rule explainer 1.0")
    (let ((running #t))
        (while running
            (display ">>> ")
            (flush-output)
            (let ((input (read-line (current-input-port))))
                (set! input (--> input (trim) (toLowerCase)))
                (cond
                    ((input.startsWith "?")
                        (let ((ans (parse-part0 (substring input 1))))
                            (if (null? ans)
                                (print "invalid input")
                                (begin
                                    (print (concat "Scheme: " (repr (to-scheme0 ans))))
                                    (print-part0 ans)
                                    (newline)))))
                    ((string=? "help" input)
                        (help))
                    ((or (string=? "exit" input) (string=? "quit" input))
                        (set! running #f))
                    (else
                        (print 'nothing-happens))))))
    (print 'bye))

(main)

Code: Select all

;; This is python.scm

(define (zip . lists)
    "(zip . lists)

    Like Python zip() it takes lists (a b c ...)
    and returns ((a0 b0 c0 ...) (a1 b1 c1 ...) ...)"
    (apply map (cons list lists)))

(define (enumerate l)
    "(enumerate l) -> ((0 l0) (1 l1) (2 l2) ...)

    Like Python enumerate()"
    (zip (range (length l)) l))

(define (nodups c)
    "(nodups list)

    Returns a new list with the duplicates removed."
    (array->list (Array.from (new Set c))))

(define (truthy? x)
    "(truthy? x)

    Returns #t if the object is Python-truthy"
    (cond
        ((null? x)
            #f)
        ((not x)
            #f)
        ((or (pair? x) (vector? x) (string?) (in "length" x))
            (positive? (length x)))
        ((number? x)
            (!= x 0))
        (else
            #t)))

(define (call-with-exit proc)
    "(call-with-exit fn)

    Calls fn with a procedure of one argument (the exit procedure), that when called, will
    stop execution of fn and cause the call-with-exit to immediately return the value passed
    to the exit procedure."
    (let* ((s (gensym 'call-with-exit))
           (exit (lambda (val) (raise (cons s val)))))
        (try (proc exit)
            (catch (err)
                (if (and (pair? err) (eq? (car err) s))
                    (cdr err)
                    (raise err))))))


(define-macro (lambda* params . body)
    "(lambda* params . body)

    It defines a lambda like (lambda) but can also
    do default arguments by specifying pair of (name default).
    The default is evaluated every time like Javascript (as opposed to Python).
    You can't do rest args like (a b . c), it will break."
    (let ((let-list (map (lambda (i p) 
                            (if (symbol? p)
                                `(,p (nth ,i arguments)) ; if param is required
                                `(,(car p) (if (> (length arguments) ,i) (nth ,i arguments) ,(cadr p))))) ; if it is optional
                         (range (length params))
                         params))
          (real-body (if (string? (car body)) (cdr body) body))
          (docstring (if (string? (car body)) (list (car body)) '())))
        `(lambda arguments ,@docstring (let* ,let-list ,@real-body))))

(define-macro (define* nap . body)
    "(define* (name . params) . body)

    Like lambda* but as a usual define style macro."
    `(define ,(car nap) (lambda* ,(cdr nap) ,@body)))

(define-macro (def nap . body)
    "(def (name . params) . body)

    Wraps "
    (let ((doc nil))
        (when (string? (car body))
            (set! doc (list (car body)))
            (set! body (cdr body)))
        `(define* ,nap ,@doc (call-with-exit (lambda (return) ,@body)))))


(define-syntax for
    (syntax-rules (in)
          ((for name in container body ...)
           (for-each (lambda (name) body ...) container)))
        "(for name in container . body)

        Syntax like a Python for loop.
        Iterates over the container with name bound to each element of container
        and runs the body each time.")
You won't see the >>> prompt due to a bug in LIPS, but it *should* be the same. LMK if it is not.
Last edited by wirehead on November 22nd, 2023, 10:57 am, edited 1 time in total.
Langton's ant: Can't play the drums, can be taught.

User avatar
confocaloid
Posts: 3066
Joined: February 8th, 2022, 3:15 pm

Re: 3D Hensel/INT notation working group

Post by confocaloid » November 22nd, 2023, 12:56 am

wirehead wrote:
November 21st, 2023, 10:36 pm
You won't see the >>> prompt due to a bug in LIPS, but it *should* be the same. LMK if it is not.
Did not test thoroughly but the output seems to be basically the same, except with "#\" inserted sometimes:

Code: Select all

? 01cepq
Scheme: (or (test 0) (and (test 1) (or (test c) (and (test e) (or (test p) (test q))))))
         0
      OR 1 AND     (   c
                    OR e AND ( #\p OR #\q ) )
? 01pqce
Scheme: (or (test 0) (and (test 1) (or (test p) (and (test q) (or (test c) (test e))))))
         0
      OR 1 AND     (   p
                    OR q AND ( #\c OR #\e ) )
? pq01ce
Scheme: (or (test p) (and (test q) (or (test 0) (and (test 1) (or (test c) (test e))))))
         p
      OR q AND     (   0
                    OR 1 AND ( #\c OR #\e ) )
? pqce01
Scheme: (or (test p) (and (test q) (or (test c) (and (test e) (or (test 0) (test 1))))))
         p
      OR q AND     (   c
                    OR e AND ( #\0 OR #\1 ) )
? ce01pq
Scheme: (or (test c) (and (test e) (or (test 0) (and (test 1) (or (test p) (test q))))))
         c
      OR e AND     (   0
                    OR 1 AND ( #\p OR #\q ) )
? cepq01
Scheme: (or (test c) (and (test e) (or (test p) (and (test q) (or (test 0) (test 1))))))
         c
      OR e AND     (   p
                    OR q AND ( #\0 OR #\1 ) )
? cepq-01
Scheme: (or (test c) (and (test e) (or (test p) (and (test q) (not (or (test 0) (test 1)))))))
         c
      OR e AND     (   p
                    OR q AND NOT ( #\0 OR #\1 ) )
? ce-pq-01
Scheme: (or (test c) (and (test e) (not (or (test p) (and (test q) (not (or (test 0) (test 1))))))))
         c
      OR e AND NOT (   p
                    OR q AND NOT ( #\0 OR #\1 ) )
? ce-pq01
Scheme: (or (test c) (and (test e) (not (or (test p) (and (test q) (or (test 0) (test 1)))))))
         c
      OR e AND NOT (   p
                    OR q AND ( #\0 OR #\1 ) )
? -cepq01
Scheme: (not (or (test c) (and (test e) (or (test p) (and (test q) (or (test 0) (test 1)))))))
 NOT (   c
      OR e AND     (   p
                    OR q AND ( #\0 OR #\1 ) ) )
? -u-ch1589ym-049yn-12356xyw-6-fin8zfhin
Scheme: (not (or (and (test u) (not (or (test c) (and (test h) (or (test 1) (test 5) (test 8) (test 9) (test y))) (and (test m) (not (or (test 0) (test 4) (test 9) (test y)))) (and (test n) (not (or (test 1) (test 2) (test 3) (test 5) (test 6) (test x) (test y))))))) (and (test w) (not (or (and (test 6) (not (or (test f) (test i) (test n)))) (test 8) (and (test z) (or (test f) (test h) (test i) (test n))))))))
 NOT (   u AND NOT (   c
                    OR h AND ( #\1 OR #\5 OR #\8 OR #\9 OR #\y )
                    OR m AND NOT ( #\0 OR #\4 OR #\9 OR #\y )
                    OR n AND NOT ( #\1 OR #\2 OR #\3 OR #\5 OR #\6 OR #\x OR #\y ) )
      OR w AND NOT (   6 AND NOT ( #\f OR #\i OR #\n )
                    OR 8
                    OR z AND ( #\f OR #\h OR #\i OR #\n ) ) )
quit
If this output format is convenient enough (?), I think I could later write some test cases, along the lines of

Code: Select all

input: xyz
output: (or (test x) (test y) (test z))
The input would be a string that could go as either "birth" or "survival" part of the entire rulestring.

User avatar
wirehead
Posts: 253
Joined: June 18th, 2022, 2:37 pm
Location: fish: wirehead: command not found
Contact:

Re: 3D Hensel/INT notation working group

Post by wirehead » November 22nd, 2023, 11:04 am

confocaloid wrote:
November 22nd, 2023, 12:56 am
Did not test thoroughly but the output seems to be basically the same, except with "#\" inserted sometimes.
Yeah, the #\ prefix is what Scheme uses for characters (they're not the same as one-character strings as they are in Python). Ideally everything would be characters, but my code doesn't convert everything (I'll have to work on it).

I also fixed the optimization so that if you enter 0cp it outputs (and (test 0) (test c) (test p)) instead of (and (test 0) (and (test c) (test p)). It's a bit harder to do this in Python because the Python code is doing string-mashing to produce the final S-expression, whereas the Scheme code is actually manipulating S-expressions.
confocaloid wrote:
November 22nd, 2023, 12:56 am
If this output format is convenient enough (?), I think I could later write some test cases, along the lines of

Code: Select all

input: xyz
output: (or (test x) (test y) (test z))
The input would be a string that could go as either "birth" or "survival" part of the entire rulestring.
I think that is a good idea. Usually test-cases would cover all the "edge cases" but I'm having trouble thinking of what would constitute an edge case in this format (aside from the cases above where it had three terms to AND and didn't put them all in the same term -- now fixed).
wirehead wrote:
November 21st, 2023, 10:36 pm
You won't see the >>> prompt due to a bug in LIPS, but it *should* be the same. LMK if it is not.
If you manually install the "devel" branch of LIPS that bug is now fixed.
Langton's ant: Can't play the drums, can be taught.

User avatar
wirehead
Posts: 253
Joined: June 18th, 2022, 2:37 pm
Location: fish: wirehead: command not found
Contact:

Re: 3D Hensel/INT notation working group

Post by wirehead » December 3rd, 2023, 8:31 pm

confocaloid wrote:
November 14th, 2023, 6:30 pm
Here is a quick&dirty Python 3 script (non-Golly). It is a REPL to parse and "explain" strings that conform to <part-0> in the grammar above.
Warning/disclaimer: this is not really reusable for parsing, not tested thoroughly, and there are no validity checks. The only purpose of writing this was to double-check that the notation makes sense.
I'm considering incorporating your code into my application. What open source license do you want to put your code under?
Langton's ant: Can't play the drums, can be taught.

Post Reply