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 all 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
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Dockerfile text
*.mkdn text diff=markdown
*.mdtxt text
*.mdtext text
*.txt text
*.txt text eol=lf
AUTHORS text
CHANGELOG text
CHANGES text
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,30 @@ and this project should adhere to [Semantic Versioning](https://semver.org/spec/

### Changed

- Updated `UncorEncounterModel` and dependent functions to support most unconventional models and the due regard model. Functionality requested by and partly tested by @Wh0DKnee
- Improved documentation and instructions in `RUN_uncor` script
- Check for MATLAB version in `startup_bayes` and mapping toolbox in `UncorEncounterModel` constructor. These updates are in response to when creating geodetic tracks with `UncorEncounterModel/track`, as `readgeoraster` is called as within `msl2agl`. `readgeoraster` was introduced as part of the MATLAB mapping toolbox in 2020a.
- Style fixes for entire repository using [MH Style from MISS_HIT](https://misshit.org/). Specifically used the following code in the root directory for this repository: `mh_style . --fix`
- Updated some variable names and replaced use of `obj` with `self` in objects to align better with the [PEP-8 style guide](https://www.python.org/dev/peps/pep-0008). Due to other repositories' dependency on `em-model-manned-bayes`, it is nontrivial to fully update `em-model-manned-bayes` to be analogously PEP-8 compliant.
- Updated and streamlined unconventional model parameter files based on MIT Lincoln Laboratory Report ATC-348
- Updated and renamed due regard model parameter file to use naming convention as the parameter files and based on MIT Lincoln Laboratory Report ATC-397
- Update .gitattributes to enforce `eol=LF` for .txt files

### Removed

- Property validation functions no longer use `mustBeVector`, which was introduced in 2020b and limits tech transfer
- Removed unconventional models with v1p2 suffix

### 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 altitude layer, L, as a variable
- Fixed bug in `UncorEncounterModel` when dof.mat from em-core did not exist by checking if dot.mat actually exists
- Update `EncounterModel` getters for cutpoints_transition and bounds_transition to not assume a specific order of variables. Getters now create returned valued based on the model's label_initial
- README now instructs user to run startup script, `startup_bayes`. Bug first identified by @lydiaZeleke

## [2.1.0] - 2021-10-01

### Added
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Each manned aircraft model is a set of Bayesian Networks, a representation of a
- [Persistent System Environment Variable](#persistent-system-environment-variable)
- [em-core](#em-core)
- [Run Order](#run-order)
- [Startup script](#startup-script)
- [Classes (Object-Oriented Programming)](#classes-object-oriented-programming)
- [Run Scripts](#run-scripts)
- [Datafiles and Documentation](#datafiles-and-documentation)
Expand Down Expand Up @@ -75,6 +76,10 @@ Clone [`em-core`](https://github.com/Airspace-Encounter-Models/em-core). Confirm

This section describes main object-oriented programming (OOP) classes and example run scripts.

### Startup script

When first launching MATLAB, please run the startup script, `start_bayes`, to set the path and other configurations.

### Classes (Object-Oriented Programming)

The latest release introduces object oriented programming with the addition of the `EncounterModel` superclass, `UncorEncounterModel` class, and `CorTerminalModel` class. These classes enable the user to easily read in the ASCII parameter files with improved input handling. Classes are not yet available for the RADES-based correlated, ETMS-based due regard, DFDR-based HAA, and most unconventional models.
Expand Down Expand Up @@ -169,7 +174,7 @@ This model includes only one Bayesian network.

Filename | Model | Description (Version) | Altitude Scope
:--- | :--- | :--- | :---:
[dueregard-v1p0.txt](./model/dueregard-v1p0.txt) | [due regard](https://github.com/Airspace-Encounter-Models/em-overview/blob/master/README.md#manned-due-regard) | Aircraft participating in the ETMS (v1.0) | (0, Inf]
[dueregard_v1.txt](./model/dueregard_v1.txt) | [due regard](https://github.com/Airspace-Encounter-Models/em-overview/blob/master/README.md#manned-due-regard) | Aircraft participating in the ETMS (v1.0) | (0, Inf]

### DFDR-Based Helicopter Air Ambulance Model

Expand Down
6 changes: 4 additions & 2 deletions code/matlab/@EncounterModel/EncounterModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,13 @@ function preallocNInitial(self)
end

function value = get.cutpoints_transition(self)
value = [self.cutpoints_initial, self.cutpoints_initial(end - 2:end)];
is_dot = contains(self.labels_initial,"\dot");
value = [self.cutpoints_initial, self.cutpoints_initial(is_dot)];
end

function value = get.bounds_transition(self)
value = [self.bounds_initial; self.bounds_initial(end - 2:end, :)]; % repeat bounds for last 3 vars in initial
is_dot = contains(self.labels_initial,"\dot");
value = [self.bounds_initial; self.bounds_initial(is_dot, :)]; % repeat bounds for last 3 vars in initial
end

function value = get.dediscretize_parameters(self)
Expand Down
62 changes: 49 additions & 13 deletions code/matlab/@UncorEncounterModel/UncorEncounterModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@
parameters_filename = p.Results.parameters_filename;
end

%
if contains(parameters_filename,["balloon_v1.txt", "weatherballoon_v1.txt"])
warning('dynvar:mdl',...
'Unconventional balloon models do not have dynamic variables for acceleration and turn rate. Models will not work with sample and track class methods.');
end

switch p.Results.input_type
case 'traditional-opensky'
labels_initial = {'G' 'A' 'L' 'v' '\dot v' '\dot h' '\dot \psi'};
Expand Down Expand Up @@ -224,6 +228,11 @@
idxDH = find(strcmp(self.labels_initial, '"\dot h"'));
idxDPsi = find(strcmp(self.labels_initial, '"\dot \psi"'));

if isempty(idxDV) | isempty(idxDH) | isempty(idxDPsi)
error('dynvar:empty',...
'Model does not have a dynamic variable for either acceleration, vertical rate, or turn rate');
end

% Convert object to struct
% There is a class method to do this, as casting via struct(obj)
% should be avoided (ref MATLAB documentation)
Expand Down Expand Up @@ -278,7 +287,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 @@ -362,6 +372,9 @@
% Preallocate output
out_results = cell(nSamples, 1);

% Default parsed digitial obstacle file (DOF) from em-core
file_dof = [getenv('AEM_DIR_CORE') filesep 'output' filesep 'dof.mat'];

% Initial 2D position
% Vertical axis set in for loop based on model sample
n_ft = 0;
Expand All @@ -383,6 +396,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 Expand Up @@ -460,31 +480,47 @@
case 'neu'
out_results{ii} = TT;
case 'geodetic'


% Check if dof.mat exists
is_file_dof = exist(file_dof,'file');
if is_file_dof == 0
warning('track:nodoffile',...
'Parsed digitial obstacle file (DOF) not found check em-core to generate dof.mat')
end

% Filter DOF based on bounding box of anchor points
if any(strcmpi(p.UsingDefaults, 'Tdof'))
if any(strcmpi(p.UsingDefaults, 'Tdof')) && is_file_dof
[latc_deg, lonc_deg] = scircle1(lat0_deg, lon0_deg, 182283, [], wgs84Ellipsoid('ft'));
bbox = [min(lonc_deg), min(latc_deg); max(lonc_deg), max(latc_deg)];
[~, Tdof] = gridDOF('inFile', [getenv('AEM_DIR_CORE') filesep 'output' filesep 'dof.mat'], ...
[~, Tdof] = gridDOF('inFile', file_dof, ...
'BoundingBox_wgs84', bbox, ...
'minHeight_ft', dofMinHeight_ft, ...
'isVerified', true, ...
'obsTypes', dofTypes);
else
Tdof = p.Results.Tdof;
end

% Create obstacles polygons
spheroid_ft = wgs84Ellipsoid('ft');
[lat_obstacle_deg, lon_obstacle_deg] = scircle1(Tdof.lat_deg, Tdof.lon_deg, repmat(dofMaxRange_ft, size(Tdof, 1), 1), [], spheroid_ft, 'degrees', 20);
alt_obstacle_ft_agl = Tdof.alt_ft_agl + dofMaxVert_ft;

% Translate to geodetic using placeTrack

% Arguments for placeTrack
args = {'labelTime', 'Time', 'labelX', 'east_ft', 'labelY', 'north_ft', 'labelZ', 'up_ft', ...
'z_agl_tol_ft', z_agl_tol_ft, 'z_units', 'agl', ...
'latObstacle', lat_obstacle_deg, 'lonObstacle', lon_obstacle_deg, 'altObstacle_ft_agl', alt_obstacle_ft_agl, ...
'Z_m', Z_m, 'refvec', refvec, 'R', R, ...
'isPlot', isPlot, 'seed', seed};

% Create obstacles polygons
% Add obstacles to args for placeTrack
if ~isempty(Tdof)
spheroid_ft = wgs84Ellipsoid('ft');
[lat_obstacle_deg, lon_obstacle_deg] = scircle1(Tdof.lat_deg, Tdof.lon_deg, repmat(dofMaxRange_ft, size(Tdof, 1), 1), [], spheroid_ft, 'degrees', 20);
alt_obstacle_ft_agl = Tdof.alt_ft_agl + dofMaxVert_ft;

args = [args, ...
{'latObstacle'}, {lat_obstacle_deg},...
{'lonObstacle'}, {lon_obstacle_deg},...
{'altObstacle_ft_agl'}, {alt_obstacle_ft_agl}];
end

% Translate to geodetic using placeTrack
outTrack = placeTrack(TT, lat0_deg, lon0_deg, args{:});

% Assign
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
42 changes: 33 additions & 9 deletions code/matlab/RUN_uncor.m
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
% Copyright 2008 - 2021, MIT Lincoln Laboratory
% SPDX-License-Identifier: BSD-2-Clause

%% Instruction
% This script demonstrates how to use the UncorEncounterModel class to
% draw samples from an uncorrelated encounter model and
% generate independent aircraft tracks by rejection sampling the model.
% The default model parameter file is for a conventional aircraft. If a
% different model is used, the start distribution needs to be updated.

%% Startup script
file_startup = [getenv('AEM_DIR_BAYES') filesep 'startup_bayes.m'];
run(file_startup);

%% Inputs
% ASCII model parameter file
parameters_filename = [getenv('AEM_DIR_BAYES') filesep 'model' filesep 'uncor_1200exclude_fwse_v1p2.txt'];
parameters_filename = [getenv('AEM_DIR_BAYES') filesep 'model' filesep 'uncor_1200only_fwse_v1p2.txt'];

% Number of sample / tracks
n_samples = 1;
Expand All @@ -13,14 +25,25 @@
% Random seed
init_seed = 1;

% Start distribution
start = cell(7, 1);
start{1} = 1;
start{2} = 4;
start{3} = 2;

%% Instantiate object
mdl = UncorEncounterModel('parameters_filename', parameters_filename);

%% Set start distribution
% Preallocate new start distribution local to workspace
% The size of the variable corresponds to the number of
% variables in the initial network
start = cell(mdl.n_initial, 1);

% Force the model to sample from specific variable bins
% These values are for the uncorrelated conventional aircraft models. If
% using an unconventional model (i.e. gliders) or the due regard model,
% update or comment out accordingly. Also comment out if you don't want to
% define a start distribution.
start{1} = 1; % Geographic domain - CONUS (G = 1)
start{2} = 4; % Airspace class - Other airspace (A = 4)
start{3} = 2; % Altitude layer - [500, 1200) feet AGL (L = 2)

% Update the object with the start distribution
mdl.start = start;

%% Demonstrate how to generate samples
Expand All @@ -34,13 +57,14 @@
% lat0_deg = 44.25889; lon0_deg = -71.31887; % Lake of the Clouds, White Mountains, NH
% lat0_deg = 40.01031; lon0_deg = -105.22097; % Flatirons Golf Course, Boulder, CO
% lat0_deg = 46.96983; lon0_deg = -101.54661; % Bison Wind Project, ND
lat0_deg = 42.29959;
lon0_deg = -71.22220; % Exit 35C on I95, Massachusetts
lat0_deg = 42.29959; lon0_deg = -71.22220; % Exit 35C on I95, Massachusetts

% Geodetic track maintaining at least 2000 feet laterally from a point obstacle
out_results_geo2000 = mdl.track(n_samples, sample_time, 'initialSeed', init_seed, 'coordSys', 'geodetic', ...
'lat0_deg', lat0_deg, 'lon0_deg', lon0_deg, ...
'dofMaxRange_ft', 2000, 'isPlot', true);

% Geodetic track maintaining at least 500 feet laterally from a point obstacle
out_results_geo500 = mdl.track(n_samples, sample_time, 'initialSeed', init_seed, 'coordSys', 'geodetic', ...
'lat0_deg', lat0_deg, 'lon0_deg', lon0_deg, ...
'dofMaxRange_ft', 500, 'isPlot', true);
Loading