Moment equations of Langevin traffic with a jam instability
The density and flux equations of Langevin traffic on a ring road, whose uniform flow breaks into jams.
- Name:
traffic_density_equation - Field:
engineering/traffic - Formalism: ordinary differential equations
- References: Bertin 2021
Factory
@register_model(
field='engineering/traffic',
tags=('traffic', 'field', 'jams', 'instability', 'pde'),
references=('Bertin 2021',),
defaults={'until': 400.0, 'dt': 4.0, 'solver': RK4Solver(dt=0.1)},
)
def traffic_density_equation(
n: int = 100,
*,
length: float = 50.0,
density: float = 0.5,
gamma: float = 1.0,
u0: float = 1.0,
rho_max: float = 1.0,
D1: float = 0.2,
perturbation: float = 0.01,
random_state: int | np.random.Generator | None = 0,
) -> System:
"""Return the density and flux equations of Langevin traffic on a ring road, whose uniform flow breaks into jams.
The density `rho` and the flux `w` of cars that relax to the desired speed `u(rho) = u0 (1 - rho / rho_max)` at
the rate `gamma`, with the diffusion coefficient `D(rho) = D1 rho`, follow the moment equations
`rho_t + w_x = (D rho_x)_x` and `w_t + (2 u w - rho u^2)_x = -gamma (w - rho u) + (D w_x)_x`, closed by expanding
the velocity distribution around `u(rho)`. A long density wave of the uniform flow grows when the effective
diffusion coefficient `D(rho) + ((rho u^2)' - (rho u)'^2) / gamma = D(rho) - rho^2 u'(rho)^2 / gamma` is negative,
so the flow is unstable above the density `D1 gamma rho_max^2 / u0^2`, and the perturbation grows into dense jams.
The uniform density starts with independent Gaussian perturbations of relative size
`perturbation` in every cell, at the flux `rho u(rho)`.
Args:
n:
The number of cells.
length:
The length of the ring road.
density:
The mean density.
gamma:
The relaxation rate of the speed.
u0:
The desired speed on an empty road.
rho_max:
The jam density, at which the desired speed vanishes.
D1:
The diffusion coefficient per unit density.
perturbation:
The relative standard deviation of the initial density perturbations.
random_state:
The seed or generator of the initial perturbations.
Returns:
system:
The model, with the density `road.rho` and the flux `road.w`.
Examples:
>>> from skcomplex.models.engineering.traffic import traffic_density_equation
>>> traffic_density_equation(10).elements[0].values['D1']
0.2
"""
rng = np.random.default_rng(random_state)
rho = density * (1 + perturbation * rng.standard_normal(n))
values = {
'rho': rho,
'w': rho * u0 * (1 - rho / rho_max),
'gamma': gamma,
'u0': u0,
'rho_max': rho_max,
'D1': D1,
}
space = LatticeSpace(n, spacing=length / n)
road = FieldElement('road', element=TrafficMomentsElement, space=space, values=values)
return System(elements=[road])
Components
class TrafficMomentsElement(Element):
"""Density `rho` and flow `w` of Langevin traffic on a road, the first two moments of its speeds."""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
rho: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
w: Annotated[object, StateSpec()] = 0.25,
gamma: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
u0: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
rho_max: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
D1: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
) -> None:
super().__init__(label, visual=visual)
self.rho = rho
self.w = w
self.gamma = gamma
self.u0 = u0
self.rho_max = rho_max
self.D1 = D1
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the closed moment equations of the density and the flow."""
return [
RateLaw(s.rho, -divergence([s.w]) + laplacian(s.D1 * s.rho**2 / 2)),
RateLaw(
s.w,
-divergence(
[
2 * LangevinDrivingInteraction._target_speed(s.rho, s) * s.w
- s.rho * LangevinDrivingInteraction._target_speed(s.rho, s) ** 2,
],
)
- s.gamma * (s.w - s.rho * LangevinDrivingInteraction._target_speed(s.rho, s))
+ s.D1 * (laplacian(s.rho * s.w) + s.rho * laplacian(s.w) - s.w * laplacian(s.rho)) / 2,
),
]
Equations
road.rho[i]: diff(rho_road(t), t) == laplacian(1/2*D1_road*rho_road(t)^2) - partial(w_road(t), 0)
road.w[i]: diff(w_road(t), t) == 1/2*(laplacian(w_road(t))*rho_road(t) - laplacian(rho_road(t))*w_road(t) + laplacian(rho_road(t)*w_road(t)))*D1_road - (u0_road*(rho_road(t)/rho_max_road - 1)*rho_road(t) + w_road(t))*gamma_road - partial(-u0_road^2*(rho_road(t)/rho_max_road - 1)^2*rho_road(t) - 2*u0_road*(rho_road(t)/rho_max_road - 1)*w_road(t), 0)