Solution to

Homework 2 - Coding the Behavior-Based Approach

The code, as given, is incomplete. In particular, the motor control function called motor_driver is called by arbitrate and assumed to exist, yet the code for motor_driver is not given.

Exercise 1. Write the code for motor_driver, assuming that your target robot has two motors, one connected to each of two drive wheels and a caster for balance.

The exact details of your code, such as the values given to the motors, are not as important as simply giving an example of code that could work for some platform. In particular, you should define a global variable motor_input of type int, and create an infinite loop that checks the value of this variable against the constant values used elsewhere in the code (FORWARD, etc.), calling the motor commands with some values. If you wanted to do nice things such as using #define statements for abstraction as shown, that would be good but not critical for this homework. Here is an example:

#define LEFT_MOTOR       0
#define RIGHT_MOTOR      2
#define DEFAULT_POWER   80
#define ARC_CONST      0.5

int motor_input;

void motor_driver(){
    while(1){
        if (motor_input == FORWARD){
            motor(LEFT_MOTOR, DEFAULT_POWER);
            motor(RIGHT_MOTOR, DEFAULT_POWER);
        }
        else if (motor_input == LEFT_TURN){
            motor(LEFT_MOTOR, -DEFAULT_POWER);
            motor(RIGHT_MOTOR, DEFAULT_POWER);
        }
        else if (motor_input == RIGHT_TURN){
            motor(LEFT_MOTOR, DEFAULT_POWER);
            motor(RIGHT_MOTOR, -DEFAULT_POWER);
        }
        else if (motor_input == LEFT_ARC){
            motor(LEFT_MOTOR, ARC_CONST * DEFAULT_POWER);
            motor(RIGHT_MOTOR, DEFAULT_POWER);
        }
        else if (motor_input == RIGHT_ARC){
            motor(LEFT_MOTOR, DEFAULT_POWER);
            motor(RIGHT_MOTOR, ARC_CONST * DEFAULT_POWER);
        }
        else if (motor_input == BACKWARD){
            motor(LEFT_MOTOR, -DEFAULT_POWER);
            motor(RIGHT_MOTOR, -DEFAULT_POWER);
        }
    }
}

One difference between the code you have just augmented and the subsumption architecture of Brooks is that the code abstracts away the motor commands from the functions in which sensing is done and places them in the centralized motor control function called motor_driver that you have just created.

Exercise 2. Rewrite the code to more closely match the subsumption architecture approach by eliminating the difference just discussed.

The main point to get from this exercise is that, while it may be easy to claim that we can simply send multiple commands along a wire and let some suppress (or inhibit) others, it is not easy to code this up in a reasonable way while still staying true to the abstract ideas underlying the subsumption architecture. One alternative is to use some centralized pieces of code, such as the arbitrate and motor_driver functions of Jones, et al. Another is to have individual layers talking directly to one another by sticking code in lower levels. Both of these violate the spirit of the subsumption architecture.

A variant of the first alternative is to use a functional language such as Haskell to say how inputs and outputs relate to one another. Then, you don't need to explicitly code up arbitration routines. They are still there, in some sense, just hidden from view in the interpreter or the compiled code, so this still violates the spirit of the subsumption architecture to some extent. (The solution Brooks gives in this technical report seems to be closest to this approach. You can't find central arbitration routines in his code as given. Not because they aren't there but because they are hidden away in the parts he didn't give.)

Yet another alternative is to implement this in hardware, rather than software, which has the negative effect of making development slow and expensive.

The alternative illustrated here is to have the layers talk to each other through global variables.

struct motor_wire_t{
    int suppression;    /* is the direct value suppressed? */
    int left_value;     /* value dropped on left motor wire from outside */
    int right_value;    /* value dropped on right motor wire from outside */

}
struct motor_wire_t cruise_wire;  /* wire to suppression node on cruise wire*/

/* CRUISE: BEHAVIOR TO GO FORWARD */
/* This is the lowest level, so the motor module is included here */

void cruise_layer(){               /* layer 0 */
    int left_motor_wire;           /* LOCAL: wire connected to left motor */
    int right_motor_wire;          /* LOCAL: wire connected to right motor */
    while(1){
        /* put our command on the wire */
        left_motor_wire = DEFAULT_VALUE;
        right_motor_wire = DEFAULT_VALUE;

        /* if command should be suppressed, suppress it */
        if (cruise_wire.suppression){
            left_motor_wire = cruise_wire.left_value;
            right_motor_wire = cruise_wire.right_value;
        }

        /* command gets to motors */
        motor(LEFT_MOTOR, left_motor_wire);
        motor(RIGHT_MOTOR, right_motor_wire);
    }
}

