CS 302 Project 2

Grocery Simulation

Due date: Wednesday, August 1 at 11:59 PM (CSL time).

Extra credit is possible if you turn it in by Tuesday at 11:59 PM.

Last modified: July 25, 2006

Latest News

Please check back frequently.

Overview

For this project, you will implement a simple simulation of a grocery store. Your goal in implementing this simulation is to decide which of the following policies the store should use to manage its checkout lanes:

You are not allowed to use the ArrayList class or any other java.util collection class on this assignment. You must use Java arrays that are accessed with the [] operator. Also, you are not allowed to use the Arrays class or the System.arraycopy method. Your ResizableArray class will provide a more useful interface to Java arrays for this project. (If you don’t know what a “collection class” is, don’t worry about it, just make sure that you use standard arrays!)

Goals

Description

In this project, you will develop the following classes:

We provide you with the following classes:

To get the classes we provide, please download grocery.jar and add it to your Eclipse project.

Background

Computer scientists and applied mathematicians often use discrete-event simulations to study how how complex systems interact. The basic idea behind a simulation is to model behaviors of real-world or computer systems in a simple but faithful way, so that you can experiment with how changing certain parameters affects the way the real-world system will work. Java is well suited for writing simulations, since it is possible to model real-world entities and their interactions using objects. (In fact, SIMULA - the first object-oriented language - was developed to provide a language for writing simulations.)

A discrete-event simulation treats time as a discrete unit. Since time is continuous, such simulations will typically make some simplifying assumptions. For example, in a real grocery store, some cashiers are slower than others and some items take longer to ring up than others (consider a price check, an obscure vegetable, or a large item that is difficult to scan). In our grocery store simulation, though, a cashier can always process exactly one item in exactly one “unit” or step of time.

In this project, you are simulating a grocery store. Therefore, you will develop instantiable classes that:

You will also implement an application class (GroceryApp) that evaluates the three policies with the same workload (set of customer arrivals and items) and prints results in a specified format. We provide a class (CustomerGenerator) to implement that workload - that is, to tell your simulation when customers have arrived and how many items each has.

We also provide a class (SampleManager) you can use to analyze the raw data from your simulation results.

Assignment specifics

Simplifying assumptions

As stated above, you are only developing a model of a grocery store and can make several simplifying assumptions. Computer scientists typically do this to make simulations easier to develop (and to make experiments easier to design, since there are fewer variables to control for). Specifically, you may assume that:

  1. A cashier can process exactly one item during a time step.
  2. Moving in line is instantaneous. That is, if a customer is to move at the end of a time step, the customer will have arrived at its new position by the beginning of the next time step.

What happens at each time step

At each time step, several things happen, in this order:

  1. New customers arrive; the number of customers that arrive is determined by the getArrivals() method of the CustomerGenerator class. You will set the arrival time for each customer based on the current time from a SimulationTimer object.
  2. The arrived customers line up in lanes, in the order they were returned by the getArrivals() method, and using the following rule: A newly-arriving customer will enter the shortest lane. In the case of a tie (e.g. two or more lanes are tied for the lowest number of customers), the newly-arriving customer will enter the lane with the lowest lane number. Note that where the customer enters the lane is different depending on which policy you are implementing!
  3. The customer at the front of each lane is served by the cashier: the customer’s current item count is decremented by one.
  4. If the customer at the front of a lane has had its last item processed, it “leaves.” To model a customer leaving, you will record its completion time, print out its information, record the ratio of service time to items (see below) in a SampleManager instance, and remove it from the array, shifting all other customers forward. The proper way to shift customers in line is illustrated in figure 1 below:

Figure 1: How to shift elements in an array. (Dashed boxes have null values.)

Rules

You must use Java language arrays. You may not use any collection class from the Java library or from other programmers. (Of course, you will write your own "collection class," ResizableArray.)

You must be able to handle an arbitrary number of customers efficiently.

The first-come, first-served policy

This is the way most real-world stores work. The cashier takes items one at a time from customer at the front of the line and calculates the bill. When a customer has no items left in her cart, she leaves; new customers arrive and are placed at the end of the line.

If a new customer arrives, she goes to the end of the line with the fewest customers. If more than one line is tied for the fewest number of customers, the new customer will go to the end of the leftmost line (that is, the one with the lowest index) with the fewest number of customers.

The illustration below (Figure 2) shows a store over one time-step of the first-come, first-served (FCFS) policy:

Figure 2: The first-come, first-served policy. (Numbers in carts represent item counts for that customer.)

The shortest-job-first policy

The shortest-job-first policy has one important difference from first-come, first-served (and from real stores): customers do not necessarily queue up at the end of the line. Rather, when a new customer arrives to a shortest-job-first store, she enters the line immediately ahead of the last customer in line with a strictly greater number of items than she. Except for the different ways of placing newly-arrived customers, shortest-job-first and first-come, first-served are identical: namely, at each time step, the first customer in each line is served.

Figure 3 shows how to insert a customer into a line.

Figure 3: Adding a customer to the line in the shortest-job-first policy.

