-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPYTHAG.CL
514 lines (447 loc) · 19 KB
/
PYTHAG.CL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
;;; PYTHAG.CL
;;; "Pythagoras" -- a program that demonstrates heuristically-guided
;;; concept formation in mathematics.
;;; (C) Copyright 1995 by Steven L. Tanimoto.
;;; This program is described in Chapter 10 ("Learning") of
;;; "The Elements of Artificial Intelligence Using Common Lisp," 2nd ed.,
;;; published by W. H. Freeman, 41 Madison Ave., New York, NY 10010.
;;; Permission is granted for noncommercial use and modification of
;;; this program, provided that this copyright notice is retained
;;; and followed by a notice of any modifications made to the program.
;;; Structure of the concept exploration system:
;;; There is an "agenda" (prioritized list of tasks)
;;; which is continually updated by the insertion of new
;;; tasks and the deletion of tasks that are completed.
;;; Each task is a specification for some activity.
;;; The function EXPLORE-CONCEPTS is a procedure which repeatedly
;;; takes the task of highest priority off the agenda and performs
;;; it with the help of other functions.
;;; An item on the agenda has the form:
;;; (interest-value (Task-spec))
;;; There are two kinds of tasks, and their specifications
;;; have the following forms:
;;; (MAKE-SPECIALIZATION concept)
;;; (FIND-EXAMPLES-OF concept)
;;; -----------------------------------------------------------------
;;; The concepts under investigation are classes of geometric figures
;;; that can be derived from the most general class "OBJECT" using
;;; specialization through restrictive predicates.
;;; Since these predicates will be applied to actual geometric figures
;;; during the course of the program's exploration, we must define the
;;; representation scheme for the geometric figures. For this program,
;;; they are all polygons.
;;; A polygon is represented as a list of points,
;;; and a point is a pair of coordinate values.
;;; The set of polygons used in the explorations
;;; is referred to as the "universe of objects" and
;;; it is defined as follows:
(defparameter *universe*
'(*box* *square* *isosceles* *right-triangle*
*trapezoid* *parallelogram* *rhombus* *multi*
*line* *dot*) )
(defparameter *box* '((0 0) (0 5) (10 5) (10 0)) )
(defparameter *square* '((0 0) (0 10) (10 10) (10 0)) )
(defparameter *isosceles* '((0 0) (5 5) (10 0)) )
(defparameter *right-triangle* '((0 0) (4 3) (4 0)) )
(defparameter *trapezoid* '((0 0) (5 5) (20 5) (25 0)) )
(defparameter *parallelogram* '((0 0) (5 5) (15 5) (10 0)) )
(defparameter *rhombus* '((0 0) (4 3) (9 3) (5 0)) )
(defparameter *multi* '((0 0) (0 10) (4 15) (10 15) (15 10)
(15 4) (10 0) ) )
(defparameter *line* '((0 0) (10 0)) )
(defparameter *dot* '((0 0)) )
;;; -----------------------------------------------------------------
;;; A collection of predicates for the purpose of forming specializations
;;; is provided below:
(defparameter *predicates*
'(equal-sides nonzero-area many-sides) )
;;; MANY-SIDES is true if P has more than 6 sides.
(defun many-sides (p)
"Returns T if P has more than 6 sides."
(> (length p) 6) )
;;; NONZERO-AREA is true if the area of P is not equal to zero.
(defun nonzero-area (p)
"Returns T if P has nonzero area."
(not (zerop (area p))) )
;;; AREA computes the area enclosed by polygon P.
(defun area (p)
"Computes the area enclosed by polygon P."
(/ (area1 p (first p)) 2) )
;;; AREA1 computes twice the sum of the areas under each segment.
;;; FIRST-PT is the first point of the polygon P when called
;;; at its top level. It's used to make sure the last segment
;;; of the polygon is considered --- the one from the last point
;;; to the first point.
(defun area1 (p first-pt)
"Returns 2 times the sum of the areas under the segments of P."
(if (null (rest p))
(* (dx first-pt (first p))
(py first-pt (first p)) )
(+ (area1 (rest p) first-pt)
(* (dx (second p) (first p))
(py (second p) (first p)) ) ) ) )
;;; EQUAL-SIDES is true if all sides of P have equal length.
(defun equal-sides (p)
"Returns T if all sides of P have equal length."
(apply #'= (side-length-sq-list p (first p))) )
;;; SIDE-LENGTH-SQ-LIST makes a list of squared lengths of
;;; the sides of P.
(defun side-length-sq-list (p first-pt)
"Returns a list of the squared lengths of the sides of P."
( if (null (rest p))
;; last side connects to 1st point...
(list (+ (sq (dx first-pt (first p)))
(sq (dy first-pt (first p))) ))
;; other sides connect successive points...
(cons (+ (sq (dx (second p) (first p)))
(sq (dy (second p) (first p))) )
(side-length-sq-list (rest p) first-pt) ) ) )
;;; SQ computes N squared.
(defun sq (n)
"Returns N squared."
(* n n) )
;;; DX, DY, PY, XC and YC are helping functions for
;;; coordinate arithmetic:
(defun dx (pt1 pt2) (- (xc pt1) (xc pt2)))
(defun dy (pt1 pt2) (- (yc pt1) (yc pt2)))
(defun py (pt1 pt2)
"Returns the sum of the y coords of PT1 and PT2."
(+ (yc pt1) (yc pt2)) )
(defun xc (pt) (first pt)) ; Get X coordinate of point.
(defun yc (pt) (second pt)); Get Y coordinate of point.
;;; declare global variables:
(defvar *reporting* nil)
(defvar *agenda* nil)
(defvar *concept-counter* nil)
;;; Let us now define the CONCEPT structure with 13 slots.
;;; In the following structure definition, we use several
;;; options, including specifying a special function
;;; for printing concepts, as well as default values for
;;; various slots.
(defstruct (concept (:print-function briefly-print-concept))
(id (new-atom))
defn
predicate
interest
(objects-to-try *universe*)
(examples-found nil)
(unused-predicates *predicates*)
predicates-used-in-spec
predicates-used-in-desc
(number-found 0)
(number-tried 0)
parent
subconcepts
)
;;; The following function is used during debugging.
;;; Without it, tracing a function that manipulates
;;; a concept leads to an infinite print recursion
;;; due to the fact that parent and child concepts
;;; cyclically reference each other.
(defun briefly-print-concept (c stream level)
"Provides a way to print out concepts without
infinite recursion."
(declare (ignore level))
;; Note (DECLARE (IGNORE LEVEL)) suppresses compiler warning.
(format stream "~A" (concept-id c)) )
;;; -----------------------------------------------------------------
;;; To find examples, the procedure FIND-EXAMPLES-OF takes the list of
;;; objects not yet tried and tries a fixed number of them, (3 of them).
;;; It puts any examples found on the list of examples for the concept,
;;; and it updates the list of objects left to try.
;;; This procedure also does the following:
;;; It updates the interest value for the concept in accordance to
;;; the results of looking for examples.
(defun find-examples-of (c)
"Tests objects for being examples of concept C, and updates
the agenda according to the findings."
(let ((objects-left (concept-objects-to-try c))
x)
;; Test 3 objects not yet tried as possible examples of C:
(dotimes (i 3 nil)
(if (null objects-left)
(return (setf (concept-objects-to-try c) nil)) )
(setf x (pop objects-left))
(incf (concept-number-tried c)) ; doesn't work in Allegro 4.1
(pop (concept-objects-to-try c))
(cond ((apply (concept-predicate c) (list (eval x)))
;; An example has been found...
(format t "~%~a is an example of concept ~a."
x (concept-id c))
(setf (concept-examples-found c)
(adjoin x (concept-examples-found c)) )
(incf (concept-number-found c)) )
;; But if the object is not an example...
(t (format t
"~%~a is not an example of concept ~a."
x (concept-id c) )) ) )
;; The example-checking part of this task is over.
;; Now update the interest value for C:
(setf (concept-interest c) (compute-concept-interest c))
;; If there are still objects not yet tried, enter a new
;; task on the agenda to try 3 more objects.
(if objects-left
(put-on-agenda
`(,(examples-task-interest c) ; Compute interest val.
(find-examples-of ,c) ) )
(if *reporting*
(format t "~%All objects now tested for ~a."
(concept-id c) ) ) )
(if *reporting* (display-agenda *agenda*))
;; If there is at least one example of the concept and no
;; specializations for this concept have yet been created,
;; and no tasks for such specialization are already on the
;; agenda, create a new task to make a specialization of C:
(if (and (> (concept-number-found c) 0)
(null (concept-subconcepts c))
(no-spec-task *agenda* (concept-id c)) )
(put-on-agenda
`(,(spec-task-interest c) ; Compute task interest.
(make-specialization ,c) ) ) )
;; Print out a current description of the concept:
(display-concept c)
;; The FIND-EXAMPLES-OF task has now been completed.
) )
;;; PUT-ON-AGENDA inserts an entry of the form
;;; (interest-value (task-spec)) onto the agenda,
;;; in its place, so that items are ordered, highest
;;; interest-value first.
(defun put-on-agenda (task)
"Inserts TASK in the priority queue *AGENDA*."
(setf *agenda* (put-on-agenda1 task *agenda*)) )
;;; PUT-ON-AGENDA1 is the recursive slave to put-on-agenda.
(defun put-on-agenda1 (task task-list)
"Handles the insertion of TASK in the right place."
(cond ((null task-list) (list task))
((< (first task) (caar task-list))
(cons (first task-list)
(put-on-agenda1 task (rest task-list)) ) )
(t (cons task task-list)) ) )
(defun no-spec-task (agenda c)
"Returns T if no MAKE-SPECIALIZATION task
with concept id C is on AGENDA."
(not
(member c agenda
:test
#'(lambda (x y)
(and (eql (caadr y) 'make-specialization)
(eql x (concept-id (cadadr y))) ) ) ) ) )
;;; CONCEPT-INTEREST computes the current interest value
;;; for concept C using a formula that involves the hit ratio.
(defun compute-concept-interest (c)
"Returns the INTEREST value for C using heuristic formula."
(if (zerop (concept-number-tried c))
(concept-interest (concept-parent c))
(let ((r (/ (concept-number-found c)
(concept-number-tried c) )))
(* 400.0 (- r (* r r))) ) ) )
;;; EXAMPLES-TASK-INTEREST computes the interest of a task to
;;; find examples of C as a weighted sum of the interests of
;;; C and it parent.
(defun examples-task-interest (c)
"Returns the interest value of a FIND-EXAMPLES-OF task."
(+ (* 0.8 (concept-interest (concept-parent c)))
(* 0.2 (concept-interest c)) ) )
;;; SPEC-TASK-INTEREST computes the interest value
;;; for a specialization task, according to the formula:
;;; value = 10 times the parent's hit ratio.
;;; We add one to the denominator to avoid the possibility
;;; of division by zero.
(defun spec-task-interest (c)
"Returns the interest value of a MAKE-SPECIALIZATION task."
(/ (* 10.0 (concept-number-found c))
(1+ (concept-number-tried c)) ) )
; -----------------------------------------------------------------
; A task of type MAKE-SPECIALIZATION requires that the system
; attempt to create a representation for a new concept.
; To create a new concept, the function MAKE-SPECIALIZATION
; creates a structure containing the following:
; - A unique id which is a symbol, e.g., C1, C2, etc.
; - A definition of the concept in terms of the parent concept.
; suitable for an explanatory printout.
; - A predicate that can be applied to any object
; to determine whether it is an example of this concept.
; - An interest value for the concept computed using a rule
; which takes into account the interest of the parent concept
; and the interest of the predicate used to form the restriction.
; - A list of objects that have not yet been tried as possible examples,
; initially the whole "UNIVERSE".
; - A list of examples found.
; - A list of the predicates NOT used in the definition of this concept -
; this simplifies the procedure MAKE-SPECIALIZATION.
; - A list of the original (provided) predicates that have been used
; along the path from OBJECT to this concept (used in DISPLAY-CONCEPTS).
; - A list of the predicates that have been used to create specializations
; of this concept.
; - The number of examples found (so far).
; - The number of objects tried (so far).
; - A parent concept.
; - A list of subconcepts.
; These items are put on the property list of the atom under the
; types: ID, DEFN, PREDICATE, INTEREST, OBJECTS-TO-TRY, EXAMPLES-FOUND,
; UNUSED-PREDICATES, PREDICATES-USED-IN-SPEC,
; PREDICATES-USED-IN-DESC, NUMBER-FOUND, NUMBER-TRIED,
; PARENT, SUBCONCEPTS.
(defun make-specialization (c)
"Performs a MAKE-SPECIALIZATION task."
(let ((pred
;; Select a predicate not already involved in the parent
;; and not already used for a specialization of C.
(select-pred (concept-unused-predicates c)
(concept-predicates-used-in-spec c) ))
newc)
(cond ((null pred)
(if *reporting*
(format t "~%Cannot further specialize concept ~a."
(concept-id c) ) )
(return-from make-specialization nil) ) )
;; Indicate that the selected predicate is no longer available
;; for other specializations of C:
(push pred (concept-predicates-used-in-spec c))
;; Create a new concept structure and make up a new ID...
(setf newc (make-concept))
;; Set up links in concept hierarchy...
(make-isa newc c)
;; Register the list of unused predicates for the new concept:
(setf (concept-unused-predicates newc)
(remove pred (concept-unused-predicates c)) )
;; Formulate the definition of NEWC:
(setf (concept-defn newc)
(append '(an instance of)(list (concept-id newc))
'(is a)(list (concept-id (concept-parent newc)))
'(having)(list pred)) )
;; Create the predicate which tests an object to see if
;; it is an example of the new concept:
(setf (concept-predicate newc)
(create-concept-predicate newc c pred) )
;; Store the list of predicates that should be used to
;; describe this concept:
(setf (concept-predicates-used-in-desc newc)
(cons pred (concept-predicates-used-in-desc c)) )
;; Set up a task to find examples of the new concept:
(put-on-agenda
;; interest = interest of parent concept.
`(,(concept-interest c)
(find-examples-of ,newc) ) )
;; Set up a task to make another specialization of C:
(put-on-agenda
`(,(spec-task-interest c)
(make-specialization ,c) ) )
) )
;;; SELECT-PRED returns a member of L1 - L2.
;;; Used to selects a predicate from those available
;;; and not yet used for specializing this concept.
(defun select-pred (l1 l2)
"Returns a member of L1 not in L2."
(let ((p (set-difference l1 l2)))
(if (null p) nil (first p)) ) )
;;; NEW-ATOM returns a new atom each time it is called,
;;; beginning with C1, then C2, etc.
(defun new-atom ()
"Returns a new symbol: first C1, then C2, etc."
(incf *concept-counter*)
(intern
(concatenate 'string
"C"
(prin1-to-string *concept-counter*) ) ) )
(defun create-concept-predicate (newc c pred)
"Creates a predicate which tests an object to see if
it is an example of the concept NEWC, using the
existing predicate for the parent concept C."
(let
((new-predicate
#'(lambda (obj)
(and (apply pred (list obj))
(apply (concept-predicate c) (list obj)) ) ) ))
;; If reporting is enabled, show predicate:
(if *reporting*
(format t "~%Creating concept ~a with predicate: ~s"
(concept-id newc) new-predicate) )
new-predicate) )
;;; EXPLORE-CONCEPTS contains the main control loop.
(defun explore-concepts ()
"Top-level loop for concept exploration."
(let (current-task)
(loop
(if (null *agenda*) (return))
;; Select task at head of agenda...
(setf current-task (second (first *agenda*)))
(pop *agenda*) ; Remove it from the agenda.
(funcall (first current-task)
(second current-task) ) ; Perform current task.
) ) )
;;; Functions for manipulating the concept hierarchy:
;;; ADD-SUBSET records the fact that X is a subset of Y.
(defun add-subset (x y)
"Inserts X into the list of subconcepts of Y."
(setf (concept-subconcepts y)
(adjoin x (concept-subconcepts y)) ) )
;;; ADD-SUPERSET records the fact that X is a superset of Y.
(defun add-superset (x y)
"Makes X be the parent of Y."
(setf (concept-parent y) x) )
;;; MAKE-ISA sets up a bi-directional ISA link between X and Y.
(defun make-isa (x y)
"Creates 2-way ISA hierarchy link."
(add-superset y x)
(add-subset x y) )
;-----------------------------------------------------------
;;; INITIALIZE sets up the concept hierarchy to contain a single
;;; concept, "OBJECT" from which specializations will be made.
(defvar *object*)
(setf *dummy* (make-concept :id 'dummy))
(defvar *dummy*)
(defun initialize ()
"Sets the concept OBJECT to the unprocessed state,
and initializes the agenda and counter for concepts."
(setf *object* (make-concept
:id 'object
:predicate
#'(lambda (x) (declare (ignore x)) t) ; always true.
;; Note (DECLARE (IGNORE X)) suppresses compiler warning.
:unused-predicates *predicates*
:predicates-used-in-spec nil
:interest 50) )
;; One of the interest-computing functions wants '*OBJECT* to
;; have a parent concept, so we set up a *DUMMY*:
(make-isa *object* *dummy*)
(setf (concept-interest *dummy*) 50)
;; Start the concept counter at 0:
(setf *concept-counter* 0)
;; Set up the initial agenda of tasks:
(setf *agenda*
`((50 (find-examples-of ,*object*))
(25 (make-specialization ,*object*)) ) ) )
;;; DISPLAY-CONCEPT prints out the status of a concept.
(defun display-concept (c)
"Prints parts of the description of concept C,
without any infinitely recursive printing."
(format t "~%~%Status of concept: ~a, " (concept-id c))
(format t "Definition: ~s."
(concept-defn c) )
(format t "~% Specialization of ~a."
(concept-id (concept-parent c)))
(format t
"~% Interest: ~7,3f; ~s examples found out of ~s tried."
(concept-interest c)
(concept-number-found c)
(concept-number-tried c) )
(format t "~% Examples found: ~s.~%"
(concept-examples-found c) ) )
(defun display-task (task)
"Prints out one agenda task, without any infinite recursion."
(format t "~% (~7,2f (~S ~S))"
(first task) (caadr task) (concept-id (cadadr task)) ) )
(defun display-agenda (agenda)
"Prints out the current agenda of tasks."
(format t "~%Agenda is: ( " )
(mapc #'display-task agenda)
(format t " ) ") )
;-----------------------------------------------------------
;;; TEST exercises the program.
(defun test ()
"Sets up and executes a run of the program."
(initialize) ; Set up initial concept OBJECT etc.
(setf *reporting* t) ; Display execution details.
(explore-concepts) ) ; Create and evaluate new concepts.
(test)