/* FOLLOW: BEHAVIOR TO FOLLOW A LIGHT */

struct motor_wire_t follow_wire;  /* wire to suppression node on follow wire */

void follow_layer(){                    /* layer 1 */
    int left_photo, right_photo, delta; /* Left and right photocells */
    while(1){
        /* default is not to suppress lower layer */
        cruise_wire.suppression = FALSE;

        /* check our sensors */
        left_photo = analog(1);
        right_photo = analog(0);
        delta = right_photo - left_photo;

        /* if we should take action, put our command on the wire */
        if (abs(delta) > PHOTO_DEAD_ZONE){
            if (delta > 0){     /* left turn */
                cruise_wire.left_value = -DEFAULT_VALUE;
                cruise_wire.right_value = DEFAULT_VALUE;
            }
            else{               /* right turn */
                cruise_wire.left_value = DEFAULT_VALUE;
                cruise_wire.right_value = -DEFAULT_VALUE;
            }
            cruise_wire.suppression = TRUE;
        }

        /* if command should be suppressed, suppress it */
        if (follow_wire.suppression){
            cruise_wire.left_value = follow_wire.left_value;
            cruise_wire.right_value = follow_wire.right_value;
            cruise_wire.suppression = TRUE;
        }
    }
}

/* AVOID: BEHAVIOR TO AVOID NEARBY OBSTACLES USING IR DETECTION */

struct motor_wire_t avoid_wire; /* wire to suppression node on avoid wire */

void avoid_layer(){                     /* layer 2 */
    int val;
    while(1){
        /* default is not to suppress lower layer */
        follow_wire.suppression = FALSE;

        /* check our sensors */
        val = ir_detect();

        /* if we should take action, put our command on the wire */
        if (val == 0b11){               /* Both left and right IRs active */
            follow_wire.left_value = ARC_CONST * DEFAULT_POWER;
            follow_wire.right_value = DEFAULT_POWER;
            follow_wire.suppression = TRUE;
        }
        else if (val = 0b10){           /* Left IR active */
            follow_wire.left_value = DEFAULT_POWER;
            follow_wire.right_value = ARC_CONST * DEFAULT_POWER;
            follow_wire.suppression = TRUE;
        }
        else if (val = 0b01){           /* Right IR active */
            follow_wire.left_value = ARC_CONST * DEFAULT_POWER;
            follow_wire.right_value = DEFAULT_POWER;
            follow_wire.suppression = TRUE;
        }

        /* if command should be suppressed, suppress it */
        if (avoid_wire.suppression){
            follow_wire.left_value = avoid_wire.left_value;
            follow_wire.right_value = avoid_wire.right_value;
            follow_wire.suppression = TRUE;
        }
    }
}

/* ESCAPE: BEHAVIOR TO TURN AWAY FROM COLLISIONS DETECTED BY BUMPERS */

void escape_layer(){                    /* layer 3 */
    while(1){
        /* default is not to suppress lower layer */
        avoid_wire.suppression = FALSE;

        /* check our sensors */
        bump_check();

        /* if we should take action, put our command on the wire */
        if (bump_left && bump_right){
            avoid_wire.suppression = TRUE;

            /* back up for 0.2 seconds */
            avoid_wire.left_value = -DEFAULT_POWER;
            avoid_wire.right_value = -DEFAULT_POWER;
            sleep(0.2);

            /* turn left for 0.4 seconds */
            avoid_wire.left_value = -DEFAULT_POWER;
            avoid_wire.right_value = DEFAULT_POWER;
            sleep(0.4);
        }
        else if (bump_left){
            avoid_wire.suppression = TRUE;

            /* turn right for 0.4 seconds */
            avoid_wire.left_value = DEFAULT_POWER;
            avoid_wire.right_value = -DEFAULT_POWER;
            sleep(0.4);
        }
        else if (bump_right){
            avoid_wire.suppression = TRUE;

            /* turn left for 0.4 seconds */
            avoid_wire.left_value = -DEFAULT_POWER;
            avoid_wire.right_value = DEFAULT_POWER;
            sleep(0.4);
        }
        else if (bump_back){
            avoid_wire.suppression = TRUE;

            /* turn left for 0.2 seconds */
            avoid_wire.left_value = -DEFAULT_POWER;
            avoid_wire.right_value = DEFAULT_POWER;
            sleep(0.2);
        }

        /* there is no higher layer to suppress our command */
    }
}

