Main Content

Esta página se ha traducido mediante traducción automática. Haga clic aquí para ver la última versión en inglés.

Planificación de movimiento en entornos urbanos utilizando un mapa de cuadrícula de ocupación dinámica

Este ejemplo le muestra cómo realizar una replanificación dinámica en una escena de conducción urbana utilizando una ruta de referencia de Frenet. En este ejemplo, se utiliza una estimación de un mapa de cuadrícula de ocupación dinámica del entorno local para encontrar trayectorias locales óptimas.

Introducción

La replanificación dinámica para vehículos autónomos normalmente se realiza con un planificador de movimiento local. El planificador de movimiento local es responsable de generar una trayectoria óptima basada en el plan global y la información sobre el entorno circundante. La información sobre el entorno circundante se puede describir principalmente de dos maneras:

  1. Conjunto discreto de objetos del entorno circundante con geometrías definidas.

  2. Cuadrícula discretizada con estimación de regiones libres y ocupadas en el entorno circundante.

En presencia de obstáculos dinámicos en el entorno, un planificador de movimiento local requiere predicciones a corto plazo de la información sobre el entorno para evaluar la validez de las trayectorias planificadas. La elección de la representación del entorno suele estar gobernada por el algoritmo de percepción ascendente. Para los algoritmos de planificación, la representación basada en objetos ofrece una descripción del entorno que ahorra memoria. También permite una forma más sencilla de definir relaciones entre objetos para la predicción de comportamiento. Por otro lado, un enfoque basado en cuadrículas permite una representación sin modelo de objetos, lo que ayuda a comprobar de manera eficiente las colisiones en escenarios complejos con una gran cantidad de objetos. La representación basada en cuadrículas también es menos sensible a las imperfecciones de la extracción de objetos, como objetivos falsos y perdidos. También es posible un híbrido de estos dos enfoques extrayendo hipótesis de objetos de la representación basada en cuadrículas.

En este ejemplo, representará el entorno circundante como un mapa de cuadrícula de ocupación dinámica. Para ver un ejemplo que utiliza el conjunto discreto de objetos, consulte el ejemplo Planificación de la trayectoria de la carretera mediante la ruta de referencia de Frenet . Un mapa de cuadrícula de ocupación dinámica es una estimación basada en cuadrícula del entorno local alrededor del vehículo ego. Además de estimar la probabilidad de ocupación, la cuadrícula de ocupación dinámica también estima los atributos cinemáticos de cada celda, como la velocidad, la velocidad de giro y la aceleración. Además, las estimaciones de la red dinámica se pueden predecir para un corto período de tiempo en el futuro para evaluar la ocupación del entorno local en el futuro cercano. En este ejemplo, se obtiene la estimación del entorno basada en cuadrícula fusionando nubes de puntos de seis lidars montados en el vehículo ego.

Configurar escenario y rastreador basado en cuadrícula

El escenario utilizado en este ejemplo representa una escena de intersección urbana y contiene una variedad de objetos, incluidos peatones, ciclistas, automóviles y camiones. El vehículo ego está equipado con seis sensores lidar homogéneos, cada uno con un campo de visión de 90 grados, lo que proporciona una cobertura de 360 ​​grados alrededor del vehículo ego. Para obtener más detalles sobre el escenario y los modelos de sensores, consulte el ejemplo Grid-Based Tracking in Urban Environments Using Multiple Lidars (Sensor Fusion and Tracking Toolbox) . La definición de escenario y sensores está incluida en la función auxiliar helperGridBasedPlanningScenario.

% For reproducible results
rng(2020);

% Create scenario, ego vehicle and simulated lidar sensors
[scenario, egoVehicle, lidars] = helperGridBasedPlanningScenario;

