Main Content

Adaptive Tracking of Maneuvering Targets with Managed Radar

This example shows how to use radar resource management to efficiently track multiple maneuvering targets. Tracking maneuvering targets requires the radar to revisit the targets more frequently than tracking non-maneuvering targets. An Interacting Multiple Model (IMM) filter estimates when the target is maneuvering. This estimate helps to manage the radar revisit time and therefore enhances the tracking. This example uses the Radar Toolbox™ for the radar model and Sensor Fusion and Tracking Toolbox™ for the tracking.


Multifunction radars can search for targets, confirm new tracks, and revisit tracks to update the state. To perform these functions, a multifunction radar is often managed by a resource manager that creates radar tasks for search, confirmation, and tracking. These tasks are scheduled according to priority and time so that, at each time step, the multifunction radar can point its beam in a desired direction. The Search and Track Scheduling for Multifunction Phased Array Radar example shows a multifunction phased-array radar managed with a resource manager.

In this example, we extend the Adaptive Tracking of Maneuvering Targets with Managed Radar example to the case of multiple maneuvering targets. There are two conflicting requirements for a radar used to track maneuvering targets:

  1. The number of targets and their initial location are typically not known in advance. Therefore, the radar must continuously search the area of interest to find the targets. Also, the radar needs to detect and establish a track on each target as soon as it enters the radar coverage area.

  2. The time period of target maneuvering is unknown in advance. If it is known that targets are not maneuvering, the radar can revisit the targets infrequently. However, since the maneuver start and end times are unknown, the radar must revisit each track frequently enough to be able to recognize when the maneuver starts and ends.

The radar must balance between providing enough beams centered on the tracked targets and leaving enough time to search for new targets. One approach is to simply define a revisit rate on each tracked target regardless of its maneuvering status and leave the remaining time for new target searching. This radar management scheme is sometimes referred to as Active Tracking [1]. As more targets become tracked, the radar can either perform fewer search tasks or it can track each target less frequently. Clearly, if the number of targets is large, the radar can be overwhelmed.

Active tracking treats all the tracks in the same way, which makes it a mode-based resource management algorithm. A more sophisticated way to manage the radar is based on the properties of each track. For example, use track properties such as the size of state uncertainty covariance, whether the track is maneuvering, and how fast it is moving towards an asset the radar site protects. When such properties are used, the radar resource management is referred to as Adaptive Tracking [1].

In this example, you compare the results of Active Tracking and Adaptive Tracking when the radar adapts based on estimated track maneuver.

Define Scenario and Radar Model

You define a scenario and a radar with update rate of 20 Hz, which means that the radar has 20 beams per second allocated for either search, confirmation, or tracking. You load the benchmark trajectories used in the Benchmark Trajectories for Multi-Object Tracking (Sensor Fusion and Tracking Toolbox) example. There are six benchmark trajectories and you define a trajectory for each one. From the figure below, the six platforms follow non-maneuvering legs interspersed with maneuvering legs. You can view the trajectories in the figure below.

% Create scenario
updateRate = 20;
scenario = trackingScenario('UpdateRate',updateRate);
% Add the benchmark trajectories

% Create visualization
f = figure;
mp = uipanel('Parent',f,'Title','Theater Plot','FontSize',12,...
    'BackgroundColor','white','Position',[.01 .25 .98 .73]);
tax = axes(mp,'ZDir','reverse');

% Visualize scenario
thp = theaterPlot('Parent',tax,'AxesUnits',["km","km","km"],'XLimits',[0 85000],'YLimits',[-45000 70000],'ZLimits',[-10000 1000]);
plp = platformPlotter(thp, 'DisplayName', 'Platforms');
pap = trajectoryPlotter(thp, 'DisplayName', 'Trajectories', 'LineWidth', 1);
dtp = detectionPlotter(thp, 'DisplayName', 'Detections');
cvp = coveragePlotter(thp, 'DisplayName', 'Radar Coverage');
trp = trackPlotter(thp, 'DisplayName', 'Tracks', 'ConnectHistory', 'on', 'ColorizeHistory', 'on');
numPlatforms = numel(scenario.Platforms);
trajectoryPositions = cell(1,numPlatforms);
for i = 1:numPlatforms
    trajectoryPositions{i} = lookupPose(scenario.Platforms{i}.Trajectory,(0:0.1:185));
