Assignment 2: ATM queue event simulation

Assignment 2: ATM queue event simulation.
The assignment 2 deadline is Wednesday the 21th of October at 4pm

Recent updates

October 15th: Specification updated to clarify definition of utilisation rate in Tasks 3 and 4. October 10th: Another update of the testing program.

The time stamps of Transaction objects used as input to the simulators in tasks 3 and 4, and in the 6730 extra task, had not been updated to reflect the change from 0- based to 1-based indexing. This has now been fixed.

Please download and use the new testing program (assign2_test.py).

Also, a typo in the specification of Task 2 has been fixed.

October 8th: We uploaded an improved testing program (assign2_test.py) that does additional tests for task 1 to make sure your function does return a correct list of Transaction objects. Make sure you download and use this new version of the testing program. If you find any problems with this new testing program please let us know (testing the testing program is tricky, as we cannot anticipate all possible mistakes).

October 1st: Testing program updated.

(The September 25 update of the testing program, to fix the calculation of the time stamp, unfortunately was done in a way that broke the error reporting for certain task 1 failures. This has now been corrected.)

Please download and use the new version of the testing program.

September 30th: Two errors in the testing program fixed. The errors affected only the summary of testing printed at the end of the program run, and the testing of the COMP6730 extra task. Please download and use the new version of the testing program.
September 29th: The extra task for COMP6730 students has been defined. The skeleton and testing program have been updated accordingly. Please download and use the new version of the testing program even if you are not a COMP6730 student.

Sept 25: As discussed in the assignment 2 prep lecture, we fixed the wrong example calculation of time stamps in taks 1. Specifically, we updated the comment in the skeleton program for tasks 1:

Combine the time and date values into a single integer number as the
minutes since the beginning of the month. This is the 'time stamp' of
the transaction.
Note: For the time_stamp to start from 0 (midnight on day 1 of a month),
you need to subtract 1 from the date value.

For example:

00:01,1,.. -> 0*60 + 01 +    (1-1)*24*60 => time_stamp = 1
12:45,3,..  -> 12*60 + 45 +  (3-1)*24*60 => time_stamp =  3645
07:11,14,.. ->  7*60 + 11 + (14-1)*24*60 => time_stamp = 19151

We have uploaded the updated skeleton and testing programs to reflect these changes.

The objectives of this second assignment are to apply the topics learned in the middle and second part of the course, specifically to (1) load and process data sets from files, (2) use data structures such as lists and dictionaries to store and summarise the loaded data, and (3) use object oriented programming to implement an event simulator.

This assignment consists of four tasks as described below. The first two tasks will require you to implement functions that use file input as well as list and dictionary data structures. The third and fourth tasks are based on object oriented programming, the topic of the lectures in semester weeks 8 to 10.

This assignment is worth 15% of your final course mark and will be marked out of 15 as detailed below. We expect this assignment to take the average student around 15 hours of work (1 hour per mark).

Important information

Assignment submission will be done through Wattle. There will be a link (“Assignment 2 submission and marks”) in the week 12 section of the course Wattle page.

You must submit a single Python module (file) named assign2_uNNNNNNN.py, where NNNNNNN is your ANU number. The file name must be all in lowercase letters.

After you upload your solution file, remember to click on “Save Changes”. If you forget to do this the file will not be stored, and we will have NO record of your submission so you will get no marks.

Note that you can upload a new submission as many times as you wish (until the deadline). This replaces your previous submission. We will only mark the last one (i.e., the one that we find when the deadline has passed). Unfortunately, there is NO way for us to see your previous submissions, so if you make a mistake (upload the wrong file), we can not go back to an earlier version. Be careful!

Assignments are individual. You can discuss the problem with fellow students, but you write and submit your own code. Writing code together, or copying code from classmates or from the internet is plagiarism and will result in penalties.

The submission deadline is Wednesday the 21th of October at 4pm. No late submissions are allowed!

No late submissions can be accepted. If you submit 1 minute after 4pm on the 21th October, you mark will be zero.

The ANU has recently adopted a university-wide policy on late assessments. Under this new policy, lecturers must choose between not accepting late submissions, or delaying submissions (and therefore marking) by two full semester weeks. For a course the size of COMP1730/6730 (330+ students), this is not possible.