Ahora, defina un rastreador basado en cuadrícula utilizando el objeto trackerGridRFS (Sensor Fusion and Tracking Toolbox) System object™. El rastreador genera una estimación del entorno tanto a nivel de objeto como a nivel de cuadrícula. La estimación a nivel de cuadrícula describe la ocupación y el estado del entorno local y se puede obtener como el cuarto resultado del rastreador. Para obtener más detalles sobre cómo configurar un rastreador basado en cuadrícula, consulte el ejemplo Grid-Based Tracking in Urban Environments Using Multiple Lidars (Sensor Fusion and Tracking Toolbox) .

% Set up sensor configurations for each lidar
sensorConfigs = cell(numel(lidars),1);

% Fill in sensor configurations
for i = 1:numel(sensorConfigs)
    sensorConfigs{i} = helperGetLidarConfig(lidars{i},egoVehicle);
end

% Set up tracker
tracker = trackerGridRFS('SensorConfigurations',sensorConfigs,...
    'HasSensorConfigurationsInput',true,...
    'GridLength',120,...
    'GridWidth',120,...
    'GridResolution',2,...
    'GridOriginInLocal',[-60 -60],...
    'NumParticles',1e5,...
    'NumBirthParticles',2e4,...
    'VelocityLimits',[-15 15;-15 15],...
    'BirthProbability',0.025,...
    'ProcessNoise',5*eye(2),...
    'DeathRate',1e-3,...
    'FreeSpaceDiscountFactor',1e-2,...
    'AssignmentThreshold',8,...
    'MinNumCellsPerCluster',4,...
    'ClusteringThreshold',4,...
    'ConfirmationThreshold',[3 4],...
    'DeletionThreshold',[4 4]);

Configurar el planificador de movimiento

Configure un algoritmo de planificación de movimiento local para planificar trayectorias óptimas en coordenadas Frenet a lo largo de una ruta de referencia global.

Defina la ruta de referencia global utilizando el objeto referencePathFrenet proporcionando los puntos de referencia en el marco de coordenadas cartesianas del escenario de conducción. La ruta de referencia utilizada en este ejemplo define una ruta que gira a la derecha en la intersección.

waypoints = [-110.6 -4.5 0;
            49 -4.5 0;
            55.5 -17.7 -pi/2;
            55.5 -130.6 -pi/2]; % [x y theta]

% Create a reference path using waypoints
refPath = referencePathFrenet(waypoints);

% Visualize the reference path
fig = figure('Units','normalized','Position',[0.1 0.1 0.8 0.8]);
ax = axes(fig);
hold(ax,'on');
plot(scenario,'Parent',ax);
show(refPath,'Parent',ax);
xlim(ax,[-120 80]);
ylim(ax,[-160 40]);

snapnow;

El algoritmo de planificación de movimiento local en este ejemplo consta de tres pasos principales:

  1. Ejemplos de trayectorias locales

  2. Encuentre trayectorias factibles y libres de colisiones

  3. Elija el criterio de optimización y seleccione la trayectoria óptima.

Las siguientes secciones analizan cada paso del algoritmo de planificación local y las funciones auxiliares utilizadas para ejecutar cada paso.

Muestras de trayectorias locales

En cada paso de la simulación, el algoritmo de planificación genera una lista de trayectorias de muestra que el vehículo ego puede elegir. Las trayectorias locales se muestrean conectando el estado actual del vehículo ego con los estados terminales deseados. Utilice el objeto trajectoryGeneratorFrenet para conectar los estados actual y terminal para generar trayectorias locales. Defina el objeto proporcionando la ruta de referencia y la resolución deseada en el tiempo para la trayectoria. El objeto conecta estados iniciales y finales en coordenadas de Frenet utilizando polinomios de quinto orden.

connector = trajectoryGeneratorFrenet(refPath,'TimeResolution',0.1);

La estrategia para muestrear estados terminales en coordenadas de Frenet a menudo depende de la red de carreteras y del comportamiento deseado del vehículo ego durante las diferentes fases de la ruta global. Para obtener ejemplos más detallados del uso de diferentes comportamientos del ego, como el control de crucero y el seguimiento de automóviles, consulte la sección "Planificación de rutas adaptativas a través del tráfico" del ejemplo Planificación de la trayectoria de la carretera mediante la ruta de referencia de Frenet . En este ejemplo, muestra los estados de la terminal utilizando dos estrategias diferentes, según la ubicación del vehículo en la ruta de referencia, que se muestran como regiones azul y verde en la siguiente figura.