Consider the head of the line to be just another place in the line. Therefore, in the shortest-job-first policy, it is possible for a newly-arriving Customer to displace the Customer at the head of the line, if the newly-arriving Customer has fewer items than the head Customer does (currently).

The cashier sharing policy

This policy may seem unusual compared to real-world stores: in cashier-sharing, the cashier takes one item from the customer at the beginning of the line. If the customer has no items left in her cart, then she leaves. Otherwise, she is sent to the back of the line and the next customer is served. Therefore, the cashier only helps one customer at any time-step, but her register is “shared” among all customers in the lane, rather than being exclusively used to serve one customer from arrival until completion.

Again, if a new customer arrives, she goes to the end of the line with the fewest customers. If more than one line is tied for the fewest number of customers, the new customer will go to the end of the leftmost line (that is, the one with the lowest index) with the fewest number of customers.

The illustration below shows a store over one time-step of the cashier sharing policy:

Figure 4: The cashier-sharing policy. (Numbers in carts represent item counts for that customer.)

Instantiable classes you will develop

The Customer class

The Customer class is a container for customer data. The data that a Customer needs to store are:

The Customer class also supports a toString() method, which you will use to generate a String describing the customer’s stay in the store. The String returned from the toString() method should be in the following format:

CUSTOMER: arrived at timestep 5, left at timestep 10, started with 3 items.

The toString() method is not in the JavaDoc for the Customer class; you can see a description of toString() in the JavaDoc for java.lang.Object. (toString(), like equals(), is a method that every object supports.)

Full JavaDoc for the public interface of the Customer class is available here. You must implement all of the public methods in this class. You may add private methods to this class, but you may not add any public methods.

The CheckoutLane class

The CheckoutLane class models a checkout lane, with a line of customers waiting to be served. Your implementation of the CheckoutLane class will use a ResizableArray.

Full JavaDoc for the public interface of the CheckoutLane class is available here. You must implement all of the public methods in this class. You may add private methods to this class, but you may not add any public methods.

The ResizableArray class

Your implementation of the ResizableArray class will use a Java language array (not a library collection class of any kind) to keep track of objects. As mentioned above, you should be able to resize a "full" array if you need to add to it.

Full JavaDoc for the public interface of the ResizableArray class is available here. You must implement all of the public methods in this class. You may add private methods to this class, but you may not add any public methods.

The Store class

The Store class models the checkout section of a grocery store; specifically, it does so by modeling an array of CheckoutLane objects. Your simulations will create a Store class and interact with it when determining which CheckoutLane to place newly-arriving Customers in.

Full JavaDoc for the public interface of the Store class is available here. You must implement all of the public methods in this class. You may add private methods to this class, but you may not add any public methods.

The SimulationTimer class

The SimulationTimer class encapsulates a simple integer timer. You will interact with the SimulationTimer class in two ways:

  1. You will query the current time with the getTime() method.
  2. You will advance the timer after your simulation has done all of the work for a given timestep by using the tick() method.

Full JavaDoc for the public interface of the SimulationTimer class is available here. You must implement all of the public methods in this class. You may add private methods to this class, but you may not add any public methods.

The simulation classes

The simulation classes do the bulk of your program’s work. You will implement three: FirstComeFirstServedSim, ShortestJobFirstSim, and CashierSharingSim. These classes actually implement the policies: creating stores (by allocating a new Store object), handling placement of customers in lanes, printing results for exiting customers, and keep track of how well the policy performs by using a SampleManager instance (see below for details).

A basic outline of what your simulation classes will do was given in the section What happens at each time step. Now that we know about the instantiable classes for this project, we can read the steps below, which give a few more details:

  1. New customers arrive (see below for more information about the CustomerGenerator class).
  2. The arrived customers line up in lanes based on the rules given above. Remember that in the shortest-job-first policy, the arrived customers will not necessarily go to the end of the line.
  3. The customer at the front of each lane is served by the cashier: the customer’s current item count is decremented by one.
  4. For each completed customer: record its completion time, print out its information (using the toString() method of class Customer), record the relevant ratio (see below) in a SampleManager instance, and remove the customer from the lane, shifting all other customers forward.

The JavaDoc for the public interfaces for your simulation classes is given here. You may add private methods to these classes, but you may not add any public methods; each simulation class must implement the Simulation interface, as detailed below.

Implementing public interfaces

In Programming Assignment 1, you developed the classes based on prose descriptions of public interfaces that were specified for you in the assignment text. In this assignment, you will be writing classes that implement a public interfaces we have provided as well. However, an important difference between the interfaces in this assignment and those of Programming Assignment 1 is that we have provided the interfaces for this assignment in a machine-readable format. Because we have done this, you can tell the Java compiler that your class is intended to implement a certain interface. It can then check that you have done so correctly, and it will generate a compile-time error if you have not. (This is a nice feature -- it's great when the compiler can check errors for you, and it's better to get compile-time errors than to lose points on an assignment!)

To specify that a class implements a certain interface, you will use the implements keyword in the class declaration. As an example, if you wanted to say that the class MyAwesomeSim implemented the interface Simulation, you'd declare the MyAwesomeSim class as follows:

