----------------------
PIANO FINGERING Domain
----------------------

Thomas Burg,   TBURG@MAX.U.WASHINGTON.EDU
               c473bg

0. Comment on this file
-----------------------
This file serves as a general documentation of the domain.
Before reading all the listings, the author REALLY recommends
at least to skip through this file:

Section                              Changes compared to Phase 2

1. Overview on Domain                None
2. Hours needed for encoding         Positively: YES !
3. Problems supplied by now          Few, 5 new ones
4. Brief discussion of approach      Concept of Cost
                                     Type of Search (important)
5. Operators                         Concept of Cost
6. Comment on generators             Few
7. The Hack Domain                   (New Section)
8. Search-Control Rules              (New Section)

1. The Piano fingering Domain
-----------------------------
The domain deals with the problem of 'piano fingering', a 'real world' problem
in which every piano player - novice or expert - is involved:

The usual notation of pieces for piano pieces (on the so-called 'staff',
consisting of 5 lines, ...) does only contain the information WHAT to play, not
HOW to play it.

In this domain we try to figure out the HOW (i.e. which fingers to take for 
which note) when given the WHAT (the notes to play).

Due to some time constraints, the domain is limited to
- playing pieces with one (right) hand only
- playing pieces only within one (variable) key-signature

2. Hours needed for encoding
----------------------------
By now (Monday, June 2nd):   approx. 90h

3. Problems supplied by now
---------------------------
The following problems are in the directory 'probs':
(note that X stands either for '1' or 'b'. For example the problem
   c-major1.lisp contains MOST-COST 999 as goal, thus leading to the
                 first solution. The problem
   c-majorb.lisp contains MOST-COST 25, which is the minimum solution length
                 for this problem. Thus the domain comes up with a good
                 solution.

c-majorX.lisp
--> Just the C-major scale, one octave ascending and descending

2c-majorX.lisp
--> Same, 2 octaves.

hansX.lisp
--> German folk song 'Haenschen Klein' in C-major.

sleepX.lisp
--> German lullaby in A-flat Major

beethovenX.lisp
--> A very small tune of Beethoven in D-flat Major

schumann1.lisp, french1.lisp, tune1.lisp, skip1.lisp, jewish1.lisp
--> More problems, only leading to first solution

4. Brief discussion of approach
-------------------------------
0) 'Cost'
   We added a new 'feature' to the domain since phase 2:
   Following a suggestion by Oren Etzioni 
   (given when talking to him about the domain, but see
      Rich, Knight: Introduction to Artificial Intelligence, p. 189
   as well) 
   we attach a cost to each operator.
   While we play the piece using the different operators (or moves), we
   sum up the cost so far.

1) We represent the 'goal', i.e. the piece to play by a list of predicates
      PLAY-NOTE <note> <number>
   (see comments in hans1.lisp)

   Finally we add a predicate with the maximum cost we allow for playing
   the piece:

      MOST-COST <cost>

2) The scale in which a piece is to be played is specified in the problem
   file and is described by the following two (static!) predicates:
      - (PITCH <note>) means <note> exists in the scale
      - (NEXT-NOTE <note1> <note2>) means that <note2> follows <note1> in
         the scale
   (see comments in scales/c-major.scale)
>>>> Remark: this notation prevents us from implementing
                - scales with different ascending and descending form
                - notes within a piece outside its signature (scale)

3) A 'state' is characterized by
   - the cost so far (see 0))
   - the notes played so far, which have been added to the state (see 1))
   - the position of the fingers on the keyboard, noted with
     (THUMB <note>) (INDEX <note>), ...
   - the number of notes played by now, needed for counting:
     (PLAYED-SO-FAR <number>)

4) Different Operators (=actions on the keyboard) change the state by:
   - changing the position of the hand
   - adding notes played to the state (PLAY-NOTE <note>)
   - changing the cost due to playing the piece that far
   - changing the number of notes played already

5) Type of Search
   We have a very special kind of problem with a strong order in time.
   We do NOT have much subgoal interleaving with the representation as
   above. We rather have a tree with
     - fixed depth = # of nodes to play
     - varying branching factor = # of applicable operators. Might be
       zero with control rules for special nodes.
   The tree grows very big with longer pieces, we cannot search it all.
   PRODIGY is 'just' used to do a 'best-first search with backtracking'.
   So we go down the tree, check if the solution is good enough and
   backtrack eventually.
   The search control rules have two functions:
       - guide the search (PREFER rules)
       - reduce the branching factor at some nodes by REJECTing operators


5. Description of Operators
---------------------------
We have four different groups of very similar operators, each group of op's 
is attached with a 'cost' as described above:

The lower the cost, the better; the maximum cost in playing the piece is
supplied in the goal state.

5.1. PLAY-NOTE-THUMB, PLAY-NOTE-INDEX, PLAY-NOTE-MIDDLE,
     PLAY-NOTE-RING,  PLAY-NOTE-PINKY

These 5 operators do the most easy (and preferrable) thing:
They just play a note with the finger on the correct position already.

Cost:  1

5.2. THUMB-UNDER-INDEX, THUMB-UNDER-MIDDLE, THUMB-UNDER-RING

These 3 Operators (hope you - Mr. or Mrs. Reader - play piano ..., or
else it is really hard to get what is happening) change the position
of the hand by shifting the thumb under a finger, playing the next note
with the thumb and moving the whole hand then.

Cost:  2

5.3. INDEX-OVER-THUMB,  MIDDLE-OVER-THUMB,  RING-OVER-THUMB

Same as 5.2., just the other way around.