/* MAIN: START ALL PROCESSES */

void main(){
    start_process(cruise_layer());
    start_process(follow_layer());
    start_process(avoid_layer());
    start_process(escape_layer());
}

/* NOTE: Neither arbitrate nor motor_driver are needed in this version of
 * the code. */

One feature of the subsumption architecture is that layers may inhibit and/or suppress other layers. However, in the given code, only one of these is done.

Exercise 3a. Answer the following questions and explain your answers:

  1. Does the code given show inhibition or suppression?
  2. The code given shows only suppression. The two differences between inhibition and suppression are (1) whether the node is attached at the output of a module (inhibition) or at the input of a module (suppression) and (2) whether the value on the wire from the lower layer is simply thrown away (inhibition) or replaced by a value from a higher layer (suppression).

    The first of these differences is impossible to see if only one or the other is shown. This is because the output of one module is the input to another [1]

    The second of these differences is what allows us to decide for this assignment that this code shows only suppression. Note that when follow() detects a significant difference in light intensity, for example, we don't merely shut off cruising (and stop) but, rather, we replace the motor_input with new values for turning to follow the light. This is true with all of the other behaviors given as well.

  3. Would it be reasonable for any of the layers given to do whichever of these two (inhibition or suppression) is not found in the given code? (That is, if you said inhibition is not found in the given code, would it be make sense to add it anywhere, given the idea behind each layer? Alternately, if you said suppression is not found in the given code, would it be make sense to add it anywhere, given the idea behind each layer?)
  4. Sure. For example, we might find that, in the environment in which our robot is used, light sources sometimes draw us into collisions and, moreover, even after we use escape(), the same light source will often draw us back in for another collision. Therefore, we might want to drive farther away from the location of the collision, so as not to be drawn back in by that light. It might be tempting to just add a period of time for driving forward after turning in the escape() routine. The problem with this approach is that if we do this, while we are driving forward avoid() won't be able to use the IR sensors to avoid collisions, likely resulting in more collisions.

    A potentially better alternative to having escape() blindly drive forward after turning, is to note that we already have a low level behavior set up which drives forward: cruise(). Further, if we allow cruise() to drive us away from the collision site, avoid() can still be active and protecting us from collisions using its IR sensors. To make this happen, we need to have escape() inhibit follow(). For the code, the inhibition period was arbitrarily chosen to be 0.7 seconds.

Exercise 3b. Regardless of whether you said it would be reasonable, modify the code to show an example of the missing subsumption architecture feature under discussion. Be sure to do this in a general way so that it would be easy (whether reasonable or not) to add examples of other layers subsuming control in this way.

struct motor_wire_t{
    int inhibition;     /* is the output value inhibited? */
    int suppression;    /* is the input value suppressed? */
    int left_value;     /* value dropped on left motor wire from outside */
    int right_value;    /* value dropped on right motor wire from outside */

}
struct motor_wire_t cruise_wire;  /* wire to suppression node on cruise wire */

/* CRUISE: BEHAVIOR TO GO FORWARD */
/* This is the lowest level, so the motor module is included here */

void cruise_layer(){               /* layer 0 */
    int left_motor_wire;           /* LOCAL: wire connected to left motor */
    int right_motor_wire;          /* LOCAL: wire connected to right motor */
    while(1){
        /* put our command on the wire */
        left_motor_wire = DEFAULT_VALUE;
        right_motor_wire = DEFAULT_VALUE;

        /* if command should be inhibited, inhibit it */
        if (cruise_wire.inhibition){
            left_motor_wire = 0;
            right_motor_wire = 0;
        }

        /* if command should be suppressed, suppress it */
        if (cruise_wire.suppression){
            left_motor_wire = cruise_wire.left_value;
            right_motor_wire = cruise_wire.right_value;
        }

        /* command gets to motors */
        motor(LEFT_MOTOR, left_motor_wire);
        motor(RIGHT_MOTOR, right_motor_wire);
    }
}

/* FOLLOW: BEHAVIOR TO FOLLOW A LIGHT */

struct motor_wire_t follow_wire;  /* wire to suppression node on follow wire */

