Some Notes and Solutions To Russell and Norvig's Artificial Intelligence: A Modern Approach (AIMA, 3rd Edition)
Some Notes and Solutions To Russell and Norvig's Artificial Intelligence: A Modern Approach (AIMA, 3rd Edition)
Peter Danenberg
Contents
1 DONE 1.1 5
2 DONE 1.2 6
3 DONE 1.3 6
4 DONE 1.4 7
5 DONE 1.5 7
6 DONE 1.6 8
7 DONE 1.7 8
8 DONE 1.8 8
9 DONE 1.9 9
10 DONE 1.10 9
11 DONE 1.11 9
12 DONE 1.12 10
13 DONE 1.13 10
1
14 DONE 1.14 10
15 DONE 1.15 11
16 DONE 2.1 12
17 DONE 2.2 13
17.1 DONE a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
17.2 DONE b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
17.3 DONE c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
18 DONE 2.3 14
19 DONE 2.4 14
19.1 Soccer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
19.2 Titan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
19.3 Shopping on the internet . . . . . . . . . . . . . . . . . . . . . 15
19.4 Playing a tennis match . . . . . . . . . . . . . . . . . . . . . . 15
19.5 Practicing tennis against a wall . . . . . . . . . . . . . . . . . 16
19.6 Performing a high jump . . . . . . . . . . . . . . . . . . . . . 16
19.7 Knitting a sweater . . . . . . . . . . . . . . . . . . . . . . . . 16
19.8 Bidding on an item . . . . . . . . . . . . . . . . . . . . . . . . 16
20 DONE 2.5 17
21 DONE 2.6 18
22 DONE 2.7 18
22.1 Goal-based agent . . . . . . . . . . . . . . . . . . . . . . . . . 18
22.2 Utility-based agent . . . . . . . . . . . . . . . . . . . . . . . . 19
23 DONE 2.8 19
24 DONE 2.9 26
25 DONE 2.10 27
25.1 DONE a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
25.2 DONE b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
25.3 DONE c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
26 DONE 2.11 29
26.1 DONE a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2
26.2 DONE b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
26.3 DONE c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
26.4 DONE d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
27 DONE 2.12 32
28 DONE 2.13 32
28.1 DONE a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
28.2 DONE b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
29 DONE 3.1 33
30 DONE 3.2 33
30.1 DONE a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
30.2 DONE b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
30.3 DONE c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
30.4 DONE d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
31 DONE 3.3 36
31.1 DONE a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
31.2 DONE b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
31.3 DONE c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
31.4 DONE d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
32 DONE 3.4 40
33 DONE 3.5 44
34 DONE 3.6 45
34.1 DONE a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
34.2 DONE b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
34.3 DONE c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
34.4 DONE d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
35 DONE 3.7 47
35.1 DONE a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
35.2 DONE b . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
35.3 DONE c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
35.4 DONE d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
36 TODO 3.8 51
3
37 DONE 3.9 51
38 TODO 3.22 54
39 Meetups 74
39.1 Mon Jun 11 2012 . . . . . . . . . . . . . . . . . . . . . . . . . 74
39.2 Mon Jun 18 2012 . . . . . . . . . . . . . . . . . . . . . . . . . 74
39.2.1 CANCELED Test a simple agent in each (Python,
Java, Clojure) implementation. . . . . . . . . . . . . . 75
39.2.2 DONE Get some standard cables to connect to the
projector. . . . . . . . . . . . . . . . . . . . . . . . . . 75
39.2.3 DONE See if we can use xrandr to get twin-view
with an external HDMI. . . . . . . . . . . . . . . . . . 75
39.3 Mon Jun 25 2012 . . . . . . . . . . . . . . . . . . . . . . . . . 75
39.3.1 Discussion . . . . . . . . . . . . . . . . . . . . . . . . . 75
39.3.2 TODO Get a minimal Clojure example up here. . . . 76
39.3.3 TODO Set up csrg.org with a mailing list. . . . . . 76
39.4 Mon Jul 2 2012 . . . . . . . . . . . . . . . . . . . . . . . . . . 76
39.5 Tue Jul 24 2012 . . . . . . . . . . . . . . . . . . . . . . . . . . 76
39.6 Mon Aug 6 2012 . . . . . . . . . . . . . . . . . . . . . . . . . 76
39.6.1 3.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
39.7 Mon Aug 27 2012 . . . . . . . . . . . . . . . . . . . . . . . . . 77
39.8 Mon Sep 10 2012 . . . . . . . . . . . . . . . . . . . . . . . . . 77
39.9 Sun Nov 4 2012 . . . . . . . . . . . . . . . . . . . . . . . . . . 78
40 Notes 78
40.1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
40.2 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
40.3 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
40.4 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
40.4.1 Hill Climbing . . . . . . . . . . . . . . . . . . . . . . . 98
40.4.2 Simulated annealing . . . . . . . . . . . . . . . . . . . 99
40.4.3 Local beam search . . . . . . . . . . . . . . . . . . . . 99
40.4.4 Genetic algorithms . . . . . . . . . . . . . . . . . . . . 100
40.4.5 Continuous spaces . . . . . . . . . . . . . . . . . . . . 101
40.4.6 Nondeterministic actions . . . . . . . . . . . . . . . . . 103
40.4.7 Partial observation . . . . . . . . . . . . . . . . . . . . 105
40.5 Lectures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
40.5.1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
40.5.2 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
4
40.6 Turing, Computing Machinery and Intelligence . . . . . . . . 115
41 TODOs 119
41.1 DONE 3.7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
41.2 DONE 3.7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
41.2.1 TODO Voronoi . . . . . . . . . . . . . . . . . . . . . 125
41.2.2 TODO Triangulation . . . . . . . . . . . . . . . . . . 125
41.2.3 TODO EMST . . . . . . . . . . . . . . . . . . . . . . 128
41.2.4 TODO Fortune’s algorithm . . . . . . . . . . . . . . . 128
41.2.5 TODO Plotting Voronoi . . . . . . . . . . . . . . . . 129
41.3 DONE 3.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
41.4 DONE Focus on one or two problems for the coming week. . 165
41.5 CANCELED Modify 2.11 to list available actions? . . . . . 165
41.6 TODO A listing environment . . . . . . . . . . . . . . . . . . 166
41.7 TODO Blog about barabási-albert vs. depth-first graph-
creation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
41.8 DONE 2.11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
41.8.1 DONE Graph world . . . . . . . . . . . . . . . . . . . 166
41.8.2 DONE Depth-first graph constructor . . . . . . . . . 179
41.8.3 DONE Allow specification of random-seeds . . . . . . 199
41.8.4 DONE Stateful graph agent . . . . . . . . . . . . . . 199
41.8.5 CANCELED make-equilibrium-limited-environment201
41.8.6 DONE Compare stateful and randomized reflex agents.201
41.8.7 DONE Figure out the correct aspect ratio for youtube.201
41.9 TODO Should we structure this somehow as a blog instead
of an org-doc? . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
41.10TODO Some sort of blog post or other publicity? . . . . . . 202
41.11TODO Find a reasonable pseudocode package in LATEX. . . . 202
41.12CANCELED Should we tangle to a bunch of text files? . . 202
41.13DONE Reimplement the Lisp environment in Scheme. . . . . 202
41.14DONE Personal notes as footnotes. . . . . . . . . . . . . . . 202
41.15CANCELED Should we try to release an e.g. Wumpus
World egg? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
1 DONE 1.1
5
Intelligence A spontaneous faculty for associating impressions (more gen-
eral than ideas); synthesizing abstractions from disparate stimuli; de-
ducing conclusions from abstractions.
Intelligence is an emergent property of simples like e.g. neurons.
Artificial intelligence Mechanism for performing association, abstrac-
tion, deduction which appears to be spontaneous; may also be an
emergent property of bit-pushing.
Agent Self-contained, autonomous input-processing mechanism.
Rationality The appropriate application of ����� or ratio; this includes the
mechanical process of deduction, as well as an ill-defined notion of
common-sense.
Logical reasoning The mechanical aspect of rationality.
2 DONE 1.2
3 DONE 1.3
6
Reflex actions are rational in the sense that they are the result of induction
on e.g. hot objects and the scientific method (see Turing); though the ac-
quisition may require intelligence (induction, storage), the reflex itself is not
intelligent in the sense that it requires no induction: it is immediate.
Reflex actions are not irrational, either, in the sense that someone does a
cost-benefit analysis and decides to contravene it; let’s call reflex actions
pararational, therefore: neither rational nor irrational. There’s no time to
apply a utility function and behave accordingly (or discordingly (sic)).
4 DONE 1.4
Tom Evan’s ANALOGY is an ad-hoc geometric solver, and would not there-
fore program. In people, you might be able to generalize from IQ-tests to
success; but not so with domain-specific AI.
5 DONE 1.5
7
That gives 106 memory updates per second; which means that a supercom-
puter houses the potential of 108 sea slugs.
6 DONE 1.6
7 DONE 1.7
8 DONE 1.8
8
Isn’t it the case that humans do do some kind of implicit calculation? An-
other example is the ability to catch a ball: there are complex physics at
play, and yet the human has evolutionarily honed and ad-hoc facilities to
perform the same.
Something like Gaussian blur, in other words, is hard-coded into our neu-
rons; vision system, on the other hand, don’t have the advantage of fuzzy
connections between analog neurons and have to simulate these biological
heuristics with algorithms.
9 DONE 1.9
10 DONE 1.10
11 DONE 1.11
9
“Surely computers . . . can do only what their programmers tell them”
might have been the case, if it weren’t for the fact that programmers can
program machines to do things even they couldn’t do (cf. chess programs
that outstrip their masters).1
This seems like a paradox I don’t adequately know how to explain; if it
proceeds apace, prepare for the singularity.
12 DONE 1.12
13 DONE 1.13
14 DONE 1.14
10
1. The Japanese got this one; just a toy, though.
2. There is at least one driverless car in Cairo; it’s not self-controlling,
though, but rather remotely driven. Driving in clusterfuck-Cairo (like
Athens) is taxing for humans, let alone AI. (Google’s making polit-
ical inroads in Nevada, though.) Sufficiently sensitive sensation of
surrounding objects, conditions; physics; navigation; are required.
3. DARPA Grand Challenge
4. This robot fetches a sandwich.
5. Grocery IQ will order groceries; a week’s worth, though?
6. Zia Mahmood got clowned once or twice; like poker, though, bridge is
probabilistic and psychological.
7. TheoryMine is selling new computer-generated proofs for £15; stan-
dard objections apply.
8. The Bulhak-Larios Postmodernism Generator is funny; intentionally
so?
9. Hilariously-named SHYSTER: ad-hoc expert system
10. Google Translate
11. Mechanically, but there is a human agent (telemanipulator); see this,
though, where “In May 2006 the first AI doctor-conducted unassisted
robotic surgery on a 34 year old male to correct heart arythmia.”
15 DONE 1.15
11
RoboCup is interesting in the sense that it requires advanced perception and
cooperation among autonomous agents; I suspect it does not detract much
from new ideas, despite the fact that it is still wrestling with some of the
oldest (and unsolved) problems in AI (vide supra).
The Loebner Prize, on the other hand, seems a little anachronistic; do people
care whether their AI counterparts really act human?
16 DONE 2.1
12
See page 38, by the way, where the authors talk about rationality in terms of
expected performance; could it be that an agent transcends T with respect
to expected performance?
Example: given a penalty for each move, a reflex agent’s expected perfor-
mance would be just as good as any other’s given T = 2; but not when T =
1000 (it would require a state-based agent to realize that the world is clean
and stop moving).
17 DONE 2.2
17.1 DONE a
17.2 DONE b
17.3 DONE c
13
18 DONE 2.3
19 DONE 2.4
19.1 Soccer
14
Environment Field
Actuators Kicking, thrwing, catching
Sensors Topology, ball, agents
Characteristics Fully observable, multiagent, stochastic, sequential, dy-
namic, continuous, known
19.2 Titan
15
19.5 Practicing tennis against a wall
16
Sensors See the artifact, understand the auctioneer
Characteristics Partially observable 4 , stochastic, sequential, dynamic,
continuous, known
20 DONE 2.5
17
21 DONE 2.6
22 DONE 2.7
- let
- state
- model
18
- goals
- action
- define (goal-based-agent percept)
- set! state (update-state state action percept model)
- let
# Shouldn't we distinguish between many different
action-sequences; and, if so, how to do so without a utility
function: evaluate them against the performance measure?
- action-sequence (search goals state)
- return (first action-sequence)
- let
- state
- model
- goals
- action
- define (utility-based-agent percept)
- set! state (update-state state action percept model)
- let
- probabilities (map probability goals)
- utilities (map utility goals)
- let
- expected-utilities (map * probabilities utilities)
- goal-of-maximum-expected-utility (max goals expected-utilities)
- action-sequence (search goal-of-maximum-expected-utility state)
- return (first action-sequence)
23 DONE 2.8
19
srfi-1
srfi-8
srfi-13
srfi-69
vector-lib)
(define (make-performance-measuring-environment
measure-performance
score-update!)
(lambda () (score-update! (measure-performance))))
20
(define clean #t)
(define clean? identity)
(define left 0)
(define left? zero?)
(define right 1)
(define right? (complement zero?))
(define-record vacuum-agent
location
score
program)
(define-record-printer vacuum-agent
(lambda (vacuum-agent output)
(format output
"#(agent ~a ~a)"
(if (left? (vacuum-agent-location vacuum-agent))
'left
'right)
(vacuum-agent-score vacuum-agent))))
21
((left) (vacuum-agent-location-set! agent left))
((right) (vacuum-agent-location-set! agent right))
((suck) (vacuum-world-location-set! world location clean))
(else (error (string-join
"make-vacuum-environment --"
"Unknown action")
action))))))
(define make-reflex-vacuum-agent
(case-lambda
((location)
(make-reflex-vacuum-agent location reflex-vacuum-agent-program))
((location program)
(make-vacuum-agent
location
0
program))))
(define simulate-vacuum
(case-lambda
((world agent) (simulate-vacuum world agent 1000))
((world agent steps)
(simulate
(compose-environments
22
(make-step-limited-environment steps)
(make-performance-measuring-environment
(make-vacuum-performance-measure world)
(make-vacuum-score-update! agent))
(make-debug-environment agent)
(make-debug-environment world vacuum-world-display)
(make-vacuum-environment world agent)))
(vacuum-agent-score agent))))
23
environment that directly communicates performance scores? In such cases,
we’d have to rely on the imperfect self-judgement of the agent; and attempt
to converge on rationality by internal coherence.
What I’m calling environments, incidentally, are now just functions: step-
functions, at that; and can be reduced by every.
Agent combinators are a little tough, though; the performance measure has
to be aware of the combined features. Can we use some kind of message-
passing mechanism?
What stops us, for instance, as modelling the agents as lambdas; too? Part
of the problem is the inversion of control: we’d have to pass a message to
the agent to store its score, as opposed to manipulating the score directly.
Every agent would be a dispatch-mechanism that would manage its own
meta-variables (including score and e.g. location) on the basis of messages.
Is it problematic, however, to have agents managing their own score? Could
we have an agent → score mapping in the environment itself? That way,
agents only maintain state according to its percepts.
Score, for instance, is not a percept in the vacuum world; location, however,
is. Agents, then, are functions with closures; functions which take as many
parameters as their percepts have components. The performance-measuring-
environment, therefore, maintains an agent->score table. Yes!
Problem is, though, that we’d have to break the nice contract we have:
environments are niladic lambdas. To maintain the performance measure
table, we’d have to receive the agent and the new score.
How to make the performance measure part of the environment, so that we
can relieve the agent from metadata?
By taking the metadata out of the agent, we have to maintain agent →
metadata mappings in the environment; this is kind of a pain in the ass.
By maintaining agents-as-lambda, we get a certain flexibility; on the other
hand, we shunt some complexity onto the environment: as it has to maintain
agent-metadata: score, location, &c.
Is this an acceptable tradeoff? The alternative, where I need to guess what
agents need (program, score, location) seems onerous; for some reason. In
practice, however, it may be simpler. We haven’t even solved the agent-
hashing-problem, for instance (wherein hashing fails if we mutate a field).
Can we hash closures?
24
I want to follow this environment-maintains-agent->metadata-mapping
thing and see how far it goes. (I see now why objects are interesting; clo-
sures, of course, do the same thing.)
If make-*-environment returned multiple values: the thunk followed by e.g.
agent->score, agent->location; you can ignore the latter values, if you
want to.
Or, we can demand that the user furnish them; better yet, we can give the
user the option of furnishing and ignoring them.
Also, shouldn’t we be able to name agents at some point? This would also
have to fall within an external data structure. Maybe the record solution
isn’t problematic if we create ad-hoc agents for each problem.
If we really need to decouple the program from the agent metadata (do we?),
one solution is to have an agent->metadata table in the environment; the
metadata would be a record containing location, score, name, &c.
This metadata table, on the other hand, would have to be passed to each
subenvironment for composition. Seems like a pain.
We found that, since environments consist of a step function, we could reduce
them to a lambda; let’s see if this continues to be the case. For the time
being, however, I think using agent-records is simplifying.
I wouldn’t mind agents being lambdas with closures; problem is: can’t ac-
cess the closure without some kind of message passing. (Message passing
simulates records.) We could possibly do it with some kind of multiple-
return-values hack, in which the subsequent values are ignored (the agent
effectively does a state dump every time its program is invoked). The prob-
lem with that is that I have to pass a percept in to access its state, or store
its state some other way.
To avoid namespacing everything (like e.g. vacuum-agent, &c.), I’d like to
have separate modules; that way, if we need to, we can import with a prefix.
For learning purposes, we should allow the student to specify no more than
the agent program; worry about all the bootstrapping on the back end.
We may have to copy worlds, incidentally, to compare how e.g. reflex- vs.
state-agents behave; thank goodness for vector-copy. (Copy by default?)
To give feedback to students, should have an e.g. environment-print
that we can pass around (this sort of function-passing, incidentally, is what
Norvig sought to avoid); environment-print might happen at every step
25
in e.g. simulate. Oh, make-debugging-environment.
24 DONE 2.9
(let ((worlds
(list (make-world clean clean)
(make-world clean clean)
(make-world clean dirty)
(make-world clean dirty)
(make-world dirty clean)
(make-world dirty clean)
(make-world dirty dirty)
(make-world dirty dirty)))
(agents
(list (make-reflex-agent left)
(make-reflex-agent right)
(make-reflex-agent left)
(make-reflex-agent right)
(make-reflex-agent left)
(make-reflex-agent right)
(make-reflex-agent left)
(make-reflex-agent right))))
(let* ((scores (map simulate-vacuum worlds agents))
(average-score (/ (apply + scores) 8)))
(test
"Scores for each configuration"
scores
'(2000 2000 1998 1999 1999 1998 1996 1996))
(test
"Average overall score"
1998.25
average-score)))
26
25 DONE 2.10
25.1 DONE a
(test
"Penalizing vacuum with reflex agent"
998
(simulate-penalizing-vacuum (make-world dirty dirty)
(make-reflex-agent left)))
The reflex agent would require state to determine that e.g. the world was
clean and that it didn’t need to move anymore.
25.2 DONE b
(debug? #f)
(define-record unknown)
27
(vector-every (lambda (location) (clean? location)) world))
(test
"Stateful agent in penalizing environment"
1995
(simulate-penalizing-vacuum
(make-world dirty dirty)
(make-reflex-agent
left
;; We could also make an initial pessimistic hypothesis of all-dirty.
(let ((world (make-world unknown unknown)))
(lambda (location clean?)
(if clean?
(begin
;; Extra work here every time; otherwise, we'd have an
;; extra `all-clean?' check after we set the state.
;; `vector-set!', I'd wager, is cheaper than
;; `all-clean?'.
(vector-set! world location clean)
(if (all-clean? world)
;; Symbols appropriate here, or should we have predefined
;; go-left, go-right, clean, do-nothing? We're message
;; passing, after all; I suppose a lambda wouldn't make any
;; sense?
;;
;; Can't be lambdas unless we redefine e.g. `go-right'
;; to penalize in the case of
;; `make-penalizing-environment'; better to keep as
;; symbols and dispatch, right? There should be some
;; sort of data-directed model we could use, though,
;; instead of the case-based dispatch.
'noop
(if (right? location)
'left
'right)))
'suck))))))
(test
"Stateful agent in penalizing environment (from the egg)"
1995
28
(simulate-penalizing-vacuum
(make-world dirty dirty)
(make-stateful-reflex-agent left)))
25.3 DONE c
26 DONE 2.11
26.1 DONE a
29
26.2 DONE b
26.3 DONE c
30
(start (random-start world)))
(let ((scores (list-tabulate
100
(lambda (i)
(let* ((world (copy-world world))
(agent (make-randomized-graph-agent start)))
(parameterize ((debug? #f))
(simulate-graph world agent))
(agent-score agent))))))
(/ (apply + scores) (length scores))))))
See the video.
26.4 DONE d
31
The basic algorithm is as follows:
1. Is the current location dirty? Clean it.
2. Otherwise, visit and clean (if necessary) all the current-location’s un-
visited neighbors.
3. If there are no unvisited neighbors for the current location, go back
the way we came.
4. If there are no unvisited (and uncleaned) locations, stop.
Traversing the world in this fashion is linearly complex.
27 DONE 2.12
28 DONE 2.13
28.1 DONE a
32
If the dirt sensor is wrong some percentage of the time, one would repeatedly
sense the status of the location until some significance criterion is reached.
For instance, it appears as though one would have to sense the status 17
times per location to achieve a confidence level of 95%.
28.2 DONE b
29 DONE 3.1
30 DONE 3.2
30.1 DONE a
33
Initial state Graph of one central node; northward orientation
Actions Turn east, west, north, south; move forward.
Transition model Moves in the currently oriented direction wherever a
wall does not intervene; where a wall intervenes, however, the robot
stays put.
Goal test Is the robot out of the maze?
Path cost None
The state space is infinite: you can reach any square, for instance, over
an unlimited number of noöps (e.g. pressing forward into a wall, turning
redundantly, &c.).5
Let’s say that redundant noöps have been pruned, however; and that the
graph, furthermore, doesn’t have any cycles: the state space is at least n,
corresponding to the number of nodes in the graph. It is at least 4n, though,
since the robot can be in any of the four orientations in each square. Let’s
say, furthermore, for every node ni there are mi unique paths (i.e. sequences
n0,1,...,i ) from the initial node n0 to ni : the state space for ni alone becomes
all the ways to fulfill each path in mi including unnecessary turns and false-
forwards into walls. Let the set of all fulfillments for a given mi be Mi .
The state space is the sum of Mi over the number of nodes in the graph;
plus the robots current position; plus the robot’s orientation.
30.2 DONE b
34
Goal test Is the robot out of the maze?
Path cost None
The state-space is still infinite, since the problem admits of redundant for-
wards at intersections; the pruned state space is smaller than 3.2a, however,
since the fulfillment of mi doesn’t involve unnecessary turns in corridors.
30.3 DONE c
30.4 DONE d
35
31 DONE 3.3
31.1 DONE a
31.2 DONE b
36
31.3 DONE c
37
Figure 1: Pathological completely connected graph
38
31.4 DONE d
39
32 DONE 3.4
40
Figure 4: Canonical 3-puzzle permutation (even)
Odd permutations (e.g. {2, 1, 3}) are an odd number of even cycles (e.g.
(1 2)) away from the canonical version (and cannot be reached from the
even canonical version):
41
Figure 5: Odd 3-puzzle permutation
Even permutations (e.g. {1, 2, 3}) are an even number of even cycles (e.g.
∅) from the canonical version:
42
Figure 6: Even 3-puzzle permutation
Similarly, this odd 8-puzzle is (6 7) away from the canonical even puzzle,
and can’t be reached:
43
Figure 7: Odd 8-puzzle permutation
It suffices, therefore, to factor the puzzle into disjoint cycles; and count those
cycles. If there are an odd number of them, the puzzle is odd; otherwise,
even.
33 DONE 3.5
44
Using the so-called Landauer limit on computation-energy, it has been ar-
gued that flipping through 2128 values is computationally infeasible; checking
2128 boards would require even more energy, but let’s take it as an upper
bound.
√
Since log2 ( 3 79!) ≈ 127.4, n = 79 is an upper bound on exhaustive explo-
ration (the actual n would be much less).
34 DONE 3.6
34.1 DONE a
45
34.2 DONE b
34.3 DONE c
34.4 DONE d
46
Initial state All three jugs are empty.
Actions Fill, empty into jug, empty onto ground.
Transition model Filling or emptying updates the water level of the cor-
responding jug(s).
Goal test Does any jug contain one gallon?
Is this reminiscent of Knuth’s conjecture?
35 DONE 3.7
35.1 DONE a
35.2 DONE b
35.3 DONE c
47
(define (search start
end
neighbors
make-frontier
frontier-add!
frontier-delete!
frontier-empty?)
(let ((visited (make-hash-table))
(frontier (make-frontier)))
(frontier-add! frontier (make-node start #f #f 0))
(let search ()
;; (debug frontier)
(if (frontier-empty? frontier)
(error "Search failed -- SEARCH")
(let* ((parent (frontier-delete! frontier))
(children (hash-table-ref neighbors (node-state parent))))
;; (debug parent)
(hash-table-set! visited (node-state parent) #f)
(if (eq? (node-state parent) end)
(reverse (predecessor-path parent))
(let ((unvisited-children
(filter (lambda (child)
(hash-table-ref/default
visited
child
#t))
children)))
;; (debug parent children unvisited-children)
(for-each
(lambda (child)
;; (debug child parent (node-state parent))
(let ((node (make-node child
parent
#f
(+ (node-path-cost parent)
(point-distance
child
(node-state parent))))))
(frontier-add! frontier node)))
unvisited-children)
48
(search))))))))
49
(define (greedy-best-first start end neighbors)
(best-first
start
end
neighbors
(make-straight-line-distance-heuristic end)))
50
"Greedy best-first"
"greedy-best-first.avi")
(plot-tessellation/animation
tessellation
depth-first
"Depth-first"
"depth-first.avi")
(plot-tessellation/animation
tessellation
breadth-first
"Breadth-first"
"breadth-first.avi")
(join-animations "search.avi"
"a-star.avi"
"uniform-cost.avi"
"greedy-best-first.avi"
"breadth-first.avi"
"depth-first.avi"))))
35.4 DONE d
36 TODO 3.8
37 DONE 3.9
51
defstruct
miscmacros
srfi-69
srfi-95)
(defstruct bank
missionaries
cannibals
boat?)
(defstruct transition
missionaries
cannibals)
(define transitions
(list (make-transition missionaries: 1
cannibals: 1)
(make-transition missionaries: 0
cannibals: 2)
(make-transition missionaries: 0
cannibals: 1)
(make-transition missionaries: 2
cannibals: 0)
(make-transition missionaries: 1
cannibals: 0)))
52
(update-transition
transition
missionaries: (- (transition-missionaries transition))
cannibals: (- (transition-cannibals transition)))
transition)))
(update-bank
bank
missionaries: (+ (bank-missionaries bank)
(transition-missionaries transition))
cannibals: (+ (bank-cannibals bank)
(transition-cannibals transition))
boat?: (not (bank-boat? bank)))))
transitions)))
(filter valid-substate? substates)))
53
(display "No solution found.")
(let ((state (queue-remove! frontier)))
(if (bank=? state end)
(begin
(display "Found solution!")
(let iter ((child end))
(debug (bank->alist child))
(let ((parent (hash-table-ref/default visited-states child #f)))
(if (not (root? parent)) (iter parent)))))
(let ((substates
(unvisited-states visited-states (substates state))))
(for-each (lambda (substate)
(queue-add! frontier substate)
(visit-state! visited-states substate state))
substates)
(iter))))))))
38 TODO 3.22
Need a graph of possible moves (initially hard-coded for 3x3?); and a Man-
hattan distance calculator. Can we have an abstract game-graph?
(use data-structures
debug
heap
stack)
(define-record node
;; @("Data structure for graphs"
;; (state "An indexable point")
;; (predecessor "The node-predecessor")
54
;; (action "Not used")
;; (path-cost "Cost of the path up to this point"))
state
predecessor
action
path-cost)
55
(if (goal? (node-state predecessor))
(reverse (predecessor-path predecessor))
(let ((unvisited-successors
(filter (lambda (successor)
(hash-table-ref/default
visited
successor
#t))
successors)))
;; (debug predecessor successors unvisited-successors)
(for-each
(lambda (successor)
;; (debug successor predecessor (node-state predecessor))
(let ((node (make-node successor
predecessor
#f
(+ (node-path-cost predecessor)
;; Should this be
;; between two
;; nodes?
(step-cost
successor
(node-state predecessor))))))
(frontier-add! frontier node)))
unvisited-successors)
(search))))))))
56
step-cost
goal?
make-queue
queue-add!
queue-remove!
queue-empty?))
57
(define (A* start successors step-cost goal? heuristic)
(search-best-first start
successors
step-cost
goal?
(lambda (node) (+ (node-path-cost node)
(heuristic node)))))
58
(let* ((best (heap-extremum heap))
(best-path-cost (node-path-cost best)))
(debug heap
(heap-size heap)
(node-state (heap-extremum heap)))
(if (> best-path-cost f-limit)
(values #f best-path-cost)
(let ((alternative
(if (= (heap-size heap) 1)
+inf
(let ((first
(heap-extract-extremum! heap))
(alternative
(node-path-cost
(heap-extremum heap))))
(debug heap
(heap-size heap)
(node-state (heap-extremum heap))
(node-path-cost first)
first
(node-state first)
'iter
(heap-member? heap first))
(heap-delete! heap first)
(debug 'iter (heap-member? heap first))
(heap-insert!
heap
(node-path-cost first)
first)
alternative))))
(receive (predecessor best-path-cost)
(search return
best
(min f-limit alternative))
(if predecessor
(values predecessor best-path-cost)
(iter))))))))))))
(call/cc (lambda (return)
(search return (make-initial-node start) +inf))))
59
(define-record-and-printer point x y)
(include "search.scm")
(define blank 0)
(define blank? zero?)
60
(define (right? k i) (zero? (modulo (+ i 1) k)))
(define (moves k i)
(let ((up (and (not (top? k i)) (up k i)))
(down (and (not (bottom? k i)) (down k i)))
(left (and (not (left? k i)) (left k i)))
(right (and (not (right? k i)) (right k i))))
(filter values (list up down left right))))
(define (manhattan-distance k i j)
(+ (abs (- (row k j) (row k i)))
(abs (- (col k j) (col k i)))))
(define (make-manhattan-distance-heuristic k)
(lambda (node)
(vector-fold (lambda (i distance tile)
(+ distance
(manhattan-distance k i tile)))
0
(game-state (node-state node)))))
(define (n-puzzle k)
(let ((game (vector-unfold (lambda (i) (moves k i)) (* k k))))
game))
61
(define (make-state k)
(list->vector
(cons blank (iota (- (* k k) 1) 1))))
(define (inversions v)
(let ((inversions (make-hash-table)))
(vector-for-each
(lambda (i x)
(do ((j i (+ j 1)))
((= j (vector-length v)))
(when (and (> x (vector-ref v j))
(not (zero? (vector-ref v j))))
(hash-table-update!/default inversions x add1 0))))
v)
(hash-table-fold
inversions
(lambda (x inversions total) (+ inversions total))
0)))
(define (make-random-game k)
(do ((state (make-state k))
(i (- (* k k) 1) (- i 1)))
((< i 1) (make-game state (vector-index blank? state)))
(let ((j (random (+ i 1))))
(vector-swap! state i j))))
62
(define (make-random-soluble-game k)
(do ((game (make-random-game k) (make-random-game k)))
((soluble? k game) game)))
(define (make-start-game k)
(make-game (make-state k) 0))
(define (ditaa-boundary k)
(string-intersperse (make-list (+ k 1) "+") "---"))
(define (ditaa-blank-row k)
(ditaa-row (make-vector k blank)))
63
(let ((boundary (ditaa-boundary k))
(blank-row (ditaa-blank-row k)))
(display boundary)
(newline)
(do ((i 0 (+ i k)))
((= i (* k k)))
(display blank-row)
(newline)
(display (make-ditaa-row state k i))
(newline)
(display blank-row)
(newline)
(display boundary)
(newline))))
64
(animation-filename
directory
(+ i (length states)))))
directory))
(let* ((k 3)
#;(states (solve k (make-random-soluble-game k)))
)
;; (debug states)
(debug
(let ((start (make-start-game k)))
(search-recursive-best-first
(make-random-soluble-game k)
(lambda (parent)
(reachable-games k (node-state parent)))
(constantly 1)
(lambda (game) (game= start game))
(make-manhattan-distance-heuristic k)))))
65
blank? could just as well be zero?, which would aid in testing vector-
equality (could use e.g. (vector= = g1 g2).
Animated ditaa for visualization?
(use debug
vector-lib)
66
(vector-for-each (lambda (i x) (hash-table-set! x->i x i)) v2)
(let iter ((i 0)
(distance 0))
(if (or (= i (vector-length v1))
(= i (vector-length v2)))
distance
(let ((x1 (vector-ref v1 i))
(x2 (vector-ref v2 i)))
(if (= x1 x2)
(iter (+ i 1) distance)
(let ((i2 (hash-table-ref x->i x1)))
(vector-swap! v1 i i2)
(iter (+ i 1) (+ distance 1))))))))
(error "Vectors incomparable -- KENDALL-TAU-DISTANCE")))
(test 3
(kendall-tau-distance (vector 1 2 3 4 5)
(vector 3 4 5 2 1)))
(debug (kendall-tau-distance
(vector 1 2 3 4 5 6 7 8 9 10 11 12 13 15 14 0)
(vector 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0)))
(debug (kendall-tau-distance
(vector 1 2 3 4 5 6 7 8 9 10 11 12 13 15 14 0)
(vector 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 15)))
(define (contiguize! k v)
(do ((i k (+ i (* 2 k))))
((>= i (vector-length v)))
(vector-reverse! v i (+ i k))))
(define (contiguize k v)
(let ((v (vector-copy v)))
(contiguize! k v)
v))
(debug (kendall-tau-distance
(contiguize 4 (vector 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0))
(contiguize 4 (vector 1 2 3 4 5 6 7 8 9 10 11 12 13 15 14 0))))
67
(debug (kendall-tau-distance
(contiguize 4 (vector 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0))
(contiguize 4 (vector 1 2 3 4 5 6 7 8 9 10 11 0 13 14 15 12))))
(use debug test vector-lib)
(define (contiguize! k v)
(do ((i k (+ i (* 2 k))))
((>= i (vector-length v)))
(vector-reverse! v i (+ i k))))
(define (cycles v)
(let ((visited (make-hash-table)))
(vector-for-each
(lambda (i x)
(debug i x)
(let iter ((next i)
(length 0))
(debug next length)
(if (unvisited? visited next)
(begin
(hash-table-set! visited next #f)
(iter x
(+ length 1)))
(debug length))))
v)))
68
(define (cycles v)
(let ((visited (make-hash-table)))
(let iter ((i 0)
(even-cycles 0))
(if (= i (vector-length v))
even-cycles
(let iter-cycle ((next i)
(length 0))
(debug next length)
(if (unvisited? visited next)
(begin
(hash-table-set! visited next #f)
(iter-cycle (vector-ref v next) (+ length 1)))
(if (even? length)
(iter (+ i 1) (+ even-cycles 1))
(iter (+ i 1) even-cycles))))))))
n <- 16
## p <- sample(1:n)
## p <- c(0,1,2,5,4,3,7,6,8)
p <- c(1,2,3,4,5,6,7,8,9,10,11,12,13,15,14,0)
p <- c(1,2,3,4,5,6,7,8,9,10,11,12,13,15,14)
p <- c(1,2,3,4,8,7,6,5,9,10,11,12,15,14,13)
p <- c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0)
69
p <- c(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)
x <- integer(n)
print(p)
for (i in 1:n) {
## debug(x)
z <- which(!x)[1]
print(x)
debug("outer", i, z)
if (is.na(z)) break
repeat {
debug("inner", i, z, x[z], p[z], x[p[z]])
x[z] <- i
z <- p[z]
if (x[z]) break
}
}
x
clen <- table(x)
clen
1L - (sum(clen %% 2 == 0) %% 2)*2L
(use debug
miscmacros
srfi-1
srfi-69
vector-lib)
(define (cycles v)
(let ((seen (make-vector (vector-length v) #f)))
(let iter ((i 0)
(cycles '()))
(debug seen)
(if (= i (vector-length v))
(begin
(debug cycles)
(length (filter even? cycles)))
(let iter-cycle ((j i)
(length 0))
(debug j length)
(if (vector-ref seen j)
70
(iter (+ i 1)
(cons length cycles))
(begin
(vector-set! seen j #t)
(iter-cycle (vector-ref v j)
(+ length 1)))))))))
(define (cycles v)
(let ((seen (make-vector (vector-length v) 0)))
(do ((i 1 (+ i 1)))
((> i (vector-length v)))
(let ((z (vector-index zero? seen)))
(debug z)
(when z
(while (zero? (vector-ref seen z))
(debug z)
(vector-set! seen z i)
(set! z (vector-ref v z))))))
seen))
(define (cycles v)
(let ((cycles (make-hash-table))
(seen (make-vector (vector-length v) #f)))
(do ((i 0 (+ i 1)))
((= i (vector-length v)))
(let iter ((j i))
(unless (vector-ref seen j)
(vector-set! seen j #t)
(hash-table-update!/default
cycles
i
(lambda (length) (+ length 1))
0)
(iter (vector-ref v j)))))
(hash-table->alist cycles)))
71
(cycles '#(13 10 11 6 5 7 4 8 1 12 14 9 3 15 2 0))
(cycles '#(6 1 10 2 7 11 4 14 5 0 9 15 8 12 13 3))
http://stackoverflow.com/questions/7701345/how-to-programatically-create-
a-valid-15-puzzle-in-code http://mathworld.wolfram.com/15Puzzle.html http://www.cs.bham.ac.uk/m
http://kevingong.com/Math/SixteenPuzzle.html
(use debug
srfi-69
test
vector-lib)
(define (inversions v)
(let ((inversions (make-hash-table)))
(vector-for-each
(lambda (i x)
(do ((j i (+ j 1)))
((= j (vector-length v)))
;; (debug i j x (vector-ref v j) (> x (vector-ref v j)))
(when (and (> x (vector-ref v j)) (positive? (vector-ref v j)))
(hash-table-update!/default inversions x add1 0))))
v)
(hash-table-fold
inversions
(lambda (x inversions total) (+ inversions total))
0)))
(test-assert
(not (soluble? '#(1 2 3 4 5 6 7 8 9 10 11 12 13 15 14 0))))
(test-assert
(soluble? '#(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0)))
(test-assert
(not (soluble? '#(2 1 3 4 5 6 7 8 9 10 11 12 13 14 15 0))))
(test-assert
(soluble? '#(6 1 10 2 7 11 4 14 5 0 9 15 8 12 13 3)))
(test-assert
(not (soluble? '#(13 10 11 6 5 7 4 8 1 12 14 9 3 15 2 0))))
72
(test-assert
(soluble? '#(1 2 3 0)))
(test-assert
(not (soluble? '#(2 1 3 0))))
(test-assert
(soluble? '#(1 2 0 3)))
(test-assert
(soluble? '#(0 2 1 3)))
73
39 Meetups
• David has the international version, which would have you write an
essay on evolution and autonomy; see e.g. Turing on child AI:
We have thus divided our problem into two parts. The and
child-programme the education process. These two remain
very closely connected. We cannot expect to find a good
child-machine at the first attempt. One must experiment
with teaching one such machine and see how well it learns.
One can then try another and see if it is better or worse.
There is an obvious connection between this process and
evolution, by the identifications
Structure of the child machine Hereditary material
Changes Mutations
Natural selection Judgment of the experimenter
One may hope, however, that this process will be more ex-
peditious than evolution. The survival of the fittest is a
slow method for measuring advantages. The experimenter,
by the exercise of intelligence, should be able to speed it up.
Equally important is the fact that he is not restricted to
random mutations. If he can trace a cause for some weak-
ness he can probably think of the kind of mutation which
will improve it.
74
39.2.1 CANCELED Test a simple agent in each (Python, Java,
Clojure) implementation.
39.3.1 Discussion
• 2.1
Change performance measure; utility-based agent aware of its own per-
formance measure: can react accordingly? Not a reflex agent, though,
that’s pre-programmed.
Example: given a penalty for each move, a reflex agent’s expected
performance would be just as good as any other’s given T = 2; but
not when T = 1000 (it would require a state-based agent to realize
that the world is clean and stop moving).
• 2.2
c Memory, motor, bump sensor (or penalty); learn geography, prob-
ability of becoming dirty. Clustering algorithm: centers of mass
for dirt.
• 2.3
75
a Best action given available information.
c One-square, not dirty.
d Page 51 (program) vs. page 36 (function).
f See c above.
g Stochastic vs. deterministic vacuum world: reflex agents are still
rational. Performance measure still the same.
i Even an omniscient poker player is subject to luck.
39.6.1 3.1
Stock example:
Initial state One P&G share
Actions available Hold and sell
76
Transition model Hold, retain the share; sell, lose the share
Goal test P&G is up 10%.
Path cost Cost of selling: dividends, &c.
Simple agent, given historical data, might determine that, in n days time,
P&G is up; then blindly executes n − 1 holds followed by one buy.
How to apply to crossfit?
One dimensional n-square puzzles cannot move even within even or odd
permutations.
This is even:
[ 1 2 3 Ø ]
and this is also even:
[ 3 1 2 Ø ]
However, the latter state is unreachable from the former.
Does three dimensions allow us to switch between even and odd sets just
like one dimension took that away (vis à vis degrees of freedom)? Jury is
out; make the game graph, search.
Three-dimensional cube:
[ 2 1 ]
[ 3 4 ]
[ 7 Ø ]
[ 6 5 ]
Using the triangle inequality, a straight line between S and E that is inter-
rupted by a convex polygon, must have a shortest path along the vertices
of the interrupting polygon?
77
39.9 Sun Nov 4 2012
40 Notes
40.1 1
78
• The world is an extremely large problem instance.
• Models based on satisficing—making decisions that are “good enough”—
gave a better description of actual human behavior.
• Searle: brains cause minds.
• Behaviorism
• “A cognitive theory should be like a computer program.”
• Intelligence and an artifact
• Parallelism—a curious convergence with the properties of the brain.
• The state of a neuron was conceived of as “factually equivalent to a
proposition which proposed its adequate stimulus.” McCulloch and
Pitts (1943)
– Neural events and the relations among them can be treated by
means of propositional logic.
– For any logical expression satisfying certain conditions, one can
find a net behaving in the fashion it describes.
– For every net behaving under one assumption, there exists an-
other net which behaves under the other and gives the same re-
sults.
• Perhaps “computational rationality” would have been more precise
and less threatening, but “AI” stuck.
• AI from the start embraced the idea of duplicating human faculties
such as creativity.
• John McCarthy referred to this period as the “Look, Ma, no hands!”
era.
• “A physical symbol system has the necessary and sufficient means for
general intelligent action.”
• 1958 . . . McCarthy define Lisp, which was to become the dominant
AI programming language for the next 30 years.
• It is useful to have a formal, explicit representation of the world and
its workings and to be able to maniplutae that representation with
deductive processes.
• McCarthy, Programs with Common Sense
79
– In this program the procedures will be described as much as pos-
sible in the language itself and, in particular, the heuristics are
all so described.
– If one wants a machine to be able to discover an abstraction, it
seems most likely that the machine must be able to represent this
abstraction in some relatively simple way.
– The improving mechanism should be improvable.
– Must have or evolve concepts of partial success.
* Something about ˜1995 that made for a cute blog.
– For example, to mest people, the number 3812 is not an object:
they have nothing to say about it except what can be deduced
from its structure. On the other hand, to most Americans the
number 1776 is an object because they have filed somewhere the
fact that it represents the year when the American Revolution
started.
– One might conjecture that division in man between conscious and
unconscious thought occurs at the boundary between stimulus-
response heuristics which do not have to be reasoned about but
only obeyed, and the others which have to serve as premises in
deductions.
• Machine evolution (genetic algorithms): Friedberg, 1958, 1959.
– Friedberg. 1958. A learning machine Part 1. IBM Journal of
Research and Development, 2, 2–13.
* From and intent, to be sure, are related quite discontinu-
ously in the compact, economical programs that program-
mers wrte.
– Friedberg, Dunham, North. 1959. A learning machine, Part 2.
IBM Journal of Research and Development, 3, 282–287.
• Failure to come to grips with the “combinatorial explosion”
• The new back-propagation learning algorithms for multilayer netwrks
that were to cause an enormous resurgence in neural-net research in
the late 1980s were actually discovered first in 1969.
• Bruce Buchanan: a philosopher turned computer scientist
80
• DENDRAL was the first successful knowledge-intensive system (expert
system).
• AI Winter
• Parallel Distributed Processing (Rumelhart, McClelland. 1986)
• Connectionist models: competitors to symbols models and logicist ap-
proach
• Ones that act rationally according to the laws of decision theory and
do not try to imitate the thought steps of human experts
• Control theory deals with designing devices that act optimally on the
basis of feedback from the environment.
40.2 2
• Rational agents
• Agents behaves as well as possible (utility function?)
• Agent perceives its environment through sensors and acts through ac-
tuators.
– Hands are actuators and sensors.
• Percept :: agent’s perceptual inputs at any given instant
• Agent’s choice depends on percept sequence to date.
• Agent function :: maps percept sequence to action.
• External characterization of agent (agent function): table mapping
percept sequences to actions; internally: agent program.
• In a sense, all areas of engineering can be seen as designing artifacts
that intaract with the world.
– Trivializing agents to view e.g. calculators as such.
• Intelligent agents, on the other hand: non-trivial decision making.
• Rational agents: does the right thing (utility).
• Performance measure
– (This all sounds reminiscent of Mitchell, by the way.)
• Sequence of actions causes the environment to go through states: en-
vironmental states are distinct from agent states.
81
– Basing performance merely off of agent-states is a form of coher-
entism.
• Design performance measures according to what one actually wants in
the environment.
• “We leave these question as an exercise for the diligent reader.”
– Classic.
• Rationality: performance measure, agent’s prior (i.e. a priori) knowl-
edge, agent’s actions, agent’s percept sequence.9
– “Percept,” it turns out, is the converse of “concept”: “A Percept
or Intuition is a single representation . . . a Concept is a collective
(general or universal) representation of a whole class of things.”
(F. C. Bowen Treat. Logic)
• For each percept sequence, a rational agent should select an action that
is expected to maximize its performance measure, given its percept
sequence and a priori knowledge.
• Omniscience vs. rationality
• Rationality maximizes expected performance; perfection, actual per-
formance.
• Our definition of rationality does not require omniscience.
– It’s possible sometimes, by the way, to detect transitions in au-
thorship.
• Information gathering: actions in order to modify future percepts.
• a priori rather than percepts: lacks autonomy.
• Ration agent: autonomous; boostrap with a priori, though.
• Just as evolution provides animals with built-in reflexes to survive long
enough to learn for themselves
• Task environments
• PEAS :: Performance, Environment, Actuators, Sensors
– Mitchell has: task, performance measure, training experience,
target function, target function representation.
• Fully observable vs. partially observable environment.
9
Bizarre to me that a programmer is responsible for the a priori; playing god, anyone?
82
• Task environment effectively fully observable if the sensors detect all
aspects that are relevant to the choice of action, performance measure.
• Single agent vs. multiagent
• Entity may vs. must be viewed as an agent.
• Competitive vs. cooperative multiagent environment
• Communication
• In some competitive environments, randomized behavior is rational
because it avoids predictability.
• Deterministic vs. stochastic environment
• “Uncertain” environment: not fully observable or not deterministic
• Stochastic: uncertainty about outcomes quantified in terms of proba-
bilities; nondeterministic: actions characterized by possible outcomes,
no probabilities attached.
• Episodal vs. sequential: atomic episodes: receives percept and per-
forms single action; sequential: current decision affect all future deci-
sions.
• Static vs. dynamic: environment change while agent is deliberating.
• Discrete vs. continuous: state of the environment, time, percepts,
actions.
• Known vs. unknown: “laws of physics” of the environment
• Hardest: partially observable, multiagent, stochastic, sequential, dy-
namic, continuous, unknown.
• Code repository includes environment simulator that places one or
more agents in a simulated environment, observes their behavior over
time, evaluates them according to a given performance measure.
– Shit: this is something we could implement in Scheme (java,
python, lisp, data); lot of work, though? Glory?
* A lot of the utilities are in SRFI-1; e.g. transpose is zip.
* Infinity is there.
* Might have to write rms-error; ms-error.
* sample-with-replacement; sample-without-replacement
· Combinatorics SRFI, anyone?
83
* fuzz
* print-grid, &c.
* the-biggest, the-biggest-random-tie, the-biggest-that,
&c.
* Binary tree stuff
* Queue
* Heap
* They did CLOS-like stuff
– Damn, they put some work in; could do it incrementally? Will
be ad-hoc, I guarantee it.
– Maybe we can program it, given the agents API.
* run-environment looks like something central.
– What would happen if we merely translated the code to Chicken?
Could do so, perhaps, without fully understanding it; write an
idiomatically Scheme-port later.
– In that case, find some alternative to CLOS; or use tinyCLOS?
– Also, beginning to think that we misnamed our repo: we’re calling
it aima, but we’d like to write an aima egg; with aima-agents,
aima-search, aima-logic, aima-planning, aima-uncertainty,
aima-learning, aima-language modules.
* Call it aima-egg? aima-chicken?
– Translation seems like the way to go: relatively mechanical.
– Incidentally, “We need a new language for logical expressions,
since we don’t have all the nice characters (like upside-down A)
that we would like to use.” We can use � in Scheme, can’t we?
Sure. Tough to type? Maybe. Also, think font-lock.
– May not be up-to-date for 3e; let’s see; also, rife with defmethod
and other OOisms. Can ignore it, possibly, and its type-checking;
defstructure is similar, I think, to SRFI-9.
– Damn, they implemented unification.
– Not to mention: the learning stuff (e.g. decision trees).
– Man, should we implement this stuff ad-hoc; or otherwise depend
on the existing implementations?
84
– Path of least resistance: do it in Allegro? Ouch.
• The job of AI is to design an agent program that implements the agent
function, the mapping from percepts to actions.
• Agent = architecture + program
• The agent program takes the current percept as input; the agent func-
tion, which takes the entire percept history.
• The agent function that the program embodies
• Write programs that produce rational behavior from a smallish pro-
gram rather than a vast table.
• Reflex agents; model-based reflex agents; goal-based agents; utility-
based agents.
• - table-driven-agent percept
- persistent
- percepts ()
- table
- append percept to percepts
- lookup percepts table
• - reflex-vacuum-agent location status
- if dirty? status
- suck
- else if location = A
- right
- else
- left
• Simple reflex agent: select actions on the basis of the current precept
– Learned responses, innate reflexes
– - simple-reflex-agent percept
- persistent
- rules
- state = interpret-input(percept)
- rule = rule-match(state rules)
- rule.action
– Works only if the correct decision can be made on the current
percept: i.e. if the environment is fully observable.
85
– Escape from infinite loops is possible if the agent can randomize
its actions.
– In single-agent environments, randomization is usually not ratio-
nal.
– In most cases we can do better with more sophisticated deter-
ministic agents.
• Model-based reflex agents
– Partial observability: keep track of the part of the world it can’t
see now.
– Internal state
– Knowledge of how world evolves independently from agent
– Knowledge of actions affect the world
– Model of the world
– - model-based-reflex-agent percept
- persistent
- state
- model
- rules
- action
- state = update-state(state action percept model)
- rule = rule-match(state)
- (action rule)
– State of the world can contain goals.
• Goal-based agents
– In addition to current state, goal information that describes sit-
uations that are desirable
– Search, planning
– Flexible, reason about world vis à vis goals
• Utility-based agents
– Whereas goals are happy/unhappy, more general performance
measure: utility
– Utility functional: internalization of performance measure
86
– This is not the only way to be rational: rational agent for vacuum
has no idea what its utility function is (see exercise 1.3).
– When there are conflicting goals, utility function opts for the
appropriate tradeoff.
– Partial observability, stochasticity: decision making under uncer-
tainty.
– Expected utility of the action outcomes: derive, given the prob-
abilities and utilities of each outcome.
– Any rational agent must behave as if it possesses a utility func-
tion.
– An agent that possesses an explicit utility function can make
rational decisions with a general-purpose algorithm that does not
depend on the specific utility function being maximized.
– Global rationality: local constraint on rational-agent designs.
– Choosing utility-maximizing course of action
• Learning agents
– Now we’re getting into some Mitchell-action: critic, learning ele-
ment, performance element, problem generator, &c.
* Need a book on big data?
– Operate in initially unknown environments and become more
competent.
– Learning element
* Making improvements
– Performance element
* Selecting external actions
– Critic
* How performance element should be modified vis à vis fixed
performance standard.
* Performance standard must be fixed (i.e. checkmate).
* Performance standard outside agent; conforms thereto.
– Problem generator
87
* Suggesting actions that will lead to new and informative ex-
periences.
* Suboptimal actions short run, better actions long run.
– Utility-based agents learning utility information
– Performance standard distinguishes percept as reward or penalty.
– Process of modification of each component
• Atomic, factored, structured environments
– Atomic
* Each state of the world indivisible
– Factored
* Variables, attributes
– Structured
* Objects and relationships can be described explicitly
– Increasing expressiveness
– Intelligent systems: operate at all points along the expressiveness-
axis simultaneously.
• Agents perceives and acts; agent function maps percept seq -> action.
• Performance measure evaluates behavior.
• Maximize expected performance measure.
• Task environment: performance measure, external environment, actu-
ators, sensors.
• Nicomachean Ethics
• McCarthy, Programs with Common Sense
• Newell and Simon, Human Problem Solving
• Horvitz suggests the use of rationality conceived as the maximization
of expected utility as the basis for AI. Pearl, 1988.10
– Horvitz, E., 1988. Reasoning Under Varying and Uncertain Re-
source Constraints, Proc. of the 7th National Conference on AI,
Minneapolis, MN, Morgan Kauffman, pp:111-116.
10
Which of these following papers do you think he’s talking about? Probably the latter:
it carries an et al.
88
– Horvitz, E. J., Breese, J.S., Henrion, M. 1988. Decision The- ory
in Expert Systems and Artificial Intelligence, Journal of Approx-
imate Reasoning, 2, pp247-302.
40.3 3
89
• Toy problems: sliding block, n-queens
• N-queens: no path cost; incremental vs. complete-state
• Knuth conjecture: factorial, square root and floor reach any desired
positive integer.
• Protein design
• Search tree: branches are actions, nodes: states
• Leaf nodes, frontier
• Expanding nodes on frontier, until solution (or not)
• State space: finite; search tree: infinite (loopy)
• Frontier separates the state-space graph into the explored region and
the unexplored region.
• Node: state, parent, action (whence), path-cost (cumulative)
– (They’re getting implementationy.)
– (Seem to be talking about breadth-first search here: there’s a
queue.)
• solution function: following parents up to root
- child-node(problem, parent, action) -> node
- let
- state (problem-result (node-state parent) action)
- parent parent
- action action
- path-cose (+ (path-cost parent) (step-cost problem (node-state
parent) action)
• (Oh, never mind: queues are supersets of stacks, apparently; LIFOs
queues being the latter. Also, priority queues.)
• Canonical form: bit-vector, sorted list (keys for hash-tables)
• Completeness, optimality, time complexity, space complexity
• In AI, graph represented implicitly by initial state, actions, transition
model; frequently infinite.
• Complexity:
b branching factor (maximum successors)
d depth
90
m maximum path in state space
• (State space: finally.)
• Search cost
• Total cost: search cost + path cost
• Uninformed search (blind search)
• Order in which nodes are expanded: informed search, heuristic search
• Breadth-first: root and successors expanded first
– FIFO queue
– Goal-test applied to each node when it is generated
– Breadth first expands the shallowest nodes
• Uniform-cost search: expands n with lowest path-cost
– (Greedy algorithm? Apparently not, since the goal-test is applied
at expansion.)
– Frontier: priority queue ordered by g
– Goal-test applied when node selected for expansion; goal node
that is generated may be on a suboptimal path
– Second test added, in case a better path is found to a node cur-
rently on the frontier. (Where is it in the algorithm?)
– Properties of priority queue and hash-table
– (There are these negative proofs.)
– Completeness is guaranteed, provided the cost of every step ex-
ceeds some small positive constant ϵ.
* (ϵ and floats)
– C ∗: optimal value for C.
– Uniform-cost does more work than breadth-first, expanding nodes
even after finding a solution.
• Depth-first search
– Expands deepest node in current frontier.
– LIFO
– In infinite state spaces, fails if infinite non-goal path encountered.
91
– Depth-first search not optimal.
* (Really? Interesting; depends, of course, on the order of
nodes selected.)
– m itself can be much larger than d; infinite, if tree unbounded.
– Space complexity: for a graph search, no advantage; for a tree
search: need store only a single path from root to leaf (plus un-
explored siblings).
– Depth first: constraint satisfaction, propositional satisfiability,
logic programming
* (Remember, though, that kanren had some breadth-first fa-
cilities.)
– Backtracking search: modifying current state description
• Depth-limited search
– Time complexity O(bl ); space complexity O(bl)
– Two kinds of failure: search failure, cutoff failure.
– Diameter of state space: any node can reach any other node in
at most D steps
• Iterative deepening (depth-first) search
– Gradually increases the depth limit
– Like depth-first: O(bd) space complexity
– Like breadth-first: complete when branching factor is finite and
optimal when the path cost is non-decreasing
- iterative-deepening-search(problem) -> solution or failure
- for depth = 0 to infinity do
- result = depth-limited-search(problem, depth)
- if result != cutoff
- return result
– Iterative deepening, preferred uninformed search method when
search space is large and depth unknown
– Do breadth-first until almost all memory consumed; iterative
deepening on all nodes in the frontier.
– Iterative analog to uniform-cost search? (Overhead, apparently.)
• Bidirectional search
92
– Forward from the initial state and backwards from the goal: bd/2 +
bd/2 << bd
– Replace goal-test to see whether frontiers intersect
– Check when node selected for expansion; with hash-table: con-
stant time
* (Hash-tables for membership testing: I like it. Hence: canon-
ical form, &c.)
– Time, space complexity: O(bd/2 )
– Reduce by half if one of the two searches done by iterative deep-
ening; at least one of the frontiers must be kept in memory
– Requires mechanism for computing predecessors; in reversible
case: reverse successor. Non-reversible: ingenuity
– Explicitly listed goal-states: construct dummy goal-states whose
predecessors are actual goal states.
* (Why?)
– Informed search
* Problem-specific knowledge
* Best-first search
· Evaluation function, f (n); cost estimate
· Uniform-cost search, except that f instead of g orders
the priority queue
· A component of f (n), the heuristic function h(n): esti-
mated cost of cheapest path from the state at node n to
a goal state.
· h(n), unlike g(n), depends only on the state at that node.
· If n is a goal node, h(n) = 0.
* Greedy best-first search
· f (n) = h(n)
· E.g. straight-line distance heuristic
· The amount of reduction depends on the particualr prob-
lem and on the quality of the heuristic.
* A∗ search (minimizing total estimated cost)
93
· f (n) = h(n) + g(n) = estimated cost of the cheapest
solution through n.
· Provided that the heuristic function (h(n)) satisfies cer-
tain conditions, A∗ is complete and optimal.
· Identical to uniform-cost search, except that A∗ uses g+h
instead of g.
· Admissible heuristic :: Never overestimates the cost to
reach the goal.
· Consistent heuristic :: h(n) ≤ c(n, a, n′ ) + h(n′ ); triangle
inequality: if there were a route from n to Gn via n′ that
was cheaper than h(n), that would violate the property
that h(n) is a lower bound on the cost to reach Gn .
· Every consistent heuristic, admissible.
· hSLD : consistent heuristic; straight line between n and
n′ no greater than c(n, a, n′ ).
· The tree-search version of A∗ is optimal if h(n) is admis-
sible, while the graph-search version is optimal if h(n) is
consistent.
· If h(n) is consistent, values of f (n) along any path are
non-decreasing.
· f (n′ ) = g(n′ ) + h(n′ ) = g(n) + c(n, a, n′ ) + h(n′ ) ≥ g(n) +
h(n) = f (n)
· There would have to be another frontier node n′ on the
optimal path, by the graph selection property of 3.9.
· See page 77: The frontier separates the state-space graph
into the explored region and the unexplored region, so
that every path from the inital state to an unexplored
state has to pass through a state in the frontier. (This is
the graph separation property.)
· With an admissible but inconsistent heuristic, A∗ re-
quires some extra book-keeping to ensure optimality.
· It follows that the sequence of nodes expanded by A∗ is
in non-decreasing order of f (n). Hence, the first goal
node selected for expansion must be an optimal solutian
94
because f is the true cost for goal nodes (which have h =
0) and all later goal nodes will be at least as expensive.
· The fact that f -costs are non-decreasing means we can
draw contours in the state space.
· A∗ fans out from the start node, adding nodes in concer-
tric bands of increasing f -cost.
· With uniform-cost search (A∗ using h(n) = 0), the bands
will be circular; more accurate heuristics: bands will
stretch toward the goal state and become more narrowlsy
focused around the optimal path.
· A∗ expands all nodes with f (n) < C ∗ .
· A∗ might expand some nodes on the goal-contour before
selecting the goal node.
· Completeness requires that there be only finitely many
nodes with cost less than or equal to C ∗ , a condition
that is true if all step costs exceed some finite ϵ and if b
is finite.
· (Actually (i.e. accidentally) finite, or finite in principle?
The latter doesn’t matter; but we can put upper bounds
on b in the case of e.g. tries.
· Subtree below Timisoara is pruned: because hSLD is ad-
missible.
· A∗ is optimally efficient for any given consistent heuris-
tic: no other optimal algorithm is guaranteed to expand
fewer nodes than A∗ (except possibly tie-breaking). Any
algorithm that does not expand all nodes with f (n) < C
runs the risk of missing the optimal solution.
· The catch is that the number of states within the goal
contour search space is still exponential.
· Absolute error, relative error
· Simplest model: state space, single goal, tree with re-
versible actions
· A∗ usually runs out of space before it runs out of time
* Memory-bounded heuristic search
95
· Iterative-deepening A∗ (IDA*): cutoff is the f -cost (g +
h) rather than the depth. Practical for unit step costs;
avoid the overhead of sorted queue of nodes.
· Recursive best-first search: using linear space
· Modifies currnt f; unwinds along the recursion
· Somewhat more efficient than IDA*, suffers from exces-
sive node regeneration
· Space complexity: linear; time-complexity: depends on
heuristic and how often best path changes.
· Memory-bounded A*
· Drop oldest worst leaf when memory full
· SMA* is a fairly robust choice for finding optimal solu-
tions, particularly when the state space is a graph.
* Metalevel state-space
· Goal of learning is to minimize total cost of problem
solving.
• 8-puzzle: exhaustive tree search: 322 ; graph search: 181,440 distinct
states.
• Manhattan distance
• Quality of heuristic: effective branching factor b∗ . (If the total nuber of
nodes generated by A∗ for a particualr problem is N and the solution
depth is d, then b∗ is the branching factor that a uniform tree of depth
d would have to have in order to contain N + 1 nodes.)
• Experimental measurements of b∗ on a small set of problems can pro-
vide a good uide to the heuristic’s overall usefulness
• A well-designed heuristic would have a value of b∗ close to 1.
• “h2 dominates h1 .”
• Problem with fewer restrictions on the actions is called a relaxed prob-
lem: whence heuristic. The state-space of the relaxed problem is a
supergraph of the original state space.
• The relaxed problem may have better solutions of the added edges
provide short cuts.
96
• The cost of an optimal solution to a relaxed problem is an admissible
heuristic for the original problem.
• It must obey the triangle inequality and is therefore consistent.
• Relaxed problems solved without search, otherwise: too expensive.
• Absolver: Prieditis
• h(n) = maxh1 (n), ..., hm (n)
• Composite heuristic: h is admissible; consistent, dominates component
heuristics.
• Pattern databases
• Solutions share moves, cannot be additively combined
• Disjoint pattern databases
• Nonadditive heuristic
• Inductive methods supplied with features of a state that are relevant
to predicting the state’s value.
• Goal identified, well-defined problem formulated.
• Initial state, actions, transition model, goal test, path cost. This is
the state space. Path through state space to goal state: solution.
• States and actions: atomic; don’t consider internal structure. (What
the fuck does this mean: features?)
• Tree-search: all paths; graph-search: avoids redundant paths.
• Completeness, optimality, time complexity, space complexity.
• Uninformed search
– Breadth-first
– Uniform-cost
– Depth-first
– Depth-limited
– Iterative deepening
– Bidirectional
• Informed search
– Heuristic function
97
– Best-first search
– Greedy best-first search
– A∗ search
– RBFS
– SMA*
• Dynamic programming can be seen as a form of depth-first search on
graphs.
40.4 4
98
• Success of hill climbing depends on the shape of the state-space land-
scape: few local maxima and plateux, random-restart hill climbing will
find a good solution very quickly.
• Local beam search keeps track of k states rather than just one.
• Successor of all k states are generated; selects k best successors.
• In a local beam search, useful information is passed among the parallel
search threads.
• Become concentrated in state space
• Stochastic beam search: k successors at random; natural selection,
successors of state popluate next generation
99
40.4.4 Genetic algorithms
100
• Intuitively, the advantage comes from the ability of crossover to com-
bine large blocks of letters that have evolved independently to per-
form useful functions, thus raising the level of granularity at which
the search operates.
– Each piece of information has to be independently useful, I sup-
pose; or useful in concert, since it raises the granularity?
• Schema: substring in which some of the positions can be left unspeci-
fied; strings that match the schema: instances.
• If the average fitness of the instances of a schema is above the mean,
then the number of instances of the schema whithin the population
will grow over time.
• Genetic algorithms work best when schemata correpsond to meaning-
ful components of a solution.
• Successful use of genetic algorithms requires careful enginering of the
representation.
• Baldwin proposed a theory in which behavior learned during an or-
ganism’s lifetime could accelerate the rate of evolution.
• This expression is correct locally, but not globally because the sets Ci
are (discontinuous) functions of the state.
101
• One way to avoid continuous problems is simply tto discretize the
neighborhood of each state: move by some fixed amount ±δ.
• We could also apply stochastic hill climbing and simulated annealing
directly, without discretizing the space. These algorithms choose suc-
cessors randomly, which can be done by generating random vectors of
length δ.
• Many methods attempt to use the gradient of the landscape to find a
maximum. The gradient of the objective function is a vector ∇f that
gives the magnitude and direction of the steepest slope.
( )
∂f ∂f ∂f ∂f ∂f ∂f
∇f = , , , , ,
∂x1 ∂y1 ∂x2 ∂y2 ∂x3 ∂y3
102
⃗ f (⃗x) is the Hessian matrix of second derivatives, whose ele-
where H
2f
ments are given by ∂x∂i ∂x j
.
• Computing n2 entries of the Hessian and inverting it may be expensive,
so approximate versions of the Newton-Raphson method have been
developed.
• Constrained optimization, linear programming: constraints: linear in-
equalities forming a convex set.
• Linear programming is probably the most widely studied and broadly
useful class of optimization problems.
• Convex optimization
103
- and-or-graph-search(problem) -> conditional plan, failure
- or-search(problem.initial-state, problem, [])
- or-search(state, problem, path) -> conditional plan, failure
- if problem.goal-test(state) then return empty plan
- if state is on path then retun failure
- for each action in problem.actions(state) do
- plan <- and-search(results(state, action), problem, [ state |
path ])
- if plan != failure then return [action | plan]
- return failure
- and-search(states, problem, path) -> conditional plan, failure
- for each s in states do
- plan <- or-search(s, problem, path)
- if plan = failure then return failure
- return [if s_1 then plan_1 else . . . if s_n then plan_n]
• Interleaving of search and execution is also useful for exploration prob-
lems and game playing
• Cycles, which often arise in nondeterministic problems
• The algorthm does not check whether the current state is a repetition
of a state on same other path from the root, which is important for
efficiency.
• AND-OR graphs can be explored breadth-first or best-first; heuristic
function must be modified to estimate the cost of a contingent solution
rather than a sequence.
• There is an analog of A*.
• Cyclic solution: keep trying right until it works.
• Add a label and use that label instead of repeating the plan itself.
• Cyclic plan may be considered a solution provided that every leaf is a
goal state and that a leaf is reachable from every point in the plan.
• Loop in the state space back to a state L translate to a loop in the
plan back to the point where the subplan for state L is executed.
• Initial problem formulation (observable, nondeterministic) is aban-
doned in favor of a different formulation (partially observable, deter-
ministic) where the failure is attributed to an unobservable property.
104
40.4.7 Partial observation
105
Nondeterministic:
∪
b′ = Result(b, a) = {s′ : s′ ∈ ResultsP (s, a)∧s ∈ b} = ResultsP (s, a)
s∈b
– Update stage: for each possible percept, the belie state that
would result from the percept. bo is the set of states in b̂ that
could have produced the percept:
106
• Each updated belief state bo can be no larger than the predicted belief
state b̂.
40.5 Lectures
40.5.1 1
40.5.2 2
• Initial state
• actions(state) → a1 , a2 , a3 , . . .
• result(state, action) → state′
• goal-test(state) → T |F
action action
• path-cost(state −−−−→ state −−−−→ state) → n
• step-cost(state, action, state′ ) → n
• Navigate the state space by applying actions
• Separate state into three parts: ends of paths (frontier); explored and
unexplored regions.
• Step-cost
• Tree-search
– Family-resemlance; difference: which path to look at first.
• Depth-first search: shortest-first search
• TODO Implementation
107
At a site event two new breakpoints appear, which start
tracing out edges. In fact, the new breakpoints coincide at
first, and then move in opposite directions to trace out the
same edge.
A breakpoint is stored at an internal node by an ordered
tuple of sites pi , p j , where pi defines the parabola left of
the breakpoint and p j defines the parabola to the right.
Computing the breakpoint (from here):
# computing-breakpoint
Between each consecutive pair of sites pi and pj , there is a
breakpoint. Although the breakpoint moves as a function
of the sweep line, observe that it is possible to compute the
exact location of the breakpoint as a function of pi , pj , and
the current y-coordinate of the sweep line. In particular,
the breakpoint is the center of a circle that passes through
pi , pj and is tangent to the sweep line. Thus, as with beach
lines, we do not explicitly store breakpoints. Rather, we
compute them only when we need them.
Given a fixed location of the sweep line, determine the arc
of the beach line that intersects a given vertical line. This
can be done by a binary search on the breakpoints, which
are computed “on the fly”. (Think about this.)
They’re talking about consecutive triples on the beach-line, by the
way; is this a factor of x-monotonicity?
For each consecutive triple pi , pj , pk on the beach line,
we compute the circumcircle of these points. (We’ll leave
the messy algebraic details as an exercise, but this can be
done in O(1) time.) If the lower endpoint of the circle (the
minimum y-coordinate on the circle) lies below the sweep
line, then we create a vertex event whose y-coordinate is
the y-coordinate of the bottom endpoint of the circumcir-
cle. We store this in the priority queue. Each such event in
the priority queue has a cross link back to the triple of sites
that generated it, and each consecutive triple of sites has
a cross link to the event that it generated in the priority
queue.
This is pretty cool:
108
Let pi be the current site. We shoot a vertical ray up to
determine the arc that lies immediately above this point
in the beach line. Let pj be the corresponding site. We
split this arc, replacing it with the triple of arcs pj , pi ,
pj which we insert into the beach line. Also we create new
(dangling) edge for the Voronoi diagram which lies on the
bisector between pi and pj . Some old triples that involved
pj may be deleted and some new triples involving pi will
be inserted. For example, suppose that prior to insertion
we had the beach-line sequence p1 , p2 , pj , p3 , p4 . The
insertion of pi splits the arc pj into two arcs, denoted pj
and pj . Although these are separate arcs, they involve the
same site, pj . The new sequence is p1 , p2 , pj , pi , pj ,
p3 , p4 . Any event associated with the old triple p2 , pj
, p3 will be deleted. We also consider the creation of new
events for the triples p2 , pj , pi and pi , pj , p3 . Note
that the new triple pj , pi , pj cannot generate an event
because it only involves two distinct sites.
Also for vertices:
Let pi , pj , and pk be the three sites that generate this
event (from left to right). We delete the arc for pj from the
beach line. We create a new vertex in the Voronoi diagram,
and tie the edges for the bisectors (pi , pj ), (pj , pk ) to it,
and start a new edge for the bisector (pi , pk ) that starts
growing down below. Finally, we delete any events that
arose from triples involving this arc of pj , and generate
new events corresponding to consecutive triples involving
pi and pk (there are two of them). For example, suppose
that prior to insertion we had the beach-line sequence p1 ,
pi , pj , pk , p2 . After the event we have the sequence p1
, pi , pk , p2 . We remove any events associated with the
triples p1 , pi , pj and pj , pk , p2 . (The event pi , pj , pk
has already been removed since we are processing it now.)
We also consider the creation of new events for the triples
p1 , pi , pk and pi , pk , p2 .
About the appearance and disappearance of triples (how to find
triples?):
So what the algorithm does is this. At every event, it
109
checks all the new triples of consecutive arcs that appear.
For instance, at a site event we can get three new triples:
one where the new arc is the left arc of the triple, one where
it is the middle arc, and one where it is the right arc.
Let’s table Voronoi for the time being, or at least out-source it to R;
even shit as trivial as finding the center of circles between two points
and a tangent seems difficult.
Can’t we just hard-code the determinant and solve the equations; how
did Fortune do it?
– TODO Binary search on breakpoints
See computing-breakpoint.
Circle from two points and a line; there are two.
Symbolic solution (given three points):
This can all be done symbolically, of course, but you'll get some
pretty complicated expressions for h and k. The simplest forms of
these involve determinants, if you know what they are:
$y
[1] 0.00 0.00 0.15 0.50 0.50 0.85 1.00 1.00 0.35 0.35 0.65 0.65
> voronoi.mosaic(tritest)
voronoi mosaic:
110
nodes: (x,y): neighbours (<0: dummy node)
1: (0.5,-0.7583333): 7 2 -1
2: (0.2392857,0.1107143): 8 3 1
3: (0.1107143,0.2392857): 9 4 2
4: (-0.7583333,0.5): 10 -2 3
5: (1.758333,0.5): 11 6 -3
6: (0.8892857,0.2392857): 12 7 5
7: (0.7607143,0.1107143): 8 1 6
8: (0.5,0.30625): 17 2 7
9: (0.30625,0.5): 18 10 3
10: (0.1107143,0.7607143): 14 4 9
11: (0.8892857,0.7607143): 16 12 5
12: (0.69375,0.5): 17 6 11
13: (0.5,1.758333): -4 14 16
14: (0.2392857,0.8892857): 10 15 13
15: (0.5,0.69375): 18 16 14
16: (0.7607143,0.8892857): 11 13 15
17: (0.5,0.5): 12 18 8
18: (0.5,0.5): 15 9 17
dummy nodes: (x,y)
1: (0.5,-3.275)
2: (-3.275,0.5)
3: (4.275,0.5)
4: (0.5,4.275)
Uh, oh; floating-point difficulties:
> voronoi.mosaic(c(0, sqrt(1/2), 1), c(1, sqrt(1/2), 0))
voronoi mosaic:
nodes: (x,y): neighbours (<0: dummy node)
1: (2.680315e-16,2.680315e-16): -1 -2 -3
dummy nodes: (x,y)
1: (2.680315e-16,2.680315e-16)
2: (2.680315e-16,2.680315e-16)
3: (2.680315e-16,2.680315e-16)
From Steven’s implementation:
� ~/prg/c/fortune/ master cat | ./voronoi
0 1
0.70710678118 0.70710678118
1 0
111
s 1.000000 0.000000
s 0.707107 0.707107
l -0.414214 1.000000 -0.000000
s 0.000000 1.000000
l -1.000000 1.000000 0.000000
v -0.000000 -0.000000
l 1.000000 -0.414214 -0.000000
e 1 -1 0
e 2 0 -1
e 0 0 -1
It appears as though he generates the bisecting-lines upon finding
points; keeps track of partial edges on those lines.
s a b indicates that an input point at coordinates l a b c
indicates a line with equation ax + by = c. v a b indicates
a vertex at a b. e l v1 v2 indicates a Voronoi segment which
is a subsegment of line number l; with endpoints numbered
v1 and v2. If v1 or v2 is -1, the line extends to infinity.
• Several implementations and “low-level” pseudo-code
http://blog.ivank.net/fortunes-algorithm-and-implementation.html
• Fortune’s notes
Here and here; gotten from here.
Site event:
Let pi be the current site. We shoot a vertical ray up to
determine the arc that lies immediately above this point
in the beach line. Let pj be the corresponding site. We
split this arc, replacing it with the triple of arcs pj , pi ,
pj which we insert into the beach line. Also we create new
(dangling) edge for the Voronoi diagram which lies on the
bisector between pi and pj . Some old triples that involved
pj may be deleted and some new triples involving pi will
be inserted.
Vertex event:
Let pi , pj , and pk be the three sites that generate this
event (from left to right). We delete the arc for pj from the
beach line. We create a new vertex in the Voronoi diagram,
and tie the edges for the bisectors (pi , pj ), (pj , pk ) to it,
and start a new edge for the bisector (pi , pk ) that starts
112
growing down below. Finally, we delete any events that
arose from triples involving this arc of pj , and generate
new events corresponding to consecutive triples involving
pi and pk (there are two of them).
• Voronoi toy
http://nodename.com/blog/2009/05/11/a-voronoi-toy/
Points to this implementation of Fortune’s.
• Good overview
See here; actually breaks down the change in the tree and double-linked
list.
• TODO Calculate convergence of breakpoints
Good animations; still doesn’t elaborate on how to calculate whether
the breakpoints converge.
First of all, there can be consecutive triples whose two
breakpoints do not converge, that is, the directions in
which they move are such that they will not meet in the
future; this happens when the breakpoints move along two
bisectors away from the intersection point. In this case the
triple does not define a potential circle event.
Someone answered their own question:
As long as three sites are not collinear, then the edges that
perpendicularly bisect the segments between the sites are
also tangent to the circle whose edge contains all three sites.
Therefore the breakpoints defined by a triple of Voronoi
sites converge if the center of the circle defined by the three
sites is in front of the middle site, where “in front” and
“behind” depend on the coordinate system and sweepline
alignment you have chosen.
• TODO Doubly-connect edge list
See here:
DCEL is more than just a doubly linked list of edges. In
the general case, a DCEL contains a record for each edge,
vertex and face of the subdivision. Each record may con-
tain additional information, for example, a face may con-
tain the name of the area. Each edge usually bounds two
faces and it is therefore convenient to regard each edge as
113
half-edge. Each half-edge bounds a single face and thus
has a pointer to that face. A half-edge has a pointer to the
next half-edge and previous half-edge of the same face. To
reach the other face, we can go to the twin of the half-edge
and then traverse the other face. Each half-edge also has a
pointer to its origin vertex (the destination vertex can be
obtained by querying the origin of its twin).
Each vertex v contains the coordinates of the vertex and
also stores a pointer to an arbitrary edge that has v as its
origin. Each face stores a pointer to some half-edge of its
outer boundary (if the face is unbounded then pointer is
null). It also has a list of half-edges, one for each hole that
may be incident within the face. If the vertices or faces
do not hold any interesting information, there is no need
to store them, thus saving space and reducing the data
structure complexity.
• TODO O(n2 log n) intersection of all the half-planes
Bisector between two points: perpendicular; points that are the same
distance from p and q.
From Tufts:
A naive approach to construct of a Voronoi diagram is to
determine the region for each site, one at a time. Since
each region is the intersection of n − 1 half-planes, we
can use an O(n log n) half-plane intersection algorithm to
determine this region. Repeating for all n points, we have
an O(n2 log n) algorithm.
Finally understand Berg’s reference to chapter 4: they employ a sweep-
line for intersection.
Another description:
For all pairs of sites, find their perpendicular bisector. This
calculation takes O(n log n) time and is done once. Next,
we take all the open half-planes that contain a site and
calculate the intersection of these half-planes to obtain the
Voronoi cell for this site. An optimal algorithm for comput-
ing the intersection of half-planes is O(n log n) 11 . Repeat
11
DEFINITION NOT FOUND: 1
114
for every site. The overall complexity then becomes O(n2
log n).
• Voronoi over Delaunay
See here:
This function creates a Voronoi mosaic.
It creates first a Delaunay triangulation, determines the
circumcircle centers of its triangles, and connects these
points according to the neighbourhood relations between
the triangles.
115
• It is not normally possible to determine from observing a machine
whether it has a random element, for a similar effect can be produced
by such devices as making the choices depend on the digits of the
decimal for π.
• Discrete state machines: strictly speaking there are no such machines.
Everything really moves continuously.
• This is reminiscent of Laplace’s view that from the complete state of
the universe at one moment of time, as described by the positions and
velocities of all particles, it should be possible to predict all future
states.
• This special property of digital computers, that they can mimic any
discrete state machine, is described by saying that they are universal
machines.
• “Are there imaginable digital computers which would do well in the
imitation game?” → “Are there discrete state machines which would
do well?”
• I believe that in about fifty years’ time it will be possible to programme
computers, with a storage capacity of about 109 , to make them play
the imitation game so well that an average interrogator will not have
more than 70% chance of making the right identification after five
minutes of questioning.
– Russell/Norvig, 12: storage units: 101 5
– Loebner prize:
Elbot of Artificial Solutions won the 2008 Loebner
Prize bronze award, for most human-like artificial
conversational entity, through fooling three of the
twelve judges who interrogated it (in the human-
parallel comparisons) into believing it was human.
This is coming very close to the 30% traditionally re-
quired to consider that a program has actually passed
the Turing test.
* From a judge:
He predicted that by the end of the century,
computers would have a 30 per cent chance of
being mistaken for a human being in five min-
utes of text-based conversation.
116
I thought this was mistaken (should be 70), but it is indeed
correct.
– In other words, a damn-good guess.
• Conjectures are of great importance since they suggest useful lines of
research.
• We might expect that He would only exercise this power in conjunction
with a mutation which provided the elephant with an appropriately
improved brain to minister to the needs of this soul.
• We like to believe that Man is in some subtle way superior to the rest
of creation.
• “The consequences of machines thinking would be too dreadful.” I do
not think that this argument sufficiently substantial to require refu-
tation. Consolation would would be more appropriate: perhaps this
should be sought the transmigration of souls.
• There are limitations to the powers of discrete-state machines. The
best known of these results is known as Gödel’s theorem, and shows
that in any sufficiently powerful logical system statements can be for-
mulated which can neither be proved nor disproved within the system,
unless possibly the system itself is inconsistent.
• “Will this machine every answer ‘Yes’ to any question?” It can be
shown that the answer is either wrong or not forthcoming.
• The only way to know that a man thinks is to be that particular man.
It is in fact the solipsist point of view.
• I do not wish to give the impression that I think there is no mystery
about consciousness. There is, for instance, something of a paradox
connected with any attempt to localise it.
• When a burnt child fears the fire and shows that he fears it by avoiding
it, I should say that he was applying scientific induction.
• It would deliberately introduce mistakes in a manner calculated to
confuse the interrogator.
• By observing the results of its own behaviour it can modify its own
programmes so as to achieve some purpose more effectively.
• This is the assumption that as soon as a fact is presented to a mind
all consequences of that fact spring into the mind simultaneously with
it.
117
• The undistributed middle is glaring.
• I would defy anyone to learn from these replies sufficint about the
programme to be able to predict any replies to untried values.
• A smallish proportion are super-critical. An idea presented to such
a mind may give rise to a whole “theory” consisting of secondary,
tertiary and more remote ideas.
– Sponteneity
• These last two paragraphs should be described as “recitations tending
to produce belief.”
• The only satisfactory support that can be given will be that provided
by waiting for the end of the century and then doing the experiment
described.
• Estimates for the storage capacity of the brain vary from 101 0 to 101 5
binary digits.
– Russell/Norvig (12): 101 3 synapses
• At my present rate of working I produce about a thousand digits of pro-
gramme a day, so that about sixty workers, working steadily through
the fifty years might accomplish the job, if nothing went into the waste-
paper basket.
– Mythical man-month?
• The child-programme and the education process
• One might have a complete system of logical inference “built in”. The
store would be largely occupied with definitions and propositions. Cer-
tain propositions may be described as “imperatives”. As soon as an
imperative is classed as “well-established” the appropriate action takes
place.
– Compare McCarthy, Programs with Common Sense, regarding
imperatives.
• These choices make the difference between a brilliant and a footling
reasoner.
• We can only see a short distance ahead, but we can see plenty there
that needs to be done.
118
41 TODOs
119
Talking about convex hulls and voronoi diagrams to construct random simple
polygons; I wonder if CGAL wouldn’t come in handy, after all. Write the
Scheme bindings? Ouch.
Can we just do a Voronoi tesselation and call it a day? It seems like it
has all the properties we’re looking for: non-overlapping simple polygons,
unreachable points. Let’s do it!
At this point, though, isn’t it indistinguishable from drawing random points
in the plane and searching with a straight-line heuristic? Yes. Voronoi
diagrams are irrelevant. We could, of course, generate them for the hell of
it (using continuous, not discrete, coördinates); and use their boundaries as
our vertices. Why? It’s pretty. Still have to figure out how to draw them,
though.
Delaunay triangulation in R, incidentally; using spatstat and maptools,
also latticeExtra.
Voronoi diagrams are one-liners in R; another example. See also sp and
deldir. CGAL does it, too, of course.
See Lloyd’s algorithm, Bowyer-Watson, and Fortune; an alternative is De-
launay triangulation, too. Basically, we have random points; and want to
divvy them up into coincident, non-overlapping, convex polygons. There’s
a number of algorithms for Delaunay triangulation; one of which is O(n2 ).
What about just a Euclidean minimum spanning tree? We can even find
one in O(n log n).
Look no further than Computational Geometry: implementable algorithms,
detailed explanations. Next candidate for LACSRG, incidentally?
We might as well go straight to Fortune; Berg is somewhat irritating with
internal references; Èuk Roman did a sub-adequate job.
Steven’s own implementation in c
Has some good non-optimal algorithms; n2̂.
Good prose from AMS:
If the next event that the sweep line encounters is a site event,
we simply insert the new site into our list of sites in the order
in which the corresponding parabolic arc appears on the beach
line. We then record the fact that we have encountered a new
edge in the diagram.
120
If the next event that the sweep line encounters is a circle event,
we record the fact that we have encountered a vertex in the
diagram and that this vertex is the endpoint of the edges corre-
sponding to the two breakpoints that have come together. We
also record the new edge corresponding to the new breakpoint
that results from the circle event.
Whether we encounter a site or circle event, we will always check
to see if we have added a new triple of parabolic arcs on the beach
line that may lead to a future circle event. It is also possible
that we will remove a future circle event from consideration if
the triple of parabolic arcs that led to it no longer exists after
the event.
Berg is pretty good, actually:
Now we know where and how the combinatorial structure of the
beach line changes: at a site event a new arc appears, and at
a circle event an existing arc drops out. We also know how
this relates to the Voronoi diagram under construction: at a site
event a new edge starts to grow, and at a circle event two growing
edges meet to form a vertex.
On the justification for T , which changes:
The paradigm involves maintaining the intersection of the
Voronoi diagram with the sweep line. Unfortunately this is not
so easy, because the part of Vor(P) above depends not only on
the sites that lie above ℓ but also on sites below ℓ. Stated dif-
ferently, when the sweep line reaches the topmost vertex of the
Voronoi cell V (pi ) it has not yet encountered the corresponding
site pi . Hence, we do not have all the information needed to
compute the vertex. We are forced to apply the plane sweep
paradigm in a slightly different fashion: instead of maintaining
the intersection of the Voronoi diagram with the sweep line, we
maintain information about the part of the Voronoi diagram of
the sites above ℓ that cannot be changed by sites below ℓ.
Sweep line ℓ; the beach line, however:
The locus of points that are closer to any site above ℓ than to
ℓ itself is bounded by parabolic arcs. We call this sequence of
parabolic arcs the beach line.
121
The beach line is x-monotone, that is, every vertical line inter-
sects it in exactly one point.
Breakpoints:
Notice that the breakpoints between the different parabolic arcs
forming the beach line lie on edges of the Voronoi diagram . . .
the breakpoints exactly trace out the Voronoi diagram while the
sweep line moves from top to bottom.
So, instead of maintaining the intersection of V or(P ) with ℓ, we
maintain the beach line as we move our sweep line ℓ. We do not
maintain the beach line explicitly, since it changes continuously
as ℓ moves.
[The beach line’s combinatorial structure changes] when a new
parabolic arc appears on it, and when a parabolic arc shrinks to
a point and disappears.
On site events:
[A new arc appears on the beach line] when the sweep line ℓ
reaches a new site. The parabola defined by this site is at first
a degenerate parabola with zero width: a vertical line segment
connecting the new site to the beach line. As the sweep line
contiues to move downward the new parabola gets wider and
wider.
We call the event where a new site is encountered a site event.
At a site event two new breakpoints appear, which start tracing
out edges. In fact, the new breakpoints coincide at first, and then
move in opposite directions to trace out the same edge. Initially,
this edge is not connected to the rest of the Voronoi diagram . .
. Later on, the growing edge will run into another edge, and it
becomes connected to the rest of the diagram.
Site events and beach lines:
The only way a new arc can appear on the beach line is through
a site event.
The beach line consists of at most 2n − 1 parabolic arcs: each
site encountered gives rise to one new arc and the splitting of at
most one existing arc into two.
Circle events:
122
The second type of event is where an existing arc of the beach
line shrinks to a point and disappears. Three arcs α, α′ , α′′ are
defined by three sites pi , pj , and pk . At the moment a′ disap-
pears, the parabolas defined by these three sites pass through a
common point q. Point q is equidistant from ℓ and each of the
three sites. Hence, there is a circle passing through pi , pj , and
pk with q as its center and whose lowest point lies on ℓ. The
point q is a vertex of the Voronoi diagram.
We call the event where the sweep line reaches the lowest point
of a circle through three sites defining consecutive arcs on the
beach line a cirle event.
Circle events and arcs:
The onl way in which an existing arc can disappear from the
beach line is through a circle event.
At a site event a new arc appears, and at a circle event an existing
arc drops out.
At a site event a new edge starts to grow, and at a circle event
two growing edges meet to form a vertex.
Data structures:
We need a data structure that stores the part of the Voronoi
diagram computed thus far. We also need the two ‘standard’
data structures for any sweep line algorithm: an even queue and
a structure that represents the status of the sweep line. Here the
latter structure is a representation of the beach line.
We store the Voronoi diagram as a doubly-connected edge list.
The beach line is represented by a balanced binary search tree
T . Its leaves correspond to the arcs of the beach line–which is
x-monotone–in an ordered manner: the leftmost leaf represents
the leftmost arc, and so on. Each leaf µ stores the site that
defines the arc it represents. The internal nodes of T represent
the breakpoints on the beach line. A breakpoint is stored at
an internal node by an ordered tuple of site < pi , pj >, where
pi defines the parabola left of the breakpoint and pj defines the
parabola to the right. We can find in O(log n) the arc of the
beach line lying above a new site. At an internal node, we simply
compare the x-coördinate of the new site with the x-coördinate
123
of the breakpoint, which can be computed from the tuple of sites
and the position of the sweep line in constant time.
In T we also store pointers to the other two data structures used
during the sweep. Each leaf of T , representing arc α, stores
a pointer to a circle event in the event queue in which α will
disappear. This pointer is nil if no circle event exists where
α will disappear, or this circle event hasn’t been detected yet.
Every internal node v has a pointer to a half-edge in the doubly-
connected edge list of the Voronoi diagram. v has a pointer to one
of the half-edges of the edge being traced out by the breakpoint
represented by v.
The event queue Q is implemented as a priority queue, where the
priority of an event is its y-coördinate. It stores the upcoming
events that are already known. For a site event we simply store
the site itself. For a circle event the event point that we store
is the lowest point of the circle, with a pointer to the leaf in T
that represents the arc that will disappear in the event.
Detection of circle events:
All the site events are known in advance, but the circle events
are not.
During the sweep the beach line changes its topological structure
at every event. This may cause new triples of consecutive arcs
to appear on the beach line and it may cause existing triples to
disappear. For every three consecutive arcs on the beach line
that define a potential circle event, the potential event is stored
in the event queue Q.
There can be consecutive triples whose two breakpoints do not
converge. It can also happen that the triple disappears before
the event has taken place: f alsealarm.
At every event, it checks all the new triples of consecutive arcs
that appear. At a site event we can get three new triples: one
where the new arc is the left arc of the triple, on where it is
the middle arc, and one where it is the right arc. When such a
new triple has converging breakpoints, the event is inserted into
the event queue Q. In the case of a site event, the triple with
the new arc being the middle one can never cause a circle event,
because the left and right arc of the triple come from the same
124
parabola and therefore the breakpoints must diverge.
For all disappearing triples it is checked whether they have a
corresponding event in Q. If so, the event is a false alarm, and it
is deleted from Q. This can be done using the pointers we have
from the leaves in T to the corresponding circle events in Q.
Voronoi vertices and circle events:
Every Voronoi vertex is detected by means of a circle event.
The corresponding circle event is in Q just before the event takes
place, and the Voronoi vertex is detected.
The algorithm:
After the event queue Q is empty, the beach line hasn’t disap-
peared yet. The breakpoints correspond to the half-infinite edges
of the Voronoi diagram. We must add a bounding box to which
these edges can be attached.
Javascript Voronoi, by the way;
125
edge, or in more mathematical terminology: there is a natural
bijection between the two which reverses the face inclusions.
A pretty good chapter on Voronoi
Triangulation algorithms
Voronoi polygon
Metric fundamental polygon
Incremental Delaunay
Oh, shit: step-by-step Voronoi
Fantastic paper by Knuth, et al.
This is good for an intuition, incidentally:
In mathematics and computational geometry, a Delaunay trian-
gulation for a set P of points in a plane is a triangulation DT(P)
such that no point in P is inside the circumcircle of any trian-
gle in DT(P). Delaunay triangulations maximize the minimum
angle of all the angles of the triangles in the triangulation; they
tend to avoid skinny triangles.
• TODO Matrices
Not a bad recursive determinant (use only the first row).
(use debug srfi-25)
(include "arlib")
(include "play")
(let (;; (array (make-array (shape 0 3 0 3) 1))
(array (array (shape 0 4 0 4)
7 4 2 0
6 3 -1 2
4 6 2 5
8 2 -7 1)))
(play array)
(array-for-each-index array (lambda x (debug x))))
No; let’s hard-code the 4x4 determinant: atlas-laplace is failing; and
we can’t do LU-decomp for every matrix.
(use srfi-25 test)
126
;;; feel like incurring the O(n!) penalty of recursive Leibniz. See
;;; <http://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fou
;;;
;;; m03 * m12 * m21 * m30 -
;;; m02 * m13 * m21 * m30 -
;;; m03 * m11 * m22 * m30 +
;;; m01 * m13 * m22 * m30 +
;;; m02 * m11 * m23 * m30 -
;;; m01 * m12 * m23 * m30 -
;;; m03 * m12 * m20 * m31 +
;;; m02 * m13 * m20 * m31 +
;;; m03 * m10 * m22 * m31 -
;;; m00 * m13 * m22 * m31 -
;;; m02 * m10 * m23 * m31 +
;;; m00 * m12 * m23 * m31 +
;;; m03 * m11 * m20 * m32 -
;;; m01 * m13 * m20 * m32 -
;;; m03 * m10 * m21 * m32 +
;;; m00 * m13 * m21 * m32 +
;;; m01 * m10 * m23 * m32 -
;;; m00 * m11 * m23 * m32 -
;;; m02 * m11 * m20 * m33 +
;;; m01 * m12 * m20 * m33 +
;;; m02 * m10 * m21 * m33 -
;;; m00 * m12 * m21 * m33 -
;;; m01 * m10 * m22 * m33 +
;;; m00 * m11 * m22 * m33
(define (4d-determinant m)
(+ (* (array-ref m 0 3) (array-ref m 1 2) (array-ref m 2 1) (array-ref m 3 0))
(- (* (array-ref m 0 2) (array-ref m 1 3) (array-ref m 2 1) (array-ref m 3 0)))
(- (* (array-ref m 0 3) (array-ref m 1 1) (array-ref m 2 2) (array-ref m 3 0)))
(* (array-ref m 0 1) (array-ref m 1 3) (array-ref m 2 2) (array-ref m 3 0))
(* (array-ref m 0 2) (array-ref m 1 1) (array-ref m 2 3) (array-ref m 3 0))
(- (* (array-ref m 0 1) (array-ref m 1 2) (array-ref m 2 3) (array-ref m 3 0)))
(- (* (array-ref m 0 3) (array-ref m 1 2) (array-ref m 2 0) (array-ref m 3 1)))
(* (array-ref m 0 2) (array-ref m 1 3) (array-ref m 2 0) (array-ref m 3 1))
(* (array-ref m 0 3) (array-ref m 1 0) (array-ref m 2 2) (array-ref m 3 1))
(- (* (array-ref m 0 0) (array-ref m 1 3) (array-ref m 2 2) (array-ref m 3 1)))
(- (* (array-ref m 0 2) (array-ref m 1 0) (array-ref m 2 3) (array-ref m 3 1)))
(* (array-ref m 0 0) (array-ref m 1 2) (array-ref m 2 3) (array-ref m 3 1))
127
(* (array-ref m 0 3) (array-ref m 1 1) (array-ref m 2 0) (array-ref m 3 2))
(- (* (array-ref m 0 1) (array-ref m 1 3) (array-ref m 2 0) (array-ref m 3 2)))
(- (* (array-ref m 0 3) (array-ref m 1 0) (array-ref m 2 1) (array-ref m 3 2)))
(* (array-ref m 0 0) (array-ref m 1 3) (array-ref m 2 1) (array-ref m 3 2))
(* (array-ref m 0 1) (array-ref m 1 0) (array-ref m 2 3) (array-ref m 3 2))
(- (* (array-ref m 0 0) (array-ref m 1 1) (array-ref m 2 3) (array-ref m 3 2)))
(- (* (array-ref m 0 2) (array-ref m 1 1) (array-ref m 2 0) (array-ref m 3 3)))
(* (array-ref m 0 1) (array-ref m 1 2) (array-ref m 2 0) (array-ref m 3 3))
(* (array-ref m 0 2) (array-ref m 1 0) (array-ref m 2 1) (array-ref m 3 3))
(- (* (array-ref m 0 0) (array-ref m 1 2) (array-ref m 2 1) (array-ref m 3 3)))
(- (* (array-ref m 0 1) (array-ref m 1 0) (array-ref m 2 2) (array-ref m 3 3)))
(* (array-ref m 0 0) (array-ref m 1 1) (array-ref m 2 2) (array-ref m 3 3))))
128
41.2.5 TODO Plotting Voronoi
See path and segment. We might actually build this Scheme-R bridge. I’d
like to do that; and Voronoi; and document cock.
Start and end points: furthest points from one another? Better would be
if we could come up with a convex hull, calculate its centroid, and find
two vertices of maximum length whose adjoining line approximately hits it.
That way, paths are forced to go through the interesting center.
library(deldir)
library(ggplot2)
library(proto)
library(debug)
library(hash)
vertices[k1] <-
coordinates.to.vertex(x1, y1)
129
vertices[k2] <-
coordinates.to.vertex(x2, y2)
## sink('/tmp/fifo')
## for (node in nodes) {
## vertex <- vertices[[node]]
## cat(sprintf('(vertex %s %s %s)', node, vertex$x, vertex$y))
## }
130
## node,
## do.call(paste, as.list(neighbors[[node]]))))
## }
vertex.path1 <-
Map(function(node) vertices[[node]], path1)
vertex.path1.x <- function(vertex.path1)
unlist(Map(function(vertex) vertex$x, vertex.path1))
vertex.path1.y <- function(vertex.path1)
unlist(Map(function(vertex) vertex$y, vertex.path1))
vertex.path2 <-
Map(function(node) vertices[[node]], path2)
vertex.path2.x <- function(vertex.path2)
unlist(Map(function(vertex) vertex$x, vertex.path2))
vertex.path2.y <- function(vertex.path2)
unlist(Map(function(vertex) vertex$y, vertex.path2))
131
png('voronoi.png', width=1024, heigh=768)
qplot(tiles$x1,
tiles$y1,
xlab="",
ylab="") +
opts(legend.position='none') +
geom_point(aes(x=leftest.vertex$x,
y=leftest.vertex$y,
alpha=1.0),
shape=1,
size=5) +
geom_text(aes(x=leftest.vertex$x,
y=leftest.vertex$y,
label='Start',
alpha=1.0),
size=3) +
geom_point(aes(x=rightest.vertex$x,
y=rightest.vertex$y,
alpha=1.0),
shape=1,
size=5) +
geom_text(aes(x=rightest.vertex$x,
y=rightest.vertex$y,
label='End',
alpha=1.0),
size=3) +
geom_segment(aes(x=tiles$x1,
xend=tiles$x2,
yend=tiles$y2,
yend=tiles$y2,
alpha=1.0)) +
geom_text(aes(x=tiles$x1 + (tiles$x2 - tiles$x1) / 2,
y=tiles$y1 + (tiles$y2 - tiles$y1) / 2,
label=signif(sqrt((tiles$x2 - tiles$x1) ^ 2 +
(tiles$y2 - tiles$y1) ^ 2),
digits=2),
alpha=1.0),
size=3) +
## We have to do this in segments, if we want to be able to model
## bi-directional search.
132
geom_path(aes(x=vertex.path1.x(vertex.path1),
y=vertex.path1.y(vertex.path1),
alpha=1.0),
color=3,
size=2.0) +
geom_path(aes(x=vertex.path2.x(vertex.path2),
y=vertex.path2.y(vertex.path2),
alpha=1.0),
color=4,
size=2.0) +
geom_point(aes(x=tiles$x1,
y=tiles$y1,
alpha=1.0))
dev.off()
(use debug extras posix shell srfi-18)
(with-input-from-file "/tmp/fifo"
(lambda () (debug (read-lines))))
(with-output-to-file "/tmp/fifo"
(lambda ()
(display "oeutnho\004\n\n")
(flush-output)))
#;
(thread-start!
(lambda ()
(with-output-to-file "/tmp/fifo"
(lambda ()
(display "oeutnhoeutnh\n")
(flush-output)))))
;; (with-output-to-file "/tmp/fifo"
;; (lambda ()
;; (display "harro, welt\n")
;; (flush-output)))
133
;; (capture "./voronoi.R")
#;
(receive (in out id err)
(process* "./voronoi.R" '())
(debug (read-all err))
;; (close-input-port err)
(debug (read-all in))
;; (close-input-port in)
;; (with-output-to-port out
;; (lambda ()
;; (display "harro\n")))
;; (flush-output)
;; (system "cat tempc74e.tmp > /tmp/fifo &")
#;
(thread-start!
(lambda ()
(with-output-to-file "/tmp/fifo"
(lambda ()
(display "oeutnhoeutnh\n")))))
;; (with-input-from-file "/tmp/fifo"
;; (read))
;; (read-all in)
;; (close-input-port err)
;; (read-all err)
)
;; (with-output-to-file "/tmp/fifo"
;; (lambda ()
;; (display "harro thar\n")
;; (display "harro thar\n")))
;; (flush-output)
./voronoi.scm &
./voronoi.R
# ls -altrc
What about using Scheme to write R? Embedded R, of course; from C first
and then from Chicken? See system.txt, Rembedded.c, &c. embeddedR-
Call.c is cool, too. Minimal Windows example.
We’re probably just talking about the C-api in R, aren’t we?
134
An example in Python; someone embedded it in PostgreSQL.
Christ, they even did a mod_R (a.k.a. rApache). Take a look at RCL, the
CL-interface?
Rpackage.c and tryEval.c have some possibly interesting initialization code.
See RSPython, rpy, rpy2.
• TODO Embedded R
rpy2 has robjects, which does lookup on .globalEnv. Has a callable
R-vector type; allows calling of arbitrary R code. The “R singleton.”
There’s r_repr: R-representation? Deals with the specific vector-
types.
Have to call on vectors:
>>> rsum = robjects.r['sum']
>>> rsum(robjects.IntVector([1,2,3]))[0]
6L
Passes keyword arguments:
>>> rsort = robjects.r['sort']
>>> res = rsort(robjects.IntVector([1,2,3]), decreasing=True)
>>> print(res.r_repr())
c(3L, 2L, 1L)
Has a specific r.X11() call; there’s a whole spiel about processing
interactive events; they talk about that a little here.
It looks like the example code deals a lot with special vector-types.
There’s mechanisms for accessing fields:
>>> print(lm_D9.names)
[1] "coefficients" "residuals" "effects" "rank"
[5] "fitted.values" "assign" "qr" "df.residual"
[9] "contrasts" "xlevels" "call" "terms"
[13] "model"
A lot of invocation of the R-singleton:
import rpy2.robjects as robjects
r = robjects.r
m = r.matrix(r.rnorm(100), ncol=5)
135
pca = r.princomp(m)
r.plot(pca, main="Eigen values")
r.biplot(pca, main="biplot")
Importing packages:
from rpy2.robjects.packages import importr
base = importr('base')
stats = importr('stats')
graphics = importr('graphics')
m = base.matrix(stats.rnorm(100), ncol = 5)
pca = stats.princomp(m)
graphics.plot(pca, main = "Eigen values")
stats.biplot(pca, main = "biplot")
It assigns variables to the imported packages so that you can reference
shit.
The instance of R; on which: call arbitrary code, too:
>>> print(robjects.r('1+2'))
[1] 3
>>> sqr = robjects.r('function(x) x^2')
>>> print(sqr)
function (x)
x^2
>>> print(sqr(2))
[1] 4
Something about an R-representation:
The astute reader will quickly realize that R objects named
by python variables can be plugged into code through their
R representation:
>>> x = robjects.r.rnorm(100)
>>> robjects.r('hist(%s, xlab="x", main="hist(x)")' %x.r_repr())
Assigning to environment:
>>> robjects.r.ls(globalenv)
>>> robjects.globalenv["a"] = 123
>>> print(robjects.r.ls(globalenv))
136
Oh, shit: formulae; fucking OO; specialized vectors, along with subset-
ting. Specialized NA, too. Special-casing operators, too. DataFrame.
Type-conversion between R <-> Python: ri2py, py2ri, py2ro, &c.
Graphics require special handling. Interactive.
Low-level interface: initr, endr, &c. globalenv, baseenv, &c.
Oh, shit:
Rpy2 is using its own reference counting system in order
to bridge R with Python and keep the pass-by-reference
approach familiar to Python users.
Calling Python from R:
As could be expected from R’s functional roots, functions
are first-class objects. This means that the use of callback
functions as passed as parameters is not seldom, and this
also means that the Python programmer has to either be
able write R code for functions as arguments, or have a
way to pass Python functions to R as genuine R functions.
That last option is becoming possible, in other words one
can write a Python function and expose it to R in such a
way that the embedded R engine can use as a regular R
function.
They have support for closures; enumeration of R-types. Don’t forget
about Rserve.
rpy looks less magical; name-munging; awkward calling; slices are not
supported; conversions; Robj object;
RSPython looks lower-level and possibly simpler; is it a good candidate
for emulation? Heh: they did the reverse with a .Python form in R.
Ah: the customizable convertes you see in rpy, too.
They also count references, apparently; RS.py is refreshingly (or de-
ceptively) simple. (The whole thing is packaged as an R-package, by
the way.)
Here’s some meat; see:
This handles calling R from Python.
This code is quickly thrown together for the purposes of a)
learning about the Python internals and C API, and b) to
137
illustrate to others how one might embed R in Python or
other applications and programming environments.
There is a lot more to come, specifically the ability to be
able to pass Python objects to R by “reference” and have R
operate on these by calling methods in those objects that
result in calls to Python functions/methods.
Interesting:
This is the routine that implements Python calling an S
function with a simple, ordered list of arguments (i.e. no
named S arguments, etc.). This converts the Python argu-
ments into S objects.
This gets 4 arguments:
1. the name of the function to call
2. the un-named arguments as a Tuple
3. the named arguments (that do not use “reserved”
words)
4. a convert argument.
Install pops things in the symbol-table, incidentally.
#include <chicken.h>
#include <Rinternals.h>
#include <Rdefines.h>
#include <Rembedded.h>
#include <chicken.h>
138
} else if (C_truep(C_booleanp(arg))) {
return ScalarLogical(C_truep(arg) ? 1 : 0);
} else if (C_truep(C_stringp(arg))) {
return ScalarString(Rf_mkChar(C_string_or_null(arg)));
} else if (C_truep(C_vectorp(arg))) {
/* Is this where we need to type the vector; or can we just
VECSXP? We probably need to type the vector. Can we stick e.g.
STRSXP in an INTSXP, though?
139
SEXP function = Rf_findFun(Rf_install(C_c_string(name)), R_GlobalEnv);
SETCAR(expression, function);
SEXP iterexp = CDR(expression);
while (!C_truep(C_i_nullp(args))) {
SETCAR(iterexp, toR(C_i_car(args)));
args = C_i_cdr(args);
iterexp = CDR(iterexp);
}
int error = 0;
SEXP Rvalue = R_tryEval(expression, R_GlobalEnv, &error);
if (!error) {
switch (TYPEOF(Rvalue)) {
case REALSXP:
{
int length = Rf_length(Rvalue);
if (length == 1) {
C_word *value = C_alloc(C_SIZEOF_FLONUM);
C_kontinue(k, C_flonum(&value, REAL(Rvalue)[0]));
} else {
C_word *v = C_alloc(C_SIZEOF_VECTOR(length)),
*v0 = v;
*(v++) = C_VECTOR_TYPE | length;
int i;
for (i = 0; i < length; i++) {
C_word *value = C_alloc(C_SIZEOF_FLONUM);
*(v++) = C_flonum(&value, REAL(Rvalue)[i]);
}
C_kontinue(k, (C_word) v0);
}
}
case INTSXP:
{
int length = Rf_length(Rvalue);
if (length == 1) {
C_kontinue(k, C_fix(INTEGER(Rvalue)[0]));
} else {
C_word *v = C_alloc(C_SIZEOF_VECTOR(length)),
140
*v0 = v;
*(v++) = C_VECTOR_TYPE | length;
int i;
for (i = 0; i < length; i++)
*(v++) = C_fix(INTEGER(Rvalue)[i]);
C_kontinue(k, (C_word) v0);
}
}
case LGLSXP:
{
int length = Rf_length(Rvalue);
if (length == 1) {
C_kontinue(k, LOGICAL(Rvalue)[0] ? C_SCHEME_TRUE : C_SCHEME_FALSE);
} else {
C_word *v = C_alloc(C_SIZEOF_VECTOR(length)),
*v0 = v;
*(v++) = C_VECTOR_TYPE | length;
int i;
for (i = 0; i < length; i++)
*(v++) = LOGICAL(Rvalue)[i] ?
C_SCHEME_TRUE : C_SCHEME_FALSE;
C_kontinue(k, (C_word) v0);
}
}
case STRSXP:
{
int length = Rf_length(Rvalue);
if (length == 1) {
const char *string = CHAR(STRING_ELT(Rvalue, 0));
C_word *value = C_alloc(C_SIZEOF_STRING(strlen(string)));
C_kontinue(k, C_string(&value, strlen(string), (char *) string));
} else {
C_word *v = C_alloc(C_SIZEOF_VECTOR(length)),
*v0 = v;
*(v++) = C_VECTOR_TYPE | length;
int i;
for (i = 0; i < length; i++) {
const char *string = CHAR(STRING_ELT(Rvalue, i));
C_word *value = C_alloc(C_SIZEOF_STRING(strlen(string)));
*(v++) = C_string(&value, strlen(string), (char *) string);
141
}
C_kontinue(k, (C_word) v0);
}
}
case VECSXP:
{
C_word *list = C_alloc(C_SIZEOF_LIST(1));
C_kontinue(k, C_a_i_list1(&list, 1, C_fix(1)));
}
default:
{
C_word *pointer = C_alloc(C_SIZEOF_POINTER);
/* We probably need to allocate a new structure, copy Rvalue
thither, and free at some point; don't we? Or at least
protect the value?
#>
#include "Reval.h"
#include <Rembedded.h>
<#
(foreign-code
#<<END
Rf_initEmbeddedR(4, (char*[]) {"R-test",
"--slave",
"--vanilla",
"--args"}) ;
142
END
)
all: R-test
Reval.o: Reval.c
gcc $(R_FLAGS) $(CHICKEN_FLAGS) -c -o $@ $^
143
There’s a missing argument marker:
LibExtern SEXP R_MissingArg; /* Missing argument marker */
Do we need to have a special symbol, e.g. *missing*? Named vari-
ables have to be handled, too. See e.g. keywords and kvlists.
Can we create a SEXP in Scheme before we pass it to R, and UN-
PROTECT it in the destructor? I wonder if we’ll have impedence
mismatch between R and Scheme GC. God, I hope not. Or can we
pass primitive types to see and do the primitive -> SEXP calculus in
C?
Look at the lua egg, by the way, for a good, basic, thoughtful API; cf.
bind and dollar. Lua doesn’t drop into C at all, it’s all-dollar. Ah, it’s
all in the associated .c and .h files; wait: that’s the entirety of Lua?
Wow. lua-main.scm is beautiful, but maybe that’s because Lua was
built from the ground up as an embeddable. R, maybe not so much.
Can be coerced, though.
Can we confine the complexities of e.g. SEXP to embedded C; or do
we have to go through the whole define-foreign-record-type rig-
marole?
See RPythonConverters.c, incidentally, for some conversion goodness.
Mother-fuckers:
typedef SEXP USER_OBJECT_;
Uses getListElementType to simplify to vectors when there are ho-
mogenous datatypes; we can just use native vectors, right? Native
vectors are of course heterogenous in Scheme.
Can we write write a C-function that takes a C_word (representing
a list somehow), and returns a C_word representing the R-object?
RSPython has an optional translate thing. Should we force the user
to package things as R-objects; or should we translate transparently?
I’m loathe to do the latter; initial hypothesis, however?
See chicken.h for things like C_inline C_word C_a_i_list1(C_word
**a, int n, C_word x1), which create lists;
On PROTECT: they unprotect return before return. How much of
the conversion can we do in Scheme? Seems more flexible that way.
Is C_c_pointer useful for returning opaque R-objects, by the way?
C_pointer_to_object?
144
There’s a reason, incidentally, why things come up as both vectors and
lists:
#define IS_LIST(x) IS_VECTOR(x)
How do we distinguish true lists from vectors? Maybe try the
Rf_isList, Rf_isPairList, &c. from Rinternals.h.
Here’s the list:
Rboolean Rf_isArray(SEXP);
Rboolean Rf_isFactor(SEXP);
Rboolean Rf_isFrame(SEXP);
Rboolean Rf_isFunction(SEXP);
Rboolean Rf_isInteger(SEXP);
Rboolean Rf_isLanguage(SEXP);
Rboolean Rf_isList(SEXP);
Rboolean Rf_isMatrix(SEXP);
Rboolean Rf_isNewList(SEXP);
Rboolean Rf_isNumber(SEXP);
Rboolean Rf_isNumeric(SEXP);
Rboolean Rf_isPairList(SEXP);
Rboolean Rf_isPrimitive(SEXP);
Rboolean Rf_isTs(SEXP);
Rboolean Rf_isUserBinop(SEXP);
Rboolean Rf_isValidString(SEXP);
Rboolean Rf_isValidStringF(SEXP);
Rboolean Rf_isVector(SEXP);
Rboolean Rf_isVectorAtomic(SEXP);
Rboolean Rf_isVectorList(SEXP);
Rboolean Rf_isVectorizable(SEXP);
Oh, yeah: there was that whole newList thing. Can we cherry-pick
the things that have obvious Scheme counterparts
From R-lang:
Matrices and arrays are simply vectors with the attribute
dim and optionally dimnames attached to the vector.
Factors sounds like enums:
Factors are currently implemented using an integer array
to specify the actual levels and a second array of names
that are mapped to the integers.
145
I wonder if Rf_isFrame applies to dataframes.
This is cool, by the way; these guys dispatch on TYPEOF(el):
#include <R_ext/PrtUtil.h>
146
Here’s another list:
#undef isNull
#define isNull(s) (TYPEOF(s) == NILSXP)
#undef isSymbol
#define isSymbol(s) (TYPEOF(s) == SYMSXP)
#undef isLogical
#define isLogical(s) (TYPEOF(s) == LGLSXP)
#undef isReal
#define isReal(s) (TYPEOF(s) == REALSXP)
#undef isComplex
#define isComplex(s) (TYPEOF(s) == CPLXSXP)
#undef isExpression
#define isExpression(s) (TYPEOF(s) == EXPRSXP)
#undef isEnvironment
#define isEnvironment(s) (TYPEOF(s) == ENVSXP)
#undef isString
#define isString(s) (TYPEOF(s) == STRSXP)
#undef isObject
#define isObject(s) (OBJECT(s) != 0)
And yet another unadorned list:
#define isArray Rf_isArray
#define isBasicClass Rf_isBasicClass
#define isComplex Rf_isComplex
#define isEnvironment Rf_isEnvironment
#define isExpression Rf_isExpression
#define isFactor Rf_isFactor
#define isFrame Rf_isFrame
#define isFree Rf_isFree
#define isFunction Rf_isFunction
#define isInteger Rf_isInteger
#define isLanguage Rf_isLanguage
#define isList Rf_isList
#define isLogical Rf_isLogical
#define isSymbol Rf_isSymbol
#define isMatrix Rf_isMatrix
#define isNewList Rf_isNewList
#define isNull Rf_isNull
#define isNumeric Rf_isNumeric
147
#define isNumber Rf_isNumber
#define isObject Rf_isObject
#define isOrdered Rf_isOrdered
#define isPairList Rf_isPairList
#define isPrimitive Rf_isPrimitive
#define isReal Rf_isReal
#define isS4 Rf_isS4
#define isString Rf_isString
#define isTs Rf_isTs
#define isUnordered Rf_isUnordered
#define isUnsorted Rf_isUnsorted
#define isUserBinop Rf_isUserBinop
#define isValidString Rf_isValidString
#define isValidStringF Rf_isValidStringF
#define isVector Rf_isVector
#define isVectorAtomic Rf_isVectorAtomic
#define isVectorizable Rf_isVectorizable
#define isVectorList Rf_isVectorList
Here’s a list of SEXP-types that we could dispatch on via e.g. switch
or some data-driven mechanism:
#define NILSXP 0 /* nil = NULL */
#define SYMSXP 1 /* symbols */
#define LISTSXP 2 /* lists of dotted pairs */
#define CLOSXP 3 /* closures */
#define ENVSXP 4 /* environments */
#define PROMSXP 5 /* promises: [un]evaluated closure arguments */
#define LANGSXP 6 /* language constructs (special lists) */
#define SPECIALSXP 7 /* special forms */
#define BUILTINSXP 8 /* builtin non-special forms */
#define CHARSXP 9 /* "scalar" string type (internal only)*/
#define LGLSXP 10 /* logical vectors */
#define INTSXP 13 /* integer vectors */
#define REALSXP 14 /* real variables */
#define CPLXSXP 15 /* complex variables */
#define STRSXP 16 /* string vectors */
#define DOTSXP 17 /* dot-dot-dot object */
#define ANYSXP 18 /* make "any" args work.
Used in specifying types for symbol
registration to mean anything is okay */
148
#define VECSXP 19 /* generic vectors */
#define EXPRSXP 20 /* expressions vectors */
#define BCODESXP 21 /* byte code */
#define EXTPTRSXP 22 /* external pointer */
#define WEAKREFSXP 23 /* weak reference */
#define RAWSXP 24 /* raw bytes */
#define S4SXP 25 /* S4, non-vector */
149
return FALSE;
}
}
150
INLINE_FUN SEXP mkNamed(SEXPTYPE TYP, const char **names)
{
SEXP ans, nms;
int i, n;
151
proportional to scope:
length(name(variable)) ~ log(countlines(scope(variable)))
Need an Rvalue, Rfind, or Rget to look up variables in a given envi-
ronment (default to e.g. R_GlobalEnv).
Cairo is replete with instances of allocating f64-vectors for e.g. pointers
in Scheme before descending into C. We could probably do that before-
hand, since we know the arguments; not necessarily the size, though?
Sure: if we do the translation from within Scheme. Nursery takes care
of garbage collection, I believe.
(use debug
miscmacros)
(define (give-me-some-shit)
((foreign-primitive
scheme-object
()
"C_word *value = C_alloc(C_SIZEOF_FLONUM);"
"return(C_flonum(&value, 1.234));")))
(dotimes (i 100000)
(debug (give-me-some-shit)))
Is this interesting? I like s11n-c.c. What’s the equivalent of symbols
in R, by the way?
Disjoint predicates in Scheme:
– boolean?
– symbol?
– char?
– vector?
– procedure?
– pair?
– number?
– string?
– port?
152
How many of them can we support without trickery? Yet to do: char,
pair, symbol, vector.
To return opaque pointers, do we need C_pointer_to_object? How
do we tag it? C_taggedmpointer, C_taggedmpointer_or_false.
C_make_pointer, C_make_tagged_pointer. Shit: they’re CPS.
Length vs. truelength; R_PreserveObject and R_ReleaseObject. We
might have to use them.
Vector-type calculus:
SEXP attribute_hidden do_c_dflt(SEXP call, SEXP op, SEXP args, SEXP env)
{
SEXP ans, t;
int mode, recurse, usenames;
struct BindData data;
struct NameData nameData;
usenames = 1;
recurse = 0;
/* this was only done for length(args) > 1 prior to 1.5.0,
_but_ `recursive' might be the only argument */
PROTECT(args = ExtractOptionals(args, &recurse, &usenames, call));
data.ans_flags = 0;
data.ans_length = 0;
data.ans_nnames = 0;
153
else data.ans_nnames = HasNames(CAR(t));
}
AnswerType(CAR(t), recurse, usenames, &data);
}
mode = NILSXP;
if (data.ans_flags & 512) mode = EXPRSXP;
else if (data.ans_flags & 256) mode = VECSXP;
else if (data.ans_flags & 128) mode = STRSXP;
else if (data.ans_flags & 64) mode = CPLXSXP;
else if (data.ans_flags & 32) mode = REALSXP;
else if (data.ans_flags & 16) mode = INTSXP;
else if (data.ans_flags & 2) mode = LGLSXP;
else if (data.ans_flags & 1) mode = RAWSXP;
154
ComplexAnswer(args, &data, call);
else if (mode == REALSXP)
RealAnswer(args, &data, call);
else if (mode == RAWSXP)
RawAnswer(args, &data, call);
else if (mode == LGLSXP)
LogicalAnswer(args, &data, call);
else /* integer */
IntegerAnswer(args, &data, call);
args = t;
/* Build and attach the names attribute for the returned object. */
155
This can rapidly become tedious, and the following
function (based on one in package stats) is very use-
ful:
/* get the list element named str, or return NULL */
if(!isString(name) || length(name) != 1)
error("name is not a single string");
if(!isEnvironment(rho))
error("rho should be an environment");
ans = findVar(install(CHAR(STRING_ELT(name, 0))), rho);
Rprintf("first value is %f\n", REAL(ans)[0]);
return(R_NilValue);
}
void defineVar(SEXP symbol, SEXP value, SEXP rho);
void setVar(SEXP symbol, SEXP value, SEXP rho);
– Copying objects
156
It is safe to modify the value of any SEXP for which
NAMED(foo) is zero, and if NAMED(foo) is two, the
value should be duplicated (via a call to duplicate)
before any modification. Note that it is the responsi-
bility of the author of the code making the modifica-
tion to do the duplication, even if it is x whose value
is being modified after y <- x.
– Evaluating R expressions
157
SETCADR(R_fcall, VECTOR_ELT(list, i));
SET_VECTOR_ELT(ans, i, eval(R_fcall, rho));
}
setAttrib(ans, R_NamesSymbol, getAttrib(list, R_NamesSymbol));
UNPROTECT(2);
return(ans);
}
– Zero finding
SEXP mkans(double x)
{
SEXP ans;
PROTECT(ans = allocVector(REALSXP, 1));
REAL(ans)[0] = x;
UNPROTECT(1);
return ans;
}
for(;;) {
xc = 0.5*(x0+x1);
if(fabs(x0-x1) < tol) return mkans(xc);
158
fc = feval(xc, f, rho);
if(fc == 0) return mkans(xc);
if(f0*fc > 0.0) {
x0 = xc; f0 = fc;
} else {
x1 = xc; f1 = fc;
}
}
}
Good stuff in there with mkans.
– Parsing R
#include <R.h>
#include <Rinternals.h>
#include <R_ext/Parse.h>
SEXP menu_ttest3()
{
char cmd[256];
SEXP cmdSexp, cmdexpr, ans = R_NilValue;
ParseStatus status;
...
if(done == 1) {
PROTECT(cmdSexp = allocVector(STRSXP, 1));
SET_STRING_ELT(cmdSexp, 0, mkChar(cmd));
cmdexpr = PROTECT(R_ParseVector(cmdSexp, -1, &status, R_NilValue));
if (status != PARSE_OK) {
UNPROTECT(2);
error("invalid call %s", cmd);
}
/* Loop is needed here as EXPSEXP will be of length > 1 */
for(R_len_t i = 0; i < length(cmdexpr); i++)
ans = eval(VECTOR_ELT(cmdexpr, i), R_GlobalEnv);
UNPROTECT(2);
}
return ans;
}
– Printing
159
The most useful function for printing from a C rou-
tine compiled into R is Rprintf. This is used in ex-
actly the same way as printf, but is guaranteed to
write to R’s output (which might be a GUI console
rather than a file, and can be re-directed by sink).
It is wise to write complete lines (including the “”)
before returning to R. It is defined in Rext /Print.h.
The function REprintf is similar but writes on the
error stream (stderr) which may or may not be dif-
ferent from the standard output stream.
Functions Rvprintf and REvprintf are analogues us-
ing the vprintf interface. Because that is a C99 inter-
face, they are only defined by Rext /Print.h in C++
code if the macro RUSEC99INCXX is defined when it
is included.
Hence, we can define callbacks like e.g.:
extern void (*ptr_R_WriteConsole)(const char *, int);
I vaguely remember doing this for rJava.
– The headers
160
your garph is a cycle, or is made by putting two smaller graphs
together at a single node, because then the puzzle is trivial, or
degenerates into the two smaller puzzles corresponding to the
two smaller graphs.
Rick Wilson has proved the remarkable theorem that for every
non-degenerate case but one we get either the full symmetric
group (if some circuit is odd) or the alternating group (other-
wise). The single exception is the graph of the Tricky Six Puzzle
for which the group consists of all possible Möbius transforma-
tions
ax + b
x→ mod 5
cx + d
See Brook’s theorem; also, Richard Wilson’s paper.
See Slocum’s review:
The set of solvable configurations can be easily described using
the theory of even and odd permutations. First, it should be
clear that the set of all solvable configurations are precisely those
that can be reached starting from the canonical state, since each
move is reversible. Second, let us imagine the blank space to
contain a block that we will call the “blank block.” Then each
move consists of swapping the blank with one of the blocks that
is adjacent to it horizontally or vertically. Third, let us focus
on the set of attainable configurations where the blank lies in
the lower right corner. Then the question is which of the 15!
permutations of 16 blocks that fix the blank can be attained,
starting from the canonical configuration.
Let us consider an arbitrary sequence of moves that returns the
blank to its initial position. Since each move swaps the blank
with another block, we may write the resulting permutation as
a product of transpositions, one per move. Now color the spaces
of the board white and black in a checkerboard pattern, with the
lower right corner colored black. Since the color of the square
occupied by the blank switches with each move and it starts
and ends on black, it must make an even number of moves, so
the resulting permutation may be written as a product of an
even number of transpositions. Now recall the well-known fact
that every permutation can be written as a product of either
an even or odd number of transpositions, but not both. Then
161
the argument we just made shows that no odd permutation that
fixes the blank is attainable. It is also the case that every even
permutation that fixes the blank is attainable, although that
result is far less obvious. To complete the picture, a configuration
of the puzzle with the blank in an arbitrary position is obtainable
from the canonical configuration if and only if either the blank
is on a black square and the permutation is even, or the blank
is on a white square and the permutation is odd. It is against
this mathematical backdrop that the remarkable history of the
15 puzzle plays itself out.
Unfortunately, Hess also makes several small mathematical mis-
steps. In his exposition of Johnson’s proof, he defines a permu-
tation of n objects as being even or odd according to the parity
of the number of cycles in its cycle representation. Luckily, this
turns out to coincide with the usual definition when n is even,
as it is for the case under consideration, namely the 16 spaces in
the 15 puzzle. But when n is odd, this definition conflicts with
the usual one. In explaining Wilson’s result, he omits an im-
portant hypothesis of the theorem, namely that the graph under
consideration contain no cut vertex. He also dismisses papers
by Edward Spitznagel and myself as “essentially following John-
son’s approach,” even though both of these papers actually give
alternate proofs of Story’s result that all even permutations are
attainable. Much of the mathematical literature on the 15 puzzle
obscures the distinction between proving that no odd permuta-
tion is attainable and proving that all even permutations are
attainable, so I was sad to see that this book did not do more to
clarify that point.
See parity of a permutation:
The identity permutation is an even permutation. An even per-
mutation can be obtained from the identity permutation by an
even number of exchanges (called transpositions) of two ele-
ments, while an odd permutation can be obtained by an odd
number of transpositions.
The following rules follow directly from the corresponding rules
about addition of integers:
• the composition of two even permutations is even
162
• the composition of two odd permutations is even
• the composition of an odd and an even permutation is odd
From these it follows that
• the inverse of every even permutation is even
• the inverse of every odd permutation is odd
Considering the symmetric group Sn of all permutations of the
set {1,�…,�n}, we can conclude that the map sgn: Sn → {−1,�1}�
that assigns to every permutation its signature is a group homo-
morphism.
Furthermore, we see that the even permutations form a subgroup
of Sn. This is the alternating group on n letters, denoted by An.
It is the kernel of the homomorphism sgn. The odd permuta-
tions cannot form a subgroup, since the composite of two odd
permutations is even, but they form a coset of An (in Sn).
If n > 1�, then there are just as many even permutations in Sn as
there are odd ones; consequently, An contains n!/2 permutations.
[The reason: if � is even, then (1�2)�� is odd; if � is odd, then (1�2)��
is even; the two maps are inverse to each other.]
A cycle is even if and only if its length is odd. This follows from
formulas like (a�b�c�d�e) = (d�e) (c�e) (b�e) (a�e)
In practice, in order to determine whether a given permutation is
even or odd, one writes the permutation as a product of disjoint
cycles. The permutation is odd if and only if this factorization
contains an odd number of even-length cycles.
Another method for determining whether a given permutation is
even or odd is to construct the corresponding Permutation ma-
trix and compute its determinant. The value of the determinant
is same as the parity of the permutation.
Every permutation of odd order must be even; the converse is
not true in general.
On the solvability of 15:
Johnson & Story (1879) used a parity argument to show that
half of the starting positions for the n-puzzle are impossible to
resolve, no matter how many moves are made. This is done by
163
considering a function of the tile configuration that is invari-
ant under any valid move, and then using this to partition the
space of all possible labeled states into two equivalence classes
of reachable and unreachable states.
The invariant is the parity of the permutation of all 16 squares
plus the parity of the taxicab distance (number of rows plus
number of columns) of the empty square from the lower right
corner. This is an invariant because each move changes both the
parity of the permutation and the parity of the taxicab distance.
In particular if the empty square is in the lower right corner
then the puzzle is solvable if and only if the permutation of the
remaining pieces is even.
Johnson & Story (1879) also showed that the converse holds on
boards of size m×n provided m and n are both at least 2: all
even permutations are solvable. This is straightforward but a
little messy to prove by induction on m and n starting with
m=n=2. Archer (1999) gave another proof, based on defining
equivalence classes via a hamiltonian path.
From Archer:
The reason for this hysteria, of course,is that Loyd’s puzzle has
no solution. Each move causes a transposition the 16 blocks
(where the empty square is considered to contain a blank block),
and for the blank to end up in the lower right corner requires an
even number of moves, so the resulting permutation is even.
Figure 2. The dashed line and the numbers in the corner of each
cell indicate a special ordering the cells that we use to define
equivalence classes of placements.
[NB: We’re looking for equivalence classes.]
Thus, given any two placements Pl1 and Pl2 , belonging to con-
figurations Cf1 and Cf2 , respectively, Pl2 is obtainable from Pl1
if and only if Cf2 is an even permutation Cf1 . Stated directly
terms of the placements, we see that if Pl1 and Pl2 have the
blank in the same cell then Pl2 is obtainable from Pl1 if and
only if Pl2 is an even permutation the 15 numbered blocks in
Pl1 . Let n be the of number of moves the blank cell in Pl1 is
away from the blank cell in Pl2 . Since each move of the blank
block causes a transposition two blocks, then for n odd of (re-
164
spectively even) Pl2 is obtainable from Pl1 if and only if Pl2 is
an odd (respectively even) permutation of the 16 blocksin Pl1 .
From Story:
From which it follows (since no arrangement whose order is pos-
itive can be changed into one whose order is negative, or vice
versa) that every arrangenment whose order is positive, and only
such, can be converted into the standard arrangement. This is
the desired criterion for the possibility of a standard solution.
We may divide all possible boards into two classes, regarding as
of the first class a board for which the number just found is even,
and as of the second class one for which this number is odd.
We have then this rule: On a board of the first class a given
arrangemnent can or cannot be converted into the natural ar-
rangement, according as its order is even or odd; but on a board
of the second class a given arrangemnent can or cannot be con-
verted into the natural arrangement, according as its order is
odd or even.
Let’s investigate writing the permutation as a product of disjoint cycles:
the permutation is odd if this factorization contains an odd number of even-
length cycles.
Wolfram has a better article on cycles, incidentally.
165
41.6 TODO A listing environment
The former produces nice hubs and cycles, but the problem of aligning
complementary directions (like graph-coloring?) is NP-complete. Resorting
to depth-first with no cycles; would have to have an exponential adjunct for
cycles.
Way to quantify the difference between the two with respect to e.g. average
degree of nodes?
166
The random reflex agent might do well in a completely connected graph,
incidentally, since it doesn’t have to make any decisions about where to go;
it might fair poorly in a linear environment, since at least half of the time
it will be unable to move.
It turns out that random graphs are an open topic of research; with
the Erdős–Rényi, Watts-Strogatz, Barabási–Albert models. Let’s go with
Barabási–Albert: it looks relatively easy to implement and has interesting
clustering properties (power-law degree-distributions).
Confer Gabor Csardi’s implementation of Barabási-Albert, which has zero-
appeal and power-of-preferential-attachment parameters; it also begins with
one node. It can dispense with the two nodes from WP because of the zero-
appeal parameter. It also does directed and undirected; and distinguishes,
somehow, between in- and both in-and-out-degrees. It also does not divide
by the total number of nodes (or is it the total number of edges?).
(Holy shit: this chick implements all three in Perl; she claims that you divide
by all the degrees in the graph, which is consistent with WP’s notation.
With Gabor Csardi, she also has a parameter m of edges to add at each
time; her graph also starts with no edges, but has m0 nodes at t0 ; they
both discuss the problem of multiple edges: it may not suffice to merely not
generate them, you should detect and ignore them. No, wait; she’s talking
about something different: “To keep the code simple, I don’t test whether
the m nodes that are connecting to the new node are all different.” Gabor,
on other hand, talks about an algorithm which “never generates multiple
edges.” Wait again: she’s talking about the same thing. Should we ignore
duplicate links and add another; or just refrain from duplicating? Damn.)
It sounds like we can create a connected-graph if we start with the seed-
graph-of-degree-one hack; using zero-appeal, we might have disconnected
but dirty squares. Disconnected but dirty squares are romantic, however;
and their effect should fall prey to the law of large numbers if we repeat it
enough times.
We are, of course, constrained by the relative directions up, down, left and
right; the degree of any node is therefore ≤ 4. In that sense, maybe the
matrix is a reasonable representation? Barabási-Albert isn’t going to be
able to create hubs with that sort of upper bound. Push forward anyway,
or capitulate to grid?
I like the idea of a graph, each of whose nodes contains 0 ≤ neighbors ≤ 4;
which are assigned the arbitrary designations up, down, left and right. The
167
only problem is that, for experimentation, we’re going to have to generate
a complete grid-like world. That is a pain in the ass.
I take that back: can we create something like a torus or a sphere where the
grid wraps around; that is, where every square is totally connected (edges
being connected to the opposite edge)? A random reflex-vacuum might be
able to do well there.
If with modified Barabási-Albert we’re going to have disconnected nodes,
by the way, if e.g. pi is false for every node; or are we guaranteed to connect
m nodes every time? See e.g.:
The generalized creation process of a scale-free-network is defined
by Barabasi and Albert [BaBo03]: Start with a small number of
nodes m0. During each step, insert a new node with connections
to m ≤ m0 existing nodes. The probability Pv of node v being
connected to a new node depends on its degree jv . The higher
its degree, the greater the probability that it will receive new
connections (“the rich get richer”).
Since there’s an upper bound on the dividend, we’re going to degenerate
into a network whose edges are picked at random; and, worse yet, might end
up with many unconnected nodes.
Can’t we just create the world initially, by the way, instead of creating the
graph and then the world? Interestingly, the relative directions won’t meet
up: up is paired with left, &c. Is such a world physically possible? Yes, if
the relative directions for each location are different; they’re relative, after
all. It shouldn’t matter to the agents.
It’s a weird world, though.
I want to have an environment that spits out an animated graph of where
the agent is (green), which cells are dirty (grey), which are clean (white).
(use aima
aima-vacuum
debug
extras
files
lolevel
posix
random-bsd
srfi-1
srfi-69)
168
(define (connect! graph connectend connector)
(hash-table-update!/default
graph
connectend
(lambda (neighbors)
(lset-adjoin eq? neighbors connector))
'()))
169
(do ((line (read-line) (read-line)))
((eof-object? line))
(write-line line))))
(close-input-port input)))
170
;;; input?
(define barabási-albert-graph
(case-lambda
(() (barabási-albert-graph (default-n-nodes) (default-m)))
((n-nodes m)
(let ((graph (make-hash-table)))
(biconnect! graph (make-node) (make-node))
(do ((n-nodes n-nodes (- n-nodes 1)))
((zero? n-nodes))
(let ((new-node (make-node)))
(do ((from (hash-table-keys graph) (cdr from))
(m m (- m 1)))
((or (null? from) (zero? m)))
(let ((sum-degrees (sum-degrees graph)))
(let* ((from (car from))
(degrees-from (length (hash-table-ref graph from))))
(if (and (< degrees-from 4)
(< (random-real) (/ degrees-from sum-degrees)))
(biconnect! graph from new-node)))))))
graph))))
;; (with-output-to-file "graph.pdf"
;; (lambda () (write-graph-as-pdf (barabási-albert-graph))))
;; (display-pdf "graph.pdf")
(define-record no-passage)
(define no-passage (make-no-passage))
(define passage? (complement no-passage?))
(define up 2)
(define up? (cute = <> 2))
(define down 3)
(define down? (cute = <> 3))
(define-record location
status
left
right
171
up
down)
(define-record-printer location
(lambda (location output)
(display (record->vector location) output)))
172
(lambda (name location)
(let ((color
(cond ((eq? (agent-location agent) name) "green")
((clean? (location-status location)) "white")
(else "gray"))))
(format #t "~a [fillcolor=~a];" name color)))))
(define write-world-as-dot
(case-lambda
((world agent) (write-world-as-dot world agent #f))
((world agent step)
(write-world-as-dot world agent step (default-height) (default-width)))
((world agent step height width)
(write-dot-preamble agent step height width)
(write-dot-nodes world agent)
(write-dot-edges world)
(write-dot-postscript))))
173
(with-output-to-port output
;; Do we really need a blank label, for some reason?
(lambda () (write-world-as-dot world agent #f #f #f)))
(flush-output output)
(close-output-port output)
(close-input-port input)))
174
start
(lambda (location clean?)
(if clean?
(list-ref '(left right up down) (random 4))
'suck))))
175
(let ((clean-locations
;; Quicker with map and apply?
(hash-table-fold
world
(lambda (name location clean-locations)
(if (clean? (location-status location))
(+ clean-locations 1)
clean-locations))
0)))
(agent-score-set! agent (+ (agent-score agent) clean-locations)))))
176
(define (make-stateful-graph-agent start)
(make-reflex-agent
start
(let ((world (make-hash-table)))
(lambda (location clean?)
'right))))
177
g17316 [fillcolor=white]
g17329 [fillcolor=gray]
g17336 [fillcolor=gray]
g17357 [fillcolor=white]
g17226 [fillcolor=white]
g17227 [fillcolor=gray]
g17229 [fillcolor=white]
g17231 [fillcolor=gray]
g17232 [fillcolor=green]
g17234 [fillcolor=gray]
g17235 [fillcolor=gray]
g17237 [fillcolor=gray]
g17238 [fillcolor=white]
g17239 [fillcolor=gray]
g17245 [fillcolor=white]
g17258 [fillcolor=white]
g17259 [fillcolor=gray]
g17262 [fillcolor=gray]
g17263->g17258 [label=d]
g17270->g17262 [label=l]
g17273->g17245 [label=u]
g17278->g17245 [label=l]
g17298->g17262 [label=u]
g17310->g17258 [label=l]
g17310->g17262 [label=u]
g17316->g17259 [label=l]
g17329->g17258 [label=u]
g17336->g17259 [label=u]
g17357->g17259 [label=u]
g17226->g17229 [label=l]
g17226->g17227 [label=r]
g17227->g17226 [label=d]
g17229->g17226 [label=l]
g17229->g17234 [label=r]
g17229->g17232 [label=u]
g17229->g17231 [label=d]
g17231->g17229 [label=l]
g17232->g17229 [label=l]
g17232->g17238 [label=u]
g17234->g17235 [label=l]
178
g17234->g17229 [label=r]
g17234->g17239 [label=u]
g17234->g17237 [label=d]
g17235->g17234 [label=u]
g17237->g17245 [label=l]
g17237->g17234 [label=r]
g17237->g17239 [label=u]
g17238->g17259 [label=l]
g17238->g17258 [label=u]
g17238->g17232 [label=d]
g17239->g17234 [label=l]
g17239->g17237 [label=r]
g17245->g17278 [label=l]
g17245->g17273 [label=r]
g17245->g17262 [label=u]
g17245->g17237 [label=d]
g17258->g17310 [label=l]
g17258->g17263 [label=r]
g17258->g17329 [label=u]
g17258->g17238 [label=d]
g17259->g17357 [label=l]
g17259->g17336 [label=r]
g17259->g17316 [label=u]
g17259->g17238 [label=d]
g17262->g17245 [label=l]
g17262->g17310 [label=r]
g17262->g17298 [label=u]
g17262->g17270 [label=d]
}
Converting the gif output to an avi à la mencoder:
mencoder graph.gif -ovc lavc -o graph.avi
179
- make-seed-world
- let
- world make-hash-table
- root make-node
- child make-node
- biconnect! world root child (random 4)
- values world root
- # Let's recreate location-left{,-set!}, &c.
- make-preferential-depth-first-world n-nodes
- receive world root
- make-seed-world
- let iter
- node root
- n-nodes n-nodes
- n-degrees 0
- if zero? n-nodes
- world
- hash-table-update
- world
- node
- lambda location
- # Wouldn't we make it easier on ourselves by having
the children of location be a tetradic vector with
left, right, up and down? Yes, a thousand times yes!
- # If barabási dictates add a child, add it; otherwise,
take a random path. This random path could backtrack,
or it could continue on.
- # Keep adding until we have the requisite number of
nodes.
- let
- degrees-from
- vector-fold
- lambda (i degrees passage) if (no-passage? passage)
degrees (+ degrees 1)
- 0
- location-passages location
- if
- and
- < degrees-from 4
- < (random-real) (/ degrees-from n-degress)
180
- let
- directions-from
- vector-fold
- lambda (direction directions-from passage) if
(no-passage? passage) (cons direction
directions-from) directions-from
- '()
- location-passages location
- direction (list-ref directions-from (random
(length directions-from)))
- let new-node (make-node)
- # Maybe biconnect! can take care of adding the
node to the world?
- biconnect! world node new-node direction
- iter new-node (- n-nodes 1) (+ n-degrees 2)
- let
- passages-from
- vector-fold
- lambda (direction passages-from passage) if
(passage? passage) (cons passage
passages-from) passages-from
- '()
- location-passages location
- passage (list-ref
- make-location
- dirty
- #(no-passage no-passage no-passage no-passage)
(use aima
aima-vacuum
debug
files
posix
(prefix random-bsd bsd-)
srfi-69
stack
vector-lib)
(define-record no-passage)
(define no-passage (make-no-passage))
181
(define passage? (complement no-passage?))
(define up 2)
(define up? (cute = <> 2))
(define down 3)
(define down? (cute = <> 3))
(define-record location
status
neighbors)
(define-record-printer location
(lambda (location output)
(display (record->vector location) output)))
182
(define (make-dirty-location)
(make-location dirty
(vector no-passage
no-passage
no-passage
no-passage)))
(define (make-seed-world)
(let ((world (make-hash-table))
(start (make-node))
(neighbor (make-node)))
(connect! world start neighbor (random-direction))
world))
183
(lambda (location clean?)
(if clean?
(list-ref '(left right up down) (random-direction))
'suck))))
184
(if (zero? n-nodes)
world
(let ((location
(hash-table-ref/default
world
node
(make-dirty-location))))
(let ((n-neighbors (n-neighbors location)))
(if (and (< n-neighbors 4)
(< (bsd-random-real) (/ n-neighbors n-degrees)))
(let* ((new-directions
(vector-fold
(lambda (direction directions neighbor)
(if (no-passage? neighbor)
(cons direction directions)
directions))
'()
(location-neighbors location)))
(new-direction
(list-ref
new-directions
(bsd-random (length new-directions)))))
;; To make this Barabási-like, we could try to
;; pick a preëxisting node; and, failing that,
;; produce one.
;;
;; Why not just produce a direction-sensitive
;; Barabási? Now that we have neighbors as a
;; vector, it should be less unpleasant.
;;
;; To connect this node to a preëxisting one,
;; however; we'd have to find nodes with
;; compatible, available directions.
;;
;; We could produce a tree, of course, and
;; randomly create appropriate cycles.
(let ((new-node (make-node)))
(connect! world node new-node new-direction)
(iter new-node (- n-nodes 1) (+ n-degrees 2))))
(let* ((neighbors
185
(vector-fold
(lambda (direction neighbors neighbor)
(if (passage? neighbor)
(cons neighbor neighbors)
neighbors))
'()
(location-neighbors location)))
(neighbor
(list-ref neighbors
(bsd-random (length neighbors)))))
(iter neighbor n-nodes n-degrees)))))))))))
186
(height-in-inches (/ height 96)))
(format #t "graph [viewport=\"~a,~a\", size=\"~a,~a!\"];"
(* width-in-inches 72)
(* height-in-inches 72)
width-in-inches
height-in-inches))))
(if step
(format #t "graph [label=\"~aScore: ~a; step: ~a\"]"
(if title (format "~a\\n" title) "")
(agent-score agent)
step)))))
187
(define write-world-as-dot
(case-lambda
((world agent) (write-world-as-dot world agent #f))
((world agent step)
(write-world-as-dot world
agent
step
(default-width)
(default-height)
(default-font-size)
(default-title)))
((world agent step width height font-size title)
(write-dot-preamble agent step width height font-size title)
(write-dot-nodes world agent)
(write-dot-edges world)
(write-dot-postscript))))
(define write-world-as-gif
(case-lambda
((world agent frame gif)
(write-world-as-gif world
agent
frame
gif
(default-width)
(default-height)
188
(default-font-size)
(default-title)))
((world agent frame gif width height font-size title)
(receive (input output id)
(process "neato" `("-Tgif" "-o" ,gif))
(with-output-to-port output
(lambda () (write-world-as-dot world
agent
frame
width
height
font-size
title)))
(flush-output output)
(close-output-port output)
(close-input-port input)))))
189
(if (passage? down)
(agent-location-set! agent down))))
((noop))
((suck)
(location-status-set! (hash-table-ref world node) clean))
(else (error "graph-environment -- Unknown action"))))))
(define make-graph-animating-environment
(case-lambda
((world agent directory)
(make-graph-animating-environment world
agent
directory
(default-width)
(default-height)
(default-font-size)
(default-title)))
((world agent directory width height font-size title)
(let ((frame 0))
(lambda ()
(let ((gif (make-pathname directory (number->string frame) "gif")))
(write-world-as-gif world
agent
frame
gif
width
height
190
font-size
title))
(set! frame (+ frame 1)))))))
191
(vector-fold
(lambda (direction undiscovered-directions neighbor)
(if (unknown? neighbor)
(cons direction undiscovered-directions)
undiscovered-directions))
'()
(location-neighbors location)))
(define-record cycle)
(define cycle (make-cycle))
;;; Dealing with all this move-location punning makes things complex;
;;; we can clean this up a little bit by writing some germane
;;; abstractions on the world.
;;;
;;; We're not dealing with cycles yet, by the way; does this entail
;;; determining whether or not a new node is accounted for in the
;;; world? I believe so.
(define (make-stateful-graph-agent start)
(make-reflex-agent
start
(let ((world (make-hash-table))
(nodes (list->stack (list start)))
192
(moves (make-stack)))
(lambda (node clean?)
(if (stack-empty? nodes)
'noop
(if (not clean?)
'suck
(let ((location
(hash-table-ref/default
world
node
(make-unknown-location clean?))))
;; The following is general house-keeping on the state.
(if (stack-empty? moves)
;; We're dealing with an uninitialized agent: set
;; the world. This could also be a terminal
;; agent, couldn't it? Is there a better place to
;; initialize?
(hash-table-set! world node location)
;; We need to distinguish the case, apparently,
;; where we've just backtracked; this isn't quite
;; the same as a fail-to-move.
;;
;; In 2.12, when we're dealing with a bump
;; sensor, when don't have to play these games
;; with an implicit bump.
(let ((last-move (stack-peek moves)))
(if (eq? last-move 'backtrack)
;; Our position is the result of
;; backtracking; remove the special
;; backtracking move.
(stack-pop! moves)
(if (eq? (stack-peek nodes) node)
;; We tried to move but could not; mark the
;; last direction as no-passage.
(let ((last-move (stack-pop! moves)))
(vector-set! (location-neighbors location)
(move->direction last-move)
no-passage))
(let* ((last-node (stack-peek nodes))
;; Need to replace hash-table-ref, &c.
193
;; with something more germane.
(last-location
(hash-table-ref world last-node)))
(if (hash-table-exists? world node)
;; Cycle detected! Push the
;; cycle-sentinel.
(stack-push! nodes cycle)
(begin
;; This is a new node: add it
;; to the world.
(hash-table-set! world node location)
;; Also, add it to the list of
;; interesting nodes.
(stack-push! nodes node)))
;; This location's reverse-move points to
;; the last node.
(vector-set! (location-neighbors location)
(move->direction
(reverse-move last-move))
last-node)
;; The last location's move points to
;; this node.
(vector-set! (location-neighbors
last-location)
(move->direction last-move)
node))))))
;; Are there any other undiscovered passages?
(let ((new-moves (map direction->move
(undiscovered-directions location))))
(if (or (cycle? (stack-peek nodes))
(null? new-moves))
(begin
;; Remove this node from the interesting
;; nodes: it's been thoroughly explored.
(stack-pop! nodes)
(if (stack-empty? moves)
;; No moves lest; let's rest. This may change
'noop
(let ((move (stack-pop! moves)))
;; Push the special backtrack move onto the
194
;; stack; this helps us distinguish the
;; backtracking case from the case where
;; we've hit a wall.
;;
;; The bump-sensor should obviate the
;; need for this, I think; or not.
(stack-push! moves 'backtrack)
;; Go back the way we came.
(reverse-move move))))
(let ((move (list-ref new-moves
(bsd-random (length new-moves)))))
(stack-push! moves move)
move))))))))))
(define (make-known-world)
(let ((world (make-hash-table))
(a (make-node))
(b (make-node))
(c (make-node))
(d (make-node))
(e (make-node))
(f (make-node)))
(connect! world 'a 'b right)
(connect! world 'b 'c down)
(connect! world 'b 'd right)
(connect! world 'd 'e down)
(connect! world 'e 'f down)
(connect! world 'f 'e right)
(connect! world 'f 'a down)
world))
(define simulate-graph
(case-lambda
((world agent)
(simulate-graph world agent (default-steps)))
((world agent steps)
(simulate-graph world
agent
195
steps
(default-width)
(default-height)
(default-font-size)
(default-title)
(default-file)))
((world agent steps width height font-size title file)
(let ((directory (create-temporary-directory)))
(simulate
;; Order of composition matters, apparently; be thoughtful.
(compose-environments
(make-step-limited-environment steps)
(make-debug-environment agent)
(make-graph-environment world agent)
(make-graph-performance-measure world agent)))
directory))))
(define simulate-graph/animation
(case-lambda
((world agent)
(simulate-graph/animation world agent (default-steps)))
((world agent steps)
(simulate-graph/animation world
agent
steps
(default-width)
(default-height)
(default-font-size)
(default-title)
(default-file)))
((world agent steps width height font-size title file)
(let ((directory (create-temporary-directory)))
(simulate
;; Order of composition matters, apparently; be thoughtful.
(compose-environments
(make-step-limited-environment steps)
;; Can't this contain its finalizer? Maybe even give it the
;; terminal frame?
(make-graph-animating-environment world
agent
196
directory
width
height
font-size
title)
(make-finalizing-environment
(make-animation-finalizer directory file)
steps)
(make-debug-environment agent)
(make-graph-environment world agent)
(make-graph-performance-measure world agent)))
directory))))
197
agent-two
title-two
composite-file
(default-steps)
(/ (default-width) 2)
(default-height)
(/ (default-font-size) 2)))
((world
agent-one
title-one
agent-two
title-two
composite-file
steps
width
height
font-size)
(let ((directory-one
(simulate-comparatively (copy-world world)
agent-one
steps
width
height
font-size
title-one))
(directory-two
(simulate-comparatively world
agent-two
steps
width
height
font-size
title-two)))
(let ((composite-directory (create-temporary-directory)))
(system* "cd ~a && for i in *; do echo $i; convert +append $i ~a/$i ~a/$i; done"
directory-one
directory-two
composite-directory)
((make-animation-finalizer composite-directory composite-file)))))))
198
(let* ((world (make-preferential-depth-first-world 20))
(start (random-start world)))
(let ((stateful-agent (make-stateful-graph-agent start))
(random-agent (make-randomized-reflex-graph-agent start)))
(parameterize ((default-steps 10)
(randomize! bsd-randomize)
(random-seed 0))
(compare-graphs world
stateful-agent
"Stateful agent"
random-agent
"Random agent"
"composite-agent-harro"))))
199
that's not quite as good as Barabási-Albert in the sense that it
doesn't generate cycles.
- # On the other hand, wouldn't it have been equivalent to store
the desirable node and the direction required to get there? I
still need the inverse of the current direction to backtrack, I
believe.
- # It would appear as though we're going to have to push a list
of interesting vertices onto the stack as well as the actions
required to get here; this notion, therefore, of visit node
entails: back-tracking until we find it.
- path ()
- visitanda (start)
- last-action #f
- lambda node clean?
- if null? visitanda
- noop
- if dirty?
- # This is where pre-/post-order becomes interesting;
pre-order it, so we can maximize our score.
- clean
- begin
- hash-table-update!/default
- world
- node
- lambda location
- location-set-status (if clean? clean dirty)
- let*
- neighbors (location-neighbors location)
- if eq? last-node node
- case last-action
- left vector-set! neighbors left no-passage
- right vector-set! neighbors right no-passage
- up vector-set! neighbors up no-passage
- down vector-set! neighbors down no-passage
- let last-node (car visitandum)
- case last-action
- left vector-set! neighbors right last-node
- right vector-set! neighbors left last-node
- up vector-set! neighbors down last-node
- down vector-set! neighbors up last-node
200
- set! visitanda (cons node visitanda)
- location
- # Let's nominate this make-unknown-location (as opposed to:
make-dirty-location).
- make-location clean? #(unknown unknown unknown unknown)
41.8.7 DONE Figure out the correct aspect ratio for youtube.
Can we create a jekyll-based blog from this org-doc and push it to the github
pages?
That would be beautiful: we could get rid of the generated pdf and might
not be confined to the relative obscurity of org (relative to, say, html).
201
41.10 TODO Some sort of blog post or other publicity?
See this survey. Algorithm2e isn’t bad; doesn’t seem to have a function,
though. Pseudocode seems to be relatively natural; even if the output is a
little ugly.
The alternative, I suppose, is straight up lists.
Algorithm2e has to be wrapped in dollars, which sucks; also: bizarre camel-
case macros. Looks good, otherwise. Has no functions, apparently, either.
202