Start working early! If you plan, and work, to finish the assignment one week ahead of the deadline, you will have spare time if unforeseen issues arise.

Plagiarism
Working together with others on the assignment is not permitted. Assignments are individual. While we encourage you to discuss the problem with us, the tutors, and

other students in the labs and lectures, you must do the assignment work by yourself.
Make sure you read the COMP1730/6730 Assessment page, especially the section on plagiarism:

http://wattlecourses.anu.edu.au/mod/page/view.php?id=690489

Also make sure to look at the information given on the ANU Research School of Computer Science Current Students page, specifically the links in the section ‘Administration’:

http://cs.anu.edu.au/study/currentstudents.
The ANU Web site on Academic Honesty and Plagiarism also contains excellent resources on this topic:

http://academichonesty.anu.edu.au/. Extensions

If, due to unforeseeable circumstances out of your control (such as illness, accidents, etc) you need an extension to complete the assignment, you must contact us and request an extension before the deadline. If you could not possibly contact us before the deadline, do so as soon as possible after. Requests for extension made after the deadline will only be granted in the most exceptional circumstances. A request for an extension must be accompanied by supporting documentation (such as a medical certificate from your doctor, in case of illness). It is OK to supply documentation at a later date; if you are going to ask for an extensions, it is very important that you contact us about it as soon as possible.

Background to the problem

In many situations, a problem can be solved in several ways. For complex problems, finding an optimal solution analytically is often not possible. An alternative is to simulate the problem with different options, such as different input data sets and / or different settings of parameters.

In this assignment we will develop a simulation of people queuing at ATM machines with the aim to estimate average waiting times for a given number of ATM machines using a real log-file of ATM transactions. If there are not enough ATMs available then the queue and thus waiting times become too long and customers become unhappy, while too many ATMs that are not used incur unnecessary costs for a financial institution.

Similar problems occur in many other applications, including how many lifts there should be in a building, or how many counters should there be during checking at an airport.

Our assignment is based on real log-file of ATM transaction data as sourced from:

http://highered.mheducation.com/sites/0073521485/student_view0/big_data_sets.html

We processed this data and extracted several smaller data sets to allow for testing of the different assignment tasks described below. We also modified the data by (1) removing unnecessary fields, (2) adding a duration field, and (3) introducing errors into some of these data sets. The data sets we provide are in comma separated values (CSV) format and contain the following fields (or columns):

1. time Time of a transaction, format being HH:MM, with 00 ≤ HH ≤ 23 and 00 ≤ MM ≤ 59. 2. date Date of a transaction, a number from 1 to potentially 31.

3. weekday
4. duration
5. amount
6. type The transaction type, one of: balance, deposit, withdrawal, transfer, advance. 7. location A string of the location where the ATM is located.

Note the first line in each CSV file we provide is a header line with the names of the fields (columns). Here is an example from one of the data sets we will provide:

Assignment tasks

First of all, download:

The automated testing program: assign2_test.py The skeleton program: assign2_skeleton.py The ATM transaction data sets:

atm-transactions-all.csv (the full transaction file) atm-transactions-1-clean.csv (1000 transactions) atm-transactions-1-dirty.csv (1000 transactions with errors)

The weekday of a transaction, one of mon, tue, to sun.

How long a transaction took in minutes (where short transactions can have a value of 0 minutes). The amount of cash withdrawn, deposited, or transferred. This is always a positive number.

time,date,weekday,duration,amount,type,location
00:19,1,tue,1,,balance,driveup
00:20,1,tue,8,357,deposit,driveup
01:26,1,tue,6,141,deposit,driveup
03:07,1,tue,1,30,withdrawal,campusa
04:24,1,tue,2,90,withdrawal,driveup
04:57,1,tue,3,20,withdrawal,driveup
05:32,1,tue,10,1784,deposit,driveup
05:56,1,tue,12,598,deposit,driveup
06:24,1,tue,7,80,withdrawal,driveup
06:25,1,tue,7,20,withdrawal,driveup

atm-transactions-2-clean.csv (1000 transactions) atm-transactions-2-dirty.csv (1000 transactions with errors)

You must use the skeleton program assign2_skeleton.py for your assignment 2 implementation. This program contains (empty) function definitions as well as a main program.

