Add scholium programming and search examples from Ray
[arxana.git] / elisp / search-expanded.el
blobca89b72285e61ee20a41a5107131e1685340ac16
1 ;; search-expanded.el - network search routines
3 ;; Copyright (C) 2013 Raymond S. Puzio
5 ;; This program is free software: you can redistribute it and/or modify
6 ;; it under the terms of the GNU Affero General Public License as published by
7 ;; the Free Software Foundation, either version 3 of the License, or
8 ;; (at your option) any later version.
10 ;; This program is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;; GNU Affero General Public License for more details.
15 ;; You should have received a copy of the GNU Affero General Public License
16 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
18 ;;; COMMENTARY:
20 ;; a query language
22 ;; We will implement the search as a pipeline which gradually
23 ;; transforms the query into a series of expressions which produce
24 ;; the sought-after result, then evaluate those expressions. To
25 ;; illustrate and explain this process, we will use the following
26 ;; query as an example:
28 ;; This tells you the predicates that apply to the nodes
29 ;; and the network relationships that apply to them. The network relationships
30 ;; function as wildcards.
32 ;; Basic model at the moment triplets that point to other triplets.
34 ;; (((a blue big)
35 ;; (b funny)
36 ;; (c green small)
37 ;; ((b src a)
38 ;; (b snk c)
39 ;; (c src a))
41 ;; This asks us to find a funny link from a big blue object to a
42 ;; small green link to the big blue object.
44 ;; The first step of processing is to put the quaerenda in some
45 ;; order so that each item links up with at least one previous item:
47 ;; (scheduler
48 ;; '((b funny)
49 ;; (c green small))
50 ;; '((b src a)
51 ;; (b snk c)
52 ;; (c src a))
53 ;; '((a blue big)))
55 ;; =>
57 ;; ((c (green small) ((b snk c) (c src a)))
58 ;; (b (funny) ((b src a)))
59 ;; (a blue big))
61 ;; (Note that the order is reversed due to technicalities of
62 ;; implementing "scheduler" --- that is to say, a is first and does
63 ;; not link to any other variable, b is next and links to only a,
64 ;; whilst c is last and links to both a and b.)
66 ;; At the same time, we have also rearranged things so that the
67 ;; links to previous items to which a given object are listed
68 ;; alongside that object.
70 ;; The next step is to replace the links with the commands which
71 ;; generate a list of such objects:
73 ;; ((c (green small) ((b snk c) (c src a)))
74 ;; (b (funny) ((b src a)))
75 ;; (a blue big))
77 ;; =>
79 ;; ((c (green small)
80 ;; (intersection (list (get-snk b)) (get-forward-links a)))
81 ;; (b (funny)
82 ;; (intersection (get-backward-links a)))
83 ;; (a blue big))
85 ;; This is done using the function tplts2cmd, e.g.
87 ;; (tplts2cmd 'c '((b snk c) (c src a)))
89 ;; => (intersection (list (get-snk b)) (get-forward-links a))
91 ;; Subsequently, we filter over the predicates:
93 ;; ((c (filter '(lambda (c) (and (green c) (small c)))
94 ;; (intersection (list (get-snk b))
95 ;; (get-forward-links))))
96 ;; (b (filter '(lambda (b) (and (funny b)))
97 ;; (intersection (get-backward-links a)))))
99 ;; This is done with the command add-filt:
101 ;; (add-filt 'c
102 ;; '(green small)
103 ;; '((b snk c) (c src a)))
105 ;; (c (filter (quote (lambda (c) (and (green c) (small c))))
106 ;; (intersection (list (get-snk b))
107 ;; (get-forward-links a))))
110 ;; This routine calls up the previously described routine tplts2cmd
111 ;; to take care of the third argument.
113 ;; The last entry,
115 ;; (a blue big)
117 ;; gets processed a little differently because we don't as yet have
118 ;; anything to filter over; instead, we generate the initial list by
119 ;; looping over the current network:
121 ;; (a (let ((ans nil))
122 ;; (donet 'node
123 ;; (when (and (blue (get-content node))
124 ;; (big (get-content node)))
125 ;; (setq ans (cons node ans))))
126 ;; ans))
128 ;; This is done by invoking first2cmd:
130 ;; (first2cmd '(blue big))
132 ;; =>
134 ;; (let ((ans nil))
135 ;; (donet (quote node)
136 ;; (when (and (blue (get-content node))
137 ;; (big (get-content node)))
138 ;; (setq ans (cons node ans))))
139 ;; ans)
141 ;; (query2cmd
142 ;; '((c (green small) ((b snk c) (c src a)))
143 ;; (b (funny) ((b src a)))
144 ;; (a blue big)))
146 ;; =>
148 ;; ((c (filter (quote (lambda (c) (and (green c) (small c))))
149 ;; (intersection (list (get-snk b))
150 ;; (get-forward-links a))))
151 ;; (b (filter (quote (lambda (b) (and (funny b))))
152 ;; (intersection (get-forward-links a))))
153 ;; (a (let ((ans nil))
154 ;; (donet (quote node)
155 ;; (when (and (blue (get-content node))
156 ;; (big (get-content node)))
157 ;; (setq ans (cons node ans))))
158 ;; ans)))
160 ;; To carry out these instructions in the correct order and generate
161 ;; a set of variable assignments, we employ the matcher function.
162 ;; Combining this last layer, we have the complete pipeline:
164 ;; (matcher nil
165 ;; (query2cmd
166 ;; (scheduler
167 ;; '((b funny)
168 ;; (c green small))
169 ;; '((b src a)
170 ;; (b snk c)
171 ;; (c src a))
172 ;; '((a blue big)))))
174 ;; This combination of operations is combined into the search
175 ;; function.
177 ;; (search
178 ;; '(((a blue big)
179 ;; (b funny)
180 ;; (c green small))
181 ;; ((b src a)
182 ;; (b snk c)
183 ;; (c src a))))
185 ;; Having described what the functions are supposed to do and how
186 ;; they work together, we now proceed to implement them.
188 ;; The scheduler function takes a list search query and rearranges it
189 ;; into an order suitable for computing the answer to that query.
190 ;; Specifically, a search query is a pair of lists --- the first list
191 ;; consists of lists whose heads are names of variables and whose
192 ;; tails are predicates which the values of the variables should
193 ;; satisfy and the second list consists of triples indicating the
194 ;; relations between the values of the variables.
196 ;; new-nodes is a list of items of the form (node &rest property)
197 ;; links is a list of triplets
198 ;; sched is a list whose items consist of triplets of the
199 ;; form (node (&rest property) (&rest link))
201 ;;; CODE:
203 ;; We start with some preliminary definitions and generalities.
205 (require 'cl)
207 ;; Returns the subset of stuff which satisfies the predicate pred.
209 (defun filter (pred stuff)
210 (let ((ans nil))
211 (dolist (item stuff (reverse ans))
212 (if (funcall pred item)
213 (setq ans (cons item ans))
214 nil))))
216 ;; Set-theoretic intersection operation.
217 ;; More general than the version coming from the `cl' package
218 (defun intersection (&rest arg)
219 (cond ((null arg) nil)
220 ((null (cdr arg)) (car arg))
221 (t (let ((ans nil))
222 (dolist (elmt (car arg) ans)
223 (let ((remainder (cdr arg)))
224 (while (and remainder
225 (member elmt (car remainder)))
226 (setq remainder (cdr remainder))
227 (when (null remainder)
228 (setq ans (cons elmt ans))))))))))
231 (defun scheduler (new-nodes links sched)
232 ;; If done, return answer.
233 (if (null new-nodes)
234 sched
235 (let (
236 ;; New nodes yet to be examined.
237 (remaining-nodes new-nodes)
238 ;; Element of remaining-nodes currently
239 ;; under consideration.
240 (candidate nil)
241 ;; List of links between candidate and old-nodes.
242 (ties nil)
243 ;; List of nodes already scheduled.
244 (old-nodes (mapcar 'car sched)))
245 ;; Loop through new nodes until find one linked
246 ;; to an old node.
247 (while (null ties)
248 ;; Look at the next possible node.
249 (setq candidate (car remaining-nodes))
250 (setq remaining-nodes (cdr remaining-nodes))
251 ;; Find the old nodes linking to the candidate
252 ;; node and record the answer in "ties".
253 (setq ties
254 ;; Pick out the triplets
255 (filter '(lambda (x)
256 (or
257 ;; whose first element is the node
258 ;; under consideration and whose third
259 ;; element is already on the list
260 (and (eq (first x) (car candidate))
261 (member (third x) old-nodes))
262 ;; or vice-versa.
263 (and (member (first x) old-nodes)
264 (eq (third x) (car candidate)))))
265 links)))
266 ;; Recursively add the rest of the nodes.
267 (scheduler (remove candidate new-nodes)
268 links
269 (cons (list (car candidate)
270 (cdr candidate)
271 ties)
272 sched)))))
274 (defun tplts2cmd (var tplts)
275 (cons 'intersection
276 (mapcar
277 '(lambda (tplt)
278 (cond ((and (eq (third tplt) var)
279 (eq (second tplt) 'src))
280 `(get-flk ,(first tplt)))
281 ((and (eq (third tplt) var)
282 (eq (second tplt) 'snk))
283 `(get-blk ,(first tplt)))
284 ((and (eq (first tplt) var)
285 (eq (second tplt) 'src))
286 `(list (get-src ,(third tplt))))
287 ((and (eq (first tplt) var)
288 (eq (second tplt) 'snk))
289 `(list (get-snk ,(third tplt))))
290 (t nil)))
291 tplts)))
293 (defun add-filt (var preds tplts)
294 `(,var
295 (filter
296 '(lambda (,var)
297 ,(cons 'and
298 (mapcar
299 '(lambda (pred)
300 (list pred
301 (list 'get-txt var)))
302 preds)))
303 ,(tplts2cmd var tplts))))
305 (defun first2cmd (preds)
306 `(let ((ans nil))
307 (dolist (node (get-ids) ans)
308 (when
309 ,(cons 'and
310 (mapcar
311 '(lambda (pred)
312 (cons pred '((get-txt node))))
313 preds))
314 (setq ans (cons node ans))))))
316 (defun query2cmd (query)
317 (let ((backwards (reverse query)))
318 (reverse
319 (cons
320 (list (caar backwards)
321 (first2cmd (cdar backwards)))
322 (mapcar
323 '(lambda (x)
324 (add-filt (first x) (second x) (third x)))
325 (cdr backwards))))))
327 (defun matcher (assgmt reqmts)
328 (if (null reqmts) (list assgmt)
329 (apply 'append
330 (mapcar '(lambda (x)
331 (matcher (cons (list (caar reqmts) x) assgmt)
332 (cdr reqmts)))
333 (apply 'intersection
334 (eval `(let ,assgmt
335 (mapcar 'eval
336 (cdar reqmts)))))))))
338 (defun search (query)
339 (matcher nil
340 (reverse
341 (query2cmd
342 (scheduler
343 (cdar query)
344 (cadr query)
345 (list (caar query)))))))
347 ;; Here are some examples unrelated to what comes up in serching
348 ;; triplets which illustrate how matcher works:
350 ;; (matcher '((x 1))
351 ;; '((y (list 1 3))
352 ;; (z (list (+ x y) (- y x)))))
354 ;; (((z 2) (y 1) (x 1))
355 ;; ((z 0) (y 1) (x 1))
356 ;; ((z 4) (y 3) (x 1))
357 ;; ((z 2) (y 3) (x 1)))
360 ;; (matcher nil
361 ;; '((x (list 1))
362 ;; (y (list 1 3))
363 ;; (z (list (+ x y) (- y x)))))
365 ;; =>
367 ;; (((z 2) (y 1) (x 1))
368 ;; ((z 0) (y 1) (x 1))
369 ;; ((z 4) (y 3) (x 1))
370 ;; ((z 2) (y 3) (x 1)))