Note that the hour of the deadline is not the same as for homework assignments.
On the day of the deadline, it may not be possible for the instructor to answer questions received after 3pm.
Collaboration is prohibited. We want to evaluate you on the basis of what you can do by consulting the resources listed below, and without help from anyone other than course staff. Seeking or giving aid on this assignment is prohibited (except for asking course staff); doing so constitutes academic misconduct which can have serious consequences. If you are unsure about whether something is allowed, ask. The course syllabus contains more information about the course and university policies regarding academic honesty.
In this project you will read some existing object-oriented code and documentation in order to understand how to use it. You will then write some new subclasses that interact with the existing code to perform requested functions.
The classes in this project represent a simplified model of factory automation system that loads finished goods onto trucks.
Ask if you're not sure whether a resource falls into one of these categories, or if you think I missed a resource that seems necessary to complete the assignment.
Ask your instructor or TA a question by email, in office hours, or on discord.
You are an employee of a manufacturing company that produces parts for wind turbines. Once manufactured, the parts are packed into crates and shipped by truck to another company that assembles the turbines (using parts from several other manufacturers, too).
There are three types of parts (0, 1, and 2) your company makes, but they share a number of properties:
Since three shipping crates fit in a truck, this makes coordination of shipping easy: When a truck pulls up it waits for one crate to come off of each production line. Then the truck drives away with one part of type 0, one of type 1, and one of type 2.
This entire process has been automated using robotic loaders, and all the plant's equipment is controlled by software written in Python.
When your company's contract to produce turbine parts is renewed, the scope of work expands. Now they must:
Thankfully, all five parts are still of the same size, so you can still use the standard crates.
These changes create problems in the plant's shipping depot. Trucks will now arrive more frequently, but each truck still only holds three crates. With five lines producing five types of parts at varying and unpredictable rates, some sort of plan is needed to decide which crates to load into a truck when it arrives. Further complicating things is the fact that storage space at the plant is very limited; the depot itself has no extra space, and each production line can only store a few of its finished parts before they run out of room and need to shut the line down.
You have been assigned to solve this problem by updating the software controlling the robotic loaders. The existing Python software uses sensors in the plant to check and report how many crates are waiting on each production line, and is able to instruct the robotic loaders to move the next crate from any line to a truck. A class representing the idea of a truck-loading strategy has already been sketched out, so all you need to do is create subclasses that implement various strategies the plant will test.
It is a ZIP archive, i.e. a group of files bundled together and compressed into a single file. The first thing you need to do is to extract the starter pack into a directory where you will do your work. Contact the instructor or TA if you need assistance extracting a ZIP file. Windows, MacOS, and most Linux distributions come with a ZIP extractor either built into the operating system or pre-installed.
If you want to browse the starter pack files on github, you can also do that. But the ZIP is the easiest way to download all of them at once (as you must do):
It contains A FILE YOU EDIT AND SUBMIT AS YOUR PROJECT:
routing.py
- Contains the Router
base class and a couple of subclasses. You will add more subclasses to the end of this file (without changing any of the classes already present).There is also a module that is used by routing.py
, but which you won't edit at all:
fixtures.py
- Imported by routing.py
. Contains classes related to crates, production lines, loadout of a truck, etc.Finally, the start pack provides a sample program that shows how these classes can be combined into a simulation of the entire system in operation. Running this program is one way to test your work on the project.
simulation.py
- Simulation programtui.py
- A module used by simulation.py
This project involves adding subclasses to an existing object-oriented program. In the end, it won't amount to a lot of code. Fully reading and understanding this project description and the contents of routing.py
will probably account for more than half of the work.
More generally, reading and understanding technical documentation and existing code are important skills that this project aims to develop.
Every object and facility involved in the shipping operation (crates containing turbine parts, waiting areas at the end of the production lines, the strategy for deciding what to put on a truck, etc.) is represented by a Python class or object.
Finished parts that have been packed in crates are tracked by the manufacturing facility software, which creates an instance of class Crate
(in fixtures.py
) for each one. While each Crate
object contains a bunch of data about the contents, the details vary from production line to production line, so the shipping software won't actually look into the contents of any Crate
object. You'll treat each Crate
object as a unit that gets moved around, not examined.
Each production line stores its finished crated parts in a queue (a line where newly made parts are added at one end and parts are taken out to be shipped from the other end). The state of each such storage area is represented by an object of class CrateQueue
(in fixtures.py
). That class has methods and attributes you can use to examine how full the storage area is, to examine the next crate waiting to be taken out, and to order the loading robots to take the next crate out and put it on a truck. Thus, calling methods of class CrateQueue
is the main job of the system that controls truck-packing.*
As described earlier, each CrateQueue
has limited size. If it becomes full, the associated production line shuts down temporarily. Naturally, this is something the company wants to avoid.
Since each truck can hold three crates, the intended contents of a truck can be represented by a list of Crate
objects (of length at most 3).
Each strategy for deciding how to load the trucks (a routing strategy) is implemented by a subclass of Router
(class in routing.py
). The constructor of this class expects five CrateQueue
objects, corresponding to the five production lines.
The Router
class has a method called prepare_load()
which will be called any time a truck arrives at the facility. This method should examine the queues of the different production lines, remove up to three Crate
objects from them, and return those objects in a list. That list will then be used by the robotic loaders to pack the truck and to generate the shipping manifest and other documents that need to go with it.
Thus, here is pseudocode for how the overall software system works:
# Make queues to track finished crates
Q0 = CrateQueue(size=5) # queue at production line 0
Q1 = CrateQueue(size=5) # queue at production line 1
Q2 = CrateQueue(size=5) # queue at production line 2
Q3 = CrateQueue(size=8) # queue at production line 3
Q4 = CrateQueue(size=8) # queue at production line 4
# Link the queues to the sensors on each production line
# `ProductionLineMonitor` notices finished crates using
# hardware sensors and adds `Crate` objects to a queue.
# (ProductionLineMonitor class doesn't really exist,
# instead we'll simulate creation of crates.)
N0 = ProductionLineMonitor(sensor_id=0,queue=Q0)
N1 = ProductionLineMonitor(sensor_id=1,queue=Q1)
N2 = ProductionLineMonitor(sensor_id=2,queue=Q2)
N3 = ProductionLineMonitor(sensor_id=3,queue=Q3)
N4 = ProductionLineMonitor(sensor_id=4,queue=Q4)
# Suppose ExcellentRouter is the subclass of Router we're trying out
# (you're going to write a class like this)
# Tell it to use the five queues
R = ExcellentRouter( [Q0,Q1,Q2,Q3,Q4] )
# Automated loader control system
# (doesn't really exist, and in practice we'll test
# routing by looking at return values of prepare_load)
Ldr = RoboticLoaderControl()
# Main loop
while True:
# Pause until a truck arrives
wait_for_truck()
# Make sure all recently created crates have been
# tracked and entered in the proper queue
for N in [N0,N1,N2,N3,N4]:
N.sensor_update()
# Ask the router to decide which crates to load
B = R.prepare_load() # returns list of <=3 crate objects
# Dispatch loader robots to pack the truck
Ldr.load_crates(B)
One of the key points to notice in the pseudocode above is that a Router
subclass instance needs to allow for the possibility that the contents of the input queues will change (perhaps drastically) between one call to prepare_load()
and the next.
routing.py
¶A few classes are provided. You'll add to the hierarchy, so you'll need to understand what methods and attributes exist.
Router
¶Superclass: None
Purpose: Base class for all routing strategies. Doesn't actually do anything useful.
Attributes you need to know about:
input_queues
- A list of the CrateQueue
objects to monitor and take Crate
s from.Methods you need to know about:
__init__(self,input_queues)
- Accepts a list of five CrateQueue
objects and stores it as an instance attribute named input_queues
.prepare_load(self)
- Subclasses will redefine this to do something useful. In this class it simply raises an exception immediately.OldRouter
¶Superclass: Router
Purpose: A minimally updated version of the old packing system from the days when there were only three production lines. It ignores the last two CrateQueue
objects and simply takes one Crate
from each of the first three queues. No longer suitable to the task, but provided for reference.
Attributes redefined or added in this subclass:
Methods redefined or added in this subclass
prepare_load(self)
- Get a Crate
from each of the first three input queues. (It is possible one or more of those queues might be empty.) The resulting three (or fewer) crates are returned in a list.routing.py
¶BreadthRouter
¶Superclass: Router
Purpose: Router that tries to take three crates from different queues every time.
Attributes required to be redefined or added in this subclass:
Methods redefined or added in this subclass
prepare_load(self)
- Examines the input queues in order (0 to 4), taking one crate out if the queue is nonempty, and stopping when three crates have been obtained or when every queue has been considered (whichever happens first). Returns the three (or fewer) crates thus obtained as a list, in the order they were removed from the queues. Note that this process never removes two crates from a single queue.CyclingDepthRouter
¶Superclass: Router
Purpose: A router that has the queues take turns being the "active one". A truck is loaded with as many crates as possible from the active queue, then the active status moves to a different queue.
Attributes required to be redefined or added in this subclass:
active_queue
- An integer, initially 0
, indicating which input queue is to be used next. Methods required to be redefined or added in this subclass
__init__(self,input_queues)
- In addition to the work of Router.__init__
, must initialize the active_queue
attribute to 0
.prepare_load(self)
- Looks at the input queue at index active_queue
in attribute input_queues
. Takes three crates out, or as many crates as are available, whichever is smaller, and puts them in a list in the order removed. Increases active_queue
by 1
, wrapping around to 0
if the result is greater than 4
. Returns the list.active_queue
by 1
).reset(self)
- Sets active_queue
back to 0
, so the router goes back to the state it was in when first created.FillPriorityRouter
¶Superclass: Router
Purpose: A router that tries to avoid full queues by repeatedly taking crates from whichever queue has the least available space. The intention is to avoid the shutdowns that result from a full queue.
Attributes required to be redefined or added in this subclass:
Methods redefined or added in this subclass
prepare_load(self)
- Repeats the process outlined below three times, or until it is determined that all input queues are empty (whichever comes first). Then returns the list of crates removed from queues in that process, in the order removed.input_queues
.BalanceRouter
¶Superclass: Router
Purpose: A router that assumes it is desirable to ship the same number of parts from each production line, on average, and which therefore prefers to take parts from a line that's seen the least shipping thus far.
Attributes required to be redefined or added in this subclass:
Methods redefined or added in this subclass
__init__(self)
- In addition to superclass initialization, will need to create an one or more instance attribute(s) that will be used to track the shipping history (i.e. how many crates we've taken out of each production line / queue). Initially, that history simply records that no crates have been taken from any queue.
prepare_load(self)
- Repeats the process outlined below three times, or until it is determined that all input queues are empty (whichever comes first). Then returns the list of crates removed from queues in that process, in the order removed.
input_queues
.reset(self)
- Reset the history of shipped crates to the state it was in when the object was initialized (i.e. forget about all the crates shipped thus far).
CyclingDepthRouter
and BalanceRouter
are the only "stateful" routers, meaning their behavior depends on their past actions. The other strategies do something that only depends on what's waiting in the queues.
Both FillPriorityRouter
and BalanceRouter
describe how to choose which queue the next crate should be taken from, and they say that process should be repeated three times to fill a truck. Keep in mind that after the first crate is selected and removed, certain things change (e.g. the number waiting in that queue, the number shipped, etc.) and this may influence which queue is chosen the second time, and so on.
I think the routing strategies are listed in the previous section in order of increasing difficulty.
fixtures.py
¶Now that you know the stragies you need to implement, you'll need to understand how to work with CrateQueue
objects to accomplish those strategies.
Note:
Router
subclasses you write must tolerate being given an instance of a subclass of CrateQueue
as a queue, or an instance of a subclass of Crate
as a crate. I recommend you don't do any explicit type checking of arguments.CrateQueue
¶Attributes you may access:
maxlen
- an integer representing the maximum number of crates that this queue can containMethods you may call:
peek(self)
- Take a look at the next Crate
without actually removing. Returns that Crate
object. If the queue is empty, raises IndexError
.pop(self)
- Remove and return the next Crate
from the queue. If the queue is empty, raises IndexError
.__len__(self)
- Return the number of Crate
objects currently in this queue. (Automatically called when you apply the built-in len
function to a CrateQueue
object).__bool__(self)
- Returns True
if the queue has at least one Crate
in it, and False
otherwise. (Thus if Q
is a CrateQueue
, the conditional if Q:
means "if Q is not empty:")is_nonempty(self)
- same as __bool__
.is_empty(self)
- Opposite of is_nonempty
. Returns True
if the queue is empty.remaining_space(self)
- Returns the number of Crate
objects that can be added before the queue becomes full (an integer).Crate
¶This class is completely opaque. It exists, and is the type of object you get from CrateQueue.peek()
and CrateQueue.pop()
, but you shouldn't call any methods of class Crate
, access any attributes of it, or attempt to instantiate any Crate
objects directly. The class Crate
might be changed arbitrarily in the version of fixtures.py
used to test your submission.
It is difficult to write a Router
subclass if you can't see it working.
The program simulation.py
included in the starter pack has the ability to instantiate a Router
or subclass thereof and to run a simple simulation. Try it out to see OldRouter
in action and then to test your subclasses of Router
during development.
Your project must follow the rules in the MCS 275 coding standards document. In addition:
Only submit routing.py
- The autograder will supply fixtures.py
when testing your project. The other files provided in the starter pack are for your testing, and are not used in grading.
No changes to existing classes - You are adding subclasses to routing.py
, and must not edit the classes already present there.
No output - Make sure the methods in routing.py
do not print anything and do not write to any files.
Only class definitions - The only things you add to routing.py
should be class definitions. Do not add any code outside of class definitions, whether active or commeted out (e.g. no test code, no commented-out false starts, etc.). In particular, importing routing.py
shouldn't result in anything happening in the terminal.
Make proper use of inheritance - when a superclass contains something useful to a subclass, use it directly (through inheritance, method calls, or attribute accesses).
No float
s - Do not use any values of type float
. The only numeric type you need in this project is int
(integer).
The autograder tests your program and grades it based on its behavior. A series of tests will be run including simulations under various circumstances. These tests will look for adherence to the specifications in this document.
I will review your code and look for adherence to the coding standards and other rules given above, and I will examine your method of solving the problem. If I see that your program does not do what was requested in this document, but the error was not detected in the automated testing, a deduction may be given at this point. The scores assigned by the autograder will not be changed during manual review unless I discover some kind of intentional wrongdoing (such as an attempt to circumvent or reverse-engineer the autograder's operation).
CyclingDepthRouter
description (2x)