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

Added a CLI for users to download models #122

Merged
merged 7 commits into from
Jan 31, 2024
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
43 changes: 43 additions & 0 deletions aviary/docs/user_guide/aviary_commands.ipynb
crecine marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,49 @@
"source": [
"!aviary fortran_to_aviary -h"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"(aviary-hangar-command)=\n",
"### aviary hangar\n",
"\n",
"The `aviary hangar` command will copy files and folders from the Aviary hangar to an accessible directory.\n",
"This is useful for users that have pip installed Aviary, but want to experiment with the included examples.\n",
"\n",
"The only required input is the name of an input deck.\n",
"This can be specified as the name of the file, the path from [aviary/models](https://github.com/OpenMDAO/Aviary/tree/main/aviary/models), the name of a folder in aviary/models. Multiple files and folders can be copied at once.\n",
"Optionally, the output directory can be specified; if it is not, the files will be copied into the current working directory in a folder called aviary_models.\n",
"If specified, the output directory will be created as needed.\n",
"\n",
"Example usage:\n",
"```\n",
"`aviary hangar engines` Copy all files in the engines folder\n",
"`aviary hangar turbofan_22k.txt` Copy the 22k turbofan deck\n",
"`aviary hangar N3CC/N3CC_data.py` Copy the N3CC data\n",
"`aviary hangar small_single_aisle_GwGm.dat small_single_aisle_GwGm.csv` Copy the Fortran and Aviary input decks for the small single aisle\n",
"`aviary hangar turbofan_22k.txt ~/example_files` Copy the engine model into ~/example_files\n",
"```\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```\n",
"aviary hangar -h\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!aviary hangar -h"
]
}
],
"metadata": {
Expand Down
32 changes: 4 additions & 28 deletions aviary/interface/cmd_entry_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,8 @@
from aviary.interface.methods_for_level1 import _exec_level1, _setup_level1_parser
from aviary.utils.Fortran_to_Aviary import _exec_F2A, _setup_F2A_parser
from aviary.visualization.dashboard import _dashboard_setup_parser, _dashboard_cmd
from aviary.interface.graphical_input import IntegratedPlottingApp


def _setup_flight_profile_parser(parser):
"""
Set up the command line options for the Flight Profile plotting tool.

Parameters
----------
parser : argparse.ArgumentParser
The parser instance.
"""
pass


def _exec_flight_profile(options, user_args):
"""
Run the Flight Profile plotting tool.

Parameters
----------
options : argparse.Namespace
Command line options.
user_args : list of str
Args to be passed to the user script.
"""
app = IntegratedPlottingApp()
app.mainloop()
from aviary.interface.graphical_input import _exec_flight_profile, _setup_flight_profile_parser
from aviary.interface.download_models import _exec_hangar, _setup_hangar_parser


def _load_and_exec(script_name, user_args):
Expand Down Expand Up @@ -74,6 +48,8 @@ def _load_and_exec(script_name, user_args):
"Allows users to draw a mission profile for use in Aviary."),
'dashboard': (_dashboard_setup_parser, _dashboard_cmd,
"Run the Dashboard tool"),
'hangar': (_setup_hangar_parser, _exec_hangar,
"Allows users that pip installed Aviary to download models from the Aviary hangar"),
}


Expand Down
100 changes: 100 additions & 0 deletions aviary/interface/download_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import os
from pathlib import Path
import argparse
import pkg_resources
import shutil


def aviary_resource(resource_name: str) -> str:
return pkg_resources.resource_filename("aviary", resource_name)


def get_model(file_name: str, verbose=False) -> Path:
'''
This function attempts to find the path to a file or folder in aviary/models
If the path cannot be found in any of the locations, a FileNotFoundError is raised.

Parameters
----------
path : str or Path
The input path, either as a string or a Path object.

Returns
-------
aviary_path
The absolute path to the file.

Raises
------
FileNotFoundError
If the path is not found.
'''

# Get the path to Aviary's models
path = Path('models', file_name)
aviary_path = Path(aviary_resource(str(path)))

# If the file name was provided without a path, check in the subfolders
if not aviary_path.exists():
sub_dirs = [x[0] for x in os.walk(aviary_resource('models'))]
for sub_dir in sub_dirs:
temp_path = Path(sub_dir, file_name)
if temp_path.exists():
# only return the first matching file
aviary_path = temp_path
continue

# If the path still doesn't exist, raise an error.
if not aviary_path.exists():
raise FileNotFoundError(
f"File or Folder not found in Aviary's hangar"
)
if verbose:
print('found', aviary_path, '\n')
return aviary_path