void follow_layer(){                    /* layer 1 */
    int left_photo, right_photo, delta; /* Left and right photocells */
    while(1){
        /* default is not to suppress lower layer */
        cruise_wire.suppression = FALSE;

        /* check our sensors */
        left_photo = analog(1);
        right_photo = analog(0);
        delta = right_photo - left_photo;

        /* if we should take action, put our command on the wire */
        if (abs(delta) > PHOTO_DEAD_ZONE){
            if (delta > 0){     /* left turn */
                cruise_wire.left_value = -DEFAULT_VALUE;
                cruise_wire.right_value = DEFAULT_VALUE;
            }
            else{               /* right turn */
                cruise_wire.left_value = DEFAULT_VALUE;
                cruise_wire.right_value = -DEFAULT_VALUE;
            }
            cruise_wire.suppression = TRUE;
        }

        /* if command should be inhibited, inhibit it */
        if (follow_wire.inhibition){
            cruise_wire.suppression = FALSE;
        }

        /* if command should be suppressed, suppress it */
        if (follow_wire.suppression){
            cruise_wire.left_value = follow_wire.left_value;
            cruise_wire.right_value = follow_wire.right_value;
            cruise_wire.suppression = TRUE;
        }
    }
}

/* AVOID: BEHAVIOR TO AVOID NEARBY OBSTACLES USING IR DETECTION */

struct motor_wire_t avoid_wire; /* wire to suppression node on avoid wire */

void avoid_layer(){                     /* layer 2 */
    int val;
    while(1){
        /* default is not to suppress lower layer */
        follow_wire.suppression = FALSE;

        /* check our sensors */
        val = ir_detect();

        /* if we should take action, put our command on the wire */
        if (val == 0b11){               /* Both left and right IRs active */
            follow_wire.left_value = ARC_CONST * DEFAULT_POWER;
            follow_wire.right_value = DEFAULT_POWER;
            follow_wire.suppression = TRUE;
        }
        else if (val = 0b10){           /* Left IR active */
            follow_wire.left_value = DEFAULT_POWER;
            follow_wire.right_value = ARC_CONST * DEFAULT_POWER;
            follow_wire.suppression = TRUE;
        }
        else if (val = 0b01){           /* Right IR active */
            follow_wire.left_value = ARC_CONST * DEFAULT_POWER;
            follow_wire.right_value = DEFAULT_POWER;
            follow_wire.suppression = TRUE;
        }

        /* if command should be inhibited, inhibit it */
        if (avoid_wire.inhibition){
            follow_wire.suppression = FALSE;
        }

        /* if command should be suppressed, suppress it */
        if (avoid_wire.suppression){
            follow_wire.left_value = avoid_wire.left_value;
            follow_wire.right_value = avoid_wire.right_value;
            follow_wire.suppression = TRUE;
        }
    }
}

/* ESCAPE: BEHAVIOR TO TURN AWAY FROM COLLISIONS DETECTED BY BUMPERS */

void escape_layer(){                    /* layer 3 */
    while(1){
        /* default is not to suppress lower layer */
        avoid_wire.suppression = FALSE;

        /* default is not to inhibit lower layer */
        follow_wire.inhibition = FALSE;

        /* check our sensors */
        bump_check();

        /* if we should take action, put our command on the wire */
        if (bump_left && bump_right){
            avoid_wire.suppression = TRUE;

            /* back up for 0.2 seconds */
            avoid_wire.left_value = -DEFAULT_POWER;
            avoid_wire.right_value = -DEFAULT_POWER;
            sleep(0.2);

            /* turn left for 0.4 seconds */
            avoid_wire.left_value = -DEFAULT_POWER;
            avoid_wire.right_value = DEFAULT_POWER;
            sleep(0.4);

            /* inhibit follow() for 0.7 seconds */
            avoid_wire.suppression = FALSE;
            follow_wire.inhibition = TRUE;
            sleep(0.7);
        }
        else if (bump_left){
            avoid_wire.suppression = TRUE;

            /* turn right for 0.4 seconds */
            avoid_wire.left_value = DEFAULT_POWER;
            avoid_wire.right_value = -DEFAULT_POWER;
            sleep(0.4);

            /* inhibit follow() for 0.7 seconds */
            avoid_wire.suppression = FALSE;
            follow_wire.inhibition = TRUE;
            sleep(0.7);
        }
        else if (bump_right){
            avoid_wire.suppression = TRUE;

            /* turn left for 0.4 seconds */
            avoid_wire.left_value = -DEFAULT_POWER;
            avoid_wire.right_value = DEFAULT_POWER;
            sleep(0.4);

            /* inhibit follow() for 0.7 seconds */
            avoid_wire.suppression = FALSE;
            follow_wire.inhibition = TRUE;
            sleep(0.7);
        }
        else if (bump_back){
            avoid_wire.suppression = TRUE;

            /* turn left for 0.2 seconds */
            avoid_wire.left_value = -DEFAULT_POWER;
            avoid_wire.right_value = DEFAULT_POWER;
            sleep(0.2);

            /* inhibit follow() for 0.7 seconds */
            avoid_wire.suppression = FALSE;
            follow_wire.inhibition = TRUE;
            sleep(0.7);
        }

        /* there is no higher layer to inhibit or suppress our command */
    }
}

