From 4f776e8f8aba6e75e928368fda652888e29493eb Mon Sep 17 00:00:00 2001 From: Thomas Lambert <t.lambert@uliege.be> Date: Sun, 24 Apr 2022 08:27:16 +0200 Subject: [PATCH] feat!: major improvement of uiuccleaner This expand the scope of the function to convert any entry format into an other output format. --- +af_tools/+utils/lednicer.m | 20 +++ +af_tools/+utils/selig.m | 15 ++ +af_tools/formatairfoilcoord.m | 255 ++++++++++++++++++++++++++++++++ +af_tools/uiuccleaner.m | 196 ------------------------ CHANGELOG.md | 3 + README.md | 63 +++++--- tests/test_formatairfoilcoord.m | 222 +++++++++++++++++++++++++++ tests/test_uiuccleaner.m | 195 ------------------------ 8 files changed, 554 insertions(+), 415 deletions(-) create mode 100644 +af_tools/+utils/lednicer.m create mode 100644 +af_tools/+utils/selig.m create mode 100644 +af_tools/formatairfoilcoord.m delete mode 100644 +af_tools/uiuccleaner.m create mode 100644 tests/test_formatairfoilcoord.m delete mode 100644 tests/test_uiuccleaner.m diff --git a/+af_tools/+utils/lednicer.m b/+af_tools/+utils/lednicer.m new file mode 100644 index 0000000..8eecbf5 --- /dev/null +++ b/+af_tools/+utils/lednicer.m @@ -0,0 +1,20 @@ +function [coord, array] = lednicer(upper, lower) + % Lednicer Output airofil coordinates in the Lednicer format + % ----- + % (c) Copyright 2022 University of Liege + % Author: Thomas Lambert <t.lambert@uliege.be> + % ULiege - Aeroelasticity and Experimental Aerodynamics + % Apache 2.0 License + % https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox + + % ---------------------------------------------------------------------------------------------- + + coord = [upper; lower]; + + nPts = size(upper, 1); + array = num2str([nPts, nPts; coord], '%0.7f '); + sep = blanks(size(array, 2)); + + array = [array(1:size(upper, 1), :); sep; array(size(upper, 1) + 1:end, :)]; + +end diff --git a/+af_tools/+utils/selig.m b/+af_tools/+utils/selig.m new file mode 100644 index 0000000..f776cbf --- /dev/null +++ b/+af_tools/+utils/selig.m @@ -0,0 +1,15 @@ +function [coord, array] = selig(upper, lower) + % SELIG Output airofil coordinates in the Selig format + % ----- + % (c) Copyright 2022 University of Liege + % Author: Thomas Lambert <t.lambert@uliege.be> + % ULiege - Aeroelasticity and Experimental Aerodynamics + % Apache 2.0 License + % https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox + + % ---------------------------------------------------------------------------------------------- + + coord = [flipud(upper); lower(2:end, :)]; + array = num2str(coord, '%0.7f '); + +end diff --git a/+af_tools/formatairfoilcoord.m b/+af_tools/formatairfoilcoord.m new file mode 100644 index 0000000..8bd986f --- /dev/null +++ b/+af_tools/formatairfoilcoord.m @@ -0,0 +1,255 @@ +function Dat = formatairfoilcoord(varargin) + % FORMATAIRFOILCOORD Re-format airfoil coordinates. + % The UIUC airfoil database from the University of Illinois gathers the coordinates for more + % than 1600 airfoils. Two different standards are used to represent the coordinates of the + % airfoil: + % - Selig: The first line is the name of the airfoil, and each line after that is a set of + % coordinates. The coordinates are ordered from the trailing edge, along the + % upper surface to the leading edge and back around the lower surface to trailing + % edge. This format is the most common one. + % - Lednicer: The first line is the name of the airfoil, the second one is the number of + % points on each side and the lines after that are the coordinates. The + % coordinates are given separately for the upper and lower surfaces, each from + % the leading edge to the trailing edge. + % + % Many engineering tools or application (such as XFOIL) rely on inputs in the Selig format + % only. This script was therefore created to reformat original data so they can be used easily + % with other tools. + % + % Even though the _Selig_ format is the default option, the script allows also the user to + % reformat _Selig_ files in _Lednicer_ form if needed. It also comes with the option to refine + % the coordinates by adding points with a spline interpolation following the half-cosine rule + % (more points are the leading and trailing edge). If that option is selected, the airfoil + % will be defined using 50 points for each surface. + % + % Note: + % This function can be used on many files at once in order to speed-up the process. + % + % <a href="https://gitlab.uliege.be/thlamb/airfoil_toolbox">Documentation (README)</a> + % <a href="https://gitlab.uliege.be/thlamb/airfoil_toolbox/-/issues">Report an issue</a> + % ----- + % + % Usage: + % DAT = FORMATAIRFOILCOORD prompts the user for all inputs, then converts the coordinates to + % the Selig format. + % + % DAT = FORMATAIRFOILCOORD(INPUTDIR) converts all dat files found in INPUTDIR. + % + % DAT = FORMATAIRFOILCOORD(INPUTDIR, INPUTFILES) converts only the polars specified by + % INPUTFILES found in INPUTDIR. + % + % DAT = FORMATAIRFOILCOORD(..., 'autosave', true) saves the resulting coordinates in a new + % dat-file. + % + % DAT = FORMATAIRFOILCOORD(..., 'overwrite', true) saves the resulting coordinates by + % overwriting the original dat-file. If autosave is not specified but overwrite is true, the + % file will be overwritten anyway. If autosave is set to false and overwrite is true, the file + % will not be saved at all. + % + % DAT = FORMATAIRFOILCOORD(..., 'refine', true) refines the input data by using a spline + % interpolation with 100 coordinates in total, spaced following the half-cosine rule. + % False by default. + % + % DAT = FORMATAIRFOILCOORD(..., 'outputFormat', 'Lednicer') select the output format (Selig or + % Lednicer). Selig by default. + % + % Inputs: + % inputDir : Path of the directory with the UIUC oridinal dat-files + % inputFiles : Files to select (ex: '*' (default), '*0012*', {'file1','file2'}, etc) + % + % Output: + % Dat : A structure for the results for each input file. + % - Dat.file : original filename + % - Dat.format : Format of the output coordinates + % - Dat.airfoil : airfoil name, parsed from the input file + % - Dat.nPoints : Number of coordinates + % - Dat.coord : whole airfoil coordinates, with 0 <= X <= 1 + % - Dat.upper : airfoil upper surface coordinates. + % - Dat.lower : airfoil lower surface coordinates. + % + % Example: + % FORMATAIRFOILCOORD + % FORMATAIRFOILCOORD('test_data') + % FORMATAIRFOILCOORD('test_data', '*0012*') + % FORMATAIRFOILCOORD('test_data', {'0012_re1e5', '0012_re1e6'}) + % FORMATAIRFOILCOORD('test_data', 'autosave', true) + % FORMATAIRFOILCOORD('test_data', 'overwrite', true) + % FORMATAIRFOILCOORD('test_data', 'refine', true) + % FORMATAIRFOILCOORD('test_data', 'outputFormat', 'Lednicer') + % + % See also: NACAAIRFOIL. + % + % ----- + % UIUC Airfoil Database: https://m-selig.ae.illinois.edu/ads/coord_database.html + % ----- + % (c) Copyright 2022 University of Liege + % Author: Thomas Lambert <t.lambert@uliege.be> + % ULiege - Aeroelasticity and Experimental Aerodynamics + % Apache 2.0 License + % https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox + + % ---------------------------------------------------------------------------------------------- + + narginchk(0, 8); + + % Import other functions from this package + import af_tools.utils.* + + % Constants and defaults + OPTION_LIST = {'autosave', 'overwrite', 'refine', 'outputFormat'}; + FILETYPE = '.dat'; + REFINED_SPACING = 'halfcosine'; + REFINED_NPOINTS = ceil((100 + 1) / 2); + + % Parse inputs + [allFileNames, fullpath, idxOpts] = parsefileinputs(OPTION_LIST, FILETYPE, varargin{:}); + [autosave, overwrite, refine, outputFormat] = parseoptioninputs(varargin{idxOpts:end}); + + % Convert filenames to string array + allFileNames = string(allFileNames); + + % ------------------------------------------- + % Convert all files + + % Number of files found + nbFiles = length(allFileNames); + + % Initialize structures + Dat = struct('file', [], 'airfoil', [], 'format', [], 'nPoints', [], 'coord', [], ... + 'upper', [], 'lower', []); + + for i = 1:nbFiles + + % Load file and start parsing + % No matter the file type, line 1 should always be the airfoil name + fileID = fopen(fullfile(fullpath, allFileNames{i})); + tmpAirfoil = textscan(fileID, '%s', 1, 'Delimiter', '\n:', 'HeaderLines', 0); + tmpCoord = cell2mat(textscan(fileID, '%f %f %*[^\n]', 'HeaderLines', 0)); + fclose(fileID); + clear fileID; + + % Split airfoil into its upper and lower surfaces + [Dat(i).upper, Dat(i).lower] = splitsurfaces(tmpCoord); + + % Refines coordinates by adding points if needed + [Dat(i).upper, Dat(i).lower] = refinesurfaces(Dat(i).upper, Dat(i).lower, refine, ... + REFINED_SPACING, REFINED_NPOINTS); + + % Fromats according to desired output + switch outputFormat + case 'Selig' + [Dat(i).coord, Dat(i).array] = selig(Dat(i).upper, Dat(i).lower); + case 'Lednicer' + [Dat(i).coord, Dat(i).array] = lednicer(Dat(i).upper, Dat(i).lower); + end + + % Output structure + Dat(i).path = fullpath; + Dat(i).file = allFileNames{i}; + Dat(i).airfoil = char(cellstr(tmpAirfoil{:})); + Dat(i).format = outputFormat; + + end + + % ------------------------------------------- + % Save results automatically + + if autosave + + for i = 1:length(Dat) + + filename = split(Dat(i).file, '.dat'); + if ~overwrite + filename = [filename{1}, '-', outputFormat]; + end + filename = appendextension(filename, '.dat'); + filename = fullfile(Dat(i).path, filename); + header = Dat(i).airfoil; + savetodat(filename, header, Dat(i).array); + end + end + + % Cleanup + Dat = rmfield(Dat, 'array'); + +end + +% -------------------------------------------------------------------------------------------------- +function [autosave, overwrite, refine, outputFormat] = parseoptioninputs(varargin) + % PARSEOPTIONINPUTS Parses the options and checks their validity + + import af_tools.utils.* + + % Constants and defaults + DEFAULT_AUTOSAVE = false; + DEFAULT_OVEWRITE = false; + DEFAULT_REFINE = false; + DEFAULT_FORMAT = 'Selig'; + ALLOWED_FORMATS = {'Selig', 'Lednicer'}; + + % Option validator + validLogical = @(x) validateattributes(x, {'logical'}, {'scalar'}, mfilename()); + validFormat = @(x) any(validatestring(x, ALLOWED_FORMATS, mfilename())); + + % Parse options + p = inputParser; + addParameter(p, 'autosave', DEFAULT_AUTOSAVE, validLogical); + addParameter(p, 'overwrite', DEFAULT_OVEWRITE, validLogical); + addParameter(p, 'refine', DEFAULT_REFINE, validLogical); + addParameter(p, 'outputFormat', DEFAULT_FORMAT, validFormat); + + parse(p, varargin{:}); + autosave = p.Results.autosave; + overwrite = p.Results.overwrite; + refine = p.Results.refine; + outputFormat = p.Results.outputFormat; + +end + +function [upper, lower] = splitsurfaces(data) + % SPLITSURFACES Splits the whole airfoil into its upper and lower surfaces + + % Determine format + if data(1, 1) > 1 + inputFormat = 'Lednicer'; + elseif data(1, 1) == 1 + inputFormat = 'Selig'; + else + error('MATLAB:formatairfoilcoord:unknownInputFormat', ... + ['The second line of the data file must either be 1 (Selig format) or '... + 'an integer larger than 1 (Lednicer).']); + end + + % Split lower and upper surfaces + switch inputFormat + case 'Selig' + idx = find(diff(data(:, 1)) < 0, 1, 'last'); + upper = data(1:idx + 1, :); + upper = flipud(upper); + lower = data(idx + 1:end, :); + case 'Lednicer' + nCoord = data(1, 1); + upper = data(2:nCoord + 1, :); + lower = data(nCoord + 2:end, :); + end + +end + +function [newUpper, newLower] = refinesurfaces(upper, lower, refine, spacing, nPoints) + % REFINESURFACES Refines the upper and lower surfaces by adding more points + + import af_tools.utils.spacedvector + + if refine && size(upper, 1) < nPoints + xc = fliplr(spacedvector(spacing, nPoints))'; + + newUpper(:, 1) = xc; + newLower(:, 1) = xc; + newUpper(:, 2) = interp1(upper(:, 1), upper(:, 2), xc, 'spline'); + newLower(:, 2) = interp1(lower(:, 1), lower(:, 2), xc, 'spline'); + else + newUpper = upper; + newLower = lower; + end + +end diff --git a/+af_tools/uiuccleaner.m b/+af_tools/uiuccleaner.m deleted file mode 100644 index 804b0de..0000000 --- a/+af_tools/uiuccleaner.m +++ /dev/null @@ -1,196 +0,0 @@ -function Dat = uiuccleaner(varargin) - % UIUCCLEANER Cleans airfoil data from UIUC Airfoil Database. - % The UIUC airfoil database from the University of Illinois gathers the coordinates for more - % than 1600 airfoils. However, there is a few discrepancies between the format and ordering of - % these coordinates. This script alleviates that by re-formatting any input file from the - % original database. - % - % Format: - % The output format of this script follows the "labeled coordinates" file, used originally by - % Selig on the UIUC Airfoil Database. - % The first line contains the name of the airfoil, and each line after that is a set of - % coordinates. The coordinates are ordered from the trailing edge, along the upper surface to - % the leading edge and back around the lower surface to trailing edge. - % - % Note: - % This function can be used on many files at once in order to speed-up the process. - % - % <a href="https://gitlab.uliege.be/thlamb/airfoil_toolbox">Documentation (README)</a> - % <a href="https://gitlab.uliege.be/thlamb/airfoil_toolbox/-/issues">Report an issue</a> - % ----- - % - % Usage: - % DAT = UIUCCLEANER prompts the user for all inputs, then converts the coordinates to the - % correct standard. - % - % DAT = UIUCCLEANER(INPUTDIR) converts all dat files found in INPUTDIR. - % - % DAT = UIUCCLEANER(INPUTDIR, INPUTFILES) converts only the polars specified by INPUTFILES - % found in INPUTDIR. - % - % DAT = UIUCCLEANER(..., 'autosave', true) saves the resulting coordinates in a new dat-file. - % - % DAT = UIUCCLEANER(..., 'overwrite', true) saves the resulting coordinates by overwriting the - % original dat-file. If autosave is not specified but overwrite is true, the file will be - % overwritten anyway. If autosave is set to false and overwrite is true, the file will not be - % saved at all. - % - % DAT = UIUCCLEANER(..., 'refine', true) refines the input data by using a spline - % interpolation with 100 coordinates in total, spaced following the half-cosine rule. - % False by default. - % - % Inputs: - % inputDir : Path of the directory with the UIUC oridinal dat-files - % inputFiles : Files to select (ex: '*' (default), '*0012*', {'file1','file2'}, etc) - % - % Output: - % Dat : A structure for the results for each input file. - % - Dat.file : original filename - % - Dat.airfoil : airfoil name, parsed from the input file - % - Dat.x : airfoil X coordinates, with 0 <= X <= 1 - % - Dat.y : airfoil Y coordinates. - % - % Example: - % UIUCCLEANER - % UIUCCLEANER('test_data') - % UIUCCLEANER('test_data', '*0012*') - % UIUCCLEANER('test_data', {'0012_re1e5', '0012_re1e6'}) - % UIUCCLEANER('test_data', 'autosave', true) - % UIUCCLEANER('test_data', 'overwrite', true) - % UIUCCLEANER('test_data', 'refine', true) - % - % See also: NACAAIRFOIL. - % - % ----- - % UIUC Airfoil Database: https://m-selig.ae.illinois.edu/ads/coord_database.html - % ----- - % (c) Copyright 2022 University of Liege - % Author: Thomas Lambert <t.lambert@uliege.be> - % ULiege - Aeroelasticity and Experimental Aerodynamics - % Apache 2.0 License - % https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox - - % ---------------------------------------------------------------------------------------------- - - narginchk(0, 8); - - % Import other functions from this package - import af_tools.utils.* - - % Constants and defaults - OPTION_LIST = {'autosave', 'overwrite', 'refine'}; - FILETYPE = '.dat'; - REFINED_SPACING = 'halfcosine'; - REFINED_NPOINTS = ceil((100 + 1) / 2); - - % Parse inputs - [allFileNames, fullpath, idxOpts] = parsefileinputs(OPTION_LIST, FILETYPE, varargin{:}); - [autosave, overwrite, refine] = parseoptioninputs(varargin{idxOpts:end}); - - % Convert filenames to string array - allFileNames = string(allFileNames); - - % ------------------------------------------- - % Convert all files - - % Number of files found - nbFiles = length(allFileNames); - - % Initialize structures - Dat = struct('file', [], 'airfoil', [], 'x', [], 'y', []); - - for i = 1:nbFiles - - % Load file and start parsing - % No matter the file type, line 1 should always be the airfoil name - fileID = fopen(fullfile(fullpath, allFileNames{i})); - tmpAirfoil = textscan(fileID, '%s', 1, 'Delimiter', '\n:', 'HeaderLines', 0); - resultArray = cell2mat(textscan(fileID, '%f %f %*[^\n]', 'HeaderLines', 0)); - fclose(fileID); - clear fileID; - - % Re-order file if it is needed - if resultArray(1, 1) ~= 0 && resultArray(1, 1) ~= 1 && resultArray(2, 1) == 0 - % Remove first line - resultArray = resultArray(2:end, :); - - % Find index at the end of the upper side (normally diff would be -1, - % but we use -0.5 in case the LE and TE are not precisely at 0 and 1. It - % should be the only negative diff anyway). - idx = find(diff(resultArray) < -0.5, 1); - upper = flipud(resultArray(1:idx, :)); - lower = resultArray(idx + 1:end, :); - resultArray = [upper; lower(2:end, :)]; - end - - % Refine coordinates to add more points - if refine && size(resultArray, 1) < 2 * REFINED_NPOINTS - xc = fliplr(spacedvector(REFINED_SPACING, REFINED_NPOINTS))'; - - idx = find(diff(resultArray(:, 1)) < 0, 1, 'last'); - upper = resultArray(1:idx + 1, :); - lower = resultArray(idx + 1:end, :); - - refinedUpperY = interp1(upper(:, 1), upper(:, 2), xc, 'spline'); - refinedLowerY = interp1(lower(:, 1), lower(:, 2), flipud(xc), 'spline'); - - x = [xc; flipud(xc(1:end - 1))]; - y = [refinedUpperY; refinedLowerY(2:end)]; - resultArray = [x, y]; - end - - % Output structure - Dat(i).path = fullpath; - Dat(i).file = allFileNames{i}; - Dat(i).airfoil = char(cellstr(tmpAirfoil{:})); - Dat(i).x = resultArray(:, 1); - Dat(i).y = resultArray(:, 2); - - end - - % ------------------------------------------- - % Save results automatically - - if autosave - - for i = 1:length(Dat) - - filename = split(Dat(i).file, '.dat'); - if ~overwrite - filename = [filename{1}, '-CLEAN']; - end - filename = appendextension(filename, '.dat'); - filename = fullfile(Dat(i).path, filename); - header = Dat(i).airfoil; - savetodat(filename, header, Dat(i).x, Dat(i).y); - - end - end - -end - -% -------------------------------------------------------------------------------------------------- -function [autosave, overwrite, refine] = parseoptioninputs(varargin) - % PARSEOPTIONINPUTS Parses the options and checks their validity - - import af_tools.utils.* - - % Constants and defaults - DEFAULT_AUTOSAVE = false; - DEFAULT_OVEWRITE = false; - DEFAULT_REFINE = false; - - % Option validator - validLogical = @(x) validateattributes(x, {'logical'}, {'scalar'}, mfilename()); - - % Parse options - p = inputParser; - addParameter(p, 'autosave', DEFAULT_AUTOSAVE, validLogical); - addParameter(p, 'overwrite', DEFAULT_OVEWRITE, validLogical); - addParameter(p, 'refine', DEFAULT_REFINE, validLogical); - parse(p, varargin{:}); - autosave = p.Results.autosave; - overwrite = p.Results.overwrite; - refine = p.Results.refine; - -end diff --git a/CHANGELOG.md b/CHANGELOG.md index 69e56b8..ff3f266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **BREAKING**: Renamed **uiuccleaner** in **formatairfoilcoord** +- Feat: Add possibility to select output style for **formatairfoilcoord** +- Feat: Add Selig and Lednicer utils for airfoil formatting - Style: Adopt style from MISS_HIT - Copyright notice: change main copyright holder to the University - Doc: Revamp `CONTRIBUTING.md` to put emphasis on MISS_HIT and conventions diff --git a/README.md b/README.md index 0915c8c..7e181b1 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,8 @@ af_tools.xf2mat(args) List of functions: - [xf2mat](#xf2mat): Aggregates multiple XFOIL or XFLR5 polar results into a single structure. -- [uiuccleaner](#uiuccleaner): Re-formats airfoil coordinates obtained from UIUC - Airfoil database. +- [formatairfoilcoord](#formatairfoilcoord): Re-format airfoil coordinates + datafile. - [Airfoil generation](#airfoil-generation) - [nacacamber](#nacacamber): Generates the coordinates of the camberline for a NACA 4 or 5 digits airfoil. - [nacaairfoil](#nacaairfoil): Generates the full coordinates of a NACA 4 or 5 digits airfoil. @@ -136,20 +136,34 @@ angles of attack and N is the number of input files. | `cd` | Drag coefficient | [M x N] | | `cm` | Moment coefficient | [M x N] | -### uiuccleaner - -The [UIUC airfoil database][uiuc-af-db] from the University of Illinois gathers the -coordinates for more than 1600 airfoils. However, there is a few -discrepancies between the format and ordering of these coordinates. This -scripts alleviates that by re-formatting any input file from the original -database. The main use of this function is to make the coordinates files -compliant with other existing tools, such as [XFOIL][xfoil]. - -The output format of this script follows the "labeled coordinates" file, used -originally by Selig on the UIUC Airfoil Database. The first line contains the -name of the airfoil, and each line after that is a set of coordinates. The -coordinates are ordered from the trailing edge, along the upper surface to the -leading edge and back around the lower surface to trailing edge. +### formatairfoilcoord + +The [UIUC airfoil database][uiuc-af-db] from the University of Illinois gathers +the coordinates for more than 1600 airfoils. Two different standards are used +to represent the coordinates of the airfoil: + +- _Selig_: The first line is the name of the airfoil, and each line after that + is a set of coordinates. The coordinates are ordered from the trailing edge, + along the upper surface to the leading edge and back around the lower surface + to trailing edge (_e.g._, see + [E205](https://m-selig.ae.illinois.edu/ads/coord/e205.dat)). This format is + the most common one. +- _Lednicer_: The first line is the name of the airfoil, the second one is the + number of points on each side and the lines after that are the coordinates. + The coordinates are given separately for the upper and lower surfaces, each + from the leading edge to the trailing edge (_e.g._, see [Clark + Y](https://m-selig.ae.illinois.edu/ads/coord/clarky.dat)). + +Many engineering tools or application (such as [XFOIL][xfoil]) rely on inputs in +the _Selig_ format only. This script was therefore created to reformat original +data so they can be used easily with other tools. + +Even though the _Selig_ format is the default option, the script allows also the +user to reformat _Selig_ files in _Lednicer_ form if needed. It also comes with +the option to refine the coordinates by adding points with a spline +interpolation following the half-cosine rule (more points are the leading and +trailing edge). If that option is selected, the airfoil will be defined using 50 +points for each surface. ```matlab import af_tools.xf2mat @@ -158,14 +172,15 @@ Polar = uiuccleaner(inputDir, inputFiles, 'autosave', true) Polar = uiuccleaner(inputDir, inputFiles, 'overwrite', true) ``` -| Input | Example | Default | -|------------- | ---------------------------------------------- | --------| -| `inputDir` | `'.'`, `'xf_results'` | - | -| `inputFiles` | `'*'`, `'*0012*'`, `'{'*0012_1*', '*0012_2*'}` | `'*'` | -| | | | -| 'autosave' | `false`, `true` | `false` | -| 'overwrite' | `false`, `true` | `false` | -| 'refine' | `false`, `true` | `false` | +| Input | Example | Default | +|--------------- | ---------------------------------------------- | --------| +| `inputDir` | `'.'`, `'xf_results'` | - | +| `inputFiles` | `'*'`, `'*0012*'`, `'{'*0012_1*', '*0012_2*'}` | `'*'` | +| | | | +| 'autosave' | `false`, `true` | `false` | +| 'overwrite' | `false`, `true` | `false` | +| 'refine' | `false`, `true` | `false` | +| 'outputFormat' | `Selig`, `Lednicer` | `Selig` | ### Airfoil generation diff --git a/tests/test_formatairfoilcoord.m b/tests/test_formatairfoilcoord.m new file mode 100644 index 0000000..d1660f5 --- /dev/null +++ b/tests/test_formatairfoilcoord.m @@ -0,0 +1,222 @@ +% TEST_FORMATAIRFOILCOORD Unitary tests for the formatairfoilcoord function +% Note: +% Matlab package functionality is not very test-framework friendly. +% Either the whole package has to be imported in EVERY SINGLE TEST or the +% functions must be called as <package>.function() everytime. +% The second option is preferred for the tests as it has the smaller scope. +% +% ----- +% (c) Copyright 2022 University of Liege +% Author: Thomas Lambert <t.lambert@uliege.be> +% ULiege - Aeroelasticity and Experimental Aerodynamics +% Apache 2.0 License +% https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox + +% -------------------------------------------------------------------------------------------------- + +%% Main test function + +function tests = test_formatairfoilcoord + + tests = functiontests(localfunctions); + +end + +%% Setup and teardown + +function setupOnce(testCase) + + addpath('../.'); % Add repository to Matlab Path + addpath('./test_utils'); % Add utils to Matlab Path + + % Set random number generator settings. + testCase.TestData.currentRNG = rng; + +end + +function teardownOnce(testCase) + + rmpath('../.'); % Remove repository from Matlab Path + rmpath('./test_utils'); % Remove utils from Matlab Path + + % Restore the random number generator settings. + s = testCase.TestData.currentRNG; + rng(s); + +end + +function teardown(~) + + close all; % Close all figures that would have been openend + +end + +%% Arguments and errors tests + +function test_nargin(testCase) + % Error if too many arguments + + dummy = (1:10)'; + + verifyError(testCase, ... + @() af_tools.formatairfoilcoord(dummy, dummy, dummy, ... + 'autosave', true, 'overwrite', false, 'refine', true), ... + 'MATLAB:narginchk:tooManyInputs'); + +end + +function test_invalidInputsType(testCase) + % Error if base inputs are of the wrong type + + empty = []; + scal = 0; + logi = true; + Struct = struct; + + % 1. inputDir (char vector or string) + verifyError(testCase, @() af_tools.formatairfoilcoord(empty), 'MATLAB:parsefileinputs:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(scal), 'MATLAB:parsefileinputs:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(logi), 'MATLAB:parsefileinputs:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(Struct), 'MATLAB:parsefileinputs:invalidType'); + + % 2. inputFile (char, string, cell of chars) + verifyError(testCase, @() af_tools.formatairfoilcoord(scal, empty), 'MATLAB:parsefileinputs:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(scal, scal), 'MATLAB:parsefileinputs:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(scal, logi), 'MATLAB:parsefileinputs:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(scal, Struct), 'MATLAB:parsefileinputs:invalidType'); + +end + +function test_invalidOptions(testCase) + % Error if invalid option name or missing parameter value + + wrongName = 'wrongOption'; + testdir = [pwd, '/test_utils']; + + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, '*', wrongName), ... + 'MATLAB:InputParser:ParamMissingValue'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, '*', wrongName, true), ... + 'MATLAB:InputParser:UnmatchedParameter'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'autosave', true, wrongName), ... + 'MATLAB:InputParser:ParamMissingValue'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'autosave', true, wrongName, true), ... + 'MATLAB:InputParser:UnmatchedParameter'); + +end + +function test_invalidOptionsVal(testCase) + % Error if valid option name but invalid value + + empty = []; + scal = 0; + char = 'test'; + Struct = struct; + vect = 1:10; + + testdir = [pwd, '/test_utils']; + + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'autosave', empty), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'autosave', scal), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'autosave', char), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'autosave', Struct), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'autosave', vect), ... + 'MATLAB:formatairfoilcoord:invalidType'); + + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'overwrite', empty), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'overwrite', scal), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'overwrite', char), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'overwrite', Struct), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'overwrite', vect), ... + 'MATLAB:formatairfoilcoord:invalidType'); + + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'refine', empty), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'refine', scal), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'refine', char), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'refine', Struct), ... + 'MATLAB:formatairfoilcoord:invalidType'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'refine', vect), ... + 'MATLAB:formatairfoilcoord:invalidType'); + + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'outputFormat', empty), ... + 'MATLAB:formatairfoilcoord:unrecognizedStringChoice'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'outputFormat', scal), ... + 'MATLAB:formatairfoilcoord:unrecognizedStringChoice'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'outputFormat', char), ... + 'MATLAB:formatairfoilcoord:unrecognizedStringChoice'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'outputFormat', Struct), ... + 'MATLAB:formatairfoilcoord:unrecognizedStringChoice'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'outputFormat', vect), ... + 'MATLAB:formatairfoilcoord:unrecognizedStringChoice'); + +end + +% ------------------------------------- +function test_dirNotFound(testCase) + % Error if valid inputDir not found + + verifyError(testCase, @() af_tools.formatairfoilcoord('missingdir'), 'MATLAB:parsefileinputs:dirNotFound'); + +end + +function test_fileNotFound(testCase) + % Error if valid inputDir not found + + testdir = [pwd, '/test_utils']; + + verifyWarning(testCase, @() af_tools.formatairfoilcoord(testdir, {'clarky.dat', 'testdummy.dat'}), ... + 'MATLAB:parsefileinputs:FileNotFound'); + % remove warning first warning about FileNotFound before it throws the error + warning('off', 'MATLAB:parsefileinputs:FileNotFound'); + verifyError(testCase, @() af_tools.formatairfoilcoord(testdir, 'randomFile'), ... + 'MATLAB:parsefileinputs:noFilesFound'); + warning on; % Reactivate warnings + +end + +%% Test if function returns expected outputs + +function test_correctOrder(testCase) + % Verify if the output starts and ends at X = 1; + + testdir = [pwd, '/test_utils']; + + Dat = af_tools.formatairfoilcoord(testdir, '*'); + + verifyEqual(testCase, Dat(1).coord(1, 1), 1); + verifyEqual(testCase, Dat(1).coord(end, 1), 1); + verifyEqual(testCase, Dat(2).coord(1, 1), 1); + verifyEqual(testCase, Dat(2).coord(end, 1), 1); + +end + +function test_correctRefinePoints(testCase) + % Verify if the output of refined solution has 101 points + + testdir = [pwd, '/test_utils']; + nPoints = 101; + + DatNoRefine = af_tools.formatairfoilcoord(testdir, '*'); + Dat = af_tools.formatairfoilcoord(testdir, '*', 'refine', true); + + for i = 1:length(Dat) + if length(DatNoRefine(i).coord(:, 1)) > nPoints + verifyEqual(testCase, Dat(i).coord(:, 1), DatNoRefine(i).coord(:, 1)); + verifyEqual(testCase, Dat(i).coord(:, 2), DatNoRefine(i).coord(:, 2)); + else + verifyEqual(testCase, length(Dat(i).coord(:, 1)), nPoints); + verifyEqual(testCase, length(Dat(i).coord(:, 2)), nPoints); + end + end + +end diff --git a/tests/test_uiuccleaner.m b/tests/test_uiuccleaner.m deleted file mode 100644 index 83b0534..0000000 --- a/tests/test_uiuccleaner.m +++ /dev/null @@ -1,195 +0,0 @@ -% TEST_UIUCCLEANER Unitary tests for the uiuccleaner function -% Note: -% Matlab package functionality is not very test-framework friendly. -% Either the whole package has to be imported in EVERY SINGLE TEST or the -% functions must be called as <package>.function() everytime. -% The second option is preferred for the tests as it has the smaller scope. -% -% ----- -% (c) Copyright 2022 University of Liege -% Author: Thomas Lambert <t.lambert@uliege.be> -% ULiege - Aeroelasticity and Experimental Aerodynamics -% Apache 2.0 License -% https://gitlab.uliege.be/am-dept/matlab_airfoil_toolbox - -% -------------------------------------------------------------------------------------------------- - -%% Main test function - -function tests = test_uiuccleaner - - tests = functiontests(localfunctions); - -end - -%% Setup and teardown - -function setupOnce(testCase) - - addpath('../.'); % Add repository to Matlab Path - addpath('./test_utils'); % Add utils to Matlab Path - - % Set random number generator settings. - testCase.TestData.currentRNG = rng; - -end - -function teardownOnce(testCase) - - rmpath('../.'); % Remove repository from Matlab Path - rmpath('./test_utils'); % Remove utils from Matlab Path - - % Restore the random number generator settings. - s = testCase.TestData.currentRNG; - rng(s); - -end - -function teardown(~) - - close all; % Close all figures that would have been openend - -end - -%% Arguments and errors tests - -function test_nargin(testCase) - % Error if too many arguments - - dummy = (1:10)'; - - verifyError(testCase, ... - @() af_tools.uiuccleaner(dummy, dummy, dummy, ... - 'autosave', true, 'overwrite', false, 'refine', true), ... - 'MATLAB:narginchk:tooManyInputs'); - -end - -function test_invalidInputsType(testCase) - % Error if base inputs are of the wrong type - - empty = []; - scal = 0; - logi = true; - Struct = struct; - - % 1. inputDir (char vector or string) - verifyError(testCase, @() af_tools.uiuccleaner(empty), 'MATLAB:parsefileinputs:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(scal), 'MATLAB:parsefileinputs:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(logi), 'MATLAB:parsefileinputs:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(Struct), 'MATLAB:parsefileinputs:invalidType'); - - % 2. inputFile (char, string, cell of chars) - verifyError(testCase, @() af_tools.uiuccleaner(scal, empty), 'MATLAB:parsefileinputs:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(scal, scal), 'MATLAB:parsefileinputs:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(scal, logi), 'MATLAB:parsefileinputs:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(scal, Struct), 'MATLAB:parsefileinputs:invalidType'); - -end - -function test_invalidOptions(testCase) - % Error if invalid option name or missing parameter value - - wrongName = 'wrongOption'; - testdir = [pwd, '/test_utils']; - - verifyError(testCase, @() af_tools.uiuccleaner(testdir, '*', wrongName), ... - 'MATLAB:InputParser:ParamMissingValue'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, '*', wrongName, true), ... - 'MATLAB:InputParser:UnmatchedParameter'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'autosave', true, wrongName), ... - 'MATLAB:InputParser:ParamMissingValue'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'autosave', true, wrongName, true), ... - 'MATLAB:InputParser:UnmatchedParameter'); - -end - -function test_invalidOptionsVal(testCase) - % Error if valid option name but invalid value - - empty = []; - scal = 0; - char = 'test'; - Struct = struct; - vect = 1:10; - - testdir = [pwd, '/test_utils']; - - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'autosave', empty), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'autosave', scal), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'autosave', char), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'autosave', Struct), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'autosave', vect), 'MATLAB:uiuccleaner:invalidType'); - - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'overwrite', empty), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'overwrite', scal), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'overwrite', char), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'overwrite', Struct), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'overwrite', vect), 'MATLAB:uiuccleaner:invalidType'); - - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'refine', empty), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'refine', scal), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'refine', char), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'refine', Struct), 'MATLAB:uiuccleaner:invalidType'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'refine', vect), 'MATLAB:uiuccleaner:invalidType'); - -end - -% ------------------------------------- -function test_dirNotFound(testCase) - % Error if valid inputDir not found - - verifyError(testCase, @() af_tools.uiuccleaner('missingdir'), 'MATLAB:parsefileinputs:dirNotFound'); - -end - -function test_fileNotFound(testCase) - % Error if valid inputDir not found - - testdir = [pwd, '/test_utils']; - - verifyWarning(testCase, @() af_tools.uiuccleaner(testdir, {'clarky.dat', 'testdummy.dat'}), ... - 'MATLAB:parsefileinputs:FileNotFound'); - % remove warning first warning about FileNotFound before it throws the error - warning('off', 'MATLAB:parsefileinputs:FileNotFound'); - verifyError(testCase, @() af_tools.uiuccleaner(testdir, 'randomFile'), 'MATLAB:parsefileinputs:noFilesFound'); - warning on; % Reactivate warnings - -end - -%% Test if function returns expected outputs - -function test_correctOrder(testCase) - % Verify if the output starts and ends at X = 1; - - testdir = [pwd, '/test_utils']; - - Dat = af_tools.uiuccleaner(testdir, '*'); - - verifyEqual(testCase, Dat(1).x(1), 1); - verifyEqual(testCase, Dat(1).x(end), 1); - verifyEqual(testCase, Dat(2).x(end), 1); - verifyEqual(testCase, Dat(2).x(end), 1); - -end - -function test_correctRefinePoints(testCase) - % Verify if the output of refined solution has 101 points - - testdir = [pwd, '/test_utils']; - nPoints = 101; - - DatNoRefine = af_tools.uiuccleaner(testdir, '*'); - Dat = af_tools.uiuccleaner(testdir, '*', 'refine', true); - - for i = 1:length(Dat) - if length(DatNoRefine(i).x) > nPoints - verifyEqual(testCase, Dat(i).x, DatNoRefine(i).x); - verifyEqual(testCase, Dat(i).y, DatNoRefine(i).y); - else - verifyEqual(testCase, length(Dat(i).x), nPoints); - verifyEqual(testCase, length(Dat(i).y), nPoints); - end - end - -end -- GitLab