% Visualize path regions for sampling strategy visualization
pathPoints = closestPoint(refPath, refPath.Waypoints(:,1:2));
roadS = pathPoints(:,end);
intersectionS = roadS(2,end);
intersectionBuffer = 20;
pathGreen = [interpolate(refPath,linspace(0,intersectionS-intersectionBuffer,20));...
            nan(1,6);...
            interpolate(refPath,linspace(intersectionS,roadS(end),100))];
pathBlue = interpolate(refPath,linspace(intersectionS-intersectionBuffer,roadS(2,end),20));
hold(ax,'on');
plot(ax,pathGreen(:,1),pathGreen(:,2),'Color',[0 1 0],'LineWidth',5);
plot(ax,pathBlue(:,1),pathBlue(:,2),'Color',[0 0 1],'LineWidth',5);

snapnow;

Cuando el vehículo ego está en la región verde, se utiliza la siguiente estrategia para muestrear trayectorias locales. El estado terminal del vehículo ego despuésΔTel tiempo se define como:

xEgo(ΔT)=[NaNs˙0d00];

donde las muestras discretas para variables se obtienen utilizando los siguientes conjuntos predefinidos:

{ΔT{linspace(2,4,6)},s˙{linspace(0,s˙max,10)},d{0wlane}}

El uso de NaN en el estado terminal permite que el objeto trajectoryGeneratorFrenet calcule automáticamente la distancia longitudinal recorrida en una trayectoria de sacudida mínima. Esta estrategia produce un conjunto de trayectorias que permiten al vehículo ego acelerar hasta el límite máximo de velocidad (s˙max) velocidades o desacelerar hasta detenerse por completo a diferentes velocidades. Además, las opciones muestradas de desplazamiento lateral (ddes) permiten que el vehículo ego cambie de carril durante estas maniobras.

% Define smax and wlane
speedLimit = 15;
laneWidth = 2.975;

Cuando el vehículo ego está en la región azul de la trayectoria, se utiliza la siguiente estrategia para muestrear trayectorias locales:

xEgo (ΔT)=[sstop 0 0 0 0 0];

dóndeΔTSe elige para minimizar el tirón durante la trayectoria. Esta estrategia permite que el vehículo se detenga a la distancia deseada (sstop) en el carril derecho con una trayectoria de mínima sacudida. El algoritmo de muestreo de trayectoria está incluido dentro de la función auxiliar, helperGenerateTrajectory, adjunta a este ejemplo.

Encontrar trayectorias factibles y libres de colisiones

El proceso de muestreo descrito en la sección anterior puede producir trayectorias que son cinemáticamente inviables y exceden los umbrales de atributos cinemáticos como la aceleración y la curvatura. Por lo tanto, limitas la aceleración y velocidad máximas del vehículo ego utilizando la función auxiliar helperKinematicFeasibility, que verifica la viabilidad de cada trayectoria frente a estas restricciones cinemáticas.

% Define kinematic constraints
accMax = 15;

Además, configura un validador de colisiones para evaluar si el vehículo ego puede maniobrar en una trayectoria cinemáticamente factible sin chocar con ningún otro obstáculo en el entorno. Para definir el validador, utilice la clase auxiliar HelperDynamicMapValidator. Esta clase utiliza la función predictMapToTime (Sensor Fusion and Tracking Toolbox) del objeto trackerGridRFS para obtener predicciones a corto plazo de la ocupación del entorno circundante. Dado que la incertidumbre en la estimación aumenta con el tiempo, configure el validador con un horizonte temporal máximo de 2 segundos.