Cost:  2

5.4  JUMP-THUMB,  JUMP-INDEX,   JUMP-MIDDLE,  JUMP-RING,  JUMP-PINKY

These 5 Operators do the most unwanted thing: playing a note by just
moving the hand to a totally new position, regardless of the old one.
They should be applied as seldom as possible.

Cost:  9

6. Comment on Generators
------------------------
Nearly only non-static generators are used due to the following
reason:

With the type of search described in Section 4., we nearly always have the
goal to play one note with a operator yet to be chosen. In order to
choose an opearator, we look into the state (for current positions
of the fingers, number of notes played, ...).

But: We NEVER want to subgoal on any conditions, i.e. if the thumb isn't
where it has to be in order to apply a special operator, we DON'T want to
subgoal on this. (We are able to explore the whole space of possible moves
anyway, using our cost-evaluation function). 
If we would use a static generator, however we would subgoal. Of course:
- in principle this doesn't change the behaviour of the solving process
  In real life it does though, as we blow up the tree very much..
- we can write a search-control rule to avoid subgoaling. In fact we did
  so (see sc-rules, SCR REJECT-ALL-SUBGOALING). However, if we use static
  generators then, we spend way to much time applying this rule. This rule
  just covers cases where we might end up subgoaling on the number of notes
  played up to now etc..

conclusion:

In this very special domain with this very special approach the author 
thinks, that static generators just do NO good. He thinks it is valid to
use non-static generators, if one is aware of what he is doing and
that they will not be subgoaled in. (In out case, remember. we really 
don't want to...)

The two counters used
- PLAYED-SO-FAR       and
- COST-SO-FAR
are implemented with non-static generators as well.
(I see NO alternative here..., a type checking could be done with a
LISP function testing the variables for Integers.)


7. The Hack Domain
------------------
Ingenious as it is, the concept of cost rises one basic question :
If I don't know how to play a piece, how can I possibly come with a
minimum cost. This is the reason a 'hack-domain' has been implemented.

What it does:
It searches the whole tree (!) and keeps track of the solutions found.
Thus it will NEVER find a solution in the usual sense, but after failing
it will each time print out a list of solution-lengths found so far. 
Browsing this list
(in the form  
   ((n1 c1) (n2 c2) (n3 c3) ....)
   where ni is the number of solutions with the cost ci
)
allows the user to find out the minimum solution the computer can come up
with.

If we have a human solution on hand, if might be preferrable to calculate
the cost with it rather than using the 'hack' domain; as the latter 
method results in a BIG search.

To use the Hack Domain, just load it in Prodigy, run it with any problem
and see the list of solutions growing...

8. Search Control Rules
-----------------------
The Piano Fingering domain involves A LOT of meta-knowledge. In principle
it is possible to put it ALL in SCR's; however due to some time-constraints
we limited ourselves to the most crucial.

The most critical group of Op's is 5.4., the JUMP group. They
- are always applicable (as they have no preconditions with the current
  hand position)
- are expensive and not very much desired
- lead to a BIG search if applied often.

However, we DO need them, especially as we still have not implemented all
possible piano-fingering actions.

So our first goal with SCRs is to 
- reduce their usage as much as possible (REJECT !)
- without loosing them when we need them.

In general in our domain we might classify SCRs as follows:

8.1. SCRs looking at the current state only
8.2. SCRs looking at the last note played
8.3. SCRs looking at future notes to play
8.4. SCRs related to a speicial scale

Having 5 Operators JUMP-xxxxx, we end up with 15 SCRs as follows:

8.1. REJECT-JUMP-xxxxx-1
     We can reject the JUMP operators for sure, if it is possible to
     play a note with a finger right on the note anyway. In this case
     we definitely don't want to jump.

8.2. REJECT-JUMP-xxxxx-2
     We do reject the JUMP operators, if the last note played is
     directly adjacent to the note to be played now.
     In this case it should be possible to come up with a fingering
     without JUMPing here. (For a counterexample see the problem
     'French1', the domain just fails... See B6) in hand-in)

8.3. PREFER-JUMP-xxxxx-1
     In case we DO jump, we want to use the best of the five jumps
     available. So what we do is:
     Look at the next 4 notes to play, count how many are lower/higher
     than the current note to play and use the 'referring' finger.
     It is not perfectly reasonable to do so, but we didn't have the
     time to put the DISTANCE of the next notes into account as well.
  
     Implementation of the SCRs 8.3. was tricky, because:
     - we needed access to the whole goal-state, not only to the
       goals on the stack. A meta-function TB-IN-GOAL has been
       written for that task.
     - With the scale described as outlined in Section 4. 2), it is
       non-trivial to decide, if a note is higher or lower than another
       note. 2 meta-functions TB-IS-LOWER-EQUAL and TB-IS-HIGHER-EQUAL
       have been written.

We implemented 4 more SCRs:
8.4. PREFER-THUMB-UNDER-MIDDLE-C-MAJOR
     PREFER-THUMB-UNDER-RING-C-MAJOR
     PREFER-MIDDLE-OVER-THUMB-C-MAJOR
     PREFER-RING-OVER-THUMB-C-MAJOR
     These 4 SCRs implement the standard fingering for the C-major scale
     as strarting fingering.

With those SCRs we already get 'cheap' solutions relatively fast.

Finally we have two very special SCRs:
- SELECT-FIRST-GOAL applies to all goals now, even the top-level goals
- REJECT-ALL-SUBGOALING prevents subgoaling on ANY predicate. See 6. 
  for comments.

For comments on 'missing' meta-knowledge see the README file.