/* MAIN: START ALL PROCESSES */

void main(){
    start_process(cruise());
    start_process(follow());
    start_process(avoid());
    start_process(escape());


}

/* NOTE: Neither arbitrate nor motor_driver are needed in this version of
 * the code. */

The subsumption architecture is only one way of implementing behavior-based control. The other method we covered in depth in this class was schema theory. In the version of schema theory we covered, each behavior is divided into a perceptual schema and a motor schema.

Exercise 4. Starting again from the code as given on the class web pages, modify it to conform to this aspect of schema theory. Use the same assumption regarding the target robot platform that you used in exercise 1.

/* Note that we could easily optimize this code in many ways.  For example,
 * we could have processes defer when their releasers are not active.
 * However, we have not tried to optimize this code for size or speed but,
 * rather, for ease of reading and relating to concepts from the class.
 */

/* CRUISE: BEHAVIOR TO GO FORWARD */

int cruise_output_flag;                 /* GLOBAL: Command active? */
int cruise_percept;                     /* GLOBAL: Output of perceptual
                                           schema for cruise */
int cruise_command;                     /* GLOBAL: Command to motors */

void cruise_releaser(){
    while(1){
        cruise_output_flag = TRUE;
    }
}

void cruise_perception(){
    while(1){
        if (cruise_output_flag)
            cruise_percept = 1;
    }
}

void cruise_motor(int active){
    while(1){
        if (cruise_output_flag)
            if (cruise_percept == 1)
                cruise_command = FORWARD;
    }
}

#define LIGHT_RIGHT     1
#define LIGHT_LEFT      2

/* FOLLOW: BEHAVIOR TO FOLLOW A LIGHT */

int follow_output_flag;                 /* GLOBAL: Command active? */
int follow_percept;                     /* GLOBAL: Output of perceptual
                                           schema for follow */
int follow_command;                     /* GLOBAL: Command to motors */

void follow_releaser(){
    int left_photo, right_photo, delta; /* Left and right photocells */
    while (1){
        left_photo = analog(1);
        right_photo = analog(0);
        delta = right_photo - left_photo;
        if (abs(delta) > PHOTO_DEAD_ZONE)
            follow_output_flag = TRUE;
        else
            follow_output_flag = FALSE;
    }
}

void follow_perception(){
    int left_photo, right_photo, delta; /* Left and right photocells */
    while (1){
        if (follow_output_flag){
            left_photo = analog(1);
            right_photo = analog(0);
            delta = right_photo - left_photo;
            if (delta > 0)
                follow_percept = LIGHT_LEFT;
            else
                follow_percept = LIGHT_RIGHT;
        }
    }
}

void follow_motor(){
    while(1){
        if (follow_output_flag){
            if (follow_percept == LIGHT_LEFT)
                follow_command = LEFT_TURN;
            else
                follow_command = RIGHT_TURN;
        }
    }
}

/* AVOID: BEHAVIOR TO AVOID NEARBY OBSTACLES USING IR DETECTION */

#define OBSTACLE_AHEAD  1
#define OBSTACLE_LEFT   2
#define OBSTACLE_RIGHT  3

int avoid_output_flag;
int avoid_percept;
int avoid_command;

void avoid_releaser(){
    int val;
    while(1){
        val = ir_detect();
        if (val ==  0b11 || val = 0b10 || val = 0b01)
            avoid_output_flag = TRUE;
        else
            avoid_output_flag = FALSE;
    }
}

void avoid_perception(){
    int val;
    while(1){
        if (avoid_output_flag){
            val = ir_detect();
            if (val == 0b11)               /* Both left and right IRs active */
                avoid_percept = OBSTACLE_AHEAD;
            else if (val = 0b10)           /* Left IR active */
                avoid_percept = OBSTACLE_LEFT;
            else if (val = 0b01)           /* Right IR active */
                avoid_percept = OBSTACLE_RIGHT;
        }
    }
}