def save_file(aviary_path: Path, outdir: Path, verbose=False) -> Path:
'''
Saves the file or folder specified into the output directory, creating directories as needed.
'''
outdir.mkdir(parents=True, exist_ok=True)
if aviary_path.is_dir():
if verbose:
print(aviary_path, 'is a directory, getting all files')
outdir = outdir.joinpath(aviary_path.stem)
outdir.mkdir(exist_ok=True)
for file in next(os.walk(aviary_path))[-1]:
if verbose:
print('copying', str(aviary_path / file), 'to', str(outdir / file))
shutil.copy2(aviary_path / file, outdir)
else:
if verbose:
print('copying', str(aviary_path), 'to', str(outdir / aviary_path.name))
shutil.copy2(aviary_path, outdir)
return outdir


def _setup_hangar_parser(parser: argparse.ArgumentParser):
def_outdir = os.path.join(os.getcwd(), "aviary_models")
parser.add_argument(
'input_decks', metavar='indecks', type=str, nargs='+', help='Name of file or folder to download from Aviary/models'
)
parser.add_argument(
"-o", "--outdir", default=def_outdir, help="Directory to write outputs. Defaults to aviary_models in the current directory."
)
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="Enable verbose outputs",
)


def _exec_hangar(args, user_args):
# check if args.input_deck is a list, if so, use the first element
input_decks = []
for input_deck in args.input_decks:
input_decks.append(get_model(input_deck, args.verbose))

for input_deck in input_decks:
save_file(input_deck, Path(args.outdir), args.verbose)
27 changes: 27 additions & 0 deletions aviary/interface/graphical_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,33 @@ def update_plot(self, ax, data, label, color):
ax.figure.canvas.draw()


def _setup_flight_profile_parser(parser):
"""
Set up the command line options for the Flight Profile plotting tool.

Parameters
----------
parser : argparse.ArgumentParser
The parser instance.
"""
pass


def _exec_flight_profile(options, user_args):
"""
Run the Flight Profile plotting tool.

Parameters
----------
options : argparse.Namespace
Command line options.
user_args : list of str
Args to be passed to the user script.
"""
app = IntegratedPlottingApp()
app.mainloop()


if __name__ == "__main__":
app = IntegratedPlottingApp()
app.mainloop()
66 changes: 66 additions & 0 deletions aviary/interface/test/test_download_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import subprocess
import unittest
from pathlib import Path
import shutil

import pkg_resources
from openmdao.utils.testing_utils import use_tempdirs
from aviary.interface.download_models import get_model, save_file


@use_tempdirs
class CommandEntryPointsTestCases(unittest.TestCase):

def run_and_test_hangar(self, filenames, out_dir=''):
# tests that the commands return an exit code of 0 and that the files are generated
if isinstance(filenames, str):
filenames = [filenames]
cmd = ['aviary', 'hangar'] + filenames

if out_dir:
cmd += ['-o', out_dir]
out_dir = Path(out_dir)
else:
out_dir = Path.cwd() / 'aviary_models'

try:
output = subprocess.check_output(cmd)
for filename in filenames:
path = out_dir / filename.split('/')[-1]
self.assertTrue(path.exists())
except subprocess.CalledProcessError as err:
self.fail(f"Command '{cmd}' failed. Return code: {err.returncode}")

def test_single_file_without_path(self):
filename = 'turbofan_22k.deck'
self.run_and_test_hangar(filename)

def test_single_file_with_path(self):
filename = 'engines/turbofan_22k.deck'
self.run_and_test_hangar(filename)

def test_multiple_files(self):
filenames = ['small_single_aisle_GwGm.dat', 'small_single_aisle_GwGm.csv']
self.run_and_test_hangar(filenames)

def test_folder(self):
filename = 'engines'
self.run_and_test_hangar(filename)

def test_single_file_custom_outdir(self):
filename = 'small_single_aisle_GwGm.csv'
out_dir = '~/test_hangar'
self.run_and_test_hangar(filename, out_dir)
shutil.rmtree(out_dir)

def test_expected_path(self):
filename = f'test_aircraft/converter_configuration_test_data_GwGm.dat'
aviary_path = get_model('converter_configuration_test_data_GwGm.dat')

expected_path = pkg_resources.resource_filename('aviary',
'models/test_aircraft/converter_configuration_test_data_GwGm.dat')
self.assertTrue(str(aviary_path) == expected_path)


if __name__ == "__main__":
unittest.main()
Loading