La ocupación prevista del entorno se convierte en un mapa de costes inflado en cada paso para tener en cuenta el tamaño del vehículo ego. El planificador de ruta utiliza un paso de tiempo de 0,1 segundos con un horizonte temporal de predicción de 2 segundos. Para reducir la complejidad computacional, se supone que la ocupación del entorno circundante es válida durante 5 pasos de tiempo, o 0,5 segundos. Como resultado, sólo se requieren 4 predicciones en el horizonte de planificación de 2 segundos. Además de tomar decisiones binarias sobre colisión o no colisión, el validador también proporciona una medida de la probabilidad de colisión del vehículo ego. Esta probabilidad se puede incorporar a la función de costes para que los criterios de optimización tengan en cuenta la incertidumbre en el sistema y tomen mejores decisiones sin aumentar el horizonte temporal del planificador.

vehDims = vehicleDimensions(egoVehicle.Length,egoVehicle.Width);
collisionValidator = HelperDynamicMapValidator('MaxTimeHorizon',2, ... % Maximum horizon for validation
    'TimeResolution',connector.TimeResolution, ... % Time steps between trajectory samples
    'Tracker',tracker, ... % Provide tracker for prediction
    'ValidPredictionSpan',5, ... % Prediction valid for 5 steps
    'VehicleDimensions',vehDims); % Provide dimensions of ego

Elegir criterio de optimización

Después de validar las trayectorias factibles frente a obstáculos o regiones ocupadas del entorno, elija un criterio de optimización para cada trayectoria válida definiendo una función de coste para las trayectorias. Se espera que diferentes funciones de costes produzcan diferentes comportamientos en el vehículo ego. En este ejemplo, usted define el coste de cada trayectoria como

C=Js+Jd+1000Pc+100(s˙(ΔT)-s˙Limit)2

donde:

Jses el tirón en la dirección longitudinal de la trayectoria de referencia

Jdes el tirón en la dirección lateral de la trayectoria de referencia

Pces la probabilidad de colisión obtenida por el validador

El cálculo del coste para cada trayectoria se define utilizando la función auxiliar helperCalculateTrajectoryCosts. De la lista de trayectorias válidas, la trayectoria con el coste mínimo se considera la trayectoria óptima.

Ejecutar escenario, estimar mapa dinámico y planificar trayectorias locales

Ejecute el escenario, genere nubes de puntos a partir de todos los sensores lidar y estime el mapa de cuadrícula de ocupación dinámica. Utilice la estimación del mapa dinámico y sus predicciones para planificar una trayectoria local para el vehículo ego.

% Close original figure and initialize a new display
close(fig);
display = helperGridBasedPlanningDisplay;

% Initial ego state
currentEgoState = [-110.6 -1.5 0 0 15 0];
helperMoveEgoVehicleToState(egoVehicle, currentEgoState);

% Initialize pointCloud outputs from each sensor
ptClouds = cell(numel(lidars),1);
sensorConfigs = cell(numel(lidars),1);

