main
Matthew Butterick 10 years ago
parent 54b24fce7a
commit 2d5396b249

@ -0,0 +1,164 @@
#lang racket/base
;; Adapted from work by Peter Norvig
;; http://aima-python.googlecode.com/svn/trunk/csp.py
(require racket/list racket/bool racket/contract)
(require "csp-utils.rkt" "csp-search.rkt")
#|
class CSP(search.Problem):
"""This class describes finite-domain Constraint Satisfaction Problems.
A CSP is specified by the following inputs:
vars A list of variables; each is atomic (e.g. int or string).
domains A dict of {var:[possible_value, ...]} entries.
neighbors A dict of {var:[var,...]} that for each variable lists
the other variables that participate in constraints.
constraints A function f(A, a, B, b) that returns true if neighbors
A, B satisfy the constraint when they have values A=a, B=b
In the textbook and in most mathematical definitions, the
constraints are specified as explicit pairs of allowable values,
but the formulation here is easier to express and more compact for
most cases. (For example, the n-Queens problem can be represented
in O(n) space using this notation, instead of O(N^4) for the
explicit representation.) In terms of describing the CSP as a
problem, that's all there is.
However, the class also supports data structures and methods that help you
solve CSPs by calling a search function on the CSP. Methods and slots are
as follows, where the argument 'a' represents an assignment, which is a
dict of {var:val} entries:
assign(var, val, a) Assign a[var] = val; do other bookkeeping
unassign(var, a) Do del a[var], plus other bookkeeping
nconflicts(var, val, a) Return the number of other variables that
conflict with var=val
curr_domains[var] Slot: remaining consistent values for var
Used by constraint propagation routines.
The following methods are used only by graph_search and tree_search:
actions(state) Return a list of actions
result(state, action) Return a successor of state
goal_test(state) Return true if all constraints satisfied
The following are just for debugging purposes:
nassigns Slot: tracks the number of assignments made
display(a) Print a human-readable representation
>>> search.depth_first_graph_search(australia)
<Node (('WA', 'B'), ('Q', 'B'), ('T', 'B'), ('V', 'B'), ('SA', 'G'), ('NT', 'R'), ('NSW', 'R'))>
"""
|#
(define (init csp vars domains neighbors constraints)
;; Construct a CSP problem. If vars is empty, it becomes domains.keys().
(define vars (if (null? vars) (hash-keys domains) vars))
(hash-set*! csp 'vars vars 'domains domains
'neighbors neighbors 'constraints constraints
'initial null 'curr_domains null 'nassigns 0))
(define (assign csp var val assignment)
;; Add {var: val} to assignment; Discard the old value if any.
(hash-set! assignment var val)
(hash-update! csp 'nassigns add1))
(define (unassign csp var assignment)
;; Remove {var: val} from assignment.
;; DO NOT call this if you are changing a variable to a new value;
;; just call assign for that.
(hash-remove! csp var))
(define (nconflicts csp var val assignment)
;; Return the number of conflicts var=val has with other variables.
(define (conflict var2)
(and (hash-has-key? assignment var2)
(not ((hash-ref csp 'constraints) var val var2 (hash-ref assignment var2)))))
(length (filter-not false? (map conflict (hash-ref (hash-ref csp 'neighbors) var)))))
(define (display csp assignment)
;; Show a human-readable representation of the CSP.
(displayln (format "CSP: ~a with assignment: ~a" csp (hash-ref csp assignment))))
(define (actions csp state)
;; Return a list of applicable actions: nonconflicting
;; assignments to an unassigned variable.
(if (= (length state) (length (hash-ref csp 'vars)))
null
(let ()
(define assignment (make-hash state))
(define var (findf (λ(v) (not (hash-has-key? assignment v))) (hash-ref csp 'vars)))
(map (λ(val) (list var val))
(filter (λ(val) (= 0 (nconflicts csp var val assignment))) (hash-ref (hash-ref csp 'domains) var))))))
#|
def actions(self, state):
"""Return a list of applicable actions: nonconflicting
assignments to an unassigned variable."""
if len(state) == len(self.vars):
return []
else:
assignment = dict(state)
var = find_if(lambda v: v not in assignment, self.vars)
return [(var, val) for val in self.domains[var]
if self.nconflicts(var, val, assignment) == 0]
def result(self, state, (var, val)):
"Perform an action and return the new state."
return state + ((var, val),)
def goal_test(self, state):
"The goal is to assign all vars, with all constraints satisfied."
assignment = dict(state)
return (len(assignment) == len(self.vars) and
every(lambda var: self.nconflicts(var, assignment[var],
assignment) == 0,
self.vars))
## These are for constraint propagation
def support_pruning(self):
"""Make sure we can prune values from domains. (We want to pay
for this only if we use it.)"""
if self.curr_domains is None:
self.curr_domains = dict((v, list(self.domains[v]))
for v in self.vars)
def suppose(self, var, value):
"Start accumulating inferences from assuming var=value."
self.support_pruning()
removals = [(var, a) for a in self.curr_domains[var] if a != value]
self.curr_domains[var] = [value]
return removals
def prune(self, var, value, removals):
"Rule out var=value."
self.curr_domains[var].remove(value)
if removals is not None: removals.append((var, value))
def choices(self, var):
"Return all values for var that aren't currently ruled out."
return (self.curr_domains or self.domains)[var]
def infer_assignment(self):
"Return the partial assignment implied by the current inferences."
self.support_pruning()
return dict((v, self.curr_domains[v][0])
for v in self.vars if 1 == len(self.curr_domains[v]))
def restore(self, removals):
"Undo a supposition and all inferences from it."
for B, b in removals:
self.curr_domains[B].append(b)
## This is for min_conflicts search
def conflicted_vars(self, current):
"Return a list of variables in current assignment that are in conflict"
return [var for var in self.vars
if self.nconflicts(var, current[var], current) > 0]
|#
Loading…
Cancel
Save