<jupyter_start><jupyter_text>Table of Contents1&nbsp;&nbsp;Load Libraries2&nbsp;&nbsp;Prepare Data2.1&nbsp;&nbsp;Adjust Units of Measurements2.2&nbsp;&nbsp;Consolidate Information about Lines2.3&nbsp;&nbsp;Convert Runtime into Expected Drive Time and Dwell Time3&nbsp;&nbsp;Exploratory Data Analysis3.1&nbsp;&nbsp;Distance Between Stations (m)3.2&nbsp;&nbsp;Drive Time between Stations (s)3.3&nbsp;&nbsp;Dwell Times (s)4&nbsp;&nbsp;Modelling4.1&nbsp;&nbsp;Trains4.2&nbsp;&nbsp;Tracks5&nbsp;&nbsp;Utilities6&nbsp;&nbsp;System Verification7&nbsp;&nbsp;References  Load Libraries<jupyter_code>import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import scipy.stats as stats
import math
import numpy as np

import simpy<jupyter_output><empty_output><jupyter_text>Prepare Data<jupyter_code>dt=pd.read_csv('LondonTube.csv')
dt.head()<jupyter_output><empty_output><jupyter_text>Adjust Units of Measurements Convert distances to meters<jupyter_code>dt['Distance']=dt['Distance'].apply(lambda x: int(x*1000))<jupyter_output><empty_output><jupyter_text>Convert running times into seconds<jupyter_code>dt['Running Time']=dt['Running Time'].apply(lambda x: int(x*60))
dt['Peak Running Time']=dt['Peak Running Time'].apply(lambda x: int(x*60))
dt['Off-Peak Running Time']=dt['Off-Peak Running Time'].apply(lambda x: int(x*60))
dt.head()<jupyter_output><empty_output><jupyter_text>Consolidate Information about Lines Note: A short looping branch line of the Central Line has been deleted from the original data set.<jupyter_code>list(dt['Line'].unique())
list(dt.Direction.unique())<jupyter_output><empty_output><jupyter_text>Note: Each line has its specific rolling stock with drive parameters depending on the track geometry, electrical power supply, and the year of construction. For simplicity we assume fixed parameters across the network.<jupyter_code>lines=dt['Line'].unique()
dl=pd.DataFrame(lines, columns=['Line'])
dl=dl.set_index('Line')

dl['Direction 1']=None
dl['Direction 2']=None
dl['Stations']=None
dl['Total Dist']=None
dl['Min Dist']=None
dl['Avg Dist']=None
dl['Max Dist']=None

dl['max a']=0.7
dl['max v']=25

for l in lines:
    
    dirs = dt[dt['Line']==l]['Direction'].unique()
    dirs = sorted(list(dirs))
    dl.at[l, 'Direction 1']=dirs[0]
    dl.at[l, 'Direction 2']=dirs[1]
    
    dist = dt[dt['Line']==l]['Distance']
    dl.at[l, 'Stations']=int(dist.count()/2)
    dl.at[l, 'Total Dist']=int(dist.sum()/2)
    dl.at[l, 'Min Dist']=int(dist.min())
    dl.at[l, 'Avg Dist']=int(dist.mean())
    dl.at[l, 'Max Dist']=int(dist.max())

dl<jupyter_output><empty_output><jupyter_text>Convert Runtime into Expected Drive Time and Dwell Time Physics Calculation: time to travel a given distance based on acceleration and max velocity<jupyter_code>def timeTo(a, maxV, d):
    # a       constant acceleration, m/s²
    # maxV    maximumum velocity, m/s
    # d       distance, km
    # return  time in seconds required to travel
    # 
    ta = maxV/a         # time to accelerate to maxV
    da = a*ta**2        # distance traveled during acceleration from 0 to maxV and back to 0
    if (d < da):        # train never reaches full speed?
        return np.sqrt(4.0*d/a)     # time needed to accelerate to half-way point then decelerate to destination
    else:
        return 2*ta + (d-da)/maxV   # time to accelerate to maxV plus travel at maxV plus decelerate to destination
dt['Drive Time'] = None
for i in range(len(dt)):
    dist = dt.at[i, 'Distance']
    l = dt.at[i, 'Line']
    a = dl.at[l, 'max a']
    v = dl.at[l, 'max v']
    dt.at[i, 'Drive Time']=int(timeTo(a, v, dist))
