diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9c7d52ae8950c6acee866f3807d0968353fa721b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +fancy-labels/ +diagrams/ +*~ \ No newline at end of file diff --git a/README.md b/README.md index d69b0fe45e291ab2bffe4d025a90ee8035506946..e3ab9e5e6018b3ee0cf34bc77fe615804199eb50 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# Choiceless grapher +# Choiceless grapher The Choiceless Grapher can produce any size of graph of the implication relationships between the consequences of the axiom of choice, [as found here](http://consequences.emich.edu/conseq.htm), with an option on the style of nodes: you can either have the Howard-Rubin (HR) numbering of the forms ("numbers"), or the full LaTeX-formatted statements ("fancy"). It's online as an [app here](http://cgraph.inters.co). -This project is inspired by and based on the **Consequences of the Axiom of Choice Project**, the encyclopedia of set theory without the axiom of choice, by *Prof. Paul Howard and Prof. Jean E. Rubin*. I thank Paul Howard for providing me with the original implication matrix (book1), a tex document with the form statements in LaTeX form, and permision to use these files, which can be found in the folder "Howard-Rubin-data". +This project is inspired by and based on the **Consequences of the Axiom of Choice Project**, the encyclopedia of set theory without the axiom of choice, by *Prof. Paul Howard and Prof. Jean E. Rubin*. I thank Paul Howard for providing me with the original implication matrix (book1), a tex document with the form statements in LaTeX form, and permision to use these files, which can be found in the folder "Howard-Rubin-data". -An overview of the program is given below. A paper with a full description and explanation of the code will appear. Until then, you can find posts about this in my ["Boole's ring"](https://boolesrings.org/ioanna/). A big **thank you** to my teammate [Max Rottenkolber](http://mr.gy) over at [interstellar ventures](http://inters.co) for showing me the light (Common Lisp), and for hosting this app: +An overview of the program is given below. A detailed description was published in my previous website, which [no longer exists](https://boolesrings.org/ioanna/). I plan to import the old posts from there to my new website [ioa.re](https://ioa.re). + +This is my first fullstack program, and I pledge to maintain it for life. I wrote this while learning common-lisp, and for this, a big **thank you** to my common-lisp sensei [Max Rottenkolber](http://mr.gy) over at [interstellar ventures](http://inters.co) for showing me the light (common-lisp), for naming back then this choiceless grapher `jeffrey` (because naming is hard), and for hosting this program's web app: ## The Website, aka the CGraph app @@ -27,58 +29,62 @@ If you want very large diagrams (more than 70 or 80 forms)*, or if you prefer to * Common-Lisp: I have only tested this with **SBCL** or **CCL** but it should work in any implementation that [external-program](https://github.com/sellout/external-program) supports. Please let me know if you check this! * [Graphviz](www.graphviz.org/) (`apt-get install graphviz`) * [Quicklisp](www.quicklisp.org) -* It only works in Linux. Windows and Macintosh support are no longer on the to-do list, since the program is accessible via its web-interface. *If you are interested in having these OS supported please send me a message or leave a comment.* -* The package `labelmaker` also requires `/bin/bash`, `pdflatex`, and `convert`. +* It only works in Linux. Windows and Macintosh support are no longer on the to-do list, since the program is accessible via its web-interface. *If you are interested in having these OS supported please send me a message.* Having said that, do try it in a Mac, as any unix system which understands `which` should work in theory. Please let me know! +* The package `labelmaker` (so to use "fancy" labels) also requires `/bin/bash`, `pdflatex`, the latex package `standalone`, and `imagemagick` for `convert`. Currently there are some issues with `convert`ing from `pdf` files, please wait for the next update to use fancy labels. ### Installation + Install this package using quicklisp ([installation instructions](https://www.quicklisp.org/beta/#installation)) and git ([installation instructions](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)) as follows: -* Create a folder called `jeffrey` in `quicklisp/local-projects/`, -* Navigate to this folder in a terminal and type `git init` and `git clone git@github.com:ioannad/jeffrey.git`. Alternatively otherwise download the cohntents of this repository to this folder. +- Install the system dependency Graphviz, for example with aptitude (Debian, Ubuntu, et.al.) using `sudo apt install graphviz`. +- In a terminal, navigate to a directory that ASDF or quicklisp can find (e.g. `quicklisp/local-projects/`) and `git clone https://gitlab.common-lisp.net/idimitriou/jeffrey.git`. Alternatively otherwise download the contents of this repository to such a directory. ### Load the Choiceless Grapher -Open a Common Lisp REPL and type in (evaluate) +In a Common Lisp REPL, evaluate `(ql:quickload \"jeffrey\")` and `(in-package :jeffrey)`. -`(ql:quickload "jeffrey")`. +### Draw the diagram between a given list of form HR numbers -### Drawing the diagram between a given list of form HR numbers +If `'(a b c ..)` is the list of form numbers (only numbers, without parameters or equivalent form letters), whose relationships you wish to graph, e.g. `'(1 2 3 4 5)` and if you want to save the diagram in a `png` file `filename.png`, then evaluate: -If '(a b c ..) is the list of form numbers (only numbers, without parameters or equivalent form letters), whose relationships you wish to graph, and if you want to save the diagram in the file "filename", then evaluate: +`(graph '(1 2 3 4 5) "filename.png")` -`(graph '(a b c ...) "filename" "style")` +Instead of `png` you can create another output format supported by `dot`, e.g., `svg`, `pdf`, `jpg`, etc. See `man dot` for details. +If you leave out the `.*` ending, a `png` file will be produced. -where "style" should be either "numbers" to get the numbers of the nodes in the diagram, or "fancy" to get the full LaTeX-formatted statements of the nodes in the diagram. "fancy" is still experimental and doesn't work well for very large diagrams, in the magnitude of the full one (goes over 13 meters in width). +All output files are created in a (new) `diagrams/` subdirectory of this repository's base directory. ### Drawing a random diagram of a given size You can also draw the implication diagram of a pseudo-random collection of forms, of a given size, as follows (for size 6): -`(random-graph 6 "filename" "style")` +`(random-graph 6 "filename")` -where "style", as above, should be either "numbers" or "fancy". +"Random" diagrams additionally include forms 1 (Axiom of Choice, top form) and form 0 (`0=0`, bottom form), otherwise these diagrams are often completely disconnected. -### Drawing the consequences or of a list of forms +### Drawing descendants of a list of forms -Similarly, you can draw the implication diagram of a list of forms (e.g. of `'(8 85)`) as follows: +To draw the consequences of forms in a list (e.g. all consequecnes of form 8 and of form 85), evaluate: -`(graph-descendants '(8 85) "filename" "style")` +`(graph-descendants '(8 85) "filename")` -### Output format +### Drawing ancestors of a list of forms -The default output is no longer pdf, as there is an issue with the borders of the fancy labels. The current default is `png` but you can output any filetype supported by dot (svg, ps,...) with, for example: +To draw all the forms that imply a form in a list of forms evaluate: -`(graph '(0 1 2 3 23) "test" "fancy" "ps")` +`(graph-ancestors '(399 256) "filename")` -or +### *NEW:* Drawing an interval between two forms -`(random-graph 5 "random5" "fancy" "svg")` +To draw all the consequences of the form with `name-1` (e.g., 261) that imply the form with `name-2` (e.g., 260), evaluate: -The resulting images and dot files will appear in `~/quicklisp/local-projects/jeffrey/diagrams`. If you have quicklisp installed somewhere else, then please do a `(setf *local-directory* "path/to/your/jeffrey")`, where "path/to/your/jeffrey" should be the directory where the Choiceless Grapher is installed. +`(graph-interval 261 260 "filename")` ## Detailed description -This program requires package "maxpc", "split-sequence", and "external-program". +This program requires the common-lisp packages "maxpc", "split-sequence", and "external-program". They are all available via Quicklisp, so you shouldn't have to install these separately. For the website it depends additionally in the quicklisp packages " + +It's nickname is `jeffrey`, as naming is hard and this was my first full stack program. **jeffrey.asd** contains the (defsystem ...) command that creates "jeffrey" as a system of packages. The files that comprise jeffrey's packages, their exported functions, and their dependencies are listed in **packages.lisp**. @@ -88,13 +94,13 @@ This program requires package "maxpc", "split-sequence", and "external-program". **process-strings.lisp** contains functions which make the text in any LaTeX-statement LaTeX compatible (the origin is TeX). It's a crude search and replace routine. -**read.lisp** contains the functions that read input, and it can be run in its whole with `(read-all-data)`. This function will first store the form data from `FORMSNUM.TEX` as nodes in `*graph*`, then add edges and parents to these nodes, following only the direct information from `book1`. That is, if book1 has code 1 in position (i,j), then it will add an edge to the node with name i (node i) with destination node j and relation T, and it will add node i to the set of parents of node j. If book1 has code 3 in position (i,j), then it will only add an edge to node i with destination node j and relation NIL. All other codes should be derivable from this information, using the predicates in the next module. +**read.lisp** contains the functions that read input, and it can be run in its whole with `(read-all-data)`. This function will first store the form data from `FORMSNUM.TEX` as nodes in `*graph*`, then add edges and parents to these nodes, following only the direct i nformation from `book1`. That is, if book1 has code 1 in position (i,j), then it will add an edge to the node with name i (node i) with destination node j and relation T, and it will add node i to the set of parents of node j. If book1 has code 3 in position (i,j), then it will only add an edge to node i with destination node j and relation NIL. All other codes should be derivable from this information, using the predicates in the next module. -**predicates.lisp** enables the program to ask whether or not a node (form) implies another. The function implies-p only answers positive implication questions, and implies-not-p only answers negative implication questions. In particular, `(implies-p A B)` asks whether A is an ancestor of B and `(implies-not-p B A)` asks whether there is an ancestor B' of B and a descendant A' of A, such that the node B' has an edge with destination A' and relation NIL. Why is the predicate "implies-p" defined like this is clear. For `(implies-not-p B A)`, assume that there is an ancestor B-anc of B and a descendant A-desc of A, such that B-anc does not imply A-desc (the meaning of a NIL-edge from B-anc to A-desc). Then `(implies-not-p B A)` must be T, i.e., B does not imply A, because otherwise we have the implication chain: +**predicates.lisp** enables the program to ask whether or not a node (form) implies another. The function implies-p only answers positive implication questions, and implies-not-p only answers negative implication questions. In particular, `(implies-p A B)` asks whether `A` is an ancestor of `B` and `(implies-not-p B A)` asks whether there is an ancestor `B-anc` of `B` and a descendant `A-desc` of `A`, such that the node `B-anc` has an edge with destination `A-desc` and relation NIL (i.e., such that `B-anc` does not imply `A-desc`). Why is the predicate `implies-p` defined like this is clear. For `(implies-not-p B A)`, assume that there is an ancestor `B-anc` of `B` and a descendant `A-desc` of `A`, such that `B-anc` does not imply `A-desc` (the meaning of a NIL-edge from `B-anc` to `A-desc`). Then `(implies-not-p B A)` must be T, i.e., it must be the case that `B` does not imply `A`, *otherwise* we have the implication chain: - B-anc implies B implies A implies A-desc, + `B-anc` implies `B` implies `A` implies `A-desc`, -therefore B-anc implies A-desc, contradiction to the NIL-edge from B-anc to A-desc. +therefore `B-anc` implies `A-desc`, which is a contradiction to the NIL-edge from `B-anc` to `A-desc`. **test.lisp** contains test data and testing functions, which should be run after every and any change in the above files. Run all tests while `(in-package :jeffrey-test)` with the command `(test-all)`, which prints a report to your REPL. @@ -105,13 +111,15 @@ Run all tests while `(in-package :jeffrey-test)` with the command `(test-all)`, **main.lisp** the main package is explained above. -**website.lisp** "The currently unfinished website of choiceless grapher, learning sources included at the top of the file. Uses hunchentoot as a webserver, cl-who, maxpc, parenscript, and a local css file, to serve you freshly made diagrams of your *choice*." +**website.lisp** "The website of choiceless grapher, uses [hunchentoot](https://www.cliki.net/Hunchentoot) as a webserver, and [html-template](https://www.cliki.net/html-template) to create the pages. Thanks to [Edi Weitz](https://www.cliki.net/Edi%20Weitz) for these two brilliant packages! + +**diagrams/** is not included here, it is created when you create your first diagram. All diagrams you create with this program are put there. -**/diagrams/** contains diagrams with sets of forms that make sense, e.g., between forms about alephs and their properties, as well as diagrams with random sets of forms. A boldfaced arrow from A to B means that the implication is non-reversible, i.e., there exists a model of ZF set theory in which B holds and A doesn't. *Just imagine the endless possibilities for random research projects, theses, and papers, filling or boldfacing those arrows! :)* The `full-diagram.pdf` is also included, only for standard "number names" (not full statements). Its size is 3,41m x 1,72 m. +**examples/** contains diagrams with sets of forms that make sense, e.g., between forms about alephs and their properties, as well as diagrams with random sets of forms. A boldfaced arrow from A to B means that the implication is non-reversible, i.e., there exists a model of ZF set theory in which B holds and A doesn't. *Just imagine the endless possibilities for random research projects, theses, and papers, filling or boldfacing those arrows! :)* The `full-diagram.pdf` is also included, only for standard "number names" (not full statements). Its size is 3,41m x 1,72 m. -**/fancy-labels/** is not included here, but you can produce it from package `:jeffrey.labelmaker` with the command `(make-fancy-labels`). +**fancy-labels/** is not included here, but you can produce it from package `:jeffrey.labelmaker` with the command `(make-fancy-labels`). Please wait for the next update to use this. -**/Howard-Rubin-data/** contains the files from the Consequences of the Axiom of Choice Project, which were kindly provided by Prof. Paul Howard. +**Howard-Rubin-data/** contains the files from the Consequences of the Axiom of Choice Project, which were kindly provided by Prof. Paul Howard. **complexity issues** -I have made some small steps to improve the original brute force algorithm for calculating the predicates. The biggest difference was made by memoising with the addition of the `*jeff-matrix*`. A 7%-25% improvement (SBCL and CCL resp.) was achieved by changing the double loop of `(graph-implies-not-p Y X)` to three single loops (finding destinations of nil edges of ancestors of Y, finding descendants of X, and intersecting these two lists). +I have made some small steps to improve the original brute force algorithm for calculating the predicates. The biggest difference was made by memoising with the addition of the `*jeff-matrix*`. A 7%-25% improvement (SBCL and CCL respectively, at the time of testing, on Debian in my thinkpad x201) was achieved by changing the double loop of `(graph-implies-not-p Y X)` to three single loops (finding destinations of nil edges of ancestors of Y, finding descendants of X, and intersecting these two lists). diff --git a/draw.lisp b/draw.lisp index ec3834a5429d1e149505c6f73c8b698a4f9a9fde..d2eba3e988ecf96b7b3755ab4df77051fea23e24 100644 --- a/draw.lisp +++ b/draw.lisp @@ -1,11 +1,11 @@ (in-package :jeffrey.draw) #| -example of expected use of exported function: -(draw '(2 3 65 43 22 102) "diagram-name") -This should draw the diagram between the nodes with names in the -above list, and save the .dot and .pdf files to -~/quicklisp/local-projects/jeffrey/diagrams/diagram-name.dot and +example of expected use of exported function: +(draw '(2 3 65 43 22 102) "diagram-name.png") +This should draw the diagram between the nodes with names in the +above list, and save the .dot and .png files to +~/quicklisp/local-projects/jeffrey/diagrams/diagram-name.dot and ...pdf respectively. |# @@ -14,55 +14,63 @@ above list, and save the .dot and .pdf files to *local-directory* "diagrams/" input-filename + "." postfix-ending)) (defun print-dot-head () "Writes the standard header of a diagram to the {*standard-output*}." - (format *standard-output* + (format *standard-output* "digraph diagram { ~% ratio=0.5;~% node [shape=ellipse,width=0.5,height=1];~%" )) (defun print-fancy-label (name) - "Takes the node `name` from the user input and returns a string -that makes Graphviz-dot use the pdf file in the folder -`\"form-name-pics\"`, that contains this form's LaTeX formatted -statement." - (format *standard-output* - "~a [image=\"~afancy-labels/~a.png\", label=\" \"];~%" - name *local-directory* name)) - + "Takes the node NAME from the user input and returns a string +that makes Graphviz-dot use the png file in the folder `fancy-labels`, +which contains this form's LaTeX formatted statement. Requires +(jeffrey.labelmaker::make-fancy-labels), which currently has issues +with `imagemagick`'s `convert`." + (let ((fancy-label-filename (format nil + "~afancy-labels/~a.png" + *local-directory* name))) + (if (probe-file fancy-label-filename) + (format *standard-output* + "~a [image=\"~a\", label=\" \"];~%" + name fancy-label-filename) + (error "~&Fancy labels seem not set up, or not set up properly. +Please evaluate (jeffrey.labelmaker::make-fancy-labels) and retry.")))) + (defun print-node-label (name node style) "Writes the labels for the nodes in the style `style`, depending on -the user input. The user as two choices for a style: +the user input. The user as two choices for a style: -* `\"fancy\"` creates diagrams with the full LaTeX statements of the -forms, which are stored as pictures in png format, in the folder -`diagrams/form-name-pics`. This is the default. -* `\"numbers\"` creates plain diagrams with only 'HR `name`' style +* `\"fancy\"` creates diagrams with the full LaTeX statements of the +forms, which are stored as pictures in png format, in the folder +`diagrams/form-name-pics`. This is the default. +* `\"numbers\"` creates plain diagrams with only 'HR `name`' style nodes." (if(equal style "fancy") (print-fancy-label name) (unless (equal style "numbers") - (error "Wrong style given in print-dot-file: ~a .~%" + (error "Wrong style given in print-dot-file: ~a .~%" style)))) (defun print-bold-arrow (name-i name-j) - "Prints a boldfaced arrow in dot-syntax from `name-i` to `name-j` + "Prints a boldfaced arrow in dot-syntax from `name-i` to `name-j` to the `*standard-output*`." - (format *standard-output* + (format *standard-output* "~a -> ~a [style=bold];~%" - name-i + name-i name-j)) (defun print-gray-arrow (name-i name-j) - (format *standard-output* + (format *standard-output* "~a -> ~a [color=dimgray];~%" name-i name-j)) (defun print-arrows-from (name node input-names) - "The loop inside the loop of `draw-dot-content.`" + "The loop inside the loop of `draw-dot-content.`" (loop for name% in input-names for node% = (call name%) when (not (equal name name%)) @@ -80,33 +88,47 @@ to the `*standard-output*`." do (print-node-label name node style) (print-arrows-from name node input-names))) +;; IO + +(defun create-dot-file (dot-file input-names style) + "DOT-FILE should be a complete pathname, with (the default) parent directory." + (ensure-directories-exist dot-file :verbose t) + (with-open-file (*standard-output* dot-file + :direction :output + :if-does-not-exist :create + :if-exists :supersede) + (print-dot-head) + (print-dot-content input-names style) + (format *standard-output* "~%}"))) + (defun run-tred-dot (dot-file result-file ending) - "Runs Graphviz tred on `dot-file` and feeds the output to -Graphviz dot. Outputs `result-file`, which contains the diagram." - (let ((command-string (concatenate 'string - "/usr/bin/tred " - dot-file - " | dot -T" - ending - " -o " - result-file))) - (run "/bin/sh" (list "-c" command-string) - :output t))) - -(defun draw (input-names diagram-filename style &optional (ending "png")) + "Runs Graphviz tred on `dot-file` and feeds the output to Graphviz dot. +Outputs `result-file`, which contains the diagram. Requires the system +package Graphviz." + (let ((command-string (concatenate 'string + "/usr/bin/tred " + (namestring dot-file) + " | dot -T" + ending + " -o " + (namestring result-file)))) + (if (probe-file (pathname "/usr/bin/tred")) + (run "/bin/sh" + (list "-c" command-string) + :output t) + (error "Graphviz is not installed. Please install and retry.")))) + +(defun draw (input-names filename style ending) "`style` should be either the string \"fancy\" or \"numbers\". `format` can be anything supported by dot, e.g., pdf, png, svg, ps. -The default format is pdf." - (let ((dot-file (make-filename ".dot" diagram-filename)) - (result-file (make-filename (concatenate 'string "." ending) diagram-filename))) - (with-open-file (*standard-output* dot-file - :direction :output - :if-does-not-exist :create - :if-exists :supersede) - (print-dot-head) - (print-dot-content input-names style) - (format *standard-output* "~%}")) - (run-tred-dot dot-file result-file ending) - (format nil - "Files ~a and ~a in diagrams/ should be created successfully." - dot-file result-file))) +If the DIAGRAM-FILENAME doesn't end in one of those formats, then png will be used." + (let ((dot-file (pathname (make-filename "dot" filename))) + (result-file (pathname + (make-filename ending filename)))) + (handler-case + (progn (create-dot-file dot-file input-names style) + (run-tred-dot dot-file result-file ending)) + (:no-error (message value) + (format *standard-output* + "~&Files ~a and ~a in diagrams/ should be created successfully.~%" + dot-file result-file))))) diff --git a/main.lisp b/main.lisp index 5f2cd34ab59c69bee72ad4ef7fef143665c5474b..8cbf99b1d7b5b4c6c9ab4878d84a4642007bd3b3 100644 --- a/main.lisp +++ b/main.lisp @@ -8,38 +8,52 @@ collect key)) (defun name-transformer (key names) - (let ((funct (case key - (:these (lambda (node) - (list node))) - (:descendants #'descendants) - (:ancestors #'ancestors)))) + (let ((transformer (case key + (:these (lambda (node) + (list node))) + (:descendants #'descendants) + (:ancestors #'ancestors) + (:interval #'interval))) + ;; the transformers require nodes input so we get the nodes + (nodes (loop for name in names collect (gethash name *graph*)))) (remove-duplicates - (append - names - (loop for name in names - append (map 'list - #'node-name - (funcall funct (gethash name *graph*)))))))) - -(assert (equal '(0 1 2 3 4 5) - (name-transformer :these '(0 1 2 3 4 5)))) -(assert (equal '(0 18 64 80 127 300 301 389 390) - (sort (name-transformer :descendants '(80 301 64)) - #'<))) -(assert (equal '(1 188 193 255 256 258 261 262) - (sort (name-transformer :ancestors '(255 188)) - #'<))) + (mapcar + #'node-name ; the transformers return nodes, so we get the names + (append nodes ; add the input nodes to the graph + (if (eq key :interval) + ;; interval takes exactly the two given nodes as input + (apply transformer nodes) + ;; ancestors or descendants of all the NAMES will be included + (reduce 'append (mapcar transformer nodes)))))))) -(defun graph (names-list filename style &optional (ending "png")) - (draw names-list filename style ending)) +(defun graph (names-list filename + &optional style ending + &aux (filename% filename)) + (let ((pathname (pathname filename))) + (when (null style) + (setq style "numbers")) + (when (null (or ending + (pathname-type pathname))) + (setq ending "png")) + (when (null ending) + (setq ending (pathname-type pathname)) + (setq filename% (pathname-name pathname))) + (when (pathname-directory pathname) + (warn "The parent directory of your output file is ignored. +Output files will be placed in ..jeffrey/diagrams/")) + (draw names-list filename% style ending))) -(defun graph-descendants (names-list filename style &optional (ending "png")) - (draw (name-transformer :descendants names-list) - filename style ending)) +(defun graph-descendants (names-list filename &optional style ending) + (graph (name-transformer :descendants names-list) + filename style ending)) -(defun graph-ancestors (names-list filename style &optional (ending "png")) - (draw (name-transformer :ancestors names-list) - filename style ending)) +(defun graph-ancestors (names-list filename &optional style ending) + (graph (name-transformer :ancestors names-list) + filename style ending)) + +(defun graph-interval (name-1 name-2 filename &optional style ending) + (graph (name-transformer :interval (list name-1 name-2)) + filename style ending)) ;;; Random graph functions @@ -51,7 +65,7 @@ (let ((l (length #1=(set-difference list except-these)))) (nth (random l) #1#))) -(defun random-numbers (n list except-these) +(defun random-numbers (n list except-these) (loop for i from 1 to n collecting (random-number list (append except-these numbers)) into numbers @@ -68,5 +82,5 @@ (append *bad-forms* '(0 1))))) -(defun random-graph (amount-of-nodes filename style &optional (ending "png")) +(defun random-graph (amount-of-nodes filename &optional style ending) (graph (random-HR-numbers amount-of-nodes) filename style ending)) diff --git a/make-fancy-labels.sh b/make-fancy-labels.sh index a6ab6cc4ce31ff65325b72783261018313222796..e6c96b61561d75f2590666897968f76f94077796 100644 --- a/make-fancy-labels.sh +++ b/make-fancy-labels.sh @@ -1,5 +1,7 @@ #!/bin/bash +mkdir -p fancy-labels + for i in fancy-labels/*.tex do pdflatex -output-directory fancy-labels "$i" diff --git a/packages.lisp b/packages.lisp index d01a96c400330ea6f45e2b8f103d6fa393ee1e53..38e0f8f6ccc850192deb6b9a939e105d0ab26825 100644 --- a/packages.lisp +++ b/packages.lisp @@ -74,6 +74,7 @@ Apart from the functions described below: {make-node}, {make-edge}, {add-parent} :setup-jeff-matrix :descendants :ancestors + :interval :implies-p :implies-not-p) (:documentation "predicates.lisp enables the program to ask whether or not a node (form) implies another. The function implies-p only answers positive implication questions, and implies-not-p only answers negative implication questions. In particular, (implies-p A B) asks whether A is an ancestor of B and (implies-not-p B A) asks whether there is an ancestor B' of B and a descendant A' of A, such that the node B' has an edge with destination A' and relation NIL. Why is the predicate \"implies-p\" defined like this is clear. For (implies-not-p B A), assume that there is an ancestor B-anc of B and a descendant A-desc of A, such that B-anc does not imply A-desc (the meaning of a NIL-edge from B-anc to A-desc). Then (implies-not-p B A) must be T, i.e., B does not imply A, because otherwise we have the implication chain: B-anc implies B implies A implies A-desc, therefore B-anc implies A-desc, contradiction to the NIL-edge from B-desc to A-desc. ")) @@ -108,6 +109,7 @@ Apart from the functions described below: {make-node}, {make-edge}, {add-parent} (defpackage jeffrey.main + (:nicknames :jeffrey) (:use :common-lisp :jeffrey.graph :jeffrey.read @@ -115,16 +117,41 @@ Apart from the functions described below: {make-node}, {make-edge}, {add-parent} :jeffrey.draw) (:export :name-transformer :graph + :graph-ancestors + :graph-descendants + :graph-interval + :random-graph :random-HR-numbers :*local-directory* :*names* :*bad-forms*) - (:documentation "Install this package using quicklisp (installation instructions for quicklisp can be found in https:////www.quicklisp.org//beta//#installation) and git (https:////git-scm.com//book//en//v2//Getting-Started-Installing-Git) as follows: + (:documentation "Install this system and load it using [quicklisp](https:////www.quicklisp.org//beta//#installation) and [git](https:////git-scm.com//book//en//v2//Getting-Started-Installing-Git) as follows: -* Create a folder called `jeffrey` in `quicklisp//local-projects//`, -* Navigate to this folder in a terminal and type `git init` and `git clone git@github.com:ioannad//jeffrey.git`. Alternatively otherwise download the contents of this repository to this folder. +- Install the system dependency Graphviz, for example with aptitude (Debian, Ubuntu, et.al.) using `sudo apt install graphviz`. +- In a terminal, navigate to a directory that ASDF or quicklisp can find (e.g. `quicklisp/local-projects/`) and `git clone https://gitlab.common-lisp.net/idimitriou/jeffrey.git`. Alternatively otherwise download the contents of this repository to such a directory. +- In a Common Lisp REPL, evaluate `(ql:quickload \"jeffrey\")` and `(in-package :jeffrey)`. -To produce a diagram, open a Common Lisp REPL (I have tested it only with SBCL and Clozure CL so far. Please let me know if you test it with other implementations). Then type in `(ql:quickload \"jeffrey\")` and then type in `(in-package :jeffrey.main)`. Now, to draw the diagram between the forms with Howard-Rubin numbers (HR) a b c d ... use the command `(main \"a b c d ...\")`.")) +**To draw the diagram between forms** with Howard-Rubin numbers (HR) `a b c d ...` in the file `\"filename.png\"`, evaluate + +``` +(graph a b c d ... \"filename.png\") +``` + +The png format is the default so this is equivalent to `(graph a b c d ... \"filename\"). Other supported formats are: `.svg`, `.ps`, `.jpg`. See ` $ man dot` + +To draw the diagram of a pseudo-random set of forms, of size N, evaluate `(random-graph n \"filename.png\")`. +Other variations: `(graph-ancestors number \"filename.png\")`, `(graph-descendants number \"filename.png\")`, but be aware that such diagrams might be quite large. + +**NEW** `(graph-interval 261 260 \"interval-261-to-260\")` draws all the consequences of form 261 which imply form 260. + +All the above graphing functions take an optional extra parameter `STYLE`, where STYLE can be: + +- either the string \"numbers\" (the default), using the form numbers as labels in the graph image, +- or the string \"fancy\" which uses LaTeX formatted labels with the full statements of the forms. + To enable this style, execute `./make-fancy-labels`. Requires `pdflatex`, the latex package `standalone`, and `imagemagick`. + Currently there are some issues with imagemagick's `convert` from PDF files, so wait for the next update to use fancy labels. + +I have tested `:jeffrey` only with SBCL and CCL (Clozure CL) so far. Please let me know if you test it with other implementations.")) (defpackage jeffrey.parse-web-input (:use :cl :hunchentoot :split-sequence diff --git a/predicates.lisp b/predicates.lisp index 3778ca25f58f2fc14fceaa6c6439fc38b2bebc14..781b35b494d3588fc003cec859672860befc0e82 100644 --- a/predicates.lisp +++ b/predicates.lisp @@ -69,6 +69,9 @@ as 'row implies column?'.") (node-children node) (map 'list #'descendants (node-children node)))))) +(defun interval (X Y) + (intersection (descendants X) (ancestors Y))) + (defun nil-edge-p (Y X) ;; => NIL or nonempty list "Returns T if there is an edge in (node-edges Y) with :destination X and :relation NIL." diff --git a/test.lisp b/test.lisp index aeaeb9ffc22aed7b6e71799f7df46deb6b7bf5bd..527a8e0ed4b9af5b1728445f700d416d97e354fa 100644 --- a/test.lisp +++ b/test.lisp @@ -1,5 +1,17 @@ (in-package :jeffrey.test) +;; Test functions in :jeffrey.main + +(defun test-name-transformer () + (assert (equal '(0 1 2 3 4 5) + (jeffrey::name-transformer :these '(0 1 2 3 4 5)))) + (assert (equal '(0 18 64 80 127 300 301 389 390) + (sort (jeffrey::name-transformer :descendants '(80 301 64)) + #'<))) + (assert (equal '(1 188 193 255 256 258 261 262) + (sort (jeffrey::name-transformer :ancestors '(255 188)) + #'<)))) + ;; Disclaimer: The test data below has no connection with the real data ;; whatsoever. (defparameter *simple-test-matrix* @@ -147,6 +159,8 @@ (defun test-all () + ;; test :jeffrey.main functions + (test-name-transformer) ;; test data (format t "Setting up test...~%") (setup-test)