This assignment is inspired by the Millennium Bridge in London, England. The Millennium Bridge is a suspension bridge for pedestrians only. Many visitors walked across the bridge in the first day of its opening in 2000. However, these visitors were treated to something unusual. They noticed that, when there were many people on the bridge, the bridge started to sway a lot. You can watch the wobbling of the bridge in this YouTube video. Since the bridge did not sway much if there were few people on the bridge, the authority decided to limit the number of people on the bridge. Eventually, the bridge was closed a few days after its opening. The bridge was re-opened after additional dampers have been put on the bridge, see  for a report on how the bridge was stabilised.
Engineers and scientists wanted to understand why the Millennium Bridge started to sway when there were many people on the bridge. You probably have learnt about resonance in your Physics class and you are right to guess that the wobbling had something to do with resonance. However, the baffling part is how pedestrians could have caused resonance to occur. One theory, which was published in , is that the pedestrians would synchronise their walking with that the bridge's motion and their synchronised movement caused the bridge to sway more. There are also other theories, such as , which disputes the theory proposed in .
The aim of this assignment is to give you an opportunity to work on a small-scale engineering problem in Python. The engineering system that you will be working on is the Millennium Bridge motion model from . (Note that we are not claiming that  is the correct model. We are merely using the model in  in a computing exercise.) You will first develop a Python function to simulate the motion of the bridge due to the pedestrians' movement. After that, you will use the simulation program to try out different modifications that can reduce the amount of swaying. On the whole, this assignment relates computer science to two important aspects of engineering, which are simulation and design.
The key objective of this assignment is to for you to learn and use Python and numpy to solve problems. At the same time, we would like to give the problem an engineering context so that you can get some ideas how computing is used in engineering. Note that we used the word inspired earlier because, for this assignment, we took the liberty to simplify the model in  and the engineering design problem.
This assignment is designed to give you practice in
Intuition behind the bridge and pedestrian motion modelling
We will give you a basic mental picture that you can use to visualise the bridge and pedestrian motions. We will use Figure 1 below, which is taken from , for our description.
Overview of tasks
We have divided the assignment into four tasks where each task corresponds to the writing of a Python function.
Task 1: Simulation
The aim of this task is to write a Python function sim_bridge() to simulate the bridge and pedestrian motion. In the yellow box below, you will see the contents of the template for sim_bridge() which you can use to start your work.
import numpy as npdef sim_bridge(t_array,M,B,K,G,N,C,Omega_array,dis0,vel0,ped0): # BEGIN: Supplied code ************************************ # Time increment dt = t_array - t_array # Initialise dis_array, vel_array, ped_array dis_array = np.zeros_like(t_array) vel_array = np.zeros_like(t_array) ped_array = np.zeros((N,len(t_array))) # Initialise for index 0 dis_array = dis0 vel_array = vel0 ped_array[:,0] = ped0 # Compute Omega0 Omega_0 = np.sqrt(K/M) # END: Supplied code ************************************ # Your code to compute the entries in dis_array, vel_array, ped_array # # Hint: Should use arctan2() from the numpy library to calculate Psi(t) # BEGIN: Supplied code ************************************ # Return the array return dis_array, vel_array, ped_array # END: Supplied code ************************************
The function sim_bridge() takes on a number of inputs. We will provide you with the parameter values to use so the important thing for you is to understand what they are referring to. The meaning of M, B, K, N, G and C have already been explained earlier at here. The meaning of the other inputs are explained in the table below.
Python variable name Meaning t_array A numpy array of regularly spaced points. They are the time instances in simulation. Omega_array This is a numpy array with N entries. The entry Omega_array[i] is the value of Ωi in the mathematical model.The quantity Ωi is related to the walking speed of the the pedestrian with index i.We will explain how you can use Omega_array on the page where we describe the mathematical model. It is here and it is best that you read that later. dis0 Initial displacement of the bridge. This is a scalar. vel0 Initial velocity of the bridge. This is a scalar. ped0 This is a numpy array with N entries. The entry ped0[i] is the initial phase of the pedestrian with index i.
The function sim_bridge() returns three outputs, see the second last line of the code above. All the three outputs are numpy arrays and their meanings are explained in the table below.
Python variable name Shape Purpose dis_array The same as t_array To store the displacement of the bridgedis_array[j] is the displacement at the time given by t_array[j] vel_array The same as t_array To store the velocity of the bridgevel_array[j] is the displacement at the time given by t_array[j] ped_array A two-dimensional numpy arrayThe shape is N by the number of entries in t_array To store the phase of the pedestriansped_array[i,j] is the phase of pedestrian i at the time given by t_array[j]
Note that the template file has included code to create these three arrays with the specific shape mentioned above. Please do not change these lines of code. In addition, the template also includes lines of code which initialise these arrays for time 0. Again, please do not change these lines.
In order for you to complete sim_bridge(), what you need to do is to add the for-loop for simulation.
In the simulation, you will need to use arctangent (or inverse tangent) to compute the phase Ψ(t) of the bridge. We ask you to use the numpy function arctan2() for this purpose. More explanation is on this page.
(Testing and incremental development) You can test this function by using the test file test_sim_bridge.py. There are four test cases in this test file. We have developed these test cases so that you can develop your sim_bridge() incrementally. We will be explaining these test cases on a separate page because it requires some understanding of the mathematical model. Our suggestion is that you keep going with this document first to get an understanding of the whole assignment. After that, you can read the mathematical model and when you are ready to think about how to implement sim_bridge(), you can read how you can incrementally develop it on this page, which is also where the mathematical model is.
Task 2: A function to calculate the design objective
The function sim_bridge() allows you to compute the displacement and velocity from the bridge parameters. In the next task (Task 3), you will explore different designs by varying the values of stiffness and damping. In order for us to choose a design later on, we need a way to measure how good a design is. This measure is known as the design objective. The aim of this task (Task 2) is to develop the function comp_obj() whose aim is to compute the design objective.
The def line of comp_obj() is:
The names of the inputs have been chosen to reflect their roles. The function is expected to return one number as the design objective. We will use an example to explain how you compute the design objective.
(Example) Note that both dis_array and vel_array are expected to be 1-dimensional numpy arrays of the same shape. For this example, we assume:
We compute the following three numbers
square root of ( (d02 + (M / K) v02 ), square root of ( d12 + (M / K) v12 ), square root of ( d22 + (M / K) v22 )
where d02 denotes the square of d0 etc. The design objective is the maximum of these three numbers. In general, if there are H entries in each of dis_array and vel_array, you will be computing H numbers and finding the maximum of them.
As a numerical example, if
then you first compute
square root of ( (-1.1)2 + (M / K) (-4.1)2 ), square root of ( (2.1)2 + (M / K) (-2.1)2 ) , square root of ( (3.1)2 + (M / K) (1.3)2 )
Their numerical values are approximately 8.09, 4.61 and 4.01. The maximum is 8.09 which is the design objective. Your comp_obj() will need to return this number.
You can test this function by using test_comp_obj.py.
Note that it is possible to do all of the computation of this function with merely one line of code in numpy. The lectures in Week 8 will give you some inspiration.
Task 3: Calculating the design objective for many pairs of B and K
Assuming that you are able to modify both the damping B and stiffness K of the bridge, you want to compute the design objective for many pairs of B and K values. The aim of this task is to develop the function run_different_designs() whose template is:
def run_different_designs( t_array, M, G, N, C, Omega_array, dis0, vel0, ped0, B_min, B_max, K_min, K_max, B_num, K_num): return B_array, K_array, sway_array
There are many inputs to this function. The first group of inputs, which is typeset in blue, has been discussed before. The second group of inputs, which is typeset in maroon, is new.
This function returns three numpy arrays, see the return line in the template code above.
In this task, you will use many different pairs of B and K values for simulation. For each pair of B and K, you will use sim_bridge() to obtain the displacement and velocity. (Note that you assume that the other parameters, M, G, N, ... remain the same.) Once the displacement and velocity have been obtained, you use them together with M and K to compute the design objective using comp_obj().
Note that the template file for run_different_designs() does not include default arguments. The last two parameters B_num and K_num can have default arguments. The default argument for B_num is 5, and that of K_num is 10. This is a piece of work that you need to complete for this task.
The steps inside the function run_different_designs() are:
You can use the file test_run_different_designs_0.py, test_run_different_designs_1.py and test_run_different_designs_2.py to test your function. Note that for both test_run_different_designs_0.py, test_run_different_designs_1.py, the value of B_num by K_num are specified. For test_run_different_designs_2.py, both B_num by K_num are not specified so the default arguments are expected to be used.
Note that for all these three test files, a number of simulations will be run so the computation may take a bit of time. The files test_run_different_designs_0, test_run_different_designs_1 and test_run_different_designs_2 run, respectively, 12, 24 and 50 different designs. If you need computing power, you can use the lab computer.
Hint: You will need to use nested-for for this function. An example of using nested-for can be found in the file nested_for.py in Week 6B.
Remark: We want to remark that the design problem that we have posed above is not entirely realistic. It is hard to modify the stiffness of a bridge, see . If stiffness and damping are modified, the other bridge parameters, such as the mass M, may need to be adjusted at the same time. We have taken the liberty to keep the problem simple.
Task 4: Choose the best design
The aim of this task is to choose the best design from all those designs that you have simulated in Task 3. In this assignment, we define the best design as the pair of B and K that minimises the design objective. In this task you will develop the function find_best_design() whose template is:
def find_best_design(sway_array,B_array,K_array): return B_chosen, K_chosen
The inputs sway_array, B_array and K_array are the outputs of run_different_designs(). The function is expected to return two outputs which are the chosen pair of B and K.
Let us use an example to illustrate the expected behaviour of find_best_design(). For this example, we assume that sway_array, B_array and K_array are given by:
sway_array = np.array( [ [3.1, 2.1, 1.1], [4.1, 1.6, 2.4], [2.2, 3.2, 3.6], [1.5, 2.5, 3.5] ] )B_array = np.array([3.7, 4.7, 5.7, 6.7])K_array = np.array([1.5, 1.8, 2.1])
Note that B_array has 4 entries, K_array has 3 entries, and from run_different_designs() we expect that sway_array should have a shape of (4,3). Recall also that sway_array[i,j] is computed from B_array[i] and K_array[j], e.g. sway_array[2,1] , which has the value of 3.2 (highlighted in blue), is the design objective computed by using B_array and K_array.
Since our goal is to find the design that minimises the design objective, we look for the smallest element in sway_array, which is sway_array[0,2] and has a value of 1.1 (highlighted in magenta). The best design therefore comes from a value of B equals to B_array and a value of K equals to K_array. You should assign B_array to B_chosen and K_array to K_chosen.
You can test find_best_design() by using the test file test_find_best_design().
Note that you can use numpy to write this function without using any loops.
Remark: We have used a rather brute force method to determine the design parameters. You will learn better optimization methods in later years.
Putting all functions into action
We have provided you with a Python program called run_all.py. In this file, you will use run_different_designs() to simulate a number of different designs and use find_best_design() to choose the best design. After that, the file will use sim_bridge() to simulate the bridge, first with the original parameters and then with new parameters obtained from the design. If your programs work correctly, you should be able to see that the new design has stabilised the bridge.
This is not part of the assessment but we think it is good for you to see how you can put everything together.
The zip file assign2_prelim.zip contains altogether 23 files. The assumption is that all these 23 files must be in same directory. Here is an overview of what the files are:
Test your functions thoroughly before submission.
You can use the provided Python programs (files like test_sim_bridge.py etc.) to test your functions. Please note that each file covers a limited number of test cases. We have purposely not included all the cases because we want you to think about how you should be testing your code. You are welcome to use the forum to discuss additional tests that you should use to test your code.
The test files will calculate the difference between the expected results and your results. The test file will inform you the maximum absolute difference. The difference should be less than 1e-6.
We will test each of your files independently. Let us give you an example. Let us assume we are testing three files: prog_a.py, prog_b.py and prog_c.py. These files contain one function each and they are: prog_a(), prog_b() and prog_c(). Let us say prog_b() calls prog_a(); and prog_c() calls both prog_b() and prog_a(). We will test your files as follows:
You need to submit the following four files. Do not submit any other files. For example, you do not need to submit your modified test files.
To submit this assignment, go to the Assignment 2 page and click the tab named "Make Submission".
Assessment Criteria We will test your program thoroughly and objectively. This assignment will be marked out of 24 where 20 marks are for correctness and 4 marks are for style. Correctness
The 20 marks for correctness are awarded according to these criteria.
Criteria Nominal marks Function sim_bridge() (Case 0: G = 0, C = 0, non-zero initial conditions. Need to get dis_array and vel_array correct) 2 Function sim_bridge() (Case 1: nonzero G. C = 0. Need to get dis_array, vel_array and ped_array correct) 3 Function sim_bridge() (Case 2: nonzero G and C. Need to get dis_array, vel_array and ped_array correct) 4 Function comp_obj() 2 Function run_different_designs() (Case 0: All arguments are specified. No default arguments will be used.) 4 Function run_different_designs() (Case 1: Testing default arguments.) 1 Function find_best_design() 3
(Assessment and incremental development) We told you earlier that the function sim_bridge() can be developed incrementally, which are Step 0, Step 1 and Step 2 mentioned here. Each step corresponds to a more complicated simulation model with Step 0 being the simplest and Step 2 being the complete model. We want to let you know that there is a one-to-one correspondence between the incremental development of sim_bridge() and the assessment of sim_bridge(). The assessment of sim_bridge() is divided into 3 cases, see the table above. Case 0 in the assessment corresponds to Step 0 of the incremental development, and so on. You need to know.
The important message is this: Just in case you find doing the simulation model difficult, you can get a partially completed simulation model going first and then move on to work on the other functions first. Do not think that you need to complete the entire simulation model before working on the other functions.
Four (4) marks are awarded by your tutor for style and complexity of your solution. The style assessment includes the following, in no particular order:
You are reminded that work submitted for assessment must be your own. It's OK to discuss approaches to solutions with other students, and to get help from tutors, but you must write the Python code yourself. Sophisticated software is used to identify submissions that are unreasonably similar, and marks will be reduced or removed in such cases.