% Simulation Loop
while advance(scenario)
    % Current simulation time
    time = scenario.SimulationTime;
    
    % Poses of objects with respect to ego vehicle
    tgtPoses = targetPoses(egoVehicle);
    
    % Simulate point cloud from each sensor
    for i = 1:numel(lidars)
        [ptClouds{i}, isValidTime] = step(lidars{i},tgtPoses,time);
        sensorConfigs{i} = helperGetLidarConfig(lidars{i},egoVehicle);
    end
    
    % Pack point clouds as sensor data format required by the tracker
    sensorData = packAsSensorData(ptClouds,sensorConfigs,time);
    
    % Call the tracker
    [tracks, ~, ~, map] = tracker(sensorData,sensorConfigs,time);
    
    % Update validator's future predictions using current estimate
    step(collisionValidator, currentEgoState, map, time);
    
    % Sample trajectories using current ego state and some kinematic
    % parameters
    [frenetTrajectories, globalTrajectories] = helperGenerateTrajectory(connector, refPath, currentEgoState, speedLimit, laneWidth, intersectionS, intersectionBuffer);
    
    % Calculate kinematic feasibility of generated trajectories
    isKinematicsFeasible = helperKinematicFeasibility(frenetTrajectories,speedLimit,accMax);
    
    % Calculate collision validity of feasible trajectories
    feasibleGlobalTrajectories = globalTrajectories(isKinematicsFeasible);
    feasibleFrenetTrajectories = frenetTrajectories(isKinematicsFeasible);
    [isCollisionFree, collisionProb] = isTrajectoryValid(collisionValidator, feasibleGlobalTrajectories);
    
    % Calculate costs and final optimal trajectory
    nonCollidingGlobalTrajectories = feasibleGlobalTrajectories(isCollisionFree);
    nonCollidingFrenetTrajectories = feasibleFrenetTrajectories(isCollisionFree);
    nonCollodingCollisionProb = collisionProb(isCollisionFree);
    costs = helperCalculateTrajectoryCosts(nonCollidingFrenetTrajectories, nonCollodingCollisionProb, speedLimit);
    
    % Find optimal trajectory
    [~,idx] = min(costs);
    optimalTrajectory = nonCollidingGlobalTrajectories(idx);
    
    % Assemble for plotting
    trajectories = helperAssembleTrajectoryForPlotting(globalTrajectories, ...
        isKinematicsFeasible, isCollisionFree, idx);
    
    % Update display
    display(scenario, egoVehicle, lidars, ptClouds, tracker, tracks, trajectories, collisionValidator);
    
    % Move ego with optimal trajectory
    if ~isempty(optimalTrajectory)
        currentEgoState = optimalTrajectory.Trajectory(2,:);
        helperMoveEgoVehicleToState(egoVehicle, currentEgoState);
    else
        % All trajectories either violated kinematic feasibility
        % constraints or resulted in a collision. More behaviors on
        % trajectory sampling may be needed.
        error('Unable to compute optimal trajectory');
    end
end

Resultados

Analice los resultados del algoritmo de planificación de rutas locales y cómo las predicciones del mapa ayudaron al planificador. Esta animación muestra el resultado del algoritmo de planificación durante todo el escenario. Observe que el vehículo ego alcanzó con éxito su destino deseado y maniobró alrededor de diferentes objetos dinámicos, siempre que fue necesario. El vehículo ego también se detuvo en la intersección debido a los cambios regionales agregados a la política de muestreo.

DynamicGridLocalPlanning.gif

A continuación, analice el algoritmo de planificación local durante el primer cambio de carril. Las instantáneas de esta sección se capturan en un tiempo = 4,3 segundos durante la simulación.

En esta instantánea, el vehículo ego acaba de comenzar a realizar una maniobra de cambio de carril hacia el carril derecho.

showSnaps(display, 3, 1);

La instantánea que sigue muestra la estimación de la cuadrícula dinámica en el mismo paso de tiempo. El color de la celda de la cuadrícula indica la dirección del movimiento del objeto que ocupa esa celda de la cuadrícula. Observe que las celdas que representan el automóvil frente al vehículo ego están coloreadas en rojo, lo que indica que están ocupadas con un objeto dinámico. Además, el automóvil se mueve en la dirección X positiva del escenario, por lo que, según la rueda de colores, el color de las celdas de la cuadrícula correspondientes es rojo.

f = showSnaps(display, 2, 1);
if ~isempty(f)
    ax = findall(f,'Type','Axes');
    ax.XLim = [0 40];
    ax.YLim = [-20 20];
    s = findall(ax,'Type','Surf');
    s.XData = 36 + 1/3*(s.XData - mean(s.XData(:)));
    s.YData = 16 + 1/3*(s.YData - mean(s.YData(:)));
end