dt['Dwell Time']=dt['Running Time']-dt['Drive Time']
dt['Peak Dwell Time']=dt['Peak Running Time']-dt['Drive Time']
dt['Off-Peak Dwell Time']=dt['Off-Peak Running Time']-dt['Drive Time']
dt=dt.drop(columns=['Running Time', 'Peak Running Time', 'Off-Peak Running Time'])
dt.head()<jupyter_output><empty_output><jupyter_text>Exploratory Data Analysis  Distance Between Stations (m)<jupyter_code>dt['Distance'].min()
dt['Distance'].max()
dt['Distance'].mean()
dt['Distance'].hist(bins=np.linspace(0, 12000, 49))<jupyter_output><empty_output><jupyter_text>Drive Time between Stations (s)<jupyter_code>dt['Drive Time'].min()
dt['Drive Time'].max()
dt['Drive Time'].hist(bins=np.linspace(0, 500, 51))<jupyter_output><empty_output><jupyter_text>Dwell Times (s)<jupyter_code>dt['Peak Dwell Time'].min()
dt['Peak Dwell Time'].max()
dt['Peak Dwell Time'].mean()
dt['Peak Dwell Time'].std()
dt['Peak Dwell Time'].hist(bins=np.linspace(0, 300, 61))
dt['Off-Peak Dwell Time'].min()
dt['Off-Peak Dwell Time'].max()
dt['Off-Peak Dwell Time'].hist(bins=np.linspace(0, 300, 31))<jupyter_output><empty_output><jupyter_text>Modelling Components of the system will be described by Python classes. The parameters are passed as data frames.  Trains<jupyter_code>class Train(object):  
    def __init__(self, i, data, line, direction):
        self.data = data
        self.name = f"{line:s}-{direction:s} [Train {i:2d}]"
    
    def process(self):

        for i in range(len(self.data)):
            
            here = self.data.at[i, 'From']
            dest = self.data.at[i, 'To']
                       
            dwelltime = self.data.at[i, 'Off-Peak Dwell Time']
            yield env.timeout(dwelltime)
            print(f"{now():s} {self.name:s} dep {here:s} for {dest:s}")
            
            drivetime = self.data.at[i, 'Drive Time']
            yield env.timeout(drivetime)
            print(f"{now():s} {self.name:s} arr {dest:s}")<jupyter_output><empty_output><jupyter_text>Tracks A track describes a direction of a line. The parameters describing a track are:- The start and stop times of the line (for example 6:00 to 23:00),- The time between trains (for example every 5 minutes), but this may vary during the day.The parameters for a line will later be taken from the line table. For the time being we use a simple procedure running the simulation of a single track.<jupyter_code>def track(env, line, direction, start, stop, timing):

    # create a slice of data for the line
    data = dt[dt['Line']==line][dt['Direction']==direction].reset_index(drop=True)
    
    yield env.timeout(start-env.now) # the line starts operating at 6am
    
    for i in range(int((stop-start)/timing)):  
        
        t = Train(i, data, line, direction)
        env.process(t.process())
        yield env.timeout(timing)<jupyter_output><empty_output><jupyter_text>Utilities Format time in seconds as hh:mm:ss<jupyter_code>def daytime(t):
    t=int(t)
    return f"{t//3600:02d}:{(t%3600)//60:02d}:{t%60:02d}"
def now():
    return daytime(env.now)<jupyter_output><empty_output><jupyter_text>System Verification As a first step we verify that the trains are running in parallel.<jupyter_code>env = simpy.Environment()
env.process(track(line='Central', direction='Eastbound', start=6*3600, stop=7*3600, timing=300))
env.run()<jupyter_output>06:00:33 Central-Eastbound [Train  0] dep WEST RUISLIP for RUISLIP GARDENS
06:02:30 Central-Eastbound [Train  0] arr RUISLIP GARDENS
06:02:50 Central-Eastbound [Train  0] dep RUISLIP GARDENS for SOUTH RUISLIP
06:04:00 Central-Eastbound [Train  0] arr SOUTH RUISLIP
06:04:24 Central-Eastbound [Train  0] dep SOUTH RUISLIP for NORTHOLT
06:05:33 Central-Eastbound [Train  1] dep WEST RUISLIP for RUISLIP GARDENS
06:06:30 Central-Eastbound [Train  0] arr NORTHOLT
06:06:44 Central-Eastbound [Train  0] dep NORTHOLT for GREENFORD
06:07:30 Central-Eastbound [Train  1] arr RUISLIP GARDENS
06:07:50 Central-Eastbound [Train  1] dep RUISLIP GARDENS for SOUTH RUISLIP
06:08:30 Central-Eastbound [Train  0] arr GREENFORD
06:09:00 Central-Eastbound [Train  1] arr SOUTH RUISLIP
06:09:02 Central-Eastbound [Train  0] dep GREENFORD for PERIVALE
06:09:24 Central-Eastbound [Train  1] dep SOUTH RUISLIP for NORTHOLT
06:10:33 Central-Eastbound [Train  2] dep WEST RUISLIP for RUISLIP GARDENS
06:10:45 Central-Eastbou[...]
{'fraction_non_alphanumeric': 0.13894472361809046, 'fraction_numerical': 0.028768844221105528, 'mean_word_length': 4.82796486090776, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 56, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '356168', 'license': 'MIT', 'n_tokens_mistral': 2973, 'n_tokens_neox': 2770, 'n_words': 732, 'path': '9,10_SingleServerApplication/London Underground Step 1.ipynb', 'repo_name': 'sobil-dalal/Model-Simulation-Optimization', 'size': '7960'}