void avoid_motor(){
    while(1){
        if (avoid_output_flag){
            if (avoid_percept == OBSTACLE_AHEAD)
                avoid_command = LEFT_ARC;
            else if (avoid_percept == OBSTACLE_LEFT)
                avoid_command = RIGHT_ARC;
            else if (avoid_percept == OBSTACLE_RIGHT)
                avoid_command = LEFT_ARC;
        }
    }
}

/* ESCAPE: BEHAVIOR TO TURN AWAY FROM COLLISIONS DETECTED BY BUMPERS */

#define COLLISION_FONT  1
#define COLLISION_LEFT  2
#define COLLISION_RIGHT 3
#define COLLISION_REAR  4

int escape_output_flag;
int escape_percept;
int escape_command;

void escape_releaser(){
    while(1){
        bump_check();
        if (bump_left || bump_right || bump_back)
            escape_output_flag = TRUE;
        else
            escape_output_flag = FALSE;
    }
}

void escape_perception(){
    while(1){
        if (escape_output_flag){
            bump_check();
            if (bump_left && bump_right)
                escape_percept = COLLISION_FONT;
            else if (bump_left)
                escape_percept = COLLISION_LEFT;
            else if (bump_right)
                escape_percept = COLLISION_RIGHT;
            else if (bump_back)
                escape_percept = COLLISION_REAR;
        }
    }
}