Según la imagen anterior, la trayectoria planificada del vehículo ego pasa a través de las regiones ocupadas del espacio, lo que representa una colisión si se realiza una validación de ocupación estática tradicional. Sin embargo, el mapa de ocupación dinámica y el validador tienen en cuenta la naturaleza dinámica de la cuadrícula al validar el estado de la trayectoria con la ocupación prevista en cada paso de tiempo. La siguiente instantánea muestra el mapa de costes previsto en diferentes pasos de predicción (ΔT), junto con la posición planificada del vehículo ego en la trayectoria. El mapa de costes previsto está inflado para tener en cuenta el tamaño del vehículo ego. Por lo tanto, si un objeto puntual que representa el origen del vehículo ego puede colocarse en el mapa de ocupación sin ninguna colisión, se puede interpretar que el vehículo ego no choca con ningún obstáculo. Las regiones amarillas en el mapa de costes indican áreas con colisiones garantizadas con un obstáculo. La probabilidad de colisión decae exponencialmente fuera de las regiones amarillas hasta el final de la región de inflación. Las regiones azules indican áreas con probabilidad cero de colisión según la predicción actual.

Observe que la región amarilla que representa el automóvil frente al vehículo ego avanza en el mapa de costes tal como se predice el mapa en el futuro. Esto refleja que la predicción de ocupación considera la velocidad de los objetos en el entorno circundante. Además, observe que las celdas clasificadas como objetos estáticos permanecieron relativamente estáticas en la cuadrícula durante la predicción. Por último, observe que la posición planificada del origen del vehículo ego no choca con ninguna región ocupada en el mapa de costes. Esto muestra que el vehículo ego puede maniobrar con éxito en esta trayectoria.

f = showSnaps(display, 1, 1);
if ~isempty(f)
ax = findall(f,'Type','Axes');
for i = 1:numel(ax)
    ax(i).XLim = [0 40];
    ax(i).YLim = [-20 20];
end
end

Resumen

En este ejemplo, aprendió a utilizar las predicciones del mapa dinámico del rastreador basado en cuadrícula, trackerGridRFS, y a integrar el mapa dinámico con un algoritmo de planificación de ruta local para generar trayectorias para el vehículo ego. en entornos dinámicos y complejos. También aprendió cómo se puede utilizar la naturaleza dinámica de la ocupación para planificar trayectorias de manera más eficiente en el entorno.

Funciones de apoyo

function sensorData = packAsSensorData(ptCloud, configs, time)
% Pack the sensor data as format required by the tracker
%
% ptCloud - cell array of pointCloud object
% configs - cell array of sensor configurations
% time    - Current simulation time

%The lidar simulation returns outputs as pointCloud objects. The Location
%property of the point cloud is used to extract x,y, and z locations of
%returns and pack them as structures with information required by a tracker.
sensorData = struct('SensorIndex',{},...
    'Time', {},...
    'Measurement', {},...
    'MeasurementParameters', {});

for i = 1:numel(ptCloud)
    % This sensor's point cloud
    thisPtCloud = ptCloud{i};
    
    % Allows mapping between data and configurations without forcing an
    % ordered input and requiring configuration input for static sensors.
    sensorData(i).SensorIndex = configs{i}.SensorIndex;
    
    % Current time
    sensorData(i).Time = time;
    
    % Exctract Measurement as a 3-by-N defining locations of points
    sensorData(i).Measurement = reshape(thisPtCloud.Location,[],3)';
    
    % Data is reported in the sensor coordinate frame and hence measurement
    % parameters are same as sensor transform parameters.
    sensorData(i).MeasurementParameters = configs{i}.SensorTransformParameters;
end

end

function config = helperGetLidarConfig(lidar, ego)
% Get configuration of the lidar sensor for tracker
%
% config - Configuration of the lidar sensor in the world frame
% lidar - lidarPointCloudGeneration object
% ego   - driving.scenario.Actor in the scenario

% Define transformation from sensor to ego
senToEgo = struct('Frame',fusionCoordinateFrameType(1),...
    'OriginPosition',[lidar.SensorLocation(:);lidar.Height],...
    'Orientation',rotmat(quaternion([lidar.Yaw lidar.Pitch lidar.Roll],'eulerd','ZYX','frame'),'frame'),...
    'IsParentToChild',true);