plotTrajectory(pap, trajectoryPositions);

You define the radar using a helperManagedRadarDataGenerator object, which is an object inherited from the radarDataGenerator System object. The helperManagedRadarDataGenerator provides a probabilistic radar model and allows pointing the radar to a certain direction. This setup enables the radar resource manager to schedule the radar for searching, confirming, and tracking targets. The radar models electronic beams that have azimuth scanning limits of [-90 60] degrees and elevation limits of [-9.9 0] degrees. Negative elevation angles mean that the radar points the beam from the horizon up. Mount the radar on a new platform in the scenario.

radar = helperManagedRadarDataGenerator(1, ...
    'ScanMode', 'Electronic', ...
    'UpdateRate', updateRate, ...
    'MountingLocation', [0 0 -15], ...
    'FieldOfView', [3;10], ...
    'AzimuthResolution', 1.5, ...
    'ElectronicAzimuthLimits', [-90 60], ...
    'ElectronicElevationLimits', [-9.9 0], ...
    'HasElevation', true, ...
    'DetectionCoordinates', 'Sensor spherical');
platform(scenario,'Position',[0 0 0],'Sensors',radar);

Define Tracker

After the radar detects objects, it feeds the detections to a tracker, which performs several operations. The tracker maintains a list of tracks that are estimates of target states in the area of interest. If a detection cannot be assigned to any track already maintained by the tracker, the tracker initiates a new track. In most cases, whether the new track represents a true target or false target is unclear. At first, a track is created with a tentative status. If enough detections are obtained, the track becomes confirmed. Similarly, if no detections are assigned to a track, the track is coasted (predicted without correction). If the track has a few missed updates, the tracker deletes the track.

In this example, you use a tracker that associates the detections to the tracks using a global nearest neighbor (GNN) algorithm. To track the maneuvering targets, you define a FilterInitializationFcn that initializes an Interacting Multiple Model (IMM) filter. The initMPARIMM function uses two motion models: a constant-velocity model and a constant-turn rate model. The trackingIMM (Sensor Fusion and Tracking Toolbox) filter is responsible for estimating the probability of each model, which you can access from its ModelProbabilities property. In this example, you classify a target as maneuvering when the probability of the constant-turn rate model is higher than 0.6.

