Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UncorEncounterModel class support for unconventional and due regard models #33

Merged
merged 5 commits into from
Feb 5, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ and this project should adhere to [Semantic Versioning](https://semver.org/spec/

### Fixed

- Updates `dbn_sample` to use previous implementation if a dynamic variable depends on another dynamic variable. In release [1.4.0] `dbn_sample` was updated to calculate the index, `j`, upfront because `asub2ind` can introduce unwanted overhead and also preallocated events as a NaN array. In this previous release, the `for ii = order_transition` loop was added to identify the relationship between dynamic variables and its parents. Notably in the for `ii = order_transition` loop, the variable `x` was not updated. Now this is where the bug was introduced. If a dynamic variable was dependent on another dynamic variable (see unconventional glider model), `xj = x(parents)` would be equal for the element with the dynamic variable dependence. This would results in `asub2ind(rj, xj)` returning a negative value, which would create an error when indexing `N_transition{ii}(:, j(ii))`. Since the uncorrelated conventional models transition networks do not have any dynamic variables not dependent on another dynamic variable, this bug was not identified in release [1.4.0]. For this release, the bug was addressed by determining if any of the dynamic variables depend on another dynamic variable. This determines if we can calculate the index, `j`, upfront or via each iterate of `t`. If there is a dependence, it will sample the model similar to Release [1.3.0] where the events matrix was also preallocated as an empty array
- Update `UncorEncounterModel/getDynamicLimits` to check that variable indices (i.e. `idx_G`, `idx_A`, etc.) are not empty. Currently this check will only pass for model structures as the uncorrelated conventional aircraft models; the unconventional models currently all lack geographic domain (G) and will not pass this check. Without this check an error would throw when trying to use a logical operator on an empty variable.
- Update how `UncorEncounterModel/sample` calculates the order of variables when reorganizing the controls matrix. The model's temporal matrix is used explicitly instead of trying to infer the order from the controls matrix
- Update `UncorEncounterModel/sample` to ensure that the altitude minimum (`min_alt_ft`) and maximum (`max_alt_ft`) are not empty. They can be empty if the model structure does not have boundaries defined for altitude layer, L

## [2.1.0] - 2021-10-01

### Added
Expand Down
10 changes: 9 additions & 1 deletion code/matlab/@UncorEncounterModel/UncorEncounterModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@

% Reorder to [t dh dpsi dv] to order that EncounterModelEvents (EME) expects
% As of July 2021, for the uncorrelated models, idxEME = [3 4 2]
idxEME = [idxDH, idxDPsi, idxDV] - size(controls, 2) + 1;
% idxEME = [idxDH, idxDPsi, idxDV] - size(controls, 2) + 1;
idxEME = [find(s.temporal_map(:, 1) == idxDH), find(s.temporal_map(:, 1) == idxDPsi), find(s.temporal_map(:, 1) == idxDV)] + 1;
controls = controls(:, [1 idxEME]);

% Convert to units used by EncounterModelEvents
Expand Down Expand Up @@ -383,6 +384,13 @@
min_alt_ft = min(self.boundaries{idx_L}); % ft
max_alt_ft = max(self.boundaries{idx_L}); % ft

if isempty(min_alt_ft)
min_alt_ft = 0;
end
if isempty(max_alt_ft)
max_alt_ft = inf;
end

% Other dynamic thresholds
min_DH_ft_s = min(self.boundaries{idx_DH}) / 60; % hdot: ft/min -> ft/s
max_DH_ft_s = max(self.boundaries{idx_DH}) / 60; % hdot: ft/min -> ft/s
Expand Down
5 changes: 4 additions & 1 deletion code/matlab/@UncorEncounterModel/getDynamicLimits.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
prct_low = 1;
prct_high = 99;

% Logical if model has all expected variable indices
is_idx = ~any(isempty(idx_G) | isempty(idx_A) | isempty(idx_L) | isempty(idx_V) | isempty(idx_DH));

%% Do something if variables are in expected order
if idx_G == 1 && idx_A == 2 && idx_L == 3 && idx_V == 4 && idx_DH == 6
if is_idx && (idx_G == 1 && idx_A == 2 && idx_L == 3 && idx_V == 4 && idx_DH == 6)

% Geographic domain
if is_discretized(idx_G)
Expand Down
161 changes: 103 additions & 58 deletions code/matlab/dbn_sample.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@
dynamic_variables = temporal_map(:, 2);
x = [initial zeros(1, numel(dynamic_variables))];

% events
events = nan(n_initial * t_max, numel(dynamic_variables));
counter = 0;

% Time between different events
delta_t = 0;

Expand All @@ -53,69 +49,118 @@
ia = ia';
end

%% Calculate index for variables
% We calculate index, j, upfront because asub2ind() can
% introduce unwanted overhead
j = nan(size(order_transition));
% N_trans_dyn = cell(size(order_transition));
% dirichlet_trans_dyn = cell(size(order_transition));

s = cell(size(order_transition));
sthres = zeros(t_max, numel(order_transition));

for ii = order_transition
if any(ii == dynamic_variables)
% only dynamic variables change
parents = G_transition(:, ii);
if any(parents)
rj = r_transition(parents);
xj = x(parents);
j(ii) = asub2ind(rj, xj);
else
j(ii) = 1;
end
% Determine if any of the dynamic variables depend on another dynamic
% variable. This determines if we can calculate the index, j, upfront
% or via each iterate of t
is_dynvar_depend = any(G_transition(dynamic_variables, dynamic_variables), 'all');

% Parse N_transition and dirichlet_transition for each dyn variable
N_trans_dyn = N_transition{ii}(:, j(ii));
dirichlet_trans_dyn = dirichlet_transition{ii}(:, j(ii));
% events
if is_dynvar_depend
events = [];
else
events = nan(n_initial * t_max, numel(dynamic_variables));
end

% Combine transition and dirichlet for weights
weights = N_trans_dyn + dirichlet_trans_dyn;
%% Iterate
if is_dynvar_depend
for t = 2:t_max
delta_t = delta_t + 1;
x_old = x;
for i = order_transition
if any(i == dynamic_variables)
% only dynamic variables change
parents = G_transition(:, i);
j = 1;
if any(parents)
j = asub2ind(r_transition(parents), x(parents));
end
x(i) = select_random(N_transition{i}(:, j) + dirichlet_transition{i}(:, j));
end
end

% Calculate cumsum of weights
s{ii} = cumsum(weights);
% map back
x(temporal_map(:, 1)) = x(temporal_map(:, 2));

% Calculate random threshold
sthres(:, ii) = s{ii}(end) * rand(t_max, 1);
if any(x(1:n_initial) ~= x_old(1:n_initial))
% change (i.e. new event)
for i = 1:n_initial
if x(i) ~= x_old(i)
events = [events; delta_t i x(i)];
delta_t = 0;
end
end
end
end
end

%% Iterate
for t = 2:t_max
delta_t = delta_t + 1;
x_old = x;

% Iterate over dynamic variables to select random index
for ii = ia
x(ii) = find(s{ii} >= sthres(t, ii), 1, 'first');
% x(i) = select_random(weights);
else

% Calculate index for variables
% We calculate index, j, upfront because asub2ind() can
% introduce unwanted overhead
j = nan(size(order_transition));
% N_trans_dyn = cell(size(order_transition));
% dirichlet_trans_dyn = cell(size(order_transition));

s = cell(size(order_transition));
sthres = zeros(t_max, numel(order_transition));

% Counter for events matrix
counter = 0;

for ii = order_transition
if any(ii == dynamic_variables)
% only dynamic variables change
parents = G_transition(:, ii);
if any(parents)
rj = r_transition(parents);
xj = x(parents);
j(ii) = asub2ind(rj, xj);
else
j(ii) = 1;
end

% Parse N_transition and dirichlet_transition for each dyn variable
N_trans_dyn = N_transition{ii}(:, j(ii));
dirichlet_trans_dyn = dirichlet_transition{ii}(:, j(ii));

% Combine transition and dirichlet for weights
weights = N_trans_dyn + dirichlet_trans_dyn;

% Calculate cumsum of weights
s{ii} = cumsum(weights);

% Calculate random threshold
sthres(:, ii) = s{ii}(end) * rand(t_max, 1);
end
end

% map back
x(temporal_map(:, 1)) = x(temporal_map(:, 2));

if any(x(1:n_initial) ~= x_old(1:n_initial))
% change (i.e. new event)
for ii = 1:n_initial
if x(ii) ~= x_old(ii)
counter = counter + 1;
events(counter, :) = [delta_t, ii, x(ii)];
% events = [events; delta_t, i, x(i)]; % Deprecated
delta_t = 0;
% Iterate
for t = 2:t_max
delta_t = delta_t + 1;
x_old = x;

% Iterate over dynamic variables to select random index
for ii = ia
x(ii) = find(s{ii} >= sthres(t, ii), 1, 'first');
% x(i) = select_random(weights);
end

% map back
x(temporal_map(:, 1)) = x(temporal_map(:, 2));

if any(x(1:n_initial) ~= x_old(1:n_initial))
% change (i.e. new event)
for ii = 1:n_initial
if x(ii) ~= x_old(ii)
counter = counter + 1;
events(counter, :) = [delta_t, ii, x(ii)];
% events = [events; delta_t, i, x(i)]; % Deprecated
delta_t = 0;
end
end
end
end
end

% Remove unused prelloacate rows
events = events(1:counter, :);
% Remove unused prelloacate rows
events = events(1:counter, :);
end