lprop2cnf.scm
```(module lprop2cnf mzscheme

(require (prefix srfi1: (lib "1.ss" "srfi"))
(prefix srfi71: (lib "71.ss" "srfi"))
(lib "42.ss" "srfi")
(lib "list.ss")
(lib "file.ss")
(lib "etc.ss")
(lib "contract.ss")
(lib "plt-match.ss")
"cardinality-constraints.scm")

;; Given f with binary ops <->, ->, ite, xor, not, and, or, simplifies formula for binary
;; not, and, or operators
(define (remove-redundant-ops f)
;(printf "Removing ROps: ~a~n" f)
(match f
(`(=> ,op1 ,op2)
`(or (not ,(remove-redundant-ops op1))
,(remove-redundant-ops op2)))
(`(<=> ,op1 ,op2)
(remove-redundant-ops
`(and (=> ,op1 ,op2)
(=> ,op2 ,op1))))
(`(xor ,op1 ,op2)            ;; Binary xor
(remove-redundant-ops
`(not (<=> ,op1 ,op2))))
(`(ite ,op1 ,op2 ,op3)
(remove-redundant-ops
`(and (=> ,op1 ,op2)
(=> (not ,op1) ,op3))))
(`(xor ,ops ..3)            ;; Multiple argument xor, which is true iff exactly only one of the ops is true
(remove-redundant-ops
`(and (or ,@ops)
,(apply encode-atmost1/lprop ops))))
(`(am1 ,ops ..2)            ;; Multiple argument atmost1 operator, encoding of cardinality constraints used
(remove-redundant-ops (apply encode-atmost1/lprop ops)))
(`(and ,ops ..1)
`(and ,@(map remove-redundant-ops ops)))
(`(or ,ops ..1)
`(or ,@(map remove-redundant-ops ops)))
(`(not ,op)
`(not ,(remove-redundant-ops op)))
((? (λ (a) (or (symbol? a)
(boolean? a))) s)
s)
(_ (error 'remove-redundant-ops "Couldn't find a match for ~a" f))))

(define (lprop->cnf formula)
(let ([var-hash (make-hash-table)]   ; hash-table code -> var
[clause-hash (make-hash-table 'equal)] ; hash-table clause (list-of integer?) -> lprop-formula
[code-hash (make-hash-table 'equal)]) ; hash-table var or formula -> code
(letrec ([new-code
(let ([avail-code 1])
(λ ()
(begin0
avail-code
(set! avail-code (+ avail-code 1)))))]
[insert-var
(λ (v)
(let ([code (new-code)])
(hash-table-put! var-hash code v)
(hash-table-put! code-hash v code)
code))]
[insert-formula
(λ (f)
(let ([code (new-code)])
(hash-table-put! code-hash f code)
code))]
[get-var-code
(λ (v) (hash-table-get code-hash v (λ () (insert-var v))))]
[get-formula-code
(λ (f) (hash-table-get code-hash f (λ () #f)))]
[insert-clauses ;; Inserts the clauses in clause-hash with respect to formula f
;; and returns a list with the clauses.
(λ (f . clauses)
(for-each (λ (c) (hash-table-put! clause-hash (sort c <) f)) clauses)
clauses)])
(apply values
var-hash
clause-hash
(let-values ([(top-level-code top-level-clauses)
(let loop ([f formula])
(match f
(`(and ,ops ..1)
(let ([ids/clauses (map (λ (op) (srfi71:values->list (loop op))) ops)])
(let* ([ids (sort! (map car ids/clauses) <)]
[clauses (apply append (map cadr ids/clauses))]
[reuse-code (get-formula-code `(and ,@ids))])
(if reuse-code
(values reuse-code clauses)
(let ([code (insert-formula `(and ,@ids))])
(values code
(append (apply insert-clauses f
(cons (cons code (map (λ (id) (- id)) ids))
(map (λ (id) (list id (- code))) ids)))
clauses)))))))
(`(or ,ops ..1)
(let ([ids/clauses (map (λ (op) (srfi71:values->list (loop op))) ops)])
(let* ([ids (sort! (map car ids/clauses) <)]
[clauses (apply append (map cadr ids/clauses))]
[reuse-code (get-formula-code `(or ,@ids))])
(if reuse-code
(values reuse-code clauses)
(let ([code (insert-formula `(or ,@ids))])
(values code
(append (apply insert-clauses f
(cons (cons (- code) ids)
(map (λ (id) (list (- id) code)) ids)))
clauses)))))))
(`(not ,op)
(let-values ([(id clauses) (loop op)])
(let ([reuse-code (get-formula-code `(not ,id))])
(if reuse-code
(values reuse-code clauses)
(let ([code (insert-formula `(not ,id))])
(values code
(append (insert-clauses f
(list id code)
(list (- id) (- code)))
clauses)))))))
((? (λ (a) (or (symbol? a)
(boolean? a))) s) (values (get-var-code s) '()))
;; Auxiliary formula definitions
(`(=> ,op1 ,op2)
(let-values ([(id1 clauses1) (loop op1)]
[(id2 clauses2) (loop op2)])
(let ([reuse-code (get-formula-code `(=> ,id1 ,id2))])
(if reuse-code
(values reuse-code (append clauses1 clauses2))
(let ([code (insert-formula `(=> ,id1 ,id2))])
(values code
(append (insert-clauses f
(list (- id1) id2 (- code))
(list id1 code)
(list (- id2) code))
clauses1
clauses2)))))))
(`(<=> ,op1 ,op2)
(let-values ([(id1 clauses1) (loop op1)]
[(id2 clauses2) (loop op2)])
(let* ([sorted-ids (sort (list id1 id2) <)]
[reuse-code (get-formula-code `(<=> ,@sorted-ids))])
(if reuse-code
(values reuse-code (append clauses1 clauses2))
(let ([code (insert-formula `(<=> ,@sorted-ids))])
(values code
(append (insert-clauses f
(list id1 id2 code)
(list id1 (- id2) (- code))
(list (- id1) (- id2) code)
(list (- id1) id2 (- code)))
clauses1
clauses2)))))))
(`(xor ,op1 ,op2)
(let-values ([(id1 clauses1) (loop op1)]
[(id2 clauses2) (loop op2)])
(let* ([sorted-ids (sort (list id1 id2) <)]
[reuse-code (get-formula-code `(<=> ,@sorted-ids))])
(if reuse-code
(values reuse-code (append clauses1 clauses2))
(let ([code (insert-formula `(<=> ,@sorted-ids))])
(values code
(append (insert-clauses f
(list id1 id2 (- code))
(list id1 (- id2) code)
(list (- id1) (- id2) (- code))
(list (- id1) id2 code))
clauses1
clauses2)))))))
(`(ite ,op1 ,op2 ,op3)
(let-values ([(id1 clauses1) (loop op1)]
[(id2 clauses2) (loop op2)]
[(id3 clauses3) (loop op3)])
(let* ([coded-f `(ite ,id1 ,id2 ,id3)]
[reuse-code (get-formula-code coded-f)])
(if reuse-code
(values reuse-code (append clauses1 clauses2 clauses3))
(let ([code (insert-formula coded-f)])
(values code
(append (insert-clauses f
(list id1 id3 (- code))
(list id1 (- id3) code)
(list (- id1) id2 (- code)))
clauses1
clauses2)))))))
(`(am1 ,ops ..2)
(let ([ids/clauses (map (λ (op) (srfi71:values->list (loop op))) ops)])
(let* ([ids (sort! (map car ids/clauses) <)]
[clauses (apply append (map cadr ids/clauses))]
[coded-f `(am1 ,@ids)]
[reuse-code (get-formula-code coded-f)])
(if reuse-code
(values reuse-code clauses)
(let ([aux-vars (build-list (apply encode-atmost1/needed-vars ids) (λ (n) (new-code)))]
[code (insert-formula coded-f)])
(values code
(append (apply insert-clauses f (apply encode-atmost1 aux-vars ids)) clauses)))))))
(`(xor ,ops ..3)
(loop `(and (or ,@ops) (am1 ,@ops))))
(_ (error 'lprop2cnf "Couldn't find a match for ~a" f))))])
(let* ([false-code (get-var-code #f)]
[true-code (get-var-code #t)]
[false-needed (>= top-level-code false-code)]
[true-needed (>= top-level-code true-code)]
[mid-clause-set (if false-needed (cons (list (- false-code)) top-level-clauses) top-level-clauses)]
[final-clause-set (if true-needed (cons (list true-code) mid-clause-set) mid-clause-set)])
(list top-level-code final-clause-set)))))))

(define (output-cnf num-vars clauses path)
(let ([num-clauses (length clauses)])
(call-with-output-file* path
(lambda (fp)
(fprintf fp "p cnf ~a ~a~n" num-vars (+ 1 num-clauses))
(for-each (λ (clause)
(for-each (λ (literal) (fprintf fp "~a " literal)) clause)
(fprintf fp "0~n"))
clauses)
(fprintf fp "~a 0~n" num-vars))
'truncate)))

(provide lprop->cnf output-cnf)

)```