Снижает ли скорость снижение скорости?
Не так давно развернулись дискуссии на тему введения денежного штрафа за превышение скорости на более чем 10 км/ч от разрешенной. Традиционно для Интернета они ведутся неконструктивно, поэтому я в целом не поддерживаю ни одну сторону подобных холиваров.
Аргументы автовладельцев в массе сводятся к огрызаниям «мне надо», которые, разумеется, не тождественны. На значительную долю людей, вынужденно ездящих на работу по 50 км ежедневно через локации, не охваченные общественным транспортом, приходится не меньшая доля ездящих на машине «в булочную», что хорошо видно по этим самым машинам, оставленным утром у дома в первый же мало-мальский снег.
Со стороны урбанистов часто слышны довольно однобокая аргументация, заезженные частные примеры европейских стран, население которых иногда целиком сопоставимо с суточным московским автотрафиком, приемы вроде оскорбительных штампов про «быдлоповозки».
А когда наступает такая ситуация, нет ничего лучше, чем отбросить чужие эмоции и призвать двух беспристрастных помощников — матана и Питона.
Слабость позиции автовладельцев — они не выдвигают путей решения своих проблем (на мой взгляд, путем решения была бы отмена стихийной застройки каждого свободного клочка земли в крупных городах и развитие регионов, чтобы людям не приходилось заниматься в родной стране трудовой миграцией в столицу, но кто я такой, чтобы предлагать такие вещи?). У урбанистов всегда есть набор аргументов и готовых решений, подкрепленных данными. Но иногда это исследования серьезных институтов, иногда — не очень внятная статистика без адекватного нормирования. Этими данными в большинстве случаев напирают на идею об общем снижении скорости движения в городах. И самый частый аргумент в ее пользу звучит так:
«все равно из-за светофоров нельзя все время двигаться с максимальной скоростью, ваша средняя скорость будет ниже разрешенной, так почему бы и не снизить немного разрешенную?»
«Хммм», думал я всегда над этим аргументом. Если снизить максимальную разрешенную, средняя тоже немножко упадет. И что это доказывает — что можно ее снова снизить? Что же это за апория такая? Нужно проверить, насколько это правда.
Дальнейшую статью я хотел бы представить вашему вниманию с оговорками, чтобы направить ее критику в комментариях в конструктивное русло:
— Да, я знаю, как некрасиво написан код, но мне важнее, чтобы он работал правильно, сам я не программист, замечания по коду лучше пишите в личку.
— Да, я знаю, что симуляцию можно было бы сделать сложнее, полнее и реалистичнее. Только я приму не упреки, что в ней отсутствует изменение плотности трафика в течение дня, различная динамика различных машин, погодные условия, фазы Луны, мать автора, а дельные замечания, указания на алгоритмические недочеты, модификацию модели для каких-либо не рассмотренных ситуаций. В моем понимании она достаточна, чтобы ответить на простой с точки зрения математики вопрос: учитывая сильную дискретность движения из-за светофоров, значительно ли влияет на время прохождения пути снижение максимальной разрешенной скорости на отдельных участках?
Суть моей модели очень проста. Поскольку светофор — это готовый программный цикл, симуляция построена вокруг отрезка дороги со светофором в конце и его фаз. Следующая итерация — следующий отрезок маршрута со своей длиной, разрешенной скоростью, фазой светофора в конце.
Автомобиль имеет три фазы движения: равноускоренное, движение с максимальной разрешенной для данного участка скоростью, равнозамедленное. Они реализуются в 9 вариантах.
- Если автомобиль двигался до нынешнего отрезка с такой же скоростью, которая разрешена на этом, то он двигается без изменений;
- Если он двигался медленнее (или стоял) — то он сначала разгоняется;
- Варианта «он двигался быстрее» нет, и вот почему: если на следующем отрезке разрешенная скорость меньше, он снижает скорость на этом. Логично?
К этим вариантам добавляются еще условия:
- Автомобиль едет как ехал или замедлялся, и приезжает к концу отрезка на красный. Это значит, что надо остановиться перед светофором. Полагая, что он тормозит всегда с одинаковым ускорением, откладываем от светофора назад путь, необходимый для торможения. Это — расстояние Sкритическое, по аналогии с критической скоростью взлета самолетов. Теперь, если по условиям авто проезжает на красный свет, он должен начать тормозить до нуля, начиная с точки Sкритическое;
- Черт, а может, отрезок такой короткий или авто такой овощной, что он не успеет набрать максимальную скорость до наступления Sкрит? А программа баганет, продолжая его разгонять, хотя ему пора тормозить перед светофором? Если так случится, заставим его не набирать скорость выше достигнутой в точке Sкрит, а далее реализуем один из вариантов. Вот что получается в итоге:
А дальше нужно реализовать это в коде. Вот так я это сделал, снабдив подробными комментариями:
import random
import math
#период красного света:
redlight = ("100",
"10",
"90",
"1", #это своего рода "стоп-кодон" . Машина должна обязательно остановиться в пункте назначения. Этому служит 1 секунда красного света
"1") # и 1 секунда зеленого, превращающаяся в 0. А еще коду нужен последний отрезок и следующий за ним. Поэтому две последние цифры кортежей условны.
#период зеленого света:
greenlight = ("30",
"120",
"30",
"1",
"1")
#расстояние до следующего светофора:
distance = ("400",
"400",
"250",
"500",
"500")
#разрешенная скорость на участке
velocity = ("60",
"60",
"60",
"60",
"40")
#переменные-счетчики для кортежей
r=0
g=0
d=0
v=0
#переменная текущей скорости:
vcurrent=float(0)
#переменная текущего времени проезда отрезка:
t=0
#переменная суммарного времени всего пути:
gtime=0
#примем, что наша машина разгоняется до 100 км/ч за 15 секунд.
#тогда ускорение разгона до разрешенной скорости будет:
accel=float(100/(3.6*15))
#примем, что наша машина тормозит вдвое резче.
#тогда ускорение торможения до разрешенной скорости будет:
decel = float(accel*2)
#пока мы перебираем все элементы кортежей, кроме последнего (и помним, что начинается с 0):
while r<=2:
red=float(redlight[r])
grn=float(greenlight[g])
dis=float(distance[d])
vel=float(float(velocity[v])/3.6)
vnext=float(float(velocity[v+1])/3.6)
#создаем переменную для расчета пути разгона машины с ускорением accel до разрешенной на участке скорости:
#saccel = float(((vcurrent*vel-vcurrent)/accel) + ((vel-vcurrent)*((vel-vcurrent)/(2*accel)
saccel = float((vcurrent*(vel-vcurrent)/accel) + (vel-vcurrent)*(vel-vcurrent)/(2*accel))
#смотрим вдаль: эта переменная определяет точку пути, с которой мы сможем затормозить точно к светофору с ускорением decel:
scrit = float(dis-(vel/decel) - (vel*vel)/(2*decel))
#рандомизируем время, в которое загорается первый красный свет цикла светофора.
#это даст нам некоторое смещение по фазе у каждого светофора:
startingred = random.randint(0, (int(grn)-1))
print ("startingred= ", startingred)
#в самом главном условии сравниваем, если текущая скорость _равна_ разрешенной - то у нас отсутствует фаза разгона:
if vcurrent == vel:
#если на следующем отрезке разрешенная скорость выше или равна текущей, едем как ехали, набирать будем на том участке:
if vnext>= vcurrent:
t = int (dis/vel)
#если красный, тормозим до нуля:
if (t+startingred)%(red+grn)<=red:
t = int (scrit/vel + (vel/decel) + red-((t+startingred)%(red+grn))) ### 2
vcurrent = 0
print ("Скорость равнялась разрешенной и будет такой же или выше, КРАСНЫЙ")
#если зеленый, то проезжаем:
else:
t = int (dis/vel)### 1
vcurrent = vel
print ("Скорость равнялась разрешенной и будет такой же или выше, ЗЕЛЕНЫЙ", " v=", vcurrent)
#если на следующем отрезке разрешенная скорость ниже текущей, снижаем скорость, начиная с scrit:
else:
t = int ((scrit/vel) +
(vcurrent - (vnext)/((vcurrent*(vcurrent - (vnext/(dis-scrit))-
((vcurrent - vnext)*(vcurrent - vnext)/(2*(dis-scrit))))))))
#но если красный, то останавливаемся:
if (t+startingred)%(red+grn)<=red:
t = int (scrit/vel + (vel/decel)+ red-((t+startingred)%(red+grn)))### 2
vcurrent = 0
print ("Скорость была разрешенной и будет ниже текущей, КРАСНЫЙ")
#а если зеленый - замедляемся до скорости, разрешенной на следующем участке, весь путь от scrit до светофора:
else:
t = int (scrit/vel +
(vcurrent - vnext)/((vcurrent*(vcurrent - (vnext/(dis-scrit))-
((vcurrent - vnext)*(vcurrent - vnext)/(2*(dis-scrit)))))))### 3
vcurrent = float(vnext/3.6)
print ("Скорость была разрешенной и будет ниже текущей, ЗЕЛЕНЫЙ", " v=", vcurrent)
#в самом главном условии сравниваем, если текущая скорость _меньше_ разрешенной - у нас появляется фаза разгона до новой разрешенной:
elif vcurrent < vel:
#вводим переменную скорости, достигнутой в точке scrit:
vcrit=math.sqrt(2*accel*scrit+vcurrent*vcurrent)
#если путь разгона до разрешенной скорости превышает расстояние scrit, то разгоняемся до проезда пункта scrit, запоминаем скорость vcrit и проверяем, надо ли тормозить:
if saccel >= scrit:
#если на следующем участке ограничение скорости такое же или выше - набирать ее будем на том участке, не тормозим
if vnext >= vcrit:
t = int(((vcrit-vcurrent)/ accel) + (dis-scrit)/vcrit)
#если красный, то останавливаемся:
if (t+startingred)%(red+grn)<=red:
t = int(((vcrit-vcurrent)/ accel) + ((dis-scrit)*2/vcrit) + red-((t+startingred)%(red+grn)))### 8
vcurrent = 0
print ("Скорость была меньше разрешенной и будет такой же или выше, неполный разгон, КРАСНЫЙ")
#если зеленый, то едем с достигнутой скоростью:
else:
t = int(((vcrit-vcurrent)/ accel) + (dis-scrit)/vcrit) ### 7
vcurrent = vcrit
print ("Скорость была меньше разрешенной и будет такой же или выше, неполный разгон, ЗЕЛЕНЫЙ", " v=", vcurrent)
#если на следующем участке ограничение скорости ниже - тормозим сразу после разгона
else:
t = int(((vcrit-vcurrent)/ accel) + (vcrit - vnext)/((vcrit*(vcrit - vnext)/(dis-scrit))-
((vcrit - vnext)*(vcrit - vnext)/(2*(dis-scrit)))))
#если красный, то останавливаемся:
if (t+startingred)%(red+grn)<=red:
t = int(((vcrit-vcurrent)/ accel) + ((dis-scrit)*2/vcrit) + red-((t+startingred)%(red+grn)) ) ### 8
vcurrent = 0
print ("Скорость была меньше разрешенной и будет ниже текущей, неполный разгон, КРАСНЫЙ")
#если зеленый - замедляемся до скорости, разрешенной на следующем участке, весь путь от scrit до светофора:
else:
t = int(((vcrit-vcurrent)/ accel) + (vcrit - vnext)/((vcrit*(vcrit - vnext)/(dis-scrit))-
((vcrit - vnext)*(vcrit - vnext)/(2*(dis-scrit))))) ### 9
vcurrent = vnext
print ("Скорость была меньше разрешенной и будет ниже текущей, неполный разгон, ЗЕЛЕНЫЙ", " v=", vcurrent)
#если путь разгона до разрешенной скорости не превышает scrit, разгоняемся до разрешенной, дальше едем с ней:
else:
#если на следующем отрезке разрешенная скорость выше или равна текущей, едем как ехали, набирать будем на том участке:
if vnext>= vel:
t = int(((vel- vcurrent)/accel) + (dis-saccel)/vel)
#если красный, то останавливаемся:
if (t+startingred)%(red+grn)<=red:
t = int (((vel- vcurrent)/accel) + (scrit-saccel)/vel + (vel/decel)+ red-((t+startingred)%(red+grn)))### 5
vcurrent = 0
print ("Скорость была меньше разрешенной и будет такой же или выше, КРАСНЫЙ")
#если зеленый, то проезжаем:
else:
t = int (((vel- vcurrent)/accel) + (dis-saccel)/vel)### 4
vcurrent = vel
print ("Скорость была меньше разрешенной и будет такой же или выше, ЗЕЛЕНЫЙ", " v=", vcurrent)
else:
#если красный, то останавливаемся:
if (t+startingred)%(red+grn)<=red:
t = int (((vel- vcurrent)/accel) + (scrit-saccel)/vel + (vel/decel)+ red-((t+startingred)%(red+grn)))### 5
vcurrent = 0
print ("Скорость была меньше разрешенной и будет ниже текущей, КРАСНЫЙ")
#если зеленый - замедляемся до скорости, разрешенной на следующем участке, весь путь от scrit до светофора:
else:
print ("scrit ", scrit)
print ("vcurrent ", vcurrent)
t = int (((vel- vcurrent)/accel) +(scrit-saccel)/vel + (vel - vnext)/((vel*(vel - vnext)/(dis-scrit))-((vel - vnext)*(vel - vnext)/(2*(dis-scrit))))) ### 6
vcurrent = vnext
print ("Скорость была меньше разрешенной и будет ниже текущей, ЗЕЛЕНЫЙ", " v=", vcurrent)
#в самом главном условии сравниваем, если текущая скорость _выше_ разрешенной - так быть не может, мы же тормозим на предыдущем участке:
else:
print ("ERROR: v current > v next")
print (t)
r+=1
g+=1
d+=1
v+=1
gtime+=t
print (gtime)
Здесь нужно несколько уточнений.
Я взял условную динамику разгона автомобиля от 0 до 100 за 15 секунд, и задал торможение вдвое резче. Мне это показалось реалистичным, кто не согласен — код в ваших руках, экспериментируйте с гружеными фурами и ламборджини мурсилеаго.
Да, я пренебрег тем, что пока автомобиль тормозит, красный свет может смениться зеленым. Маловероятно, но такое может быть — равнозамедленное торможение до нуля занимает вдвое больше времени, чем проезд того же расстояния с неизменной скоростью. Значит, речь идет о 3–5 секундах. Ради них я не стал городить еще формул, успокоив себя тем, что это — симуляция машины, стоящей перед нами на светофоре. Кстааааати…
Да, я пренебрег трафиком вообще. Объясняю, почему. Чисто математически трафик будет выглядеть как смещение точки светофора на несколько корпусов машин и соответственно смещенную по фазе линию скорости. Просто оттащите мышкой картинку выше на сантиметр влево — наглядная демонстрация.
Но дело в том, что реальность совсем не такова. Машины не будут стартовать одновременно, как вагоны метро, хотя теоретически могут сделать это, не столкнувшись. Они стартуют последовательно, и повлияет это на наше время прохождения маршрута следующим образом: мы можем не успеть проехать светофор, если их довольно много. В противном случае это — всего лишь смещение по фазе и снижение средней скорости на участке (как будто по всему городу действуют более строгие правила). А значит, симулируется ситуация простым добавлением еще одного цикла светофора, вроде:
t = int (scrit/vel + (vel/decel) + red-((t+startingred)%(red+grn)) + (red+grn))
Дает ли это что-то нам? Вряд ли, поскольку это просто слагаемое, просто еще одно свойство светофора, если хотите. Вся игра со временем прохождения маршрута происходит во время движения на отрезках. И тут плотный, мешающий друг другу трафик можно симулировать, зарезав ускорение или/и разрешенную скорость на участке.
Но нас ведь интересует, как лимитируют ситуацию ограничения скорости, а не соседние машины? Вот мы и будем разбирать такой вариант.
Я не стал ничего выдумывать, а взял реальный городской маршрут. Скажем так, знакомый мне ранее. Я не помню точно все фазы светофоров, но я взял его за основу симуляции, потому как на нем есть и длинные, и короткие, и ограниченные по скорости участки.
А еще я модифицировал код. Стер все каменты и принты, добавил более глобальный цикл, чтобы прогнать его, например, 10000 раз:
import random
import math
n=0
overalltime=0
while n<=10000:
redlight = ("100",
"10",
"90",
"20",
"60",
"20",
"20",
"20",
"20",
"60",
"20",
"20",
"90",
"90",
"100",
"60",
"100",
"80",
"80",
"60",
"90",
"60",
"120",
"60",
"80",
"60",
"1",
"1")
greenlight = ("30",
"120",
"30",
"120",
"40",
"120",
"120",
"120",
"120",
"40",
"120",
"120",
"40",
"15",
"20",
"20",
"20",
"20",
"20",
"40",
"30",
"20",
"40",
"40",
"20",
"40",
"1",
"1")
distance = ("400",
"400",
"250",
"250",
"250",
"450",
"300",
"650",
"1000",
"450",
"500",
"900",
"450",
"400",
"1100",
"900",
"600",
"1000",
"450",
"450",
"400",
"450",
"200",
"500",
"350",
"400",
"500",
"500")
velocity = ("80",
"80",
"80",
"80",
"80",
"80",
"60",
"80",
"80",
"80",
"80",
"80",
"80",
"80",
"80",
"80",
"80",
"60",
"80",
"80",
"80",
"80",
"80",
"60",
"80",
"80",
"80",
"40")
r=0
g=0
d=0
v=0
vcurrent=float(0)
t=0
gtime=0
accel=float(100/(3.6*15))
decel = float(accel*2)
while r<=26:
red=float(redlight[r])
grn=float(greenlight[g])
dis=float(distance[d])
vel=float(float(velocity[v])/3.6)
vnext=float(float(velocity[v+1])/3.6)
saccel = float((vcurrent*(vel-vcurrent)/accel) + (vel-vcurrent)*(vel-vcurrent)/(2*accel))
scrit = float(dis-(vel/decel) - (vel*vel)/(2*decel))
startingred = random.randint(0, (int(grn)-1))
if vcurrent == vel:
if vnext>= vcurrent:
t = int (dis/vel)
if (t+startingred)%(red+grn)<=red:
t = int (scrit/vel + (vel/decel) + red-((t+startingred)%(red+grn))) ### 2
vcurrent = 0
else:
t = int (dis/vel)### 1
vcurrent = vel
else:
t = int ((scrit/vel) +
(vcurrent - (vnext)/((vcurrent*(vcurrent - (vnext/(dis-scrit))-
((vcurrent - vnext)*(vcurrent - vnext)/(2*(dis-scrit))))))))
if (t+startingred)%(red+grn)<=red:
t = int (scrit/vel + (vel/decel)+ red-((t+startingred)%(red+grn)))### 2
vcurrent = 0
else:
t = int (scrit/vel +
(vcurrent - vnext)/((vcurrent*(vcurrent - (vnext/(dis-scrit))-
((vcurrent - vnext)*(vcurrent - vnext)/(2*(dis-scrit)))))))### 3
vcurrent = float(vnext/3.6)
elif vcurrent < vel:
vcrit=math.sqrt(2*accel*scrit+vcurrent*vcurrent)
if saccel >= scrit:
if vnext >= vcrit:
t = int(((vcrit-vcurrent)/ accel) + (dis-scrit)/vcrit)
if (t+startingred)%(red+grn)<=red:
t = int(((vcrit-vcurrent)/ accel) + ((dis-scrit)*2/vcrit) + red-((t+startingred)%(red+grn)))### 8
vcurrent = 0
else:
t = int(((vcrit-vcurrent)/ accel) + (dis-scrit)/vcrit) ### 7
vcurrent = vcrit
else:
t = int(((vcrit-vcurrent)/ accel) + (vcrit - vnext)/((vcrit*(vcrit - vnext)/(dis-scrit))-
((vcrit - vnext)*(vcrit - vnext)/(2*(dis-scrit)))))
if (t+startingred)%(red+grn)<=red:
t = int(((vcrit-vcurrent)/ accel) + ((dis-scrit)*2/vcrit) + red-((t+startingred)%(red+grn)) ) ### 8
vcurrent = 0
else:
t = int(((vcrit-vcurrent)/ accel) + (vcrit - vnext)/((vcrit*(vcrit - vnext)/(dis-scrit))-
((vcrit - vnext)*(vcrit - vnext)/(2*(dis-scrit))))) ### 9
vcurrent = vnext
else:
if vnext>= vel:
t = int(((vel- vcurrent)/accel) + (dis-saccel)/vel)
if (t+startingred)%(red+grn)<=red:
t = int (((vel- vcurrent)/accel) + (scrit-saccel)/vel + (vel/decel)+ red-((t+startingred)%(red+grn)))### 5
vcurrent = 0
else:
t = int (((vel- vcurrent)/accel) + (dis-saccel)/vel)### 4
vcurrent = vel
else:
if (t+startingred)%(red+grn)<=red:
t = int (((vel- vcurrent)/accel) + (scrit-saccel)/vel + (vel/decel)+ red-((t+startingred)%(red+grn)))### 5
vcurrent = 0
else:
t = int (((vel- vcurrent)/accel) +(scrit-saccel)/vel + (vel - vnext)/((vel*(vel - vnext)/(dis-scrit))-((vel - vnext)*(vel - vnext)/(2*(dis-scrit))))) ### 6
vcurrent = vnext
else:
print ("ERROR: v current > v next")
#print (t)
r+=1
g+=1
d+=1
v+=1
gtime+=t
dev=(1476-gtime)*(1476-gtime)
#print (gtime)
n+=1
dev+=dev
overalltime+=gtime
print ("mean= ", overalltime/n)
print ("deviation= ", math.sqrt(dev/n))
Представленный вариант отображает «обычную езду» — с максимальным превышением скорости, которое наказывается только устным предупреждением.
А вот теперь занятно будет с помощью симуляции на основе этого маршрута сравнить следующие варианты: введение нештрафуемого интервала в 10 км/ч, сверхзаконопослушную езду точно по ограничениям, педалируемую урбанистами максимальную городскую скорость в 50 км/ч.
Результаты 10 000 симуляций, raw data:
(ограничение, км/ч; время прохождения маршрута, с; стандартное отклонение):
90 1466,6 0,5
80 1475,6 0,4
70 1479,7 0,9
60 1593,7 0,8
50 1701,3 0,5
40 1869,8 0,6
Диаграмма показывает минуты пути в зависимости от глобального ограничения скорости. Да, знаю, что ось Y не с нуля, но так нагляднее.
Результаты показались мне немного неожиданными. Во-первых, планируемое уменьшение нештрафуемого порога на данном маршруте действительно не приведет к значимому увеличению времени в пути. И начиная как раз где-то с 70 км/ч увеличение скорости почти ничего не дает. Во-вторых, снижение разрешенной скорости заметно увеличит время, на 8% даже относительно совсем законопослушных граждан и на 13% от негласно эксплуатируемых скоростей «устного предупреждения». То есть, утверждение, что снижение разрешенной скорости не отразится на средней, строго говоря неверно. Опять же, в рамках данной модели нельзя показать, будет ли «стакаться» замедление на 10% в условиях плотного трафика и усугублять дорожную обстановку.
Пожалуйста, пишите замечания по модели (повторюсь, конструктивные), ну, а если вы заложите в модель свои маршруты и принесете в комментарии результат — будет вообще замечательно.