void escape_motor(){
    while(1){
        if (escape_output_flag){
            if (escape_percept = COLLISION_FONT){
                escape_output_flag == TRUE;
                escape_command = BACKWARD;
                sleep(0.2);
                escape_command = LEFT_TURN;
                sleep(0.4);
            }
            else if (escape_percept == COLLISION_LEFT){
                escape_output_flag = TRUE;
                escape_command = RIGHT_TURN;
                sleep(0.4);
            }
            else if (escape_percept == COLLISION_RIGHT){
                escape_output_flag = TRUE;
                escape_command = LEFT_TURN;
                sleep(0.4);
            }
            else if (escape_percept == COLLISION_REAR){
                escape_output_flag = TRUE;
                escape_command = LEFT_TURN;
                sleep(0.2);
            }
    }
}

/* MAIN: START ALL PROCESSES */

void main(){
    start_process(motor_driver());
    start_process(cruise_releaser());
    start_process(cruise_perception());
    start_process(cruise_motor());
    start_process(follow_releaser());
    start_process(follow_perception());
    start_process(follow_motor());
    start_process(avoid_releaser());
    start_process(avoid_perception());
    start_process(avoid_motor());
    start_process(escape_Releaser());
    start_process(escape_perception());
    start_process(escape_motor());
    start_process(arbitrate());
}

/* ARBITRATE: CHOOSE BEHAVIOR */

void arbitrate(){
    while(1){
        if (cruise_output_flag == TRUE)
            motor_input = cruise_command;
        if (follow_output_flag == TRUE)
            motor_input = follow_command;
        if (avoid_output_flag == TRUE)
            motor_input = avoid_command;
        if (escape_output_flag == TRUE)
            motor_input = escape_command;
        sleep(tick);
    }
}

One purported advantage of schema theory over the subsumption architecture is that schema theory promotes code reuse and easy modification if the hardware changes, whereas the subsumption architecture does not because it is so closely tied to the hardware. Here is your chance to verify it for yourself, at least for one particular case.

Exercise 5a. Answer the following questions and explain your answers:

  1. If you were to replace the motor setup described above with an adder-subtractor transmission (in which one motor drives the vehicle forward or backward and the other causes it to rotate left or right and their combination causes it to move in arcs of one sort or another), how many functions could be reused without change from the code as given on the web pages?
  2. All six functions given - cruise(), follow(), avoid(), escape(), main(), and arbitrate() - could be reused without change. The first four of these functions merely describe what action the the robot should take and under what circumstances but not how an action is carried out at the low level of motor control, which is what needs to be changed. The fifth merely starts up the others and the sixth determines which of the first four has priority.

  3. If you were to replace the motor setup described above with an adder-subtractor transmission, how many functions from the code as given on the web pages would need to be changed?
  4. None. How an action is carried out - the low level motor control - is left to motor_driver, which was not given on the web page but, rather, created during exercise 1. This is the only function that needs to be changed.

  5. If you were to replace the motor setup described above with an adder-subtractor transmission, how many functions could be reused without change from the code you produced in exercise 2?
  6. One function - main() - does not need to be changed. It merely starts the other four.

  7. If you were to replace the motor setup described above with an adder-subtractor transmission, how many functions from the code you produced in exercise 2 would need to be changed?
  8. Four functions - cruise_layer(), follow_layer(), avoid_layer(), and escape_layer() - need to be changed. All of these assign values to wires and since the values need to be changed to handle the new transmission set-up, they all need to be changed.

  9. If you were to replace the motor setup described above with an adder-subtractor transmission, how many functions could be reused without change from the code you produced in exercise 4?
  10. Fourteen functions - all of the releaser, perceptual schema, and motor schema functions for all behaviors, plus main() and arbitrate - can be reused without change. The four releaser functions tell when behaviors should be active. The four perceptual schema functions do some sensory processing. The four motor schema functions, like the original four behavior functions, tell what action the robot should take but not how an action is carried out at the low level of motor control, which is what needs to be changed. As with the original code, main() and arbitrate merely starts up the other functions and determines which motor command has priority, respectively.

  11. If you were to replace the motor setup described above with an adder-subtractor transmission, how many functions from the code you produced in exercise 4 would need to be changed?
  12. One. The motor_driver function, which can be the same one created for exercise 1, needs to be changed (and can be changed in exactly the same way as for question 2).

  13. Given your answers to the previous six questions and any additional analysis you care to provide, which version of the code best promotes code reuse and easy modification?
  14. I'd argue that schema theory promotes code reuse more than either subsumption as implemented in the code as given or as created in exercise 2. The code from exercise 2 obviously needs more changes than the other two versions under consideration. However, the code from exercise 4 has broken each behavior down into more, smaller parts which would allow for greater mixing and matching - i.e., greater code reuse - if the system was modified in other ways such as changing sensory systems.

Exercise 5b. Given your answer to question seven in part a of this exercise, choose the easiest code version to modify and modify it to accommodate an adder-subtractor transmission.

Because only the motor_driver() function needs to be changed from both exercise 1 (which built on the original organization of the code) and exercise 4 (schema theory), the code given below could be used for either answer. Here I am considering it as code for the schema theory version.

#define DRIVE_MOTOR               0
#define TURN_MOTOR                2
#define DEFAULT_DRIVE_POWER      80
#define DEFAULT_TURN_POWER      100

int motor_input;

void motor_driver(){
    while(1){
        if (motor_input == FORWARD){
            motor(DRIVE_MOTOR, DEFAULT_DRIVE_POWER);
        }
        else if (motor_input == LEFT_TURN){
            motor(TURN_MOTOR, DEFAULT_TURN_POWER);
        }
        else if (motor_input == RIGHT_TURN){
            motor(TURN_MOTOR, -DEFAULT_TURN_POWER);
        }
        else if (motor_input == LEFT_ARC){
            motor(DRIVE_MOTOR, DEFAULT_DRIVE_POWER);
            motor(TURN_MOTOR, DEFAULT_TURN_POWER);
        }
        else if (motor_input == RIGHT_ARC){
            motor(DRIVE_MOTOR, DEFAULT_DRIVE_POWER);
            motor(TURN_MOTOR, -DEFAULT_TURN_POWER);
        }
        else if (motor_input == BACKWARD){
            motor(DRIVE_MOTOR, -DEFAULT_DRIVE_POWER);
        }
    }
}

The previous versions of the code we have been dealing with all combine the output of the behaviors by simple replacement. That is, a dominant behavior will prevent another behavior from taking its normal action. Another possibility is to have the output of these behaviors fused additively. That is, we could add together the output of two or more behaviors to get a single resultant output motion. For example, adding together a FORWARD and a LEFT_TURN command might be equivalent to a LEFT_ARC command while adding a LEFT_ARC and a RIGHT_ARC command might yield something equivalent to a FORWARD command.

Exercise 6. Choose any version of the code above that you wish and modify it to use additive behavioral fusion. Indicate which version you have chosen and why. You may define the results of the combinations in any way you choose, as long as it is reasonable and general. (It isn't reasonable to add together a FORWARD and a LEFT_TURN command to give something equivalent to a BACKWARD command, for example. Moreover, to make it general, you should consider what would happen if you added many more behaviors to the system and needed to add together one FORWARD command and a couple of dozen LEFT_TURN commands, and so forth.)

For this exercise, I chose to start over with the code as given on the web site and, rather than adding a motor_driver() function as I did for exercise 1, I modified arbitrate() to be a combined motor driving and arbitration function that I called fusion() with a couple of helper functions. (Naturally, main() would have to be modified as well, to start up fusion() rather than motor_driver() and arbitrate().)

The reason I didn't keep two separate functions, one adding up the commands and one sending them to the motors is that I wanted to be sure that we never divided the motor power for either motor by zero commands, causing an exception. This is much harder to guarantee with two asynchronous processes or threads.

#define LEFT_MOTOR       0
#define RIGHT_MOTOR      2
#define DEFAULT_POWER   80
#define ARC_CONST      0.5

int right_motor_power;
int left_motor_power;
int num_motor_commands;

void clear_motors(){
    right_motor_power = 0;
    left_motor_power = 0;
    num_motor_commands = 0;
}

/* This function should be checking for overflow values but doesn't, to
 * keep clear the points most related to the material currently under
 * study. */
void add_to_motors(int new_command){
    num_motor_commands= num_motor_commands + 1;
    if (new_command == FORWARD){
        left_motor_power = left_motor_power + DEFAULT_POWER;
        right_motor_power = right_motor_power + DEFAULT_POWER;
    }
    else if (new_command == LEFT_TURN){
        left_motor_power = left_motor_power - DEFAULT_POWER;
        right_motor_power = right_motor_power + DEFAULT_POWER;
    }
    else if (new_command == RIGHT_TURN){
        left_motor_power = left_motor_power + DEFAULT_POWER;
        right_motor_power = right_motor_power - DEFAULT_POWER;
    }
    else if (new_command == LEFT_ARC){
        left_motor_power = left_motor_power + ARC_CONST * DEFAULT_POWER;
        right_motor_power = right_motor_power + DEFAULT_POWER;
    }
    else if (new_command == RIGHT_ARC){
        left_motor_power = left_motor_power + DEFAULT_POWER;
        right_motor_power = right_motor_power + ARC_CONST * DEFAULT_POWER;
    }
    else if (new_command == BACKWARD){
        left_motor_power = left_motor_power - DEFAULT_POWER;
        right_motor_power = right_motor_power - DEFAULT_POWER;
    }
    else  /* unknown command! */
      num_motor_commands = num_motor_commands - 1;
}


/* FUSION: COMBINE BEHAVIOR AND SEND MOTOR COMMANDS */

void fusion(){
    while(1){
        clear_motors();
        if (cruise_output_flag == TRUE)
            add_to_motors(cruise_command);
        if (follow_output_flag == TRUE)
            add_to_motors(follow_command);
        if (avoid_output_flag == TRUE)
            add_to_motors(avoid_command);
        if (escape_output_flag == TRUE)
            add_to_motors(escape_command);
        if (num_motor_commands > 0){
            motor(LEFT_MOTOR, left_motor_power/num_motor_commands);
            motor(RIGHT_MOTOR, right_motor_power/num_motor_commands);
            sleep(tick);
        }
    }
}

ENDNOTES

1. Note that you could see this difference if both inhibition and suppression were shown on the same wire, however.

Take the following example. Module M1 sends a command on wire W to module M2. Modules M3 and M4, from a higher layer, have nodes on W. You aren't told in the code whether these nodes are inhibition or suppression nodes. However, you can see that if both M3 and M4 are active, the result on the wire is different than if only M3 is active but the same as when only M4 is active. You can conclude with relative confidence that M3 is an inhibition node and M4 is a suppression node.

To reach this conclusion, note that all inhibition nodes do the same thing (see difference 2 in the main text). So, if both nodes were inhibition nodes, the result on this wire of both M3 and M4 being active could not be different than when only one of them is active. Therefore, they cannot both be inhibition nodes.

Next, note that the combination of two suppression nodes is done using logical 'or.' So, if M3 and M4 both have suppression nodes on this line, and M3 or M4 is the same as M4, M3 must always be putting zeros on the line. This is essentially the same thing as inhibition. So it is relatively safe to conclude that M3 is an inhibition node. (Whether you want to argue that a suppression node that always puts zeros on the line really is an inhibition node is a semantic game that I'm not all that interested in playing.)

So, if M3 has an inhibition node on W, and both nodes aren't inhibition nodes, M4's node must be a suppression node.