Optimal velocity
The optimal velocity model of cars on a ring road, where uniform flow breaks into stop-and-go waves.
- Name:
optimal_velocity - Field:
engineering/traffic - Formalism: ordinary differential equations
- References: Bando et al. 1995; Chowdhury, Santen, and Schadschneider 2000
Factory
@register_model(
field='engineering/traffic',
tags=('traffic', 'jams', 'ode', 'car following'),
references=('Bando et al. 1995', 'Chowdhury, Santen, and Schadschneider 2000'),
defaults={'until': 200.0, 'dt': 1.0},
)
def optimal_velocity(
n_cars: int = 20,
length: float = 40.0,
*,
a: float = 1.0,
V0: float = 1.0,
hc: float = 2.0,
perturbation: float = 0.1,
) -> System:
"""Return the optimal velocity model of cars on a ring road, where uniform flow breaks into stop-and-go waves.
Car `i` relaxes its speed towards the optimal velocity of its headway `h`, `v_i' = a (V(h) - v_i)` with
`V(h) = V0 (tanh(h - hc) + tanh(hc))`. Uniform flow with headway `b = length / n_cars` is unstable when
`V'(b) > a / 2`, and then a jam forms that moves against the direction of travel. The cars are one group, and the
headway of car `i` is the distance along the ring to member `i + 1`.
Args:
n_cars:
The number of cars.
length:
The length of the ring road.
a:
The sensitivity of drivers.
V0:
The scale of the optimal velocity, which tends to `V0 (1 + tanh(hc))` on an empty road.
hc:
The headway at which the optimal velocity changes fastest.
perturbation:
The forward shift of the first car from uniform spacing.
Returns:
system:
The model, with the group `cars` of positions `cars.x` and speeds `cars.v`, car `i + 1` ahead of car `i`.
Examples:
>>> from skcomplex.models.engineering.traffic import optimal_velocity
>>> system = optimal_velocity(n_cars=5)
>>> system.elements[0].size, len(system.interactions)
(5, 1)
"""
headway = length / n_cars
speed = V0 * (np.tanh(headway - hc) + np.tanh(hc))
cars = GroupElement(
'cars',
element=CarElement,
size=n_cars,
values={'x': _ring_positions(n_cars, length, perturbation), 'v': float(speed)},
)
following = OptimalVelocityInteraction('follow', target='cars', a=a, V0=V0, hc=hc, road_length=length)
return System(elements=[cars], interactions=[following])
Components
class CarElement(Element):
"""Car on a road whose position `x` advances at its speed `v`."""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
x: Annotated[object, StateSpec(role='position')] = 0.0,
v: Annotated[object, StateSpec(role='velocity')] = 0.0,
) -> None:
super().__init__(label, visual=visual)
self.x = x
self.v = v
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the advance of the position at the speed."""
return [RateLaw(s.x, s.v)]
class OptimalVelocityInteraction(RelaxationInteraction):
"""Relaxation of the speed `v` of every car of a ring road towards the optimal velocity of its headway.
The speed follows `v' = a (V(h) - v)` with the optimal velocity `V(h) = V0 (tanh(h - hc) + tanh(hc))` of the
headway `h` to the next car, a relaxation over the time `1 / a`.
Args:
label:
The label of the interaction.
target:
The path of the group of cars, in the order they drive.
a:
The sensitivity, the inverse of the relaxation time.
V0:
The scale of the optimal velocity.
hc:
The safe headway, where the optimal velocity turns.
road_length:
The length of the ring road.
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
a: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
V0: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
hc: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 2.0,
road_length: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 40.0,
) -> None:
super().__init__(label, target=target, state='v')
self.a = a
self.V0 = V0
self.hc = hc
self.road_length = road_length
def _get_goal(self, namespace: SimpleNamespace) -> object:
"""Return the optimal velocity of the headway of every car."""
headway = OptimalVelocityInteraction._headway(namespace.target.x, namespace.road_length)
return namespace.V0 * (tanh(headway - namespace.hc) + tanh(namespace.hc))
def _get_time(self, namespace: SimpleNamespace) -> object:
"""Return the relaxation time `1 / a`."""
return 1 / namespace.a
@staticmethod
def _headway(x: Expression, road_length: Expression) -> Expression:
"""Return the distance from every car of a ring road to the car ahead, the next member of the group."""
return mod(shift_member(x, 1) - x, road_length)
Equations
cars.x[i]: diff(x_cars(t), t) == v_cars(t)
cars.v[i]: diff(v_cars(t), t) == (V0_follow*(tanh(hc_follow) + tanh(-hc_follow + mod(x_cars__shift0 - x_cars(t), road_length_follow))) - v_cars(t))*a_follow