tracker = trackerGNN('FilterInitializationFcn',@initMPARIMM,...
    'ConfirmationThreshold',[2 3], 'DeletionThreshold',[5 5],...
posSelector = [1 0 0 0 0 0; 0 0 1 0 0 0; 0 0 0 0 1 0];

Radar Resource Management

This section only briefly outlines the radar resource management, For more details, see the Adaptive Tracking of Maneuvering Targets with Managed Radar example.

Search Tasks

The search tasks are assigned deterministically in this example. A raster scan is used to cover the desired airspace. If no other tasks exist, the radar scans the space one angular cell at a time. The size of an angular cell is determined by the radar FieldOfView property and is bounded by the radar ElectronicAzimuthLimits and ElectronicElevatonLimits properties.

azscanspan   = diff(radar.ElectronicAzimuthLimits);
numazscan    = floor(azscanspan/radar.FieldOfView(1))+1;
azscanangles = linspace(radar.ElectronicAzimuthLimits(1),radar.ElectronicAzimuthLimits(2),numazscan)+radar.MountingAngles(1);
elscanspan   = diff(radar.ElectronicElevationLimits);
numelscan    = floor(elscanspan/radar.FieldOfView(2))+1;
elscanangles = linspace(radar.ElectronicElevationLimits(1),radar.ElectronicElevationLimits(2),numelscan)+radar.MountingAngles(2);
[elscangrid,azscangrid] = meshgrid(elscanangles,azscanangles);  
scanangles   = [azscangrid(:) elscangrid(:)].';
searchq = struct('JobType','Search','BeamDirection',num2cell(scanangles,1),...
current_search_idx = 1;

Track Tasks

Unlike search tasks, track tasks cannot be planned in advance. Instead, the resource manager creates confirmation and tracking tasks based on the changing scenario. The main difference in this example from the Adaptive Tracking of Maneuvering Targets with Managed Radar example is that the JobType for each track task can be either "TrackNonManeuvering" or "TrackManeuvering". The distinction between the two types of tracking tasks enables you to schedule tasks for each type of track at different revisit rates, making it an adaptive tracking algorithm. Similar to search tasks, tracking tasks are also managed in a job queue.

trackq = repmat(struct('JobType',[],'BeamDirection',[],'Priority',3000,'WaveformIndex',[],...
    'Time',[],'Range',[],'TrackID',[]), 10, 1);
num_trackq_items = 0;

Group search and tracking queues together in a structure for easier reference in the simulation loop.

jobq.SearchQueue  = searchq;
jobq.SearchIndex  = current_search_idx;
jobq.TrackQueue   = trackq;
jobq.NumTrackJobs = num_trackq_items;
jobq.PositionSelector = posSelector;
% Keep a reset state of jobq
resetJobQ = jobq;

Task Scheduling

In this example, for simplicity, the multifunction radar executes only one type of job within a small time period, often referred to as a dwell, but can switch tasks at the beginning of each dwell. For each dwell, the radar looks at all tasks that are due for execution and picks a confirmation or track task if their time to run has come. Otherwise, the radar picks a search task. To control the time to run tasks, you set the managerPreferences struct defined below. The highest revisit rate, which is equal to the radar update rate, is given to the confirm task to guarantee that a confirmation beam follows every new tentative track that exists. Similarly, you can control the revisit rate for non-maneuvering and maneuvering targets. In this case, you choose the values of 1Hz and 4 Hz for non-maneuvering and maneuvering targets, respectively. Because there are six targets while the radar update rate is 20, if the targets are not maneuvering the radar should spend about 70% of the time in search mode and 30% of the time in tracking mode. When new tracks are initialized and when the tracker considers that the targets are maneuvering, the resource manager allocates more tracking beams at the expense of search beams.

The Analyze Results section shows the results of other options.

managerPreferences = struct(...
    'Type', {"Search","Confirm","TrackNonManeuvering","TrackManeuvering"}, ...
    'RevisitRate', {0, updateRate, 1, 4}, ...
    'Priority', {1000, 3000, 1500, 2500});

Run the Scenario

During the simulation, the radar beam is depicted by blue or purple colors representing search and track-related beams, respectively. You can also see the distribution of tasks between search and specific tracks for the last second of simulation using the Resource Allocation in the Last Second panel at the bottom of the figure. In the top figure, you can see the history of each track and compare it to the trajectory.

% Create a radar resource allocation display
rp = uipanel('Parent',f,'Title','Resource Allocation in the Last Second','FontSize',12,...
    'BackgroundColor','white','Position',[.01 0.01 0.98 0.23]);
rax = axes(rp);

% Run the scenario
allocationType = helperAdaptiveTrackingSim(scenario, thp, rax, tracker, resetJobQ, managerPreferences);

Analyze Results

Analyze the radar task load and its division between search, confirmation, and tracking jobs. The graph below shows that during most of the time the radar allocates about 70% search jobs and 30% tracking jobs, which is as expected when the targets are not maneuvering. When the targets are maneuvering, the resource manager adapts to allocate more tracking jobs. When more tracks are maneuvering at the same time, there are more tracking jobs, as seen near the 700th time step for example. The confirmation jobs occupy very little of the radar time because the tracker is configured to confirm tracks after two detection associations in three attempts. Therefore, the confirmation or rejection of tentative tracks is swift.

numSteps = numel(allocationType);
allocationSummary = zeros(3,numSteps);
for i = 1:numSteps
    for jobType = 1:3
        allocationSummary(jobType,i) = sum(allocationType(1,max(1,i-2*updateRate+1):i)==jobType)/min(i-1,2*updateRate);
title('Radar Allocation vs. Time Step');
xlabel('Time Step');
ylabel('Fraction of step in the last two seconds in each mode');
grid on

Now you want to compare the result above with the result of Active Tracking at 1Hz track revisit rate. The following figures show the tracking results and radar allocation graph for the Active Tracking case. The tracking results show that some tracks were lost and broken, but the radar resource allocation graph shows a similar 70% search and 30% tracking task division as in the case of the Adaptive Tracking. You can obtain these results by executing the code sample below.

% Modify manager preferences to 1Hz revisit rate for both non-maneuvering and maneuvering targets
managerPreferences = struct(...
    'Type', {"Search","Confirm","TrackNonManeuvering","TrackManeuvering"}, ...
    'RevisitRate', {0, updateRate, 1, 1}, ...
    'Priority', {1000, 3000, 1500, 2500});
% Run the scenario
allocationType = helperAdaptiveTrackingSim(scenario, thp, rax, tracker, resetJobQ, managerPreferences);

Clearly, a higher tracking revisit rate is needed for Active Tracking. The two graphs below show that increasing the track revisit rate to 2Hz improves the tracking of maneuvering targets. However, the cost is that the radar dedicates more than 50% of its time to tracking tasks even when the tracks are not maneuvering. If the number of targets were greater, the radar would become overwhelmed.

The previous results show that it is enough to revisit the maneuvering targets at a rate of 2Hz. However, can Adaptive Tracking be used to reduce the revisit rate of non-maneuvering targets to 0.5Hz? The graphs below show that the tracking is still good. With this setting, the radar allocation allows for 80%-90% of the time in search mode, leaving the radar with capacity to search and track even more targets.


This example shows how to use the combination of tracking and radar resource management to adapt the revisit rate for maneuvering tracks. Adaptive tracking allows you to select revisit rate that is appropriate for each target type and maneuvering status. As a result, the radar becomes more efficient and can track a larger number of maneuvering targets.


[1] Alexander Charlish, Folker Hoffmann, Christoph Degen, and Isabel Schlangen, "The Development from Adaptive to Cognitive Radar Resource Management", in IEEE Aerospace and Electronic Systems Magazine, vol. 35, no. 6, pp. 8-19, 1 June 2020, doi: 10.1109/MAES.2019.2957847.

Supporting Functions


Returns the radar task that is used for pointing the radar beam.

function [currentjob,jobq] = getCurrentRadarTask(jobq,current_time)

searchq   = jobq.SearchQueue;
trackq    = jobq.TrackQueue;
searchidx = jobq.SearchIndex;
num_trackq_items = jobq.NumTrackJobs;

% Update search queue index
searchqidx = mod(searchidx-1,numel(searchq))+1;

% Find the track job that is due and has the highest priority
readyidx = find([trackq(1:num_trackq_items).Time]<=current_time);
[~,maxpidx] = max([trackq(readyidx).Priority]);
taskqidx = readyidx(maxpidx);

% If the track job found has a higher priority, use that as the current job
% and increase the next search job priority since it gets postponed.
% Otherwise, the next search job due is the current job.
if ~isempty(taskqidx) % && trackq(taskqidx).Priority >= searchq(searchqidx).Priority
    currentjob = trackq(taskqidx);
    for m = taskqidx+1:num_trackq_items
        trackq(m-1) = trackq(m);
    num_trackq_items = num_trackq_items-1;
    searchq(searchqidx).Priority = searchq(searchqidx).Priority+100;
    currentjob = searchq(searchqidx);
    searchidx = searchqidx+1;


jobq.SearchQueue  = searchq;
jobq.SearchIndex  = searchidx;
jobq.TrackQueue   = trackq;
jobq.NumTrackJobs = num_trackq_items;


Runs the simulation

function allocationType = helperAdaptiveTrackingSim(scenario, thp, rax, tracker, resetJobQ, managerPreferences)
% Initialize variables
radar = scenario.Platforms{end}.Sensors{1};
updateRate = radar.UpdateRate;
resourceAllocation = nan(1,updateRate);
h = histogram(rax,resourceAllocation,'BinMethod','integers');
ylabel(h.Parent,'Num beams');
numSteps = updateRate * 185;
allocationType = nan(1,numSteps);
currentStep = 1;
% Return to a reset state of jobq
jobq = resetJobQ;

% Plotters and axes
plp = thp.Plotters(1);
dtp = thp.Plotters(3);
cvp = thp.Plotters(4);
trp = thp.Plotters(5);

% For repeatable results, set the random seed and revert it when done
s = rng(2020);
oc = onCleanup(@() rng(s));

% Main loop
while advance(scenario)
    time = scenario.SimulationTime;
    % Update ground truth display
    poses = platformPoses(scenario);
    plotPlatform(plp, reshape([poses.Position],3,[])');
    % Point the radar based on the scheduler current job
    [currentJob,jobq] = getCurrentRadarTask(jobq,time);
    currentStep = currentStep + 1;
    if currentStep > updateRate
        resourceAllocation(1:end-1) = resourceAllocation(2:updateRate);
    if strcmpi(currentJob.JobType,'Search')
        detectableTracks = zeros(0,1,'uint32');
        resourceAllocation(min([currentStep,updateRate])) = 0;
        allocationType(currentStep) = 1;
        cvp.Color = [0 0 1]; 
        detectableTracks = currentJob.TrackID;
        resourceAllocation(min([currentStep,updateRate])) = currentJob.TrackID;
        cvp.Color = [1 0 1];
        if strcmpi(currentJob.JobType,'Confirm')
            allocationType(currentStep) = 2;
            allocationType(currentStep) = 3;
    ra = resourceAllocation(~isnan(resourceAllocation));
    h.Data = ra;
    h.Parent.YLim = [0 updateRate];
    h.Parent.XTick = 0:max(ra);
    point(radar, currentJob.BeamDirection);
    plotCoverage(cvp, coverageConfig(scenario));
    % Collect detections and plot them
    detections = detect(scenario);
    if isempty(detections)
        meas = zeros(0,3);
        dets = [detections{:}];
        meassph = reshape([dets.Measurement],3,[])';
        [x,y,z] = sph2cart(deg2rad(meassph(1)),deg2rad(meassph(2)),meassph(3));
        meas = (detections{1}.MeasurementParameters.Orientation*[x;y;z]+detections{1}.MeasurementParameters.OriginPosition)';
    plotDetection(dtp, meas);
    % Track and plot tracks
    if isLocked(tracker) || ~isempty(detections)
        tracks = tracker(detections, time, detectableTracks);
        pos = getTrackPositions(tracks,jobq.PositionSelector);
    % Manage resources for next jobs
    jobq = manageResource(detections,jobq,tracker,currentJob,time,managerPreferences);


Initializes the interacting multiple model filter used by the tracker.

function imm = initMPARIMM(detection)

cvekf = initcvekf(detection);
cvekf.StateCovariance(2,2) = 1e6;
cvekf.StateCovariance(4,4) = 1e6;
cvekf.StateCovariance(6,6) = 1e6;

ctekf = initctekf(detection);
ctekf.StateCovariance(2,2) = 1e6;
ctekf.StateCovariance(4,4) = 1e6;
ctekf.StateCovariance(7,7) = 1e6;
ctekf.ProcessNoise(3,3) = 1e6; % Large noise for unknown angular acceleration

imm = trackingIMM('TrackingFilters', {cvekf;ctekf}, 'TransitionProbabilities', [0.99, 0.99]);


Manages the job queue and creates new tasks based on the tracking results.

function jobq = manageResource(detections,jobq,tracker,current_job,current_time,managerPreferences)

trackq           = jobq.TrackQueue;
num_trackq_items = jobq.NumTrackJobs;
if ~isempty(detections)
    detection = detections{1};
    detection = [];

% Execute current job
switch current_job.JobType
    case 'Search'
        % For search job, if there is a detection, establish tentative
        % track and schedule a confirmation job
        if ~isempty(detection)
            rng_est = detection.Measurement(3);
            revisit_time = current_time+1;
            allTracks = predictTracksToTime(tracker, 'all', revisit_time);
            % A search task can still find a track we already have. Define
            % a confirmation task only if it's a tentative track. There
            % could be more than one if there are false alarms.
            toConfirm = find(~[allTracks.IsConfirmed]);
            numToConfirm = numel(toConfirm);
            for i = 1:numToConfirm
                trackid = allTracks(toConfirm(i)).TrackID;
                job = revisitTrackJob(tracker, trackid, current_time, managerPreferences, 'Confirm');
                num_trackq_items = num_trackq_items+1;
                trackq(num_trackq_items) = job;

    case 'Confirm'
        % For confirm job, if the detection is confirmed, establish a track
        % and create a track job corresponding to the revisit time
        if ~isempty(detection)
            trackid = current_job.TrackID;
            job = revisitTrackJob(tracker, trackid, current_time, managerPreferences, 'TrackNonManeuvering');
            if ~isempty(job)
                num_trackq_items = num_trackq_items+1;
                trackq(num_trackq_items) = job;

    otherwise % Covers both types of track jobs
        % For track job, if there is a detection, update the track and
        % schedule a track job corresponding to the revisit time. If there
        % is no detection, predict and schedule a track job sooner so the
        % target is not lost.
        if ~isempty(detection)
            trackid = current_job.TrackID;
            job = revisitTrackJob(tracker, trackid, current_time, managerPreferences, 'TrackNonManeuvering');
            if ~isempty(job)
                num_trackq_items = num_trackq_items+1;
                trackq(num_trackq_items) = job;
            trackid = current_job.TrackID;
            job = revisitTrackJob(tracker, trackid, current_time, managerPreferences, 'TrackNonManeuvering');
            if ~isempty(job)
                num_trackq_items = num_trackq_items+1;
                trackq(num_trackq_items) = job;

jobq.TrackQueue   = trackq;
jobq.NumTrackJobs = num_trackq_items;

function job = revisitTrackJob(tracker, trackID, currentTime, managerPreferences, jobType)
types = [managerPreferences.Type];
inTypes = strcmpi(jobType,types);
revisitTime = 1/managerPreferences(inTypes).RevisitRate + currentTime;
predictedTracks = predictTracksToTime(tracker,'All',revisitTime);

% If the track is not dropped, try to revisit it
allTrackIDs = [predictedTracks.TrackID];
if any(trackID == allTrackIDs)
    mdlProbs = getTrackFilterProperties(tracker, trackID, 'ModelProbabilities');
    if mdlProbs{1}(2) > 0.6
        jobType = 'TrackManeuvering';
        inTypes = strcmpi(jobType,types);
        revisitTime = 1/managerPreferences(inTypes).RevisitRate+currentTime;
        predictedTracks = predictTracksToTime(tracker,trackID,revisitTime);
        xpred = predictedTracks.State([1 3 5]);
        xpred = predictedTracks(trackID == allTrackIDs).State([1 3 5]);   
    [phipred,thetapred,rpred] = cart2sph(xpred(1),xpred(2),xpred(3));
    job = struct('JobType',jobType,'Priority',3000,...
        'BeamDirection',rad2deg([phipred thetapred]),'WaveformIndex',1,'Time',revisitTime,...
    job = [];