Your task is to implement the functions and methods in the given skeleton program. Do NOT rename these functions or change their arguments, as our testing program will call these functions. You can add other functions, that are used within your implementation of the functions and methods given in the skeleton program. You can also add other classes, and change the superclasses of the two simulator classes if you wish. However, you may not modify the Transaction class.

Have a good read through the skeleton program, especially the class Transaction and its methods at the beginning of the program. While at the moment you might not fully understand this class (we cover classes in the first three weeks after the semester break), this class simply provides a new data type (called Transaction) which encapsulates all the information for one transaction, almost like a tuple. You can create a new variable of type Transaction in the following way:

new_trans = Transaction(time_stamp, time_of_day, day_of_month ,week_day, duration, amount, trans_type, location)
where time_stamp, time_of_day, and so on are variable names for values you might have loaded from one of the transaction files (as in task 1 described below).

Task 1: Implement function load_atm_file(atm_file_name): (3 marks)

In this task you need to implement the function load_atm_file(atm_file_name) given in the skeleton program. The doc-string (comment) at the beginning of the empty function describes what this function needs to do, including what checks need to be applied on each row of the file to make sure only correct and consistent ATM transactions are included in the list returned. The following checks need to be performed:

1. Illegal time value structure (not HH:MM) and illegal time value (HH < 0 or HH > 23, MM < 0 or MM > 59). 2. Illegal date value (< 1 or > 31).
3. Illegal weekday value (not in ‘mon’, ‘tue’, ‘wed’, ‘thu’, ‘fri’, ‘sat’, ‘sun’).
4. Missing or illegal duration (i.e. a negative value for duration).

5. The value of amount must be a positive integer for all transaction types except ‘balance’.

6. If transaction type is ‘balance’ and a non-zero amount is given.
Do not include any record (row) with errors 1. to 5., but fix error 6. by setting the amount to 0 for a transaction of type balance.

Your function must also calculate a time_stamp for each transaction which is basically the number of minutes since the beginning of the month when the transaction happened. The skeleton program contains examples how to calculate such time stamps from the time and day values as available in the transaction files.

Important: Your function must return a list of Transaction objects (variables) as specified in the skeleton program. Note that some of the attributes (variables) of a Transaction object are strings while others are integers. The test program will check if both the types and values of all Transaction objects in a returned list are correct.

The returned list must be sorted according to the values of time_stamp with smallest values first. Our testing program will check the correctness of the list returned by your function for different test data sets.

Task 2: Implement function summarise_atm_data(atm_transaction_list): (3 marks)

In this task you need to implement the function summarise_atm_data(atm_transaction_list) given in the skeleton program. The doc-string (comment) at the beginning of the empty function describes what this function needs to do. Specifically, the function takes as input a list of Transaction objects atm_transaction_list as generated by the load_atm_file() function from task 1 above, and calculates and prints six summary statistics of the given list of ATM transactions. Some of these are single numbers only, others are tables summarising information for each location that occurs in a file.

The function must both print these summaries onto the terminal output, and also return a list that contains one element for each of the six summaries, as follows (two examples are given below):

  1. List element 1: Number of transactions (integer).
  2. List element 2: Number of locations covered (integer).
  3. List element 3: Percentages of the different types of transactions as a list of tuples, where each tuple consists of the transaction type (a string) and a percentage value (floating-point number). This list must be sorted alphabetically. The sum of percentage values must be 100.
  4. List element 4: Percentages of transactions at the different locations as a list of tuples, where each tuple consists of the location (a string) and a percentage value (floating-point number). This list must be sorted alphabetically. The sum of percentage values must be 100.
  5. List element 5: Number of transactions of different types per location. This is a list that contains lists. The first of these lists contains the string ‘Location’ followed by the names of the different types of transactions (sorted alphabetically), followed by the string ‘Total’:

    [‘Location’, ‘Advance’, ‘Balance’, ‘Deposit’, ‘Transfer’, ‘Withdrawal’, ‘Total’],
    Each of the following lists contains the name of the location (a string), followed by the number of transactions of the given type at that location (integer), followed

    by the total number of transactions at that location (integer).
    The location names (and thus their corresponding lists of values) must be sorted alphabetically.

  6. List element 6: Average amount per transaction type (not balance) per location. This is a list that contains lists. The first of these lists contains the string ‘Location’ followed by the names of the different types of transactions (sorted alphabetically) except the name ‘Balance’:

    [‘Location’, ‘Advance’, ‘Deposit’, ‘Transfer’, ‘Withdrawal’],
    Each of the following lists contains the name of the location (a string), followed by the average amount per transaction of the given type at that location (floating-

    point).
    The location names (and thus their corresponding lists of values) must be sorted alphabetically.

