ROUGH IDEA FOR CONSTRAINTS PACKAGE:

A *constraint network* is a collection of *variables* and *constraint
arcs*.

Each *variable* has a name (a symbol or number) and a finite domain (a
list of acceptable values).

Each *constraint arc* has a pair of variables (between which the
constraint is enforced), a string description of the arc (for
debugging), a *predicate* that checks if the constraint is satisfied,
and a list of parameters that can affect the behavior of the
predicate.

Constraint-based reasoning consists of two interdigitated phases:
propagation of constraints and search. Each of these phases can take
several forms, depending on the precise reasoning approach being used.
Recall that propagation on its own does *not* ensure identification of
a single, consistent solution; search may be needed to try out various
choices.

In this system, *constraint-search-strategies* contain all the
information about how the system is supposed to make these choices.
There are three components:

- How to pick which variable to search on, at each step. This takes
  the form of a procedure that is called on the network (capturing
  the state of all assignments made so far) and a list of the names
  of the variables that have yet to be assigned by the search
  process.

  In practice, this procedure might compare domain sizes (in an effort
  to first choose the "most constrained" domain) or use other
  sophisticated heuristics. For this problem set, you'll only have to
  use simple, arbitrary strategies that add no heuristic information,
  such as picking the first variable from the list.
  
- How much propagation should be done in between each step. This
  essentially takes the form of a predicate that is given the variable,
  the variable's previous domain size, and the variable's new domain
  size, and returns true if inconsistent domain values should be
  eliminated.

  See propagate-constraints.scm for examples (e.g. the
  *propagate-constraints-strategy::domains-of-one* procedure).

- How to decide when to stop. This takes the form of a predicate
  called on the list of successes so far (where each success consists
  of a list of bindings that solves the reasoning problem). If this
  always returns false, this will cause the reasoner to search until
  no further choices can be made, and return all possible successes.
  If this predicate returns true whenever the list of successes is
  nonempty, the search will stop as soon as the first set of
  satisfactory bindings is found.

Constraint strategy objects are constructed by the
new-constraint-search-strategy procedure (documented in
constraint-search.scm), with examples such as

*constraint-search-strategy::only-check-assignments*

that does no constraint propagation, and

*constraint-search-strategy::domains-of-one/all*

which propagates constraints when the relevant domain is of size one,
and continues until all solutions have been found.

Note that you will not be able to use the only-check-assignments
strategy until you have completed problem 6.