public class MyAwesomeSim implements Simulation {
    // Class goes here...
}

By using the implements keyword to indicate that a class implements a particular interface, you are committing to implement all of the methods specified in that interface. This means that, if the Simulation interface contains a void method called runOneStep(), the MyAwesomeSim class must implement such a method. You must use the implements keyword as above to indicate that your classes implement the interfaces provided with this assignment.

Simulation is also a type (it is a wider type than the type of any class that implements Simulation). Therefore, you can declare a variable of type Simulation and give it a reference to a CashierSharingSim, or a FirstComeFirstServedSim, or a ShortestJobFirstSim. Think about how you can use this to make your main method simpler.

Running experiments and reporting results

The CustomerGenerator class

The CustomerGenerator class models a stream of customers arriving at the checkout lanes. Your application class will construct instances of the CustomerGenerator class by invoking its factory methods: makeLight(), makeModerate(), and makeHeavy(), for modeling light, moderate, and heavy customer traffic, respectively. You will then pass an instance of the CustomerGenerator class to the constructor for your simulation classes. Your simulation classes will interact with the CustomerGenerator instance by invoking its getArrivals() method, which will return an array of Customer instances.

We have provided an implementation of the CustomerGenerator class in the grocery.jar file. Full JavaDoc for the public interface of the CustomerGenerator class is available here.

The SampleManager class

The SampleManager class aggregates information about a sequence of samples. In this application, you will be sampling the values of the following ratio for every customer that leaves:

(departure time - arrival time + 1) / initial number of items

The SampleManager will automatically track the number of samples, minimum, maximum, and mean values. You can then query this information after you have sampled a number of values to get statistical information about all the samples you’ve recorded.

We have provided an implementation of the SampleManager class in the grocery.jar file. Full JavaDoc for the public interface of the SampleManager class is available here.

Designing repeatable experiments

The CustomerGenerator uses a pseudorandom number generator (PRNG) in order to decide how many customers are arriving at a given time step and how many items each has. One important property of a PRNG is that you can use it to produce a repeatable stream of numbers that satisfy a certain distribution by specifying a “seed value”. This is useful if you want to see, for example, how different policies work with the exact same stream of customer arrivals. You can specify a “seed” value as a parameter to the makeLight(), makeModerate(), and makeHeavy() factory methods - be sure to use the same seed when making comparisons between policies.

You don’t need to understand how PRNGs and seeds work for this assignment, but if you’re interested in learning more, you can read the Wikipedia entry for pseudorandomness; still more information is available from MathWorld.

Your application: the GroceryApp class

Your application class will be called GroceryApp, and it will create a Store with seven lanes and run the three simulations, one for each policy. You will compare the policies in several configurations; when making direct comparisons (e.g. between three policies in the same configuration), you should use the same seed to initialize the customer generator so that you are testing with the same customer workload (and thus comparing apples to apples). You should design experiments so that you can report the minimum, maximum, and mean ratios of the quantity ( (departureTime - arrivalTime + 1) / initialItemCount ) for each policy under each kind of customer load provided by the CustomerGenerator class. (You should report 27 numbers: min, max, and mean for each of three policies under three different loads.) You should run all of your experiments for at least 5,000 time-steps.

Testing

You are only required to hand in one tester class, ResizableArrayTester. However, your life will be easier and more carefree if you develop unit tests for all of your instantiable classes. You should develop unit tests that exercise every method in the public interface for a class. For each method, you should develop tests that will verify

Questions

  1. Which kind of store has a lower average wait time under light traffic? Under heavy traffic? What is the difference between the worst-case behaviors of these store policies?
  2. If you were building a new store, would you use a policy that worked well under light traffic but poorly under heavy traffic, or vice versa? Why?
  3. Which policy is “fairer”? Why?
  4. How would you make this simulation more realistic?
  5. What would you have to change in your simulation in order to implement a policy in which customers were able to move to different lanes? Would this always improve performance?
  6. How would you change your simulation if customers chose which lane to enter based on the number of items awaiting service rather than the number of customers awaiting service?
  7. What would have happened had you not defined a constructor for the SimulationTimer class? Why?
  8. How would your simulation code have to change to add an “express lane,” or a lane which only accepts customers with fewer than n items (for some small n)? Do you believe that this would be a good use of lanes? Why or why not? How would you have to change the customer-placement rule? (Think about how you would behave in a store.)
  9. Devise and describe an alternate store checkout policy. Do you think your policy would be better than the ones you studied in the assignment? Explain your answer.
  10. List at least three other kinds of systems could you model with this kind of simulation.

Hints

Handin

Use the same technique as last time to hand in your work.

Only hand in the files listed below; do not hand in any other files. These files may be handed in any time prior to the due date. Note: you may hand in your files before the deadline as many times as you wish. We suggest you use your hand-in directory to keep your most recent working copy as you develop your solution.

Required files for this assignment

Turn in all of the instantiable and application classes. (Of course, you should develop tester classes as well, but you don’t have to turn them in.) You should turn in the following classes:

Also turn in

Copyright © 2007 Will Benton