% Define transformation from ego to tracking coordinates
egoToScenario = struct('Frame',fusionCoordinateFrameType(1),...
    'OriginPosition',ego.Position(:),...
    'Orientation',rotmat(quaternion([ego.Yaw ego.Pitch ego.Roll],'eulerd','ZYX','frame'),'frame'),...
    'IsParentToChild',true);

% Assemble using trackingSensorConfiguration.
config = trackingSensorConfiguration(...
    'SensorIndex',lidar.SensorIndex,...
    'IsValidTime', true,...
    'SensorLimits',[lidar.AzimuthLimits;0 lidar.MaxRange],...
    'SensorTransformParameters',[senToEgo;egoToScenario],...
    'DetectionProbability',0.95);

end

function helperMoveEgoVehicleToState(egoVehicle, currentEgoState)
% Move ego vehicle in scenario to a state calculated by the planner
% 
% egoVehicle - driving.scenario.Actor in the scenario
% currentEgoState - [x y theta kappa speed acc]

% Set 2-D Position
egoVehicle.Position(1:2) = currentEgoState(1:2);

% Set 2-D Velocity (s*cos(yaw) s*sin(yaw))
egoVehicle.Velocity(1:2) = [cos(currentEgoState(3)) sin(currentEgoState(3))]*currentEgoState(5);

% Set Yaw in degrees
egoVehicle.Yaw = currentEgoState(3)*180/pi;

% Set angular velocity in Z (yaw rate) as v/r
egoVehicle.AngularVelocity(3) = currentEgoState(4)*currentEgoState(5);

end

function isFeasible = helperKinematicFeasibility(frenetTrajectories, speedLimit, aMax)
% Check kinematic feasibility of trajectories
%
% frenetTrajectories - Array of trajectories in Frenet coordinates
% speedLimit - Speed limit (m/s)
% aMax - Maximum acceleration (m/s^2)

isFeasible = false(numel(frenetTrajectories),1);
for i = 1:numel(frenetTrajectories)
    % Speed of the trajectory
    speed = frenetTrajectories(i).Trajectory(:,2);
    
    % Acceleration of the trajectory
    acc = frenetTrajectories(i).Trajectory(:,3);
    
    % Is speed valid?
    isSpeedValid = ~any(speed < -0.1 | speed > speedLimit + 1);
    
    % Is acceleration valid?
    isAccelerationValid = ~any(abs(acc) > aMax);
    
    % Trajectory feasible if both speed and acc valid
    isFeasible(i) = isSpeedValid & isAccelerationValid;
end

end

function cost = helperCalculateTrajectoryCosts(frenetTrajectories, Pc, smax)
% Calculate cost for each trajectory.
%
% frenetTrajectories - Array of trajectories in Frenet coordinates
% Pc - Probability of collision for each trajectory calculated by validator

n = numel(frenetTrajectories);
Jd = zeros(n,1);
Js = zeros(n,1);
s = zeros(n,1);

for i = 1:n
    % Time
    time = frenetTrajectories(i).Times;
    
    % resolution
    dT = time(2) - time(1);
    
    % Jerk along the path
    dds = frenetTrajectories(i).Trajectory(:,3);
    Js(i) = sum(gradient(dds,time).^2)*dT;
    
    % Jerk perpendicular to path
    % d2L/dt2 = d/dt(dL/ds*ds/dt)
    ds = frenetTrajectories(i).Trajectory(:,2);
    ddL = frenetTrajectories(i).Trajectory(:,6).*(ds.^2) + frenetTrajectories(i).Trajectory(:,5).*dds;
    Jd(i) = sum(gradient(ddL,time).^2)*dT;
    
    s(i) = frenetTrajectories(i).Trajectory(end,2);
end

cost = Js + Jd + 1000*Pc(:) + 100*(s - smax).^2;

end