A large number of car drivers regularly want to travel from the Hawthorns bus stop (the corner of St Michael’s Park and Woodland Road – call it 's') to the "Scotsman and his Pack" pub (the corner of St Michael’s Hill and Horfield Road - call it 't') - as shown in the map on the view.html page. Complete the following 3 tasks by implementing a number of algorithms in JavaScript function found in the controller.html file. This should be fairly straightforward, even if you have little experience of JavaScript (see the help sections at the end of this document for some tips).
Note, this exercise is fictional. In real life there are other possible routes from s to t, the road capacities are probably different, there are drivers on these roads with multiple different destinations most of time. Oh, and the app mentioned below doesn't actually exist!
Bristol City Council has introduced a new congestion-management app. The app works out the routes between a driver's starting point and desired end point that have spare capacity (i.e. don't pass thorough a road that currently has a flow in either direction equal to the road's capacity). The app will recommend the route with the greatest static capacity. The algorithm does not incorporate current traffic flow along individual roads into its calculations (except that when a route is full to capacity, it is no longer recommended). Remember that a route is only as good as the most restricted edge along it's entire path. The maximum capacity of a route is the minimum value of the capacities, not the residual capacities of the roads on the route.
Complete the calcMaxFlow
function in the controller.html file to simulate what
will happen when the Bristol City Council app is used. The test graph will be
automatically loaded from the file - you won't need to do anything to populate
the system with data. Backwards edges will be created - however you should not
make use of these in this particular task (since the city council don't consider
them. You can assume that routes recommended by the app do not use any roads
other than the ones listed. What is the greatest value of flow that is reached?
A group of local residents have decided to protest about traffic congestion in this area, which seems to have gotten worse despite the introduction of the congestion-management app. They are planning to hold a sit-down protest in Tyndall Avenue, at the time of day when the large number of drivers travel from s to t. The sit-down will effectively reduce the Avenue's capacity to zero during that time. Simulate the effect of the protest on the traffic flow by editing the model.js file to remove the edge that runs along Tyndall Avenue (the edge between nodes "a" and "c"). Then re-run your simulation code. Is the protest likely to be successful in convincing the Council that the problem needs addressing?
Implement a full max flow calculation for the above map using the Ford-Fulkerson algorithm. You don't need to insert backwards edges - these are all created automatically for you. You will however need to update them as the flow changes!
You may select the most appropriate search pattern for the task (i.e. breadth first, or depth first) It is possible to create equivalent recursive and simple-loop implementations. You may choose which you implement.
Creating "objects" in JavaScript is a bit like a C structs:
var element = { graphNode: source, edgeFromParent: null, parentElement: null };
Arrays can be easily created as in the following example. The values need not all be of the same type.
var empty = [];
var occupied = [0,'1',"2","three",{}];
Arrays are indexed as usual:
var path = ['a->b','c->d','a->d'];
var edge = path[2]; // "a->d"
Finding the length of an array should also be familiar (note the use of var instead of int):
for (var i=0; i<path.length; i++) { ... }
Arrays can be used as queues or stacks by calling the following methods on them:
.push(e1,e2,...)
: adds new elements to the back of the queue/top of the stack.pop()
: pops the top element off the stack.shift()
: pulls the next element off the head of the queue
You may access the source and sink nodes using graph.source
and graph.sink
.
All nodes have an array called "edges" which contains all edges eminating from
that node. Each edge is a member of the class Edge, with backwards edges members
of the subclass BackEdge. To tell you whether or not an edge is a backwards
edge, either call .isBackEdge
, or test for (edge instanceof BackEdge)
. Each
node has a boolean property called "discovered" which you can use to keep track
of whether an node has previously been visited in a path search.
Edges also have the following properties and methods:
.from
: returns the node from which this edge eminates.to
: returns the node to which this node travels.residualCapacity
: returns the amount of flow capacity remaining (total capacity minus current flow value).augment(value)
: reduces the available spare capacity by a certain amount (increasing the flow value). To decrease, provide a negative value.
One other extra function that you might find useful:
graph.undiscover()
: Resets all of the "discovered" flags on all nodes
See the sample calcMaxFlow function in controller.js file that comes with the assignment to see how some of these features can be used.
Open up both view.html and controller.html in Chrome in different windows side by side, or index.html to view them in a single window. The controller contains four buttons to control the execution of your code:
- Step: Steps through an edge at a time
- Run/Pause: Runs your code automatically. Click again to pause (use slider to adjust playback speed)
- Reset: Resets all of the flow to zero, and stops the execution
- Reset to FF/CC: Resets, then changes the global boolean variable useFF. This is useful for Task 3, where you implement the Ford-Fulkerson algorithm. In the example code, this variable is not used.
It is probably safer to use the "stepped" run mode when you first start to build and test your code (it is easier to stop when things go wrong!). As you get further on, you may wish to change to the "run" mode using an appropriate speed using the slider.
Transversing an edge (triggered with edge.read()
) highlights that edge in
black. Augmenting an edge's flow (by calling edge.augment()
) highlights that
edge in green/red for increasing/decreasing. Marking a node as discovered will
colour it blue, unless otherwise coloured by an edge.
You can toggle between "normal" graph and "residual" graph by pressing the 'r' key with the view selected. You can also hide or reveal the back edges on the graph by pressing the 'b' key with the view selected.