As examples, here is the printed output for the test file atm-transactions-1-clean.csv:

Summary statistics:
  1) Number of transactions: 1000
  2) Number of locations covered: 1
  3) Percentages of the different types of transactions:
     advance    are  0.40 percent of transactions
     balance    are 19.10 percent of transactions

while the expected list returned for this test file is:

And here the printed output for the test file atm-transactions-2-dirty.csv:

[1000, 1,

 [('advance', 0.4), ('balance', 19.1), ('deposit', 24.7), ('transfer', 2.8), ('withdrawal', 53.0)],
 [('driveup', 100.0)],
 [['Location', 'Advance', 'Balance', 'Deposit', 'Transfer', 'Withdrawal', 'Total'],
  ['driveup', 4, 191, 247, 28, 530, 1000]],
 [['Location', 'Advance', 'Deposit', 'Transfer', 'Withdrawal'],
  ['driveup', 30.0, 930.3238866396761, 1213.607142857143, 84.47169811320755]]
]
Summary statistics:
  1) Number of transactions: 700
  2) Number of locations covered: 3
  3) Percentages of the different types of transactions:
advance
balance
deposit
transfer   are  2.00 percent of transactions
withdrawal are 58.43 percent of transactions
campusa   |
campusb   |
driveup   |

0| 5| 0| 0| 3| 77| 56| 8| 1| 62| 73| 6|

42| 47 219| 363 148| 290

are  0.57 percent of transactions
are 20.57 percent of transactions
are 18.43 percent of transactions
4) Percentages of transactions at different locations:
    6.71 percent of transactions are at campusa
   51.86 percent of transactions are at campusb
   41.43 percent of transactions are at driveup
5) Number of transactions of different types per location:
   Location  |   Advance  |   Balance  |   Deposit  |  Transfer  | Withdrawal | Total
   ----------------------------------------------------------------------------------
6) Average amount per transaction type per location:
   Location  |   Advance  |   Deposit  |  Transfer  | Withdrawal
   -------------------------------------------------------------
   campusa   |       0.0  |       0.0  |       0.0  |      39.8
   campusb   |      13.3  |     370.4  |     507.5  |      37.4
   driveup   |      40.0  |    1736.1  |    1725.0  |      77.4

while the expected list returned for this test file is:

Task 3: Implement queuing simulation for one ATM: (3 marks)

The purpose task 3 and 4 is to implement a simulation of how customers queue for ATMs. Because we can consider the different locations to be independent (they are sufficiently far apart), the simulation will only need to deal with customers at a single location. We will use this simulation to determine what is the best number of ATMs to place in each location.

To implement a simulation, we must first clearly state the rules for how the simulated world evolves. (See Section 13.1 of Chapter 13 in the text book for another example of how a simulation is defined.) For the single queue case, the rules are as follows:

Input to the simulation is a list of Transactions (i.e., the same type of input as to the summarise_atm_data function in task 2). You can assume that all Transactions in the input list are sorted in increasing order by their time stamp.

For the purpose of simulation, we will assume that the time of each Transaction record is the time when the customer arrived at the location.

There are N ATMs at the location, numbered 1,…,N. For task 3, we will assume that N = 1, i.e., that there is a single ATM in the location. For task 4, you will need to generalise the simulation to work for any (positive) number of ATMs.

When a customer arrives at the location, if there is a free ATM the customer will begin using it immediately, and will finish d minutes later, where d is the duration of

   deposit    are 24.70 percent of transactions
   transfer   are  2.80 percent of transactions
   withdrawal are 53.00 percent of transactions
4) Percentages of transactions at different locations:
   100.00 percent of transactions are at driveup

5) Number of transactions of different types per location:
Location | Advance | Balance | Deposit | Transfer | Withdrawal | Total ———————————————————————————- driveup | 4| 191| 247| 28| 530|1000

6) Average amount per transaction type per location:
   Location  |   Advance  |   Deposit  |  Transfer  | Withdrawal
   -------------------------------------------------------------
   driveup   |      30.0  |     930.3  |    1213.6  |      84.5

[700, 3,

 [('advance', 0.5714285714285714), ('balance', 20.571428571428573), ('deposit', 18.428571428571427), ('transfer', 2.0), ('withdrawal', 58.428
 [('campusa', 6.714285714285714), ('campusb', 51.857142857142854), ('driveup', 41.42857142857143)],
 [['Location', 'Advance', 'Balance', 'Deposit', 'Transfer', 'Withdrawal', 'Total'],
  ['campusa', 0, 5, 0, 0, 42, 47], ['campusb', 3, 77, 56, 8, 219, 363], ['driveup', 1, 62, 73, 6, 148, 290]],
 [['Location', 'Advance', 'Deposit', 'Transfer', 'Withdrawal'],
  ['campusa', 0.0, 0.0, 0.0, 39.76190476190476], ['campusb', 13.333333333333334, 370.44642857142856, 507.5, 37.397260273972606], ['driveup',
]

5

4

the transaction record.

If there is no free ATM when the customer arrives, the customer is placed at the end of a queue. For task 3 and 4, we assume there is a single queue, in which customers wait for any ATM to become free.

When a transaction finishes, the ATM that the customer was using becomes free. If there are any customers in the queue at the location, the first customer in the queue then immediately moves to and begins using the ATM that has become free (and finishes d minutes later, where d is the duration of the transaction record).

If more than one ATM becomes free at the same time and there are customers waiting, the first customer in the queue moves to the lowest numbered ATM; the next waiting customer then moves to the next free ATM, and so on until there are no free ATMs or no waiting customers.

Implementation requirements

For this task, you only need to consider the case of a single ATM at the location. (That is, you can assume N = 1.) You will have to implement the simulation in an object- oriented framework. The skeleton file defines a class called SingleATMSimulator. This class has three important methods:

__init__(self, location) is the constructor. It takes a single argument, which is the name of the location to simulate (a string).

simulate(self, atm_transaction_list) is where you should implement the simulation of a single queue for a single ATM. The argument is a time-ordered list of Transaction records, as described above. The method should only consider those records in the list whose location equals that of the simulator object (i.e., the argument that was given to the constructor); any records in the input that refer to a different location should be ignored. The simulate method does not return any value.

statistics(self) which returns the summary statistics for the simulation so far. The method should return a tuple of four values: number_of_customers, utilisation_rate, number_of_waits, average_wait
The precise meaning of these is described below.

The four values to be returned by the statistics method are:
number_of_customers is the number of customers who have used an ATM at the location during the simulation. utilisation_rate is the fraction of time that ATMs were in use. The utilisation rate at a location L is defined as

(sum of durations of Transactions at L) / (number of ATMs at L * total simulation time)
In Task 3, the number of ATMs is always 1, and there is only a single location for which to do the calculation. However, Task 4 below involves simulation of

multiple locations with multiple ATMs, in which case the utilisation rate is calculated according to this general formula.

Important: For the purpose of calculating the utilisation rate, the simulation is considered to have started at the beginning of the day of the first transaction, and to have ended at the end of the day on which the last transaction finishes. (See the example below for illustration.)

number_of_waits is the number of customers who had to wait.

average_wait is the average waiting time, for those customers who had to wait.
Every call to the simulate method runs a new simulation. The statistics method should return the statistics from the last simulation run. If statistics is called before any

simulation has been run, it should return zero for all four values.
The SingleATMSimulator class provided in the skeleton file is a subclass of the “top” class, object. You may change this if you wish; that is, you can introduce another

class to be the parent class of SingleATMSimulator.
Example
To explain how the simulation should work, here is an example. Suppose the input to the simulation is the following list of Transactions:

The simulation then proceeds as follows:

At 22:24, the first customer arrives. The ATM is free, so his transaction begins immediately.
At 22:25, the second customer arrives. Since the ATM is in use, this customer must wait.
At 22:27 (22:24 + 3), the first customer finishes, and the second customer’s transaction begins. She has then waited 2 minutes. 3 is the duration of the first transaction.
At 22:31 (22:27 + 4), the second customer’s transaction finishes. 4 is the duration of the second transaction.
At 23:48, the third customer arrives, and begins his transaction. The customer does not have to wait as the ATM is free.
At 23:50, the fourth customer arrives, and joins the queue.
At 23:55 (23:48 + 7), the third customer finishes, and the fourth customer begins his transaction. He has waited 5 minutes. 7 is the duration of the third transaction. At 23:55, the fifth customer also arrives. Since the ATM is busy, she joins the queue.
At 23:58 (23:55 + 3), the fourth customer finishes and the fifth customer can begin her transaction. She has waited 3 minutes.
At 00:02 (23:58 + 4), on the 20th, the fifth customer’s transaction finishes.

At this point, the statistics for the example are as follows:

The number of customers served is 5.
The start of the first transaction occurred on the 19th, and the end of the last transaction on the 20th. Therefore, the total simulation time is 48 hours. The sum of transaction durations is 21 minutes, so the utilisation rate is 21 / (60 * 48) = 0.00729166.
The number of customers who had to wait is 3.
The average waiting time is 10 / 3 = 3.33333333.

Testing

To test your SingleATMSimulator, you can proceed as follows: First, create a list of Transactions (for example, by reading them from a file using the function from Task 1). Suppose you have called this test_trans_list. Then, you can run:

22:24,19,sat,3,,balance,test1
22:25,19,sat,4,20,withdrawal,test1
23:48,19,sat,7,90,withdrawal,test1
23:50,19,sat,3,35,withdrawal,test1
23:55,19,sat,4,,balance,test1
my_sim = SingleATMSimulator("campusb")
my_sim.simulate(test_trans_list)
stats = my_sim.statistics()
print(stats)

Task 4: Implement queuing simulation for several ATMs in multiple locations: (2 marks)

In this task, you will extend the simulator to work with multiple locations and multiple ATMs in each location. The rules for the simulation are the same as described for Task 3 above, except that you can no longer assume a single ATM per location. Each location is independent, meaning customers will only queue and do their transaction at the location named in the Transaction record.

The skeleton file defines a class called MultiATMSimulator. This class also has methods __init__, simulate and statistics.

The __init__ method takes a single argument, which is a dictionary that has location names (strings) as keys and the number of ATMs at those locations as the corresponding values. The simulator must simulate queuing at all locations that appear in the dictionary. The simulate method takes as argument a time-ordered list of Transactions. In the same way as in Task 3, the simulate method must ignore any transactions in the list that refer to locations that are not being simulated, i.e., not found in the dictionary passed to the simulator’s constructor. The statistics method takes no argument, and must return a dictionary that maps the name of each simulated location to the the 4-tuple of statistics (number of customers served, utilisation rate, number of customers who waited, average wait time) for that location.

Important: For the purpose of calculating the utilisation rate, the simulation is considered to have started at the beginning of the first day on which there was any transaction at any of the locations being simulated (i.e., that are found in the dictionary passed to the simulator constructor). The simulation end time is the end of the day on which the last transaction at any of the simulated locations ended.

The MultiATMSimulator class provided in the skeleton file is a subclass of the “top” class, object. You may change this if you wish; that is, you can introduce another class to be the parent class of MultiATMSimulator.

Example

The following code runs a simulation using the complete transaction file, with 2 ATMs at location ‘driveup’, 1 ATM at ‘campusa’ and 3 ATMs at ‘campusb’, and prints the statistics:

atm_transaction_list = load_atm_file("atm-transactions-all.csv")
atm_num_loc_dict = {'driveup' : 2, 'campusa' : 1, 'campusb' : 3}
my_sim = MultiATMSimulator(atm_num_loc_dict)
my_sim.simulate(atm_transaction_list)
stats = my_sim.statistics()
for key,val in stats.items():
    print(key, ':', val)

It should print the following:

campusa : (658, 0.05604166666666666, 126, 3.7777777777777777)
campusb : (7285, 0.22869598765432098, 1445, 3.513494809688581)
driveup : (6970, 0.34866898148148145, 3928, 12.593431771894094)

(though not necessarily in that order).

Extension task (COMP6730 only): Scheduling ATM restocking

For this task, you will need to implement a different analysis of the ATM transaction data. There is a limit on how much money an ATM can store. The ATMs in our data set can take deposits as well as dispense cash, so over time, we may hope the two types of transactions balance out. If they do not, however, it is necessary to restock the ATM, meaning to refill it with cash, or remove some of the surplus if deposits have outweighted withdrawals. The objective of this part of the assignment is to compute, from a list of transaction records, a schedule of restocking operations.

The task is to implement the function

compute_restock_schedule(transaction_list, location, limit)

The arguments are a (time-ordered) list of Transaction objects, the location to compute a schedule for (a string), and the limit on the amount of cash that can be stored in the ATM (a positive number).

The function must return a restocking schedule. This is a list of pairs of values, (day, amount), where day (an integer) is the day on which the restock occurs (see details below) and amount (a number) is the amount of money to be moved to the ATM. A negative number means money is collected from the ATM instead.

The conditions on the schedule are as follows:

The function must only compute a restocking schedule for a single location. The transaction list may contain transactions referring to other locations, but these should be ignored.

The schedule must not contain multiple events for the same day, and the days of events in the schedule must be in increasing order.

We assume that ATMs in a single location have a shared cash storage. The amount of cash in storage must be between 0 and the given limit (inclusive) at every point. (This means after every transaction and restocking.)

Transactions are assumed to occur in the order that they are found in the list, on the day that is given in the transaction record. (This means that for this task we are ignoring queuing.)

If a restocking is scheduled on day X, it occurs between day X and X+1, i.e., after the last transaction on day X and before the first transaction on day X+1.
Days in the schedule must be between 0 and 31 (inclusive). We assume that the initial amount in storage at every location is 0. However, since the first transaction

for every location in our data occurs on day 1, it is possible (and sometimes necessary) to schedule a restocking before the first transaction, by scheduling it on day 0.

The function will only be called with arguments for which a feasible restocking schedule exists. In fact, it is almost always the case that there is more than one feasible solution. As an additional challenge (though not for additional marks), you can consider how to compute a schedule with as few restocking events as possible.

Testing

To test your implementation, add 6730 (as an integer) to TEST_TASKS_LIST in the main part of the testing program and run it.

Submission requirements
You must implement the specific functions given in the skeleton file assign2_skeleton.py given above. Partial marks will be given if some of your functions work, and/or

if your functions do not pass all tests with the different test ATM files we will be using.

Make sure you test your program with ALL the test ATM files given above.
You must submit one Python module (file), named assign2_uNNNNNNN.py, where NNNNNNN is your ANU u-number. The file name must be all in lowercase letters. This module must contain:

A comment (at the top of the file) with your u-number, but not your name (we will deduct marks if this comment is missing or if you include your name!) Your implementation of the required functions and methods as detailed in tasks 1 to 4 above.
(COMP6730 students only) Your implementation of the functions and methods detailed in Task 5 above.

If you create additional functions used by the required functions then these must also be in the module. However, your module will be imported into the testing framework (described below), so it must not contain any statements other than function definitions (except possibly import statements) that are executed when the module is imported. Do not call any plotting or other graphics functions in your submitted module.

Importing other modules

You may import and use modules from the python3 standard library (such as, for example, csv or math), as well as, of course, all built-in functions (arithmetic operators, len, enumerate, etc), data types and their methods. However, keep in mind the rule of simplicity (detailed below): We will deduct marks for solutions that use unnecessary external modules or functions.

You may not import or use any module that is not in the python3 standard library (such as numpy and scipy). You may also not split your submission into multiple modules (files) since you can submit only one file.

Testing

To help you test your implementation, we provide an automated testing program. Make sure to use the testing program! This is essentially the same software that we will be using to test your assignment submission. If your submission does not work in the testing framework, we will not consider it to be correct and you will not receive any marks.

The automated testing program will use the four smaller test data sets we have provided, so you must copy them into the same directory as your program and the test program assign2_test.py.

The automated testing program

The testing program is called assign2_test.py. Save a copy of it to your working directory. To use the testing program, you have to open it in an editor (IDLE or another editor), scroll down to the end of the file, and replace the name of the module to be tested (“assign2_uNNNNNNN.py”) with the name of your module.

You can also specify with the TEST_TASKS_LIST which tasks you like to have tested.
After that, you can simply run the testing program (e.g., by pressing F5 in IDLE). It will run the program with the selected tasks and print if a test is passed or not. If not it

will print a message with details of what was wrong.
As an example, running the testing program with the skeleton program assign2_skeleton.py and assuming TEST_TASKS_LIST = [1,2,3,4] produces:

Marking

**************************************************
*** TESTING TASK 1 FUNCTIONALITY ***
Tesing function 'load_atm_file' (task 1) with input file: 'atm-transactions-1-clean.csv'
FAILED task 1 with input file: "atm-transactions-1-clean.csv"
  List of transactions returned by function "load_atm_file" contains 0 elements (1000 expected)
**************************************************
*** TESTING TASK 2 FUNCTIONALITY ***
Testing 'summarise_atm_data' (task 2) with data file: 'atm-transactions-1-clean.csv'
FAILED task 2 with input file: "atm-transactions-1-clean.csv"
  Summary statistics list returned by function "summarise_atm_data" contains 0 elements (6 expected)
**************************************************
*** TESTING TASK 3 FUNCTIONALITY ***
Testing simulation: from = 1, to = 1, loc = driveup...Failed
simulator.statistics() returned (0, 0, 0, 0), correct values are (290, 0.4354166666666667, 282, 126.71631205673759); differ in positions [0,
**************************************************
*** TESTING TASK 4 FUNCTIONALITY ***
Testing simulation: from = 1, to = 1, config = {'driveup': 3, 'campusb': 1, 'campusa': 1}...Failed
simulator.statistics() returned locations [], should be ['campusa', 'campusb', 'driveup']
**************************************************
Summary of testing:
Task 1: FAILED
Task 2: FAILED
Task 3: FAILED
Task 4: FAILED

2

Marks are divided into two categories: Functional requirements and code quality. To evaluate the functional requirements we will test your code using essentially the same testing framework that we provide to you (but using additional data sets and test cases). We will test your code on the CSIT lab computers using python3. If it does not work in this setting it is not correct.

Note: Unlike for assignment 1, task 5 is for COMP6730 students only, and COMP1730 students will not receive bonus marks if they solve this task.

Task 1:

The load_atm_file(atm_file_name) function runs and passes the task 1 test for all test data sets – both clean and dirty (with errors). All tests means both the cases we provided you with through the testing program and our additional test cases.
If the function passes only the clean test data set then the maximum mark for this part is 1.
Note: Passing the test cases is just an indicator that your code is correct, it is not the correctness criterion in itself. If we find that you have done something silly, like hardcode the correct values for the provided test cases, we will not consider your submission to have passed those tests.

We expect your implementation of all tasks to be reasonably efficient, but do not stipulate any particular time bounds. As a point of reference, our implementation (which is not very optimised) completes the full suite of tests (all tasks) in less than one minute.

3 marks

Task 2:

Full marks will be given if the function passes all task 2 tests.
Partial marks will be given if the function correctly prints and generates only some of the six expected summary statistics. Partial marks will be given if the function passes only the test cases based on clean data sets.

3 marks

Task 3:

Full marks will be given if the class implementation passes all task 3 tests.
Partial marks will be given if the simulator returns some correct values for the four values to be returned by the statistics method.

3 marks

Task 4:

Full marks will be given if the class implementation passes all task 4 tests.
Partial marks will be given if the simulator returns some correct values for the four values to be returned by the statistics method.

2 marks

Additional task (COMP6730 students only)

The additional task is worth three marks. It is only for COMP6730 students. Their total mark out of 18 will be scaled down to a mark out of 15. Full marks will be given if the function runs without error and returns valid schedules.

3 marks

Code quality.

The submitted file must have a comment at the beginning, stating your ANU number. (We will deduct marks if this is missing!)
The code is readable and has appropriate comments. Use comments to explain parts of the code that are not obvious.
Use of good variable (and function) naming.
The code is not unnecessarily complicated. We will deduct marks for solving problems in a way that is significantly more complicated than necessary. This includes importing and using modules that do not contribute to the solution.

4 marks