diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a5f4cc007..cb0597e80 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.9.2-dev +current_version = 0.9.3-dev commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b86321f76..e3fa89790 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -21,7 +21,7 @@ body: attributes: label: Aviary Version description: What version of Aviary is being used. - placeholder: "0.9.2-dev" + placeholder: "0.9.3-dev" validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5b2a47efe..aa6e97d44 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - name: Discussion Board - url: https://github.com/OpenMDAO/om-Aviary/discussions + url: https://github.com/OpenMDAO/Aviary/discussions about: "If you have questions or need support, please check the discussion board and create a \"New Discussion\" if necessary." diff --git a/.github/install_env_from_github.sh b/.github/install_env_from_github.sh index fb5eb9cf9..a87cd103f 100755 --- a/.github/install_env_from_github.sh +++ b/.github/install_env_from_github.sh @@ -49,7 +49,7 @@ fi # pyoptsparse_line=$(grep ' pyoptsparse' $input_yaml | sed 's/^ //') # Remove specified packages and write to an intermediate file -grep -v -e 'aviary' -e 'om-aviary' -e 'build-pyoptsparse' -e 'pyoptsparse' -e 'boring-battery' -e 'networkx' -e ' - pip:' $input_yaml > $intermediate_yaml +grep -v -e 'aviary' -e 'om-aviary' -e 'build-pyoptsparse' -e 'pyoptsparse' -e 'networkx' -e ' - pip:' $input_yaml > $intermediate_yaml # Check for 'dev' versions of OpenMDAO and Dymos if grep -q -e 'openmdao.*dev' $intermediate_yaml; then @@ -99,8 +99,6 @@ conda activate $env_name # Check flags and install special packages if needed pip install -r $requirements_txt -pip install "boring_battery @ git+https://github.com/jcchin/boring.git" - if [ "$install_openmdao_from_git" = true ]; then pip install git+https://github.com/OpenMDAO/OpenMDAO.git fi diff --git a/.github/workflows/notify_teams.yml b/.github/workflows/notify_teams.yml new file mode 100644 index 000000000..235d0e915 --- /dev/null +++ b/.github/workflows/notify_teams.yml @@ -0,0 +1,112 @@ +name: Notify Teams + +on: + pull_request_target: + branches: [main] + types: [opened, reopened, assigned, closed] + +env: + SMTP_SERVER: smtp.gmail.com + SMTP_PORT: 465 + EMAIL_USERNAME: ${{ secrets.EMAIL_USERNAME }} + EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }} + EMAIL_FROM: GitHub Actions + EMAIL_TO: ${{ secrets.DESTINATION_EMAIL }} + # Optional priority: 'high', 'normal' (default) or 'low' + +jobs: + send_email_on_opened_pr: + if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && (github.event.action == 'opened' || github.event.action == 'reopened') + runs-on: ubuntu-latest + steps: + - name: Get PR Data + run: | + echo "action=${{ github.event.action }}" >> $GITHUB_ENV + echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_ENV + echo "user=${{ github.event.pull_request.user.login }}" >> $GITHUB_ENV + echo "title=${{ github.event.pull_request.title }}" >> $GITHUB_ENV + echo "url=${{ github.event.pull_request.html_url }}" >> $GITHUB_ENV + echo "assignees=${{ join(github.event.pull_request.assignees.*.login, ',') }}" >> $GITHUB_ENV + + - name: Send Email on Opened PR + uses: dawidd6/action-send-mail@v3 + with: + server_address: ${{ env.SMTP_SERVER }} + server_port: ${{ env.SMTP_PORT }} + username: ${{ env.EMAIL_USERNAME }} + password: ${{ env.EMAIL_PASSWORD }} + to: ${{ env.EMAIL_TO }} + from: ${{ env.EMAIL_FROM }} + subject: "***Notification*** Opened Pull Request: ${{ env.number }}" + body: | + { + "action": "${{ env.action }}", + "number": "${{ env.number }}", + "user": "${{ env.user }}", + "title": "${{ env.title }}", + "url": "${{ env.url }}", + "assignees": "${{ env.assignees }}" + } + ignore_cert: true + nodemailerlog: true + nodemailerdebug: true + + send_email_on_updated_assignees: + if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.action == 'assigned' + runs-on: ubuntu-latest + steps: + - name: Get Updated Assignees + run: | + echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_ENV + echo "assignees=${{ join(github.event.pull_request.assignees.*.login, ',') }}" >> $GITHUB_ENV + echo "title=${{ github.event.pull_request.title }}" >> $GITHUB_ENV + + - name: Send Email on Updated Assignees + uses: dawidd6/action-send-mail@v3 + with: + server_address: ${{ env.SMTP_SERVER }} + server_port: ${{ env.SMTP_PORT }} + username: ${{ env.EMAIL_USERNAME }} + password: ${{ env.EMAIL_PASSWORD }} + to: ${{ env.EMAIL_TO }} + from: ${{ env.EMAIL_FROM }} + subject: "***Notification*** Updated Assignees for Pull Request: ${{ env.number }}" + body: | + { + "number": "${{ env.number }}", + "assignees": "${{ env.assignees }}", + "title": "${{ env.title }}" + } + ignore_cert: true + nodemailerlog: true + nodemailerdebug: true + + send_email_on_closed_pr: + if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - name: Get PR Data + run: | + echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_ENV + echo "title=${{ github.event.pull_request.title }}" >> $GITHUB_ENV + + - name: Send Email on Closed PR + uses: dawidd6/action-send-mail@v3 + with: + server_address: ${{ env.SMTP_SERVER }} + server_port: ${{ env.SMTP_PORT }} + username: ${{ env.EMAIL_USERNAME }} + password: ${{ env.EMAIL_PASSWORD }} + to: ${{ env.EMAIL_TO }} + from: ${{ env.EMAIL_FROM }} + subject: "***Notification*** Closed Pull Request: ${{ env.number }}" + body: | + { + "number": "${{ env.number }}", + "title": "${{ env.title }}" + } + ignore_cert: true + nodemailerlog: true + nodemailerdebug: true + + diff --git a/.github/workflows/test_workflow.yml b/.github/workflows/test_workflow.yml index 3edbe2f3e..b90198918 100644 --- a/.github/workflows/test_workflow.yml +++ b/.github/workflows/test_workflow.yml @@ -11,10 +11,12 @@ on: merge_group: branches: [ main ] + # Allow running the workflow manually from the Actions tab + workflow_dispatch: + jobs: pre_commit: - if: github.repository_owner == 'OpenMDAO' # run pre-commit checks runs-on: ubuntu-latest @@ -26,7 +28,6 @@ jobs: - uses: pre-commit/action@v3.0.0 test_ubuntu: - if: github.repository_owner == 'OpenMDAO' runs-on: ubuntu-latest timeout-minutes: 90 @@ -42,9 +43,10 @@ jobs: SCIPY: '1.6' PYOPTSPARSE: 'v2.9.1' SNOPT: '7.7' - OPENMDAO: '3.27' + OPENMDAO: 'dev' DYMOS: '1.8.0' MAKE_DOCS: 0 + RUN_BENCHES: 0 # development versions of openmdao/dymos - NAME: dev @@ -56,6 +58,7 @@ jobs: OPENMDAO: 'dev' DYMOS: 'dev' MAKE_DOCS: 0 + RUN_BENCHES: 0 # latest versions of openmdao/dymos - NAME: latest @@ -64,9 +67,10 @@ jobs: SCIPY: 1 PYOPTSPARSE: 'v2.9.1' SNOPT: '7.7' - OPENMDAO: 'latest' + OPENMDAO: 'dev' DYMOS: 'latest' MAKE_DOCS: 0 + RUN_BENCHES: 0 # latest versions of openmdao/dymos for docs only - NAME: latest_docs @@ -75,9 +79,22 @@ jobs: SCIPY: 1 PYOPTSPARSE: 'v2.9.1' SNOPT: '7.7' - OPENMDAO: 'latest' + OPENMDAO: 'dev' DYMOS: 'latest' MAKE_DOCS: 1 + RUN_BENCHES: 0 + + # latest versions of openmdao/dymos for benches only + - NAME: latest_benches + PY: 3 + NUMPY: 1 + SCIPY: 1 + PYOPTSPARSE: 'v2.9.1' + SNOPT: '7.7' + OPENMDAO: 'latest' + DYMOS: 'latest' + MAKE_DOCS: 0 + RUN_BENCHES: 1 steps: - name: Display run details @@ -106,6 +123,7 @@ jobs: with: auto-update-conda: true python-version: ${{ matrix.PY }} + channels: conda-forge - name: Install dependencies shell: bash -l {0} @@ -115,7 +133,7 @@ jobs: echo "=============================================================" conda install numpy=${{ matrix.NUMPY }} scipy=${{ matrix.SCIPY }} -q -y conda install matplotlib pandas panel hvplot -q -y - pip install testflo pyxdsm -q + pip install testflo -q - name: Install pyOptSparse if: matrix.PYOPTSPARSE @@ -138,15 +156,6 @@ jobs: pip install git+https://github.com/OpenMDAO/build_pyoptsparse build_pyoptsparse -v -b ${{ matrix.PYOPTSPARSE }} $SNOPT - - name: Install TeX Live - shell: bash - run: | - echo "=============================================================" - echo "Install TeX Live (for pyXDSM)" - echo "=============================================================" - sudo apt-get update - sudo apt-get install texlive-pictures texlive-latex-extra -y - - name: Install OpenMDAO if: matrix.OPENMDAO shell: bash -l {0} @@ -202,25 +211,23 @@ jobs: path: ${{ matrix.NAME }}_environment.yml retention-days: 5 - - name: Generate specs - if: matrix.MAKE_DOCS == 0 + - name: Run tests + if: matrix.MAKE_DOCS == 0 && matrix.RUN_BENCHES == 0 shell: bash -l {0} run: | echo "=============================================================" - echo "Generate specs" + echo "Run Tests" echo "=============================================================" - cd aviary/xdsm - python run_all.py - cd ../.. + testflo . -n 1 --show_skipped --coverage --coverpkg aviary - - name: Run tests - if: matrix.MAKE_DOCS == 0 + - name: Run benchmarks + if: matrix.RUN_BENCHES shell: bash -l {0} run: | echo "=============================================================" - echo "Run Tests" + echo "Run Benchmarks" echo "=============================================================" - testflo . -n 1 --show_skipped --coverage --coverpkg aviary + testflo . --testmatch=bench_test* - name: Build docs if: matrix.MAKE_DOCS @@ -232,13 +239,13 @@ jobs: echo "Build the docs" echo "=============================================================" bash build_book.sh - + - name: Display doc build reports continue-on-error: True if: matrix.MAKE_DOCS shell: bash -l {0} run: | - cd $HOME/work/om-Aviary/om-Aviary/aviary/docs + cd $HOME/work/Aviary/Aviary/aviary/docs find _build/html/reports/ -type f -name '*.log' \ -exec echo "#################################################################" \; \ -exec echo {} \; \ @@ -255,7 +262,7 @@ jobs: echo "Publishing Docs to github.io" echo "=============================================================" pip install ghp-import - cd $HOME/work/om-Aviary/om-Aviary/aviary + cd $HOME/work/Aviary/Aviary/aviary ghp-import -n -p -f docs/_build/html - name: Scan for security issues diff --git a/.github/workflows/test_workflow_no_dev_install.yml b/.github/workflows/test_workflow_no_dev_install.yml index 98f0eefe1..6531d1a85 100644 --- a/.github/workflows/test_workflow_no_dev_install.yml +++ b/.github/workflows/test_workflow_no_dev_install.yml @@ -11,10 +11,12 @@ on: merge_group: branches: [ main ] + # Allow running the workflow manually from the Actions tab + workflow_dispatch: + jobs: test_ubuntu_no_dev_install: - if: github.repository_owner == 'OpenMDAO' runs-on: ubuntu-latest timeout-minutes: 90 @@ -54,6 +56,17 @@ jobs: with: auto-update-conda: true python-version: ${{ matrix.PY }} + channels: conda-forge + + - name: Install OpenMDAO Dev + shell: bash -l {0} + run: | + echo "=============================================================" + echo "THIS STEP IS TEMPORARY" + echo "Please remove after the release of OpenMDAO 3.31." + echo "Install OpenMDAO" + echo "=============================================================" + pip install git+https://github.com/OpenMDAO/OpenMDAO - name: Checkout Aviary uses: actions/checkout@v2 @@ -65,7 +78,7 @@ jobs: echo "Install Aviary" echo "=============================================================" pip install packaging - pip install .[test] + pip install .[all] - name: Display conda environment info shell: bash -l {0} @@ -88,4 +101,4 @@ jobs: echo "Run Tests (from directory other than repo root)" echo "=============================================================" cd $HOME - testflo aviary -n 1 --show_skipped --timeout=240 --durations=20 \ No newline at end of file + testflo aviary -n 1 --show_skipped --timeout=240 --durations=20 diff --git a/.gitignore b/.gitignore index 73195354f..0b2f951ce 100644 --- a/.gitignore +++ b/.gitignore @@ -138,9 +138,6 @@ reports/ *.tex *.tikz -# Generated by XDSM files -*_specs/ - #VSCode user settings .vscode/ diff --git a/README.md b/README.md index 00c013282..e4f930438 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Aviary -- NASA's aircraft design tool -**Check out the Aviary documentation [here](https://openmdao.github.io/om-Aviary/intro.html).** +**Check out the Aviary documentation [here](https://openmdao.github.io/Aviary/intro.html).** ## Description @@ -26,7 +26,7 @@ If you also want to install all packages used for the Aviary tests _and_ externa ## Documentation -The Aviary documentation is located [here](https://openmdao.github.io/om-Aviary/intro.html). +The Aviary documentation is located [here](https://openmdao.github.io/Aviary/intro.html). Otherwise you can build the docs locally: @@ -36,12 +36,6 @@ Otherwise you can build the docs locally: 3. Run the command `sh build_book.sh` from your command prompt of choice 4. Navigate to the built html: `/Aviary/aviary/docs/\_build/html/intro.html` -## Visualization - -To create XDSM visualizations of some of the Aviary code, you can run the `run_all.py` utility script within the `aviary/xdsm` directory. -This is not strictly necessary but is used in some of the unit tests. -This requires installing Aviary with the `[test]` or `[all]` options as described above. - ## Validation This code has been validated using output and data from the GASP and FLOPS codes themselves. The GASP-based weight calculations in this code include in their comments which versions of the GASP standalone weights module were used in validation. The aero and EOM subsystem validations were based on runs of the entire GASP and FLOPS code as they stood in the summer of 2021 and the summer of 2022 respectively. @@ -76,4 +70,4 @@ We have also provided a static version of the `environment.yml` at the top level ## Planned future features Aviary is in active development. -We plan to expand its capabilities and have provided a non-exhaustive [list of future features](https://openmdao.github.io/om-Aviary/misc_resources/planned_future_features.html). \ No newline at end of file +We plan to expand its capabilities and have provided a non-exhaustive [list of future features](https://openmdao.github.io/Aviary/misc_resources/planned_future_features.html). \ No newline at end of file diff --git a/aviary/__init__.py b/aviary/__init__.py index 99316d3f4..b6cf24f58 100644 --- a/aviary/__init__.py +++ b/aviary/__init__.py @@ -1 +1 @@ -__version__ = "0.9.2-dev" +__version__ = "0.9.3-dev" diff --git a/aviary/api.py b/aviary/api.py index 9bd1e7104..d390b6789 100644 --- a/aviary/api.py +++ b/aviary/api.py @@ -32,10 +32,9 @@ from aviary.utils.data_interpolator_builder import build_data_interpolator from aviary.variable_info.enums import AlphaModes, AnalysisScheme, ProblemType, SpeedType, GASPEngineType, FlapType, EquationsOfMotion, LegacyCode, Verbosity from aviary.interface.default_phase_info.two_dof import phase_info as default_2DOF_phase_info -from aviary.interface.default_phase_info.height_energy import phase_info as default_height_energy_phase_info from aviary.interface.default_phase_info.two_dof_fiti import create_2dof_based_ascent_phases, create_2dof_based_descent_phases from aviary.interface.default_phase_info.solved import phase_info as default_solved_phase_info -from aviary.interface.default_phase_info.simple import phase_info as default_simple_phase_info +from aviary.interface.default_phase_info.height_energy import phase_info as default_height_energy_phase_info from aviary.interface.methods_for_level1 import run_level_1 from aviary.interface.methods_for_level1 import run_aviary from aviary.interface.methods_for_level2 import AviaryProblem @@ -68,7 +67,6 @@ from aviary.mission.gasp_based.ode.base_ode import BaseODE from aviary.mission.flops_based.ode.landing_ode import LandingODE as DetailedLandingODE from aviary.mission.flops_based.ode.landing_ode import FlareODE as DetailedFlareODE -from aviary.mission.flops_based.ode.mission_ODE import MissionODE as HeightEnergyMissionODE from aviary.mission.flops_based.ode.takeoff_ode import TakeoffODE as DetailedTakeoffODE from aviary.mission.gasp_based.ode.accel_ode import AccelODE as TwoDOFAccelerationODE from aviary.mission.gasp_based.ode.ascent_ode import AscentODE as TwoDOFAscentODE @@ -85,14 +83,12 @@ # Phase builders -from aviary.mission.flops_based.phases.phase_builder_base import PhaseBuilderBase +from aviary.mission.phase_builder_base import PhaseBuilderBase # note that this is only for simplified right now +from aviary.mission.flops_based.phases.energy_phase import EnergyPhase as HeightEnergyPhaseBuilder from aviary.mission.flops_based.phases.build_landing import Landing as HeightEnergyLandingPhaseBuilder # note that this is only for simplified right now from aviary.mission.flops_based.phases.build_takeoff import Takeoff as HeightEnergyTakeoffPhaseBuilder -from aviary.mission.flops_based.phases.climb_phase import Climb as HeightEnergyClimbPhaseBuilder -from aviary.mission.flops_based.phases.cruise_phase import Cruise as HeightEnergyCruisePhaseBuilder -from aviary.mission.flops_based.phases.descent_phase import Descent as HeightEnergyDescentPhaseBuilder from aviary.mission.flops_based.phases.detailed_landing_phases import LandingApproachToMicP3 as DetailedLandingApproachToMicP3PhaseBuilder from aviary.mission.flops_based.phases.detailed_landing_phases import LandingMicP3ToObstacle as DetailedLandingMicP3ToObstaclePhaseBuilder from aviary.mission.flops_based.phases.detailed_landing_phases import LandingObstacleToFlare as DetailedLandingObstacleToFlarePhaseBuilder @@ -111,13 +107,13 @@ from aviary.mission.flops_based.phases.detailed_takeoff_phases import TakeoffMicP1ToClimb as DetailedTakeoffMicP1ToClimbPhaseBuilder from aviary.mission.flops_based.phases.detailed_takeoff_phases import TakeoffBrakeToAbort as DetailedTakeoffBrakeToAbortPhaseBuilder -# Phase getters # TODO these should be going away in favor of phase builders -from aviary.mission.gasp_based.phases.accel_phase import get_accel as get_2DOF_acceleration_phase -from aviary.mission.gasp_based.phases.ascent_phase import get_ascent as get_2DOF_ascent_phase -from aviary.mission.gasp_based.phases.climb_phase import get_climb as get_2DOF_climb_phase -from aviary.mission.gasp_based.phases.desc_phase import get_descent as get_2DOF_descent_phase -from aviary.mission.gasp_based.phases.groundroll_phase import get_groundroll as get_2DOF_groundroll_phase -from aviary.mission.gasp_based.phases.rotation_phase import get_rotation as get_2DOF_rotation_phase +# Phase builders +from aviary.mission.gasp_based.phases.accel_phase import AccelPhase as TwoDOFAccelerationPhase +from aviary.mission.gasp_based.phases.ascent_phase import AscentPhase as TwoDOFAscentPhase +from aviary.mission.gasp_based.phases.climb_phase import ClimbPhase as TwoDOFClimbPhase +from aviary.mission.gasp_based.phases.descent_phase import DescentPhase as TwoDOFDescentPhase +from aviary.mission.gasp_based.phases.groundroll_phase import GroundrollPhase as TwoDOFGroundrollPhase +from aviary.mission.gasp_based.phases.rotation_phase import RotationPhase as TwoDOFRotationPhase # Trajectory builders @@ -147,7 +143,3 @@ from aviary.subsystems.propulsion.engine_model import EngineModel from aviary.subsystems.propulsion.propulsion_builder import PropulsionBuilderBase from aviary.subsystems.propulsion.propulsion_builder import CorePropulsionBuilder - -# Testing -from aviary.validation_cases.validation_tests import get_flops_inputs, get_flops_outputs -from aviary.validation_cases.validation_data.flops_data.FLOPS_Test_Data import FLOPS_Test_Data diff --git a/aviary/constants.py b/aviary/constants.py index 55dfca1d5..c7770d804 100644 --- a/aviary/constants.py +++ b/aviary/constants.py @@ -1,6 +1,6 @@ import openmdao.utils.units as units -units.add_unit('range_units', '1*m') +units.add_unit('distance_units', '1*m') GRAV_METRIC_GASP = 9.81 # m/s^2 GRAV_ENGLISH_GASP = 32.2 # ft/s^2 diff --git a/aviary/docs/_toc.yml b/aviary/docs/_toc.yml index fa6b72ba4..a0714e8eb 100644 --- a/aviary/docs/_toc.yml +++ b/aviary/docs/_toc.yml @@ -86,6 +86,5 @@ parts: - file: misc_resources/feature_comparison sections: - file: misc_resources/comparison_to_flops - - file: misc_resources/using_XDSM - file: misc_resources/planned_future_features - file: _srcdocs/index diff --git a/aviary/docs/build_pdf_book.sh b/aviary/docs/build_pdf_book.sh deleted file mode 100644 index 846f20db7..000000000 --- a/aviary/docs/build_pdf_book.sh +++ /dev/null @@ -1 +0,0 @@ -jupyter-book build . --builder pdflatex \ No newline at end of file diff --git a/aviary/docs/developer_guide/codebase_overview.md b/aviary/docs/developer_guide/codebase_overview.md index b24cb859d..8193dae01 100644 --- a/aviary/docs/developer_guide/codebase_overview.md +++ b/aviary/docs/developer_guide/codebase_overview.md @@ -13,4 +13,3 @@ Within the Aviary repository and package, the codebase is structured as follows: - `utils` contains utility functions for use in Aviary code, examples, and tests - `validation_cases` contains validation cases for testing and benchmarking Aviary - `visualization` is where the Aviary dashboard is located -- `xdsm` contains diagrams to help developers and users of Aviary understand how systems are connected together diff --git a/aviary/docs/developer_guide/contributing_guidelines.md b/aviary/docs/developer_guide/contributing_guidelines.md index 11ad8b384..523cbc8be 100755 --- a/aviary/docs/developer_guide/contributing_guidelines.md +++ b/aviary/docs/developer_guide/contributing_guidelines.md @@ -15,7 +15,7 @@ Documentation is the backbone of the Aviary team's support for our user communit The Aviary codebase is currently under active development and cleanup, including the addition of docstrings. Thus, not every function and class currently includes a docstring, however, we are slowly adding them. In order to move forwards instead of backwards we require that all added functions and classes include a docstring in the numpy format. ## Benchmark Tests -The Aviary codebase has several benchmark tests which test some of the baseline models included in Aviary. These tests supplement the unit test capability, and are tested frequently by the Aviary team. We encourage you to run these tests using our test runner located [here](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/run_all_benchmarks.py). +The Aviary codebase has several benchmark tests which test some of the baseline models included in Aviary. These tests supplement the unit test capability, and are tested frequently by the Aviary team. We encourage you to run these tests using our test runner located [here](https://github.com/OpenMDAO/Aviary/blob/main/aviary/run_all_benchmarks.py). ## Use of Issue Backlog -The Aviary team would like a chance to interact with and get community engagement in feature changes to the codebase. The primary place that this engagement happens is in the [issue backlog](https://github.com/OpenMDAO/om-Aviary/issues/new/choose) using the "feature or change request" section. In addition, we would like to be able to track bug fixes that come through the code. To support these goals we encourage users to create issues, and we encourage code contributors to link issues to their pull requests. \ No newline at end of file +The Aviary team would like a chance to interact with and get community engagement in feature changes to the codebase. The primary place that this engagement happens is in the [issue backlog](https://github.com/OpenMDAO/Aviary/issues/new/choose) using the "feature or change request" section. In addition, we would like to be able to track bug fixes that come through the code. To support these goals we encourage users to create issues, and we encourage code contributors to link issues to their pull requests. \ No newline at end of file diff --git a/aviary/docs/examples/OAS_subsystem.ipynb b/aviary/docs/examples/OAS_subsystem.ipynb new file mode 100644 index 000000000..c36d6ea2e --- /dev/null +++ b/aviary/docs/examples/OAS_subsystem.ipynb @@ -0,0 +1,407 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using Aviary and OpenAeroStruct Together\n", + "\n", + "This is an example of an external subsystem using the [OpenAeroStruct (OAS)](https://github.com/mdolab/OpenAeroStruct) structural analysis system to perform aerostructural optimization of a typical large single aisle transport aircraft wing.\n", + "The subsystem is based on the [OpenAeroStruct aerostructural optimization with wingbox](https://mdolab-openaerostruct.readthedocs-hosted.com/en/latest/aerostructural_wingbox_walkthrough.html) example problem.\n", + "\n", + "This example performs a sub-optimization for minimum wing weight that is then used by Aviary.\n", + "Another use case would be to perform a structural analysis only.\n", + "Structural design variables would be passed to the subsystem from Aviary and wing weight and a constraint or constraints representing the structural analysis would be passed back to Aviary for use by the top level optimization.\n", + "\n", + "## Motivation\n", + "\n", + "There may be a need for a higher fidelity tool to compute wing weight instead of relying on the empirical methods in core Aviary.\n", + "A structural analysis external tool is usually used because of an unusual aircraft configuration that may not be predicted my Aviary empirical weight estimation methods, but in this example case it is simply a demonstration of an external capability to compute wing weight.\n", + "\n", + "## External Dependencies\n", + "\n", + "The user must install [OpenAeroStruct](https://github.com/mdolab/OpenAeroStruct) into their Python environment using the command 'pip install openaerostruct'.\n", + "The user must also install the [ambiance](https://ambiance.readthedocs.io/en/latest/) package using the command 'pip install ambiance'.\n", + "\n", + "## Subsystem Details\n", + "\n", + "There are two parts to building an external subsystem -- the analysis tool and the Aviary external subsystem builder for that tool.\n", + "The analysis tool takes inputs and parameters from Aviary and return outputs that Aviary can use to override existing variables.\n", + "The subsystem builder uses the Aviary external subsystem builder template to connect the analysis tool to Aviary as either a pre-mission, mission or post-mission subsystem.\n", + "\n", + "For this case, the analysis tool will compute a wing weight in the pre-mission portion of the Aviary analysis and return its value to Aviary to override the empirical wing weight value.\n", + "Fuel weight is passed in from Aviary as the only input currently, but other inputs may also be passed in through the subsystem builder, [OAS_wing_weight_builder](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_builder.py), by the `promotes_inputs` parameter.\n", + "Other Aviary variables can also be added as additional inputs based on user needs.\n", + "\n", + "```{note}\n", + "Some modifications of the [OAS_wing_weight_analysis](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) code will be necessary to add new inputs not already defined.\n", + "```\n", + "\n", + "Here is the builder object for the OAS wing weight analysis example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %load ../../examples/external_subsystems/OAS_weight/OAS_wing_weight_builder.py\n", + "\"\"\"\n", + "Builder for an OpenAeroStruct component that computes a new wing mass.\n", + "\n", + "\"\"\"\n", + "import openmdao.api as om\n", + "import aviary.api as av\n", + "from aviary.examples.external_subsystems.OAS_weight.OAS_wing_weight_analysis import OAStructures\n", + "\n", + "\n", + "class OASWingWeightBuilder(av.SubsystemBuilderBase):\n", + " def __init__(self, name='wing_weight'):\n", + " super().__init__(name)\n", + "\n", + " def build_pre_mission(self, aviary_inputs):\n", + " '''\n", + " Build an OpenMDAO system for the pre-mission computations of the subsystem.\n", + "\n", + " Returns\n", + " -------\n", + " pre_mission_sys : openmdao.core.System\n", + " An OpenMDAO system containing all computations that need to happen in\n", + " the pre-mission (formerly statics) part of the Aviary problem. This\n", + " includes sizing, design, and other non-mission parameters.\n", + " '''\n", + "\n", + " wing_group = om.Group()\n", + " wing_group.add_subsystem(\"aerostructures\",\n", + " OAStructures(\n", + " symmetry=True,\n", + " wing_weight_ratio=1.0,\n", + " S_ref_type='projected',\n", + " n_point_masses=1,\n", + " num_twist_cp=4,\n", + " num_box_cp=51),\n", + " promotes_inputs=[\n", + " ('fuel', av.Mission.Design.FUEL_MASS),\n", + " ],\n", + " promotes_outputs=[('wing_weight', av.Aircraft.Wing.MASS)])\n", + "\n", + " return wing_group\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analysis Model Details\n", + "\n", + "This analysis is based on the Aviary benchmark [aircraft_for_bench_FwFm](https://github.com/OpenMDAO/Aviary/blob/main/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv) input data representing a typical large single aisle class transport aircraft.\n", + "The analysis code [OAS_wing_weight_analysis](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) contains the `OAStructures` class which performs a structural analysis of the wing.\n", + "\n", + "We'll now discuss this code in more detail.\n", + "\n", + "First, we create a mesh that defines the shape of the wing based on the span, the location of the wing break typical of a transport aircraft, the dihedral and the wing root, wing break, and wing tip chord lengths.\n", + "The twist of the wing is defined along the span at a set of control points and must be present as it is used in the optimization problem.\n", + "\n", + "We then use this mesh to define a simplified finite element model of the wing structural box.\n", + "We also define the airfoil shapes as an input to OpenAeroStruct for a given wing thickness to chord ratio (t/c) to represent the wing box thickness.\n", + "We then set initial values for the wing skin thickness and spar thickness are set, along with material properties and stress allowables for a metal material, typically aluminum.\n", + "OpenAeroStruct will then calculate aeroelastic loads for a 2.5g maneuver condition and apply those loads to the finite element model of the wing structure.\n", + "\n", + "Results of the structural optimization determine the optimum wing skin thickness, spar cap thickness, wing twist, wing t/c and maneuver angle of attack that satisfies strength constraints while minimizing wing weight.\n", + "The 'OAStructures' class returns the optimized wing mass and the fuel mass burned but currently only the wing mass is used to override the Aviary variable 'Aircraft.Wing.MASS'.\n", + "\n", + "The [OAS_wing_weight_analysis](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) code may be executed in Python to test the OpenAeroStruct analysis outside of the Aviary subsystem interface.\n", + "Default values for each of the inputs and options are included at the bottom of the analysis code file.\n", + "This can be a useful test to demonstrate that the OpenAeroStruct analysis model has been properly defined and the model returns reasonable results.\n", + "\n", + "Once the user is satisfied that the results are acceptable, the analysis tool can then be added as an external subsystem and tested in a mission.\n", + "\n", + "## Subsystem variables\n", + "\n", + "A variety of parameters may be defined for an OpenAeroStruct model.\n", + "These allow the user to control how the aerodynamic and finite element meshes are subdivided, give details about the aerodynamic solution and provide structural material properties and structural scaling factors.\n", + "The input variables passed in from Aviary may include the fuel mass, reserve fuel mass, airfoil description, engine mass and its location, lift and drag coefficients and the cruise conditions of Mach, altitude, SFC and range.\n", + "\n", + "This is a list of the available options defined for the structural analysis:\n", + "\n", + "- symmetry\n", + "- chord_cos_spacing\n", + "- span_cos_spacing\n", + "- num_box_cp\n", + "- num_twist_cp\n", + "- S_ref_type\n", + "- fem_model_type\n", + "- with_viscous\n", + "- with_wave\n", + "- k_lam\n", + "- c_max_t\n", + "- E\n", + "- G\n", + "- yield\n", + "- mrho\n", + "- strength_factor_for_upper_skin\n", + "- wing_weight_ratio\n", + "- exact_failure_constraint\n", + "- struct_weight_relief\n", + "- distributed_fuel_weight\n", + "- n_point_masses\n", + "- fuel_density\n", + "\n", + "This is a list of the inputs defined for the structural analysis:\n", + "\n", + "- box_upper_x\n", + "- box_lower_x\n", + "- box_upper_y\n", + "- box_lower_y\n", + "- twist_cp\n", + "- spar_thickness_cp\n", + "- skin_thickness_cp\n", + "- t_over_c_cp\n", + "- airfoil_t_over_c\n", + "- fuel\n", + "- fuel_reserve\n", + "- CL0\n", + "- CD0\n", + "- cruise_Mach\n", + "- cruise_altitude\n", + "- cruise_range\n", + "- cruise_SFC\n", + "- engine_mass\n", + "- engine_location\n", + "\n", + "The 2 outputs from the analysis tool are:\n", + "\n", + "- wing_weight\n", + "- fuel_burn\n", + "\n", + "See [OAS_wing_weight_analysis](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) and the [OpenAeroStruct aerostructural optimization with wingbox](https://mdolab-openaerostruct.readthedocs-hosted.com/en/latest/aerostructural_wingbox_walkthrough.html) documentation for descriptions of these variables.\n", + "\n", + "## Test Case\n", + "\n", + "A simple Aviary mission is defined to test the inclusion of the OpenAeroStruct wing weight subsystem during the pre-mission phase.\n", + "The test mission is defined in [run_simple_OAS_mission](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS/run_simple_OAS_mission.py) and is a mission based on input data read from the benchmark data file [aircraft_for_bench_FwFm](https://github.com/OpenMDAO/Aviary/blob/main/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv).\n", + "\n", + "The OpenAeroStruct subsystem is used to compute an optimum wing mass which will override the Aviary computed wing mass value.\n", + "The value of the Aviary variable `Aircraft.Wing.MASS` is printed at the conclusion of the mission to verify that the wing weight from the subsystem is overriding the Aviary computed wing weight.\n", + "\n", + "A variable in the [run_simple_OAS_mission](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS/run_simple_OAS_mission.py) file named `use_OAS` may be set by the user to `True` or `False` to run the simple mission with or without the OpenAeroStruct subsystem included.\n", + "This will allow the mission to be flown either using the Aviary empirical wing weight estimation (`use_OAS=False`) or with the OpenAeroStruct subsystem (`use_OAS=True`).\n", + "\n", + "Wing weight optimization of this type usually does not have knowledge of non-optimum wing mass values such as leading and training edge structure, actuators, stiffeners, etc.\n", + "The optimum wing mass computed by the `OAStructures` class can be scaled using the option `wing_weight_ratio` to better match either the Aviary empirical wing weight value or a known fly-away weight estimate for your wing model.\n", + "One method to determine the wing_weight_ratio would be to run the mission to calculate the Aviary empirical wing weight and then run [OAS_wing_weight_analysis](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) by itself using its default input values and compare wing weights. The `wing_weight_ratio` value may then be set to calibrate the OpenAeroStruct wing weight to the expected fly-away weight.\n", + "\n", + "This calibration step has already been performed for this model, so the user may run the simple mission with or without the OpenAeroStruct subsystem active and compare the results.\n", + "\n", + "## Example Run Script\n", + "\n", + "Here is the full run script used to run the simple mission with the OpenAeroStruct subsystem active.\n", + "This run script is also available in the [run_simple_OAS_mission file.](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/OAS/run_simple_OAS_mission.py)\n", + "\n", + "\n", + "```{note}\n", + "We do not actually perform the optimization below.\n", + "Instead, we define and set up the model and the call to run the optimization is commented you.\n", + "You can uncomment this and run the code block to perform a full optimization.\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %load ../../examples/external_subsystems/OAS_weight/run_simple_OAS_mission.py\n", + "'''\n", + "This is a simple test mission to demonstrate the inclusion of a\n", + "pre-mission user defined external subsystem. The simple mission\n", + "is based on input data read from the benchmark data file bench_4.csv,\n", + "which represents a single-aisle commercial transport aircraft. The\n", + "OpenAeroStruct (OAS) subsystem is used to compute an optimum wing\n", + "mass which will override the Aviary computed wing mass value.\n", + "\n", + "The flag 'use_OAS' is set to 'True' to include the OAS subsystem in\n", + "the mission, or set to 'False' to run the mission without the\n", + "subsystem so that wing mass values between the 2 methods may be\n", + "compared.\n", + "\n", + "'''\n", + "\n", + "import numpy as np\n", + "import openmdao.api as om\n", + "import aviary.api as av\n", + "from aviary.examples.external_subsystems.OAS_weight.OAS_wing_weight_builder import OASWingWeightBuilder\n", + "\n", + "# flag to turn on/off OpenAeroStruct subsystem for comparison testing\n", + "use_OAS = True\n", + "\n", + "wing_weight_builder = OASWingWeightBuilder()\n", + "\n", + "# Load the phase_info and other common setup tasks\n", + "phase_info = {\n", + " 'climb_1': {\n", + " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", + " 'user_options': {\n", + " 'optimize_mach': False,\n", + " 'optimize_altitude': False,\n", + " 'polynomial_control_order': 1,\n", + " 'num_segments': 5,\n", + " 'order': 3,\n", + " 'solve_for_distance': False,\n", + " 'initial_mach': (0.2, 'unitless'),\n", + " 'final_mach': (0.72, 'unitless'),\n", + " 'mach_bounds': ((0.18, 0.74), 'unitless'),\n", + " 'initial_altitude': (0.0, 'ft'),\n", + " 'final_altitude': (32000.0, 'ft'),\n", + " 'altitude_bounds': ((0.0, 34000.0), 'ft'),\n", + " 'throttle_enforcement': 'path_constraint',\n", + " 'fix_initial': True,\n", + " 'constrain_final': False,\n", + " 'fix_duration': False,\n", + " 'initial_bounds': ((0.0, 0.0), 'min'),\n", + " 'duration_bounds': ((64.0, 192.0), 'min'),\n", + " },\n", + " 'initial_guesses': {'times': ([0, 128], 'min')},\n", + " },\n", + " 'climb_2': {\n", + " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", + " 'user_options': {\n", + " 'optimize_mach': False,\n", + " 'optimize_altitude': False,\n", + " 'polynomial_control_order': 1,\n", + " 'num_segments': 5,\n", + " 'order': 3,\n", + " 'solve_for_distance': False,\n", + " 'initial_mach': (0.72, 'unitless'),\n", + " 'final_mach': (0.72, 'unitless'),\n", + " 'mach_bounds': ((0.7, 0.74), 'unitless'),\n", + " 'initial_altitude': (32000.0, 'ft'),\n", + " 'final_altitude': (34000.0, 'ft'),\n", + " 'altitude_bounds': ((23000.0, 38000.0), 'ft'),\n", + " 'throttle_enforcement': 'boundary_constraint',\n", + " 'fix_initial': False,\n", + " 'constrain_final': False,\n", + " 'fix_duration': False,\n", + " 'initial_bounds': ((64.0, 192.0), 'min'),\n", + " 'duration_bounds': ((56.5, 169.5), 'min'),\n", + " },\n", + " 'initial_guesses': {'times': ([128, 113], 'min')},\n", + " },\n", + " 'descent_1': {\n", + " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", + " 'user_options': {\n", + " 'optimize_mach': False,\n", + " 'optimize_altitude': False,\n", + " 'polynomial_control_order': 1,\n", + " 'num_segments': 5,\n", + " 'order': 3,\n", + " 'solve_for_distance': False,\n", + " 'initial_mach': (0.72, 'unitless'),\n", + " 'final_mach': (0.36, 'unitless'),\n", + " 'mach_bounds': ((0.34, 0.74), 'unitless'),\n", + " 'initial_altitude': (34000.0, 'ft'),\n", + " 'final_altitude': (500.0, 'ft'),\n", + " 'altitude_bounds': ((0.0, 38000.0), 'ft'),\n", + " 'throttle_enforcement': 'path_constraint',\n", + " 'fix_initial': False,\n", + " 'constrain_final': True,\n", + " 'fix_duration': False,\n", + " 'initial_bounds': ((120.5, 361.5), 'min'),\n", + " 'duration_bounds': ((29.0, 87.0), 'min'),\n", + " },\n", + " 'initial_guesses': {'times': ([241, 58], 'min')},\n", + " },\n", + " 'post_mission': {\n", + " 'include_landing': False,\n", + " 'constrain_range': True,\n", + " 'target_range': (1800., 'nmi'),\n", + " },\n", + "}\n", + "\n", + "phase_info['pre_mission'] = {'include_takeoff': False, 'optimize_mass': True}\n", + "if use_OAS:\n", + " phase_info['pre_mission']['external_subsystems'] = [wing_weight_builder]\n", + "\n", + "aircraft_definition_file = 'models/test_aircraft/aircraft_for_bench_FwFm.csv'\n", + "mission_method = 'simple'\n", + "mass_method = 'FLOPS'\n", + "make_plots = False\n", + "max_iter = 100\n", + "optimizer = 'IPOPT'\n", + "\n", + "prob = av.AviaryProblem()\n", + "\n", + "prob.load_inputs(aircraft_definition_file, phase_info)\n", + "prob.check_and_preprocess_inputs()\n", + "prob.add_pre_mission_systems()\n", + "prob.add_phases()\n", + "prob.add_post_mission_systems()\n", + "prob.link_phases()\n", + "prob.add_driver(optimizer=optimizer, max_iter=max_iter)\n", + "prob.add_design_variables()\n", + "prob.add_objective()\n", + "prob.setup()\n", + "\n", + "if use_OAS:\n", + " OAS_sys = 'pre_mission.wing_weight.aerostructures.'\n", + " prob.set_val(OAS_sys + 'box_upper_x', np.array([0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31, 0.32,\n", + " 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.57, 0.58, 0.59, 0.6]), units='unitless')\n", + " prob.set_val(OAS_sys + 'box_lower_x', np.array([0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31, 0.32,\n", + " 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.57, 0.58, 0.59, 0.6]), units='unitless')\n", + " prob.set_val(OAS_sys + 'box_upper_y', np.array([0.0447, 0.046, 0.0472, 0.0484, 0.0495, 0.0505, 0.0514, 0.0523, 0.0531, 0.0538, 0.0545, 0.0551, 0.0557, 0.0563, 0.0568, 0.0573, 0.0577, 0.0581, 0.0585, 0.0588, 0.0591, 0.0593, 0.0595, 0.0597,\n", + " 0.0599, 0.06, 0.0601, 0.0602, 0.0602, 0.0602, 0.0602, 0.0602, 0.0601, 0.06, 0.0599, 0.0598, 0.0596, 0.0594, 0.0592, 0.0589, 0.0586, 0.0583, 0.058, 0.0576, 0.0572, 0.0568, 0.0563, 0.0558, 0.0553, 0.0547, 0.0541]), units='unitless')\n", + " prob.set_val(OAS_sys + 'box_lower_y', np.array([-0.0447, -0.046, -0.0473, -0.0485, -0.0496, -0.0506, -0.0515, -0.0524, -0.0532, -0.054, -0.0547, -0.0554, -0.056, -0.0565, -0.057, -0.0575, -0.0579, -0.0583, -0.0586, -0.0589, -0.0592, -0.0594, -0.0595, -0.0596, -\n", + " 0.0597, -0.0598, -0.0598, -0.0598, -0.0598, -0.0597, -0.0596, -0.0594, -0.0592, -0.0589, -0.0586, -0.0582, -0.0578, -0.0573, -0.0567, -0.0561, -0.0554, -0.0546, -0.0538, -0.0529, -0.0519, -0.0509, -0.0497, -0.0485, -0.0472, -0.0458, -0.0444]), units='unitless')\n", + " prob.set_val(OAS_sys + 'twist_cp', np.array([-6., -6., -4., 0.]), units='deg')\n", + " prob.set_val(OAS_sys + 'spar_thickness_cp',\n", + " np.array([0.004, 0.005, 0.008, 0.01]), units='m')\n", + " prob.set_val(OAS_sys + 'skin_thickness_cp',\n", + " np.array([0.005, 0.01, 0.015, 0.025]), units='m')\n", + " prob.set_val(OAS_sys + 't_over_c_cp',\n", + " np.array([0.08, 0.08, 0.10, 0.08]), units='unitless')\n", + " prob.set_val(OAS_sys + 'airfoil_t_over_c', 0.12, units='unitless')\n", + " prob.set_val(OAS_sys + 'fuel', 40044.0, units='lbm')\n", + " prob.set_val(OAS_sys + 'fuel_reserve', 3000.0, units='lbm')\n", + " prob.set_val(OAS_sys + 'CD0', 0.0078, units='unitless')\n", + " prob.set_val(OAS_sys + 'cruise_Mach', 0.785, units='unitless')\n", + " prob.set_val(OAS_sys + 'cruise_altitude', 11303.682962301647, units='m')\n", + " prob.set_val(OAS_sys + 'cruise_range', 3500, units='nmi')\n", + " prob.set_val(OAS_sys + 'cruise_SFC', 0.53 / 3600, units='1/s')\n", + " prob.set_val(OAS_sys + 'engine_mass', 7400, units='lbm')\n", + " prob.set_val(OAS_sys + 'engine_location', np.array([25, -10.0, 0.0]), units='m')\n", + "\n", + "prob.set_initial_guesses()\n", + "# prob.run_aviary_problem('dymos_solution.db', make_plots=False)\n", + "# print('wing mass = ', prob.model.get_val(av.Aircraft.Wing.MASS, units='lbm'))\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.17" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/aviary/docs/examples/OAS_subsystem.md b/aviary/docs/examples/OAS_subsystem.md deleted file mode 100644 index 10958f502..000000000 --- a/aviary/docs/examples/OAS_subsystem.md +++ /dev/null @@ -1,120 +0,0 @@ -# Using Aviary and OpenAeroStruct Together - -This is an example of an external subsystem using the [OpenAeroStruct](https://github.com/mdolab/OpenAeroStruct) structural analysis system to perform aerostructural optimization of a typical large single aisle transport aircraft wing. -The subsystem is based on the [OpenAeroStruct aerostructural optimization with wingbox](https://mdolab-openaerostruct.readthedocs-hosted.com/en/latest/aerostructural_wingbox_walkthrough.html) example problem. - -While this example performs a sub-optimization for minimum wing weight that is then used by Aviary, a more typical case would be to perform a structural analysis only. -Structural design variables would be passed to the subsystem from Aviary and wing weight and a constraint or constraints representing the structural analysis would be passed back to Aviary for use by the top level optimization. - -## Motivation - -There may be a need for a higher fidelity tool to compute wing weight instead of relying on the empirical methods in core Aviary. -A structural analysis external tool is usually used because of an unusual aircraft configuration that may not be predicted my Aviary empirical weight estimation methods, but in this example case it is simply a demonstration of an external capability to compute wing weight. - -## External Dependencies - -The user must install [OpenAeroStruct](https://github.com/mdolab/OpenAeroStruct) into their Python environment using the command 'pip install openaerostruct'. -The user must also install the [ambiance](https://ambiance.readthedocs.io/en/latest/) 1993 standard atmosphere code using the command 'pip install ambiance'. - -## Subsystem Details - -There are two parts to building an external subsystem -- the analysis tool and the Aviary external subsystem builder for that tool. -The analysis tool takes inputs and parameters from Aviary and return outputs that Aviary can use to override existing variables. -The subsystem builder uses the Aviary external subsystem builder template to connect the analysis tool to Aviary as either a pre-mission, mission or post-mission subsystem. - -For this case, the analysis tool will compute a wing weight in the pre-mission portion of the Aviary analysis and return its value to Aviary to override the empirical wing weight value. -Fuel weight is passed in from Aviary as the only input currently, but other inputs may also be passed in through the subsystem builder, [OAS_wing_weight_builder](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_builder.py), by the 'promotes_inputs' parameter. -Other Aviary variables can also be added in the future as additional inputs. Some modifications of the [OAS_wing_weight_analysis](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) code may be necessary to add new inputs not already defined. - -## Analysis Model Details - -This analysis is based on the Aviary benchmark [aircraft_for_bench_FwFm](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv) input data representing a typical large single aisle class transport aircraft. -The analysis code [OAS_wing_weight_analysis](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) contains the `OAStructures` class which performs a structural analysis of the wing. -A mesh is created that defines the shape of the wing based on the span, the location of the wing break typical of a transport aircraft, the dihedral and the wing root, wing break and wing tip chord lengths. -The twist of the wing is defined along the span at a set of control points, and must be present as it is used in the optimization problem. -This mesh will also be used to define a simplified finite element model of the wing structural box. -An airfoil shape is defined as an input to OpenAeroStruct for a given wing thickness to chord ratio (t/c) to represent the wing box thickness. -Initial values for wing skin thickness and spar thickness are set, along with material properties and stress allowables for a metal material, typically aluminum. -OpenAeroStruct will then calculate aeroelastic loads for a 2.5g maneuver condition and apply those loads to the finite element model of the wing structure. -Results of the structural optimization determine the optimum wing skin thickness, spar cap thickness, wing twist, wing t/c and maneuver angle of attack that satisfies strength constraints while minimizing wing weight. -The 'OAStructures' class returns the optimized wing mass and the fuel mass burned but currently only the wing mass is used to override the Aviary variable 'Aircraft.Wing.MASS'. -The [OAS_wing_weight_analysis](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) code may be executed in Python to test the OpenAeroStruct analysis outside of the Aviary subsystem interface. -Default values for each of the inputs and options are included at the bottom of the analysis code file. -This can be a useful test to demonstrate that the OpenAeroStruct analysis model has been properly defined and the model returns reasonable results. -Once the user is satisfied that the results are acceptable, the analysis tool can then be added as an external subsystem and tested in a mission. - -## Subsystem variables - -A variety of parameters may be defined for an OpenAeroStruct model. -These allow the user to control how the aerodynamic and finite element meshes are subdivided, give details about the aerodynamic solution and provide structural material properties and structural scaling factors. -The input variables passed in from Aviary may include the fuel mass, reserve fuel mass, airfoil description, engine mass and its location, lift and drag coefficients and the cruise conditions of Mach, altitude, SFC and range. - -This is a list of the available options defined for the structural analysis: - -- symmetry -- chord_cos_spacing -- span_cos_spacing -- num_box_cp -- num_twist_cp -- S_ref_type -- fem_model_type -- with_viscous -- with_wave -- k_lam -- c_max_t -- E -- G -- yield -- mrho -- strength_factor_for_upper_skin -- wing_weight_ratio -- exact_failure_constraint -- struct_weight_relief -- distributed_fuel_weight -- n_point_masses -- fuel_density - -This is a list of the inputs defined for the structural analysis: - -- box_upper_x -- box_lower_x -- box_upper_y -- box_lower_y -- twist_cp -- spar_thickness_cp -- skin_thickness_cp -- t_over_c_cp -- airfoil_t_over_c -- fuel -- fuel_reserve -- CL0 -- CD0 -- cruise_Mach -- cruise_altitude -- cruise_range -- cruise_SFC -- engine_mass -- engine_location - -The 2 outputs from the analysis tool are: - -- wing_weight -- fuel_burn - -See [OAS_wing_weight_analysis](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) and the [OpenAeroStruct aerostructural optimization with wingbox](https://mdolab-openaerostruct.readthedocs-hosted.com/en/latest/aerostructural_wingbox_walkthrough.html) documentation for descriptions of these variables. - -## Test Case - -A simple Aviary mission is defined to test the inclusion of the OpenAeroStruct wing weight subsystem during the pre-mission phase. -The test mission is defined in [run_simple_OAS_mission](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/OAS/run_simple_OAS_mission.py) and is a mission based on input data read from the benchmark data file [aircraft_for_bench_FwFm](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv). -The OpenAeroStruct (OAS) subsystem is used to compute an optimum wing mass which will override the Aviary computed wing mass value. -The value of the Aviary variable `Aircraft.Wing.MASS` is printed at the conclusion of the mission to verify that the wing weight from the subsystem is overriding the Aviary computed wing weight. - -A variable in the [run_simple_OAS_mission](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/OAS/run_simple_OAS_mission.py) file named `use_OAS` may be set by the user to `True` or `False` to run the simple mission with or without the OpenAeroStruct subsystem included. -This will allow the mission to be flown either using the Aviary empirical wing weight estimation (use_OAS='False') or with the OpenAero Struct subsystem (use_OAS='True'). - -Wing weight optimization of this type usually does not have knowledge of non-optimum wing mass values such as leading and training edge structure, actuators, stiffeners, etc. -The optimum wing mass computed by the `OAStructures` class can be scaled using the option 'wing_weight_ratio' to better match either the Aviary empirical wing weight value or a known fly-away weight estimate for your wing model. -One method to determine the wing_weight_ratio would be to run the mission to calculate the Aviary empirical wing weight and then run [OAS_wing_weight_analysis](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py) by itself using its default input values and compare wing weights. The 'wing_weight_ratio' value may then be set to calibrate the OpenAeroStruct wing weight to the expected fly-away weight. - -This calibration step has already been performed for this model, so the user may run the simple mission with or without the OpenAeroStruct subsystem active and compare the results. diff --git a/aviary/docs/examples/additional_flight_phases.ipynb b/aviary/docs/examples/additional_flight_phases.ipynb index a28066d63..ab4ebb2fd 100644 --- a/aviary/docs/examples/additional_flight_phases.ipynb +++ b/aviary/docs/examples/additional_flight_phases.ipynb @@ -38,7 +38,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.2, \"unitless\"),\n", " \"final_mach\": (0.72, \"unitless\"),\n", " \"mach_bounds\": ((0.18, 0.74), \"unitless\"),\n", @@ -62,7 +62,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.72, \"unitless\"),\n", " \"final_mach\": (0.72, \"unitless\"),\n", " \"mach_bounds\": ((0.7, 0.74), \"unitless\"),\n", @@ -86,7 +86,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.72, \"unitless\"),\n", " \"final_mach\": (0.74, \"unitless\"),\n", " \"mach_bounds\": ((0.7, 0.76), \"unitless\"),\n", @@ -110,7 +110,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.74, \"unitless\"),\n", " \"final_mach\": (0.74, \"unitless\"),\n", " \"mach_bounds\": ((0.72, 0.76), \"unitless\"),\n", @@ -134,7 +134,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.74, \"unitless\"),\n", " \"final_mach\": (0.76, \"unitless\"),\n", " \"mach_bounds\": ((0.72, 0.78), \"unitless\"),\n", @@ -158,7 +158,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.76, \"unitless\"),\n", " \"final_mach\": (0.76, \"unitless\"),\n", " \"mach_bounds\": ((0.74, 0.78), \"unitless\"),\n", @@ -182,7 +182,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.76, \"unitless\"),\n", " \"final_mach\": (0.2, \"unitless\"),\n", " \"mach_bounds\": ((0.18, 0.78), \"unitless\"),\n", @@ -223,7 +223,7 @@ "source": [ "import aviary.api as av\n", "\n", - "prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm_simple.csv',\n", + "prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm.csv',\n", " phase_info, optimizer=\"SLSQP\", make_plots=True)" ] }, diff --git a/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb b/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb index 631f5fe37..6f92f388f 100644 --- a/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb +++ b/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb @@ -54,7 +54,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 5,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.2, \"unitless\"),\n", " \"final_mach\": (0.72, \"unitless\"),\n", " \"mach_bounds\": ((0.18, 0.84), \"unitless\"),\n", @@ -78,7 +78,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 5,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.72, \"unitless\"),\n", " \"final_mach\": (0.80, \"unitless\"),\n", " \"mach_bounds\": ((0.7, 0.84), \"unitless\"),\n", @@ -102,7 +102,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 5,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.72, \"unitless\"),\n", " \"final_mach\": (0.21, \"unitless\"),\n", " \"mach_bounds\": ((0.19, 0.84), \"unitless\"),\n", @@ -126,13 +126,6 @@ "}" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "metadata": {}, @@ -155,7 +148,7 @@ "source": [ "import aviary.api as av\n", "\n", - "aircraft_filename = 'models/test_aircraft/aircraft_for_bench_FwFm_simple.csv'\n", + "aircraft_filename = 'models/test_aircraft/aircraft_for_bench_FwFm.csv'\n", "optimizer = \"IPOPT\"\n", "make_plots = True\n", "max_iter = 200\n", @@ -215,9 +208,7 @@ "# Allow for user overrides here\n", "prob.load_inputs(aircraft_filename, phase_info)\n", "\n", - "# Have checks for clashing user inputs\n", - "# Raise warnings or errors depending on how clashing the issues are\n", - "prob.check_inputs()\n", + "prob.check_and_preprocess_inputs()\n", "\n", "prob.add_pre_mission_systems()\n", "\n", @@ -335,10 +326,8 @@ "# Allow for user overrides here\n", "prob.load_inputs(aircraft_filename, phase_info)\n", "\n", - "# Have checks for clashing user inputs\n", - "# Raise warnings or errors depending on how clashing the issues are\n", - "prob.check_inputs()\n", - "\n", + "prob.check_and_preprocess_inputs()\n", + "# Preprocess inputs\n", "prob.add_pre_mission_systems()\n", "\n", "prob.add_phases()\n", diff --git a/aviary/docs/examples/intro.md b/aviary/docs/examples/intro.md index 2af40c2d9..abc285ef1 100644 --- a/aviary/docs/examples/intro.md +++ b/aviary/docs/examples/intro.md @@ -4,7 +4,7 @@ Aviary provides a range of built-in examples that serve as both regression tests and demonstrations of the tool's capabilities. These examples showcase various full mission analysis and optimization problems, incorporating different subsystem analyses from FLOPS and GASP. -You can find these examples [here](https://github.com/OpenMDAO/om-Aviary/tree/main/aviary/validation_cases/benchmark_tests), especially the files that start `test_swap`. +You can find these examples [here](https://github.com/OpenMDAO/Aviary/tree/main/aviary/validation_cases/benchmark_tests), especially the files that start `test_swap`. These cases highlight Aviary's ability to replicate GASP and FLOPS capabilities, as well as use both code's methods in a single analysis. In addition to the examples for core Aviary, we also provide some examples for using external subsystems. diff --git a/aviary/docs/examples/more_advanced_example.ipynb b/aviary/docs/examples/more_advanced_example.ipynb index ee8e50ece..865794564 100644 --- a/aviary/docs/examples/more_advanced_example.ipynb +++ b/aviary/docs/examples/more_advanced_example.ipynb @@ -36,7 +36,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.2, \"unitless\"),\n", " \"final_mach\": (0.72, \"unitless\"),\n", " \"mach_bounds\": ((0.18, 0.74), \"unitless\"),\n", @@ -60,7 +60,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.72, \"unitless\"),\n", " \"final_mach\": (0.72, \"unitless\"),\n", " \"mach_bounds\": ((0.7, 0.74), \"unitless\"),\n", @@ -84,7 +84,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 3,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.72, \"unitless\"),\n", " \"final_mach\": (0.2, \"unitless\"),\n", " \"mach_bounds\": ((0.18, 0.74), \"unitless\"),\n", @@ -125,7 +125,7 @@ "source": [ "import aviary.api as av\n", "\n", - "prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm_simple.csv',\n", + "prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm.csv',\n", " phase_info, optimizer=\"SLSQP\", make_plots=True)" ] }, @@ -188,7 +188,7 @@ "source": [ "import csv\n", "\n", - "filename = 'models/test_aircraft/aircraft_for_bench_FwFm_simple.csv'\n", + "filename = 'models/test_aircraft/aircraft_for_bench_FwFm.csv'\n", "filename = av.get_path(filename)\n", "\n", "# Read the file\n", @@ -279,7 +279,7 @@ "phase_info['cruise']['user_options']['polynomial_control_order'] = 1\n", "phase_info['descent_1']['user_options']['polynomial_control_order'] = 3\n", "\n", - "prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm_simple.csv',\n", + "prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm.csv',\n", " phase_info, optimizer=\"IPOPT\", make_plots=True)" ] }, diff --git a/aviary/docs/examples/simple_mission_example.ipynb b/aviary/docs/examples/simple_mission_example.ipynb index 40556809d..71dd7a3fb 100644 --- a/aviary/docs/examples/simple_mission_example.ipynb +++ b/aviary/docs/examples/simple_mission_example.ipynb @@ -21,7 +21,7 @@ "* value: the user-defined value of the variable\n", "* units: the units of the variable\n", "\n", - "Let's take a look at the first few lines of an example aircraft file, `aircraft_for_bench_FwFm_simple.csv`.\n", + "Let's take a look at the first few lines of an example aircraft file, `aircraft_for_bench_FwFm.csv`.\n", "This aircraft is a commercial single-aisle aircraft with two conventional turbofan engines.\n", "Think of it in the same class as a Boeing 737 or Airbus A320.\n", "\n", @@ -40,7 +40,7 @@ "source": [ "import aviary.api as av\n", "\n", - "filename = 'models/test_aircraft/aircraft_for_bench_FwFm_simple.csv'\n", + "filename = 'models/test_aircraft/aircraft_for_bench_FwFm.csv'\n", "filename = av.get_path(filename)\n", "\n", "with open(filename, 'r') as file:\n", @@ -143,7 +143,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 2,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.2, \"unitless\"),\n", " \"final_mach\": (0.72, \"unitless\"),\n", " \"mach_bounds\": ((0.18, 0.74), \"unitless\"),\n", @@ -167,7 +167,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 2,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.72, \"unitless\"),\n", " \"final_mach\": (0.72, \"unitless\"),\n", " \"mach_bounds\": ((0.7, 0.74), \"unitless\"),\n", @@ -191,7 +191,7 @@ " \"polynomial_control_order\": 1,\n", " \"num_segments\": 2,\n", " \"order\": 3,\n", - " \"solve_for_range\": False,\n", + " \"solve_for_distance\": False,\n", " \"initial_mach\": (0.72, \"unitless\"),\n", " \"final_mach\": (0.2, \"unitless\"),\n", " \"mach_bounds\": ((0.18, 0.74), \"unitless\"),\n", @@ -279,7 +279,7 @@ "source": [ "import aviary.api as av\n", "\n", - "prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm_simple.csv',\n", + "prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm.csv',\n", " phase_info, optimizer=\"SLSQP\", make_plots=True)" ] }, diff --git a/aviary/docs/getting_started/input_csv_phase_info.md b/aviary/docs/getting_started/input_csv_phase_info.md index 08fc04076..f0d4c0d8a 100644 --- a/aviary/docs/getting_started/input_csv_phase_info.md +++ b/aviary/docs/getting_started/input_csv_phase_info.md @@ -6,6 +6,28 @@ This section is under development. ``` +`initial_guesses` is a dictionary that contains values used to initialize the trajectory. It contains the following keys along with default values: + +- actual_takeoff_mass: 0, +- rotation_mass: .99, +- operating_empty_mass: 0, +- fuel_burn_per_passenger_mile: 0.1, +- cruise_mass_final: 0, +- flight_duration: 0, +- time_to_climb: 0, +- climb_range: 0, +- reserves: 0 + +The initial guess of `reserves` is used to define the reserve fuel. Initially, its value can be anything larger than or equal to 0. There are two Aviary variables to control the reserve fuel in the model file (`.csv`): +- `Aircraft.Design.RESERVE_FUEL_ADDITIONAL`: the required fuel reserves: directly in lbm, +- `Aircraft.Design.RESERVE_FUEL_FRACTION`: the required fuel reserves: given as a proportion of mission fuel. + +If the value of initial guess of `reserves` (also in the model file if any) is 0, the initial guess of reserve fuel comes from the above two Aviary variables. Otherwise, it is determined by the parameter `reserves`: +- if `reserves > 10`, we assume it is the actual fuel reserves. +- if `0.0 <= reserves <= 10`, we assume it is the fraction of the mission fuel. + +The initial guess of `reserves` is always converted to the actual design reserves (instead of reserve factor) and is used to update the initial guesses of `fuel_burn_per_passenger_mile` and `cruise_mass_final`. + ## Phase Info Dictionary `phase_info` is a nested dictionary that Aviary uses for users to define their mission phases - how they are built, the design variables, constraints, connections, etc. @@ -27,7 +49,7 @@ We will now discuss the meaning of the keys within the `phase_info` objects. - If a key ends with `_constraint_eq`, it is an equality constraint. - Keys related to altitude: - - In `FLOPS` missions, it is `final_altitude`. In GASP missions, it is `final_alt`. + - We use `final_altitude` to indicate the final altitude of the phase. - Meanwhile, `alt` is a key in acceleration phase parameter for altitude in `GASP` missions and `altitude` is a key in all other phases of all missions. - Some keys are a boolean flag of True or False: @@ -67,7 +89,7 @@ We will now discuss the meaning of the keys within the `phase_info` objects. - `fix_duration`: default to `False`. ```{note} -Not all the keys apply to all phases. The users should select the right keys for each phase of interest. The required keys for each phase are defined in [check_phase_info](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/interface/utils.py) function. Currently, this function does the check only for `FLOPS` and `GASP` missions. +Not all the keys apply to all phases. The users should select the right keys for each phase of interest. The required keys for each phase are defined in [check_phase_info](https://github.com/OpenMDAO/Aviary/blob/main/aviary/interface/utils.py) function. Currently, this function does the check only for `FLOPS` and `GASP` missions. ``` Users can add their own keys as needed. diff --git a/aviary/docs/getting_started/installation.md b/aviary/docs/getting_started/installation.md index e2484307d..4691430a9 100644 --- a/aviary/docs/getting_started/installation.md +++ b/aviary/docs/getting_started/installation.md @@ -17,6 +17,10 @@ If you also want to install all packages used for the Aviary tests _and_ externa If you are a developer and plan to modify parts of the Aviary code, install in an "editable mode" with pip: + pip install . + +If you are a developer and plan to modify parts of the Aviary code, install in an "editable mode" with ``pip``: + pip install -e . This installs the package in the current environment such that changes to the Python code don't require re-installation. @@ -61,7 +65,6 @@ dependencies: - pip: - parameterized - testflo - - pyxdsm - jupyter-book - mdolab-baseclasses - sqlitedict @@ -71,7 +74,7 @@ dependencies: In this file, the `name` can be anything you like. The version of python is not limited to 3.9, but we recommend that you stay with this version because it is the version that we use to fully test Aviary and that it is required for some packages later on. For example, if you are going to add `OpenVSP` to your environment, you will find that you need this version. -In the list, we see the popular Python packages for scientific computations: `numpy`, `scipy`, `matplotlib` and `pandas`. Aviary follows a standard source code formatting convention. `autopep8` provides an easy way to check your source code for this purpose. `jupyter` and `jupyter-book` are used to create Aviary manual. `parameterized` and `testflo` are for Aviary testing. `pyxdsm` is used to create XDSM diagrams. Aviary uses a lot of packages developed by [MDOLab](https://mdolab.engin.umich.edu/). So, we want to include its base classes. OpenMDAO records data in SQLite database and that is what `sqlitedict` comes for. `f90nml` is A Python module and command line tool for parsing Fortran namelist files. `bokeh` is an interactive visualization library for modern web browsers. It is needed to generate Aviary output (traj_results_report.html). +In the list, we see the popular Python packages for scientific computations: `numpy`, `scipy`, `matplotlib` and `pandas`. Aviary follows a standard source code formatting convention. `autopep8` provides an easy way to check your source code for this purpose. `jupyter` and `jupyter-book` are used to create Aviary manual. `parameterized` and `testflo` are for Aviary testing. Aviary uses a lot of packages developed by [MDOLab](https://mdolab.engin.umich.edu/). So, we want to include its base classes. OpenMDAO records data in SQLite database and that is what `sqlitedict` comes for. `f90nml` is A Python module and command line tool for parsing Fortran namelist files. `bokeh` is an interactive visualization library for modern web browsers. It is needed to generate Aviary output (traj_results_report.html). Since we are going to depend on `OpenMDAO` and `dymos`, we could have included them in the `pip` list. We leave them out because we will install the developer version later. In this way, we will get the latest working copies that Aviary depends on. But we do not intend to make changes to them. @@ -215,7 +218,7 @@ Successfully installed pyoptsparse-2.10.1 ### Installing Aviary and Running Tests -Now, we are ready to install Aviary. Assuming that you will become a contributor sooner or later, we want to install a copy from the main source. (You will need a GitHub account for this) Let us open `https://github.com/openMDAO/om-aviary/` in a web browser and click [fork](https://github.com/OpenMDAO/om-Aviary/fork) on the top-right corner. You then have created your own copy of Aviary on GitHub website. Now we create a copy on your local drive (supposing `USER_ID` is your GitHub account ID): +Now, we are ready to install Aviary. Assuming that you will become a contributor sooner or later, we want to install a copy from the main source. (You will need a GitHub account for this) Let us open `https://github.com/openMDAO/om-aviary/` in a web browser and click [fork](https://github.com/OpenMDAO/Aviary/fork) on the top-right corner. You then have created your own copy of Aviary on GitHub website. Now we create a copy on your local drive (supposing `USER_ID` is your GitHub account ID): ``` $ cd ~/workspace @@ -224,13 +227,6 @@ $ cd Aviary $ pip install -e . ``` -Before we run tests on Aviary, we need to build XDSM diagrams first: - -``` -% cd aviary/xdsm/ -% python run_all.py -``` - When it is done, let us run test: ``` diff --git a/aviary/docs/getting_started/onboarding_ext_subsystem.ipynb b/aviary/docs/getting_started/onboarding_ext_subsystem.ipynb index c7dea5425..852c6c9c1 100644 --- a/aviary/docs/getting_started/onboarding_ext_subsystem.ipynb +++ b/aviary/docs/getting_started/onboarding_ext_subsystem.ipynb @@ -52,7 +52,7 @@ "\n", "# Max iterations set to 1 to reduce runtime of example\n", "max_iter = 1\n", - "phase_info = deepcopy(av.default_simple_phase_info)\n", + "phase_info = deepcopy(av.default_height_energy_phase_info)\n", "# Here we just add the simple weight system to only the pre-mission\n", "phase_info['pre_mission']['external_subsystems'] = [WingWeightBuilder(name=\"wing_external\")]\n", "\n", @@ -60,11 +60,11 @@ "\n", "# Load aircraft and options data from user\n", "# Allow for user overrides here\n", - "prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm_simple.csv', phase_info)\n", + "prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm.csv', phase_info)\n", "\n", "# Have checks for clashing user inputs\n", "# Raise warnings or errors depending on how clashing the issues are\n", - "# prob.check_inputs()\n", + "# prob.check_and_preprocess_inputs()\n", "\n", "prob.add_pre_mission_systems()\n", "\n", @@ -119,11 +119,11 @@ "\n", "# Load aircraft and options data from user\n", "# Allow for user overrides here\n", - "prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm_simple.csv', av.default_simple_phase_info)\n", + "prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm.csv', av.default_height_energy_phase_info)\n", "\n", "# Have checks for clashing user inputs\n", "# Raise warnings or errors depending on how clashing the issues are\n", - "# prob.check_inputs()\n", + "# prob.check_and_preprocess_inputs()\n", "\n", "prob.add_pre_mission_systems()\n", "\n", @@ -170,7 +170,7 @@ "\n", "- **init**\n", "- **load_inputs**\n", - "- **check_inputs**\n", + "- **check_and_preprocess_inputs**\n", "- **add_pre_mission_systems**\n", "- **add_phases**\n", "- **add_post_mission_systems**\n", @@ -231,7 +231,7 @@ "source": [ "prob = av.AviaryProblem()\n", "\n", - "prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm_simple.csv', phase_info)" + "prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm.csv', phase_info)" ] }, { @@ -240,7 +240,7 @@ "metadata": {}, "source": [ "\n", - "In the battery subsystem, the type of battery cell we use is `18650`. The option is not set in `input_file`, instead is is controlled by importing the correct battery map [here](https://github.com/OpenMDAO/om-Aviary/blob/1fca1c03cb2e1d6387442162e8d7dabf83eee197/aviary/examples/external_subsystems/battery/model/reg_thevenin_interp_group.py#L5)." + "In the battery subsystem, the type of battery cell we use is `18650`. The option is not set in `input_file`, instead is is controlled by importing the correct battery map [here](https://github.com/OpenMDAO/Aviary/blob/1fca1c03cb2e1d6387442162e8d7dabf83eee197/aviary/examples/external_subsystems/battery/model/reg_thevenin_interp_group.py#L5)." ] }, { @@ -260,7 +260,7 @@ "source": [ "# Have checks for clashing user inputs\n", "# Raise warnings or errors depending on severity of the issues\n", - "prob.check_inputs()" + "prob.check_and_preprocess_inputs()" ] }, { @@ -315,7 +315,7 @@ "# user defined outputs\n", "print('Battery MASS', prob.get_val(Aircraft.Battery.MASS))\n", "print('Cell Max', prob.get_val(Aircraft.Battery.Cell.MASS))\n", - "masses_descent = prob.get_val('traj.descent.timeseries.states:mass', units='kg')\n", + "masses_descent = prob.get_val('traj.descent.timeseries.mass', units='kg')\n", "print(f\"Final Descent Mass: {masses_descent[-1]}\")\n", "\n", "print('done')" @@ -359,7 +359,7 @@ "metadata": {}, "outputs": [], "source": [ - "masses_descent = prob.get_val('traj.descent.timeseries.states:mass', units='kg')\n", + "masses_descent = prob.get_val('traj.descent.timeseries.mass', units='kg')\n", "print(f\"Final Descent Mass: {masses_descent[-1]}\")" ] }, @@ -381,7 +381,7 @@ "\n", "```\n", "soc_cruise = prob.get_val(\n", - " 'traj.climb.timeseries.states:mission:battery:state_of_charge')\n", + " 'traj.climb.timeseries.mission:battery:state_of_charge')\n", "print(f\"State of Charge: {soc_cruise[-1]}\")\n", "```\n", "\n", @@ -532,7 +532,7 @@ " 'polynomial_control_order': 1,\n", " 'num_segments': 5,\n", " 'order': 3,\n", - " 'solve_for_range': False,\n", + " 'solve_for_distance': False,\n", " 'initial_mach': (0.2, 'unitless'),\n", " 'final_mach': (0.72, 'unitless'),\n", " 'mach_bounds': ((0.18, 0.74), 'unitless'),\n", @@ -556,7 +556,7 @@ " 'polynomial_control_order': 1,\n", " 'num_segments': 5,\n", " 'order': 3,\n", - " 'solve_for_range': False,\n", + " 'solve_for_distance': False,\n", " 'initial_mach': (0.72, 'unitless'),\n", " 'final_mach': (0.72, 'unitless'),\n", " 'mach_bounds': ((0.7, 0.74), 'unitless'),\n", @@ -580,7 +580,7 @@ " 'polynomial_control_order': 1,\n", " 'num_segments': 5,\n", " 'order': 3,\n", - " 'solve_for_range': False,\n", + " 'solve_for_distance': False,\n", " 'initial_mach': (0.72, 'unitless'),\n", " 'final_mach': (0.36, 'unitless'),\n", " 'mach_bounds': ((0.34, 0.74), 'unitless'),\n", @@ -622,7 +622,7 @@ "metadata": {}, "outputs": [], "source": [ - "aircraft_definition_file = 'models/test_aircraft/aircraft_for_bench_FwFm_simple.csv'\n", + "aircraft_definition_file = 'models/test_aircraft/aircraft_for_bench_FwFm.csv'\n", "make_plots = False\n", "max_iter = 0\n", "optimizer = 'SNOPT'\n", @@ -630,7 +630,7 @@ "prob = av.AviaryProblem()\n", "\n", "prob.load_inputs(aircraft_definition_file, phase_info)\n", - "prob.check_inputs()\n", + "prob.check_and_preprocess_inputs()\n", "prob.add_pre_mission_systems()\n", "prob.add_phases()\n", "prob.add_post_mission_systems()\n", @@ -755,7 +755,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.8.17" } }, "nbformat": 4, diff --git a/aviary/docs/getting_started/onboarding_level1.ipynb b/aviary/docs/getting_started/onboarding_level1.ipynb index 62fff50a8..44cd459c0 100644 --- a/aviary/docs/getting_started/onboarding_level1.ipynb +++ b/aviary/docs/getting_started/onboarding_level1.ipynb @@ -253,7 +253,7 @@ " - `mission:objectives:fuel` if `mission_method` is `GASP` \n", " - `fuel_burned` if `mission_method` is `FLOPS`.\n", "\n", - "- `--phase_info`: Path to phase info file. If not provided, it is `default_phase_info/gasp.py` if Mission origin is `2DOF` and `default_phase_info/flops.py` for `height_energy`.\n", + "- `--phase_info`: Path to phase info file. If not provided, it is `default_phase_info/gasp.py` if Mission origin is `2DOF` and `default_phase_info/flops.py` for `simple`.\n", "\n", "- `--n2`: Generate an n2 diagram after the analysis. “[N2](https://openmdao.org/newdocs/versions/latest/features/model_visualization/n2_basics/n2_basics.html)” diagram is an OpenMDAO helper to visualize the model (see [details](https://openmdao.org/newdocs/versions/latest/features/model_visualization/n2_details/n2_details.html) too and [tutorial](https://www.youtube.com/watch?v=42VtbX6CX3A)). It is highly recommended that this option is turned on for new users.\n", "\n", @@ -523,10 +523,10 @@ "source": [ "### File `n2.html`\n", "\n", - "N2 is a powerful tool in OpenMDAO. It is an N-squared diagram in the shape of a matrix, representing functional or physical interfaces between system elements. It can be used to systematically identify, define, tabulate, design, and analyze functional and physical interfaces.\n", + "[N2](https://openmdao.org/newdocs/versions/latest/features/model_visualization/n2_details/n2_details.html), sometimes referred to as an eXtended Design Structure Matrix (XDSM), is a powerful tool for understanding your model in OpenMDAO. It is an N-squared diagram in the shape of a matrix representing functional or physical interfaces between system elements. It can be used to systematically identify, define, tabulate, design, and analyze functional and physical interfaces.\n", "\n", "```{note}\n", - "We *strongly* recommend that you understand N2 diagrams well, especially if you are debugging a model or adding external subsystems. Here is a [doc page](https://openmdao.github.io/PracticalMDO/Notebooks/ModelConstruction/using_n2.html) featuring an informative video on how to use N2 diagrams effectively.\n", + "We *strongly* recommend that you understand N2 diagrams well, especially if you are debugging a model or adding external subsystems. Here is a [doc page](https://openmdao.github.io/PracticalMDO/Notebooks/ModelConstruction/using_n2.html) featuring an informative video on how to use N2 diagrams effectively. MDO lab has another resource for learning about N2 and [XDSM](https://mdolab-pyxdsm.readthedocs-hosted.com/).\n", "```\n", "\n", " Here is a screenshot of an N2 diagram after we run `aviary run_mission models/test_aircraft/aircraft_for_bench_GwGm.csv`.\n", diff --git a/aviary/docs/getting_started/onboarding_level2.ipynb b/aviary/docs/getting_started/onboarding_level2.ipynb index 317f42cdd..4a2ba0ffc 100644 --- a/aviary/docs/getting_started/onboarding_level2.ipynb +++ b/aviary/docs/getting_started/onboarding_level2.ipynb @@ -42,7 +42,7 @@ "\n", "- `prob = AviaryProblem()`\n", "- `prob.load_inputs()`\n", - "- `prob.check_inputs()`\n", + "- `prob.check_and_preprocess_inputs()`\n", "- `prob.add_pre_mission_systems()`\n", "- `prob.add_phases()`\n", "- `prob.add_post_mission_systems()`\n", @@ -76,6 +76,7 @@ "source": [ "from aviary.api import Aircraft, Mission\n", "import aviary.api as av\n", + "from copy import deepcopy\n", "\n", "\n", "# inputs that run_aviary() requires\n", @@ -86,17 +87,17 @@ "record_filename = 'aviary_history.db'\n", "restart_filename = None\n", "max_iter = 0\n", + "phase_info = deepcopy(av.default_2DOF_phase_info)\n", "\n", "# Build problem\n", "prob = av.AviaryProblem(analysis_scheme)\n", "\n", "# Load aircraft and options data from user\n", "# Allow for user overrides here\n", - "prob.load_inputs(aircraft_filename, av.default_2DOF_phase_info)\n", + "prob.load_inputs(aircraft_filename, phase_info)\n", "\n", - "# Have checks for clashing user inputs\n", - "# Raise warnings or errors depending on how clashing the issues are\n", - "prob.check_inputs()\n", + "# Preprocess inputs\n", + "prob.check_and_preprocess_inputs()\n", "\n", "# adds a pre-mission group (propulsion, geometry, static aerodynamics, and mass)\n", "prob.add_pre_mission_systems()\n", @@ -197,7 +198,8 @@ "metadata": {}, "outputs": [], "source": [ - "prob.load_inputs(aircraft_filename, av.default_2DOF_phase_info)" + "phase_info = deepcopy(av.default_2DOF_phase_info)\n", + "prob.load_inputs(aircraft_filename, phase_info)" ] }, { @@ -207,11 +209,14 @@ "source": [ "is a function that has a few tasks:\n", "\n", - "- read aircraft deck file `aircraft_filename`\n", - "- read phase info file `phase_info`\n", - "- build core subsystems, including setting up engines\n", + "- Read aircraft deck file `aircraft_filename`\n", + "- Read phase info file `phase_info`\n", + "- Build core subsystems\n", + "\n", + "We have seen `aircraft_filename` file (a `.csv` file) in our level 1 examples. In [level 1](onboarding_level1), we simply called it input file. An aircraft model can also be directly defined in Python, by setting up an `AviaryValues` object with the desired inputs and options normally found in an input file. That object can be provided in the place of `aircraft_filename`.\n", + "\n", "\n", - "We have seen `aircraft_filename` file (a `.csv` file) in our level 1 examples. In [level 1](onboarding_level1), we simply called it input file. Engine is built by using `aircraft:engine:data_file` in the .csv file. For example in `aircraft_for_bench_GwGm.csv` file, we see:\n", + "Engines are built by using `aircraft:engine:data_file` in the .csv file. For example in `aircraft_for_bench_GwGm.csv` file, we see:\n", "\n", "```\n", "aircraft:engine:data_file,models/engines/turbofan_28k.deck,unitless\n", @@ -227,7 +232,7 @@ "| 0.0, | 0.0, | 42.0, | 21214.0, | 0.0, | 5979.1, | 19.8547 |\n", "| 0.0, | 0.0, | 38.0, | 17356.9, | 0.0, | 4795.2, | 17.5877 |\n", "\n", - "The engine builder allows users to provide an `EngineModel` instance of their own to use in Aviary's propulsion systems.\n", + "Users can provide an `EngineModel` instance of their own to use in Aviary's propulsion systems by adding it to `engine_models`.\n", "\n", "Other subsystems, including mass, geometry, and aerodynamics, are set up according to which legacy code options the user has specified in their input file, using `settings:equations_of_motion` and `settings:mass_method`. Aerodynamics is set up to match the selected equations of motion, while geometry will use either GASP, FLOPS, or both methods as required to calculate all values needed by other subsystems.\n", "\n", @@ -241,7 +246,7 @@ "metadata": {}, "outputs": [], "source": [ - "prob.check_inputs()" + "prob.check_and_preprocess_inputs()" ] }, { @@ -315,7 +320,7 @@ "id": "0c8038df", "metadata": {}, "source": [ - "Similar to pre-mission, it adds a landing phase if `include_landing` key of `post_mission` has value of `True`. If user chooses to define a `post_mission`, it will override the default. For `2dof` missions, landing is defined in [mission/gasp_based/phases/landing_group.py](https://github.com/OpenMDAO/Aviary/blob/main/aviary/mission/gasp_based/phases/landing_group.py). For `height_energy` mission, landing means a [simplified landing](https://github.com/OpenMDAO/Aviary/blob/main/aviary/mission/flops_based/phases/simplified_landing.py). Note that the `solved` method currently doesn't have any post mission systems.\n", + "Similar to pre-mission, it adds a landing phase if `include_landing` key of `post_mission` has value of `True`. If user chooses to define a `post_mission`, it will override the default. For `2dof` missions, landing is defined in [mission/gasp_based/phases/landing_group.py](https://github.com/OpenMDAO/Aviary/blob/main/aviary/mission/gasp_based/phases/landing_group.py). For `simple` mission, landing means a [simplified landing](https://github.com/OpenMDAO/Aviary/blob/main/aviary/mission/flops_based/phases/simplified_landing.py). Note that the `solved` method currently doesn't have any post mission systems.\n", "\n", "The next line is" ] @@ -337,7 +342,7 @@ "source": [ "This is important for allowing each phase of flight to pass to the next without discontinuities in the parameters. Consider Dymos' [Aircraft Balanced Field Length Calculation](https://openmdao.github.io/dymos/examples/balanced_field/balanced_field.html) example. In that example, we see separate nonlinear boundary constraints, nonlinear path constraints, and phase continuity constraints between phases. We don't want to go deeper in this function call, but just point out that each individual link can be set via dymos function `link_phases`. See [dymos API](https://openmdao.github.io/dymos/api/trajectory_api.html) for more details.\n", "\n", - "The code blocks in this function (namely, `link_phases()`) are for `2DOF`, `height_energy`, and `solved` missions. The links are set up based on physical principals (e.g. you can’t have instantaneous changes in mass, velocity, position etc.). Special care is required if the user selects a different or unusual set of phases. \n", + "The code blocks in this function (namely, `link_phases()`) are for `2DOF`, `simple`, and `solved` missions. The links are set up based on physical principals (e.g. you can’t have instantaneous changes in mass, velocity, position etc.). Special care is required if the user selects a different or unusual set of phases. \n", "\n", "Now, our aircraft and the mission are fully defined. We are ready to define an optimization problem. This is achieved by adding an optimization driver, adding design variables, and an objective. \n", "\n", @@ -513,7 +518,7 @@ "id": "57986647", "metadata": {}, "source": [ - "For `height_energy` and `2DOF` missions, this method performs several calls to `set_val` on the trajectory for states and controls to seed the problem with reasonable initial guesses using `initial_guesses` within corresponding phases (e.g. `default_flops_phases.py` and `default_gasp_phases.py`). For `solved` missions, it performs similar tasks but for hard-coded state parameters. This is reasonable because a `solved` mission is actually a level 3 Aviary approach. We will cover it in [level 3 onboarding doc](onboarding_level3.ipynb) in the next page. Note that initial guesses for all phases are especially important for collocation methods.\n", + "For `simple` and `2DOF` missions, this method performs several calls to `set_val` on the trajectory for states and controls to seed the problem with reasonable initial guesses using `initial_guesses` within corresponding phases (e.g. `default_flops_phases.py` and `default_gasp_phases.py`). For `solved` missions, it performs similar tasks but for hard-coded state parameters. This is reasonable because a `solved` mission is actually a level 3 Aviary approach. We will cover it in [level 3 onboarding doc](onboarding_level3.ipynb) in the next page. Note that initial guesses for all phases are especially important for collocation methods.\n", "\n", "The last line is to run the problem we just set up:" ] @@ -637,32 +642,26 @@ " 'core_aerodynamics': {'method': 'computed'}\n", " },\n", " 'user_options': {\n", - " 'fix_initial': False,\n", - " 'fix_duration': True,\n", - " 'num_segments': 1,\n", + " 'optimize_mach': False,\n", + " 'optimize_altitude': False,\n", + " 'polynomial_control_order': 1,\n", + " 'num_segments': 2,\n", " 'order': 3,\n", - " 'initial_bounds': ((500., 4000.), 's'),\n", - " 'initial_ref': (1., 's'),\n", - " 'duration_ref': (24370.8, 's'),\n", - " 'duration_bounds': ((726., 48741.6), 's'),\n", - " 'min_altitude': (10.668e3, 'm'),\n", - " 'max_altitude': (10.668e3, 'm'),\n", - " 'min_mach': 0.79,\n", - " 'max_mach': 0.79,\n", - " 'fix_final': False,\n", - " 'required_available_climb_rate': (1.524, 'm/s'),\n", - " 'mass_f_cruise': (1.e4, 'lbm'),\n", - " 'range_f_cruise': (1.e6, 'm'),\n", + " 'solve_for_distance': False,\n", + " 'initial_mach': (0.72, 'unitless'),\n", + " 'final_mach': (0.72, 'unitless'),\n", + " 'mach_bounds': ((0.7, 0.74), 'unitless'),\n", + " 'initial_altitude': (35000.0, 'ft'),\n", + " 'final_altitude': (35000.0, 'ft'),\n", + " 'altitude_bounds': ((23000.0, 38000.0), 'ft'),\n", + " 'throttle_enforcement': 'boundary_constraint',\n", + " 'fix_initial': True,\n", + " 'constrain_final': False,\n", + " 'fix_duration': False,\n", + " 'initial_bounds': ((0.0, 0.0), 'min'),\n", + " 'duration_bounds': ((10., 30.), 'min'),\n", " },\n", - " 'initial_guesses': {\n", - " 'times': ([26.2, 406.18], 'min'),\n", - " 'altitude': ([35.e3, 35.e3], 'ft'),\n", - " 'velocity': ([455.49, 455.49], 'kn'),\n", - " 'mass': ([165.e3, 140.e3], 'lbm'),\n", - " 'range': ([160.3, 3243.9], 'nmi'),\n", - " 'velocity_rate': ([0., 0.], 'm/s**2'),\n", - " 'throttle': ([0.95, 0.9], 'unitless'),\n", - " }\n", + " 'initial_guesses': {'times': ([0, 30], 'min')},\n", " },\n", " 'post_mission': {\n", " 'include_landing': False,\n", @@ -671,7 +670,7 @@ "\n", "# inputs that run_aviary() requires\n", "aircraft_filename = \"models/test_aircraft/aircraft_for_bench_FwFm.csv\"\n", - "mission_method = \"FLOPS\"\n", + "mission_method = \"height_energy_energy\"\n", "mass_method = \"FLOPS\"\n", "optimizer = \"SLSQP\"\n", "analysis_scheme = av.AnalysisScheme.COLLOCATION\n", @@ -686,9 +685,8 @@ "# Allow for user overrides here\n", "prob.load_inputs(aircraft_filename, phase_info)\n", "\n", - "# Have checks for clashing user inputs\n", - "# Raise warnings or errors depending on how clashing the issues are\n", - "prob.check_inputs()\n", + "# Preprocess inputs\n", + "prob.check_and_preprocess_inputs()\n", "\n", "prob.add_pre_mission_systems()\n", "\n", @@ -725,33 +723,8 @@ "\n", "We will see more details for what users can do in [level 3](onboarding_level3.ipynb).\n", "\n", - "Level 2 is where you can integrate user-defined [external subsystems](https://github.com/OpenMDAO/Aviary/blob/main/aviary/docs/user_guide/subsystems.md), which is one of the main features of the Aviary tool. [Examples](https://github.com/OpenMDAO/Aviary/blob/main/aviary/docs/user_guide/using_external_subsystems.md) of external subsystems are: acoustics, battery modeling, etc.\n", - "Assume that you already have an external subsystem that you want to incorporate it into your model. We show how to add external subsystems via `external_subsystems` key in `phase_info`. External subsystems can be added in all phases. For example, we can have:\n", - "\n", - "```\n", - "from aviary.subsystems.test.test_dummy_subsystem import PreOnlyBuilder, PostOnlyBuilder, \\\n", - " ArrayGuessSubsystemBuilder, AdditionalArrayGuessSubsystemBuilder\n", - "\n", - "phase_info = {\n", - "\t'pre_mission': {\n", - "\t\t'include_takeoff': True,\n", - "\t\t'external_subsystems': [ArrayGuessSubsystemBuilder()],\n", - "\t\t'optimize_mass': False,\n", - "\t},\n", - "\t'cruise': {\n", - "\t\t...\n", - " 'external_subsystems': \n", - " [\n", - " ArrayGuessSubsystemBuilder(),\n", - " AdditionalArrayGuessSubsystemBuilder()\n", - " ],\n", - "\t},\n", - "\t'post_mission': {\n", - "\t\t'include_landing': False,\n", - "\t\t'external_subsystems': [PostOnlyBuilder()],\n", - "\t}\n", - "}\n", - "```\n", + "Level 2 is where you can integrate user-defined [external subsystems](../user_guide/subsystems.md), which is one of the main features of the Aviary tool. [Examples](../user_guide/using_external_subsystems.md) of external subsystems are: acoustics, battery modeling, etc.\n", + "Assume that you already have an external subsystem that you want to incorporate it into your model. We show how to add external subsystems via `external_subsystems` key in `phase_info`.\n", "\n", "We will cover external subsystems in details in [Models with External Subsystems](onboarding_ext_subsystem.ipynb) page.\n" ] @@ -866,9 +839,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Have checks for clashing user inputs\n", - "# Raise warnings or errors depending on how clashing the issues are\n", - "prob.check_inputs()\n", + "# Preprocess inputs\n", + "prob.check_and_preprocess_inputs()\n", "\n", "prob.add_pre_mission_systems()\n", "\n", @@ -931,7 +903,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.8.17" } }, "nbformat": 4, diff --git a/aviary/docs/getting_started/onboarding_level3.ipynb b/aviary/docs/getting_started/onboarding_level3.ipynb index 0f4bb0fbe..49c9136b6 100644 --- a/aviary/docs/getting_started/onboarding_level3.ipynb +++ b/aviary/docs/getting_started/onboarding_level3.ipynb @@ -34,7 +34,7 @@ "In `aviary/validation_cases/benchmark_tests` folder, there is an N3CC aircraft full mission benchmark test `test_FLOPS_based_full_mission_N3CC.py`. Now, we will show how to create an N3CC example in level3. The key is that we follow the same steps:\n", "- init\n", "- load_inputs\n", - "- check_inputs (optional)\n", + "- check_and_preprocess_inputs (optional)\n", "- add_pre_mission_systems\n", "- add_phases\n", "- add_post_mission_systems\n", @@ -69,28 +69,19 @@ "Computed Aero\n", "N3CC data\n", "'''\n", - "import warnings\n", "\n", "import dymos as dm\n", "import openmdao.api as om\n", "import scipy.constants as _units\n", - "from packaging import version\n", "\n", - "from aviary.api import Aircraft, Mission, Dynamic\n", "import aviary.api as av\n", + "from aviary.validation_cases.validation_tests import get_flops_inputs\n", "\n", - "use_new_dymos_syntax = version.parse(dm.__version__) > version.parse(\"1.8.0\")\n", "\n", - "# benchmark based on N3CC (fixed cruise alt) FLOPS model\n", - "\n", - "\n", - "##########################################\n", - "# init problem #\n", - "##########################################\n", "prob = om.Problem(model=om.Group())\n", - "\n", - "prob.driver = driver = om.pyOptSparseDriver(optimizer='IPOPT')\n", - "driver.opt_settings[\"max_iter\"] = 0\n", + "driver = prob.driver = om.pyOptSparseDriver()\n", + "driver.options[\"optimizer\"] = \"IPOPT\"\n", + "driver.opt_settings[\"max_iter\"] = 100\n", "driver.opt_settings[\"tol\"] = 1e-3\n", "driver.opt_settings['print_level'] = 4\n", "\n", @@ -98,40 +89,34 @@ "# Aircraft Input Variables and Options #\n", "##########################################\n", "\n", - "aviary_inputs = av.get_flops_inputs('N3CC')\n", + "aviary_inputs = get_flops_inputs('N3CC')\n", "\n", + "aviary_inputs.set_val(av.Mission.Landing.LIFT_COEFFICIENT_MAX,\n", + " 2.4, units=\"unitless\")\n", + "aviary_inputs.set_val(av.Mission.Takeoff.LIFT_COEFFICIENT_MAX,\n", + " 2.0, units=\"unitless\")\n", "aviary_inputs.set_val(\n", - " Mission.Landing.LIFT_COEFFICIENT_MAX, 2.4, units='unitless')\n", - "aviary_inputs.set_val(\n", - " Mission.Takeoff.LIFT_COEFFICIENT_MAX, 2.0, units='unitless')\n", - "aviary_inputs.set_val(\n", - " Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, val=.0175,\n", - " units='unitless')\n", + " av.Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT,\n", + " val=.0175, units=\"unitless\")\n", "\n", - "takeoff_fuel_burned = 577 # lbm\n", - "takeoff_thrust_per_eng = 24555.5 # lbf\n", - "takeoff_L_over_D = 17.35\n", + "takeoff_fuel_burned = 577 # lbm TODO: where should this get connected from?\n", + "takeoff_thrust_per_eng = 24555.5 # lbf TODO: where should this get connected from?\n", + "takeoff_L_over_D = 17.35 # TODO: should this come from aero?\n", "\n", - "aviary_inputs.set_val(\n", - " Mission.Takeoff.FUEL_SIMPLE, takeoff_fuel_burned, units='lbm')\n", - "aviary_inputs.set_val(\n", - " Mission.Takeoff.LIFT_OVER_DRAG, takeoff_L_over_D, units='unitless')\n", - "aviary_inputs.set_val(\n", - " Mission.Design.THRUST_TAKEOFF_PER_ENG, takeoff_thrust_per_eng, units='lbf')\n", - "\n", - "##########################################\n", - "# Phase Info #\n", - "##########################################\n", + "aviary_inputs.set_val(av.Mission.Takeoff.FUEL_SIMPLE,\n", + " takeoff_fuel_burned, units='lbm')\n", + "aviary_inputs.set_val(av.Mission.Takeoff.LIFT_OVER_DRAG,\n", + " takeoff_L_over_D, units=\"unitless\")\n", + "aviary_inputs.set_val(av.Mission.Design.THRUST_TAKEOFF_PER_ENG,\n", + " takeoff_thrust_per_eng, units='lbf')\n", "\n", "alt_airport = 0 # ft\n", - "cruise_mach = .785\n", + "cruise_mach = 0.79\n", "\n", "alt_i_climb = 0*_units.foot # m\n", "alt_f_climb = 35000.0*_units.foot # m\n", "mass_i_climb = 131000*_units.lb # kg\n", "mass_f_climb = 126000*_units.lb # kg\n", - "v_i_climb = 198.44*_units.knot # m/s\n", - "v_f_climb = 455.49*_units.knot # m/s\n", "# initial mach set to lower value so it can intersect with takeoff end mach\n", "# mach_i_climb = 0.3\n", "mach_i_climb = 0.2\n", @@ -148,10 +133,7 @@ "alt_max_cruise = 35000*_units.foot # m\n", "mass_i_cruise = 126000*_units.lb # kg\n", "mass_f_cruise = 102000*_units.lb # kg\n", - "v_i_cruise = 455.49*_units.knot # m/s\n", - "v_f_cruise = 455.49*_units.knot # m/s\n", - "mach_min_cruise = cruise_mach\n", - "mach_max_cruise = cruise_mach\n", + "cruise_mach = cruise_mach\n", "range_i_cruise = 160.3*_units.nautical_mile # m\n", "range_f_cruise = 3243.9*_units.nautical_mile # m\n", "t_i_cruise = 26.20*_units.minute # sec\n", @@ -162,22 +144,35 @@ "# final altitude set to 35 to ensure landing is feasible point\n", "# alt_f_descent = 0*_units.foot\n", "alt_f_descent = 35*_units.foot\n", - "v_i_descent = 455.49*_units.knot\n", - "v_f_descent = 198.44*_units.knot\n", - "mach_i_descent = cruise_mach\n", + "mach_i_descent = 0.79\n", "mach_f_descent = 0.3\n", "mass_i_descent = 102000*_units.pound\n", "mass_f_descent = 101000*_units.pound\n", - "range_i_descent = 3243.9*_units.nautical_mile\n", - "range_f_descent = 3378.7*_units.nautical_mile\n", + "distance_i_descent = 3243.9*_units.nautical_mile\n", + "distance_f_descent = 3378.7*_units.nautical_mile\n", "t_i_descent = 432.38*_units.minute\n", "t_f_descent = 461.62*_units.minute\n", "t_duration_descent = t_f_descent - t_i_descent\n", "\n", + "##########################\n", + "# Design Variables #\n", + "##########################\n", + "\n", + "# Nudge it a bit off the correct answer to verify that the optimize takes us there.\n", + "aviary_inputs.set_val(av.Mission.Design.GROSS_MASS, 135000.0, units='lbm')\n", + "\n", + "prob.model.add_design_var(av.Mission.Design.GROSS_MASS, units='lbm',\n", + " lower=100000.0, upper=200000.0, ref=135000)\n", + "\n", + "takeoff_options = av.HeightEnergyTakeoffPhaseBuilder(\n", + " airport_altitude=alt_airport, # ft\n", + " # no units\n", + " num_engines=aviary_inputs.get_val(av.Aircraft.Engine.NUM_ENGINES)\n", + ")\n", + "\n", "##################\n", "# Define Phases #\n", "##################\n", - "\n", "num_segments_climb = 6\n", "num_segments_cruise = 1\n", "num_segments_descent = 5\n", @@ -194,12 +189,7 @@ " num_segments=num_segments_descent, order=3, compressed=True,\n", " segment_ends=descent_seg_ends)\n", "\n", - "takeoff_options = av.HeightEnergyTakeoffPhaseBuilder(\n", - " airport_altitude=alt_airport, # ft\n", - " num_engines=aviary_inputs.get_val(Aircraft.Engine.NUM_ENGINES)\n", - ")\n", - "\n", - "climb_options = av.HeightEnergyClimbPhaseBuilder(\n", + "climb_options = av.HeightEnergyPhaseBuilder(\n", " 'test_climb',\n", " user_options=av.AviaryValues({\n", " 'initial_altitude': (alt_i_climb, 'm'),\n", @@ -207,31 +197,30 @@ " 'initial_mach': (mach_i_climb, 'unitless'),\n", " 'final_mach': (mach_f_climb, 'unitless'),\n", " 'fix_initial': (False, 'unitless'),\n", - " 'fix_range': (False, 'unitless'),\n", " 'input_initial': (True, 'unitless'),\n", + " 'use_polynomial_control': (False, 'unitless'),\n", " }),\n", " core_subsystems=av.default_mission_subsystems,\n", " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", " transcription=transcription_climb,\n", ")\n", "\n", - "cruise_options = av.HeightEnergyCruisePhaseBuilder(\n", + "cruise_options = av.HeightEnergyPhaseBuilder(\n", " 'test_cruise',\n", " user_options=av.AviaryValues({\n", - " 'min_altitude': (alt_min_cruise, 'm'),\n", - " 'max_altitude': (alt_max_cruise, 'm'),\n", - " 'min_mach': (mach_min_cruise, 'unitless'),\n", - " 'max_mach': (mach_max_cruise, 'unitless'),\n", + " 'initial_altitude': (alt_min_cruise, 'm'),\n", + " 'final_altitude': (alt_max_cruise, 'm'),\n", + " 'initial_mach': (cruise_mach, 'unitless'),\n", + " 'final_mach': (cruise_mach, 'unitless'),\n", " 'required_available_climb_rate': (300, 'ft/min'),\n", " 'fix_initial': (False, 'unitless'),\n", - " 'fix_final': (False, 'unitless'),\n", " }),\n", " core_subsystems=av.default_mission_subsystems,\n", " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", " transcription=transcription_cruise,\n", ")\n", "\n", - "descent_options = av.HeightEnergyDescentPhaseBuilder(\n", + "descent_options = av.HeightEnergyPhaseBuilder(\n", " 'test_descent',\n", " user_options=av.AviaryValues({\n", " 'final_altitude': (alt_f_descent, 'm'),\n", @@ -239,7 +228,7 @@ " 'initial_mach': (mach_i_descent, 'unitless'),\n", " 'final_mach': (mach_f_descent, 'unitless'),\n", " 'fix_initial': (False, 'unitless'),\n", - " 'fix_range': (True, 'unitless'),\n", + " 'use_polynomial_control': (False, 'unitless'),\n", " }),\n", " core_subsystems=av.default_mission_subsystems,\n", " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", @@ -247,16 +236,13 @@ ")\n", "\n", "landing_options = av.HeightEnergyLandingPhaseBuilder(\n", - " ref_wing_area=aviary_inputs.get_val(Aircraft.Wing.AREA, 'ft**2'),\n", - " Cl_max_ldg=aviary_inputs.get_val(Mission.Landing.LIFT_COEFFICIENT_MAX)\n", + " ref_wing_area=aviary_inputs.get_val(av.Aircraft.Wing.AREA, units='ft**2'),\n", + " Cl_max_ldg=aviary_inputs.get_val(\n", + " av.Mission.Landing.LIFT_COEFFICIENT_MAX) # no units\n", ")\n", "\n", "av.preprocess_crewpayload(aviary_inputs)\n", "\n", - "##########################################\n", - "# add pre mission systems #\n", - "##########################################\n", - "\n", "# Upstream static analysis for aero\n", "prob.model.add_subsystem(\n", " 'pre_mission',\n", @@ -265,10 +251,6 @@ " promotes_inputs=['aircraft:*', 'mission:*'],\n", " promotes_outputs=['aircraft:*', 'mission:*'])\n", "\n", - "##########################################\n", - "# add phases #\n", - "##########################################\n", - "\n", "# directly connect phases (strong_couple = True), or use linkage constraints (weak\n", "# coupling / strong_couple=False)\n", "strong_couple = False\n", @@ -291,15 +273,20 @@ "\n", "# if fix_initial is false, can we always set input_initial to be true for\n", "# necessary states, and then ignore if we use a linkage?\n", - "climb.set_time_options(\n", - " fix_initial=True, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_climb*2), duration_ref=t_duration_climb)\n", - "cruise.set_time_options(\n", - " fix_initial=False, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise)\n", + "climb.set_time_options(fix_initial=True, fix_duration=False, units='s',\n", + " duration_bounds=(t_duration_climb*0.5, t_duration_climb*2),\n", + " duration_ref=t_duration_climb)\n", + "cruise.set_time_options(fix_initial=False, fix_duration=False, units='s',\n", + " duration_bounds=(t_duration_cruise*0.5, t_duration_cruise*2),\n", + " duration_ref=t_duration_cruise,\n", + " initial_bounds=(t_duration_climb*0.5, t_duration_climb*2))\n", "descent.set_time_options(\n", " fix_initial=False, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_descent*2), duration_ref=t_duration_descent)\n", + " duration_bounds=(t_duration_descent*0.5, t_duration_descent*2),\n", + " duration_ref=t_duration_descent,\n", + " initial_bounds=(\n", + " (t_duration_cruise + t_duration_climb)*0.5,\n", + " (t_duration_cruise + t_duration_climb)*2))\n", "\n", "traj.add_phase('climb', climb)\n", "\n", @@ -307,15 +294,6 @@ "\n", "traj.add_phase('descent', descent)\n", "\n", - "if use_new_dymos_syntax:\n", - " climb.timeseries_options['use_prefix'] = True\n", - " cruise.timeseries_options['use_prefix'] = True\n", - " descent.timeseries_options['use_prefix'] = True\n", - "\n", - "##########################################\n", - "# add post mission system #\n", - "##########################################\n", - "\n", "prob.model.add_subsystem(\n", " 'landing', landing, promotes_inputs=['aircraft:*', 'mission:*'],\n", " promotes_outputs=['mission:*'])\n", @@ -324,91 +302,100 @@ "# link phases #\n", "##########################################\n", "\n", - "traj.link_phases([\"climb\", \"cruise\"], [\"time\", Dynamic.Mission.ALTITUDE,\n", - " Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple)\n", - "traj.link_phases([\"cruise\", \"descent\"], [\"time\", Dynamic.Mission.ALTITUDE,\n", - " Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple)\n", + "traj.link_phases([\"climb\", \"cruise\", \"descent\"], [\"time\", av.Dynamic.Mission.MASS, av.Dynamic.Mission.DISTANCE], connected=strong_couple)\n", "\n", "traj = av.setup_trajectory_params(prob.model, traj, aviary_inputs)\n", "\n", "##################################\n", "# Connect in Takeoff and Landing #\n", "##################################\n", - "\n", - "prob.model.add_subsystem(\n", - " \"takeoff_constraints\",\n", - " om.ExecComp(\n", - " [\n", - " \"takeoff_mass_con=takeoff_mass-climb_start_mass\",\n", - " \"takeoff_range_con=takeoff_range-climb_start_range\",\n", - " \"takeoff_vel_con=takeoff_vel-climb_start_vel\",\n", - " \"takeoff_alt_con=takeoff_alt-climb_start_alt\"\n", - " ],\n", - " takeoff_mass_con={'units': 'lbm'}, takeoff_mass={'units': 'lbm'},\n", - " climb_start_mass={'units': 'lbm'},\n", - " takeoff_range_con={'units': 'ft'}, takeoff_range={'units': 'ft'},\n", - " climb_start_range={'units': 'ft'},\n", - " takeoff_vel_con={'units': 'm/s'}, takeoff_vel={'units': 'm/s'},\n", - " climb_start_vel={'units': 'm/s'},\n", - " takeoff_alt_con={'units': 'ft'}, takeoff_alt={'units': 'ft'},\n", - " climb_start_alt={'units': 'ft'}\n", - " ),\n", - " promotes_inputs=[\n", - " (\"takeoff_mass\", Mission.Takeoff.FINAL_MASS),\n", - " (\"takeoff_range\", Mission.Takeoff.GROUND_DISTANCE),\n", - " (\"takeoff_vel\", Mission.Takeoff.FINAL_VELOCITY),\n", - " (\"takeoff_alt\", Mission.Takeoff.FINAL_ALTITUDE),\n", - " ],\n", - " promotes_outputs=[\"takeoff_mass_con\", \"takeoff_range_con\",\n", - " \"takeoff_vel_con\", \"takeoff_alt_con\"],\n", - ")\n", - "\n", - "prob.model.connect('traj.climb.states:mass',\n", - " 'takeoff_constraints.climb_start_mass', src_indices=[0])\n", - "prob.model.connect('traj.climb.states:range',\n", - " 'takeoff_constraints.climb_start_range', src_indices=[0])\n", - "prob.model.connect('traj.climb.states:velocity',\n", - " 'takeoff_constraints.climb_start_vel', src_indices=[0])\n", - "prob.model.connect('traj.climb.states:altitude',\n", - " 'takeoff_constraints.climb_start_alt', src_indices=[0])\n", - "\n", - "prob.model.connect(Mission.Takeoff.FINAL_MASS,\n", + "prob.model.connect(av.Mission.Takeoff.FINAL_MASS,\n", " 'traj.climb.initial_states:mass')\n", - "prob.model.connect(Mission.Takeoff.GROUND_DISTANCE,\n", - " 'traj.climb.initial_states:range')\n", - "prob.model.connect(Mission.Takeoff.FINAL_VELOCITY,\n", - " 'traj.climb.initial_states:velocity')\n", - "prob.model.connect(Mission.Takeoff.FINAL_ALTITUDE,\n", - " 'traj.climb.initial_states:altitude')\n", + "prob.model.connect(av.Mission.Takeoff.GROUND_DISTANCE,\n", + " 'traj.climb.initial_states:distance')\n", "\n", "prob.model.connect('traj.descent.states:mass',\n", - " Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1])\n", - "prob.model.connect('traj.descent.states:altitude', Mission.Landing.INITIAL_ALTITUDE,\n", + " av.Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1])\n", + "# TODO: approach velocity should likely be connected\n", + "prob.model.connect('traj.descent.control_values:altitude', av.Mission.Landing.INITIAL_ALTITUDE,\n", " src_indices=[-1])\n", "\n", "##########################\n", + "# Constraints #\n", + "##########################\n", + "\n", + "ecomp = om.ExecComp('fuel_burned = initial_mass - descent_mass_final',\n", + " initial_mass={'units': 'lbm', 'shape': 1},\n", + " descent_mass_final={'units': 'lbm', 'shape': 1},\n", + " fuel_burned={'units': 'lbm', 'shape': 1})\n", + "\n", + "prob.model.add_subsystem('fuel_burn', ecomp,\n", + " promotes_inputs=[\n", + " ('initial_mass', av.Mission.Design.GROSS_MASS)],\n", + " promotes_outputs=['fuel_burned'])\n", + "\n", + "prob.model.connect(\"traj.descent.states:mass\",\n", + " \"fuel_burn.descent_mass_final\", src_indices=[-1])\n", + "\n", + "# TODO: need to add some sort of check that this value is less than the fuel capacity\n", + "# TODO: need to update this with actual FLOPS value, this gives unrealistic\n", + "# appearance of accuracy\n", + "# TODO: the overall_fuel variable is the burned fuel plus the reserve, but should\n", + "# also include the unused fuel, and the hierarchy variable name should be more clear\n", + "ecomp = om.ExecComp('overall_fuel = fuel_burned + fuel_reserve',\n", + " fuel_burned={'units': 'lbm', 'shape': 1},\n", + " fuel_reserve={'units': 'lbm', 'val': 2173.},\n", + " overall_fuel={'units': 'lbm'})\n", + "prob.model.add_subsystem('fuel_calc', ecomp,\n", + " promotes_inputs=['fuel_burned'],\n", + " promotes_outputs=['overall_fuel'])\n", + "\n", + "ecomp = om.ExecComp(\n", + " 'mass_resid = operating_empty_mass + overall_fuel + payload_mass '\n", + " '- initial_mass',\n", + " operating_empty_mass={'units': 'lbm'},\n", + " overall_fuel={'units': 'lbm'},\n", + " payload_mass={'units': 'lbm'},\n", + " initial_mass={'units': 'lbm'},\n", + " mass_resid={'units': 'lbm'})\n", + "\n", + "prob.model.add_subsystem(\n", + " 'mass_constraint', ecomp,\n", + " promotes_inputs=[\n", + " ('operating_empty_mass', av.Aircraft.Design.OPERATING_MASS),\n", + " 'overall_fuel',\n", + " ('payload_mass', av.Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS),\n", + " ('initial_mass', av.Mission.Design.GROSS_MASS)],\n", + " promotes_outputs=['mass_resid'])\n", + "\n", + "prob.model.add_constraint('mass_resid', equals=0.0, ref=1.0)\n", + "\n", + "##########################\n", "# Add Objective Function #\n", "##########################\n", "\n", "# This is an example of a overall mission objective\n", "# create a compound objective that minimizes climb time and maximizes final mass\n", "# we are maxing final mass b/c we don't have an independent value for fuel_mass yet\n", - "# we are going to normalize these (makign each of the sub-objectives approx = 1 )\n", + "# we are going to normalize these (making each of the sub-objectives approx = 1 )\n", + "# TODO: change the scaling on climb_duration\n", "prob.model.add_subsystem(\n", " \"regularization\",\n", " om.ExecComp(\n", - " \"reg_objective = - descent_mass_final/60000\",\n", + " \"reg_objective = fuel_mass/1500\",\n", " reg_objective=0.0,\n", - " descent_mass_final={\"units\": \"kg\", \"shape\": 1},\n", + " fuel_mass={\"units\": \"lbm\", \"shape\": 1},\n", " ),\n", " promotes_outputs=['reg_objective']\n", ")\n", "# connect the final mass from cruise into the objective\n", - "prob.model.connect(\"traj.descent.states:mass\",\n", - " \"regularization.descent_mass_final\", src_indices=[-1])\n", + "prob.model.connect(av.Mission.Design.FUEL_MASS, \"regularization.fuel_mass\")\n", "\n", "prob.model.add_objective('reg_objective', ref=1)\n", "\n", + "# Set initial default values for all LEAPS aircraft variables.\n", + "av.set_aviary_initial_values(prob.model, aviary_inputs)\n", + "\n", "prob.model.add_subsystem(\n", " 'input_sink',\n", " av.VariablesIn(aviary_options=aviary_inputs),\n", @@ -416,16 +403,7 @@ " promotes_outputs=['*']\n", ")\n", "\n", - "# suppress warnings:\n", - "# \"input variable '...' promoted using '*' was already promoted using 'aircraft:*'\n", - "with warnings.catch_warnings():\n", - "\n", - " # Set initial default values for all LEAPS aircraft variables.\n", - " warnings.simplefilter(\"ignore\", om.PromotionWarning)\n", - " av.set_aviary_initial_values(prob.model, aviary_inputs)\n", - "\n", - " warnings.simplefilter(\"ignore\", om.PromotionWarning)\n", - " prob.setup()\n", + "prob.setup(force_alloc_complex=True)\n", "\n", "###########################################\n", "# Intial Settings for States and Controls #\n", @@ -434,97 +412,79 @@ "prob.set_val('traj.climb.t_initial', t_i_climb, units='s')\n", "prob.set_val('traj.climb.t_duration', t_duration_climb, units='s')\n", "\n", - "prob.set_val('traj.climb.states:altitude', climb.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m')\n", - "# prob.set_val(\n", - "# 'traj.climb.states:velocity', climb.interp(Dynamic.Mission.VELOCITY, ys=[170, v_f_climb]),\n", - "# units='m/s')\n", - "prob.set_val('traj.climb.states:velocity', climb.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_climb, v_f_climb]), units='m/s')\n", + "prob.set_val('traj.climb.controls:altitude', climb.interp(\n", + " av.Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m')\n", + "prob.set_val(\n", + " 'traj.climb.controls:mach', climb.interp(\n", + " av.Dynamic.Mission.MACH, ys=[mach_i_climb, mach_f_climb]), units='unitless')\n", "prob.set_val('traj.climb.states:mass', climb.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg')\n", - "prob.set_val('traj.climb.states:range', climb.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_climb, range_f_climb]), units='m') # nmi\n", - "\n", - "prob.set_val('traj.climb.controls:velocity_rate',\n", - " climb.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.25, 0.0]),\n", - " units='m/s**2')\n", - "prob.set_val('traj.climb.controls:throttle',\n", - " climb.interp(Dynamic.Mission.THROTTLE, ys=[1.0, 1.0]),\n", - " units='unitless')\n", + " av.Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg')\n", + "prob.set_val('traj.climb.states:distance', climb.interp(\n", + " av.Dynamic.Mission.DISTANCE, ys=[range_i_climb, range_f_climb]), units='m')\n", "\n", "prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s')\n", "prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s')\n", "\n", - "prob.set_val('traj.cruise.states:altitude', cruise.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m')\n", - "prob.set_val('traj.cruise.states:velocity', cruise.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_cruise, v_f_cruise]), units='m/s')\n", + "prob.set_val('traj.cruise.polynomial_controls:altitude', cruise.interp(\n", + " av.Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m')\n", + "prob.set_val(\n", + " 'traj.cruise.polynomial_controls:mach', cruise.interp(\n", + " av.Dynamic.Mission.MACH, ys=[cruise_mach, cruise_mach]), units='unitless')\n", "prob.set_val('traj.cruise.states:mass', cruise.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg')\n", - "prob.set_val('traj.cruise.states:range', cruise.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi\n", - "\n", - "prob.set_val('traj.cruise.controls:velocity_rate',\n", - " cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]),\n", - " units='m/s**2')\n", - "prob.set_val('traj.cruise.controls:throttle',\n", - " cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.8, 0.75]),\n", - " units='unitless')\n", + " av.Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg')\n", + "prob.set_val('traj.cruise.states:distance', cruise.interp(\n", + " av.Dynamic.Mission.DISTANCE, ys=[range_i_cruise, range_f_cruise]), units='m')\n", "\n", "prob.set_val('traj.descent.t_initial', t_i_descent, units='s')\n", "prob.set_val('traj.descent.t_duration', t_duration_descent, units='s')\n", "\n", - "prob.set_val('traj.descent.states:altitude', descent.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m')\n", - "prob.set_val('traj.descent.states:velocity', descent.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_descent, v_f_descent]), units='m/s')\n", + "prob.set_val('traj.descent.controls:altitude', descent.interp(\n", + " av.Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m')\n", + "prob.set_val(\n", + " 'traj.descent.controls:mach', descent.interp(\n", + " av.Dynamic.Mission.MACH, ys=[mach_i_descent, mach_f_descent]), units='unitless')\n", "prob.set_val('traj.descent.states:mass', descent.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg')\n", - "prob.set_val('traj.descent.states:range', descent.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_descent, range_f_descent]), units='m')\n", - "\n", - "prob.set_val('traj.descent.controls:velocity_rate',\n", - " descent.interp(Dynamic.Mission.VELOCITY_RATE, ys=[-0.25, 0.0]),\n", - " units='m/s**2')\n", - "prob.set_val('traj.descent.controls:throttle',\n", - " descent.interp(Dynamic.Mission.THROTTLE, ys=[0.0, 0.0]),\n", - " units='unitless')\n", + " av.Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg')\n", + "prob.set_val('traj.descent.states:distance', descent.interp(\n", + " av.Dynamic.Mission.DISTANCE, ys=[distance_i_descent, distance_f_descent]), units='m')\n", "\n", "# Turn off solver printing so that the SNOPT output is readable.\n", "prob.set_solver_print(level=0)\n", "\n", - "dm.run_problem(prob, make_plots=False, solution_record_file='N3CC_full_mission.db')\n", + "dm.run_problem(prob, simulate=False, make_plots=False, simulate_kwargs={\n", + " 'times_per_seg': 100, 'atol': 1e-9, 'rtol': 1e-9},\n", + " solution_record_file='N3CC_sizing.db')\n", "prob.record(\"final\")\n", "prob.cleanup()\n", "\n", "times_climb = prob.get_val('traj.climb.timeseries.time', units='s')\n", "altitudes_climb = prob.get_val(\n", - " 'traj.climb.timeseries.states:altitude', units='m')\n", - "masses_climb = prob.get_val('traj.climb.timeseries.states:mass', units='kg')\n", - "ranges_climb = prob.get_val('traj.climb.timeseries.states:range', units='m')\n", + " 'traj.climb.timeseries.altitude', units='m')\n", + "masses_climb = prob.get_val('traj.climb.timeseries.mass', units='kg')\n", + "ranges_climb = prob.get_val('traj.climb.timeseries.distance', units='m')\n", "velocities_climb = prob.get_val(\n", - " 'traj.climb.timeseries.states:velocity', units='m/s')\n", + " 'traj.climb.timeseries.velocity', units='m/s')\n", "thrusts_climb = prob.get_val('traj.climb.timeseries.thrust_net_total', units='N')\n", "times_cruise = prob.get_val('traj.cruise.timeseries.time', units='s')\n", "altitudes_cruise = prob.get_val(\n", - " 'traj.cruise.timeseries.states:altitude', units='m')\n", - "masses_cruise = prob.get_val('traj.cruise.timeseries.states:mass', units='kg')\n", - "ranges_cruise = prob.get_val('traj.cruise.timeseries.states:range', units='m')\n", + " 'traj.cruise.timeseries.altitude', units='m')\n", + "masses_cruise = prob.get_val('traj.cruise.timeseries.mass', units='kg')\n", + "ranges_cruise = prob.get_val('traj.cruise.timeseries.distance', units='m')\n", "velocities_cruise = prob.get_val(\n", - " 'traj.cruise.timeseries.states:velocity', units='m/s')\n", + " 'traj.cruise.timeseries.velocity', units='m/s')\n", "thrusts_cruise = prob.get_val(\n", " 'traj.cruise.timeseries.thrust_net_total', units='N')\n", "times_descent = prob.get_val('traj.descent.timeseries.time', units='s')\n", "altitudes_descent = prob.get_val(\n", - " 'traj.descent.timeseries.states:altitude', units='m')\n", - "masses_descent = prob.get_val('traj.descent.timeseries.states:mass', units='kg')\n", - "ranges_descent = prob.get_val('traj.descent.timeseries.states:range', units='m')\n", + " 'traj.descent.timeseries.altitude', units='m')\n", + "masses_descent = prob.get_val('traj.descent.timeseries.mass', units='kg')\n", + "ranges_descent = prob.get_val('traj.descent.timeseries.distance', units='m')\n", "velocities_descent = prob.get_val(\n", - " 'traj.descent.timeseries.states:velocity', units='m/s')\n", + " 'traj.descent.timeseries.velocity', units='m/s')\n", "thrusts_descent = prob.get_val(\n", " 'traj.descent.timeseries.thrust_net_total', units='N')\n", "\n", + "\n", "print(\"-------------------------------\")\n", "print(f\"times_climb: {times_climb[-1]} (s)\")\n", "print(f\"altitudes_climb: {altitudes_climb[-1]} (m)\")\n", @@ -547,597 +507,12 @@ "print(\"-------------------------------\")\n" ] }, - { - "cell_type": "markdown", - "id": "4c0f7de8", - "metadata": {}, - "source": [ - "### Move each code block to a function\n", - "\n", - "This code is quite verbose!\n", - "\n", - "To make it easier to follow the steps mentioned above, we split this script into several functions each representing a step at the beginning of this section (except `check_inputs()` and `add_design_variables()`).\n", - "These methods also closely mirror those from `aviary/interface/methods_for_level2.py`, so you can draw parallels between Level 2 and 3.\n", - "\n", - "Here's what the new run script looks like:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e1b0432f", - "metadata": {}, - "outputs": [], - "source": [ - "'''\n", - "NOTES:\n", - "Includes:\n", - "Takeoff, Climb, Cruise, Descent, Landing\n", - "Computed Aero\n", - "N3CC data\n", - "'''\n", - "import warnings\n", - "\n", - "import dymos as dm\n", - "import openmdao.api as om\n", - "import scipy.constants as _units\n", - "from packaging import version\n", - "\n", - "import aviary.api as av\n", - "\n", - "use_new_dymos_syntax = version.parse(dm.__version__) > version.parse(\"1.8.0\")\n", - "\n", - "\n", - "##########################################\n", - "# Phase Info\n", - "##########################################\n", - "\n", - "alt_airport = 0 # ft\n", - "cruise_mach = .785\n", - "\n", - "alt_i_climb = 0*_units.foot # m\n", - "alt_f_climb = 35000.0*_units.foot # m\n", - "mass_i_climb = 131000*_units.lb # kg\n", - "mass_f_climb = 126000*_units.lb # kg\n", - "v_i_climb = 198.44*_units.knot # m/s\n", - "v_f_climb = 455.49*_units.knot # m/s\n", - "# initial mach set to lower value so it can intersect with takeoff end mach\n", - "# mach_i_climb = 0.3\n", - "mach_i_climb = 0.2\n", - "mach_f_climb = cruise_mach\n", - "range_i_climb = 0*_units.nautical_mile # m\n", - "range_f_climb = 160.3*_units.nautical_mile # m\n", - "t_i_climb = 2 * _units.minute # sec\n", - "t_f_climb = 26.20*_units.minute # sec\n", - "t_duration_climb = t_f_climb - t_i_climb\n", - "\n", - "alt_i_cruise = 35000*_units.foot # m\n", - "alt_f_cruise = 35000*_units.foot # m\n", - "alt_min_cruise = 35000*_units.foot # m\n", - "alt_max_cruise = 35000*_units.foot # m\n", - "mass_i_cruise = 126000*_units.lb # kg\n", - "mass_f_cruise = 102000*_units.lb # kg\n", - "v_i_cruise = 455.49*_units.knot # m/s\n", - "v_f_cruise = 455.49*_units.knot # m/s\n", - "mach_min_cruise = cruise_mach\n", - "mach_max_cruise = cruise_mach\n", - "range_i_cruise = 160.3*_units.nautical_mile # m\n", - "range_f_cruise = 3243.9*_units.nautical_mile # m\n", - "t_i_cruise = 26.20*_units.minute # sec\n", - "t_f_cruise = 432.38*_units.minute # sec\n", - "t_duration_cruise = t_f_cruise - t_i_cruise\n", - "\n", - "alt_i_descent = 35000*_units.foot\n", - "# final altitude set to 35 to ensure landing is feasible point\n", - "# alt_f_descent = 0*_units.foot\n", - "alt_f_descent = 35*_units.foot\n", - "v_i_descent = 455.49*_units.knot\n", - "v_f_descent = 198.44*_units.knot\n", - "mach_i_descent = cruise_mach\n", - "mach_f_descent = 0.3\n", - "mass_i_descent = 102000*_units.pound\n", - "mass_f_descent = 101000*_units.pound\n", - "range_i_descent = 3243.9*_units.nautical_mile\n", - "range_f_descent = 3378.7*_units.nautical_mile\n", - "t_i_descent = 432.38*_units.minute\n", - "t_f_descent = 461.62*_units.minute\n", - "t_duration_descent = t_f_descent - t_i_descent\n", - "\n", - "def load_inputs():\n", - " aviary_inputs = av.get_flops_inputs('N3CC')\n", - "\n", - " aviary_inputs.set_val(\n", - " Mission.Landing.LIFT_COEFFICIENT_MAX, 2.4, units='unitless')\n", - " aviary_inputs.set_val(\n", - " Mission.Takeoff.LIFT_COEFFICIENT_MAX, 2.0, units='unitless')\n", - " aviary_inputs.set_val(\n", - " Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, val=.0175,\n", - " units='unitless')\n", - "\n", - " takeoff_fuel_burned = 577 # lbm\n", - " takeoff_thrust_per_eng = 24555.5 # lbf\n", - " takeoff_L_over_D = 17.35\n", - "\n", - " aviary_inputs.set_val(\n", - " Mission.Takeoff.FUEL_SIMPLE, takeoff_fuel_burned, units='lbm')\n", - " aviary_inputs.set_val(\n", - " Mission.Takeoff.LIFT_OVER_DRAG, takeoff_L_over_D, units='unitless')\n", - " aviary_inputs.set_val(\n", - " Mission.Design.THRUST_TAKEOFF_PER_ENG, takeoff_thrust_per_eng, units='lbf')\n", - "\n", - " return aviary_inputs\n", - "\n", - "\n", - "def add_pre_mission_system(aviary_inputs, prob):\n", - " takeoff_options = av.HeightEnergyTakeoffPhaseBuilder(\n", - " airport_altitude=alt_airport, # ft\n", - " num_engines=aviary_inputs.get_val(Aircraft.Engine.NUM_ENGINES)\n", - " )\n", - "\n", - " av.preprocess_crewpayload(aviary_inputs)\n", - "\n", - " # Upstream static analysis for aero\n", - " prob.model.add_subsystem(\n", - " 'pre_mission',\n", - " av.CorePreMission(aviary_options=aviary_inputs,\n", - " subsystems=av.default_premission_subsystems),\n", - " promotes_inputs=['aircraft:*', 'mission:*'],\n", - " promotes_outputs=['aircraft:*', 'mission:*'])\n", - "\n", - " takeoff = takeoff_options.build_phase(False)\n", - "\n", - " prob.model.add_subsystem(\n", - " 'takeoff', takeoff, promotes_inputs=['aircraft:*', 'mission:*'],\n", - " promotes_outputs=['mission:*'])\n", - "\n", - " prob.model.add_subsystem(\n", - " \"takeoff_constraints\",\n", - " om.ExecComp(\n", - " [\n", - " \"takeoff_mass_con=takeoff_mass-climb_start_mass\",\n", - " \"takeoff_range_con=takeoff_range-climb_start_range\",\n", - " \"takeoff_vel_con=takeoff_vel-climb_start_vel\",\n", - " \"takeoff_alt_con=takeoff_alt-climb_start_alt\"\n", - " ],\n", - " takeoff_mass_con={'units': 'lbm'}, takeoff_mass={'units': 'lbm'},\n", - " climb_start_mass={'units': 'lbm'},\n", - " takeoff_range_con={'units': 'ft'}, takeoff_range={'units': 'ft'},\n", - " climb_start_range={'units': 'ft'},\n", - " takeoff_vel_con={'units': 'm/s'}, takeoff_vel={'units': 'm/s'},\n", - " climb_start_vel={'units': 'm/s'},\n", - " takeoff_alt_con={'units': 'ft'}, takeoff_alt={'units': 'ft'},\n", - " climb_start_alt={'units': 'ft'}\n", - " ),\n", - " promotes_inputs=[\n", - " (\"takeoff_mass\", Mission.Takeoff.FINAL_MASS),\n", - " (\"takeoff_range\", Mission.Takeoff.GROUND_DISTANCE),\n", - " (\"takeoff_vel\", Mission.Takeoff.FINAL_VELOCITY),\n", - " (\"takeoff_alt\", Mission.Takeoff.FINAL_ALTITUDE),\n", - " ],\n", - " promotes_outputs=[\"takeoff_mass_con\", \"takeoff_range_con\",\n", - " \"takeoff_vel_con\", \"takeoff_alt_con\"],\n", - " )\n", - "\n", - " prob.model.connect(Mission.Takeoff.FINAL_MASS,\n", - " 'traj.climb.initial_states:mass')\n", - " prob.model.connect(Mission.Takeoff.GROUND_DISTANCE,\n", - " 'traj.climb.initial_states:range')\n", - " prob.model.connect(Mission.Takeoff.FINAL_VELOCITY,\n", - " 'traj.climb.initial_states:velocity')\n", - " prob.model.connect(Mission.Takeoff.FINAL_ALTITUDE,\n", - " 'traj.climb.initial_states:altitude')\n", - "\n", - "\n", - "def add_post_mission_systems(aviary_inputs, prob):\n", - " landing_options = av.HeightEnergyLandingPhaseBuilder(\n", - " ref_wing_area=aviary_inputs.get_val(Aircraft.Wing.AREA, 'ft**2'),\n", - " Cl_max_ldg=aviary_inputs.get_val(Mission.Landing.LIFT_COEFFICIENT_MAX)\n", - " )\n", - "\n", - " landing = landing_options.build_phase(\n", - " False,\n", - " )\n", - "\n", - " prob.model.add_subsystem(\n", - " 'landing', landing, promotes_inputs=['aircraft:*', 'mission:*'],\n", - " promotes_outputs=['mission:*'])\n", - "\n", - " prob.model.connect('traj.descent.states:mass',\n", - " Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1])\n", - " prob.model.connect('traj.descent.states:altitude', Mission.Landing.INITIAL_ALTITUDE,\n", - " src_indices=[-1])\n", - "\n", - "\n", - "def set_initial_guesses(prob, traj):\n", - " # have to add this block to recover climb, cruise, and descent phases first\n", - " phase_items = traj._phases.items()\n", - " for idx, (_, phase) in enumerate(phase_items):\n", - " if idx == 0: climb = phase\n", - " elif idx == 1: cruise = phase\n", - " else: descent = phase\n", - "\n", - " prob.set_val('traj.climb.t_initial', t_i_climb, units='s')\n", - " prob.set_val('traj.climb.t_duration', t_duration_climb, units='s')\n", - "\n", - " prob.set_val('traj.climb.states:altitude', climb.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m')\n", - " # prob.set_val(\n", - " # 'traj.climb.states:velocity', climb.interp(Dynamic.Mission.VELOCITY, ys=[170, v_f_climb]),\n", - " # units='m/s')\n", - " prob.set_val('traj.climb.states:velocity', climb.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_climb, v_f_climb]), units='m/s')\n", - " prob.set_val('traj.climb.states:mass', climb.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg')\n", - " prob.set_val('traj.climb.states:range', climb.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_climb, range_f_climb]), units='m') # nmi\n", - "\n", - " prob.set_val('traj.climb.controls:velocity_rate',\n", - " climb.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.25, 0.0]),\n", - " units='m/s**2')\n", - " prob.set_val('traj.climb.controls:throttle',\n", - " climb.interp(Dynamic.Mission.THROTTLE, ys=[1.0, 1.0]),\n", - " units='unitless')\n", - "\n", - " prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s')\n", - " prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s')\n", - "\n", - " prob.set_val('traj.cruise.states:altitude', cruise.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m')\n", - " prob.set_val('traj.cruise.states:velocity', cruise.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_cruise, v_f_cruise]), units='m/s')\n", - " prob.set_val('traj.cruise.states:mass', cruise.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg')\n", - " prob.set_val('traj.cruise.states:range', cruise.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi\n", - "\n", - " prob.set_val('traj.cruise.controls:velocity_rate',\n", - " cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]),\n", - " units='m/s**2')\n", - " prob.set_val('traj.cruise.controls:throttle',\n", - " cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.8, 0.75]),\n", - " units='unitless')\n", - "\n", - " prob.set_val('traj.descent.t_initial', t_i_descent, units='s')\n", - " prob.set_val('traj.descent.t_duration', t_duration_descent, units='s')\n", - "\n", - " prob.set_val('traj.descent.states:altitude', descent.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m')\n", - " prob.set_val('traj.descent.states:velocity', descent.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_descent, v_f_descent]), units='m/s')\n", - " prob.set_val('traj.descent.states:mass', descent.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg')\n", - " prob.set_val('traj.descent.states:range', descent.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_descent, range_f_descent]), units='m')\n", - "\n", - " prob.set_val('traj.descent.controls:velocity_rate',\n", - " descent.interp(Dynamic.Mission.VELOCITY_RATE, ys=[-0.25, 0.0]),\n", - " units='m/s**2')\n", - " prob.set_val('traj.descent.controls:throttle',\n", - " descent.interp(Dynamic.Mission.THROTTLE, ys=[0.0, 0.0]),\n", - " units='unitless')\n", - "\n", - "\n", - "def setup(aviary_inputs, prob):\n", - " prob.model.add_subsystem(\n", - " 'input_sink',\n", - " av.VariablesIn(aviary_options=aviary_inputs),\n", - " promotes_inputs=['*'],\n", - " promotes_outputs=['*']\n", - " )\n", - "\n", - " # suppress warnings:\n", - " # \"input variable '...' promoted using '*' was already promoted using 'aircraft:*'\n", - " with warnings.catch_warnings():\n", - "\n", - " # Set initial default values for all LEAPS aircraft variables.\n", - " warnings.simplefilter(\"ignore\", om.PromotionWarning)\n", - " av.set_aviary_initial_values(prob.model, aviary_inputs)\n", - "\n", - " warnings.simplefilter(\"ignore\", om.PromotionWarning)\n", - " prob.setup()\n", - "\n", - "\n", - "def add_objective(prob):\n", - " # This is an example of a overall mission objective\n", - " # create a compound objective that minimizes climb time and maximizes final mass\n", - " # we are maxing final mass b/c we don't have an independent value for fuel_mass yet\n", - " # we are going to normalize these (makign each of the sub-objectives approx = 1 )\n", - " prob.model.add_subsystem(\n", - " \"regularization\",\n", - " om.ExecComp(\n", - " \"reg_objective = - descent_mass_final/60000\",\n", - " reg_objective=0.0,\n", - " descent_mass_final={\"units\": \"kg\", \"shape\": 1},\n", - " ),\n", - " promotes_outputs=['reg_objective']\n", - " )\n", - " # connect the final mass from descent into the objective\n", - " prob.model.connect(\"traj.descent.states:mass\",\n", - " \"regularization.descent_mass_final\", src_indices=[-1])\n", - "\n", - " prob.model.add_objective('reg_objective', ref=1)\n", - "\n", - "\n", - "def add_phases(aviary_inputs, prob):\n", - " ##################\n", - " # Define Phases #\n", - " ##################\n", - "\n", - " num_segments_climb = 6\n", - " num_segments_cruise = 1\n", - " num_segments_descent = 5\n", - "\n", - " climb_seg_ends, _ = dm.utils.lgl.lgl(num_segments_climb + 1)\n", - " descent_seg_ends, _ = dm.utils.lgl.lgl(num_segments_descent + 1)\n", - "\n", - " transcription_climb = dm.Radau(\n", - " num_segments=num_segments_climb, order=3, compressed=True,\n", - " segment_ends=climb_seg_ends)\n", - " transcription_cruise = dm.Radau(\n", - " num_segments=num_segments_cruise, order=3, compressed=True)\n", - " transcription_descent = dm.Radau(\n", - " num_segments=num_segments_descent, order=3, compressed=True,\n", - " segment_ends=descent_seg_ends)\n", - "\n", - " climb_options = av.HeightEnergyClimbPhaseBuilder(\n", - " 'test_climb',\n", - " user_options=av.AviaryValues({\n", - " 'initial_altitude': (alt_i_climb, 'm'),\n", - " 'final_altitude': (alt_f_climb, 'm'),\n", - " 'initial_mach': (mach_i_climb, 'unitless'),\n", - " 'final_mach': (mach_f_climb, 'unitless'),\n", - " 'fix_initial': (False, 'unitless'),\n", - " 'fix_range': (False, 'unitless'),\n", - " 'input_initial': (True, 'unitless'),\n", - " }),\n", - " core_subsystems=av.default_mission_subsystems,\n", - " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", - " transcription=transcription_climb,\n", - " )\n", - "\n", - " cruise_options = av.HeightEnergyCruisePhaseBuilder(\n", - " 'test_cruise',\n", - " user_options=av.AviaryValues({\n", - " 'min_altitude': (alt_min_cruise, 'm'),\n", - " 'max_altitude': (alt_max_cruise, 'm'),\n", - " 'min_mach': (mach_min_cruise, 'unitless'),\n", - " 'max_mach': (mach_max_cruise, 'unitless'),\n", - " 'required_available_climb_rate': (300, 'ft/min'),\n", - " 'fix_initial': (False, 'unitless'),\n", - " 'fix_final': (False, 'unitless'),\n", - " }),\n", - " core_subsystems=av.default_mission_subsystems,\n", - " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", - " transcription=transcription_cruise,\n", - " )\n", - "\n", - " descent_options = av.HeightEnergyDescentPhaseBuilder(\n", - " 'test_descent',\n", - " user_options=av.AviaryValues({\n", - " 'final_altitude': (alt_f_descent, 'm'),\n", - " 'initial_altitude': (alt_i_descent, 'm'),\n", - " 'initial_mach': (mach_i_descent, 'unitless'),\n", - " 'final_mach': (mach_f_descent, 'unitless'),\n", - " 'fix_initial': (False, 'unitless'),\n", - " 'fix_range': (True, 'unitless'),\n", - " }),\n", - " core_subsystems=av.default_mission_subsystems,\n", - " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", - " transcription=transcription_descent,\n", - " )\n", - "\n", - " climb = climb_options.build_phase(aviary_options=aviary_inputs)\n", - "\n", - " cruise = cruise_options.build_phase(aviary_options=aviary_inputs)\n", - "\n", - " descent = descent_options.build_phase(aviary_options=aviary_inputs)\n", - "\n", - " traj = prob.model.add_subsystem('traj', dm.Trajectory())\n", - "\n", - " climb.set_time_options(\n", - " fix_initial=True, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_climb*2), duration_ref=t_duration_climb)\n", - " cruise.set_time_options(\n", - " fix_initial=False, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise)\n", - " descent.set_time_options(\n", - " fix_initial=False, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_descent*2), duration_ref=t_duration_descent)\n", - "\n", - " traj.add_phase('climb', climb)\n", - "\n", - " traj.add_phase('cruise', cruise)\n", - "\n", - " traj.add_phase('descent', descent)\n", - "\n", - " if use_new_dymos_syntax: # needed for print_outputs()\n", - " climb.timeseries_options['use_prefix'] = True\n", - " cruise.timeseries_options['use_prefix'] = True\n", - " descent.timeseries_options['use_prefix'] = True\n", - "\n", - " return traj\n", - "\n", - "\n", - "def link_phases(aviary_inputs, prob, traj):\n", - " # directly connect phases (strong_couple = True), or use linkage constraints (weak\n", - " # coupling / strong_couple=False)\n", - " strong_couple = False\n", - "\n", - " traj.link_phases([\"climb\", \"cruise\"], [\"time\", Dynamic.Mission.ALTITUDE,\n", - " Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple)\n", - " traj.link_phases([\"cruise\", \"descent\"], [\"time\", Dynamic.Mission.ALTITUDE,\n", - " Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple)\n", - "\n", - " traj = av.setup_trajectory_params(prob.model, traj, aviary_inputs)\n", - "\n", - " ##################################\n", - " # Connect in Takeoff #\n", - " ##################################\n", - "\n", - " prob.model.connect('traj.climb.states:mass',\n", - " 'takeoff_constraints.climb_start_mass', src_indices=[0])\n", - " prob.model.connect('traj.climb.states:range',\n", - " 'takeoff_constraints.climb_start_range', src_indices=[0])\n", - " prob.model.connect('traj.climb.states:velocity',\n", - " 'takeoff_constraints.climb_start_vel', src_indices=[0])\n", - " prob.model.connect('traj.climb.states:altitude',\n", - " 'takeoff_constraints.climb_start_alt', src_indices=[0])\n", - "\n", - "\n", - "def run_aviary_problem(prob, sim):\n", - " # Turn off solver printing so that the SNOPT output is readable.\n", - " prob.set_solver_print(level=0)\n", - "\n", - " dm.run_problem(prob, simulate=sim, make_plots=True, simulate_kwargs={\n", - " 'times_per_seg': 100, 'atol': 1e-9, 'rtol': 1e-9}, \n", - " solution_record_file='N3CC_full_mission.db')\n", - " prob.record(\"final\")\n", - " prob.cleanup()\n", - "\n", - "\n", - "def run_N3CC_full_mission(sim=True):\n", - " ##########################################\n", - " # Build problem #\n", - " ##########################################\n", - " prob = om.Problem(model=om.Group())\n", - " #prob.driver = driver\n", - "\n", - " traj = None\n", - "\n", - " ##########################################\n", - " # Aircraft Input Variables and Options #\n", - " ##########################################\n", - "\n", - " aviary_inputs = load_inputs()\n", - "\n", - " ##########################################\n", - " # add pre-mission systems #\n", - " ##########################################\n", - "\n", - " add_pre_mission_system(aviary_inputs, prob)\n", - "\n", - " ##########################################\n", - " # add phases #\n", - " ##########################################\n", - "\n", - " traj = add_phases(aviary_inputs, prob)\n", - "\n", - " ##########################################\n", - " # add post mission system #\n", - " ##########################################\n", - "\n", - " add_post_mission_systems(aviary_inputs, prob)\n", - "\n", - " ##########################################\n", - " # add driver #\n", - " ##########################################\n", - "\n", - " prob.driver = add_driver()\n", - "\n", - " ##########################################\n", - " # link phases #\n", - " ##########################################\n", - "\n", - " link_phases(aviary_inputs, prob, traj)\n", - "\n", - " ##########################\n", - " # Add Objective Function #\n", - " ##########################\n", - "\n", - " add_objective(prob)\n", - "\n", - " #######\n", - " # setup\n", - " #######\n", - "\n", - " setup(aviary_inputs, prob)\n", - "\n", - " ###########################################\n", - " # Intial Settings for States and Controls #\n", - " ###########################################\n", - "\n", - " set_initial_guesses(prob, traj)\n", - "\n", - " #####################\n", - " # run aviary problem\n", - " #####################\n", - "\n", - " run_aviary_problem(prob, sim)\n", - "\n", - " print_outputs(prob)\n", - "\n", - "\n", - "def add_driver():\n", - " driver = om.ScipyOptimizeDriver()\n", - " driver.options['optimizer'] = 'SLSQP'\n", - " driver.opt_settings['maxiter'] = 0\n", - " driver.opt_settings['ftol'] = 5.0e-3\n", - " driver.opt_settings['eps'] = 1e-2\n", - "\n", - " return driver\n", - "\n", - "\n", - "def print_outputs(prob):\n", - " times_climb = prob.get_val('traj.climb.timeseries.time', units='s')\n", - " altitudes_climb = prob.get_val(\n", - " 'traj.climb.timeseries.states:altitude', units='m')\n", - " masses_climb = prob.get_val('traj.climb.timeseries.states:mass', units='kg')\n", - " ranges_climb = prob.get_val('traj.climb.timeseries.states:range', units='m')\n", - " velocities_climb = prob.get_val(\n", - " 'traj.climb.timeseries.states:velocity', units='m/s')\n", - " thrusts_climb = prob.get_val('traj.climb.timeseries.thrust_net_total', units='N')\n", - " times_cruise = prob.get_val('traj.cruise.timeseries.time', units='s')\n", - " altitudes_cruise = prob.get_val(\n", - " 'traj.cruise.timeseries.states:altitude', units='m')\n", - " masses_cruise = prob.get_val('traj.cruise.timeseries.states:mass', units='kg')\n", - " ranges_cruise = prob.get_val('traj.cruise.timeseries.states:range', units='m')\n", - " velocities_cruise = prob.get_val(\n", - " 'traj.cruise.timeseries.states:velocity', units='m/s')\n", - " thrusts_cruise = prob.get_val(\n", - " 'traj.cruise.timeseries.thrust_net_total', units='N')\n", - " times_descent = prob.get_val('traj.descent.timeseries.time', units='s')\n", - " altitudes_descent = prob.get_val(\n", - " 'traj.descent.timeseries.states:altitude', units='m')\n", - " masses_descent = prob.get_val('traj.descent.timeseries.states:mass', units='kg')\n", - " ranges_descent = prob.get_val('traj.descent.timeseries.states:range', units='m')\n", - " velocities_descent = prob.get_val(\n", - " 'traj.descent.timeseries.states:velocity', units='m/s')\n", - " thrusts_descent = prob.get_val(\n", - " 'traj.descent.timeseries.thrust_net_total', units='N')\n", - "\n", - " print(\"-------------------------------\")\n", - " print(f\"times_climb: {times_climb[-1]} (s)\")\n", - " print(f\"altitudes_climb: {altitudes_climb[-1]} (m)\")\n", - " print(f\"masses_climb: {masses_climb[-1]} (kg)\")\n", - " print(f\"ranges_climb: {ranges_climb[-1]} (m)\")\n", - " print(f\"velocities_climb: {velocities_climb[-1]} (m/s)\")\n", - " print(f\"thrusts_climb: {thrusts_climb[-1]} (N)\")\n", - " print(f\"times_cruise: {times_cruise[-1]} (s)\")\n", - " print(f\"altitudes_cruise: {altitudes_cruise[-1]} (m)\")\n", - " print(f\"masses_cruise: {masses_cruise[-1]} (kg)\")\n", - " print(f\"ranges_cruise: {ranges_cruise[-1]} (m)\")\n", - " print(f\"velocities_cruise: {velocities_cruise[-1]} (m/s)\")\n", - " print(f\"thrusts_cruise: {thrusts_cruise[-1]} (N)\")\n", - " print(f\"times_descent: {times_descent[-1]} (s)\")\n", - " print(f\"altitudes_descent: {altitudes_descent[-1]} (m)\")\n", - " print(f\"masses_descent: {masses_descent[-1]} (kg)\")\n", - " print(f\"ranges_descent: {ranges_descent[-1]} (m)\")\n", - " print(f\"velocities_descent: {velocities_descent[-1]} (m/s)\")\n", - " print(f\"thrusts_descent: {thrusts_descent[-1]} (N)\")\n", - " print(\"-------------------------------\")\n", - "\n", - "\n", - "run_N3CC_full_mission(sim=False)" - ] - }, { "cell_type": "markdown", "id": "2b43d293", "metadata": {}, "source": [ - "You see the same outputs.\n", - "\n", - "This model demonstrates the flexibility of level 3. For example, the `load_inputs` function does not load the aircraft model from a `.csv` file but from a Python file using the `get_flops_inputs()` method.\n", + "This model demonstrates the flexibility of level 3. For example, we do not load the aircraft model from a `.csv` file but from a Python file using the `get_flops_inputs()` method.\n", "\n", "This function not only reads Aviary and mission variables but also builds the engine. More information can be found in `aviary/models/N3CC/N3CC_data.py`.\n", "\n", @@ -1151,10 +526,10 @@ "metadata": {}, "outputs": [], "source": [ - "aviary_inputs = av.get_flops_inputs('LargeSingleAisle1FLOPS')\n", - "aviary_inputs = av.get_flops_inputs('LargeSingleAisle2FLOPS')\n", - "aviary_inputs = av.get_flops_inputs('LargeSingleAisle2FLOPSdw')\n", - "aviary_inputs = av.get_flops_inputs('LargeSingleAisle2FLOPSalt')" + "aviary_inputs = get_flops_inputs('LargeSingleAisle1FLOPS')\n", + "aviary_inputs = get_flops_inputs('LargeSingleAisle2FLOPS')\n", + "aviary_inputs = get_flops_inputs('LargeSingleAisle2FLOPSdw')\n", + "aviary_inputs = get_flops_inputs('LargeSingleAisle2FLOPSalt')" ] }, { diff --git a/aviary/docs/getting_started/what_aviary_does.md b/aviary/docs/getting_started/what_aviary_does.md index 880887397..acf630706 100644 --- a/aviary/docs/getting_started/what_aviary_does.md +++ b/aviary/docs/getting_started/what_aviary_does.md @@ -17,7 +17,7 @@ Let's discuss what Aviary does in more detail. ```{warning} Aviary is under active development! If you're using it, know that we are working to update Aviary to make it more user-friendly and capable. -If you have suggestions or comments please let the Aviary team know by [submitting an issue on GitHub](https://github.com/OpenMDAO/om-Aviary/issues/new/choose). +If you have suggestions or comments please let the Aviary team know by [submitting an issue on GitHub](https://github.com/OpenMDAO/Aviary/issues/new/choose). ``` ## Core functionalities @@ -77,7 +77,7 @@ We've touched on some of these already, but let's discuss them more. ### Benefits -- Open-source: Aviary is open-source and available on [GitHub](https://github.com/OpenMDAO/om-Aviary) +- Open-source: Aviary is open-source and available on [GitHub](https://github.com/OpenMDAO/Aviary) - Flexible: Aviary is intended to be flexible enough to design a variety of aircraft and missions - Customizable: Aviary allows users to add their own subsystems to the problem - Optimization: Aviary is designed to be used effectively with gradient-based optimization diff --git a/aviary/docs/intro.md b/aviary/docs/intro.md index f085b0f58..0904749a4 100644 --- a/aviary/docs/intro.md +++ b/aviary/docs/intro.md @@ -4,7 +4,7 @@ This is the landing page for all of Aviary's documentation, including a user's g ## What Aviary is -[Aviary](https://github.com/OpenMDAO/om-Aviary) is an aircraft analysis, design, and optimization tool built on top of the Python-based optimization framework [OpenMDAO](https://github.com/OpenMDAO/OpenMDAO). +[Aviary](https://github.com/OpenMDAO/Aviary) is an aircraft analysis, design, and optimization tool built on top of the Python-based optimization framework [OpenMDAO](https://github.com/OpenMDAO/OpenMDAO). Aviary provides a flexible and user-friendly optimization platform that allows the beginning aircraft modeler to build a useful model, the intermediate aircraft modeler to build an advanced model, and the advanced aircraft modeler to build any model they can imagine. Features of Aviary include: diff --git a/aviary/docs/misc_resources/using_XDSM.md b/aviary/docs/misc_resources/using_XDSM.md deleted file mode 100644 index 189004772..000000000 --- a/aviary/docs/misc_resources/using_XDSM.md +++ /dev/null @@ -1,55 +0,0 @@ -# Using XDSM - -Aviary has two ways to visualize its structures. One way is the OpenMDAO [N2 Diagram](https://openmdao.org/newdocs/versions/latest/features/model_visualization/n2_details/n2_details.html). Another is the [XDSM](https://mdolab-pyxdsm.readthedocs-hosted.com/) from MDO Lab. In this page, we explain how XDSM diagrams are organized in Aviary. - -## What is XDSM? - -The [Practical MDO](https://openmdao.github.io/PracticalMDO/) website has a [fantastic post on understanding XDSM diagrams](https://openmdao.github.io/PracticalMDO/Notebooks/ModelConstruction/understanding_xdsm_diagrams.html). This lesson also includes a [helpful video](https://www.youtube.com/watch?v=yutKRwIL3BA). - -## Creation of all XDSM diagrams - -All the XDSM files reside in `xdsm` directory. And all of their names end with `_xdsm.py`: - -```bash -...... -descent1_xdsm.py -descent2_xdsm.py -design_load_xdsm.py -empennage_size_xdsm.py -...... -``` - -There is a particular python file `run_all.py` in `xdsm` folder. Using `run_all.py` in your Aviary environment, you can generate all the XDSM diagrams: - -```bash -python run_all.py -``` - -If everything runs correctly, you will see a list of `.pdf` files (e.g. `accel_xdsm.pdf`) and some `sub-folders` (e.g. `accel_specs`). Those sub-folders are created for [unit testing](https://docs.python.org/3/library/unit test.html). - -## The Aviary rules - -Readers should read about basic XDSM from MDO Lab [website](https://mdolab-pyxdsm.readthedocs-hosted.com/). Besides those, Aviary has its own rules. Let's take a look at a sample XDSM diagram: - -![Sample Aviary XDSM](images/sample_aviary_xdsm.PNG) - -Let us use the above example to discuss how Aviary uses XDSMs: - -1. All XDSM diagram files end with `_xdsm.py` in `xdsm` folder. -2. Some XDSM diagrams are coupled with sub-folders with names ending with `_specs`. They correspond to related Aviary unit tests. Search keyword [assert_match_spec](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/utils/test_utils/IO_test_util.py) for a specific unit test. -3. Single components have rectangular shape (e.g. `MyComp1`). -4. Groups of components have rectangular shape with chopped corners (e.g. `MyGroup`). -5. System input on the top-left corner is shown as a group (e.g. `Group Object (Input)`). This is a special symbol for the whole diagram (which is a group). -6. The components linked to unit tests are in bold. A `_specs` folder contains the data to be validated. -7. Components with feedback variables are shown in pink red background (e.g. `MyComp2` with feedback variable `var_feedback`). In this case, components are not run only sequentially, but in a loop. Usually, a nonlinear solver is required for the coupled subsystem. -8. Conditional variables, components, and groups are grayed out (e.g. `opt_Input_value`). Sometimes, a component or a component group can be left off. They are shown using gray color indicating they are conditional. Users can configure the appropriate Python file to hide them in the diagram. -9. Array of components and groups are shown as stacks (e.g. `MyComps`). A stack of components is a set of systems that are not connected to each other. Usually they can be run in parallel. -10. "InputValues" represents a list of input variables if the user set `simplified = True` when an XDSM diagram is generated. Otherwise, a complete list of variables are shown. Sometimes this list can be too long. We give an option for users to simplify the display of input variables. -11. Variables like "aircraft:wing:\*" are lists of some variables in `Aircraft.Wing` class. We display them this way to make the XDSM diagram easy to read. Sometimes, these lists can be very long and it is impractical to show all of them. If "aircraft:wing:\*" is an input to a group, it is possible that this group has its own XDSM diagram. -12. In the source code of some of the XDSM diagrams, there are boolean variables to control the inputs/outputs of variables, components, and groups (e.g. `simplified` or `show_outputs`). Users can set them themselves and regenerate those diagrams with different appearances. The next section shows how to regenerate a single diagram instead of all. - -## Unit Testing - -All XDSM diagrams within Aviary are tested against the actual Aviary code. -This means that all of the inputs and outputs for each component and group are checked against the XDSM diagram. -If you modify Aviary, you should run the unit tests to ensure that the XDSM diagrams are still valid and update the XDSM diagrams if necessary. diff --git a/aviary/docs/theory_guide/building_metadata_syntax.ipynb b/aviary/docs/theory_guide/building_metadata_syntax.ipynb index cfa972381..b99412cbc 100644 --- a/aviary/docs/theory_guide/building_metadata_syntax.ipynb +++ b/aviary/docs/theory_guide/building_metadata_syntax.ipynb @@ -27,7 +27,7 @@ "source": [ "# Building Metadata\n", "\n", - "When working with Aviary models, sometimes it is necessary to extend the Aviary-core variable hierarchy or update existing metadata. The syntax for `add_meta_data` and `update_meta_data` can be found below. For a more detailed explanation of metadata please visit [this link](../user_guide/variable_metadata.md).\n", + "When working with Aviary models, sometimes it is necessary to extend the Aviary-core variable hierarchy or update existing metadata. The syntax for `add_meta_data` and `update_meta_data` can be found below. For a more detailed explanation of metadata please visit [this link](../user_guide/variable_metadata).\n", "\n", "```{eval-rst}\n", ".. autofunction:: aviary.utils.develop_metadata.add_meta_data\n", diff --git a/aviary/docs/theory_guide/intro.md b/aviary/docs/theory_guide/intro.md index 1c894a8db..5bed0564d 100644 --- a/aviary/docs/theory_guide/intro.md +++ b/aviary/docs/theory_guide/intro.md @@ -20,7 +20,7 @@ The [mass](./mass) calculations that Aviary provides are similar to the geometry The [mission analysis](./mission) calculations that Aviary provides are broader in scope than the other disciplines. Aviary provides two different types of equations of motion (2DOF and height energy), and also provides two different ways to integrate these EOMs (collocation and analytic shooting). The intended capability is that either EOM can be used with either integration technique. -The second aspect of Aviary, instead of being computational like the first, is about subsystem integration. In addition to the capability to build purely Aviary-based aircraft models, Aviary provides the ability to build mixed-origin aircraft models, which are aircraft models consisting partially or entirely of external user-provided subsystems. In the case of a mixed-origin model, instead of just selecting existing Aviary subsystems and combining them into an aircraft model, the user also provides the Aviary code with their own subsystems (these could be pre-existing codes such as [pyCycle](https://github.com/OpenMDAO/pyCycle), or they could be new subsystems the user has built themselves). An example of a mixed-origin model is the [OpenAeroStruct example case](../examples/OAS_subsystem.md). In this example, the built-in Aviary subsystem for mass is partially replaced by external subsystems. +The second aspect of Aviary, instead of being computational like the first, is about subsystem integration. In addition to the capability to build purely Aviary-based aircraft models, Aviary provides the ability to build mixed-origin aircraft models, which are aircraft models consisting partially or entirely of external user-provided subsystems. In the case of a mixed-origin model, instead of just selecting existing Aviary subsystems and combining them into an aircraft model, the user also provides the Aviary code with their own subsystems (these could be pre-existing codes such as [pyCycle](https://github.com/OpenMDAO/pyCycle), or they could be new subsystems the user has built themselves). An example of a mixed-origin model is the [OpenAeroStruct example case](../examples/OAS_subsystem). In this example, the built-in Aviary subsystem for mass is partially replaced by external subsystems. ### FLOPS/LEAPS References 1. [Here](https://ntrs.nasa.gov/api/citations/20190000442/downloads/20190000442.pdf) is an overview of the LEAPS code development. diff --git a/aviary/docs/theory_guide/merging_syntax.ipynb b/aviary/docs/theory_guide/merging_syntax.ipynb index 84837c324..2f9bc319f 100644 --- a/aviary/docs/theory_guide/merging_syntax.ipynb +++ b/aviary/docs/theory_guide/merging_syntax.ipynb @@ -26,7 +26,7 @@ "source": [ "# Merging Variable Info\n", "\n", - "When working with multiple external subsystems for Aviary, sometimes it is helpful to merge together variable hierarchies and metadata. The syntax for those merging functions can be found below. For more detailed descriptions of how to merge [metadata](../user_guide/variable_metadata.md) and [hierarchies](../user_guide/variable_hierarchy.md) visit the included links.\n", + "When working with multiple external subsystems for Aviary, sometimes it is helpful to merge together variable hierarchies and metadata. The syntax for those merging functions can be found below. For more detailed descriptions of how to merge [metadata](../user_guide/variable_metadata) and [hierarchies](../user_guide/variable_hierarchy) visit the included links.\n", "\n", "```{eval-rst}\n", " .. autofunction:: aviary.utils.merge_hierarchies.merge_hierarchies\n", @@ -64,4 +64,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/aviary/docs/user_guide/FLOPS_based_detailed_takeoff_and_landing.ipynb b/aviary/docs/user_guide/FLOPS_based_detailed_takeoff_and_landing.ipynb index c032c68dd..97de4a087 100644 --- a/aviary/docs/user_guide/FLOPS_based_detailed_takeoff_and_landing.ipynb +++ b/aviary/docs/user_guide/FLOPS_based_detailed_takeoff_and_landing.ipynb @@ -138,13 +138,13 @@ "\n", "takeoff_brake_release_user_options.set_val('max_duration', val=60.0, units='s')\n", "takeoff_brake_release_user_options.set_val('duration_ref', val=60.0, units='s')\n", - "takeoff_brake_release_user_options.set_val('max_range', val=7500.0, units='ft')\n", + "takeoff_brake_release_user_options.set_val('distance_max', val=7500.0, units='ft')\n", "takeoff_brake_release_user_options.set_val('max_velocity', val=167.85, units='kn')\n", "\n", "takeoff_brake_release_initial_guesses = av.AviaryValues()\n", "\n", "takeoff_brake_release_initial_guesses.set_val('times', [0., 30.], 's')\n", - "takeoff_brake_release_initial_guesses.set_val('range', [0., 4100.], 'ft')\n", + "takeoff_brake_release_initial_guesses.set_val('distance', [0., 4100.], 'ft')\n", "takeoff_brake_release_initial_guesses.set_val('velocity', [0.01, 150.], 'kn')\n", "\n", "gross_mass_units = 'lbm'\n", @@ -295,11 +295,11 @@ "takeoff_trajectory_builder.build_trajectory(\n", " aviary_options=aviary_options, model=takeoff.model, traj=traj)\n", "\n", - "max_range, units = takeoff_liftoff_user_options.get_item('max_range')\n", + "distance_max, units = takeoff_liftoff_user_options.get_item('distance_max')\n", "liftoff = takeoff_trajectory_builder.get_phase('takeoff_liftoff')\n", "\n", "liftoff.add_objective(\n", - " Dynamic.Mission.RANGE, loc='final', ref=max_range, units=units)\n", + " Dynamic.Mission.DISTANCE, loc='final', ref=distance_max, units=units)\n", "\n", "# Insert a constraint for a fake decision speed, until abort is added.\n", "takeoff.model.add_constraint(\n", @@ -431,7 +431,7 @@ "landing_approach_to_mic_p3_user_options.set_val('max_duration', val=50., units='s')\n", "landing_approach_to_mic_p3_user_options.set_val('duration_ref', val=50., units='s')\n", "landing_approach_to_mic_p3_user_options.set_val('initial_ref', val=50.0, units='s')\n", - "landing_approach_to_mic_p3_user_options.set_val('max_range', val=10000., units='ft')\n", + "landing_approach_to_mic_p3_user_options.set_val('distance_max', val=10000., units='ft')\n", "landing_approach_to_mic_p3_user_options.set_val('max_velocity', val=140., units='kn')\n", "landing_approach_to_mic_p3_user_options.set_val('altitude_ref', val=800., units='ft')\n", "\n", @@ -449,7 +449,7 @@ "landing_approach_to_mic_p3_initial_guesses = av.AviaryValues()\n", "\n", "landing_approach_to_mic_p3_initial_guesses.set_val('times', [-42., 15.], 's')\n", - "landing_approach_to_mic_p3_initial_guesses.set_val('range', [-4000., -2000.], 'ft')\n", + "landing_approach_to_mic_p3_initial_guesses.set_val('distance', [-4000., -2000.], 'ft')\n", "landing_approach_to_mic_p3_initial_guesses.set_val('velocity', 140., 'kn')\n", "landing_approach_to_mic_p3_initial_guesses.set_val('mass', detailed_landing_mass, 'lbm')\n", "landing_approach_to_mic_p3_initial_guesses.set_val('throttle', throttle)\n", @@ -548,10 +548,10 @@ "landing_trajectory_builder.build_trajectory(\n", " aviary_options=aviary_options, model=landing.model, traj=traj)\n", "\n", - "max_range, units = landing_fullstop_user_options.get_item('max_range')\n", + "distance_max, units = landing_fullstop_user_options.get_item('distance_max')\n", "fullstop = landing_trajectory_builder.get_phase('landing_fullstop')\n", "\n", - "fullstop.add_objective(Dynamic.Mission.RANGE, loc='final', ref=max_range, units=units)\n", + "fullstop.add_objective(Dynamic.Mission.DISTANCE, loc='final', ref=distance_max, units=units)\n", "\n", "landing.model.add_subsystem(\n", " 'input_sink',\n", diff --git a/aviary/docs/user_guide/aerodynamics.ipynb b/aviary/docs/user_guide/aerodynamics.ipynb index 8cc0b5929..18a25ca75 100644 --- a/aviary/docs/user_guide/aerodynamics.ipynb +++ b/aviary/docs/user_guide/aerodynamics.ipynb @@ -124,7 +124,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.8.17" } }, "nbformat": 4, diff --git a/aviary/docs/user_guide/aviary_commands.ipynb b/aviary/docs/user_guide/aviary_commands.ipynb index 87d179dd8..e3fe21a4e 100644 --- a/aviary/docs/user_guide/aviary_commands.ipynb +++ b/aviary/docs/user_guide/aviary_commands.ipynb @@ -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": { diff --git a/aviary/docs/user_guide/battery_subsystem_example.ipynb b/aviary/docs/user_guide/battery_subsystem_example.ipynb index 8b4732435..21faef03d 100644 --- a/aviary/docs/user_guide/battery_subsystem_example.ipynb +++ b/aviary/docs/user_guide/battery_subsystem_example.ipynb @@ -13,7 +13,7 @@ "\n", "We'll show you how to define the builder object, how to specify the pre-mission and mission models, and how to implement the interface methods.\n", "In each of these methods, we've removed the initial docstring to increase readability on this page.\n", - "The [`battery_builder.py`](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/examples/external_subsystems/battery/battery_builder.py) file contains the original methods and docstrings.\n", + "The [`battery_builder.py`](https://github.com/OpenMDAO/Aviary/blob/main/aviary/examples/external_subsystems/battery/battery_builder.py) file contains the original methods and docstrings.\n", "Please see that file if you want to examine this example builder in its entirety as one file.\n", "\n", "You can define these methods in any order you want within your subsystem builder and Aviary will use them at the appropriate times during analysis and optimization.\n", @@ -475,7 +475,7 @@ "In the `get_constraints` method, a constraint was added to the dictionary but did not include a `type` key, which is required as stated by the error message.\n", "\n", "If you encounter an error when using your subsystem, but the test here did not find it, please let the Aviary dev team know!\n", - "We'd love to hear from you on the [GitHub issues page](https://github.com/OpenMDAO/om-Aviary/issues) so we can help everyone write great external subsystems." + "We'd love to hear from you on the [GitHub issues page](https://github.com/OpenMDAO/Aviary/issues) so we can help everyone write great external subsystems." ] } ], diff --git a/aviary/docs/user_guide/examples_of_the_same_mission_at_different_UI_levels.ipynb b/aviary/docs/user_guide/examples_of_the_same_mission_at_different_UI_levels.ipynb index 159df792b..9ed57fff1 100644 --- a/aviary/docs/user_guide/examples_of_the_same_mission_at_different_UI_levels.ipynb +++ b/aviary/docs/user_guide/examples_of_the_same_mission_at_different_UI_levels.ipynb @@ -54,111 +54,87 @@ "metadata": {}, "outputs": [], "source": [ - "from aviary.api import Dynamic\n", "import aviary.api as av\n", "\n", "phase_info = {\n", - " 'pre_mission': {\n", - " 'include_takeoff': True,\n", - " 'optimize_mass': True,\n", - " },\n", - " 'climb': {\n", - " 'subsystem_options': {\n", - " 'core_aerodynamics': {'method': 'computed'}\n", - " },\n", - " 'user_options': {\n", - " 'fix_initial': {Dynamic.Mission.MASS: False, Dynamic.Mission.RANGE: False},\n", - " 'fix_initial_time': True,\n", - " 'fix_duration': False,\n", - " 'num_segments': 6,\n", - " 'order': 3,\n", - " 'initial_altitude': (0., 'ft'),\n", - " 'initial_ref': (1., 's'),\n", - " 'initial_bounds': ((0., 500.), 's'),\n", - " 'duration_ref': (1452., 's'),\n", - " 'duration_bounds': ((726., 2904.), 's'),\n", - " 'final_altitude': (10668, 'm'),\n", - " 'input_initial': True,\n", - " 'no_descent': False,\n", - " 'initial_mach': 0.1,\n", - " 'final_mach': 0.79,\n", - " 'fix_range': False,\n", - " 'add_initial_mass_constraint': False,\n", + " \"pre_mission\": {\"include_takeoff\": False, \"optimize_mass\": True},\n", + " \"climb\": {\n", + " \"subsystem_options\": {\"core_aerodynamics\": {\"method\": \"computed\"}},\n", + " \"user_options\": {\n", + " \"optimize_mach\": False,\n", + " \"optimize_altitude\": False,\n", + " \"polynomial_control_order\": 1,\n", + " \"num_segments\": 5,\n", + " \"order\": 3,\n", + " \"solve_for_distance\": False,\n", + " \"initial_mach\": (0.2, \"unitless\"),\n", + " \"final_mach\": (0.72, \"unitless\"),\n", + " \"mach_bounds\": ((0.18, 0.74), \"unitless\"),\n", + " \"initial_altitude\": (0.0, \"ft\"),\n", + " \"final_altitude\": (32000.0, \"ft\"),\n", + " \"altitude_bounds\": ((0.0, 34000.0), \"ft\"),\n", + " \"throttle_enforcement\": \"path_constraint\",\n", + " \"fix_initial\": True,\n", + " \"constrain_final\": False,\n", + " \"fix_duration\": False,\n", + " \"initial_bounds\": ((0.0, 0.0), \"min\"),\n", + " \"duration_bounds\": ((64.0, 192.0), \"min\"),\n", + " \"add_initial_mass_constraint\": False,\n", " },\n", - " 'initial_guesses': {\n", - " 'times': ([2., 24.2], 'min'),\n", - " 'altitude': ([0., 35.e3], 'ft'),\n", - " 'velocity': ([220., 455.49], 'kn'),\n", - " 'mass': ([170.e3, 165.e3], 'lbm'),\n", - " 'range': ([0., 160.3], 'nmi'),\n", - " 'velocity_rate': ([0.25, 0.05], 'm/s**2'),\n", - " 'throttle': ([0.5, 0.5], 'unitless'),\n", - " }\n", + " \"initial_guesses\": {\"times\": ([0, 128], \"min\")},\n", " },\n", - " 'cruise': {\n", - " 'subsystem_options': {\n", - " 'core_aerodynamics': {'method': 'computed'}\n", - " },\n", - " 'user_options': {\n", - " 'fix_initial': False,\n", - " 'fix_final': False,\n", - " 'fix_duration': False,\n", - " 'num_segments': 1,\n", - " 'order': 3,\n", - " 'initial_ref': (1., 's'),\n", - " 'initial_bounds': ((500., 4000.), 's'),\n", - " 'duration_ref': (24370.8, 's'),\n", - " 'duration_bounds': ((726., 48741.6), 's'),\n", - " 'min_altitude': (10.668e3, 'm'),\n", - " 'max_altitude': (10.668e3, 'm'),\n", - " 'min_mach': 0.79,\n", - " 'max_mach': 0.79,\n", - " 'required_available_climb_rate': (1.524, 'm/s'),\n", - " 'mass_f_cruise': (1.e4, 'kg'),\n", - " 'range_f_cruise': (1.e6, 'm'),\n", + " \"cruise\": {\n", + " \"subsystem_options\": {\"core_aerodynamics\": {\"method\": \"computed\"}},\n", + " \"user_options\": {\n", + " \"optimize_mach\": False,\n", + " \"optimize_altitude\": False,\n", + " \"polynomial_control_order\": 1,\n", + " \"num_segments\": 5,\n", + " \"order\": 3,\n", + " \"solve_for_distance\": False,\n", + " \"initial_mach\": (0.72, \"unitless\"),\n", + " \"final_mach\": (0.72, \"unitless\"),\n", + " \"mach_bounds\": ((0.7, 0.74), \"unitless\"),\n", + " \"initial_altitude\": (32000.0, \"ft\"),\n", + " \"final_altitude\": (34000.0, \"ft\"),\n", + " \"altitude_bounds\": ((23000.0, 38000.0), \"ft\"),\n", + " \"throttle_enforcement\": \"boundary_constraint\",\n", + " \"fix_initial\": False,\n", + " \"constrain_final\": False,\n", + " \"fix_duration\": False,\n", + " \"initial_bounds\": ((64.0, 192.0), \"min\"),\n", + " \"duration_bounds\": ((56.5, 169.5), \"min\"),\n", " },\n", - " 'initial_guesses': {\n", - " 'times': ([26.2, 406.18], 'min'),\n", - " 'altitude': ([35.e3, 35.e3], 'ft'),\n", - " 'velocity': ([455.49, 455.49], 'kn'),\n", - " 'mass': ([165.e3, 140.e3], 'lbm'),\n", - " 'range': ([160.3, 3243.9], 'nmi'),\n", - " 'velocity_rate': ([0., 0.], 'm/s**2'),\n", - " 'throttle': ([0.95, 0.9], 'unitless'),\n", - " }\n", + " \"initial_guesses\": {\"times\": ([128, 113], \"min\")},\n", " },\n", - " 'descent': {\n", - " 'subsystem_options': {\n", - " 'core_aerodynamics': {'method': 'computed'}\n", + " \"descent\": {\n", + " \"subsystem_options\": {\"core_aerodynamics\": {\"method\": \"computed\"}},\n", + " \"user_options\": {\n", + " \"optimize_mach\": False,\n", + " \"optimize_altitude\": False,\n", + " \"polynomial_control_order\": 1,\n", + " \"num_segments\": 5,\n", + " \"order\": 3,\n", + " \"solve_for_distance\": False,\n", + " \"initial_mach\": (0.72, \"unitless\"),\n", + " \"final_mach\": (0.36, \"unitless\"),\n", + " \"mach_bounds\": ((0.34, 0.74), \"unitless\"),\n", + " \"initial_altitude\": (34000.0, \"ft\"),\n", + " \"final_altitude\": (500.0, \"ft\"),\n", + " \"altitude_bounds\": ((0.0, 38000.0), \"ft\"),\n", + " \"throttle_enforcement\": \"path_constraint\",\n", + " \"fix_initial\": False,\n", + " \"constrain_final\": True,\n", + " \"fix_duration\": False,\n", + " \"initial_bounds\": ((120.5, 361.5), \"min\"),\n", + " \"duration_bounds\": ((29.0, 87.0), \"min\"),\n", " },\n", - " 'user_options': {\n", - " 'fix_initial': False,\n", - " 'fix_range': True,\n", - " 'fix_duration': False,\n", - " 'num_segments': 5,\n", - " 'order': 3,\n", - " 'initial_ref': (1., 's'),\n", - " 'initial_bounds': ((10.e3, 30.e3), 's'),\n", - " 'duration_ref': (1754.4, 's'),\n", - " 'duration_bounds': ((726., 3508.8), 's'),\n", - " 'initial_altitude': (10.668e3, 'm'),\n", - " 'final_altitude': (10.668, 'm'),\n", - " 'initial_mach': 0.79,\n", - " 'final_mach': 0.3,\n", - " 'no_climb': False,\n", - " },\n", - " 'initial_guesses': {\n", - " 'times': ([432.38, 29.24], 'min'),\n", - " 'altitude': ([35.e3, 35.], 'ft'),\n", - " 'velocity': ([455.49, 198.44], 'kn'),\n", - " 'mass': ([120.e3, 115.e3], 'lbm'),\n", - " 'range': ([3243.9, 3378.7], 'nmi'),\n", - " 'velocity_rate': ([-0.25, 0.0], 'm/s**2'),\n", - " 'throttle': ([0., 0.], 'unitless'),\n", - " }\n", + " \"initial_guesses\": {\"times\": ([241, 58], \"min\")},\n", " },\n", - " 'post_mission': {\n", - " 'include_landing': True,\n", + " \"post_mission\": {\n", + " \"include_landing\": False,\n", + " \"constrain_range\": True,\n", + " \"target_range\": (1906, \"nmi\"),\n", " },\n", "}\n", "\n", @@ -170,8 +146,7 @@ "# Allow for user overrides here\n", "prob.load_inputs(csv_path, phase_info)\n", "\n", - "# Have checks for clashing user inputs\n", - "prob.check_inputs()\n", + "prob.check_and_preprocess_inputs()\n", "\n", "prob.add_pre_mission_systems()\n", "\n", @@ -215,96 +190,78 @@ "import dymos as dm\n", "import openmdao.api as om\n", "import scipy.constants as _units\n", - "from packaging import version\n", - "\n", - "from aviary.api import HeightEnergyLandingPhaseBuilder as Landing\n", - "from aviary.api import HeightEnergyTakeoffPhaseBuilder as Takeoff\n", - "from aviary.api import HeightEnergyClimbPhaseBuilder as Climb\n", - "from aviary.api import HeightEnergyCruisePhaseBuilder as Cruise\n", - "from aviary.api import HeightEnergyDescentPhaseBuilder as Descent\n", - "\n", - "from aviary.api import Aircraft, Dynamic, Mission\n", "\n", "import aviary.api as av\n", "\n", "\n", - "use_new_dymos_syntax = version.parse(dm.__version__) > version.parse(\"1.8.0\")\n", - "\n", - "prob = om.Problem()\n", - "\n", + "prob = om.Problem(model=om.Group())\n", "driver = prob.driver = om.ScipyOptimizeDriver()\n", - "opt_settings = prob.driver.opt_settings\n", - "\n", - "driver.options['optimizer'] = 'SLSQP'\n", - "opt_settings['maxiter'] = 0\n", - "opt_settings['ftol'] = 5.0e-3\n", - "opt_settings['eps'] = 1e-2\n", + "driver.options[\"optimizer\"] = \"SLSQP\"\n", + "driver.options[\"maxiter\"] = 1\n", "\n", "##########################################\n", "# Aircraft Input Variables and Options #\n", "##########################################\n", "\n", - "aviary_inputs = av.get_flops_inputs('LargeSingleAisle1FLOPS')\n", - "aviary_outputs = av.get_flops_outputs('LargeSingleAisle1FLOPS')\n", + "csv_path = \"models/test_aircraft/aircraft_for_bench_FwFm.csv\"\n", + "\n", + "aviary_inputs, _ = av.create_vehicle('models/test_aircraft/aircraft_for_bench_FwFm.csv')\n", "\n", - "av.preprocess_options(aviary_inputs)\n", + "engine = av.EngineDeck(options=aviary_inputs)\n", + "av.preprocess_propulsion(aviary_inputs, [engine])\n", "\n", "alt_airport = 0 # ft\n", "\n", "alt_i_climb = 0*_units.foot # m\n", - "alt_f_climb = 35000.0*_units.foot # m\n", - "mass_i_climb = 180623*_units.lb # kg\n", - "mass_f_climb = 176765*_units.lb # kg\n", - "v_i_climb = 198.44*_units.knot # m/s\n", - "v_f_climb = 455.49*_units.knot # m/s\n", - "# initial mach set to lower value so it can intersect with takeoff end mach\n", - "# mach_i_climb = 0.3\n", + "alt_f_climb = 32000.0*_units.foot # m\n", + "mass_i_climb = 131000*_units.lb # kg\n", + "mass_f_climb = 126000*_units.lb # kg\n", "mach_i_climb = 0.2\n", - "mach_f_climb = 0.79\n", - "range_i_climb = 0*_units.nautical_mile # m\n", - "range_f_climb = 160.3*_units.nautical_mile # m\n", - "t_i_climb = 2 * _units.minute # sec\n", - "t_f_climb = 26.20*_units.minute # sec\n", + "mach_f_climb = 0.72\n", + "distance_i_climb = 0*_units.nautical_mile # m\n", + "distance_f_climb = 160.3*_units.nautical_mile # m\n", + "t_i_climb = 0. * _units.minute # sec\n", + "t_f_climb = 128 * _units.minute # sec\n", "t_duration_climb = t_f_climb - t_i_climb\n", "\n", - "alt_i_cruise = 35000*_units.foot # m\n", - "alt_f_cruise = 35000*_units.foot # m\n", - "alt_min_cruise = 35000*_units.foot # m\n", - "alt_max_cruise = 35000*_units.foot # m\n", - "mass_i_cruise = 176765*_units.lb # kg\n", - "mass_f_cruise = 143521*_units.lb # kg\n", - "v_i_cruise = 455.49*_units.knot # m/s\n", - "v_f_cruise = 455.49*_units.knot # m/s\n", - "# mach_i_cruise = 0.79\n", - "# mach_f_cruise = 0.79\n", - "mach_min_cruise = 0.79\n", - "mach_max_cruise = 0.79\n", - "range_i_cruise = 160.3*_units.nautical_mile # m\n", - "range_f_cruise = 3243.9*_units.nautical_mile # m\n", - "t_i_cruise = 26.20*_units.minute # sec\n", - "t_f_cruise = 432.38*_units.minute # sec\n", + "alt_i_cruise = 32000*_units.foot # m\n", + "alt_f_cruise = 34000*_units.foot # m\n", + "alt_min_cruise = 32000*_units.foot # m\n", + "alt_max_cruise = 34000*_units.foot # m\n", + "mass_i_cruise = 126000*_units.lb # kg\n", + "mass_f_cruise = 102000*_units.lb # kg\n", + "cruise_mach = 0.79\n", + "distance_i_cruise = 160.3*_units.nautical_mile # m\n", + "distance_f_cruise = 3243.9*_units.nautical_mile # m\n", + "t_i_cruise = 128. * _units.minute # sec\n", + "t_f_cruise = 241. * _units.minute # sec\n", "t_duration_cruise = t_f_cruise - t_i_cruise\n", "\n", - "alt_i_descent = 35000*_units.foot\n", - "# final altitude set to 35 to ensure landing is feasible point\n", - "# alt_f_descent = 0*_units.foot\n", - "alt_f_descent = 35*_units.foot\n", - "v_i_descent = 455.49*_units.knot\n", - "v_f_descent = 198.44*_units.knot\n", - "mach_i_descent = 0.79\n", + "alt_i_descent = 34000*_units.foot\n", + "alt_f_descent = 500*_units.foot\n", + "mach_i_descent = 0.72\n", "mach_f_descent = 0.3\n", - "mass_i_descent = 143521*_units.pound\n", - "mass_f_descent = 143035*_units.pound\n", - "range_i_descent = 3243.9*_units.nautical_mile\n", - "range_f_descent = 3378.7*_units.nautical_mile\n", - "t_i_descent = 432.38*_units.minute\n", - "t_f_descent = 461.62*_units.minute\n", + "mass_i_descent = 102000*_units.pound\n", + "mass_f_descent = 101000*_units.pound\n", + "distance_i_descent = 3243.9*_units.nautical_mile\n", + "distance_f_descent = 3378.7*_units.nautical_mile\n", + "t_i_descent = 241. * _units.minute\n", + "t_f_descent = 299. * _units.minute\n", "t_duration_descent = t_f_descent - t_i_descent\n", "\n", + "##########################\n", + "# Design Variables #\n", + "##########################\n", + "\n", + "# Nudge it a bit off the correct answer to verify that the optimize takes us there.\n", + "aviary_inputs.set_val(av.Mission.Design.GROSS_MASS, 135000.0, units='lbm')\n", + "\n", + "prob.model.add_design_var(av.Mission.Design.GROSS_MASS, units='lbm',\n", + " lower=100000.0, upper=200000.0, ref=135000)\n", + "\n", "##################\n", "# Define Phases #\n", "##################\n", - "\n", "num_segments_climb = 6\n", "num_segments_cruise = 1\n", "num_segments_descent = 5\n", @@ -321,44 +278,37 @@ " num_segments=num_segments_descent, order=3, compressed=True,\n", " segment_ends=descent_seg_ends)\n", "\n", - "takeoff_options = Takeoff(\n", - " airport_altitude=alt_airport, # ft\n", - " num_engines=aviary_inputs.get_val(Aircraft.Engine.NUM_ENGINES)\n", - ")\n", - "\n", - "climb_options = Climb(\n", + "climb_options = av.HeightEnergyPhaseBuilder(\n", " 'test_climb',\n", " user_options=av.AviaryValues({\n", " 'initial_altitude': (alt_i_climb, 'm'),\n", " 'final_altitude': (alt_f_climb, 'm'),\n", " 'initial_mach': (mach_i_climb, 'unitless'),\n", " 'final_mach': (mach_f_climb, 'unitless'),\n", - " 'fix_initial': (False, 'unitless'),\n", - " 'fix_range': (False, 'unitless'),\n", - " 'input_initial': (True, 'unitless'),\n", + " 'fix_initial': (True, 'unitless'),\n", + " 'use_polynomial_control': (False, 'unitless'),\n", " }),\n", " core_subsystems=av.default_mission_subsystems,\n", " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", " transcription=transcription_climb,\n", ")\n", "\n", - "cruise_options = Cruise(\n", + "cruise_options = av.HeightEnergyPhaseBuilder(\n", " 'test_cruise',\n", " user_options=av.AviaryValues({\n", - " 'min_altitude': (alt_min_cruise, 'm'),\n", - " 'max_altitude': (alt_max_cruise, 'm'),\n", - " 'min_mach': (mach_min_cruise, 'unitless'),\n", - " 'max_mach': (mach_max_cruise, 'unitless'),\n", + " 'initial_altitude': (alt_min_cruise, 'm'),\n", + " 'final_altitude': (alt_max_cruise, 'm'),\n", + " 'initial_mach': (cruise_mach, 'unitless'),\n", + " 'final_mach': (cruise_mach, 'unitless'),\n", " 'required_available_climb_rate': (300, 'ft/min'),\n", " 'fix_initial': (False, 'unitless'),\n", - " 'fix_final': (False, 'unitless'),\n", " }),\n", " core_subsystems=av.default_mission_subsystems,\n", " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", " transcription=transcription_cruise,\n", ")\n", "\n", - "descent_options = Descent(\n", + "descent_options = av.HeightEnergyPhaseBuilder(\n", " 'test_descent',\n", " user_options=av.AviaryValues({\n", " 'final_altitude': (alt_f_descent, 'm'),\n", @@ -366,17 +316,14 @@ " 'initial_mach': (mach_i_descent, 'unitless'),\n", " 'final_mach': (mach_f_descent, 'unitless'),\n", " 'fix_initial': (False, 'unitless'),\n", - " 'fix_range': (True, 'unitless'),\n", + " 'use_polynomial_control': (False, 'unitless'),\n", " }),\n", " core_subsystems=av.default_mission_subsystems,\n", " subsystem_options={'core_aerodynamics': {'method': 'computed'}},\n", " transcription=transcription_descent,\n", ")\n", "\n", - "landing_options = Landing(\n", - " ref_wing_area=aviary_inputs.get_val(Aircraft.Wing.AREA, 'ft**2'),\n", - " Cl_max_ldg=aviary_inputs.get_val(Mission.Landing.LIFT_COEFFICIENT_MAX)\n", - ")\n", + "av.preprocess_crewpayload(aviary_inputs)\n", "\n", "# Upstream static analysis for aero\n", "prob.model.add_subsystem(\n", @@ -390,33 +337,30 @@ "# coupling / strong_couple=False)\n", "strong_couple = False\n", "\n", - "takeoff = takeoff_options.build_phase(False)\n", - "\n", "climb = climb_options.build_phase(aviary_options=aviary_inputs)\n", "\n", "cruise = cruise_options.build_phase(aviary_options=aviary_inputs)\n", "\n", "descent = descent_options.build_phase(aviary_options=aviary_inputs)\n", "\n", - "landing = landing_options.build_phase(False)\n", - "\n", - "prob.model.add_subsystem(\n", - " 'takeoff', takeoff, promotes_inputs=['aircraft:*', 'mission:*'],\n", - " promotes_outputs=['mission:*'])\n", - "\n", "traj = prob.model.add_subsystem('traj', dm.Trajectory())\n", "\n", "# if fix_initial is false, can we always set input_initial to be true for\n", "# necessary states, and then ignore if we use a linkage?\n", - "climb.set_time_options(\n", - " fix_initial=True, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_climb*2), duration_ref=t_duration_climb)\n", - "cruise.set_time_options(\n", - " fix_initial=False, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise)\n", + "climb.set_time_options(fix_initial=True, fix_duration=False, units='s',\n", + " duration_bounds=(t_duration_climb*0.5, t_duration_climb*2),\n", + " duration_ref=t_duration_climb)\n", + "cruise.set_time_options(fix_initial=False, fix_duration=False, units='s',\n", + " duration_bounds=(t_duration_cruise*0.5, t_duration_cruise*2),\n", + " duration_ref=t_duration_cruise,\n", + " initial_bounds=(t_duration_climb*0.5, t_duration_climb*2))\n", "descent.set_time_options(\n", " fix_initial=False, fix_duration=False, units='s',\n", - " duration_bounds=(1, t_duration_descent*2), duration_ref=t_duration_descent)\n", + " duration_bounds=(t_duration_descent*0.5, t_duration_descent*2),\n", + " duration_ref=t_duration_descent,\n", + " initial_bounds=(\n", + " (t_duration_cruise + t_duration_climb)*0.5,\n", + " (t_duration_cruise + t_duration_climb)*2))\n", "\n", "traj.add_phase('climb', climb)\n", "\n", @@ -424,109 +368,37 @@ "\n", "traj.add_phase('descent', descent)\n", "\n", - "if use_new_dymos_syntax:\n", - " climb.timeseries_options['use_prefix'] = True\n", - " cruise.timeseries_options['use_prefix'] = True\n", - " descent.timeseries_options['use_prefix'] = True\n", - "\n", - "prob.model.add_subsystem(\n", - " 'landing', landing, promotes_inputs=['aircraft:*', 'mission:*'],\n", - " promotes_outputs=['mission:*'])\n", - "\n", - "traj.link_phases([\"climb\", \"cruise\"], [\"time\", Dynamic.Mission.ALTITUDE,\n", - " Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple)\n", - "traj.link_phases([\"cruise\", \"descent\"], [\"time\", Dynamic.Mission.ALTITUDE,\n", - " Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple)\n", + "traj.link_phases([\"climb\", \"cruise\", \"descent\"], [\n", + " \"time\", \"mass\", \"distance\"], connected=strong_couple)\n", "\n", "traj = av.setup_trajectory_params(prob.model, traj, aviary_inputs)\n", "\n", - "##################################\n", - "# Connect in Takeoff and Landing #\n", - "##################################\n", + "##########################\n", + "# Constraints #\n", + "##########################\n", "\n", - "prob.model.add_subsystem(\n", - " \"takeoff_constraints\",\n", - " om.ExecComp(\n", - " [\n", - " \"takeoff_mass_con=takeoff_mass-climb_start_mass\",\n", - " \"takeoff_range_con=takeoff_range-climb_start_range\",\n", - " \"takeoff_vel_con=takeoff_vel-climb_start_vel\",\n", - " \"takeoff_alt_con=takeoff_alt-climb_start_alt\"\n", - " ],\n", - " takeoff_mass_con={'units': 'lbm'}, takeoff_mass={'units': 'lbm'},\n", - " climb_start_mass={'units': 'lbm'},\n", - " takeoff_range_con={'units': 'ft'}, takeoff_range={'units': 'ft'},\n", - " climb_start_range={'units': 'ft'},\n", - " takeoff_vel_con={'units': 'm/s'}, takeoff_vel={'units': 'm/s'},\n", - " climb_start_vel={'units': 'm/s'},\n", - " takeoff_alt_con={'units': 'ft'}, takeoff_alt={'units': 'ft'},\n", - " climb_start_alt={'units': 'ft'}\n", - " ),\n", - " promotes_inputs=[\n", - " (\"takeoff_mass\", Mission.Takeoff.FINAL_MASS),\n", - " (\"takeoff_range\", Mission.Takeoff.GROUND_DISTANCE),\n", - " (\"takeoff_vel\", Mission.Takeoff.FINAL_VELOCITY),\n", - " (\"takeoff_alt\", Mission.Takeoff.FINAL_ALTITUDE),\n", - " ],\n", - " promotes_outputs=[\"takeoff_mass_con\", \"takeoff_range_con\",\n", - " \"takeoff_vel_con\", \"takeoff_alt_con\"],\n", - ")\n", + "ecomp = om.ExecComp('fuel_burned = initial_mass - descent_mass_final',\n", + " initial_mass={'units': 'lbm', 'shape': 1},\n", + " descent_mass_final={'units': 'lbm', 'shape': 1},\n", + " fuel_burned={'units': 'lbm', 'shape': 1})\n", "\n", - "prob.model.connect('traj.climb.states:mass',\n", - " 'takeoff_constraints.climb_start_mass', src_indices=[0])\n", - "prob.model.connect('traj.climb.states:range',\n", - " 'takeoff_constraints.climb_start_range', src_indices=[0])\n", - "prob.model.connect('traj.climb.states:velocity',\n", - " 'takeoff_constraints.climb_start_vel', src_indices=[0])\n", - "prob.model.connect('traj.climb.states:altitude',\n", - " 'takeoff_constraints.climb_start_alt', src_indices=[0])\n", - "\n", - "prob.model.connect(Mission.Takeoff.FINAL_MASS,\n", - " 'traj.climb.initial_states:mass')\n", - "prob.model.connect(Mission.Takeoff.GROUND_DISTANCE,\n", - " 'traj.climb.initial_states:range')\n", - "prob.model.connect(Mission.Takeoff.FINAL_VELOCITY,\n", - " 'traj.climb.initial_states:velocity')\n", - "prob.model.connect(Mission.Takeoff.FINAL_ALTITUDE,\n", - " 'traj.climb.initial_states:altitude')\n", - "\n", - "prob.model.connect('traj.descent.states:mass',\n", - " Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1])\n", - "prob.model.connect('traj.descent.states:altitude', Mission.Landing.INITIAL_ALTITUDE,\n", - " src_indices=[-1])\n", + "prob.model.add_subsystem('fuel_burn', ecomp,\n", + " promotes_inputs=[\n", + " ('initial_mass', av.Mission.Design.GROSS_MASS)],\n", + " promotes_outputs=['fuel_burned'])\n", + "\n", + "prob.model.connect(\"traj.descent.states:mass\",\n", + " \"fuel_burn.descent_mass_final\", src_indices=[-1])\n", "\n", "##########################\n", "# Add Objective Function #\n", "##########################\n", "\n", - "# This is an example of a overall mission objective\n", - "# create a compound objective that minimizes climb time and maximizes final mass\n", - "# we are maxing final mass b/c we don't have an independent value for fuel_mass yet\n", - "# we are going to normalize these (makign each of the sub-objectives approx = 1 )\n", - "prob.model.add_subsystem(\n", - " \"regularization\",\n", - " om.ExecComp(\n", - " # TODO: change the scaling on climb_duration\n", - " \"reg_objective = - descent_mass_final/60000\",\n", - " reg_objective=0.0,\n", - " descent_mass_final={\"units\": \"kg\", \"shape\": 1},\n", - " ),\n", - " promotes_outputs=['reg_objective']\n", - ")\n", - "# connect the final mass from cruise into the objective\n", - "prob.model.connect(\"traj.descent.states:mass\",\n", - " \"regularization.descent_mass_final\", src_indices=[-1])\n", - "\n", - "prob.model.add_objective('reg_objective', ref=1)\n", + "prob.model.add_objective(\"fuel_burned\", ref=1e4)\n", "\n", - "# Set initial default values for all aircraft variables.\n", + "# Set initial default values for all LEAPS aircraft variables.\n", "av.set_aviary_initial_values(prob.model, aviary_inputs)\n", "\n", - "# TODO: Why is this in outputs and not inputs?\n", - "key = Aircraft.Engine.THRUST_REVERSERS_MASS\n", - "val, units = aviary_outputs.get_item(key)\n", - "prob.model.set_input_defaults(key, val, units)\n", - "\n", "prob.model.add_subsystem(\n", " 'input_sink',\n", " av.VariablesIn(aviary_options=aviary_inputs),\n", @@ -534,78 +406,56 @@ " promotes_outputs=['*']\n", ")\n", "\n", - "prob.setup()\n", + "prob.setup(force_alloc_complex=True)\n", "\n", "###########################################\n", - "# Intial Settings for States and Controls #\n", + "# Initial Settings for States and Controls #\n", "###########################################\n", "\n", "prob.set_val('traj.climb.t_initial', t_i_climb, units='s')\n", "prob.set_val('traj.climb.t_duration', t_duration_climb, units='s')\n", "\n", - "prob.set_val('traj.climb.states:altitude', climb.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m')\n", - "# prob.set_val(\n", - "# 'traj.climb.states:velocity', climb.interp(Dynamic.Mission.VELOCITY, ys=[170, v_f_climb]),\n", - "# units='m/s')\n", - "prob.set_val('traj.climb.states:velocity', climb.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_climb, v_f_climb]), units='m/s')\n", + "prob.set_val('traj.climb.controls:altitude', climb.interp(\n", + " av.Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m')\n", + "prob.set_val(\n", + " 'traj.climb.controls:mach', climb.interp(\n", + " av.Dynamic.Mission.MACH, ys=[mach_i_climb, mach_f_climb]), units='unitless')\n", "prob.set_val('traj.climb.states:mass', climb.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg')\n", - "prob.set_val('traj.climb.states:range', climb.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_climb, range_f_climb]), units='m') # nmi\n", - "\n", - "prob.set_val('traj.climb.controls:velocity_rate',\n", - " climb.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.25, 0.0]),\n", - " units='m/s**2')\n", - "prob.set_val('traj.climb.controls:throttle',\n", - " climb.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]),\n", - " units='unitless')\n", + " av.Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg')\n", + "prob.set_val('traj.climb.states:distance', climb.interp(\n", + " av.Dynamic.Mission.DISTANCE, ys=[distance_i_climb, distance_f_climb]), units='m')\n", "\n", "prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s')\n", "prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s')\n", "\n", - "prob.set_val('traj.cruise.states:altitude', cruise.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m')\n", - "prob.set_val('traj.cruise.states:velocity', cruise.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_cruise, v_f_cruise]), units='m/s')\n", + "prob.set_val('traj.cruise.polynomial_controls:altitude', cruise.interp(\n", + " av.Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m')\n", + "prob.set_val(\n", + " 'traj.cruise.polynomial_controls:mach', cruise.interp(\n", + " av.Dynamic.Mission.MACH, ys=[cruise_mach, cruise_mach]), units='unitless')\n", "prob.set_val('traj.cruise.states:mass', cruise.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg')\n", - "prob.set_val('traj.cruise.states:range', cruise.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi\n", - "\n", - "prob.set_val('traj.cruise.controls:velocity_rate',\n", - " cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]),\n", - " units='m/s**2')\n", - "prob.set_val('traj.cruise.controls:throttle',\n", - " cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]),\n", - " units='unitless')\n", + " av.Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg')\n", + "prob.set_val('traj.cruise.states:distance', cruise.interp(\n", + " av.Dynamic.Mission.DISTANCE, ys=[distance_i_cruise, distance_f_cruise]), units='m')\n", "\n", "prob.set_val('traj.descent.t_initial', t_i_descent, units='s')\n", "prob.set_val('traj.descent.t_duration', t_duration_descent, units='s')\n", "\n", - "prob.set_val('traj.descent.states:altitude', descent.interp(\n", - " Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m')\n", - "prob.set_val('traj.descent.states:velocity', descent.interp(\n", - " Dynamic.Mission.VELOCITY, ys=[v_i_descent, v_f_descent]), units='m/s')\n", + "prob.set_val('traj.descent.controls:altitude', descent.interp(\n", + " av.Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m')\n", + "prob.set_val(\n", + " 'traj.descent.controls:mach', descent.interp(\n", + " av.Dynamic.Mission.MACH, ys=[mach_i_descent, mach_f_descent]), units='unitless')\n", "prob.set_val('traj.descent.states:mass', descent.interp(\n", - " Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg')\n", - "prob.set_val('traj.descent.states:range', descent.interp(\n", - " Dynamic.Mission.RANGE, ys=[range_i_descent, range_f_descent]), units='m')\n", - "\n", - "prob.set_val('traj.descent.controls:velocity_rate',\n", - " descent.interp(Dynamic.Mission.VELOCITY_RATE, ys=[-0.25, 0.0]),\n", - " units='m/s**2')\n", - "prob.set_val('traj.descent.controls:throttle',\n", - " descent.interp(Dynamic.Mission.THROTTLE, ys=[0.0, 0.0]),\n", - " units='unitless')\n", + " av.Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg')\n", + "prob.set_val('traj.descent.states:distance', descent.interp(\n", + " av.Dynamic.Mission.DISTANCE, ys=[distance_i_descent, distance_f_descent]), units='m')\n", "\n", "# Turn off solver printing so that the SNOPT output is readable.\n", "prob.set_solver_print(level=0)\n", "\n", "dm.run_problem(prob, simulate=False, make_plots=False, simulate_kwargs={\n", - " 'times_per_seg': 100, 'atol': 1e-9, 'rtol': 1e-9},\n", - " solution_record_file='large_single_aisle_1_solution.db')\n" + " 'times_per_seg': 100, 'atol': 1e-9, 'rtol': 1e-9})" ] } ], @@ -625,7 +475,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.8.17" } }, "nbformat": 4, diff --git a/aviary/docs/user_guide/external_aero.ipynb b/aviary/docs/user_guide/external_aero.ipynb index ff514ac63..906c65b90 100644 --- a/aviary/docs/user_guide/external_aero.ipynb +++ b/aviary/docs/user_guide/external_aero.ipynb @@ -24,7 +24,7 @@ "outputs": [], "source": [ "# This hidden cell just creates a component that produces the lift and drag polar. \n", - "# The implementation details are uninportant to the example.\n", + "# The implementation details are unimportant to the example.\n", "import openmdao.api as om\n", "import numpy as np\n", "\n", @@ -268,7 +268,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.8.17" }, "orphan": true }, diff --git a/aviary/docs/user_guide/features/overriding.ipynb b/aviary/docs/user_guide/features/overriding.ipynb index e7f81401d..de7b9a634 100644 --- a/aviary/docs/user_guide/features/overriding.ipynb +++ b/aviary/docs/user_guide/features/overriding.ipynb @@ -46,8 +46,9 @@ "source": [ "from aviary.api import Aircraft\n", "import aviary.api as av\n", + "from aviary.validation_cases.validation_data.flops_data.FLOPS_Test_Data import FLOPS_Test_Data\n", "\n", - "aviary_inputs = av.AviaryValues(av.FLOPS_Test_Data['LargeSingleAisle1FLOPS']['inputs'])\n", + "aviary_inputs = av.AviaryValues(FLOPS_Test_Data['LargeSingleAisle1FLOPS']['inputs'])\n", "\n", "aviary_inputs.set_val(Aircraft.HorizontalTail.MASS, 2200.0, units='lbm')" ] diff --git a/aviary/docs/user_guide/input_files.ipynb b/aviary/docs/user_guide/input_files.ipynb index 39e37182e..cf8a12fb5 100644 --- a/aviary/docs/user_guide/input_files.ipynb +++ b/aviary/docs/user_guide/input_files.ipynb @@ -18,7 +18,7 @@ "source": [ "## Aviary Aircraft Input Files\n", "\n", - "Aviary uses a .csv file to load in information about the aircraft to be designed.\n", + "Aviary can use a .csv file to load in information about the aircraft to be designed.\n", "The file format is straightforward and follows the convention of `name, value, units` for each row.\n", "The names of the variables are detailed in the [Understanding Variable Metadata doc page](variable_metadata).\n", "\n", diff --git a/aviary/docs/user_guide/propulsion.md b/aviary/docs/user_guide/propulsion.md index 74a438c08..a4611cdeb 100644 --- a/aviary/docs/user_guide/propulsion.md +++ b/aviary/docs/user_guide/propulsion.md @@ -13,7 +13,7 @@ Each unique type of engine is referred to as an engine model. Engine decks are a type of engine model - they use the same basic interface, but have additional functionality to handle reading and processing data files. ### Formatting -An engine deck data file requires specific formatting for Aviary to correctly interpret. These files must follow the [Aviary data file format](input_files).An example of a properly-formatted engine deck can be found [here](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/models/engines/turbofan_22k.deck). +An engine deck data file requires specific formatting for Aviary to correctly interpret. These files must follow the [Aviary data file format](input_files).An example of a properly-formatted engine deck can be found [here](https://github.com/OpenMDAO/Aviary/blob/main/aviary/models/engines/turbofan_22k.deck). ### Variables diff --git a/aviary/docs/user_guide/step_by_step_external_guide.md b/aviary/docs/user_guide/step_by_step_external_guide.md index 9ebc812d2..7556873d0 100644 --- a/aviary/docs/user_guide/step_by_step_external_guide.md +++ b/aviary/docs/user_guide/step_by_step_external_guide.md @@ -27,13 +27,13 @@ If your subsystem does not need to tell Aviary about any new variables (i.e. all -1. First, view the [battery_variables.py file](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/battery/battery_variables.py) to see an example of the variable hierarchy we use in Aviary. You can also look at the [core Aviary variables](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/variable_info/variables.py) to see what's available already. You can add any arbitrary system as a subclass within here. Items that define aircraft properties should go within `Aircraft` whereas any variables pertaining to the `Mission` should exist there. +1. First, view the [battery_variables.py file](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/battery/battery_variables.py) to see an example of the variable hierarchy we use in Aviary. You can also look at the [core Aviary variables](https://github.com/OpenMDAO/Aviary/blob/main/aviary/variable_info/variables.py) to see what's available already. You can add any arbitrary system as a subclass within here. Items that define aircraft properties should go within `Aircraft` whereas any variables pertaining to the `Mission` should exist there. These variable names are what's used within the OpenMDAO system that Aviary uses. This sort of hierarchy is a purposeful design choice to make it clear what values live where, which subsystem they belong to, and that they are named the same between different systems. Additionally, we recommend including the legacy name of any variable you add that so that users of your builder will know how the variable names map to existing analyses. -2. Now, with the variable names defined, we need to define variable metadata. Variable metadata helps Aviary understand your system. It also helps humans understand what units, defaults, and other values your variables use. Check out the [battery metadata example](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/battery/battery_variable_meta_data.py) as well as the [core Aviary metadata](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/variable_info/variable_meta_data.py). +2. Now, with the variable names defined, we need to define variable metadata. Variable metadata helps Aviary understand your system. It also helps humans understand what units, defaults, and other values your variables use. Check out the [battery metadata example](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/battery/battery_variable_meta_data.py) as well as the [core Aviary metadata](https://github.com/OpenMDAO/Aviary/blob/main/aviary/variable_info/variable_meta_data.py). When you define your variable metadata, you'll use the same names you just defined. With those names, you'll provide units, a brief description, and default values. You're not locking yourself into specific units here, but by providing units then Aviary can convert the values behind-the-scenes to whatever units are actually used in the code. Users can input variables in any units that can be converted to those units prescribed in the metadata. @@ -67,7 +67,7 @@ The methods you should implement depend on what type of subsystem you're buildin ## Testing your builder and ensuring it's behaving as intended -Okay, now we should test your subsystem builder to make sure it's providing the correct outputs to Aviary. You don't have to put it into Aviary (yet!) to do this. Look at the [`test_battery_builder.py` file](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/battery/test_battery_builder.py) to see how to test your subsystem. This is also detailed in the [battery subsystem example](battery_subsystem_example) doc page. +Okay, now we should test your subsystem builder to make sure it's providing the correct outputs to Aviary. You don't have to put it into Aviary (yet!) to do this. Look at the [`test_battery_builder.py` file](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/battery/test_battery_builder.py) to see how to test your subsystem. This is also detailed in the [battery subsystem example](battery_subsystem_example) doc page. If everything goes well then those tests passed. If they didn't, you should get some info about your builder that you can use to fix any bugs or errors. @@ -75,7 +75,7 @@ This test probably won't catch *everything* that could go wrong when interfacing ## Using your builder within Aviary -Awesome. Let's keep going and start to discuss using these subsystems within Aviary. The overarching idea is that now that you have a subsystem builder, you can pass an instantiation of this builder object to Aviary via the `phase_info` dictionary. Take a look at [`run_cruise.py`](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/battery/run_cruise.py) and [`run_multiphase_mission.py`](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/battery/run_multiphase_mission.py) to see how we do this for the battery model. Specifically, here are the relevant lines of code from the `run_cruise.py` file: +Awesome. Let's keep going and start to discuss using these subsystems within Aviary. The overarching idea is that now that you have a subsystem builder, you can pass an instantiation of this builder object to Aviary via the `phase_info` dictionary. Take a look at [`run_cruise.py`](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/battery/run_cruise.py) and [`run_multiphase_mission.py`](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/battery/run_multiphase_mission.py) to see how we do this for the battery model. Specifically, here are the relevant lines of code from the `run_cruise.py` file: ```python battery_builder = BatteryBuilder() @@ -101,7 +101,7 @@ Those additional `'external_subsystems'` entires are where you add your subsyste 2. **Add any `external_subsystems` to your pre- and post-mission phases too.** If you have pre- or post-mission analyses in your subsystem, make sure to add the `external_subsystems` list to the `pre_mission` and `post_mission` sub-dicts within the `phase_info` dict. This means that Aviary will build and use those systems before or after the mission; otherwise Aviary won't know to put the systems there. -3. **Start with a simple mission in Aviary.** To begin, try adapting the [`run_cruise.py`](https://github.com/OpenMDAO/om-Aviary/blob/main/aviary/external_subsystems/battery/run_cruise.py) script to use your subsystem. Replace the `BatteryBuilder()` instance with your subsystem, for example. You might need other setup or inputs based on the complexity of your model. But in general, it helps to start with the simplest mission you can. In this case, that's probably a steady level cruise flight. +3. **Start with a simple mission in Aviary.** To begin, try adapting the [`run_cruise.py`](https://github.com/OpenMDAO/Aviary/blob/main/aviary/external_subsystems/battery/run_cruise.py) script to use your subsystem. Replace the `BatteryBuilder()` instance with your subsystem, for example. You might need other setup or inputs based on the complexity of your model. But in general, it helps to start with the simplest mission you can. In this case, that's probably a steady level cruise flight. Even though this is a "simple" mission, there's still a lot that can go wrong. It turns out that designing an aircraft is challenging sometimes. I don't expect your mission optimization to converge well your first try; it often takes some debugging and digging to get your subsystem integrated well. It's really challenging to write docs to help you do this without knowing more about your system, what it's trying to do while the aircraft is flying, and what you've already checked. That being said, make sure to reach out if you're encountering problems here. diff --git a/aviary/docs/user_guide/subsystems.md b/aviary/docs/user_guide/subsystems.md index 0b19295ed..d5441dbac 100644 --- a/aviary/docs/user_guide/subsystems.md +++ b/aviary/docs/user_guide/subsystems.md @@ -12,7 +12,7 @@ In the following outline, the methods listed at the top-level are defined in `me Any sub-listed method is one that you can provide with your subsystem builder, showing where within the level 3 method hierarchy that subsystem method gets used. - `load_inputs` - loads the aviary_values inputs and options that the user specifies. -- `check_inputs` - checks the user-supplied input values for any potential problems. +- `check_and_preprocess_inputs` - checks the user-supplied input values for any potential problems. - `preprocess_inputs` - `add_pre_mission_systems` - adds pre-mission Systems to the Aviary problem - `get_mass_names` diff --git a/aviary/examples/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py b/aviary/examples/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py index 3e71d3f58..ca7785a0e 100644 --- a/aviary/examples/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py +++ b/aviary/examples/external_subsystems/OAS_weight/OAS_wing_weight_analysis.py @@ -1,9 +1,9 @@ ''' OpenMDAO component for aerostructural analysis using OpenAeroStruct. -This analysis is based on the bench_4.csv input data representing a +This analysis is based on the aircraft_for_bench_FwFm.csv input data representing a single-aisle commercial transport aircraft. The user_mesh method is currently -hard-coded with values taken from the bench_4 data, but should be coded +hard-coded with values taken from the aircraft_for_bench_FwFm data, but should be coded to use the Aircraft.Wing.* variables for a more general capability. The OAStructures class performs a structural analysis of the given wing @@ -551,7 +551,7 @@ def compute(self, inputs, outputs): prob.setup() # test data taken from the OpenAeroStruct example aeroelastic wingbox example - # and the bench_4.csv benchmark data file. All length units are in meters + # and the aircraft_for_bench_FwFm.csv benchmark data file. All length units are in meters # and all mass units are in kilograms for this test data. prob['OAS.box_upper_x'] = np.array([0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0.5, 0.51, 0.52, 0.53, 0.54, 0.55, 0.56, 0.57, 0.58, 0.59, 0.6]) diff --git a/aviary/examples/external_subsystems/OAS_weight/run_simple_OAS_mission.py b/aviary/examples/external_subsystems/OAS_weight/run_simple_OAS_mission.py index 9ff7cc2a6..34bd5d8a6 100644 --- a/aviary/examples/external_subsystems/OAS_weight/run_simple_OAS_mission.py +++ b/aviary/examples/external_subsystems/OAS_weight/run_simple_OAS_mission.py @@ -33,7 +33,7 @@ 'polynomial_control_order': 1, 'num_segments': 5, 'order': 3, - 'solve_for_range': False, + 'solve_for_distance': False, 'initial_mach': (0.2, 'unitless'), 'final_mach': (0.72, 'unitless'), 'mach_bounds': ((0.18, 0.74), 'unitless'), @@ -57,7 +57,7 @@ 'polynomial_control_order': 1, 'num_segments': 5, 'order': 3, - 'solve_for_range': False, + 'solve_for_distance': False, 'initial_mach': (0.72, 'unitless'), 'final_mach': (0.72, 'unitless'), 'mach_bounds': ((0.7, 0.74), 'unitless'), @@ -81,7 +81,7 @@ 'polynomial_control_order': 1, 'num_segments': 5, 'order': 3, - 'solve_for_range': False, + 'solve_for_distance': False, 'initial_mach': (0.72, 'unitless'), 'final_mach': (0.36, 'unitless'), 'mach_bounds': ((0.34, 0.74), 'unitless'), @@ -108,29 +108,20 @@ if use_OAS: phase_info['pre_mission']['external_subsystems'] = [wing_weight_builder] -aircraft_definition_file = 'models/test_aircraft/aircraft_for_bench_FwFm_simple.csv' +aircraft_definition_file = 'models/test_aircraft/aircraft_for_bench_FwFm.csv' make_plots = False -max_iter = 100 -optimizer = 'SNOPT' - +max_iter = 1 # set this to a higher number to fully run the optimization +optimizer = 'SLSQP' prob = av.AviaryProblem() prob.load_inputs(aircraft_definition_file, phase_info) -prob.check_inputs() +prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() prob.add_phases() prob.add_post_mission_systems() prob.link_phases() - -driver = prob.driver = om.pyOptSparseDriver() -driver.options["optimizer"] = optimizer -driver.declare_coloring() -driver.opt_settings["Major iterations limit"] = max_iter -driver.opt_settings["Major optimality tolerance"] = 1e-4 -driver.opt_settings["Major feasibility tolerance"] = 1e-5 -driver.opt_settings["iSumm"] = 6 - +prob.add_driver(optimizer=optimizer, max_iter=max_iter) prob.add_design_variables() prob.add_objective() prob.setup() diff --git a/aviary/examples/external_subsystems/battery/model/reg_thevenin_interp_group.py b/aviary/examples/external_subsystems/battery/model/reg_thevenin_interp_group.py index d9096214f..ed271a289 100644 --- a/aviary/examples/external_subsystems/battery/model/reg_thevenin_interp_group.py +++ b/aviary/examples/external_subsystems/battery/model/reg_thevenin_interp_group.py @@ -2,7 +2,16 @@ from openmdao.api import Group, MetaModelStructuredComp -from boring.src.model.maps.s18650_battery import battery + +# These battery values are entirely fabricated and not representative of any real battery. +# This model is used as an example of how to integrate external subsystems in Aviary. +T_bp = np.array([0., 60.]) +SOC_bp = np.linspace(0., 1., 32) + +tU_oc = np.vstack((np.linspace(2.8, 4.2, 32), np.linspace(2.7, 4.1, 32))) +tC_Th = np.ones((2, 32)) * 1000.0 +tR_Th = np.vstack((np.linspace(0.06, 0.03, 32), np.linspace(0.04, 0.02, 32))) +tR_0 = np.vstack((np.linspace(0.06, 0.03, 32), np.linspace(0.04, 0.02, 32))) class RegTheveninInterpGroup(Group): @@ -16,13 +25,13 @@ def setup(self): thevenin_interp_comp = MetaModelStructuredComp(method='slinear', vec_size=n, training_data_gradients=False, extrapolate=True) - thevenin_interp_comp.add_input('T_batt', 1.0, battery.T_bp, units='degC') - thevenin_interp_comp.add_input('SOC', 1.0, battery.SOC_bp, units=None) - thevenin_interp_comp.add_output('C_Th', 1.0, battery.tC_Th, units='F') + thevenin_interp_comp.add_input('T_batt', 1.0, T_bp, units='degC') + thevenin_interp_comp.add_input('SOC', 1.0, SOC_bp, units=None) + thevenin_interp_comp.add_output('C_Th', 1.0, tC_Th, units='F') # add R_th and R_0 to get total R - thevenin_interp_comp.add_output('R_Th', 1.0, battery.tR_Th, units='ohm') - thevenin_interp_comp.add_output('R_0', 1.0, battery.tR_0, units='ohm') - thevenin_interp_comp.add_output('U_oc', 1.0, battery.tU_oc, units='V') + thevenin_interp_comp.add_output('R_Th', 1.0, tR_Th, units='ohm') + thevenin_interp_comp.add_output('R_0', 1.0, tR_0, units='ohm') + thevenin_interp_comp.add_output('U_oc', 1.0, tU_oc, units='V') self.add_subsystem(name='interp_comp', subsys=thevenin_interp_comp, diff --git a/aviary/examples/external_subsystems/battery/run_cruise.py b/aviary/examples/external_subsystems/battery/run_cruise.py index 528493120..a0463c89d 100644 --- a/aviary/examples/external_subsystems/battery/run_cruise.py +++ b/aviary/examples/external_subsystems/battery/run_cruise.py @@ -7,7 +7,7 @@ battery_builder = BatteryBuilder() -phase_info = deepcopy(av.default_simple_phase_info) +phase_info = deepcopy(av.default_height_energy_phase_info) # Here we just add the simple weight system to only the pre-mission phase_info['pre_mission']['external_subsystems'] = [battery_builder] phase_info['climb']['external_subsystems'] = [battery_builder] diff --git a/aviary/examples/external_subsystems/battery/run_multiphase_mission.py b/aviary/examples/external_subsystems/battery/run_multiphase_mission.py index 1cae165e9..975dc7ef5 100644 --- a/aviary/examples/external_subsystems/battery/run_multiphase_mission.py +++ b/aviary/examples/external_subsystems/battery/run_multiphase_mission.py @@ -24,9 +24,9 @@ # Allow for user overrides here prob.load_inputs(input_file, phase_info) -# Have checks for clashing user inputs -# Raise warnings or errors depending on how clashing the issues are -prob.check_inputs() + +# Preprocess inputs +prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() @@ -37,7 +37,7 @@ # Link phases and variables prob.link_phases() -prob.add_driver("SNOPT") +prob.add_driver("SLSQP") prob.add_design_variables() diff --git a/aviary/examples/external_subsystems/simple_weight/run_simple_weight.py b/aviary/examples/external_subsystems/simple_weight/run_simple_weight.py index 225b89b05..f5b7769e2 100644 --- a/aviary/examples/external_subsystems/simple_weight/run_simple_weight.py +++ b/aviary/examples/external_subsystems/simple_weight/run_simple_weight.py @@ -8,7 +8,7 @@ from aviary.examples.external_subsystems.simple_weight.simple_weight_builder import WingWeightBuilder -phase_info = deepcopy(av.default_simple_phase_info) +phase_info = deepcopy(av.default_height_energy_phase_info) # Here we just add the simple weight system to only the pre-mission phase_info['pre_mission']['external_subsystems'] = [WingWeightBuilder()] @@ -16,7 +16,7 @@ # Load aircraft and options data from user # Allow for user overrides here -prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm_simple.csv', phase_info) +prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm.csv', phase_info) prob.add_pre_mission_systems() diff --git a/aviary/examples/level2_shooting_traj.py b/aviary/examples/level2_shooting_traj.py index 983f42d6f..b02eba8ee 100644 --- a/aviary/examples/level2_shooting_traj.py +++ b/aviary/examples/level2_shooting_traj.py @@ -36,9 +36,9 @@ def run_aviary(aircraft_filename, phase_info, optimizer=None, analysis_scheme=An # Allow for user overrides here prob.load_inputs(aircraft_filename, phase_info) - # Have checks for clashing user inputs - # Raise warnings or errors depending on how clashing the issues are - prob.check_inputs() + +# Preprocess inputs + prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() @@ -58,8 +58,6 @@ def run_aviary(aircraft_filename, phase_info, optimizer=None, analysis_scheme=An alpha_mode=AlphaModes.REQUIRED_LIFT, simupy_args=dict( DEBUG=True, - blocked_state_names=['engine.nox', 'nox', - 'TAS', Dynamic.Mission.FLIGHT_PATH_ANGLE], ), ) cruise_vals = { @@ -74,7 +72,6 @@ def run_aviary(aircraft_filename, phase_info, optimizer=None, analysis_scheme=An ode_args=prob.ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine.nox', 'nox'], ), ) descent1_vals = { diff --git a/aviary/interface/default_phase_info/simple.py b/aviary/examples/run_aviary_example.py similarity index 67% rename from aviary/interface/default_phase_info/simple.py rename to aviary/examples/run_aviary_example.py index 0efd931c3..c0ded9597 100644 --- a/aviary/interface/default_phase_info/simple.py +++ b/aviary/examples/run_aviary_example.py @@ -1,19 +1,14 @@ -from aviary.subsystems.propulsion.propulsion_builder import CorePropulsionBuilder -from aviary.subsystems.geometry.geometry_builder import CoreGeometryBuilder -from aviary.subsystems.mass.mass_builder import CoreMassBuilder -from aviary.subsystems.aerodynamics.aerodynamics_builder import CoreAerodynamicsBuilder -from aviary.variable_info.variable_meta_data import _MetaData as BaseMetaData -from aviary.variable_info.enums import LegacyCode +""" +This is a slightly more complex Aviary example of running a coupled aircraft design-mission optimization. +It runs the same mission as the `run_basic_aviary_example.py` script, but it uses the AviaryProblem class to set up the problem. +This exposes more options and flexibility to the user and uses the "Level 2" API within Aviary. -FLOPS = LegacyCode.FLOPS - -prop = CorePropulsionBuilder('core_propulsion', BaseMetaData) -mass = CoreMassBuilder('core_mass', BaseMetaData, FLOPS) -aero = CoreAerodynamicsBuilder('core_aerodynamics', BaseMetaData, FLOPS) -geom = CoreGeometryBuilder('core_geometry', BaseMetaData, FLOPS) - -default_premission_subsystems = [prop, geom, mass, aero] -default_mission_subsystems = [aero, prop] +We define a `phase_info` object, which tells Aviary how to model the mission. +Here we have climb, cruise, and descent phases. +We then call the correct methods in order to set up and run an Aviary optimization problem. +This performs a coupled design-mission optimization and outputs the results from Aviary into the `reports` folder. +""" +import aviary.api as av phase_info = { @@ -26,7 +21,7 @@ "polynomial_control_order": 1, "num_segments": 5, "order": 3, - "solve_for_range": False, + "solve_for_distance": False, "initial_mach": (0.2, "unitless"), "final_mach": (0.72, "unitless"), "mach_bounds": ((0.18, 0.74), "unitless"), @@ -50,7 +45,7 @@ "polynomial_control_order": 1, "num_segments": 5, "order": 3, - "solve_for_range": False, + "solve_for_distance": False, "initial_mach": (0.72, "unitless"), "final_mach": (0.72, "unitless"), "mach_bounds": ((0.7, 0.74), "unitless"), @@ -74,7 +69,7 @@ "polynomial_control_order": 1, "num_segments": 5, "order": 3, - "solve_for_range": False, + "solve_for_distance": False, "initial_mach": (0.72, "unitless"), "final_mach": (0.36, "unitless"), "mach_bounds": ((0.34, 0.74), "unitless"), @@ -96,3 +91,37 @@ "target_range": (1906, "nmi"), }, } + + +prob = av.AviaryProblem() + +# Load aircraft and options data from user +# Allow for user overrides here +prob.load_inputs('models/test_aircraft/aircraft_for_bench_FwFm.csv', phase_info) + + +# Preprocess inputs +prob.check_and_preprocess_inputs() + +prob.add_pre_mission_systems() + +prob.add_phases() + +prob.add_post_mission_systems() + +# Link phases and variables +prob.link_phases() + +prob.add_driver("SLSQP", max_iter=100) + +prob.add_design_variables() + +# Load optimization problem formulation +# Detail which variables the optimizer can control +prob.add_objective() + +prob.setup() + +prob.set_initial_guesses() + +prob.run_aviary_problem() diff --git a/aviary/examples/run_basic_aviary_example.py b/aviary/examples/run_basic_aviary_example.py new file mode 100644 index 000000000..a1fe4abd7 --- /dev/null +++ b/aviary/examples/run_basic_aviary_example.py @@ -0,0 +1,15 @@ +""" +This is a straightforward and basic example of running a coupled aircraft design-mission optimization in Aviary. +This uses the "level 1" API within Aviary. + +We use the pre-defined single aisle commercial transport aircraft definition and use a pre-defined phase_info object +to describe the mission optimization problem to Aviary. +This mission consists of climb, cruise, and descent phases. +We then call the `run_aviary` function, which takes in the path to the aircraft model, the phase info, and some other options. +This performs a coupled design-mission optimization and outputs the results from Aviary into the `reports` folder. +""" +import aviary.api as av + + +prob = av.run_aviary('models/test_aircraft/aircraft_for_bench_FwFm.csv', av.default_height_energy_phase_info, + optimizer="SLSQP", make_plots=True) diff --git a/aviary/examples/test/test_all_examples.py b/aviary/examples/test/test_all_examples.py index 387654108..a405e0827 100644 --- a/aviary/examples/test/test_all_examples.py +++ b/aviary/examples/test/test_all_examples.py @@ -45,8 +45,9 @@ def setUpClass(cls): This method is called once before starting the tests and is used to populate the 'run_files' attribute with a list of run scripts. """ - cls.base_directory = "../external_subsystems" # Adjust the path as necessary - cls.run_files = cls.find_run_files(cls.base_directory) + base_directory = os.path.join(os.path.dirname( + os.path.dirname(os.path.abspath(__file__))), ".") + cls.run_files = cls.find_run_files(base_directory) @staticmethod def find_run_files(base_dir): @@ -84,15 +85,17 @@ def run_script(self, script_path): Exception Any exception other than ImportError or TimeoutExpired that occurs while running the script. """ - try: - subprocess.check_call(['python', script_path], timeout=10) - except subprocess.TimeoutExpired: - pass # Test passes if it runs longer than 10 seconds - except subprocess.CalledProcessError as e: - if 'ImportError' in str(e): + proc = subprocess.Popen(['python', script_path], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc.wait() + (stdout, stderr) = proc.communicate() + + if proc.returncode != 0: + if 'ImportError' in str(stderr): self.skipTest(f"Skipped {script_path.name} due to ImportError") else: - raise + raise Exception( + f"Error running {script_path.name}:\n{stderr.decode('utf-8')}") def test_run_scripts(self): """ diff --git a/aviary/interface/cmd_entry_points.py b/aviary/interface/cmd_entry_points.py index 56dff8119..3652a4d60 100644 --- a/aviary/interface/cmd_entry_points.py +++ b/aviary/interface/cmd_entry_points.py @@ -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): @@ -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"), } diff --git a/aviary/interface/default_phase_info/height_energy.py b/aviary/interface/default_phase_info/height_energy.py index 66d584192..a51c812bc 100644 --- a/aviary/interface/default_phase_info/height_energy.py +++ b/aviary/interface/default_phase_info/height_energy.py @@ -8,7 +8,6 @@ FLOPS = LegacyCode.FLOPS - prop = CorePropulsionBuilder('core_propulsion', BaseMetaData) mass = CoreMassBuilder('core_mass', BaseMetaData, FLOPS) aero = CoreAerodynamicsBuilder('core_aerodynamics', BaseMetaData, FLOPS) @@ -17,115 +16,90 @@ default_premission_subsystems = [prop, geom, mass, aero] default_mission_subsystems = [aero, prop] + phase_info = { - 'pre_mission': { - 'include_takeoff': True, - 'optimize_mass': True, - }, - 'climb': { - 'subsystem_options': { - 'core_aerodynamics': {'method': 'computed'} - }, - 'user_options': { - 'fix_initial': {Dynamic.Mission.MASS: False, Dynamic.Mission.RANGE: False}, - 'fix_initial_time': True, - 'fix_duration': False, - 'num_segments': 6, - 'order': 3, - 'initial_altitude': (0., 'ft'), - 'initial_ref': (1., 's'), - 'initial_bounds': ((0., 500.), 's'), - 'duration_ref': (1452., 's'), - 'duration_bounds': ((726., 2904.), 's'), - 'final_altitude': (10668, 'm'), - 'input_initial': True, - 'no_descent': False, - 'initial_mach': 0.1, - 'final_mach': 0.79, - 'fix_range': False, - 'add_initial_mass_constraint': False, + "pre_mission": {"include_takeoff": False, "optimize_mass": True}, + "climb": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + "optimize_mach": False, + "optimize_altitude": False, + "num_segments": 5, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.2, "unitless"), + "final_mach": (0.72, "unitless"), + "mach_bounds": ((0.18, 0.74), "unitless"), + "initial_altitude": (0.0, "ft"), + "final_altitude": (32000.0, "ft"), + "altitude_bounds": ((0.0, 34000.0), "ft"), + "throttle_enforcement": "path_constraint", + "fix_initial": True, + "constrain_final": False, + "fix_duration": False, + "initial_bounds": ((0.0, 0.0), "min"), + "duration_bounds": ((64.0, 192.0), "min"), + "add_initial_mass_constraint": False, }, - 'initial_guesses': { - 'times': ([2., 24.2], 'min'), - 'altitude': ([0., 35.e3], 'ft'), - 'velocity': ([220., 455.49], 'kn'), - 'mass': ([170.e3, 165.e3], 'lbm'), - 'range': ([0., 160.3], 'nmi'), - 'velocity_rate': ([0.25, 0.05], 'm/s**2'), - 'throttle': ([0.5, 0.5], 'unitless'), - } + "initial_guesses": {"times": ([0, 128], "min")}, }, - 'cruise': { - 'subsystem_options': { - 'core_aerodynamics': {'method': 'computed'} + "cruise": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + "optimize_mach": False, + "optimize_altitude": False, + "num_segments": 5, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.72, "unitless"), + "final_mach": (0.72, "unitless"), + "mach_bounds": ((0.7, 0.74), "unitless"), + "initial_altitude": (32000.0, "ft"), + "final_altitude": (34000.0, "ft"), + "altitude_bounds": ((23000.0, 38000.0), "ft"), + "throttle_enforcement": "boundary_constraint", + "fix_initial": False, + "constrain_final": False, + "fix_duration": False, + "initial_bounds": ((64.0, 192.0), "min"), + "duration_bounds": ((56.5, 169.5), "min"), }, - 'user_options': { - 'fix_initial': False, - 'fix_final': False, - 'fix_duration': False, - 'num_segments': 1, - 'order': 3, - 'initial_ref': (1., 's'), - 'initial_bounds': ((500., 4000.), 's'), - 'duration_ref': (24370.8, 's'), - 'duration_bounds': ((726., 48741.6), 's'), - 'min_altitude': (10.668e3, 'm'), - 'max_altitude': (10.668e3, 'm'), - 'min_mach': 0.79, - 'max_mach': 0.79, - 'required_available_climb_rate': (1.524, 'm/s'), - 'mass_f_cruise': (1.e4, 'kg'), - 'range_f_cruise': (1.e6, 'm'), - }, - 'initial_guesses': { - 'times': ([26.2, 406.18], 'min'), - 'altitude': ([35.e3, 35.e3], 'ft'), - 'velocity': ([455.49, 455.49], 'kn'), - 'mass': ([165.e3, 140.e3], 'lbm'), - 'range': ([160.3, 3243.9], 'nmi'), - 'velocity_rate': ([0., 0.], 'm/s**2'), - 'throttle': ([0.95, 0.9], 'unitless'), - } + "initial_guesses": {"times": ([128, 113], "min")}, }, - 'descent': { - 'subsystem_options': { - 'core_aerodynamics': {'method': 'computed'} - }, - 'user_options': { - 'fix_initial': False, - 'fix_range': True, - 'fix_duration': False, - 'num_segments': 5, - 'order': 3, - 'initial_ref': (1., 's'), - 'initial_bounds': ((10.e3, 30.e3), 's'), - 'duration_ref': (1754.4, 's'), - 'duration_bounds': ((726., 3508.8), 's'), - 'initial_altitude': (10.668e3, 'm'), - 'final_altitude': (10.668, 'm'), - 'initial_mach': 0.79, - 'final_mach': 0.3, - 'no_climb': False, + "descent": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + "optimize_mach": False, + "optimize_altitude": False, + "num_segments": 5, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.72, "unitless"), + "final_mach": (0.36, "unitless"), + "mach_bounds": ((0.34, 0.74), "unitless"), + "initial_altitude": (34000.0, "ft"), + "final_altitude": (500.0, "ft"), + "altitude_bounds": ((0.0, 38000.0), "ft"), + "throttle_enforcement": "path_constraint", + "fix_initial": False, + "constrain_final": True, + "fix_duration": False, + "initial_bounds": ((120.5, 361.5), "min"), + "duration_bounds": ((29.0, 87.0), "min"), }, - 'initial_guesses': { - 'times': ([432.38, 29.24], 'min'), - 'altitude': ([35.e3, 35.], 'ft'), - 'velocity': ([455.49, 198.44], 'kn'), - 'mass': ([120.e3, 115.e3], 'lbm'), - 'range': ([3243.9, 3378.7], 'nmi'), - 'velocity_rate': ([-0.25, 0.0], 'm/s**2'), - 'throttle': ([0., 0.], 'unitless'), - } + "initial_guesses": {"times": ([241, 58], "min")}, }, - 'post_mission': { - 'include_landing': True, + "post_mission": { + "include_landing": False, + "constrain_range": True, + "target_range": (1906., "nmi"), }, } -def phase_info_parameterization(phase_info, aviary_inputs): +def phase_info_parameterization(phase_info, post_mission_info, aviary_inputs): """ - Modify the values in the phase_info dictionary to accomodate different values + Modify the values in the phase_info dictionary to accommodate different values for the following mission design inputs: cruise altitude, cruise Mach number, cruise range, design gross mass. @@ -143,62 +117,30 @@ def phase_info_parameterization(phase_info, aviary_inputs): parameters """ - range_cruise = aviary_inputs.get_val(Mission.Design.RANGE, units='NM') alt_cruise = aviary_inputs.get_val(Mission.Design.CRUISE_ALTITUDE, units='ft') - gross_mass = aviary_inputs.get_val(Mission.Design.GROSS_MASS, units='lbm') mach_cruise = aviary_inputs.get_val(Mission.Summary.CRUISE_MACH) # Range - range_scale = 1.0 - old_range_cruise = 3500.0 + old_range_cruise, range_units = post_mission_info['target_range'] + range_cruise = aviary_inputs.get_val(Mission.Design.RANGE, units=range_units) if range_cruise != old_range_cruise: - range_scale = range_cruise / old_range_cruise - - vals = phase_info['descent']['initial_guesses']['range'][0] - new_vals = [vals[0] * range_scale, vals[1] * range_scale] - phase_info['descent']['initial_guesses']['range'] = (new_vals, 'NM') - - vals = phase_info['cruise']['initial_guesses']['range'][0] - new_val = vals[1] * range_scale - phase_info['cruise']['initial_guesses']['range'] = ([vals[0], new_val], 'NM') + new_val = post_mission_info['target_range'][0] * range_cruise / old_range_cruise + post_mission_info['target_range'] = (new_val, range_units) # Altitude - - # This doesn't seem to be stored regularly in some of the files. - old_alt_cruise = 35000 + old_alt_cruise = 32000. if alt_cruise != old_alt_cruise: - - phase_info['climb']['initial_guesses']['altitude'] = ([0.0, alt_cruise], 'ft') phase_info['climb']['user_options']['final_altitude'] = (alt_cruise, 'ft') - phase_info['cruise']['initial_guesses']['altitude'] = \ - ([alt_cruise, alt_cruise], 'ft') - phase_info['cruise']['user_options']['min_altitude'] = (alt_cruise, 'ft') - phase_info['cruise']['user_options']['max_altitude'] = (alt_cruise, 'ft') - phase_info['descent']['initial_guesses']['altitude'] = ([alt_cruise, 0.0], 'ft') - - # TODO - Could adjust time guesses/bounds in climb2 and desc2. - - # Mass - old_gross_mass = 175400.0 - if gross_mass != old_gross_mass: - - # Note, this requires that the guess for gross mass is pretty close to the - # compute mass. - - mass_scale = gross_mass / old_gross_mass - - for phase in ['climb', 'cruise', 'descent']: - vals = phase_info[phase]['initial_guesses']['mass'][0] - new_vals = [vals[0] * mass_scale, vals[1] * mass_scale] - phase_info[phase]['initial_guesses']['mass'] = (new_vals, 'lbm') + phase_info['cruise']['user_options']['initial_altitude'] = (alt_cruise, 'ft') + phase_info['cruise']['user_options']['final_altitude'] = (alt_cruise, 'ft') + phase_info['descent']['user_options']['initial_altitude'] = (alt_cruise, 'ft') # Mach - old_mach_cruise = 0.79 + old_mach_cruise = 0.72 if mach_cruise != old_mach_cruise: + phase_info['climb']['user_options']['final_mach'] = (mach_cruise, "unitless") + phase_info['cruise']['user_options']['initial_mach'] = (mach_cruise, "unitless") + phase_info['cruise']['user_options']['final_mach'] = (mach_cruise, "unitless") + phase_info['descent']['user_options']['initial_mach'] = (mach_cruise, "unitless") - phase_info['climb']['user_options']['final_mach'] = mach_cruise - phase_info['cruise']['user_options']['min_mach'] = mach_cruise - phase_info['cruise']['user_options']['max_mach'] = mach_cruise - phase_info['descent']['user_options']['initial_mach'] = mach_cruise - - return phase_info + return phase_info, post_mission_info diff --git a/aviary/interface/default_phase_info/two_dof.py b/aviary/interface/default_phase_info/two_dof.py index a34a5098d..6c1050d51 100644 --- a/aviary/interface/default_phase_info/two_dof.py +++ b/aviary/interface/default_phase_info/two_dof.py @@ -1,5 +1,3 @@ -import numpy as np - from aviary.variable_info.enums import SpeedType from aviary.subsystems.propulsion.propulsion_builder import CorePropulsionBuilder from aviary.subsystems.geometry.geometry_builder import CoreGeometryBuilder @@ -11,11 +9,6 @@ GASP = LegacyCode.GASP -throttle_max = 1.0 -throttle_climb = 0.956 -throttle_cruise = 0.930 -throttle_idle = 0.0 - prop = CorePropulsionBuilder('core_propulsion', BaseMetaData) mass = CoreMassBuilder('core_mass', BaseMetaData, GASP) aero = CoreAerodynamicsBuilder('core_aerodynamics', BaseMetaData, GASP) @@ -28,25 +21,26 @@ phase_info = { 'groundroll': { - 'num_segments': 1, - 'order': 3, - 'connect_initial_mass': False, - 'fix_initial': True, - 'fix_initial_mass': False, - 'duration_ref': (50., 's'), - 'duration_bounds': ((1., 100.), 's'), - 'TAS_lower': (0, 'kn'), - 'TAS_upper': (1000, 'kn'), - 'TAS_ref': (150, 'kn'), - 'mass_lower': (0, 'lbm'), - 'mass_upper': (None, 'lbm'), - 'mass_ref': (150_000, 'lbm'), - 'mass_defect_ref': (150_000, 'lbm'), - 'distance_lower': (0, 'ft'), - 'distance_upper': (10.e3, 'ft'), - 'distance_ref': (3000, 'ft'), - 'distance_defect_ref': (3000, 'ft'), - 'throttle_setting': throttle_max, + 'user_options': { + 'num_segments': 1, + 'order': 3, + 'connect_initial_mass': False, + 'fix_initial': True, + 'fix_initial_mass': False, + 'duration_ref': (50., 's'), + 'duration_bounds': ((1., 100.), 's'), + 'TAS_lower': (0, 'kn'), + 'TAS_upper': (1000, 'kn'), + 'TAS_ref': (150, 'kn'), + 'mass_lower': (0, 'lbm'), + 'mass_upper': (None, 'lbm'), + 'mass_ref': (150_000, 'lbm'), + 'mass_defect_ref': (150_000, 'lbm'), + 'distance_lower': (0, 'ft'), + 'distance_upper': (10.e3, 'ft'), + 'distance_ref': (3000, 'ft'), + 'distance_defect_ref': (3000, 'ft'), + }, 'initial_guesses': { 'times': ([0.0, 40.0], 's'), 'TAS': ([0.066, 143.1], 'kn'), @@ -55,29 +49,30 @@ } }, 'rotation': { - 'num_segments': 1, - 'order': 3, - 'fix_initial': False, - 'duration_bounds': ((1, 100), 's'), - 'duration_ref': (50.0, 's'), - 'TAS_lower': (0, 'kn'), - 'TAS_upper': (1000, 'kn'), - 'TAS_ref': (100, 'kn'), - 'TAS_ref0': (0, 'kn'), - 'mass_lower': (0, 'lbm'), - 'mass_upper': (None, 'lbm'), - 'mass_ref': (150_000, 'lbm'), - 'mass_defect_ref': (150_000, 'lbm'), - 'distance_lower': (0, 'ft'), - 'distance_upper': (10_000, 'ft'), - 'distance_ref': (5000, 'ft'), - 'distance_defect_ref': (5000, 'ft'), - 'angle_lower': (0., 'deg'), - 'angle_upper': (5., 'deg'), - 'angle_ref': (5., 'deg'), - 'angle_defect_ref': (5., 'deg'), - 'normal_ref': (10000, 'lbf'), - 'throttle_setting': throttle_max, + 'user_options': { + 'num_segments': 1, + 'order': 3, + 'fix_initial': False, + 'duration_bounds': ((1, 100), 's'), + 'duration_ref': (50.0, 's'), + 'TAS_lower': (0, 'kn'), + 'TAS_upper': (1000, 'kn'), + 'TAS_ref': (100, 'kn'), + 'TAS_ref0': (0, 'kn'), + 'mass_lower': (0, 'lbm'), + 'mass_upper': (None, 'lbm'), + 'mass_ref': (150_000, 'lbm'), + 'mass_defect_ref': (150_000, 'lbm'), + 'distance_lower': (0, 'ft'), + 'distance_upper': (10_000, 'ft'), + 'distance_ref': (5000, 'ft'), + 'distance_defect_ref': (5000, 'ft'), + 'angle_lower': (0., 'rad'), + 'angle_upper': (5., 'rad'), + 'angle_ref': (5., 'rad'), + 'angle_defect_ref': (5., 'rad'), + 'normal_ref': (10000, 'lbf'), + }, 'initial_guesses': { 'times': ([40.0, 5.0], 's'), 'alpha': ([0.0, 2.5], 'deg'), @@ -87,36 +82,36 @@ } }, 'ascent': { - 'num_segments': 4, - 'order': 3, - 'fix_initial': False, - 'TAS_lower': (0, 'kn'), - 'TAS_upper': (700, 'kn'), - 'TAS_ref': (200, 'kn'), - 'TAS_ref0': (0, 'kn'), - 'mass_lower': (0, 'lbm'), - 'mass_upper': (None, 'lbm'), - 'mass_ref': (150_000, 'lbm'), - 'mass_defect_ref': (150_000, 'lbm'), - 'distance_lower': (0, 'ft'), - 'distance_upper': (15_000, 'ft'), - 'distance_ref': (1e4, 'ft'), - 'distance_defect_ref': (1e4, 'ft'), - 'alt_lower': (0, 'ft'), - 'alt_upper': (700, 'ft'), - 'alt_ref': (1000, 'ft'), - 'alt_defect_ref': (1000, 'ft'), - 'alt_constraint_eq': (500, 'ft'), - 'alt_constraint_ref': (500, 'ft'), - 'alt_constraint_ref0': (0, 'ft'), - 'angle_lower': (-10, 'deg'), - 'angle_upper': (20, 'deg'), - 'angle_ref': (1, 'deg'), - 'angle_defect_ref': (1.0, 'deg'), - 'pitch_constraint_lower': (0., 'deg'), - 'pitch_constraint_upper': (15., 'deg'), - 'pitch_constraint_ref': (1., 'deg'), - 'throttle_setting': throttle_max, + 'user_options': { + 'num_segments': 4, + 'order': 3, + 'fix_initial': False, + 'TAS_lower': (0, 'kn'), + 'TAS_upper': (700, 'kn'), + 'TAS_ref': (200, 'kn'), + 'TAS_ref0': (0, 'kn'), + 'mass_lower': (0, 'lbm'), + 'mass_upper': (None, 'lbm'), + 'mass_ref': (150_000, 'lbm'), + 'mass_defect_ref': (150_000, 'lbm'), + 'distance_lower': (0, 'ft'), + 'distance_upper': (15_000, 'ft'), + 'distance_ref': (1e4, 'ft'), + 'distance_defect_ref': (1e4, 'ft'), + 'alt_lower': (0, 'ft'), + 'alt_upper': (700, 'ft'), + 'alt_ref': (1000, 'ft'), + 'alt_defect_ref': (1000, 'ft'), + 'final_altitude': (500, 'ft'), + 'alt_constraint_ref': (500, 'ft'), + 'angle_lower': (-10, 'rad'), + 'angle_upper': (20, 'rad'), + 'angle_ref': (57.2958, 'deg'), + 'angle_defect_ref': (57.2958, 'deg'), + 'pitch_constraint_lower': (0., 'deg'), + 'pitch_constraint_upper': (15., 'deg'), + 'pitch_constraint_ref': (1., 'deg'), + }, 'initial_guesses': { 'times': ([45., 25.], 's'), 'flight_path_angle': ([0.0, 8.], 'deg'), @@ -124,33 +119,33 @@ 'TAS': ([150., 185.], 'kn'), 'distance': ([4.e3, 10.e3], 'ft'), 'altitude': ([0.0, 500.], 'ft'), - 'tau_gear': (0.2, None), - 'tau_flaps': (0.9, None), + 'tau_gear': (0.2, 'unitless'), + 'tau_flaps': (0.9, 'unitless'), 'throttle': ([0.956, 0.956], 'unitless'), } }, 'accel': { - 'num_segments': 1, - 'order': 3, - 'fix_initial': False, - 'alt': (500, 'ft'), - 'EAS_constraint_eq': (250, 'kn'), - 'time_initial_bounds': ((5, 1000), 's'), - 'duration_bounds': ((1, 200), 's'), - 'duration_ref': (1000, 's'), - 'TAS_lower': (150, 'kn'), - 'TAS_upper': (270, 'kn'), - 'TAS_ref': (250, 'kn'), - 'TAS_ref0': (150, 'kn'), - 'mass_lower': (0, 'lbm'), - 'mass_upper': (None, 'lbm'), - 'mass_ref': (150_000, 'lbm'), - 'mass_defect_ref': (150_000, 'lbm'), - 'distance_lower': (0, 'NM'), - 'distance_upper': (150, 'NM'), - 'distance_ref': (5, 'NM'), - 'distance_defect_ref': (5, 'NM'), - 'throttle_setting': throttle_max, + 'user_options': { + 'num_segments': 1, + 'order': 3, + 'fix_initial': False, + 'alt': (500, 'ft'), + 'EAS_constraint_eq': (250, 'kn'), + 'duration_bounds': ((1, 200), 's'), + 'duration_ref': (1000, 's'), + 'TAS_lower': (150, 'kn'), + 'TAS_upper': (270, 'kn'), + 'TAS_ref': (250, 'kn'), + 'TAS_ref0': (150, 'kn'), + 'mass_lower': (0, 'lbm'), + 'mass_upper': (None, 'lbm'), + 'mass_ref': (150_000, 'lbm'), + 'mass_defect_ref': (150_000, 'lbm'), + 'distance_lower': (0, 'NM'), + 'distance_upper': (150, 'NM'), + 'distance_ref': (5, 'NM'), + 'distance_defect_ref': (5, 'NM'), + }, 'initial_guesses': { 'times': ([70., 13.], 's'), 'TAS': ([185., 250.], 'kn'), @@ -159,28 +154,28 @@ } }, 'climb1': { - 'num_segments': 1, - 'order': 3, - 'fix_initial': False, - 'EAS_target': (250, 'kn'), - 'mach_cruise': 0.8, - 'target_mach': False, - 'final_alt': (10.e3, 'ft'), - 'time_initial_bounds': ((20, 200), 's'), - 'duration_bounds': ((30, 300), 's'), - 'duration_ref': (1000, 's'), - 'alt_lower': (400, 'ft'), - 'alt_upper': (11_000, 'ft'), - 'alt_ref': (10.e3, 'ft'), - 'mass_lower': (0, 'lbm'), - 'mass_upper': (None, 'lbm'), - 'mass_ref': (150_000, 'lbm'), - 'mass_defect_ref': (150_000, 'lbm'), - 'distance_lower': (0, 'NM'), - 'distance_upper': (500, 'NM'), - 'distance_ref': (10, 'NM'), - 'distance_ref0': (0, 'NM'), - 'throttle_setting': throttle_climb, + 'user_options': { + 'num_segments': 1, + 'order': 3, + 'fix_initial': False, + 'EAS_target': (250, 'kn'), + 'mach_cruise': 0.8, + 'target_mach': False, + 'final_altitude': (10.e3, 'ft'), + 'duration_bounds': ((30, 300), 's'), + 'duration_ref': (1000, 's'), + 'alt_lower': (400, 'ft'), + 'alt_upper': (11_000, 'ft'), + 'alt_ref': (10.e3, 'ft'), + 'mass_lower': (0, 'lbm'), + 'mass_upper': (None, 'lbm'), + 'mass_ref': (150_000, 'lbm'), + 'mass_defect_ref': (150_000, 'lbm'), + 'distance_lower': (0, 'NM'), + 'distance_upper': (500, 'NM'), + 'distance_ref': (10, 'NM'), + 'distance_ref0': (0, 'NM'), + }, 'initial_guesses': { 'times': ([1., 2.], 'min'), 'distance': ([20.e3, 100.e3], 'ft'), @@ -189,31 +184,31 @@ } }, 'climb2': { - 'num_segments': 3, - 'order': 3, - 'fix_initial': False, - 'EAS_target': (270, 'kn'), - 'mach_cruise': 0.8, - 'target_mach': True, - 'final_alt': (37.5e3, 'ft'), - 'required_available_climb_rate': (0.1, 'ft/min'), - 'time_initial_bounds': ((10, 700), 's'), - 'duration_bounds': ((200, 17_000), 's'), - 'duration_ref': (5000, 's'), - 'alt_lower': (9000, 'ft'), - 'alt_upper': (40000, 'ft'), - 'alt_ref': (30000, 'ft'), - 'alt_ref0': (0, 'ft'), - 'mass_lower': (0, 'lbm'), - 'mass_upper': (None, 'lbm'), - 'mass_ref': (150_000, 'lbm'), - 'mass_defect_ref': (150_000, 'lbm'), - 'distance_lower': (10, 'NM'), - 'distance_upper': (1000, 'NM'), - 'distance_ref': (500, 'NM'), - 'distance_ref0': (0, 'NM'), - 'distance_defect_ref': (500, 'NM'), - 'throttle_setting': throttle_climb, + 'user_options': { + 'num_segments': 3, + 'order': 3, + 'fix_initial': False, + 'EAS_target': (270, 'kn'), + 'mach_cruise': 0.8, + 'target_mach': True, + 'final_altitude': (37.5e3, 'ft'), + 'required_available_climb_rate': (0.1, 'ft/min'), + 'duration_bounds': ((200, 17_000), 's'), + 'duration_ref': (5000, 's'), + 'alt_lower': (9000, 'ft'), + 'alt_upper': (40000, 'ft'), + 'alt_ref': (30000, 'ft'), + 'alt_ref0': (0, 'ft'), + 'mass_lower': (0, 'lbm'), + 'mass_upper': (None, 'lbm'), + 'mass_ref': (150_000, 'lbm'), + 'mass_defect_ref': (150_000, 'lbm'), + 'distance_lower': (10, 'NM'), + 'distance_upper': (1000, 'NM'), + 'distance_ref': (500, 'NM'), + 'distance_ref0': (0, 'NM'), + 'distance_defect_ref': (500, 'NM'), + }, 'initial_guesses': { 'times': ([216., 1300.], 's'), 'distance': ([100.e3, 200.e3], 'ft'), @@ -222,6 +217,10 @@ } }, 'cruise': { + 'user_options': { + 'alt_cruise': (37.5e3, 'ft'), + 'mach_cruise': 0.8, + }, 'initial_guesses': { # [Initial mass, delta mass] for special cruise phase. 'mass': ([171481., -35000], 'lbm'), @@ -232,34 +231,33 @@ } }, 'desc1': { - 'num_segments': 3, - 'order': 3, - 'fix_initial': False, - 'input_initial': False, - 'EAS_limit': (350, 'kn'), - 'mach_cruise': 0.8, - 'input_speed_type': SpeedType.MACH, - 'final_alt': (10.e3, 'ft'), - 'time_initial_bounds': ((1000, 35_000), 's'), - 'time_initial_ref': (10_000, 's'), - 'duration_bounds': ((300., 900.), 's'), - 'duration_ref': (1000, 's'), - 'alt_lower': (1000, 'ft'), - 'alt_upper': (40_000, 'ft'), - 'alt_ref': (30_000, 'ft'), - 'alt_ref0': (0, 'ft'), - 'alt_constraint_ref': (10000, 'ft'), - 'mass_lower': (0, 'lbm'), - 'mass_upper': (None, 'lbm'), - 'mass_ref': (140_000, 'lbm'), - 'mass_ref0': (0, 'lbm'), - 'mass_defect_ref': (140_000, 'lbm'), - 'distance_lower': (3000., 'NM'), - 'distance_upper': (5000., 'NM'), - 'distance_ref': (mission_distance, 'NM'), - 'distance_ref0': (0, 'NM'), - 'distance_defect_ref': (100, 'NM'), - 'throttle_setting': throttle_idle, + 'user_options': { + 'num_segments': 3, + 'order': 3, + 'fix_initial': False, + 'input_initial': False, + 'EAS_limit': (350, 'kn'), + 'mach_cruise': 0.8, + 'input_speed_type': SpeedType.MACH, + 'final_altitude': (10.e3, 'ft'), + 'duration_bounds': ((300., 900.), 's'), + 'duration_ref': (1000, 's'), + 'alt_lower': (1000, 'ft'), + 'alt_upper': (40_000, 'ft'), + 'alt_ref': (30_000, 'ft'), + 'alt_ref0': (0, 'ft'), + 'alt_constraint_ref': (10000, 'ft'), + 'mass_lower': (0, 'lbm'), + 'mass_upper': (None, 'lbm'), + 'mass_ref': (140_000, 'lbm'), + 'mass_ref0': (0, 'lbm'), + 'mass_defect_ref': (140_000, 'lbm'), + 'distance_lower': (3000., 'NM'), + 'distance_upper': (5000., 'NM'), + 'distance_ref': (mission_distance, 'NM'), + 'distance_ref0': (0, 'NM'), + 'distance_defect_ref': (100, 'NM'), + }, 'initial_guesses': { 'mass': (136000., 'lbm'), 'altitude': ([37.5e3, 10.e3], 'ft'), @@ -269,32 +267,31 @@ } }, 'desc2': { - 'num_segments': 1, - 'order': 7, - 'fix_initial': False, - 'input_initial': False, - 'EAS_limit': (250, 'kn'), - 'mach_cruise': 0.80, - 'input_speed_type': SpeedType.EAS, - 'final_alt': (1000, 'ft'), - 'time_initial_bounds': ((2000, 50_000), 's'), - 'time_initial_ref': (15000, 's'), - 'duration_bounds': ((100., 5000), 's'), - 'duration_ref': (500, 's'), - 'alt_lower': (500, 'ft'), - 'alt_upper': (11_000, 'ft'), - 'alt_ref': (10.e3, 'ft'), - 'alt_ref0': (1000, 'ft'), - 'alt_constraint_ref': (1000, 'ft'), - 'mass_lower': (0, 'lbm'), - 'mass_upper': (None, 'lbm'), - 'mass_ref': (150_000, 'lbm'), - 'mass_defect_ref': (150_000, 'lbm'), - 'distance_lower': (0, 'NM'), - 'distance_upper': (5000, 'NM'), - 'distance_ref': (3500, 'NM'), - 'distance_defect_ref': (100, 'NM'), - 'throttle_setting': throttle_idle, + 'user_options': { + 'num_segments': 1, + 'order': 7, + 'fix_initial': False, + 'input_initial': False, + 'EAS_limit': (250, 'kn'), + 'mach_cruise': 0.80, + 'input_speed_type': SpeedType.EAS, + 'final_altitude': (1000, 'ft'), + 'duration_bounds': ((100., 5000), 's'), + 'duration_ref': (500, 's'), + 'alt_lower': (500, 'ft'), + 'alt_upper': (11_000, 'ft'), + 'alt_ref': (10.e3, 'ft'), + 'alt_ref0': (1000, 'ft'), + 'alt_constraint_ref': (1000, 'ft'), + 'mass_lower': (0, 'lbm'), + 'mass_upper': (None, 'lbm'), + 'mass_ref': (150_000, 'lbm'), + 'mass_defect_ref': (150_000, 'lbm'), + 'distance_lower': (0, 'NM'), + 'distance_upper': (5000, 'NM'), + 'distance_ref': (3500, 'NM'), + 'distance_defect_ref': (100, 'NM'), + }, 'initial_guesses': { 'mass': (136000., 'lbm'), 'altitude': ([10.e3, 1.e3], 'ft'), @@ -306,7 +303,7 @@ } -def phase_info_parameterization(phase_info, aviary_inputs): +def phase_info_parameterization(phase_info, post_mission_info, aviary_inputs): """ Modify the values in the phase_info dictionary to accomodate different values for the following mission design inputs: cruise altitude, cruise mach number, @@ -343,10 +340,10 @@ def phase_info_parameterization(phase_info, aviary_inputs): range_scale = range_cruise / old_range_cruise # Altitude - old_alt_cruise = phase_info['climb2']['final_alt'][0] + old_alt_cruise = phase_info['climb2']['user_options']['final_altitude'][0] if alt_cruise != old_alt_cruise: - phase_info['climb2']['final_alt'] = (alt_cruise, 'ft') + phase_info['climb2']['user_options']['final_altitude'] = (alt_cruise, 'ft') phase_info['climb2']['initial_guesses']['altitude'] = ([10.e3, alt_cruise], 'ft') phase_info['cruise']['initial_guesses']['altitude'] = (alt_cruise, 'ft') phase_info['desc1']['initial_guesses']['altitude'] = ([alt_cruise, 10.e3], 'ft') @@ -383,4 +380,4 @@ def phase_info_parameterization(phase_info, aviary_inputs): phase_info['cruise']['initial_guesses']['mach'] = (mach_cruise, 'unitless') - return phase_info + return phase_info, post_mission_info diff --git a/aviary/interface/default_phase_info/two_dof_fiti.py b/aviary/interface/default_phase_info/two_dof_fiti.py index 577bfc20c..def0c1fdc 100644 --- a/aviary/interface/default_phase_info/two_dof_fiti.py +++ b/aviary/interface/default_phase_info/two_dof_fiti.py @@ -17,7 +17,6 @@ def create_2dof_based_ascent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) groundroll_vals = { @@ -29,7 +28,6 @@ def create_2dof_based_ascent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) rotation_vals = {} @@ -38,7 +36,6 @@ def create_2dof_based_ascent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) ascent_vals = { @@ -53,7 +50,6 @@ def create_2dof_based_ascent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) accel_vals = {} @@ -64,7 +60,6 @@ def create_2dof_based_ascent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) climb1_vals = { @@ -79,7 +74,6 @@ def create_2dof_based_ascent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) climb2_vals = { @@ -94,7 +88,6 @@ def create_2dof_based_ascent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) climb3_vals = { @@ -150,7 +143,6 @@ def create_2dof_based_descent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) descent1_vals = { @@ -166,7 +158,6 @@ def create_2dof_based_descent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) descent2_vals = { @@ -182,7 +173,6 @@ def create_2dof_based_descent_phases( ode_args=ode_args, simupy_args=dict( DEBUG=False, - blocked_state_names=['engine_deck.nox', 'nox'], ), ) descent3_vals = { diff --git a/aviary/interface/download_models.py b/aviary/interface/download_models.py new file mode 100644 index 000000000..e58c3dea1 --- /dev/null +++ b/aviary/interface/download_models.py @@ -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) diff --git a/aviary/interface/graphical_input.py b/aviary/interface/graphical_input.py index babc6e72d..baeda5cf5 100644 --- a/aviary/interface/graphical_input.py +++ b/aviary/interface/graphical_input.py @@ -145,9 +145,10 @@ def create_bounds(center): 'optimize_mach': optimize_mach_phase_vars[i].get(), 'optimize_altitude': optimize_altitude_phase_vars[i].get(), 'polynomial_control_order': polynomial_order, + 'use_polynomial_control': True, 'num_segments': num_segments, 'order': 3, - 'solve_for_range': False, + 'solve_for_distance': False, 'initial_mach': (mach_values[i], 'unitless'), 'final_mach': (mach_values[i+1], 'unitless'), 'mach_bounds': ((np.min(mach_values[i:i+2]) - 0.02, np.max(mach_values[i:i+2]) + 0.02), 'unitless'), @@ -177,7 +178,7 @@ def create_bounds(center): if 'pre_mission' in phase_name or 'post_mission' in phase_name: continue phase_info[phase_name]['user_options'].update({ - 'solve_for_range': user_choices.get('solve_for_range', False), + 'solve_for_distance': user_choices.get('solve_for_distance', False), }) # Apply global settings if required @@ -306,9 +307,9 @@ def __init__(self): tk.Checkbutton(checkbox_frame, text="Constrain Range", variable=self.constrain_range_var).pack(anchor="w") self.constrain_range_var.set(True) - self.solve_for_range_var = tk.BooleanVar() + self.solve_for_distance_var = tk.BooleanVar() tk.Checkbutton(checkbox_frame, text="Solve for Range", - variable=self.solve_for_range_var).pack(anchor="w") + variable=self.solve_for_distance_var).pack(anchor="w") # Textbox for Polynomial Control Order self.polynomial_order_var = tk.StringVar() @@ -457,7 +458,7 @@ def on_done(self, event=None): # event=None to handle both button click and key # Retrieve user choices from checkboxes user_choices = { "constrain_range": self.constrain_range_var.get(), - "solve_for_range": self.solve_for_range_var.get() + "solve_for_distance": self.solve_for_distance_var.get() } polynomial_order = int(self.polynomial_order_var.get()) @@ -558,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() diff --git a/aviary/interface/methods_for_level1.py b/aviary/interface/methods_for_level1.py index 0bb4d810c..eef49bbf1 100644 --- a/aviary/interface/methods_for_level1.py +++ b/aviary/interface/methods_for_level1.py @@ -66,9 +66,8 @@ def run_aviary(aircraft_filename, phase_info, optimizer=None, # Allow for user overrides here prob.load_inputs(aircraft_filename, phase_info) - # Have checks for clashing user inputs - # Raise warnings or errors depending on how clashing the issues are - prob.check_inputs() + # Preprocess inputs + prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() diff --git a/aviary/interface/methods_for_level2.py b/aviary/interface/methods_for_level2.py index eb6e44166..e97534b06 100644 --- a/aviary/interface/methods_for_level2.py +++ b/aviary/interface/methods_for_level2.py @@ -1,6 +1,5 @@ import csv import warnings -from packaging import version import inspect from pathlib import Path from copy import deepcopy @@ -15,36 +14,32 @@ import openmdao.api as om from openmdao.utils.units import convert_units -from openmdao.utils.units import valid_units from openmdao.utils.reports_system import _default_reports from aviary.constants import GRAV_ENGLISH_LBM, RHO_SEA_LEVEL_ENGLISH from aviary.mission.flops_based.phases.build_landing import Landing from aviary.mission.flops_based.phases.build_takeoff import Takeoff -from aviary.mission.flops_based.phases.climb_phase import Climb -from aviary.mission.flops_based.phases.cruise_phase import Cruise -from aviary.mission.flops_based.phases.descent_phase import Descent -from aviary.mission.flops_based.phases.simple_energy_phase import EnergyPhase +from aviary.mission.flops_based.phases.energy_phase import EnergyPhase from aviary.mission.gasp_based.ode.groundroll_ode import GroundrollODE from aviary.mission.gasp_based.ode.params import ParamPort from aviary.mission.gasp_based.ode.unsteady_solved.unsteady_solved_ode import \ UnsteadySolvedODE from aviary.mission.gasp_based.phases.time_integration_traj import FlexibleTraj from aviary.mission.gasp_based.phases.time_integration_phases import SGMCruise -from aviary.mission.gasp_based.phases.accel_phase import get_accel -from aviary.mission.gasp_based.phases.ascent_phase import get_ascent -from aviary.mission.gasp_based.phases.climb_phase import get_climb -from aviary.mission.gasp_based.phases.desc_phase import get_descent -from aviary.mission.gasp_based.phases.groundroll_phase import get_groundroll +from aviary.mission.gasp_based.phases.groundroll_phase import GroundrollPhase +from aviary.mission.gasp_based.phases.rotation_phase import RotationPhase +from aviary.mission.gasp_based.phases.climb_phase import ClimbPhase +from aviary.mission.gasp_based.phases.cruise_phase import CruisePhase +from aviary.mission.gasp_based.phases.accel_phase import AccelPhase +from aviary.mission.gasp_based.phases.ascent_phase import AscentPhase +from aviary.mission.gasp_based.phases.descent_phase import DescentPhase from aviary.mission.gasp_based.phases.landing_group import LandingSegment -from aviary.mission.gasp_based.phases.rotation_phase import get_rotation from aviary.mission.gasp_based.phases.taxi_group import TaxiSegment from aviary.mission.gasp_based.phases.v_rotate_comp import VRotateComp from aviary.mission.gasp_based.polynomial_fit import PolynomialFit from aviary.subsystems.premission import CorePreMission -from aviary.mission.gasp_based.ode.breguet_cruise_ode import BreguetCruiseODESolution -from aviary.utils.functions import set_aviary_initial_values, Null, create_opts2vals, add_opts2vals, promote_aircraft_and_mission_vars -from aviary.utils.process_input_decks import create_vehicle +from aviary.utils.functions import set_aviary_initial_values, create_opts2vals, add_opts2vals, promote_aircraft_and_mission_vars +from aviary.utils.process_input_decks import create_vehicle, update_GASP_options, initial_guessing from aviary.utils.preprocessors import preprocess_crewpayload from aviary.interface.utils.check_phase_info import check_phase_info from aviary.utils.aviary_values import AviaryValues @@ -71,9 +66,8 @@ FLOPS = LegacyCode.FLOPS GASP = LegacyCode.GASP -HEIGHT_ENERGY = EquationsOfMotion.HEIGHT_ENERGY TWO_DEGREES_OF_FREEDOM = EquationsOfMotion.TWO_DEGREES_OF_FREEDOM -SIMPLE = EquationsOfMotion.SIMPLE +HEIGHT_ENERGY = EquationsOfMotion.HEIGHT_ENERGY SOLVED = EquationsOfMotion.SOLVED @@ -158,7 +152,7 @@ def __init__(self, analysis_scheme=AnalysisScheme.COLLOCATION, **kwargs): self.analysis_scheme = analysis_scheme - def load_inputs(self, input_filename, phase_info=None, engine_builder=None): + def load_inputs(self, aviary_inputs, phase_info=None, engine_builder=None): """ This method loads the aviary_values inputs and options that the user specifies. They could specify files to load and values to @@ -171,14 +165,22 @@ def load_inputs(self, input_filename, phase_info=None, engine_builder=None): """ ## LOAD INPUT FILE ### self.engine_builder = engine_builder - self.aviary_inputs, self.initial_guesses = create_vehicle(input_filename) - aviary_inputs = self.aviary_inputs + # Create AviaryValues object from file (or process existing AviaryValues object + # with default values from metadata) and generate initial guesses + aviary_inputs, initial_guesses = create_vehicle(aviary_inputs) + # pull which methods will be used for subsystems and mission self.mission_method = mission_method = aviary_inputs.get_val( Settings.EQUATIONS_OF_MOTION) self.mass_method = mass_method = aviary_inputs.get_val(Settings.MASS_METHOD) + if mission_method is TWO_DEGREES_OF_FREEDOM or mass_method is GASP: + aviary_inputs = update_GASP_options(aviary_inputs) + initial_guesses = initial_guessing(aviary_inputs, initial_guesses) + self.aviary_inputs = aviary_inputs + self.initial_guesses = initial_guesses + if mission_method is TWO_DEGREES_OF_FREEDOM: aviary_inputs.set_val(Mission.Summary.CRUISE_MASS_FINAL, val=self.initial_guesses['cruise_mass_final'], units='lbm') @@ -213,15 +215,13 @@ def load_inputs(self, input_filename, phase_info=None, engine_builder=None): # Access the phase_info variable from the loaded module phase_info = outputted_phase_info.phase_info - print('Loaded outputted_phase_info.py generated with GUI') - else: - # 2dof -> two_dof - method = self.mission_method.value.replace('2DOF', 'two_dof') - - module = importlib.import_module( - self.mission_method.value, 'aviary.interface.default_phase_info') - phase_info = getattr(module, 'phase_info') + if self.mission_method is TWO_DEGREES_OF_FREEDOM: + from aviary.interface.default_phase_info.two_dof import phase_info + elif self.mission_method is HEIGHT_ENERGY: + from aviary.interface.default_phase_info.height_energy import phase_info + elif self.mission_method is SOLVED: + from aviary.interface.default_phase_info.solved import phase_info print('Loaded default phase_info for' f'{self.mission_method.value.lower()} equations of motion') @@ -252,7 +252,7 @@ def load_inputs(self, input_filename, phase_info=None, engine_builder=None): ## PROCESSING ## # set up core subsystems - if mission_method in (HEIGHT_ENERGY, SIMPLE): + if mission_method is HEIGHT_ENERGY: everything_else_origin = FLOPS elif mission_method in (TWO_DEGREES_OF_FREEDOM, SOLVED): everything_else_origin = GASP @@ -354,11 +354,10 @@ def _update_metadata_from_subsystems(self): if variable in self.meta_data: self.meta_data.pop(variable) - def check_inputs(self): + def check_and_preprocess_inputs(self): """ - This method checks the user-supplied input values for any potential problems. - These problems include variable names that are not recognized in Aviary, - conflicting options or values, or units mismatching. + This method checks the user-supplied input values for any potential problems + and preprocesses the inputs to prepare them for use in the Aviary problem. """ check_phase_info(self.phase_info, self.mission_method) @@ -421,13 +420,13 @@ def add_pre_mission_systems(self): # Check for 2DOF mission method # NOTE should solved trigger this as well? if self.mission_method is TWO_DEGREES_OF_FREEDOM: - self._add_gasp_takeoff_systems() + self._add_two_dof_takeoff_systems() # Check for HE mission method - elif self.mission_method is HEIGHT_ENERGY or self.mission_method is SIMPLE: - self._add_flops_takeoff_systems() + elif self.mission_method is HEIGHT_ENERGY: + self._add_height_energy_takeoff_systems() - def _add_flops_takeoff_systems(self): + def _add_height_energy_takeoff_systems(self): # Initialize takeoff options takeoff_options = Takeoff( airport_altitude=0., # ft @@ -440,7 +439,7 @@ def _add_flops_takeoff_systems(self): 'takeoff', takeoff, promotes_inputs=['aircraft:*', 'mission:*'], promotes_outputs=['mission:*']) - def _add_gasp_takeoff_systems(self): + def _add_two_dof_takeoff_systems(self): # Create options to values OptionsToValues = create_opts2vals( [Aircraft.CrewPayload.NUM_PASSENGERS, @@ -475,7 +474,13 @@ def _add_gasp_takeoff_systems(self): self.model.add_subsystem( "event_xform", om.ExecComp( - ["t_init_gear=m*tau_gear+b", "t_init_flaps=m*tau_flaps+b"], units="s" + ["t_init_gear=m*tau_gear+b", "t_init_flaps=m*tau_flaps+b"], + t_init_gear={"units": "s"}, + t_init_flaps={"units": "s"}, + tau_gear={"units": "unitless"}, + tau_flaps={"units": "unitless"}, + m={"units": "s"}, + b={"units": "s"}, ), promotes_inputs=[ "tau_gear", @@ -569,108 +574,47 @@ def _get_2dof_phase(self, phase_name): # Get the phase options for the specified phase name phase_options = self.phase_info[phase_name] - if 'cruise' in phase_name: - phase = dm.AnalyticPhase( - ode_class=BreguetCruiseODESolution, - ode_init_kwargs=self.ode_args, - num_nodes=5, - ) - - # Time here is really the independent variable through which we are integrating. - # In the case of the Breguet Range ODE, it's mass. - # We rely on mass being monotonically non-increasing across the phase. - phase.set_time_options( - name='mass', - fix_initial=False, - fix_duration=False, - units="lbm", - targets="mass", - initial_bounds=(10.e3, 500_000), - initial_ref=100_000, - duration_bounds=(-50000, -10), - duration_ref=50000, - ) - - phase.add_parameter(Dynamic.Mission.ALTITUDE, opt=False, - val=self.phase_info['climb2']['final_alt'][0], units=self.phase_info['climb2']['final_alt'][1]) - phase.add_parameter(Dynamic.Mission.MACH, opt=False, - val=self.phase_info['climb2']['mach_cruise']) - phase.add_parameter("initial_distance", opt=False, val=0.0, - units="NM", static_target=True) - phase.add_parameter("initial_time", opt=False, val=0.0, - units="s", static_target=True) - - phase.add_timeseries_output("time", units="s") + subsystems = self.core_subsystems + default_mission_subsystems = [ + subsystems['aerodynamics'], subsystems['propulsion']] - else: + if 'cruise' not in phase_name: + num_segments = phase_options['user_options']['num_segments'] + order = phase_options['user_options']['order'] # Create a Radau transcription scheme object with the specified num_segments and order transcription = dm.Radau( - num_segments=phase_options['num_segments'], - order=phase_options['order'], + num_segments=num_segments, + order=order, compressed=True, solve_segments=False) - - # Create a dictionary of phase functions - phase_functions = { - 'groundroll': get_groundroll, - 'rotation': get_rotation, - 'ascent': get_ascent, - 'accel': get_accel - } - - # Set the phase function based on the phase name - if 'climb' in phase_name: - phase_functions[phase_name] = get_climb - elif 'desc' in phase_name: - phase_functions[phase_name] = get_descent - - # Get the phase function corresponding to the phase name - phase_func = phase_functions.get(phase_name) - - # Calculate the phase by calling the phase function - # with the transcription object and remaining phase options - trimmed_phase_options = {k: v for k, v in phase_options.items( - ) if k not in ['num_segments', 'order', 'initial_guesses', 'throttle_setting', 'external_subsystems']} - - # define expected units for each phase option - expected_units = { - 'alt': 'ft', - 'mass': 'lbm', - 'distance': 'ft', - 'time': 's', - 'duration': 's', - 'initial': 's', - 'EAS': 'kn', - 'TAS': 'kn', - 'angle': 'deg', - 'pitch': 'deg', - 'normal': 'lbf', - 'final_alt': 'ft', - 'required_available_climb_rate': 'ft/min', - } - - if phase_name in ['accel', 'climb1', 'climb2', 'desc1', 'desc2']: - expected_units['distance'] = 'NM' - - # loop through all trimmed_phase_options and call wrapped_convert_units with the correct expected units - for key, value in trimmed_phase_options.items(): - for expected_key, expected_unit in expected_units.items(): - if key.startswith(expected_key): - trimmed_phase_options[key] = wrapped_convert_units( - value, expected_unit) - - phase = phase_func( - ode_args=self.ode_args, - transcription=transcription, - **trimmed_phase_options) - + else: + transcription = None + + if 'groundroll' in phase_name: + phase_builder = GroundrollPhase + elif 'rotation' in phase_name: + phase_builder = RotationPhase + elif 'accel' in phase_name: + phase_builder = AccelPhase + elif 'ascent' in phase_name: + phase_builder = AscentPhase + elif 'climb' in phase_name: + phase_builder = ClimbPhase + elif 'cruise' in phase_name: + phase_builder = CruisePhase + elif 'desc' in phase_name: + phase_builder = DescentPhase + + phase_object = phase_builder.from_phase_info( + phase_name, phase_options, default_mission_subsystems, meta_data=self.meta_data, transcription=transcription) + phase = phase_object.build_phase(aviary_options=self.aviary_inputs) + + if 'cruise' not in phase_name: phase.add_control( Dynamic.Mission.THROTTLE, targets=Dynamic.Mission.THROTTLE, units='unitless', - opt=False, lower=0.0, upper=1.0 + opt=False, ) - phase.timeseries_options['use_prefix'] = True - return phase def _add_groundroll_eq_constraint(self, phase): @@ -726,30 +670,9 @@ def _get_height_energy_phase(self, phase_name, phase_idx): default_mission_subsystems = [ subsystems['aerodynamics'], subsystems['propulsion']] - if self.mission_method is HEIGHT_ENERGY or self.mission_method is SIMPLE: - if self.mission_method is SIMPLE: - climb_builder = EnergyPhase - cruise_builder = EnergyPhase - descent_builder = EnergyPhase - else: - climb_builder = Climb - cruise_builder = Cruise - descent_builder = Descent - - if phase_name == 'climb': - phase_object = climb_builder.from_phase_info( - phase_name, phase_options, default_mission_subsystems, meta_data=self.meta_data) - - elif phase_name == 'cruise': - phase_object = cruise_builder.from_phase_info( - phase_name, phase_options, default_mission_subsystems, meta_data=self.meta_data) - - elif phase_name == 'descent': - phase_object = descent_builder.from_phase_info( - phase_name, phase_options, default_mission_subsystems, meta_data=self.meta_data) - else: - phase_object = EnergyPhase.from_phase_info( - phase_name, phase_options, default_mission_subsystems, meta_data=self.meta_data) + if self.mission_method is HEIGHT_ENERGY: + phase_object = EnergyPhase.from_phase_info( + phase_name, phase_options, default_mission_subsystems, meta_data=self.meta_data) phase = phase_object.build_phase(aviary_options=self.aviary_inputs) @@ -780,7 +703,7 @@ def _get_height_energy_phase(self, phase_name, phase_idx): fix_initial_time = get_initial(fix_initial, "time", True) input_initial = False - if self.mission_method is SIMPLE: + if self.mission_method is HEIGHT_ENERGY: user_options.set_val('initial_ref', 10., 'min') duration_bounds = user_options.get_val("duration_bounds", 'min') user_options.set_val( @@ -811,8 +734,6 @@ def _get_height_energy_phase(self, phase_name, phase_idx): initial_ref=user_options.get_val("initial_ref", 's'), ) - phase.timeseries_options['use_prefix'] = True - return phase def _get_solved_phase(self, phase_name): @@ -850,7 +771,7 @@ def _get_solved_phase(self, phase_name): phase.set_state_options("mass", rate_source="dmass_dv", fix_initial=True, fix_final=False, lower=1, upper=195_000, ref=takeoff_mass, defect_ref=takeoff_mass) - phase.set_state_options(Dynamic.Mission.RANGE, rate_source="over_a", + phase.set_state_options(Dynamic.Mission.DISTANCE, rate_source="over_a", fix_initial=True, fix_final=False, lower=0, upper=2000., ref=1.e2, defect_ref=1.e2) phase.add_parameter("t_init_gear", units="s", @@ -889,15 +810,15 @@ def _get_solved_phase(self, phase_name): static_target=False) phase.set_time_options(fix_initial=False, fix_duration=False, - units="range_units", name=Dynamic.Mission.RANGE, + units="distance_units", name=Dynamic.Mission.DISTANCE, duration_bounds=wrapped_convert_units( - phase_options['duration_bounds'], "range_units"), + phase_options['duration_bounds'], "distance_units"), duration_ref=wrapped_convert_units( - phase_options['duration_ref'], "range_units"), + phase_options['duration_ref'], "distance_units"), initial_bounds=wrapped_convert_units( - phase_options['initial_bounds'], "range_units"), + phase_options['initial_bounds'], "distance_units"), initial_ref=wrapped_convert_units( - phase_options['initial_ref'], "range_units"), + phase_options['initial_ref'], "distance_units"), ) if phase_name == "cruise" or phase_name == "descent": @@ -966,8 +887,6 @@ def _get_solved_phase(self, phase_name): rate_targets=['dh_dr'], rate2_targets=['d2h_dr2'], opt=phase_options['opt'], upper=40.e3, ref=30.e3, lower=-1.) - phase.timeseries_options['use_prefix'] = True - return phase def add_phases(self, phase_info_parameterization=None): @@ -985,8 +904,9 @@ def add_phases(self, phase_info_parameterization=None): traj: The Dymos Trajectory object containing the added mission phases. """ if phase_info_parameterization is not None: - self.phase_info = phase_info_parameterization(self.phase_info, - self.aviary_inputs) + self.phase_info, self.post_mission_info = phase_info_parameterization(self.phase_info, + self.post_mission_info, + self.aviary_inputs) phase_info = self.phase_info @@ -1005,7 +925,6 @@ def add_phases(self, phase_info_parameterization=None): descent_phases = create_2dof_based_descent_phases( self.ode_args, - cruise_alt=self.cruise_alt, cruise_mach=self.cruise_mach) descent_estimation = descent_range_and_fuel( @@ -1026,8 +945,6 @@ def add_phases(self, phase_info_parameterization=None): alpha_mode=AlphaModes.REQUIRED_LIFT, simupy_args=dict( DEBUG=True, - blocked_state_names=['engine.nox', 'nox', - 'TAS', Dynamic.Mission.FLIGHT_PATH_ANGLE], ), ) cruise_vals = { @@ -1084,7 +1001,7 @@ def add_subsystem_timeseries_outputs(phase, phase_name): if phase_name == 'ascent': self._add_groundroll_eq_constraint(phase) - elif self.mission_method is HEIGHT_ENERGY or self.mission_method is SIMPLE: + elif self.mission_method is HEIGHT_ENERGY: for phase_idx, phase_name in enumerate(phases): phase = traj.add_phase( phase_name, self._get_height_energy_phase(phase_name, phase_idx)) @@ -1142,7 +1059,7 @@ def add_subsystem_timeseries_outputs(phase, phase_name): pass elif phase_name == "descent": phase.add_boundary_constraint( - Dynamic.Mission.RANGE, + Dynamic.Mission.DISTANCE, loc="final", equals=target_range, units="NM", @@ -1205,11 +1122,14 @@ def add_post_mission_systems(self, include_landing=True): A user can override this with their own postmission systems. """ + if self.pre_mission_info['include_takeoff'] and self.mission_method is HEIGHT_ENERGY: + self._add_post_mission_takeoff_systems() + if include_landing and self.post_mission_info['include_landing']: if self.mission_method is HEIGHT_ENERGY: - self._add_flops_landing_systems() + self._add_height_energy_landing_systems() elif self.mission_method is TWO_DEGREES_OF_FREEDOM: - self._add_gasp_landing_systems() + self._add_two_dof_landing_systems() self.model.add_subsystem('post_mission', self.post_mission, promotes_inputs=['*'], @@ -1225,7 +1145,7 @@ def add_post_mission_systems(self, include_landing=True): self.post_mission.add_subsystem(external_subsystem.name, subsystem_postmission) - if self.mission_method is HEIGHT_ENERGY or self.mission_method is SIMPLE: + if self.mission_method is HEIGHT_ENERGY: phases = list(self.phase_info.keys()) ecomp = om.ExecComp('fuel_burned = initial_mass - mass_final', initial_mass={'units': 'lbm'}, @@ -1235,7 +1155,7 @@ def add_post_mission_systems(self, include_landing=True): self.post_mission.add_subsystem('fuel_burn', ecomp, promotes_outputs=['fuel_burned']) - self.model.connect(f"traj.{phases[0]}.timeseries.states:mass", + self.model.connect(f"traj.{phases[0]}.timeseries.mass", "fuel_burn.initial_mass", src_indices=[0]) self.model.connect(f"traj.{phases[-1]}.states:mass", "fuel_burn.mass_final", src_indices=[-1]) @@ -1273,7 +1193,7 @@ def add_post_mission_systems(self, include_landing=True): ("range_resid", Mission.Constraints.RANGE_RESIDUAL)], ) - self.model.connect(f"traj.{phases[-1]}.timeseries.states:range", + self.model.connect(f"traj.{phases[-1]}.timeseries.distance", "range_constraint.actual_range", src_indices=[-1]) self.model.add_constraint( Mission.Constraints.RANGE_RESIDUAL, equals=0.0, ref=1.e2) @@ -1388,29 +1308,22 @@ def add_linkage_constraint(self, phase_a, phase_b, var_b): self.traj.link_phases(phases, vars=['time'], ref=100.) self.traj.link_phases(phases, vars=['mass'], ref=10.e3) self.traj.link_phases( - phases, vars=[Dynamic.Mission.RANGE], units='m', ref=10.e3) + phases, vars=[Dynamic.Mission.DISTANCE], units='m', ref=10.e3) self.traj.link_phases(phases[:7], vars=['TAS'], units='kn', ref=200.) - elif self.mission_method is SIMPLE: + elif self.mission_method is HEIGHT_ENERGY: self.traj.link_phases( - phases, ["time", Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=True) + phases, ["time", Dynamic.Mission.MASS, Dynamic.Mission.DISTANCE], connected=True) self._link_phases_helper_with_options( phases, 'optimize_altitude', Dynamic.Mission.ALTITUDE, ref=1.e4) self._link_phases_helper_with_options( phases, 'optimize_mach', Dynamic.Mission.MACH) - elif self.mission_method is HEIGHT_ENERGY: - self.traj.link_phases( - phases, ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=False, ref=1.e4) - self.traj.link_phases( - phases, [Dynamic.Mission.VELOCITY], connected=False, ref=250.) - elif self.mission_method is TWO_DEGREES_OF_FREEDOM: if self.analysis_scheme is AnalysisScheme.COLLOCATION: self.traj.link_phases(["groundroll", "rotation", "ascent"], [ - "time", "TAS", "mass", "distance"], connected=True) + "time", "TAS", "mass", Dynamic.Mission.DISTANCE], connected=True) self.traj.link_phases( ["rotation", "ascent"], ["alpha"], connected=False, ref=5e1, @@ -1418,8 +1331,8 @@ def add_linkage_constraint(self, phase_a, phase_b, var_b): self.traj.add_linkage_constraint( "ascent", "accel", - "distance", - "distance", + Dynamic.Mission.DISTANCE, + Dynamic.Mission.DISTANCE, "final", "initial", connected=False, @@ -1432,13 +1345,14 @@ def add_linkage_constraint(self, phase_a, phase_b, var_b): "time", "mass", "TAS"], connected=True) self.traj.link_phases( phases=["accel", "climb1", "climb2"], - vars=["time", Dynamic.Mission.ALTITUDE, "mass", "distance"], + vars=["time", Dynamic.Mission.ALTITUDE, + "mass", Dynamic.Mission.DISTANCE], connected=True, ) self.traj.link_phases( phases=["desc1", "desc2"], - vars=["time", "mass", "distance"], + vars=["time", "mass", Dynamic.Mission.DISTANCE], connected=True, ) @@ -1506,17 +1420,17 @@ def add_linkage_constraint(self, phase_a, phase_b, var_a, var_b, connected, self.model.connect("traj.ascent.timeseries.time", "h_fit.time_cp") self.model.connect( - "traj.ascent.timeseries.states:altitude", "h_fit.h_cp") + "traj.ascent.timeseries.altitude", "h_fit.h_cp") self.model.connect( - "traj.desc2.timeseries.states:mass", + "traj.desc2.timeseries.mass", "landing.mass", src_indices=[-1], flat_src_indices=True, ) connect_map = { - "traj.desc2.timeseries.states:distance": Mission.Summary.RANGE, + "traj.desc2.timeseries.distance": Mission.Summary.RANGE, "traj.desc2.states:mass": Mission.Landing.TOUCHDOWN_MASS, } else: @@ -1534,7 +1448,7 @@ def add_linkage_constraint(self, phase_a, phase_b, var_a, var_b, connected, self.model.promotes("landing", inputs=param_list) if self.analysis_scheme is AnalysisScheme.SHOOTING: self.model.promotes("traj", inputs=param_list) - self.model.list_inputs() + # self.model.list_inputs() # self.model.promotes("traj", inputs=['ascent.ODE_group.eoms.'+Aircraft.Design.MAX_FUSELAGE_PITCH_ANGLE]) self.model.connect("taxi.mass", "vrot.mass") @@ -1550,8 +1464,6 @@ def connect_with_common_params(self, source, target): for source, target in connect_map.items(): connect_with_common_params(self, source, target) - self.model.set_input_defaults(Mission.Takeoff.ASCENT_DURATION, val=30.0) - def add_driver(self, optimizer=None, use_coloring=None, max_iter=50, debug_print=False): """ Add an optimization driver to the Aviary problem. @@ -1659,7 +1571,7 @@ def add_design_variables(self): for dv_name, dv_dict in dv_dict.items(): self.model.add_design_var(dv_name, **dv_dict) - if self.mission_method is HEIGHT_ENERGY or self.mission_method is SIMPLE: + if self.mission_method is HEIGHT_ENERGY: optimize_mass = self.pre_mission_info.get('optimize_mass') if optimize_mass: self.model.add_design_var(Mission.Design.GROSS_MASS, units='lbm', @@ -1673,9 +1585,9 @@ def add_design_variables(self): self.model.add_design_var(Mission.Takeoff.ASCENT_DURATION, lower=1, upper=1000, ref=10.) self.model.add_design_var("tau_gear", lower=0.01, - upper=1.0, units="s", ref=1) + upper=1.0, units="unitless", ref=1) self.model.add_design_var("tau_flaps", lower=0.01, - upper=1.0, units="s", ref=1) + upper=1.0, units="unitless", ref=1) self.model.add_constraint( "h_fit.h_init_gear", equals=50.0, units="ft", ref=50.0) self.model.add_constraint("h_fit.h_init_flaps", @@ -1781,8 +1693,7 @@ def add_objective(self, objective_type=None, ref=None): elif objective_type == "fuel": self.model.add_objective(Mission.Objectives.FUEL, ref=ref) - # If 'mission_method' is 'FLOPS', add a 'fuel_burned' objective - elif self.mission_method is HEIGHT_ENERGY or self.mission_method is SIMPLE: + elif self.mission_method is HEIGHT_ENERGY: ref = ref if ref is not None else default_ref_values.get("fuel_burned", 1) self.model.add_objective("fuel_burned", ref=ref) @@ -1882,6 +1793,7 @@ def setup(self, **kwargs): set_aviary_initial_values( self.model, self.aviary_inputs, meta_data=self.meta_data) + warnings.simplefilter("ignore", om.OpenMDAOWarning) warnings.simplefilter("ignore", om.PromotionWarning) super().setup(**kwargs) @@ -2071,13 +1983,13 @@ def _add_guesses(self, phase_name, phase, guesses): rotation_mass = self.initial_guesses['rotation_mass'] flight_duration = self.initial_guesses['flight_duration'] - if self.mission_method is SIMPLE: + if self.mission_method is HEIGHT_ENERGY: control_keys = ["mach", "altitude"] - state_keys = ["mass", "range"] + state_keys = ["mass", Dynamic.Mission.DISTANCE] else: control_keys = ["velocity_rate", "throttle"] state_keys = ["altitude", "velocity", "mass", - "range", "TAS", "distance", "flight_path_angle", "alpha"] + Dynamic.Mission.DISTANCE, "TAS", Dynamic.Mission.DISTANCE, "flight_path_angle", "alpha"] if self.mission_method is TWO_DEGREES_OF_FREEDOM and phase_name == 'ascent': # Alpha is a control for ascent. control_keys.append('alpha') @@ -2085,7 +1997,7 @@ def _add_guesses(self, phase_name, phase, guesses): prob_keys = ["tau_gear", "tau_flaps"] # for the simple mission method, use the provided initial and final mach and altitude values from phase_info - if self.mission_method is SIMPLE: + if self.mission_method is HEIGHT_ENERGY: initial_altitude = self.phase_info[phase_name]['user_options']['initial_altitude'] final_altitude = self.phase_info[phase_name]['user_options']['final_altitude'] initial_mach = self.phase_info[phase_name]['user_options']['initial_mach'] @@ -2181,7 +2093,7 @@ def _add_guesses(self, phase_name, phase, guesses): # Set the distance guesses as the initial values for the distance state variable self.set_val( f"traj.{phase_name}.states:distance", phase.interp( - "distance", ys=ys) + Dynamic.Mission.DISTANCE, ys=ys) ) def _add_solved_guesses(self, idx, phase_name, phase): @@ -2254,11 +2166,11 @@ def _add_solved_guesses(self, idx, phase_name, phase): time_guesses = np.hstack((0., np.cumsum(range_durations / mean_TAS))) if phase_name != "groundroll": - range_initial = range_guesses[idx] + distance_initial = range_guesses[idx] self.set_val(f"traj.{phase_name}.t_initial", - range_initial, units='range_units') + distance_initial, units='distance_units') self.set_val(f"traj.{phase_name}.t_duration", - range_guesses[idx+1] - range_initial, units='range_units') + range_guesses[idx+1] - distance_initial, units='distance_units') self.set_val( f"traj.{phase_name}.polynomial_controls:altitude", @@ -2399,7 +2311,7 @@ def _get_all_subsystems(self, external_subsystems=None): return all_subsystems - def _add_flops_landing_systems(self): + def _add_height_energy_landing_systems(self): landing_options = Landing( ref_wing_area=self.aviary_inputs.get_val( Aircraft.Wing.AREA, units='ft**2'), @@ -2407,33 +2319,69 @@ def _add_flops_landing_systems(self): Mission.Landing.LIFT_COEFFICIENT_MAX) # no units ) - landing = landing_options.build_phase( - False, - ) + landing = landing_options.build_phase(False) self.model.add_subsystem( 'landing', landing, promotes_inputs=['aircraft:*', 'mission:*'], promotes_outputs=['mission:*']) - connect_takeoff_to_climb = not self.phase_info['climb']['user_options'].get( + last_flight_phase_name = list(self.phase_info.keys())[-1] + if self.phase_info[last_flight_phase_name]['user_options'].get('use_polynomial_control', True): + control_type_string = 'polynomial_control_values' + else: + control_type_string = 'control_values' + + self.model.connect(f'traj.{last_flight_phase_name}.states:mass', + Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1]) + self.model.connect(f'traj.{last_flight_phase_name}.{control_type_string}:altitude', Mission.Landing.INITIAL_ALTITUDE, + src_indices=[0]) + + def _add_post_mission_takeoff_systems(self): + first_flight_phase_name = list(self.phase_info.keys())[0] + connect_takeoff_to_climb = not self.phase_info[first_flight_phase_name]['user_options'].get( 'add_initial_mass_constraint', True) if connect_takeoff_to_climb: self.model.connect(Mission.Takeoff.FINAL_MASS, - 'traj.climb.initial_states:mass') + f'traj.{first_flight_phase_name}.initial_states:mass') self.model.connect(Mission.Takeoff.GROUND_DISTANCE, - 'traj.climb.initial_states:range') - self.model.connect(Mission.Takeoff.FINAL_VELOCITY, - 'traj.climb.initial_states:velocity') - self.model.connect(Mission.Takeoff.FINAL_ALTITUDE, - 'traj.climb.initial_states:altitude') + f'traj.{first_flight_phase_name}.initial_states:distance') - self.model.connect('traj.descent.states:mass', - Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1]) - # TODO: approach velocity should likely be connected - self.model.connect('traj.descent.states:altitude', Mission.Landing.INITIAL_ALTITUDE, - src_indices=[-1]) + if self.phase_info[first_flight_phase_name]['user_options'].get('use_polynomial_control', True): + control_type_string = 'polynomial_control_values' + else: + control_type_string = 'control_values' + + if self.phase_info[first_flight_phase_name]['user_options'].get('optimize_mach', False): + # Create an ExecComp to compute the difference in mach + mach_diff_comp = om.ExecComp( + 'mach_resid_for_connecting_takeoff = final_mach - initial_mach') + self.model.add_subsystem('mach_diff_comp', mach_diff_comp) + + # Connect the inputs to the mach difference component + self.model.connect(Mission.Takeoff.FINAL_MACH, + 'mach_diff_comp.final_mach') + self.model.connect(f'traj.{first_flight_phase_name}.{control_type_string}:mach', + 'mach_diff_comp.initial_mach', src_indices=[0]) - def _add_gasp_landing_systems(self): + # Add constraint for mach difference + self.model.add_constraint( + 'mach_diff_comp.mach_resid_for_connecting_takeoff', equals=0.0) + + if self.phase_info[first_flight_phase_name]['user_options'].get('optimize_altitude', False): + # Similar steps for altitude difference + alt_diff_comp = om.ExecComp( + 'altitude_resid_for_connecting_takeoff = final_altitude - initial_altitude', units='ft') + self.model.add_subsystem('alt_diff_comp', alt_diff_comp) + + self.model.connect(Mission.Takeoff.FINAL_ALTITUDE, + 'alt_diff_comp.final_altitude') + self.model.connect(f'traj.{first_flight_phase_name}.{control_type_string}:altitude', + 'alt_diff_comp.initial_altitude', src_indices=[0]) + + self.model.add_constraint( + 'alt_diff_comp.altitude_resid_for_connecting_takeoff', equals=0.0) + + def _add_two_dof_landing_systems(self): self.model.add_subsystem( "landing", LandingSegment( @@ -2511,31 +2459,35 @@ def _add_gasp_landing_systems(self): ) def _add_fuel_reserve_component(self, reserves_name=Mission.Design.RESERVE_FUEL): - reserves_val = self.aviary_inputs.get_val(Aircraft.Design.RESERVES) - if reserves_val <= 0: - reserves_val = -reserves_val - self.model.add_subsystem( - "reserves_calc", - om.ExecComp( - f"reserve_fuel = {reserves_val}*(takeoff_mass - final_mass)", - takeoff_mass={"units": "lbm"}, - final_mass={"units": "lbm"}, - reserve_fuel={"units": "lbm"} - ), - promotes_inputs=[ - ("takeoff_mass", Mission.Summary.GROSS_MASS), - ("final_mass", Mission.Landing.TOUCHDOWN_MASS), - ], - promotes_outputs=[("reserve_fuel", reserves_name)], - ) - elif reserves_val > 10: - self.model.add_subsystem( - "reserves_calc", - om.ExecComp( - f"reserve_fuel = {reserves_val}", - reserve_fuel={"val": reserves_val, "units": "lbm"} - ), - promotes_outputs=[("reserve_fuel", reserves_name)], - ) - else: - raise ValueError('"aircraft:design:reserves" is not valid between 0 and 10.') + reserves_val = self.aviary_inputs.get_val( + Aircraft.Design.RESERVE_FUEL_ADDITIONAL, units='lbm') + reserves_frac = self.aviary_inputs.get_val( + Aircraft.Design.RESERVE_FUEL_FRACTION, units='unitless') + + if reserves_frac == 0 and reserves_val == 0: + return + + input_data = {} + input_promotions = {} + equation_string = f"reserve_fuel = " + if reserves_frac != 0: + input_data = {'takeoff_mass': {"units": "lbm"}, + 'final_mass': {"units": "lbm"}} + input_promotions = {'promotes_inputs': [ + ("takeoff_mass", Mission.Summary.GROSS_MASS), + ("final_mass", Mission.Landing.TOUCHDOWN_MASS) + ]} + equation_string += f"{reserves_frac}*(takeoff_mass - final_mass)" + if reserves_val != 0: + equation_string += f" + {reserves_val}" + + self.model.add_subsystem( + "reserves_calc", + om.ExecComp( + equation_string, + reserve_fuel={"val": reserves_val, "units": "lbm"}, + **input_data + ), + promotes_outputs=[("reserve_fuel", reserves_name)], + **input_promotions + ) diff --git a/aviary/interface/test/test_check_phase_info.py b/aviary/interface/test/test_check_phase_info.py index cf455372b..9e18ca521 100644 --- a/aviary/interface/test/test_check_phase_info.py +++ b/aviary/interface/test/test_check_phase_info.py @@ -2,8 +2,8 @@ import copy from aviary.interface.utils.check_phase_info import check_phase_info -from aviary.interface.default_phase_info.height_energy import phase_info as phase_info_flops -from aviary.interface.default_phase_info.two_dof import phase_info as phase_info_gasp +from aviary.interface.default_phase_info.height_energy import phase_info as phase_info_height_energy +from aviary.interface.default_phase_info.two_dof import phase_info as phase_info_two_dof from aviary.variable_info.enums import EquationsOfMotion HEIGHT_ENERGY = EquationsOfMotion.HEIGHT_ENERGY @@ -11,31 +11,39 @@ class TestCheckInputs(unittest.TestCase): - def test_correct_input_flops(self): + def test_correct_input_height_energy(self): # This should pass without any issue as it's the same valid dict as before - self.assertTrue(check_phase_info(phase_info_flops, mission_method=HEIGHT_ENERGY)) + self.assertTrue(check_phase_info( + phase_info_height_energy, mission_method=HEIGHT_ENERGY)) def test_incorrect_tuple(self): # Let's replace a tuple with a float in the 'climb' phase - incorrect_tuple_phase_info = copy.deepcopy(phase_info_flops) + incorrect_tuple_phase_info = copy.deepcopy(phase_info_height_energy) incorrect_tuple_phase_info['climb']['user_options']['initial_altitude'] = 10.668e3 with self.assertRaises(ValueError): check_phase_info(incorrect_tuple_phase_info, mission_method=HEIGHT_ENERGY) def test_incorrect_unit(self): # Let's replace a valid unit with an invalid one in the 'climb' phase - incorrect_unit_phase_info = copy.deepcopy(phase_info_flops) + incorrect_unit_phase_info = copy.deepcopy(phase_info_height_energy) incorrect_unit_phase_info['climb']['user_options']['initial_altitude'] = ( 10.668e3, 'invalid_unit') with self.assertRaisesRegex(ValueError, "Key initial_altitude in phase climb has an invalid unit invalid_unit."): check_phase_info(incorrect_unit_phase_info, mission_method=HEIGHT_ENERGY) - def test_correct_input_gasp(self): + def test_correct_input_two_dof(self): # This should pass without any issue as it's the same valid dict as before - self.assertTrue(check_phase_info(phase_info_gasp, + self.assertTrue(check_phase_info(phase_info_two_dof, mission_method=TWO_DEGREES_OF_FREEDOM)) + def test_incorrect_mission_method(self): + # Let's pass an incorrect mission_method name + incorrect_mission_method = 'INVALID_METHOD' + with self.assertRaises(ValueError): + check_phase_info(phase_info_height_energy, + mission_method=incorrect_mission_method) + if __name__ == '__main__': unittest.main() diff --git a/aviary/interface/test/test_cmd_entry_points.py b/aviary/interface/test/test_cmd_entry_points.py index 41da59bd7..1ae378f7e 100644 --- a/aviary/interface/test/test_cmd_entry_points.py +++ b/aviary/interface/test/test_cmd_entry_points.py @@ -28,6 +28,12 @@ def bench_test_IPOPT_cmd(self): cmd = 'aviary run_mission models/test_aircraft/aircraft_for_bench_GwGm.csv --optimizer IPOPT --max_iter 1' self.run_and_test_cmd(cmd) + @require_pyoptsparse(optimizer="IPOPT") + def bench_test_IPOPT_cmd(self): + cmd = 'aviary run_mission models/test_aircraft/aircraft_for_bench_GwGm.csv' \ + ' --optimizer IPOPT --max_iter 1 --shooting' + self.run_and_test_cmd(cmd) + def test_diff_configuration_conversion(self): filepath = pkg_resources.resource_filename('aviary', 'models/test_aircraft/converter_configuration_test_data_GwGm.dat') diff --git a/aviary/interface/test/test_download_models.py b/aviary/interface/test/test_download_models.py new file mode 100644 index 000000000..c8fa4d036 --- /dev/null +++ b/aviary/interface/test/test_download_models.py @@ -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() diff --git a/aviary/interface/test/test_interface_bugs.py b/aviary/interface/test/test_interface_bugs.py index 59f614e3d..4c5d1bed4 100644 --- a/aviary/interface/test/test_interface_bugs.py +++ b/aviary/interface/test/test_interface_bugs.py @@ -74,9 +74,9 @@ def test_post_mission_promotion(self): prob.load_inputs(csv_path, phase_info) - # Have checks for clashing user inputs - # Raise warnings or errors depending on how clashing the issues are - # prob.check_inputs() + +# Preprocess inputs + # prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() diff --git a/aviary/interface/test/test_phase_info.py b/aviary/interface/test/test_phase_info.py index 29e32b2ee..685414215 100644 --- a/aviary/interface/test/test_phase_info.py +++ b/aviary/interface/test/test_phase_info.py @@ -3,21 +3,18 @@ consistency and correctness. """ import unittest -import pkg_resources from copy import deepcopy from openmdao.utils.assert_utils import assert_near_equal -from aviary.interface.default_phase_info.height_energy import phase_info as ph_in_flops -from aviary.interface.default_phase_info.height_energy import phase_info_parameterization as phase_info_parameterization_flops -from aviary.interface.default_phase_info.two_dof import phase_info as ph_in_gasp -from aviary.interface.default_phase_info.two_dof import phase_info_parameterization as phase_info_parameterization_gasp +from aviary.interface.default_phase_info.two_dof import phase_info as ph_in_two_dof +from aviary.interface.default_phase_info.two_dof import phase_info_parameterization as phase_info_parameterization_two_dof +from aviary.interface.default_phase_info.height_energy import phase_info as ph_in_height_energy +from aviary.interface.default_phase_info.height_energy import phase_info_parameterization as phase_info_parameterization_height_energy from aviary.interface.methods_for_level2 import AviaryProblem -from aviary.mission.flops_based.phases.phase_builder_base import \ +from aviary.mission.phase_builder_base import \ PhaseBuilderBase as PhaseBuilder, phase_info_to_builder -# must keep this import to register the phase -from aviary.mission.flops_based.phases.climb_phase import Climb from aviary.variable_info.variables import Aircraft, Mission @@ -80,25 +77,24 @@ def _test_phase_info_dict(self, phase_info_dict, name): raise RuntimeError( f'value mismatch ({key}): {lhs_value} != {rhs_value}') - def test_default_phase_flops(self): - """Tests the roundtrip conversion for default_phase_info.flops""" + def test_default_phase_height_energy(self): + """Tests the roundtrip conversion for default_phase_info.height_energy""" from aviary.interface.default_phase_info.height_energy import phase_info local_phase_info = deepcopy(phase_info) - self._test_phase_info_dict(local_phase_info, 'climb') + self._test_phase_info_dict(local_phase_info, 'cruise') class TestParameterizePhaseInfo(unittest.TestCase): - def test_phase_info_parameterization_gasp(self): - phase_info = deepcopy(ph_in_gasp) + def test_phase_info_parameterization_two_dof(self): + phase_info = deepcopy(ph_in_two_dof) prob = AviaryProblem() - csv_path = pkg_resources.resource_filename( - "aviary", "models/small_single_aisle/small_single_aisle_GwGm.csv") + csv_path = "models/small_single_aisle/small_single_aisle_GwGm.csv" prob.load_inputs(csv_path, phase_info) - prob.check_inputs() + prob.check_and_preprocess_inputs() # We can set some crazy vals, since we aren't going to optimize. prob.aviary_inputs.set_val(Mission.Design.RANGE, 5000, 'km') @@ -107,7 +103,7 @@ def test_phase_info_parameterization_gasp(self): prob.aviary_inputs.set_val(Mission.Design.MACH, 0.6, 'unitless') prob.add_pre_mission_systems() - prob.add_phases(phase_info_parameterization=phase_info_parameterization_gasp) + prob.add_phases(phase_info_parameterization=phase_info_parameterization_two_dof) prob.add_post_mission_systems() prob.link_phases() @@ -117,34 +113,34 @@ def test_phase_info_parameterization_gasp(self): prob.run_model() - assert_near_equal(prob.get_val("traj.desc2.timeseries.input_values:states:distance", units='km')[-1], + assert_near_equal(prob.get_val("traj.desc2.timeseries.input_values:distance", units='km')[-1], 5000.0) - assert_near_equal(prob.get_val("traj.climb2.timeseries.input_values:states:altitude", units='ft')[-1], + assert_near_equal(prob.get_val("traj.climb2.timeseries.input_values:altitude", units='ft')[-1], 31000.0) - assert_near_equal(prob.get_val("traj.groundroll.timeseries.input_values:states:mass", units='lbm')[0], + assert_near_equal(prob.get_val("traj.groundroll.timeseries.input_values:mass", units='lbm')[0], 120000.0) assert_near_equal(prob.get_val("traj.cruise.rhs.mach")[0], 0.6) - def test_phase_info_parameterization_flops(self): - phase_info = deepcopy(ph_in_flops) + def test_phase_info_parameterization_height_energy(self): + phase_info = deepcopy(ph_in_height_energy) prob = AviaryProblem() - csv_path = pkg_resources.resource_filename( - "aviary", "models/test_aircraft/aircraft_for_bench_FwFm.csv") + csv_path = "models/test_aircraft/aircraft_for_bench_FwFm.csv" prob.load_inputs(csv_path, phase_info) - prob.check_inputs() + prob.check_and_preprocess_inputs() # We can set some crazy vals, since we aren't going to optimize. - prob.aviary_inputs.set_val(Mission.Design.RANGE, 5000, 'km') - prob.aviary_inputs.set_val(Mission.Design.CRUISE_ALTITUDE, 31000, units='ft') - prob.aviary_inputs.set_val(Mission.Design.GROSS_MASS, 195000, 'lbm') + prob.aviary_inputs.set_val(Mission.Design.RANGE, 5000., 'km') + prob.aviary_inputs.set_val(Mission.Design.CRUISE_ALTITUDE, 31000., units='ft') + prob.aviary_inputs.set_val(Mission.Design.GROSS_MASS, 195000., 'lbm') prob.aviary_inputs.set_val(Mission.Summary.CRUISE_MACH, 0.6, 'unitless') prob.add_pre_mission_systems() - prob.add_phases(phase_info_parameterization=phase_info_parameterization_flops) + prob.add_phases( + phase_info_parameterization=phase_info_parameterization_height_energy) prob.add_post_mission_systems() prob.link_phases() @@ -154,20 +150,16 @@ def test_phase_info_parameterization_flops(self): prob.run_model() - assert_near_equal(prob.get_val("traj.descent.timeseries.input_values:states:range", units='km')[-1], - 5000.0 * 3378.7 / 3500) - assert_near_equal(prob.get_val("traj.cruise.timeseries.input_values:states:altitude", units='ft')[0], + range_resid = prob.get_val(Mission.Constraints.RANGE_RESIDUAL, units='km')[-1] + assert_near_equal(range_resid, 5000.0 - 1.e-3) + assert_near_equal(prob.get_val("traj.cruise.timeseries.altitude", units='ft')[0], 31000.0) - assert_near_equal(prob.get_val("traj.climb.timeseries.input_values:states:mass", units='lbm')[-1], - 195000.0 * 165000 / 175400) - - # Mach enters as a constraint, so it won't impact openmdao outputs until successful optimization. - # So, to verify we are setting it, reach into internal constraint dicts. - # Order may change if more path constraints are added. - assert_near_equal(prob.model.traj.phases.cruise._path_constraints[1]['equals'], + assert_near_equal(prob.get_val("traj.cruise.timeseries.mach")[0], 0.6) # To run the tests if __name__ == '__main__': unittest.main() + # test = TestPhaseInfo() + # test.test_default_phase_height_energy() diff --git a/aviary/interface/test/test_reserve_support.py b/aviary/interface/test/test_reserve_support.py index 983bb3e8f..542d760bd 100644 --- a/aviary/interface/test/test_reserve_support.py +++ b/aviary/interface/test/test_reserve_support.py @@ -1,5 +1,4 @@ from copy import deepcopy -import pkg_resources import unittest from openmdao.utils.assert_utils import assert_near_equal @@ -19,16 +18,13 @@ def test_post_mission_promotion(self): prob = AviaryProblem() - csv_path = pkg_resources.resource_filename( - "aviary", "models/test_aircraft/aircraft_for_bench_GwFm.csv") + csv_path = "models/test_aircraft/aircraft_for_bench_GwFm.csv" prob.load_inputs(csv_path, phase_info) - prob.check_inputs() + prob.check_and_preprocess_inputs() - # TODO: This needs to be converted into a reserve and a scaler so that it can - # be given proper units. - # The units here are lbm. - prob.aviary_inputs.set_val(Aircraft.Design.RESERVES, 10000.0, units='unitless') + prob.aviary_inputs.set_val( + Aircraft.Design.RESERVE_FUEL_ADDITIONAL, 10000.0, units='lbm') prob.add_pre_mission_systems() prob.add_phases() @@ -40,6 +36,7 @@ def test_post_mission_promotion(self): prob.add_objective(objective_type="mass", ref=-1e5) prob.setup() + prob.set_initial_guesses() prob.run_model() @@ -53,11 +50,10 @@ def test_gasp_relative_reserve(self): prob = AviaryProblem() - csv_path = pkg_resources.resource_filename( - "aviary", "models/small_single_aisle/small_single_aisle_GwGm.csv") + csv_path = "models/small_single_aisle/small_single_aisle_GwGm.csv" prob.load_inputs(csv_path, phase_info) - prob.check_inputs() + prob.check_and_preprocess_inputs() prob.aviary_inputs.set_val(Mission.Summary.GROSS_MASS, 140000.0, units='lbm') @@ -75,10 +71,11 @@ def test_gasp_relative_reserve(self): prob.run_model() - res_frac = prob.aviary_inputs.get_val(Aircraft.Design.RESERVES, units='unitless') + res_frac = prob.aviary_inputs.get_val( + Aircraft.Design.RESERVE_FUEL_FRACTION, units='unitless') td_mass = prob.model.get_val(Mission.Landing.TOUCHDOWN_MASS, units='lbm') reserve = prob.model.get_val(Mission.Design.RESERVE_FUEL, units='lbm') - assert_near_equal(reserve, -res_frac * (140000.0 - td_mass), 1e-3) + assert_near_equal(reserve, res_frac * (140000.0 - td_mass), 1e-3) if __name__ == '__main__': diff --git a/aviary/interface/test/test_simple_mission.py b/aviary/interface/test/test_simple_mission.py index 74ad71aa8..3f6d8dd4a 100644 --- a/aviary/interface/test/test_simple_mission.py +++ b/aviary/interface/test/test_simple_mission.py @@ -1,3 +1,4 @@ +import os import unittest import subprocess @@ -5,13 +6,13 @@ from aviary.interface.methods_for_level1 import run_aviary from aviary.subsystems.test.test_dummy_subsystem import ArrayGuessSubsystemBuilder -from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs @use_tempdirs class AircraftMissionTestSuite(unittest.TestCase): def setUp(self): + # Load the phase_info and other common setup tasks self.phase_info = { "pre_mission": {"include_takeoff": False, "optimize_mass": True}, @@ -23,7 +24,7 @@ def setUp(self): "polynomial_control_order": 1, "num_segments": 5, "order": 3, - "solve_for_range": False, + "solve_for_distance": False, "initial_mach": (0.2, "unitless"), "final_mach": (0.72, "unitless"), "mach_bounds": ((0.18, 0.74), "unitless"), @@ -47,7 +48,7 @@ def setUp(self): "polynomial_control_order": 1, "num_segments": 5, "order": 3, - "solve_for_range": False, + "solve_for_distance": False, "initial_mach": (0.72, "unitless"), "final_mach": (0.72, "unitless"), "mach_bounds": ((0.7, 0.74), "unitless"), @@ -71,7 +72,7 @@ def setUp(self): "polynomial_control_order": 1, "num_segments": 5, "order": 3, - "solve_for_range": False, + "solve_for_distance": False, "initial_mach": (0.72, "unitless"), "final_mach": (0.36, "unitless"), "mach_bounds": ((0.34, 0.74), "unitless"), @@ -94,7 +95,7 @@ def setUp(self): }, } - self.aircraft_definition_file = 'models/test_aircraft/aircraft_for_bench_FwFm_simple.csv' + self.aircraft_definition_file = 'models/test_aircraft/aircraft_for_bench_FwFm.csv' self.make_plots = False self.max_iter = 100 @@ -115,11 +116,21 @@ def run_mission(self, phase_info, optimizer): optimization_history_filename="driver_test.db") def test_mission_basic_and_dashboard(self): + # We need to remove the TESTFLO_RUNNING environment variable for this test to run. + # The reports code checks to see if TESTFLO_RUNNING is set and will not do anything if set + # But we need to remember whether it was set so we can restore it + testflo_running = os.environ.pop('TESTFLO_RUNNING', None) + prob = self.run_mission(self.phase_info, "SLSQP") + + # restore what was there before running the test + if testflo_running is not None: + os.environ['TESTFLO_RUNNING'] = testflo_running + self.assertIsNotNone(prob) self.assertFalse(prob.failed) - cmd = f'aviary dashboard --problem_recorder dymos_solution.db --driver_recorder driver_test.db tmp' + cmd = f'aviary dashboard --problem_recorder dymos_solution.db --driver_recorder driver_test.db {prob.driver._problem()._name}' # this only tests that a given command line tool returns a 0 return code. It doesn't # check the expected output at all. The underlying functions that implement the # commands should be tested seperately. @@ -163,17 +174,17 @@ def test_mission_optimize_altitude_only(self): self.assertFalse(prob.failed) @require_pyoptsparse(optimizer="IPOPT") - def test_mission_solve_for_range(self): + def test_mission_solve_for_distance(self): modified_phase_info = self.phase_info.copy() for phase in ["climb_1", "climb_2", "descent_1"]: - modified_phase_info[phase]["user_options"]["solve_for_range"] = True + modified_phase_info[phase]["user_options"]["solve_for_distance"] = True prob = self.run_mission(modified_phase_info, "IPOPT") self.assertFalse(prob.failed) - def test_mission_solve_for_range(self): + def test_mission_solve_for_distance(self): modified_phase_info = self.phase_info.copy() for phase in ["climb_1", "climb_2", "descent_1"]: - modified_phase_info[phase]["user_options"]["solve_for_range"] = True + modified_phase_info[phase]["user_options"]["solve_for_distance"] = True prob = self.run_mission(modified_phase_info, "SLSQP") self.assertFalse(prob.failed) diff --git a/aviary/interface/utils/check_phase_info.py b/aviary/interface/utils/check_phase_info.py index 1e592be4d..0ea5faa96 100644 --- a/aviary/interface/utils/check_phase_info.py +++ b/aviary/interface/utils/check_phase_info.py @@ -1,9 +1,8 @@ from openmdao.utils.units import valid_units from aviary.variable_info.enums import SpeedType, EquationsOfMotion -HEIGHT_ENERGY = EquationsOfMotion.HEIGHT_ENERGY TWO_DEGREES_OF_FREEDOM = EquationsOfMotion.TWO_DEGREES_OF_FREEDOM -SIMPLE = EquationsOfMotion.SIMPLE +HEIGHT_ENERGY = EquationsOfMotion.HEIGHT_ENERGY SOLVED = EquationsOfMotion.SOLVED @@ -17,48 +16,25 @@ def check_phase_info(phase_info, mission_method): # Common key-values for climb, cruise, and descent common_entries = { - 'initial_ref': tuple, - 'initial_bounds': tuple, - 'duration_ref': tuple, - 'duration_bounds': tuple, - } - - # Phase-specific entries - climb_specific = { - 'input_initial': bool, - 'no_descent': bool, - 'initial_mach': float, - 'initial_altitude': tuple, - 'final_altitude': tuple, - 'final_mach': float, - 'fix_range': bool, - 'fix_initial_time': bool, - } - - cruise_specific = { - 'min_mach': float, - 'max_mach': float, - 'required_available_climb_rate': tuple, - 'mass_f_cruise': tuple, - 'range_f_cruise': tuple, - 'fix_final': bool, - } - - descent_specific = { + 'optimize_mach': bool, + 'optimize_altitude': bool, + 'solve_for_distance': bool, + 'initial_mach': tuple, + 'final_mach': tuple, + 'mach_bounds': tuple, 'initial_altitude': tuple, 'final_altitude': tuple, - 'initial_mach': float, - 'final_mach': float, - 'no_climb': bool, - 'fix_range': bool, + 'altitude_bounds': tuple, + 'throttle_enforcement': str, + 'constrain_final': bool, + 'fix_duration': bool, + 'initial_bounds': tuple, + 'duration_bounds': tuple, } # Combine common and phase-specific entries - phase_keys_flops = { + phase_keys_height_energy = { 'pre_mission': {'include_takeoff': bool, 'optimize_mass': bool}, - 'climb': {**common_entries, **climb_specific}, - 'cruise': {**common_entries, **cruise_specific}, - 'descent': {**common_entries, **descent_specific}, 'post_mission': {'include_landing': bool} } @@ -94,15 +70,12 @@ def check_phase_info(phase_info, mission_method): 'alt_ref': tuple, } common_descent = { - 'input_initial': bool, + 'input_initial': (bool, dict), 'EAS_limit': tuple, 'mach_cruise': float, 'input_speed_type': SpeedType, - 'final_alt': tuple, - 'time_initial_bounds': tuple, - 'time_initial_ref': tuple, + 'final_altitude': tuple, 'alt_constraint_ref': tuple, - 'throttle_setting': float, } phase_keys_gasp = { @@ -113,7 +86,6 @@ def check_phase_info(phase_info, mission_method): **common_TAS, **common_mass, **common_distance, - 'throttle_setting': float, 'distance_defect_ref': tuple, }, 'rotation': { @@ -123,7 +95,6 @@ def check_phase_info(phase_info, mission_method): **common_distance, **common_angle, 'normal_ref': tuple, - 'throttle_setting': float, 'TAS_ref0': tuple, 'distance_defect_ref': tuple, }, @@ -132,28 +103,24 @@ def check_phase_info(phase_info, mission_method): **common_mass, **common_distance, **common_alt, - 'alt_constraint_eq': tuple, + 'final_altitude': tuple, 'alt_constraint_ref': tuple, - 'alt_constraint_ref0': tuple, 'alt_defect_ref': tuple, **common_angle, 'pitch_constraint_lower': tuple, 'pitch_constraint_upper': tuple, 'pitch_constraint_ref': tuple, - 'throttle_setting': float, 'TAS_ref0': tuple, 'distance_defect_ref': tuple, }, 'accel': { 'alt': tuple, 'EAS_constraint_eq': tuple, - 'time_initial_bounds': tuple, **common_duration, 'duration_ref': tuple, **common_TAS, **common_mass, **common_distance, - 'throttle_setting': float, 'TAS_ref0': tuple, 'distance_defect_ref': tuple, }, @@ -161,32 +128,31 @@ def check_phase_info(phase_info, mission_method): 'EAS_target': tuple, 'mach_cruise': float, 'target_mach': bool, - 'final_alt': tuple, - 'time_initial_bounds': tuple, + 'final_altitude': tuple, **common_duration, **common_alt, **common_mass, **common_distance, - 'throttle_setting': float, 'distance_ref0': tuple, }, 'climb2': { 'EAS_target': tuple, 'mach_cruise': float, 'target_mach': bool, - 'final_alt': tuple, + 'final_altitude': tuple, 'required_available_climb_rate': tuple, - 'time_initial_bounds': tuple, **common_duration, **common_alt, **common_mass, **common_distance, - 'throttle_setting': float, 'alt_ref0': tuple, 'distance_ref0': tuple, 'distance_defect_ref': tuple, }, - 'cruise': {'initial_guesses': dict, }, + 'cruise': { + 'mach_cruise': float, + 'alt_cruise': tuple, + }, 'desc1': { **common_descent, **common_duration, @@ -209,25 +175,26 @@ def check_phase_info(phase_info, mission_method): } phase_keys = {} - if mission_method is HEIGHT_ENERGY: + if mission_method is TWO_DEGREES_OF_FREEDOM: for phase in phase_info: if phase != 'pre_mission' and phase != 'post_mission': - phase_keys[phase] = {**common_keys, **phase_keys_flops[phase]} - else: - phase_keys[phase] = phase_keys_flops[phase] - elif mission_method is TWO_DEGREES_OF_FREEDOM: - for phase in phase_info: - if phase != 'pre_mission' and phase != 'post_mission' and phase != 'cruise': - phase_keys[phase] = {**common_keys, **phase_keys_gasp[phase]} - else: - phase_keys[phase] = phase_keys_gasp[phase] + if phase == 'cruise': + phase_keys[phase] = {**phase_keys_gasp[phase]} + else: + phase_keys[phase] = {**common_keys, **phase_keys_gasp[phase]} elif mission_method is SOLVED: return - elif mission_method is SIMPLE: - return + elif mission_method is HEIGHT_ENERGY: + for phase in phase_info: + if phase != 'pre_mission' and phase != 'post_mission': + phase_keys[phase] = {**common_keys, **common_entries} + else: + phase_keys[phase] = phase_keys_height_energy[phase] else: - raise ValueError( - "Invalid mission_method. Please choose either 'FLOPS', 'GASP', 'simple', or 'solved'.") + possible_values = ["'"+e.value+"'" for e in EquationsOfMotion] + possible_values[-1] = "or " + possible_values[-1] + raise ValueError("Invalid mission_method. Please choose from " + + ", ".join(possible_values) + ".") # Check if all phases exist in phase_info for phase in phase_info: diff --git a/aviary/mission/flops_based/ode/landing_eom.py b/aviary/mission/flops_based/ode/landing_eom.py index af73cc2fc..1a2d7a61c 100644 --- a/aviary/mission/flops_based/ode/landing_eom.py +++ b/aviary/mission/flops_based/ode/landing_eom.py @@ -13,10 +13,7 @@ from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.functions import add_aviary_input from aviary.variable_info.variables import Aircraft -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Dynamic, Mission class FlareEOM(om.Group): @@ -43,8 +40,8 @@ def setup(self): 'num_nodes': nn, 'climbing': True} - inputs = [Dynamic.FLIGHT_PATH_ANGLE, Dynamic.VELOCITY] - outputs = [Dynamic.RANGE_RATE, Dynamic.ALTITUDE_RATE] + inputs = [Dynamic.Mission.FLIGHT_PATH_ANGLE, Dynamic.Mission.VELOCITY] + outputs = [Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.ALTITUDE_RATE] self.add_subsystem( 'distance_rates', @@ -57,8 +54,8 @@ def setup(self): 'aviary_options': aviary_options} inputs = [ - Dynamic.MASS, Dynamic.LIFT, Dynamic.THRUST_TOTAL, Dynamic.DRAG, - 'angle_of_attack', Dynamic.FLIGHT_PATH_ANGLE] + Dynamic.Mission.MASS, Dynamic.Mission.LIFT, Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.DRAG, + 'angle_of_attack', Dynamic.Mission.FLIGHT_PATH_ANGLE] outputs = ['forces_horizontal', 'forces_vertical'] @@ -68,7 +65,7 @@ def setup(self): promotes_inputs=inputs, promotes_outputs=outputs) - inputs = ['forces_horizontal', 'forces_vertical', Dynamic.MASS] + inputs = ['forces_horizontal', 'forces_vertical', Dynamic.Mission.MASS] outputs = ['acceleration_horizontal', 'acceleration_vertical'] self.add_subsystem( @@ -79,9 +76,9 @@ def setup(self): inputs = [ 'acceleration_horizontal', 'acceleration_vertical', - Dynamic.RANGE_RATE, Dynamic.ALTITUDE_RATE] + Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.ALTITUDE_RATE] - outputs = [Dynamic.VELOCITY_RATE,] + outputs = [Dynamic.Mission.VELOCITY_RATE,] self.add_subsystem( 'velocity_rate', @@ -90,10 +87,10 @@ def setup(self): promotes_outputs=outputs) inputs = [ - Dynamic.RANGE_RATE, Dynamic.ALTITUDE_RATE, + Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.ALTITUDE_RATE, 'acceleration_horizontal', 'acceleration_vertical'] - outputs = [Dynamic.FLIGHT_PATH_ANGLE_RATE] + outputs = [Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE] self.add_subsystem( 'flight_path_angle_rate', FlightPathAngleRate(num_nodes=nn), @@ -101,8 +98,8 @@ def setup(self): promotes_outputs=outputs) inputs = [ - Dynamic.MASS, Dynamic.LIFT, Dynamic.DRAG, - 'angle_of_attack', Dynamic.FLIGHT_PATH_ANGLE] + Dynamic.Mission.MASS, Dynamic.Mission.LIFT, Dynamic.Mission.DRAG, + 'angle_of_attack', Dynamic.Mission.FLIGHT_PATH_ANGLE] outputs = ['forces_perpendicular', 'required_thrust'] @@ -147,13 +144,14 @@ def setup(self): nn = options['num_nodes'] - add_aviary_input(self, Dynamic.MASS, val=np.ones(nn), units='kg') - add_aviary_input(self, Dynamic.LIFT, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.DRAG, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.MASS, val=np.ones(nn), units='kg') + add_aviary_input(self, Dynamic.Mission.LIFT, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.DRAG, val=np.ones(nn), units='N') self.add_input('angle_of_attack', val=np.zeros(nn), units='rad') - add_aviary_input(self, Dynamic.FLIGHT_PATH_ANGLE, val=np.zeros(nn), units='rad') + add_aviary_input(self, Dynamic.Mission.FLIGHT_PATH_ANGLE, + val=np.zeros(nn), units='rad') self.add_output( 'forces_perpendicular', val=np.zeros(nn), units='N', @@ -182,14 +180,14 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') total_num_engines = aviary_options.get_val(Aircraft.Propulsion.TOTAL_NUM_ENGINES) - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] - drag = inputs[Dynamic.DRAG] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] + drag = inputs[Dynamic.Mission.DRAG] weight = mass * grav_metric alpha = inputs['angle_of_attack'] - gamma = inputs[Dynamic.FLIGHT_PATH_ANGLE] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] # FLOPS measures glideslope below horizontal gamma = -gamma @@ -220,14 +218,14 @@ def compute_partials(self, inputs, J, discrete_inputs=None): t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') total_num_engines = aviary_options.get_val(Aircraft.Propulsion.TOTAL_NUM_ENGINES) - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] - drag = inputs[Dynamic.DRAG] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] + drag = inputs[Dynamic.Mission.DRAG] weight = mass * grav_metric alpha = inputs['angle_of_attack'] - gamma = inputs[Dynamic.FLIGHT_PATH_ANGLE] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] # FLOPS measures glideslope below horizontal gamma = -gamma @@ -246,20 +244,20 @@ def compute_partials(self, inputs, J, discrete_inputs=None): f_h = -grav_metric * s_gamma / c_angle f_v = grav_metric * c_gamma / s_angle - J[forces_key, Dynamic.MASS] = f_h - f_v - J[thrust_key, Dynamic.MASS] = (f_h + f_v) / (2.) + J[forces_key, Dynamic.Mission.MASS] = f_h - f_v + J[thrust_key, Dynamic.Mission.MASS] = (f_h + f_v) / (2.) f_h = 0. f_v = -1. / s_angle - J[forces_key, Dynamic.LIFT] = -f_v - J[thrust_key, Dynamic.LIFT] = f_v / (2.) + J[forces_key, Dynamic.Mission.LIFT] = -f_v + J[thrust_key, Dynamic.Mission.LIFT] = f_v / (2.) f_h = 1. / c_angle f_v = 0. - J[forces_key, Dynamic.DRAG] = f_h - J[thrust_key, Dynamic.DRAG] = f_h / (2.) + J[forces_key, Dynamic.Mission.DRAG] = f_h + J[thrust_key, Dynamic.Mission.DRAG] = f_h / (2.) # ddx(1 / cos(x)) = sec(x) * tan(x) = tan(x) / cos(x) # ddx(1 / sin(x)) = -csc(x) * cot(x) = -1 / (sin(x) * tan(x)) @@ -274,8 +272,8 @@ def compute_partials(self, inputs, J, discrete_inputs=None): f_h = -weight * c_gamma / c_angle f_v = -weight * s_gamma / s_angle - J[forces_key, Dynamic.FLIGHT_PATH_ANGLE] = - f_h + f_v - J[thrust_key, Dynamic.FLIGHT_PATH_ANGLE] = -(f_h + f_v) / (2.) + J[forces_key, Dynamic.Mission.FLIGHT_PATH_ANGLE] = - f_h + f_v + J[thrust_key, Dynamic.Mission.FLIGHT_PATH_ANGLE] = -(f_h + f_v) / (2.) class FlareSumForces(om.ExplicitComponent): @@ -298,14 +296,15 @@ def setup(self): nn = options['num_nodes'] - add_aviary_input(self, Dynamic.MASS, val=np.ones(nn), units='kg') - add_aviary_input(self, Dynamic.LIFT, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.THRUST_TOTAL, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.DRAG, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.MASS, val=np.ones(nn), units='kg') + add_aviary_input(self, Dynamic.Mission.LIFT, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.THRUST_TOTAL, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.DRAG, val=np.ones(nn), units='N') self.add_input('angle_of_attack', val=np.zeros(nn), units='rad') - add_aviary_input(self, Dynamic.FLIGHT_PATH_ANGLE, val=np.zeros(nn), units='rad') + add_aviary_input(self, Dynamic.Mission.FLIGHT_PATH_ANGLE, + val=np.zeros(nn), units='rad') self.add_output( 'forces_horizontal', val=np.zeros(nn), units='N', @@ -322,15 +321,15 @@ def setup_partials(self): rows_cols = np.arange(nn) - self.declare_partials('forces_horizontal', Dynamic.MASS, dependent=False) + self.declare_partials('forces_horizontal', Dynamic.Mission.MASS, dependent=False) self.declare_partials( - 'forces_vertical', Dynamic.MASS, val=-grav_metric, rows=rows_cols, + 'forces_vertical', Dynamic.Mission.MASS, val=-grav_metric, rows=rows_cols, cols=rows_cols) wrt = [ - Dynamic.LIFT, Dynamic.THRUST_TOTAL, Dynamic.DRAG, 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE] + Dynamic.Mission.LIFT, Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.DRAG, 'angle_of_attack', + Dynamic.Mission.FLIGHT_PATH_ANGLE] self.declare_partials('*', wrt, rows=rows_cols, cols=rows_cols) @@ -342,13 +341,13 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): alpha0 = aviary_options.get_val(Mission.Takeoff.ANGLE_OF_ATTACK_RUNWAY, 'rad') t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] - thrust = inputs[Dynamic.THRUST_TOTAL] - drag = inputs[Dynamic.DRAG] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] + thrust = inputs[Dynamic.Mission.THRUST_TOTAL] + drag = inputs[Dynamic.Mission.DRAG] alpha = inputs['angle_of_attack'] - gamma = inputs[Dynamic.FLIGHT_PATH_ANGLE] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] # FLOPS measures glideslope below horizontal gamma = -gamma @@ -380,13 +379,13 @@ def compute_partials(self, inputs, J, discrete_inputs=None): alpha0 = aviary_options.get_val(Mission.Takeoff.ANGLE_OF_ATTACK_RUNWAY, 'rad') t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] - thrust = inputs[Dynamic.THRUST_TOTAL] - drag = inputs[Dynamic.DRAG] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] + thrust = inputs[Dynamic.Mission.THRUST_TOTAL] + drag = inputs[Dynamic.Mission.DRAG] alpha = inputs['angle_of_attack'] - gamma = inputs[Dynamic.FLIGHT_PATH_ANGLE] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] # FLOPS measures glideslope below horizontal gamma = -gamma @@ -400,25 +399,25 @@ def compute_partials(self, inputs, J, discrete_inputs=None): s_gamma = np.sin(gamma) f_h_key = 'forces_horizontal' - J[f_h_key, Dynamic.LIFT] = -s_gamma + J[f_h_key, Dynamic.Mission.LIFT] = -s_gamma f_v_key = 'forces_vertical' - J[f_v_key, Dynamic.LIFT] = c_gamma + J[f_v_key, Dynamic.Mission.LIFT] = c_gamma - J[f_h_key, Dynamic.THRUST_TOTAL] = -c_angle - J[f_v_key, Dynamic.THRUST_TOTAL] = s_angle + J[f_h_key, Dynamic.Mission.THRUST_TOTAL] = -c_angle + J[f_v_key, Dynamic.Mission.THRUST_TOTAL] = s_angle - J[f_h_key, Dynamic.DRAG] = c_gamma - J[f_v_key, Dynamic.DRAG] = s_gamma + J[f_h_key, Dynamic.Mission.DRAG] = c_gamma + J[f_v_key, Dynamic.Mission.DRAG] = s_gamma J[f_h_key, 'angle_of_attack'] = thrust * s_angle J[f_v_key, 'angle_of_attack'] = thrust * c_angle f_h = -drag * s_gamma - lift * c_gamma - thrust * s_angle - J[f_h_key, Dynamic.FLIGHT_PATH_ANGLE] = -f_h + J[f_h_key, Dynamic.Mission.FLIGHT_PATH_ANGLE] = -f_h f_v = -lift * s_gamma + drag * c_gamma - thrust * c_angle - J[f_v_key, Dynamic.FLIGHT_PATH_ANGLE] = -f_v + J[f_v_key, Dynamic.Mission.FLIGHT_PATH_ANGLE] = -f_v class GroundSumForces(om.ExplicitComponent): @@ -442,10 +441,10 @@ def setup(self): nn = options['num_nodes'] - add_aviary_input(self, Dynamic.MASS, val=np.ones(nn), units='kg') - add_aviary_input(self, Dynamic.LIFT, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.THRUST_TOTAL, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.DRAG, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.MASS, val=np.ones(nn), units='kg') + add_aviary_input(self, Dynamic.Mission.LIFT, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.THRUST_TOTAL, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.DRAG, val=np.ones(nn), units='N') self.add_output( 'forces_horizontal', val=np.zeros(nn), units='N', @@ -463,25 +462,25 @@ def setup_partials(self): rows_cols = np.arange(nn) self.declare_partials( - 'forces_vertical', Dynamic.MASS, val=-grav_metric, rows=rows_cols, + 'forces_vertical', Dynamic.Mission.MASS, val=-grav_metric, rows=rows_cols, cols=rows_cols) self.declare_partials( - 'forces_vertical', Dynamic.LIFT, val=1., rows=rows_cols, cols=rows_cols) + 'forces_vertical', Dynamic.Mission.LIFT, val=1., rows=rows_cols, cols=rows_cols) self.declare_partials( - 'forces_vertical', [Dynamic.THRUST_TOTAL, Dynamic.DRAG], dependent=False) + 'forces_vertical', [Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.DRAG], dependent=False) self.declare_partials( - 'forces_horizontal', [Dynamic.MASS, Dynamic.LIFT], rows=rows_cols, + 'forces_horizontal', [Dynamic.Mission.MASS, Dynamic.Mission.LIFT], rows=rows_cols, cols=rows_cols) self.declare_partials( - 'forces_horizontal', Dynamic.THRUST_TOTAL, val=-1., rows=rows_cols, + 'forces_horizontal', Dynamic.Mission.THRUST_TOTAL, val=-1., rows=rows_cols, cols=rows_cols) self.declare_partials( - 'forces_horizontal', Dynamic.DRAG, val=1., rows=rows_cols, cols=rows_cols) + 'forces_horizontal', Dynamic.Mission.DRAG, val=1., rows=rows_cols, cols=rows_cols) def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): options = self.options @@ -489,10 +488,10 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): nn = options['num_nodes'] friction_coefficient = options['friction_coefficient'] - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] - thrust = inputs[Dynamic.THRUST_TOTAL] - drag = inputs[Dynamic.DRAG] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] + thrust = inputs[Dynamic.Mission.THRUST_TOTAL] + drag = inputs[Dynamic.Mission.DRAG] weight = mass * grav_metric @@ -512,8 +511,8 @@ def compute_partials(self, inputs, J, discrete_inputs=None): nn = options['num_nodes'] friction_coefficient = options['friction_coefficient'] - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] weight = mass * grav_metric @@ -523,8 +522,8 @@ def compute_partials(self, inputs, J, discrete_inputs=None): friction = np.zeros(nn) friction[idx_sup] = friction_coefficient * grav_metric - J['forces_horizontal', Dynamic.MASS] = friction + J['forces_horizontal', Dynamic.Mission.MASS] = friction friction = np.zeros(nn) friction[idx_sup] = -friction_coefficient - J['forces_horizontal', Dynamic.LIFT] = friction + J['forces_horizontal', Dynamic.Mission.LIFT] = friction diff --git a/aviary/mission/flops_based/ode/landing_ode.py b/aviary/mission/flops_based/ode/landing_ode.py index c768083bb..76f5f9a36 100644 --- a/aviary/mission/flops_based/ode/landing_ode.py +++ b/aviary/mission/flops_based/ode/landing_ode.py @@ -14,13 +14,9 @@ from aviary.mission.gasp_based.flight_conditions import FlightConditions from aviary.utils.aviary_values import AviaryValues from aviary.utils.functions import set_aviary_initial_values, promote_aircraft_and_mission_vars -from aviary.variable_info.variables import Aircraft -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission +from aviary.variable_info.variables import Aircraft, Dynamic, Mission from aviary.variable_info.variables_in import VariablesIn -Dynamic = _Dynamic.Mission - class ExternalSubsystemGroup(om.Group): def configure(self): @@ -79,16 +75,18 @@ def setup(self): self.add_subsystem( "USatm", USatm1976Comp(num_nodes=nn), - promotes_inputs=[("h", Dynamic.ALTITUDE)], + promotes_inputs=[("h", Dynamic.Mission.ALTITUDE)], promotes_outputs=[ - "rho", ("sos", Dynamic.SPEED_OF_SOUND), ("temp", Dynamic.TEMPERATURE), - ("pres", Dynamic.STATIC_PRESSURE), "viscosity"]) + "rho", ("sos", Dynamic.Mission.SPEED_OF_SOUND), ("temp", + Dynamic.Mission.TEMPERATURE), + ("pres", Dynamic.Mission.STATIC_PRESSURE), "viscosity"]) self.add_subsystem( "fc", FlightConditions(num_nodes=nn), - promotes_inputs=["rho", Dynamic.SPEED_OF_SOUND, ("TAS", Dynamic.VELOCITY)], - promotes_outputs=[Dynamic.DYNAMIC_PRESSURE, Dynamic.MACH, "EAS"]) + promotes_inputs=["rho", Dynamic.Mission.SPEED_OF_SOUND, + ("TAS", Dynamic.Mission.VELOCITY)], + promotes_outputs=[Dynamic.Mission.DYNAMIC_PRESSURE, Dynamic.Mission.MACH, "EAS"]) # NOTE: the following are potentially signficant differences in implementation # between FLOPS and Aviary: @@ -155,13 +153,13 @@ def setup(self): 'eoms', FlareEOM(**kwargs), promotes_inputs=[ - Dynamic.FLIGHT_PATH_ANGLE, Dynamic.VELOCITY, Dynamic.MASS, Dynamic.LIFT, - Dynamic.THRUST_TOTAL, Dynamic.DRAG, 'angle_of_attack', + Dynamic.Mission.FLIGHT_PATH_ANGLE, Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.LIFT, + Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.DRAG, 'angle_of_attack', 'angle_of_attack_rate', Mission.Landing.FLARE_RATE ], promotes_outputs=[ - Dynamic.RANGE_RATE, Dynamic.ALTITUDE_RATE, Dynamic.VELOCITY_RATE, - Dynamic.FLIGHT_PATH_ANGLE_RATE, 'forces_perpendicular', + Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.VELOCITY_RATE, + Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, 'forces_perpendicular', 'required_thrust', 'net_alpha_rate' ] ) @@ -174,10 +172,10 @@ def setup(self): v={'units': 'm/s', 'shape': nn}, # NOTE: FLOPS detailed takeoff stall speed is not dynamic - see above v_stall={'units': 'm/s', 'shape': nn}), - promotes_inputs=[('v', Dynamic.VELOCITY), 'v_stall'], + promotes_inputs=[('v', Dynamic.Mission.VELOCITY), 'v_stall'], promotes_outputs=['v_over_v_stall']) - self.set_input_defaults(Dynamic.ALTITUDE, np.zeros(nn), 'm') - self.set_input_defaults(Dynamic.VELOCITY, np.zeros(nn), 'm/s') + self.set_input_defaults(Dynamic.Mission.ALTITUDE, np.zeros(nn), 'm') + self.set_input_defaults(Dynamic.Mission.VELOCITY, np.zeros(nn), 'm/s') set_aviary_initial_values(self, aviary_options) diff --git a/aviary/mission/flops_based/ode/mission_EOM.py b/aviary/mission/flops_based/ode/mission_EOM.py index 946a85468..353e3758e 100644 --- a/aviary/mission/flops_based/ode/mission_EOM.py +++ b/aviary/mission/flops_based/ode/mission_EOM.py @@ -1,9 +1,10 @@ import openmdao.api as om -from aviary.mission.flops_based.ode.altitude_rate import AltitudeRate +from aviary.mission.ode.altitude_rate import AltitudeRate from aviary.mission.flops_based.ode.range_rate import RangeRate -from aviary.mission.flops_based.ode.specific_energy_rate import SpecificEnergyRate +from aviary.mission.ode.specific_energy_rate import SpecificEnergyRate from aviary.variable_info.variables import Dynamic +from aviary.mission.flops_based.ode.required_thrust import RequiredThrust class MissionEOM(om.Group): @@ -14,21 +15,18 @@ def initialize(self): def setup(self): nn = self.options['num_nodes'] - self.add_subsystem(name='specific_power', - subsys=SpecificEnergyRate(num_nodes=nn), - promotes_inputs=[Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, - Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.DRAG], - promotes_outputs=[Dynamic.Mission.SPECIFIC_ENERGY_RATE]) - self.add_subsystem(name=Dynamic.Mission.ALTITUDE_RATE, - subsys=AltitudeRate(num_nodes=nn), - promotes_inputs=[Dynamic.Mission.SPECIFIC_ENERGY_RATE, - Dynamic.Mission.VELOCITY_RATE, Dynamic.Mission.VELOCITY], - promotes_outputs=[Dynamic.Mission.ALTITUDE_RATE]) + self.add_subsystem(name='required_thrust', + subsys=RequiredThrust(num_nodes=nn), + promotes_inputs=[Dynamic.Mission.DRAG, Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.VELOCITY, Dynamic.Mission.VELOCITY_RATE, Dynamic.Mission.MASS], + promotes_outputs=['thrust_required']) + self.add_subsystem(name='groundspeed', subsys=RangeRate(num_nodes=nn), promotes_inputs=[ Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.VELOCITY], - promotes_outputs=[Dynamic.Mission.RANGE_RATE]) + promotes_outputs=[Dynamic.Mission.DISTANCE_RATE]) + self.add_subsystem(name='excess_specific_power', subsys=SpecificEnergyRate(num_nodes=nn), promotes_inputs=[(Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL), diff --git a/aviary/mission/flops_based/ode/mission_ODE.py b/aviary/mission/flops_based/ode/mission_ODE.py index 0ce3b3541..71fe5b932 100644 --- a/aviary/mission/flops_based/ode/mission_ODE.py +++ b/aviary/mission/flops_based/ode/mission_ODE.py @@ -2,9 +2,6 @@ import openmdao.api as om from dymos.models.atmosphere import USatm1976Comp -from aviary.mission.flops_based.ode.mission_EOM import MissionEOM -from aviary.subsystems.aerodynamics.flops_based.mach_number import MachNumber - from aviary.mission.flops_based.ode.mission_EOM import MissionEOM from aviary.utils.aviary_values import AviaryValues from aviary.utils.functions import promote_aircraft_and_mission_vars @@ -42,6 +39,11 @@ def initialize(self): self.options.declare( 'use_actual_takeoff_mass', default=False, desc='flag to use actual takeoff mass in the climb phase, otherwise assume 100 kg fuel burn') + self.options.declare( + 'throttle_enforcement', default='path_constraint', + values=['path_constraint', 'boundary_constraint', 'bounded', None], + desc='flag to enforce throttle constraints on the path or at the segment boundaries or using solver bounds' + ) def setup(self): options = self.options @@ -65,11 +67,36 @@ def setup(self): promotes_outputs=[ ('sos', Dynamic.Mission.SPEED_OF_SOUND), ('rho', Dynamic.Mission.DENSITY), ('temp', Dynamic.Mission.TEMPERATURE), ('pres', Dynamic.Mission.STATIC_PRESSURE)]) + + # add an execcomp to compute velocity based off mach and sos self.add_subsystem( - name=Dynamic.Mission.MACH, - subsys=MachNumber(num_nodes=nn), - promotes_inputs=[Dynamic.Mission.VELOCITY, Dynamic.Mission.SPEED_OF_SOUND], - promotes_outputs=[Dynamic.Mission.MACH]) + name='velocity_comp', + subsys=om.ExecComp( + 'velocity = mach * sos', + mach={'units': 'unitless', 'shape': (nn,)}, + sos={'units': 'm/s', 'shape': (nn,)}, + velocity={'units': 'm/s', 'shape': (nn,)}, + has_diag_partials=True, + ), + promotes_inputs=[('mach', Dynamic.Mission.MACH), + ('sos', Dynamic.Mission.SPEED_OF_SOUND)], + promotes_outputs=[('velocity', Dynamic.Mission.VELOCITY)], + ) + + # add execcomp to compute velocity_rate based off mach_rate and sos + self.add_subsystem( + name='velocity_rate_comp', + subsys=om.ExecComp( + 'velocity_rate = mach_rate * sos', + mach_rate={'units': 'unitless/s', 'shape': (nn,)}, + sos={'units': 'm/s', 'shape': (nn,)}, + velocity_rate={'units': 'm/s**2', 'shape': (nn,)}, + has_diag_partials=True, + ), + promotes_inputs=[('mach_rate', Dynamic.Mission.MACH_RATE), + ('sos', Dynamic.Mission.SPEED_OF_SOUND)], + promotes_outputs=[('velocity_rate', Dynamic.Mission.VELOCITY_RATE)], + ) base_options = {'num_nodes': nn, 'aviary_inputs': aviary_options} @@ -116,13 +143,31 @@ def setup(self): subsys=MissionEOM(num_nodes=nn), promotes_inputs=[ Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, - Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL, - Dynamic.Mission.DRAG, Dynamic.Mission.VELOCITY_RATE], + Dynamic.Mission.THRUST_MAX_TOTAL, + Dynamic.Mission.DRAG, Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.VELOCITY_RATE], promotes_outputs=[ - Dynamic.Mission.SPECIFIC_ENERGY_RATE, Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS, - Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.ALTITUDE_RATE_MAX, - Dynamic.Mission.RANGE_RATE]) + Dynamic.Mission.ALTITUDE_RATE_MAX, + Dynamic.Mission.DISTANCE_RATE, + 'thrust_required', + ]) + + # add a balance comp to compute throttle based on the altitude rate + self.add_subsystem(name='throttle_balance', + subsys=om.BalanceComp(name=Dynamic.Mission.THROTTLE, + units="unitless", + val=np.ones(nn), + lhs_name='thrust_required', + rhs_name=Dynamic.Mission.THRUST_TOTAL, + eq_units="lbf", + normalize=False, + lower=0.0 if options['throttle_enforcement'] == 'bounded' else None, + upper=1.0 if options['throttle_enforcement'] == 'bounded' else None, + res_ref=1.0e6, + ), + promotes_inputs=['*'], + promotes_outputs=['*']) self.set_input_defaults(Dynamic.Mission.MASS, val=np.ones(nn), units='kg') self.set_input_defaults(Dynamic.Mission.VELOCITY, val=np.ones(nn), units='m/s') @@ -143,7 +188,7 @@ def setup(self): exec_comp_string, initial_mass={'units': 'kg'}, mass={'units': 'kg', 'shape': (nn,)}, - initial_mass_residual={'units': 'kg'}, + initial_mass_residual={'units': 'kg', 'res_ref': 1.0e5}, ) self.add_subsystem('initial_mass_residual_constraint', initial_mass_residual_constraint, @@ -152,3 +197,12 @@ def setup(self): ('mass', Dynamic.Mission.MASS) ], promotes_outputs=['initial_mass_residual']) + + self.nonlinear_solver = om.NewtonSolver(solve_subsystems=True, + atol=1.0e-10, + rtol=1.0e-10, + ) + self.nonlinear_solver.linesearch = om.BoundsEnforceLS() + self.linear_solver = om.DirectSolver(assemble_jac=True) + self.nonlinear_solver.options['err_on_non_converge'] = True + self.nonlinear_solver.options['iprint'] = 2 diff --git a/aviary/mission/flops_based/ode/range_rate.py b/aviary/mission/flops_based/ode/range_rate.py index 9ef5d8fca..86614aeed 100644 --- a/aviary/mission/flops_based/ode/range_rate.py +++ b/aviary/mission/flops_based/ode/range_rate.py @@ -22,7 +22,7 @@ def setup(self): desc='current velocity', units='m/s') self.add_output( - Dynamic.Mission.RANGE_RATE, + Dynamic.Mission.DISTANCE_RATE, val=np.ones(nn), desc='current horizontal velocity (assumed no wind)', units='m/s') @@ -35,19 +35,19 @@ def compute(self, inputs, outputs): if (climb_rate_2 >= velocity_2).any(): raise om.AnalysisError( "WARNING: climb rate exceeds velocity (range_rate.py)") - outputs[Dynamic.Mission.RANGE_RATE] = (velocity_2 - climb_rate_2)**0.5 + outputs[Dynamic.Mission.DISTANCE_RATE] = (velocity_2 - climb_rate_2)**0.5 def setup_partials(self): arange = np.arange(self.options['num_nodes']) self.declare_partials( - Dynamic.Mission.RANGE_RATE, [ + Dynamic.Mission.DISTANCE_RATE, [ Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.VELOCITY], rows=arange, cols=arange) def compute_partials(self, inputs, J): climb_rate = inputs[Dynamic.Mission.ALTITUDE_RATE] velocity = inputs[Dynamic.Mission.VELOCITY] - J[Dynamic.Mission.RANGE_RATE, Dynamic.Mission.ALTITUDE_RATE] = -climb_rate / \ + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.ALTITUDE_RATE] = -climb_rate / \ (velocity**2 - climb_rate**2)**0.5 - J[Dynamic.Mission.RANGE_RATE, Dynamic.Mission.VELOCITY] = velocity / \ + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.VELOCITY] = velocity / \ (velocity**2 - climb_rate**2)**0.5 diff --git a/aviary/mission/flops_based/ode/required_thrust.py b/aviary/mission/flops_based/ode/required_thrust.py index 25903ac6d..af3c5ed62 100644 --- a/aviary/mission/flops_based/ode/required_thrust.py +++ b/aviary/mission/flops_based/ode/required_thrust.py @@ -7,7 +7,7 @@ class RequiredThrust(om.ExplicitComponent): """ Computes the required thrust using the equation: - T_required = drag + (altitude_rate*gravity/velocity + velocity_rate) * mass + thrust_required = drag + (altitude_rate*gravity/velocity + velocity_rate) * mass """ def initialize(self): @@ -26,17 +26,18 @@ def setup(self): nn), units='m/s**2', desc='rate of change of velocity') self.add_input(Dynamic.Mission.MASS, val=np.zeros( nn), units='kg', desc='mass of the aircraft') - self.add_output('T_required', val=np.zeros( + self.add_output('thrust_required', val=np.zeros( nn), units='N', desc='required thrust') ar = np.arange(nn) - self.declare_partials('T_required', Dynamic.Mission.DRAG, rows=ar, cols=ar) + self.declare_partials('thrust_required', Dynamic.Mission.DRAG, rows=ar, cols=ar) self.declare_partials( - 'T_required', Dynamic.Mission.ALTITUDE_RATE, rows=ar, cols=ar) - self.declare_partials('T_required', Dynamic.Mission.VELOCITY, rows=ar, cols=ar) + 'thrust_required', Dynamic.Mission.ALTITUDE_RATE, rows=ar, cols=ar) self.declare_partials( - 'T_required', Dynamic.Mission.VELOCITY_RATE, rows=ar, cols=ar) - self.declare_partials('T_required', Dynamic.Mission.MASS, rows=ar, cols=ar) + 'thrust_required', Dynamic.Mission.VELOCITY, rows=ar, cols=ar) + self.declare_partials( + 'thrust_required', Dynamic.Mission.VELOCITY_RATE, rows=ar, cols=ar) + self.declare_partials('thrust_required', Dynamic.Mission.MASS, rows=ar, cols=ar) def compute(self, inputs, outputs): drag = inputs[Dynamic.Mission.DRAG] @@ -45,9 +46,9 @@ def compute(self, inputs, outputs): velocity_rate = inputs[Dynamic.Mission.VELOCITY_RATE] mass = inputs[Dynamic.Mission.MASS] - T_required = drag + (altitude_rate*gravity/velocity + velocity_rate) * mass + thrust_required = drag + (altitude_rate*gravity/velocity + velocity_rate) * mass - outputs['T_required'] = T_required + outputs['thrust_required'] = thrust_required def compute_partials(self, inputs, partials): altitude_rate = inputs[Dynamic.Mission.ALTITUDE_RATE] @@ -55,10 +56,10 @@ def compute_partials(self, inputs, partials): velocity_rate = inputs[Dynamic.Mission.VELOCITY_RATE] mass = inputs[Dynamic.Mission.MASS] - partials['T_required', Dynamic.Mission.DRAG] = 1.0 - partials['T_required', Dynamic.Mission.ALTITUDE_RATE] = gravity/velocity * mass - partials['T_required', Dynamic.Mission.VELOCITY] = - \ + partials['thrust_required', Dynamic.Mission.DRAG] = 1.0 + partials['thrust_required', Dynamic.Mission.ALTITUDE_RATE] = gravity/velocity * mass + partials['thrust_required', Dynamic.Mission.VELOCITY] = - \ altitude_rate*gravity/velocity**2 * mass - partials['T_required', Dynamic.Mission.VELOCITY_RATE] = mass - partials['T_required', Dynamic.Mission.MASS] = altitude_rate * \ + partials['thrust_required', Dynamic.Mission.VELOCITY_RATE] = mass + partials['thrust_required', Dynamic.Mission.MASS] = altitude_rate * \ gravity/velocity + velocity_rate diff --git a/aviary/mission/flops_based/ode/simple_mission_EOM.py b/aviary/mission/flops_based/ode/simple_mission_EOM.py deleted file mode 100644 index 8e3e25e1f..000000000 --- a/aviary/mission/flops_based/ode/simple_mission_EOM.py +++ /dev/null @@ -1,46 +0,0 @@ -import openmdao.api as om - -from aviary.variable_info.variables import Dynamic -from aviary.mission.flops_based.ode.altitude_rate import AltitudeRate -from aviary.mission.flops_based.ode.range_rate import RangeRate -from aviary.mission.flops_based.ode.specific_energy_rate import SpecificEnergyRate -from aviary.mission.flops_based.ode.required_thrust import RequiredThrust - - -class MissionEOM(om.Group): - def initialize(self): - self.options.declare('num_nodes', types=int, - desc='Number of nodes to be evaluated in the RHS') - - def setup(self): - nn = self.options['num_nodes'] - - self.add_subsystem(name='required_thrust', - subsys=RequiredThrust(num_nodes=nn), - promotes_inputs=[Dynamic.Mission.DRAG, Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.VELOCITY, Dynamic.Mission.VELOCITY_RATE, Dynamic.Mission.MASS], - promotes_outputs=['T_required']) - - self.add_subsystem(name='groundspeed', - subsys=RangeRate(num_nodes=nn), - promotes_inputs=[ - Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.VELOCITY], - promotes_outputs=[Dynamic.Mission.RANGE_RATE]) - - self.add_subsystem(name='excess_specific_power', - subsys=SpecificEnergyRate(num_nodes=nn), - promotes_inputs=[(Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL), - Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.DRAG], - promotes_outputs=[(Dynamic.Mission.SPECIFIC_ENERGY_RATE, Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS)]) - self.add_subsystem( - name=Dynamic.Mission.ALTITUDE_RATE_MAX, - subsys=AltitudeRate( - num_nodes=nn), - promotes_inputs=[ - (Dynamic.Mission.SPECIFIC_ENERGY_RATE, - Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS), - Dynamic.Mission.VELOCITY_RATE, - Dynamic.Mission.VELOCITY], - promotes_outputs=[ - (Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.ALTITUDE_RATE_MAX)]) diff --git a/aviary/mission/flops_based/ode/simple_mission_ODE.py b/aviary/mission/flops_based/ode/simple_mission_ODE.py deleted file mode 100644 index 58554956b..000000000 --- a/aviary/mission/flops_based/ode/simple_mission_ODE.py +++ /dev/null @@ -1,206 +0,0 @@ -import numpy as np -import openmdao.api as om -from dymos.models.atmosphere import USatm1976Comp - -from aviary.mission.flops_based.ode.simple_mission_EOM import MissionEOM -from aviary.utils.aviary_values import AviaryValues -from aviary.utils.functions import promote_aircraft_and_mission_vars -from aviary.variable_info.variable_meta_data import _MetaData -from aviary.variable_info.variables import Dynamic, Mission -from aviary.variable_info.variables_in import VariablesIn - - -class ExternalSubsystemGroup(om.Group): - def configure(self): - promote_aircraft_and_mission_vars(self) - - -class MissionODE(om.Group): - def initialize(self): - self.options.declare( - 'num_nodes', types=int, - desc='Number of nodes to be evaluated in the RHS') - self.options.declare( - 'subsystem_options', types=dict, default={}, - desc='dictionary of parameters to be passed to the subsystem builders') - self.options.declare( - 'aviary_options', types=AviaryValues, - desc='collection of Aircraft/Mission specific options') - self.options.declare( - 'core_subsystems', - desc='list of core subsystem builder instances to be added to the ODE' - ) - self.options.declare( - 'external_subsystems', default=[], - desc='list of external subsystem builder instances to be added to the ODE') - self.options.declare( - 'meta_data', default=_MetaData, - desc='metadata associated with the variables to be passed into the ODE') - self.options.declare( - 'use_actual_takeoff_mass', default=False, - desc='flag to use actual takeoff mass in the climb phase, otherwise assume 100 kg fuel burn') - self.options.declare( - 'throttle_enforcement', default='path', - desc='flag to enforce throttle constraints on the path or at the segment boundaries or using solver bounds' - ) - - def setup(self): - options = self.options - nn = options['num_nodes'] - aviary_options = options['aviary_options'] - core_subsystems = options['core_subsystems'] - subsystem_options = options['subsystem_options'] - engine_count = len(aviary_options.get_val('engine_models')) - - self.add_subsystem( - 'input_port', - VariablesIn(aviary_options=aviary_options, - meta_data=self.options['meta_data'], - context='mission'), - promotes_inputs=['*'], - promotes_outputs=['*']) - self.add_subsystem( - name='atmosphere', - subsys=USatm1976Comp(num_nodes=nn), - promotes_inputs=[('h', Dynamic.Mission.ALTITUDE)], - promotes_outputs=[ - ('sos', Dynamic.Mission.SPEED_OF_SOUND), ('rho', Dynamic.Mission.DENSITY), - ('temp', Dynamic.Mission.TEMPERATURE), ('pres', Dynamic.Mission.STATIC_PRESSURE)]) - - # add an execcomp to compute velocity based off mach and sos - self.add_subsystem( - name='velocity_comp', - subsys=om.ExecComp( - 'velocity = mach * sos', - mach={'units': 'unitless', 'shape': (nn,)}, - sos={'units': 'm/s', 'shape': (nn,)}, - velocity={'units': 'm/s', 'shape': (nn,)}, - has_diag_partials=True, - ), - promotes_inputs=[('mach', Dynamic.Mission.MACH), - ('sos', Dynamic.Mission.SPEED_OF_SOUND)], - promotes_outputs=[('velocity', Dynamic.Mission.VELOCITY)], - ) - - # add execcomp to compute velocity_rate based off mach_rate and sos - self.add_subsystem( - name='velocity_rate_comp', - subsys=om.ExecComp( - 'velocity_rate = mach_rate * sos', - mach_rate={'units': 'unitless/s', 'shape': (nn,)}, - sos={'units': 'm/s', 'shape': (nn,)}, - velocity_rate={'units': 'm/s**2', 'shape': (nn,)}, - has_diag_partials=True, - ), - promotes_inputs=[('mach_rate', Dynamic.Mission.MACH_RATE), - ('sos', Dynamic.Mission.SPEED_OF_SOUND)], - promotes_outputs=[('velocity_rate', Dynamic.Mission.VELOCITY_RATE)], - ) - - base_options = {'num_nodes': nn, 'aviary_inputs': aviary_options} - - for subsystem in core_subsystems: - # check if subsystem_options has entry for a subsystem of this name - if subsystem.name in subsystem_options: - kwargs = subsystem_options[subsystem.name] - else: - kwargs = {} - - kwargs.update(base_options) - system = subsystem.build_mission(**kwargs) - - if system is not None: - self.add_subsystem(subsystem.name, - system, - promotes_inputs=subsystem.mission_inputs(**kwargs), - promotes_outputs=subsystem.mission_outputs(**kwargs)) - - # Create a lightly modified version of an OM group to add external subsystems - # to the ODE with a special configure() method that promotes - # all aircraft:* and mission:* variables to the ODE. - external_subsystem_group = ExternalSubsystemGroup() - add_subsystem_group = False - - for subsystem in self.options['external_subsystems']: - subsystem_mission = subsystem.build_mission( - num_nodes=nn, aviary_inputs=aviary_options) - if subsystem_mission is not None: - add_subsystem_group = True - external_subsystem_group.add_subsystem(subsystem.name, subsystem_mission) - - # Only add the external subsystem group if it has at least one subsystem. - # Without this logic there'd be an empty OM group added to the ODE. - if add_subsystem_group: - self.add_subsystem( - name='external_subsystems', - subsys=external_subsystem_group, - promotes_inputs=['*'], - promotes_outputs=['*']) - - self.add_subsystem( - name='mission_EOM', - subsys=MissionEOM(num_nodes=nn), - promotes_inputs=[ - Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, - Dynamic.Mission.THRUST_MAX_TOTAL, - Dynamic.Mission.DRAG, Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.VELOCITY_RATE], - promotes_outputs=[ - Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS, - Dynamic.Mission.ALTITUDE_RATE_MAX, - Dynamic.Mission.RANGE_RATE, - 'T_required', - ]) - - # add a balance comp to compute throttle based on the altitude rate - self.add_subsystem(name='throttle_balance', - subsys=om.BalanceComp(name=Dynamic.Mission.THROTTLE, - units="unitless", - val=np.ones(nn), - lhs_name='T_required', - rhs_name=Dynamic.Mission.THRUST_TOTAL, - eq_units="lbf", - normalize=False, - lower=0.0 if options['throttle_enforcement'] == 'bounded' else None, - upper=1.0 if options['throttle_enforcement'] == 'bounded' else None, - ), - promotes_inputs=['*'], - promotes_outputs=['*']) - - self.set_input_defaults(Dynamic.Mission.MASS, val=np.ones(nn), units='kg') - self.set_input_defaults(Dynamic.Mission.VELOCITY, val=np.ones(nn), units='m/s') - self.set_input_defaults(Dynamic.Mission.ALTITUDE, val=np.ones(nn), units='m') - self.set_input_defaults( - Dynamic.Mission.THROTTLE, val=np.ones((nn, engine_count)), - units='unitless') - - if options['use_actual_takeoff_mass']: - exec_comp_string = 'initial_mass_residual = initial_mass - mass[0]' - initial_mass_string = Mission.Takeoff.FINAL_MASS - else: - exec_comp_string = 'initial_mass_residual = initial_mass - mass[0] - 100.' - initial_mass_string = Mission.Design.GROSS_MASS - - # Experimental: Add a component to constrain the initial mass to be equal to design gross weight. - initial_mass_residual_constraint = om.ExecComp( - exec_comp_string, - initial_mass={'units': 'kg'}, - mass={'units': 'kg', 'shape': (nn,)}, - initial_mass_residual={'units': 'kg'}, - ) - - self.add_subsystem('initial_mass_residual_constraint', initial_mass_residual_constraint, - promotes_inputs=[ - ('initial_mass', initial_mass_string), - ('mass', Dynamic.Mission.MASS) - ], - promotes_outputs=['initial_mass_residual']) - - self.nonlinear_solver = om.NewtonSolver(solve_subsystems=True, - atol=1.0e-10, - rtol=1.0e-10, - ) - self.nonlinear_solver.linesearch = om.BoundsEnforceLS() - self.linear_solver = om.DirectSolver(assemble_jac=True) - self.nonlinear_solver.options['err_on_non_converge'] = True - self.nonlinear_solver.options['iprint'] = 2 diff --git a/aviary/mission/flops_based/ode/takeoff_eom.py b/aviary/mission/flops_based/ode/takeoff_eom.py index b50127327..058254c1f 100644 --- a/aviary/mission/flops_based/ode/takeoff_eom.py +++ b/aviary/mission/flops_based/ode/takeoff_eom.py @@ -8,10 +8,7 @@ from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.functions import add_aviary_input, add_aviary_output -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Dynamic, Mission class StallSpeed(om.ExplicitComponent): @@ -131,8 +128,8 @@ def setup(self): 'climbing': climbing } - inputs = [Dynamic.FLIGHT_PATH_ANGLE, Dynamic.VELOCITY] - outputs = [Dynamic.RANGE_RATE, Dynamic.ALTITUDE_RATE] + inputs = [Dynamic.Mission.FLIGHT_PATH_ANGLE, Dynamic.Mission.VELOCITY] + outputs = [Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.ALTITUDE_RATE] self.add_subsystem( 'distance_rates', DistanceRates(**kwargs), @@ -197,11 +194,14 @@ def setup(self): nn = options['num_nodes'] - add_aviary_input(self, Dynamic.FLIGHT_PATH_ANGLE, val=np.zeros(nn), units='rad') - add_aviary_input(self, Dynamic.VELOCITY, val=np.zeros(nn), units='m/s') + add_aviary_input(self, Dynamic.Mission.FLIGHT_PATH_ANGLE, + val=np.zeros(nn), units='rad') + add_aviary_input(self, Dynamic.Mission.VELOCITY, val=np.zeros(nn), units='m/s') - add_aviary_output(self, Dynamic.RANGE_RATE, val=np.zeros(nn), units='m/s') - add_aviary_output(self, Dynamic.ALTITUDE_RATE, val=np.zeros(nn), units='m/s') + add_aviary_output(self, Dynamic.Mission.DISTANCE_RATE, + val=np.zeros(nn), units='m/s') + add_aviary_output(self, Dynamic.Mission.ALTITUDE_RATE, + val=np.zeros(nn), units='m/s') def setup_partials(self): options = self.options @@ -215,18 +215,18 @@ def setup_partials(self): else: self.declare_partials( - Dynamic.RANGE_RATE, Dynamic.FLIGHT_PATH_ANGLE, dependent=False) + Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE, dependent=False) self.declare_partials( - Dynamic.RANGE_RATE, Dynamic.VELOCITY, val=np.identity(nn)) + Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.VELOCITY, val=np.identity(nn)) - self.declare_partials(Dynamic.ALTITUDE_RATE, '*', dependent=False) + self.declare_partials(Dynamic.Mission.ALTITUDE_RATE, '*', dependent=False) def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): - velocity = inputs[Dynamic.VELOCITY] + velocity = inputs[Dynamic.Mission.VELOCITY] if self.options['climbing']: - flight_path_angle = inputs[Dynamic.FLIGHT_PATH_ANGLE] + flight_path_angle = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] cgam = np.cos(flight_path_angle) range_rate = cgam * velocity @@ -234,26 +234,26 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): sgam = np.sin(flight_path_angle) altitude_rate = sgam * velocity - outputs[Dynamic.ALTITUDE_RATE] = altitude_rate + outputs[Dynamic.Mission.ALTITUDE_RATE] = altitude_rate else: range_rate = velocity - outputs[Dynamic.RANGE_RATE] = range_rate + outputs[Dynamic.Mission.DISTANCE_RATE] = range_rate def compute_partials(self, inputs, J, discrete_inputs=None): if self.options['climbing']: - flight_path_angle = inputs[Dynamic.FLIGHT_PATH_ANGLE] - velocity = inputs[Dynamic.VELOCITY] + flight_path_angle = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] + velocity = inputs[Dynamic.Mission.VELOCITY] cgam = np.cos(flight_path_angle) sgam = np.sin(flight_path_angle) - J[Dynamic.RANGE_RATE, Dynamic.FLIGHT_PATH_ANGLE] = -sgam * velocity - J[Dynamic.RANGE_RATE, Dynamic.VELOCITY] = cgam + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE] = -sgam * velocity + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.VELOCITY] = cgam - J[Dynamic.ALTITUDE_RATE, Dynamic.FLIGHT_PATH_ANGLE] = cgam * velocity - J[Dynamic.ALTITUDE_RATE, Dynamic.VELOCITY] = sgam + J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE] = cgam * velocity + J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.VELOCITY] = sgam class Accelerations(om.ExplicitComponent): @@ -269,7 +269,7 @@ def initialize(self): def setup(self): nn = self.options['num_nodes'] - add_aviary_input(self, Dynamic.MASS, val=np.ones(nn), units='kg') + add_aviary_input(self, Dynamic.Mission.MASS, val=np.ones(nn), units='kg') self.add_input( 'forces_horizontal', val=np.zeros(nn), units='N', @@ -293,10 +293,10 @@ def setup_partials(self): rows_cols = np.arange(nn) self.declare_partials( - 'acceleration_horizontal', Dynamic.MASS, rows=rows_cols, cols=rows_cols) + 'acceleration_horizontal', Dynamic.Mission.MASS, rows=rows_cols, cols=rows_cols) self.declare_partials( - 'acceleration_vertical', Dynamic.MASS, rows=rows_cols, cols=rows_cols) + 'acceleration_vertical', Dynamic.Mission.MASS, rows=rows_cols, cols=rows_cols) self.declare_partials( 'acceleration_horizontal', 'forces_horizontal', rows=rows_cols, @@ -312,7 +312,7 @@ def setup_partials(self): 'acceleration_vertical', 'forces_vertical', rows=rows_cols, cols=rows_cols) def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): - mass = inputs[Dynamic.MASS] + mass = inputs[Dynamic.Mission.MASS] f_h = inputs['forces_horizontal'] f_v = inputs['forces_vertical'] @@ -323,14 +323,14 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): outputs['acceleration_vertical'] = a_v def compute_partials(self, inputs, J, discrete_inputs=None): - mass = inputs[Dynamic.MASS] + mass = inputs[Dynamic.Mission.MASS] f_h = inputs['forces_horizontal'] f_v = inputs['forces_vertical'] m2 = mass * mass - J['acceleration_horizontal', Dynamic.MASS] = -f_h / m2 - J['acceleration_vertical', Dynamic.MASS] = -f_v / m2 + J['acceleration_horizontal', Dynamic.Mission.MASS] = -f_h / m2 + J['acceleration_vertical', Dynamic.Mission.MASS] = -f_v / m2 J['acceleration_horizontal', 'forces_horizontal'] = 1. / mass @@ -358,10 +358,13 @@ def setup(self): 'acceleration_vertical', val=np.zeros(nn), desc='current vertical acceleration', units='m/s**2') - add_aviary_input(self, Dynamic.RANGE_RATE, val=np.zeros(nn), units='m/s') - add_aviary_input(self, Dynamic.ALTITUDE_RATE, val=np.zeros(nn), units='m/s') + add_aviary_input(self, Dynamic.Mission.DISTANCE_RATE, + val=np.zeros(nn), units='m/s') + add_aviary_input(self, Dynamic.Mission.ALTITUDE_RATE, + val=np.zeros(nn), units='m/s') - add_aviary_output(self, Dynamic.VELOCITY_RATE, val=np.ones(nn), units='m/s**2') + add_aviary_output(self, Dynamic.Mission.VELOCITY_RATE, + val=np.ones(nn), units='m/s**2') rows_cols = np.arange(nn) @@ -370,30 +373,30 @@ def setup(self): def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): a_h = inputs['acceleration_horizontal'] a_v = inputs['acceleration_vertical'] - v_h = inputs[Dynamic.RANGE_RATE] - v_v = inputs[Dynamic.ALTITUDE_RATE] + v_h = inputs[Dynamic.Mission.DISTANCE_RATE] + v_v = inputs[Dynamic.Mission.ALTITUDE_RATE] v_mag = np.sqrt(v_h**2 + v_v**2) - outputs[Dynamic.VELOCITY_RATE] = (a_h * v_h + a_v * v_v) / v_mag + outputs[Dynamic.Mission.VELOCITY_RATE] = (a_h * v_h + a_v * v_v) / v_mag def compute_partials(self, inputs, J, discrete_inputs=None): a_h = inputs['acceleration_horizontal'] a_v = inputs['acceleration_vertical'] - v_h = inputs[Dynamic.RANGE_RATE] - v_v = inputs[Dynamic.ALTITUDE_RATE] + v_h = inputs[Dynamic.Mission.DISTANCE_RATE] + v_v = inputs[Dynamic.Mission.ALTITUDE_RATE] num = (a_h * v_h + a_v * v_v) fact = v_h**2 + v_v**2 den = np.sqrt(fact) - J[Dynamic.VELOCITY_RATE, 'acceleration_horizontal'] = v_h / den - J[Dynamic.VELOCITY_RATE, 'acceleration_vertical'] = v_v / den + J[Dynamic.Mission.VELOCITY_RATE, 'acceleration_horizontal'] = v_h / den + J[Dynamic.Mission.VELOCITY_RATE, 'acceleration_vertical'] = v_v / den - J[Dynamic.VELOCITY_RATE, - Dynamic.RANGE_RATE] = a_h / den - 0.5 * num / fact**(3/2) * 2.0 * v_h + J[Dynamic.Mission.VELOCITY_RATE, + Dynamic.Mission.DISTANCE_RATE] = a_h / den - 0.5 * num / fact**(3/2) * 2.0 * v_h - J[Dynamic.VELOCITY_RATE, - Dynamic.ALTITUDE_RATE] = a_v / den - 0.5 * num / fact**(3/2) * 2.0 * v_v + J[Dynamic.Mission.VELOCITY_RATE, + Dynamic.Mission.ALTITUDE_RATE] = a_v / den - 0.5 * num / fact**(3/2) * 2.0 * v_v class FlightPathAngleRate(om.ExplicitComponent): @@ -409,8 +412,10 @@ def initialize(self): def setup(self): nn = self.options['num_nodes'] - add_aviary_input(self, Dynamic.RANGE_RATE, val=np.zeros(nn), units='m/s') - add_aviary_input(self, Dynamic.ALTITUDE_RATE, val=np.zeros(nn), units='m/s') + add_aviary_input(self, Dynamic.Mission.DISTANCE_RATE, + val=np.zeros(nn), units='m/s') + add_aviary_input(self, Dynamic.Mission.ALTITUDE_RATE, + val=np.zeros(nn), units='m/s') self.add_input( 'acceleration_horizontal', val=np.zeros(nn), @@ -425,25 +430,25 @@ def setup(self): ) add_aviary_output( - self, Dynamic.FLIGHT_PATH_ANGLE_RATE, val=np.zeros(nn), units='rad/s') + self, Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, val=np.zeros(nn), units='rad/s') rows_cols = np.arange(nn) self.declare_partials('*', '*', rows=rows_cols, cols=rows_cols) def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): - v_h = inputs[Dynamic.RANGE_RATE] - v_v = inputs[Dynamic.ALTITUDE_RATE] + v_h = inputs[Dynamic.Mission.DISTANCE_RATE] + v_v = inputs[Dynamic.Mission.ALTITUDE_RATE] a_h = inputs['acceleration_horizontal'] a_v = inputs['acceleration_vertical'] x = (a_v * v_h - a_h * v_v) / (v_h**2 + v_v**2) - outputs[Dynamic.FLIGHT_PATH_ANGLE_RATE] = x + outputs[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE] = x def compute_partials(self, inputs, J, discrete_inputs=None): - v_h = inputs[Dynamic.RANGE_RATE] - v_v = inputs[Dynamic.ALTITUDE_RATE] + v_h = inputs[Dynamic.Mission.DISTANCE_RATE] + v_v = inputs[Dynamic.Mission.ALTITUDE_RATE] a_h = inputs['acceleration_horizontal'] a_v = inputs['acceleration_vertical'] @@ -458,10 +463,10 @@ def compute_partials(self, inputs, J, discrete_inputs=None): df_dav = v_h / den - J[Dynamic.FLIGHT_PATH_ANGLE_RATE, Dynamic.RANGE_RATE] = df_dvh - J[Dynamic.FLIGHT_PATH_ANGLE_RATE, Dynamic.ALTITUDE_RATE] = df_dvv - J[Dynamic.FLIGHT_PATH_ANGLE_RATE, 'acceleration_horizontal'] = df_dah - J[Dynamic.FLIGHT_PATH_ANGLE_RATE, 'acceleration_vertical'] = df_dav + J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, Dynamic.Mission.DISTANCE_RATE] = df_dvh + J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, Dynamic.Mission.ALTITUDE_RATE] = df_dvv + J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, 'acceleration_horizontal'] = df_dah + J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, 'acceleration_vertical'] = df_dav class SumForces(om.ExplicitComponent): @@ -494,14 +499,15 @@ def setup(self): nn = options['num_nodes'] - add_aviary_input(self, Dynamic.MASS, val=np.ones(nn), units='kg') - add_aviary_input(self, Dynamic.LIFT, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.THRUST_TOTAL, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.DRAG, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.MASS, val=np.ones(nn), units='kg') + add_aviary_input(self, Dynamic.Mission.LIFT, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.THRUST_TOTAL, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.DRAG, val=np.ones(nn), units='N') self.add_input('angle_of_attack', val=np.zeros(nn), units='rad') - add_aviary_input(self, Dynamic.FLIGHT_PATH_ANGLE, val=np.zeros(nn), units='rad') + add_aviary_input(self, Dynamic.Mission.FLIGHT_PATH_ANGLE, + val=np.zeros(nn), units='rad') self.add_output( 'forces_horizontal', val=np.zeros(nn), units='N', @@ -520,15 +526,16 @@ def setup_partials(self): rows_cols = np.arange(nn) if climbing: - self.declare_partials('forces_horizontal', Dynamic.MASS, dependent=False) + self.declare_partials('forces_horizontal', + Dynamic.Mission.MASS, dependent=False) self.declare_partials( - 'forces_vertical', Dynamic.MASS, val=-grav_metric, rows=rows_cols, + 'forces_vertical', Dynamic.Mission.MASS, val=-grav_metric, rows=rows_cols, cols=rows_cols) wrt = [ - Dynamic.THRUST_TOTAL, Dynamic.LIFT, Dynamic.DRAG, 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE] + Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.LIFT, Dynamic.Mission.DRAG, 'angle_of_attack', + Dynamic.Mission.FLIGHT_PATH_ANGLE] self.declare_partials('*', wrt, rows=rows_cols, cols=rows_cols) @@ -539,26 +546,27 @@ def setup_partials(self): val = -grav_metric * mu self.declare_partials( - 'forces_horizontal', Dynamic.MASS, val=val, rows=rows_cols, + 'forces_horizontal', Dynamic.Mission.MASS, val=val, rows=rows_cols, cols=rows_cols) self.declare_partials( - 'forces_horizontal', Dynamic.LIFT, val=mu, rows=rows_cols, + 'forces_horizontal', Dynamic.Mission.LIFT, val=mu, rows=rows_cols, cols=rows_cols) t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') val = np.cos(t_inc) + np.sin(t_inc) * mu self.declare_partials( - 'forces_horizontal', Dynamic.THRUST_TOTAL, val=val, rows=rows_cols, + 'forces_horizontal', Dynamic.Mission.THRUST_TOTAL, val=val, rows=rows_cols, cols=rows_cols) self.declare_partials( - 'forces_horizontal', Dynamic.DRAG, val=-1., rows=rows_cols, + 'forces_horizontal', Dynamic.Mission.DRAG, val=-1., rows=rows_cols, cols=rows_cols) self.declare_partials( - 'forces_horizontal', ['angle_of_attack', Dynamic.FLIGHT_PATH_ANGLE], + 'forces_horizontal', ['angle_of_attack', + Dynamic.Mission.FLIGHT_PATH_ANGLE], dependent=False) self.declare_partials('forces_vertical', ['*'], dependent=False) @@ -571,10 +579,10 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] - thrust = inputs[Dynamic.THRUST_TOTAL] - drag = inputs[Dynamic.DRAG] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] + thrust = inputs[Dynamic.Mission.THRUST_TOTAL] + drag = inputs[Dynamic.Mission.DRAG] weight = mass * grav_metric @@ -587,7 +595,7 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): aviary_options.get_val(Mission.Takeoff.ANGLE_OF_ATTACK_RUNWAY, 'rad') alpha = inputs['angle_of_attack'] - gamma = inputs[Dynamic.FLIGHT_PATH_ANGLE] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] angle = alpha - alpha0 + t_inc + gamma @@ -631,12 +639,12 @@ def compute_partials(self, inputs, J, discrete_inputs=None): alpha0 = aviary_options.get_val(Mission.Takeoff.ANGLE_OF_ATTACK_RUNWAY, 'rad') t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') - lift = inputs[Dynamic.LIFT] - thrust = inputs[Dynamic.THRUST_TOTAL] - drag = inputs[Dynamic.DRAG] + lift = inputs[Dynamic.Mission.LIFT] + thrust = inputs[Dynamic.Mission.THRUST_TOTAL] + drag = inputs[Dynamic.Mission.DRAG] alpha = inputs['angle_of_attack'] - gamma = inputs[Dynamic.FLIGHT_PATH_ANGLE] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] angle = alpha - alpha0 + t_inc + gamma @@ -646,22 +654,22 @@ def compute_partials(self, inputs, J, discrete_inputs=None): c_gamma = np.cos(gamma) s_gamma = np.sin(gamma) - J['forces_horizontal', Dynamic.THRUST_TOTAL] = c_angle - J['forces_vertical', Dynamic.THRUST_TOTAL] = s_angle + J['forces_horizontal', Dynamic.Mission.THRUST_TOTAL] = c_angle + J['forces_vertical', Dynamic.Mission.THRUST_TOTAL] = s_angle - J['forces_horizontal', Dynamic.LIFT] = -s_gamma - J['forces_vertical', Dynamic.LIFT] = c_gamma + J['forces_horizontal', Dynamic.Mission.LIFT] = -s_gamma + J['forces_vertical', Dynamic.Mission.LIFT] = c_gamma - J['forces_horizontal', Dynamic.DRAG] = -c_gamma - J['forces_vertical', Dynamic.DRAG] = -s_gamma + J['forces_horizontal', Dynamic.Mission.DRAG] = -c_gamma + J['forces_vertical', Dynamic.Mission.DRAG] = -s_gamma J['forces_horizontal', 'angle_of_attack'] = -thrust * s_angle J['forces_vertical', 'angle_of_attack'] = thrust * c_angle - J['forces_horizontal', Dynamic.FLIGHT_PATH_ANGLE] = \ + J['forces_horizontal', Dynamic.Mission.FLIGHT_PATH_ANGLE] = \ -thrust * s_angle + drag * s_gamma - lift * c_gamma - J['forces_vertical', Dynamic.FLIGHT_PATH_ANGLE] = \ + J['forces_vertical', Dynamic.Mission.FLIGHT_PATH_ANGLE] = \ thrust * c_angle - drag * c_gamma - lift * s_gamma @@ -685,14 +693,15 @@ def setup(self): nn = options['num_nodes'] - add_aviary_input(self, Dynamic.MASS, val=np.ones(nn), units='kg') - add_aviary_input(self, Dynamic.LIFT, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.THRUST_TOTAL, val=np.ones(nn), units='N') - add_aviary_input(self, Dynamic.DRAG, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.MASS, val=np.ones(nn), units='kg') + add_aviary_input(self, Dynamic.Mission.LIFT, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.THRUST_TOTAL, val=np.ones(nn), units='N') + add_aviary_input(self, Dynamic.Mission.DRAG, val=np.ones(nn), units='N') self.add_input('angle_of_attack', val=np.zeros(nn), units='rad') - add_aviary_input(self, Dynamic.FLIGHT_PATH_ANGLE, val=np.zeros(nn), units='rad') + add_aviary_input(self, Dynamic.Mission.FLIGHT_PATH_ANGLE, + val=np.zeros(nn), units='rad') self.add_output( 'climb_gradient_forces_horizontal', val=np.zeros(nn), units='N', @@ -714,22 +723,22 @@ def setup_partials(self): self.declare_partials( '*', [ - Dynamic.MASS, Dynamic.THRUST_TOTAL, 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE], + Dynamic.Mission.MASS, Dynamic.Mission.THRUST_TOTAL, 'angle_of_attack', + Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=rows_cols, cols=rows_cols) self.declare_partials( - 'climb_gradient_forces_horizontal', Dynamic.DRAG, val=-1., + 'climb_gradient_forces_horizontal', Dynamic.Mission.DRAG, val=-1., rows=rows_cols, cols=rows_cols) self.declare_partials( - 'climb_gradient_forces_vertical', Dynamic.DRAG, dependent=False) + 'climb_gradient_forces_vertical', Dynamic.Mission.DRAG, dependent=False) self.declare_partials( - 'climb_gradient_forces_horizontal', Dynamic.LIFT, dependent=False) + 'climb_gradient_forces_horizontal', Dynamic.Mission.LIFT, dependent=False) self.declare_partials( - 'climb_gradient_forces_vertical', Dynamic.LIFT, val=1., + 'climb_gradient_forces_vertical', Dynamic.Mission.LIFT, val=1., rows=rows_cols, cols=rows_cols) def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): @@ -740,15 +749,15 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): alpha0 = aviary_options.get_val(Mission.Takeoff.ANGLE_OF_ATTACK_RUNWAY, 'rad') t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] - thrust = inputs[Dynamic.THRUST_TOTAL] - drag = inputs[Dynamic.DRAG] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] + thrust = inputs[Dynamic.Mission.THRUST_TOTAL] + drag = inputs[Dynamic.Mission.DRAG] weight = mass * grav_metric alpha = inputs['angle_of_attack'] - gamma = inputs[Dynamic.FLIGHT_PATH_ANGLE] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] angle = alpha - alpha0 + t_inc @@ -774,15 +783,15 @@ def compute_partials(self, inputs, J, discrete_inputs=None): alpha0 = aviary_options.get_val(Mission.Takeoff.ANGLE_OF_ATTACK_RUNWAY, 'rad') t_inc = aviary_options.get_val(Mission.Takeoff.THRUST_INCIDENCE, 'rad') - mass = inputs[Dynamic.MASS] - lift = inputs[Dynamic.LIFT] - thrust = inputs[Dynamic.THRUST_TOTAL] - drag = inputs[Dynamic.DRAG] + mass = inputs[Dynamic.Mission.MASS] + lift = inputs[Dynamic.Mission.LIFT] + thrust = inputs[Dynamic.Mission.THRUST_TOTAL] + drag = inputs[Dynamic.Mission.DRAG] weight = mass * grav_metric alpha = inputs['angle_of_attack'] - gamma = inputs[Dynamic.FLIGHT_PATH_ANGLE] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] angle = alpha - alpha0 + t_inc @@ -795,14 +804,14 @@ def compute_partials(self, inputs, J, discrete_inputs=None): f_h_key = 'climb_gradient_forces_horizontal' f_v_key = 'climb_gradient_forces_vertical' - J[f_h_key, Dynamic.MASS] = -grav_metric * s_gamma - J[f_v_key, Dynamic.MASS] = -grav_metric * c_gamma + J[f_h_key, Dynamic.Mission.MASS] = -grav_metric * s_gamma + J[f_v_key, Dynamic.Mission.MASS] = -grav_metric * c_gamma - J[f_h_key, Dynamic.THRUST_TOTAL] = c_angle - J[f_v_key, Dynamic.THRUST_TOTAL] = s_angle + J[f_h_key, Dynamic.Mission.THRUST_TOTAL] = c_angle + J[f_v_key, Dynamic.Mission.THRUST_TOTAL] = s_angle J[f_h_key, 'angle_of_attack'] = -thrust * s_angle J[f_v_key, 'angle_of_attack'] = thrust * c_angle - J[f_h_key, Dynamic.FLIGHT_PATH_ANGLE] = -weight * c_gamma - J[f_v_key, Dynamic.FLIGHT_PATH_ANGLE] = weight * s_gamma + J[f_h_key, Dynamic.Mission.FLIGHT_PATH_ANGLE] = -weight * c_gamma + J[f_v_key, Dynamic.Mission.FLIGHT_PATH_ANGLE] = weight * s_gamma diff --git a/aviary/mission/flops_based/ode/takeoff_ode.py b/aviary/mission/flops_based/ode/takeoff_ode.py index 73accaa91..e8304e181 100644 --- a/aviary/mission/flops_based/ode/takeoff_ode.py +++ b/aviary/mission/flops_based/ode/takeoff_ode.py @@ -9,13 +9,9 @@ from aviary.mission.gasp_based.flight_conditions import FlightConditions from aviary.utils.aviary_values import AviaryValues from aviary.utils.functions import set_aviary_initial_values, promote_aircraft_and_mission_vars -from aviary.variable_info.variables import Aircraft -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission +from aviary.variable_info.variables import Aircraft, Dynamic, Mission from aviary.variable_info.variables_in import VariablesIn -Dynamic = _Dynamic.Mission - class ExternalSubsystemGroup(om.Group): def configure(self): @@ -77,16 +73,18 @@ def setup(self): self.add_subsystem( "USatm", USatm1976Comp(num_nodes=nn), - promotes_inputs=[("h", Dynamic.ALTITUDE)], + promotes_inputs=[("h", Dynamic.Mission.ALTITUDE)], promotes_outputs=[ - "rho", ("sos", Dynamic.SPEED_OF_SOUND), ("temp", Dynamic.TEMPERATURE), - ("pres", Dynamic.STATIC_PRESSURE), "viscosity"]) + "rho", ("sos", Dynamic.Mission.SPEED_OF_SOUND), ("temp", + Dynamic.Mission.TEMPERATURE), + ("pres", Dynamic.Mission.STATIC_PRESSURE), "viscosity"]) self.add_subsystem( "fc", FlightConditions(num_nodes=nn), - promotes_inputs=["rho", Dynamic.SPEED_OF_SOUND, ("TAS", Dynamic.VELOCITY)], - promotes_outputs=[Dynamic.DYNAMIC_PRESSURE, Dynamic.MACH, "EAS"]) + promotes_inputs=["rho", Dynamic.Mission.SPEED_OF_SOUND, + ("TAS", Dynamic.Mission.VELOCITY)], + promotes_outputs=[Dynamic.Mission.DYNAMIC_PRESSURE, Dynamic.Mission.MACH, "EAS"]) # NOTE: the following are potentially signficant differences in implementation # between FLOPS and Aviary: @@ -154,11 +152,11 @@ def setup(self): self.add_subsystem( 'eoms', TakeoffEOM(**kwargs), promotes_inputs=[ - Dynamic.FLIGHT_PATH_ANGLE, Dynamic.VELOCITY, Dynamic.MASS, Dynamic.LIFT, - Dynamic.THRUST_TOTAL, Dynamic.DRAG, 'angle_of_attack'], + Dynamic.Mission.FLIGHT_PATH_ANGLE, Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.LIFT, + Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.DRAG, 'angle_of_attack'], promotes_outputs=[ - Dynamic.RANGE_RATE, Dynamic.ALTITUDE_RATE, Dynamic.VELOCITY_RATE, - Dynamic.FLIGHT_PATH_ANGLE_RATE]) + Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.VELOCITY_RATE, + Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE]) self.add_subsystem( 'comp_v_ratio', @@ -168,11 +166,11 @@ def setup(self): v={'units': 'm/s', 'shape': nn}, # NOTE: FLOPS detailed takeoff stall speed is not dynamic - see above v_stall={'units': 'm/s', 'shape': nn}), - promotes_inputs=[('v', Dynamic.VELOCITY), 'v_stall'], + promotes_inputs=[('v', Dynamic.Mission.VELOCITY), 'v_stall'], promotes_outputs=['v_over_v_stall']) - self.set_input_defaults(Dynamic.ALTITUDE, np.zeros(nn), 'm') - self.set_input_defaults(Dynamic.VELOCITY, np.zeros(nn), 'm/s') + self.set_input_defaults(Dynamic.Mission.ALTITUDE, np.zeros(nn), 'm') + self.set_input_defaults(Dynamic.Mission.VELOCITY, np.zeros(nn), 'm/s') self.set_input_defaults(Mission.Design.GROSS_MASS, val=1.0, units='kg') set_aviary_initial_values(self, aviary_options) diff --git a/aviary/mission/flops_based/ode/test/test_landing_eom.py b/aviary/mission/flops_based/ode/test/test_landing_eom.py index 0afa883eb..ef4903f4e 100644 --- a/aviary/mission/flops_based/ode/test/test_landing_eom.py +++ b/aviary/mission/flops_based/ode/test/test_landing_eom.py @@ -6,9 +6,7 @@ from aviary.models.N3CC.N3CC_data import ( detailed_landing_flare, inputs) from aviary.validation_cases.validation_tests import do_validation_test -from aviary.variable_info.variables import Dynamic as _Dynamic - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Dynamic class FlareEOMTest(unittest.TestCase): @@ -34,15 +32,15 @@ def test_case(self): output_validation_data=detailed_landing_flare, input_keys=[ 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE, - Dynamic.VELOCITY, - Dynamic.MASS, - Dynamic.LIFT, - Dynamic.THRUST_TOTAL, - Dynamic.DRAG], + Dynamic.Mission.FLIGHT_PATH_ANGLE, + Dynamic.Mission.VELOCITY, + Dynamic.Mission.MASS, + Dynamic.Mission.LIFT, + Dynamic.Mission.THRUST_TOTAL, + Dynamic.Mission.DRAG], output_keys=[ - Dynamic.RANGE_RATE, - Dynamic.ALTITUDE_RATE], + Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.ALTITUDE_RATE], tol=1e-2, atol=1e-8, rtol=5e-10) diff --git a/aviary/mission/flops_based/ode/test/test_landing_ode.py b/aviary/mission/flops_based/ode/test/test_landing_ode.py index 16e34c0f2..526fbc9cf 100644 --- a/aviary/mission/flops_based/ode/test/test_landing_ode.py +++ b/aviary/mission/flops_based/ode/test/test_landing_ode.py @@ -7,9 +7,7 @@ from aviary.models.N3CC.N3CC_data import ( detailed_landing_flare, inputs, landing_subsystem_options) from aviary.validation_cases.validation_tests import do_validation_test -from aviary.variable_info.variables import Dynamic as _Dynamic - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Dynamic class FlareODETest(unittest.TestCase): @@ -39,15 +37,15 @@ def test_case(self): output_validation_data=detailed_landing_flare, input_keys=[ 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE, - Dynamic.VELOCITY, - Dynamic.MASS, - Dynamic.LIFT, - Dynamic.THRUST_TOTAL, - Dynamic.DRAG], + Dynamic.Mission.FLIGHT_PATH_ANGLE, + Dynamic.Mission.VELOCITY, + Dynamic.Mission.MASS, + Dynamic.Mission.LIFT, + Dynamic.Mission.THRUST_TOTAL, + Dynamic.Mission.DRAG], output_keys=[ - Dynamic.RANGE_RATE, - Dynamic.ALTITUDE_RATE], + Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.ALTITUDE_RATE], tol=1e-2, atol=5e-9, rtol=5e-9, check_values=False, check_partials=True) diff --git a/aviary/mission/flops_based/ode/test/test_mission_EOM.py b/aviary/mission/flops_based/ode/test/test_mission_EOM.py deleted file mode 100644 index c2210d1f1..000000000 --- a/aviary/mission/flops_based/ode/test/test_mission_EOM.py +++ /dev/null @@ -1,55 +0,0 @@ -import unittest - -import openmdao.api as om - -from aviary.mission.flops_based.ode.mission_EOM import MissionEOM -from aviary.utils.test_utils.variable_test import assert_match_varnames -from aviary.validation_cases.validation_data.flops_data.full_mission_test_data import \ - data -from aviary.validation_cases.validation_tests import do_validation_test -from aviary.variable_info.variables import Dynamic - - -class MissionEOMTest(unittest.TestCase): - def setUp(self): - prob = self.prob = om.Problem() - - time, _ = data.get_item('time') - - prob.model.add_subsystem( - "mission_EOM", - MissionEOM(num_nodes=len(time)), - promotes_inputs=['*'], - promotes_outputs=['*'], - ) - - prob.setup(check=False, force_alloc_complex=True) - - def test_case1(self): - - do_validation_test(self.prob, - 'full_mission_test_data', - input_validation_data=data, - output_validation_data=data, - input_keys=[Dynamic.Mission.DRAG, - Dynamic.Mission.MASS, - Dynamic.Mission.THRUST_TOTAL, - Dynamic.Mission.THRUST_MAX_TOTAL, - Dynamic.Mission.VELOCITY, - Dynamic.Mission.VELOCITY_RATE], - output_keys=[Dynamic.Mission.ALTITUDE_RATE, - # TODO: why does altitude_rate_max fail for cruise? - # - actual: 760.55416759 - # - desired: 3.86361517135375 - # Dynamic.Mission.ALTITUDE_RATE_MAX, - Dynamic.Mission.RANGE_RATE, - Dynamic.Mission.SPECIFIC_ENERGY_RATE, - Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS], - tol=1e-12) - - def test_IO(self): - assert_match_varnames(self.prob.model) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/flops_based/ode/test/test_range_rate.py b/aviary/mission/flops_based/ode/test/test_range_rate.py index 7809e849e..e371e2212 100644 --- a/aviary/mission/flops_based/ode/test/test_range_rate.py +++ b/aviary/mission/flops_based/ode/test/test_range_rate.py @@ -17,7 +17,7 @@ def setUp(self): time, _ = data.get_item('time') prob.model.add_subsystem( - Dynamic.Mission.RANGE_RATE, + Dynamic.Mission.DISTANCE_RATE, RangeRate(num_nodes=len(time)), promotes_inputs=['*'], promotes_outputs=['*'], @@ -33,7 +33,7 @@ def test_case1(self): output_validation_data=data, input_keys=[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.VELOCITY], - output_keys=Dynamic.Mission.RANGE_RATE, + output_keys=Dynamic.Mission.DISTANCE_RATE, tol=1e-12) def test_IO(self): diff --git a/aviary/mission/flops_based/ode/test/test_takeoff_eom.py b/aviary/mission/flops_based/ode/test/test_takeoff_eom.py index 6494143ba..f92e04771 100644 --- a/aviary/mission/flops_based/ode/test/test_takeoff_eom.py +++ b/aviary/mission/flops_based/ode/test/test_takeoff_eom.py @@ -6,10 +6,7 @@ from aviary.models.N3CC.N3CC_data import ( detailed_takeoff_climbing, detailed_takeoff_ground, inputs) from aviary.validation_cases.validation_tests import do_validation_test -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Dynamic, Mission class TakeoffEOMTest(unittest.TestCase): @@ -23,16 +20,16 @@ def test_case_ground(self): output_validation_data=detailed_takeoff_ground, input_keys=[ 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE, - Dynamic.VELOCITY, - Dynamic.MASS, - Dynamic.LIFT, - Dynamic.THRUST_TOTAL, - Dynamic.DRAG], + Dynamic.Mission.FLIGHT_PATH_ANGLE, + Dynamic.Mission.VELOCITY, + Dynamic.Mission.MASS, + Dynamic.Mission.LIFT, + Dynamic.Mission.THRUST_TOTAL, + Dynamic.Mission.DRAG], output_keys=[ - Dynamic.RANGE_RATE, - Dynamic.ALTITUDE_RATE, - Dynamic.VELOCITY_RATE], + Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.VELOCITY_RATE], tol=1e-2) def test_case_climbing(self): @@ -45,16 +42,16 @@ def test_case_climbing(self): output_validation_data=detailed_takeoff_climbing, input_keys=[ 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE, - Dynamic.VELOCITY, - Dynamic.MASS, - Dynamic.LIFT, - Dynamic.THRUST_TOTAL, - Dynamic.DRAG], + Dynamic.Mission.FLIGHT_PATH_ANGLE, + Dynamic.Mission.VELOCITY, + Dynamic.Mission.MASS, + Dynamic.Mission.LIFT, + Dynamic.Mission.THRUST_TOTAL, + Dynamic.Mission.DRAG], output_keys=[ - Dynamic.RANGE_RATE, - Dynamic.ALTITUDE_RATE, - Dynamic.VELOCITY_RATE], + Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.VELOCITY_RATE], tol=1e-2, atol=1e-9, rtol=1e-11) @staticmethod diff --git a/aviary/mission/flops_based/ode/test/test_takeoff_ode.py b/aviary/mission/flops_based/ode/test/test_takeoff_ode.py index fd39339e5..6ffbf6e20 100644 --- a/aviary/mission/flops_based/ode/test/test_takeoff_ode.py +++ b/aviary/mission/flops_based/ode/test/test_takeoff_ode.py @@ -8,10 +8,7 @@ from aviary.models.N3CC.N3CC_data import ( detailed_takeoff_climbing, detailed_takeoff_ground, takeoff_subsystem_options, inputs) from aviary.validation_cases.validation_tests import do_validation_test -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Dynamic, Mission takeoff_subsystem_options = deepcopy(takeoff_subsystem_options) @@ -27,17 +24,17 @@ def test_case_ground(self): output_validation_data=detailed_takeoff_ground, input_keys=[ 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE, - Dynamic.ALTITUDE, - Dynamic.VELOCITY, - Dynamic.MASS, - Dynamic.LIFT, - Dynamic.THRUST_TOTAL, - Dynamic.DRAG], + Dynamic.Mission.FLIGHT_PATH_ANGLE, + Dynamic.Mission.ALTITUDE, + Dynamic.Mission.VELOCITY, + Dynamic.Mission.MASS, + Dynamic.Mission.LIFT, + Dynamic.Mission.THRUST_TOTAL, + Dynamic.Mission.DRAG], output_keys=[ - Dynamic.RANGE_RATE, - Dynamic.ALTITUDE_RATE, - Dynamic.VELOCITY_RATE], + Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.VELOCITY_RATE], tol=1e-2, atol=1e-9, rtol=1e-11, check_values=False, check_partials=True) @@ -51,17 +48,17 @@ def test_case_climbing(self): output_validation_data=detailed_takeoff_climbing, input_keys=[ 'angle_of_attack', - Dynamic.FLIGHT_PATH_ANGLE, - Dynamic.ALTITUDE, - Dynamic.VELOCITY, - Dynamic.MASS, - Dynamic.LIFT, - Dynamic.THRUST_TOTAL, - Dynamic.DRAG], + Dynamic.Mission.FLIGHT_PATH_ANGLE, + Dynamic.Mission.ALTITUDE, + Dynamic.Mission.VELOCITY, + Dynamic.Mission.MASS, + Dynamic.Mission.LIFT, + Dynamic.Mission.THRUST_TOTAL, + Dynamic.Mission.DRAG], output_keys=[ - Dynamic.RANGE_RATE, - Dynamic.ALTITUDE_RATE, - Dynamic.VELOCITY_RATE], + Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.VELOCITY_RATE], tol=1e-2, atol=1e-9, rtol=1e-11, check_values=False, check_partials=True) diff --git a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_climb_large_single_aisle_1.py b/aviary/mission/flops_based/phases/benchmark_tests/test_bench_climb_large_single_aisle_1.py deleted file mode 100644 index 002760085..000000000 --- a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_climb_large_single_aisle_1.py +++ /dev/null @@ -1,260 +0,0 @@ -''' -NOTES: -Includes: -Climb -Computed Aero -Large Single Aisle 1 data -CURRENTLY REMOVED FROM BENCHMARK TEST SUITE -''' - -import unittest - -import dymos as dm -# Suppress the annoying warnings from matplotlib when running dymos. -import matplotlib as mpl -import numpy as np -import openmdao.api as om -import scipy.constants as _units -from dymos.utils.testing_utils import assert_timeseries_near_equal -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.flops_based.ode.mission_ODE import MissionODE -from aviary.mission.flops_based.phases.climb_phase import Climb -from aviary.interface.default_phase_info.height_energy import prop, aero, geom -from aviary.subsystems.premission import CorePreMission -from aviary.utils.functions import set_aviary_initial_values -from aviary.validation_cases.validation_tests import get_flops_inputs -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Dynamic - -mpl.rc('figure', max_open_warning=0) - -try: - import pyoptsparse -except ImportError: - pyoptsparse = None - - -''' -NOTE benchmark currently hits iteration limit - problem only converges when velocity ref & ref0 in energyphase is changed to 1e3. - Changing velocity ref breaks some full-mission benchmarks, so is currently not - implemented -''' -# benchmark based on Large Single Aisle 1 (fixed cruise alt) FLOPS model - - -def run_trajectory(): - prob = om.Problem(model=om.Group()) - - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 50 - driver.opt_settings["Major optimality tolerance"] = 1e-5 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-3 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - # TODO enable coloring once issue has been resolved: - # https://github.com/OpenMDAO/OpenMDAO/issues/2507 - # driver.declare_coloring() - - ########################## - # Problem Settings # - ########################## - alt_i_climb = 0*_units.foot # m - alt_f_climb = 35000.0*_units.foot # m - mass_i_climb = 180623*_units.lb # kg - mass_f_climb = 176765*_units.lb # kg - v_i_climb = 198.44*_units.knot # m/s - v_f_climb = 455.49*_units.knot # m/s - mach_i_climb = 0.3 - mach_f_climb = 0.79 - range_i_climb = 0*_units.nautical_mile # m - range_f_climb = 160.3*_units.nautical_mile # m - t_i_climb = 2 * _units.minute # sec - t_f_climb = 26.20*_units.minute # sec - t_duration_climb = t_f_climb - t_i_climb - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle1FLOPS') - - climb_options = Climb( - 'test_climb', - initial_altitude=alt_i_climb, - final_altitude=alt_f_climb, - no_descent=False, - # note, Mach = 0.0 causes an error in aero, perhaps in other code - initial_mach=mach_i_climb, - final_mach=mach_f_climb, - fix_initial=True, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - aviary_options=aviary_inputs, - ) - - # replace debug_no_mass flag with list of subsystem builders without mass - core_subsystems = [prop, geom, aero] - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'core_premission', - CorePreMission(aviary_options=aviary_inputs, subsystems=core_subsystems), - promotes_inputs=['aircraft:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - transcription = dm.Radau(num_segments=6, order=3, compressed=False) - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - climb = climb_options.build_phase(MissionODE, transcription) - - traj.add_phase('climb', climb) - - climb.add_objective('time', loc='final', ref=1e3) - # climb.add_objective(Dynamic.Mission.MASS, loc='final', ref=-1e4) - - climb.set_time_options( - fix_initial=True, fix_duration=False, units='s', - duration_bounds=(1, t_duration_climb*2), duration_ref=t_duration_climb) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - - prob.setup() - - prob.set_val('traj.climb.t_initial', t_i_climb, units='s') - prob.set_val('traj.climb.t_duration', t_duration_climb, units='s') - - prob.set_val('traj.climb.states:altitude', climb.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m') - prob.set_val('traj.climb.states:velocity', climb.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_climb, v_f_climb]), units='m/s') - prob.set_val('traj.climb.states:mass', climb.interp( - Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg') - prob.set_val('traj.climb.states:range', climb.interp( - Dynamic.Mission.RANGE, ys=[range_i_climb, range_f_climb]), units='m') - - prob.set_val('traj.climb.controls:velocity_rate', - climb.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.25, 0.05]), - units='m/s**2') - prob.set_val('traj.climb.controls:throttle', - climb.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=False, make_plots=False, - solution_record_file='climb_max.db') - prob.record("final") - prob.cleanup() - - return prob - - -@unittest.skip('benchmark_climb_large_single_aisle_1 currently broken') -class ClimbPhaseTestCase(unittest.TestCase): - def bench_test_climb_large_single_aisle_1(self): - - prob = run_trajectory() - - times = prob.get_val('traj.climb.timeseries.time', units='s') - altitudes = prob.get_val('traj.climb.timeseries.states:altitude', units='m') - masses = prob.get_val('traj.climb.timeseries.states:mass', units='kg') - ranges = prob.get_val('traj.climb.timeseries.states:range', units='m') - velocities = prob.get_val('traj.climb.timeseries.states:velocity', units='m/s') - thrusts = prob.get_val('traj.climb.timeseries.thrust_net_total', units='N') - - expected_times_s = [ - [120.], [194.07139282], [296.27479672], [328.62182461], - [328.62182461], [402.69321743], [504.89662133], [537.24364922], - [537.24364922], [611.31504204], [713.51844594], [745.86547383], - [745.86547383], [819.93686665], [922.14027055], [954.48729844], - [954.48729844], [1028.55869126], [1130.76209516], [1163.10912305], - [1163.10912305], [1237.18051587], [1339.38391977], [1371.73094766]] - expected_altitudes_m = [ - [0.], [682.4957554], [2476.9915886], [3148.23528025], - [3148.23528025], [4477.82410739], [5953.00480303], [6351.40548265], - [6351.40548265], [7144.57711953], [8022.01978103], [8258.96785373], - [8258.96785373], [8740.86702454], [9286.23944667], [9434.70712276], - [9434.70712276], [9740.61479402], [10091.17192047], [10187.1054111], - [10187.1054111], [10383.06677812], [10606.85289388], [10668.]] - expected_masses_kg = [ - [81929.21464651], [81734.3159625], [81482.28032417], [81409.67409534], - [81409.67409534], [81261.52268984], [81089.79910736], [81041.35712905], - [81041.35712905], [80939.57475401], [80815.51316484], [80779.24867668], - [80779.24867668], [80700.64682801], [80600.66210256], [80570.67210015], - [80570.67210015], [80504.18318007], [80416.95604372], [80390.28585848], - [80390.28585848], [80330.69704984], [80251.40682218], [80226.91650584]] - expected_ranges_m = [ - [0.], [11134.82482615], [33305.36225275], [40552.08037408], - [40552.08037408], [57694.75967874], [81567.92031544], [89129.68416299], - [89129.68416299], [106463.26453256], [130389.14274257], [137962.93786], - [137962.93786], [155308.03326502], [179243.7729914], [186819.85564691], - [186819.85564691], [204168.96379317], [228108.3526042], [235685.29047396], - [235685.29047396], [253035.99320713], [276976.90557005], [284554.20584747]] - expected_velocities_ms = [ - [102.08605905], [190.58935413], [228.38194188], [230.69562115], - [230.69562115], [233.30227629], [234.24467254], [234.25795132], - [234.25795132], [234.25795132], [234.25795132], [234.25795132], - [234.25795132], [234.25795132], [234.25795132], [234.25795132], - [234.25795132], [234.25795132], [234.25795132], [234.25795132], - [234.25795132], [234.25795132], [234.25795132], [234.25795132]] - expected_thrusts_N = [ - [218457.50162414], [171332.33326933], [141120.77188326], [131766.72736631], - [131766.72736631], [112828.24508393], [94178.64187388], [89529.25529378], - [89529.25529378], [80383.22196309], [71047.86501907], [68765.74648505], - [68765.74648505], [64112.90745184], [59068.27795964], [57877.0412593], - [57877.0412593], [55418.69760271], [52595.0053262], [51821.03735429], - [51821.03735429], [50238.39534494], [48428.26539664], [47933.1465328]] - expected_times_s = np.array(expected_times_s) - expected_altitudes_m = np.array(expected_altitudes_m) - expected_masses_kg = np.array(expected_masses_kg) - expected_ranges_m = np.array(expected_ranges_m) - expected_velocities_ms = np.array(expected_velocities_ms) - expected_thrusts_N = np.array(expected_thrusts_N) - - # NOTE rtol = 0.01 = 1% different from truth (first timeseries) - # atol = 1 = no more than +/-1 meter difference between values - atol = 1e-2 - rtol = 1e-3 - - # Objective - assert_near_equal(times[-1], expected_times_s[-1], tolerance=rtol) - - # Flight path - - assert_timeseries_near_equal( - expected_times_s, expected_masses_kg, times, masses, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_ranges_m, times, ranges, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_velocities_ms, times, velocities, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_thrusts_N, times, thrusts, - abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_climb_large_single_aisle_2.py b/aviary/mission/flops_based/phases/benchmark_tests/test_bench_climb_large_single_aisle_2.py deleted file mode 100644 index f03466f7f..000000000 --- a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_climb_large_single_aisle_2.py +++ /dev/null @@ -1,366 +0,0 @@ -''' -NOTES: -Includes: -Climb -Computed Aero -Large Single Aisle 2 data -''' - -import unittest - -import dymos as dm -# Suppress the annoying warnings from matplotlib when running dymos. -import matplotlib as mpl -import numpy as np -import openmdao.api as om -import scipy.constants as _units - -from dymos.utils.testing_utils import assert_timeseries_near_equal -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import use_tempdirs -from packaging import version -from aviary.variable_info.variables_in import VariablesIn - -from aviary.utils.aviary_values import AviaryValues -from aviary.mission.flops_based.phases.climb_phase import Climb -from aviary.interface.default_phase_info.height_energy import prop, aero, geom -from aviary.subsystems.premission import CorePreMission -from aviary.utils.functions import set_aviary_initial_values -from aviary.validation_cases.validation_tests import get_flops_inputs -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Dynamic - -mpl.rc('figure', max_open_warning=0) - -try: - import pyoptsparse -except ImportError: - pyoptsparse = None - - -def run_trajectory(): - prob = om.Problem(model=om.Group()) - - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 40 - driver.opt_settings["Major optimality tolerance"] = 1e-5 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-3 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - # TODO enable coloring once issue has been resolved: - # https://github.com/OpenMDAO/OpenMDAO/issues/2507 - # driver.declare_coloring() - - ########################## - # Problem Settings # - ########################## - alt_i_climb = 35 * _units.foot # m - alt_f_climb = 35000.0 * _units.foot # m - mass_i_climb = 169172 * _units.lb # kg - mass_f_climb = 164160 * _units.lb # kg - v_i_climb = 198.44 * _units.knot # m/s - v_f_climb = 452.61 * _units.knot # m/s - mach_i_climb = 0.3 - mach_f_climb = 0.775 - range_i_climb = 0 * _units.nautical_mile # m - range_f_climb = 124 * _units.nautical_mile # m - t_i_climb = 0 - t_f_climb = 20.14 * _units.minute # sec - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle2FLOPS') - transcription = dm.Radau(num_segments=6, order=3, compressed=False) - climb_options = Climb( - 'test_climb', - user_options=AviaryValues({ - 'initial_altitude': (alt_i_climb, 'm'), - 'final_altitude': (alt_f_climb, 'm'), - 'initial_mach': (mach_i_climb, 'unitless'), - 'final_mach': (mach_f_climb, 'unitless'), - 'fix_initial': (True, 'unitless'), - 'fix_range': (False, 'unitless'), - 'input_initial': (False, 'unitless'), - }), - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - core_subsystems=[aero, prop], - transcription=transcription, - ) - - # replace debug_no_mass flag with list of subsystem builders without mass - core_subsystems = [prop, geom, aero] - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'core_premission', - CorePreMission(aviary_options=aviary_inputs, subsystems=core_subsystems), - promotes_inputs=['aircraft:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - climb = climb_options.build_phase(aviary_options=aviary_inputs) - - traj.add_phase('climb', climb) - - climb.timeseries_options['use_prefix'] = True - - climb.add_objective('time', loc='final', ref=t_f_climb) - - climb.set_time_options( - fix_initial=True, fix_duration=False, units='s', - duration_bounds=(t_f_climb * 0.5, t_f_climb * 2), duration_ref=t_f_climb) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs, phases=['climb']) - - prob.model.add_subsystem( - 'input_sink', - VariablesIn(aviary_options=aviary_inputs), - promotes_inputs=['*'], - promotes_outputs=['*'] - ) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - - prob.setup() - - prob.set_val('traj.climb.t_initial', t_i_climb, units='s') - prob.set_val('traj.climb.t_duration', t_f_climb, units='s') - - prob.set_val('traj.climb.states:altitude', climb.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m') - prob.set_val('traj.climb.states:velocity', climb.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_climb, v_f_climb]), units='m/s') - prob.set_val('traj.climb.states:mass', climb.interp( - Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg') - prob.set_val('traj.climb.states:range', climb.interp( - Dynamic.Mission.RANGE, ys=[range_i_climb, range_f_climb]), units='m') # nmi - - prob.set_val('traj.climb.controls:velocity_rate', - climb.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.25, 0.05]), - units='m/s**2') - prob.set_val('traj.climb.controls:throttle', - climb.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=False, make_plots=False, - solution_record_file='climb_large_single_aisle_2.db') - prob.record("final") - prob.cleanup() - - return prob - - -@use_tempdirs -class ClimbPhaseTestCase(unittest.TestCase): - def bench_test_climb_large_single_aisle_2(self): - - prob = run_trajectory() - - times = prob.get_val('traj.climb.timeseries.time', units='s') - altitudes = prob.get_val('traj.climb.timeseries.states:altitude', units='m') - masses = prob.get_val('traj.climb.timeseries.states:mass', units='kg') - ranges = prob.get_val('traj.climb.timeseries.states:range', units='m') - velocities = prob.get_val('traj.climb.timeseries.states:velocity', units='m/s') - thrusts = prob.get_val('traj.climb.timeseries.thrust_net_total', units='N') - - expected_times_s = [[0.], - [47.24020828], - [112.42205385], - [133.05188512], - [133.05188512], - [180.2920934], - [245.47393897], - [266.10377023], - [266.10377023], - [313.34397852], - [378.52582409], - [399.15565535], - [399.15565535], - [446.39586363], - [511.5777092], - [532.20754047], - [532.20754047], - [579.44774875], - [644.62959432], - [665.25942558], - [665.25942558], - [712.49963387], - [777.68147944], - [798.3113107]] - expected_altitudes_m = [[1.06680000e+01], - [1.64717443e+02], - [1.21077165e+03], - [1.65467452e+03], - [1.65467452e+03], - [2.63158548e+03], - [3.89195114e+03], - [4.26784031e+03], - [4.26784031e+03], - [5.08501440e+03], - [6.12319963e+03], - [6.43302565e+03], - [6.43302565e+03], - [7.11281142e+03], - [7.98114971e+03], - [8.23811059e+03], - [8.23811059e+03], - [8.78587957e+03], - [9.45185516e+03], - [9.64164118e+03], - [9.64164118e+03], - [1.00442465e+04], - [1.05301137e+04], - [1.06680000e+04]] - expected_masses_kg = [[76735.12841764], - [76606.6291851], - [76423.55875817], - [76369.21098493], - [76369.21098493], - [76249.27821197], - [76096.52180626], - [76051.03001076], - [76051.03001076], - [75951.61028918], - [75824.47689696], - [75786.47518169], - [75786.47518169], - [75703.26334226], - [75596.63038983], - [75564.65308165], - [75564.65308165], - [75493.66861479], - [75400.85356264], - [75372.67886333], - [75372.67886333], - [75310.24572772], - [75228.35359487], - [75203.34794937]] - expected_ranges_m = [[0.], - [6656.35106052], - [19411.34037117], - [23606.44276229], - [23606.44276229], - [33597.9222712], - [47757.51502804], - [52308.8364503], - [52308.8364503], - [62843.02681099], - [77579.88662088], - [82280.86896803], - [82280.86896803], - [93082.94529627], - [108032.30577631], - [112765.20449737], - [112765.20449737], - [123607.6358803], - [138572.23956024], - [143309.37828794], - [143309.37828794], - [154158.17439593], - [169129.72020176], - [173868.65393385]] - expected_velocities_ms = [[102.08635556], - [173.4078533], - [206.56979636], - [209.71234319], - [209.71234319], - [215.12096513], - [220.69657248], - [222.14669648], - [222.14669648], - [225.06833215], - [228.02045426], - [228.64470582], - [228.64470582], - [229.49739505], - [229.80567178], - [229.81001554], - [229.81001554], - [229.81001554], - [229.81001554], - [229.81001554], - [229.81001554], - [229.81001554], - [229.81001554], - [229.81001554]] - expected_thrusts_N = [[183100.97258682], - [163996.9938714], - [144753.36497874], - [140427.87965941], - [140427.87965941], - [131136.19372391], - [119398.47487166], - [115939.50935006], - [115939.50935006], - [108449.59352749], - [99269.67371293], - [96533.89180294], - [96533.89180294], - [90902.4892038], - [84031.52375462], - [81889.41102505], - [81889.41102505], - [77138.3352612], - [71330.53467419], - [69720.96587699], - [69720.96587699], - [66405.48655242], - [62587.8225939], - [61541.80174696]] - - expected_times_s = np.array(expected_times_s) - expected_altitudes_m = np.array(expected_altitudes_m) - expected_masses_kg = np.array(expected_masses_kg) - expected_ranges_m = np.array(expected_ranges_m) - expected_velocities_ms = np.array(expected_velocities_ms) - expected_thrusts_N = np.array(expected_thrusts_N) - - # NOTE rtol = 0.01 = 1% different from truth (first timeseries) - # atol = 1 = no more than +/-1 meter difference between values - atol = 1e-2 - rtol = 1e-3 - - # Objective - assert_near_equal(times[-1], expected_times_s[-1], tolerance=rtol) - - # Flight path - assert_timeseries_near_equal( - expected_times_s, expected_masses_kg, times, masses, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_ranges_m, times, ranges, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_velocities_ms, times, velocities, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_thrusts_N, times, thrusts, - abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == "__main__": - test = ClimbPhaseTestCase() - test.bench_test_climb_large_single_aisle_2() diff --git a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_climb_large_single_aisle_1.py b/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_climb_large_single_aisle_1.py deleted file mode 100644 index c88f5dd8e..000000000 --- a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_climb_large_single_aisle_1.py +++ /dev/null @@ -1,316 +0,0 @@ -############################################################### -# NOTES: -# Includes: -# Cruise -# Computed Aero -# Large Single Aisle 1 data -############################################################### -import unittest - -import dymos as dm -# Suppress the annoying warnings from matplotlib when running dymos. -import matplotlib as mpl -import numpy as np -import openmdao.api as om -import scipy.constants as _units - -from dymos.utils.testing_utils import assert_timeseries_near_equal -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.flops_based.ode.mission_ODE import MissionODE -from aviary.mission.flops_based.phases.cruise_phase import Cruise -from aviary.interface.default_phase_info.height_energy import prop, aero, geom -from aviary.subsystems.premission import CorePreMission -from aviary.utils.functions import set_aviary_initial_values -from aviary.utils.functions import get_path -from aviary.validation_cases.validation_tests import get_flops_inputs -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Dynamic - -mpl.rc('figure', max_open_warning=0) - - -try: - import pyoptsparse -except ImportError: - pyoptsparse = None - - -# benchmark based on Large Single Aisle 1 (fixed cruise alt) FLOPS model - -def run_trajectory(): - prob = om.Problem(model=om.Group()) - - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 30 - driver.opt_settings["Major optimality tolerance"] = 1e-5 - driver.opt_settings["Major feasibility tolerance"] = 1e-5 - driver.opt_settings["iSumm"] = 6 - driver.opt_settings["Verify level"] = 3 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-6 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - # TODO enable coloring once issue has been resolved: - # https://github.com/OpenMDAO/OpenMDAO/issues/2507 - # driver.declare_coloring() - - ########################## - # Problem Settings # - ########################## - alt_min_cruise = 30000*_units.foot # m - alt_max_cruise = 36000*_units.foot # m - alt_i_cruise = alt_min_cruise - alt_f_cruise = 36000*_units.foot # m - mass_i_cruise = 176765*_units.lb # kg - mass_f_cruise = 143521*_units.lb # kg - velocity_i_cruise = 465.55*_units.knot # m/s - velocity_f_cruise = 465.55*_units.knot # m/s - mach_i_cruise = 0.79 - mach_f_cruise = 0.79 - mach_min_cruise = 0.78999 - mach_max_cruise = 0.79001 - range_i_cruise = 160.3*_units.nautical_mile # m - range_f_cruise = 3243.9*_units.nautical_mile # m - t_i_cruise = 26.20*_units.minute # sec - t_f_cruise = 432.38*_units.minute # sec - t_duration_cruise = t_f_cruise - t_i_cruise - - prob.set_solver_print(level=0) - - transcription = dm.Radau(num_segments=8, order=3, compressed=True) - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle1FLOPS') - # engine_models = aviary_inputs['engine_models'] - - cruise_options = Cruise( - 'test_cruise', - min_altitude=alt_min_cruise, # m - max_altitude=alt_max_cruise, # m - # note, Mach = 0.0 causes an error in aero, perhaps in other code - min_mach=mach_min_cruise, - max_mach=mach_max_cruise, - required_available_climb_rate=300*_units.foot/_units.minute, # ft/min to m/s - fix_initial=True, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - aviary_options=aviary_inputs, - # engine_models=engine_models, - no_descent=True, - velocity_f_cruise=velocity_f_cruise, - mass_f_cruise=mass_f_cruise, - range_f_cruise=range_f_cruise, - - ) - - # replace debug_no_mass flag with list of subsystem builders without mass - core_subsystems = [prop, geom, aero] - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'core_premission', - CorePreMission(aviary_options=aviary_inputs, subsystems=core_subsystems), - promotes_inputs=['aircraft:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - cruise = cruise_options.build_phase(MissionODE, transcription) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - traj.add_phase('cruise', cruise) - - cruise.set_time_options( - fix_initial=True, fix_duration=False, units='s', - duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise) - - cruise.add_objective(Dynamic.Mission.MASS, loc='final', ref=-mass_f_cruise) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - - # IVC = prob.model.add_subsystem('IVC', om.IndepVarComp(), promotes_outputs=['*']) - # IVC.add_output('alt_i', val=32000, units='ft') - prob.model.add_constraint( - 'traj.cruise.states:altitude', - indices=[0], equals=alt_min_cruise, units='m', ref=alt_max_cruise) - - # Alternative implmentation of above - # holds the first node in altitude constant - # cruise.set_state_options(Dynamic.Mission.ALTITUDE, fix_initial=True) - - prob.setup() - - prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s') - prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s') - - prob.set_val( - 'traj.cruise.states:altitude', cruise.interp( - Dynamic.Mission.ALTITUDE, ys=[ - alt_i_cruise, (alt_f_cruise + alt_i_cruise) / 2]), units='m') - prob.set_val( - 'traj.cruise.states:velocity', cruise.interp( - Dynamic.Mission.VELOCITY, ys=[ - velocity_i_cruise, velocity_f_cruise]), units='m/s') - prob.set_val('traj.cruise.states:mass', cruise.interp( - Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg') - prob.set_val('traj.cruise.states:range', cruise.interp( - Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi - - prob.set_val('traj.cruise.controls:velocity_rate', - cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.cruise.controls:throttle', - cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - dm.run_problem(prob, simulate=False, make_plots=False, - solution_record_file='cruise_climb_max.db') - # TODO Simulate=True is crashing here so turned it off - - prob.cleanup() - prob.record("final") - - return prob - - -@unittest.skip('benchmark cruise climb currently broken') -class CruisePhaseTestCase(unittest.TestCase): - def bench_test_cruise_climb_large_single_aisle_1(self): - - prob = run_trajectory() - - times = prob.get_val('traj.cruise.timeseries.time', units='s') - altitudes = prob.get_val('traj.cruise.timeseries.states:altitude', units='m') - masses = prob.get_val('traj.cruise.timeseries.states:mass', units='kg') - ranges = prob.get_val('traj.cruise.timeseries.states:range', units='m') - velocities = prob.get_val('traj.cruise.timeseries.states:velocity', units='m/s') - thrusts = prob.get_val('traj.cruise.timeseries.thrust_net_total', units='N') - - expected_times_s = [ - [1572.0], [2654.1067424751645], [4147.193185981006], - [4619.749940380141], [4619.749940380141], [5701.856682855307], - [7194.943126361148], [7667.499880760283], [7667.499880760283], - [8749.606623235448], [10242.693066741289], [10715.249821140425], - [10715.249821140425], [11797.356563615589], [13290.44300712143], - [13762.999761520567], [13762.999761520567], [14845.106503995732], - [16338.192947501573], [16810.749701900706], [16810.749701900706], - [17892.856444375873], [19385.942887881713], [19858.49964228085], - [19858.49964228085], [20940.606384756014], [22433.692828261857], - [22906.249582660992], [22906.249582660992], [23988.356325136156], - [25481.442768642], [25953.999523041133]] - expected_altitudes_m = [ - [9143.999999999998], [9708.208298692567], [10127.822388940609], - [10214.217379879337], [10214.217379879337], [10369.633608560018], - [10522.493213427248], [10561.102831034279], [10561.102831034279], - [10628.89921313077], [10700.84689461149], [10720.94842484514], - [10720.94842484514], [10759.7268947497], [10811.373374694173], - [10828.933975559758], [10828.933975559758], [10866.85055516194], - [10912.976573928156], [10924.787065056735], [10924.787065056735], - [10948.28098891448], [10967.163582971652], [10969.578328436102], - [10969.578328436102], [10971.188528219269], [10971.305320817934], - [10972.40306787028], [10972.40306787028], [10972.800000000001], - [10972.682828875833], [10972.8]] - expected_masses_kg = [ - [80179.25528304999], [79388.27899969438], [78357.5492916318], - [78038.12469387538], [78038.12469387538], [77318.48636094612], - [76344.12665022336], [76039.40895912607], [76039.40895912607], - [75347.90752400359], [74405.46275374181], [74109.60273338258], - [74109.60273338258], [73436.39272100825], [72516.3728326044], - [72227.22710472894], [72227.22710472894], [71569.02331279762], - [70669.56072490575], [70386.99122373879], [70386.99122373879], - [69743.47490624938], [68863.48019743375], [68586.78382987661], - [68586.78382987661], [67956.17424058718], [67092.43136900471], - [66820.49389265837], [66820.49389265837], [66200.47169407656], - [65350.50275054499], [65082.74154168183]] - expected_ranges_m = [ - [296875.60000000003], [554932.6549301515], [908399.8849826958], - [1019938.0213070473], [1019938.0213070473], [1274830.9442516388], - [1625714.5770829467], [1736624.0715069345], [1736624.0715069345], - [1990386.259724133], [2340154.5629672543], [2450786.080862452], - [2450786.080862452], [2704005.1890667905], [3053155.1477462808], - [3163604.8984363866], [3163604.8984363866], [3416419.8294669725], - [3765031.23567717], [3875315.9608939146], [3875315.9608939146], - [4127790.908533788], [4476045.826943081], [4586252.017090351], - [4586252.017090351], [4838601.188787049], [5186784.687764149], - [5296985.604312349], [5296985.604312349], [5549329.03066934], - [5897505.925045533], [6007702.8]] - expected_velocities_ms = [ - [239.49901683172416], [237.57721365239863], [236.1272332817375], - [235.83480578606836], [235.83480578606836], [235.29228140558052], - [234.76087668521515], [234.63268475430843], [234.63268475430843], - [234.3910317141664], [234.14254607637204], [234.07650600638797], - [234.07650600638797], [233.93612811601653], [233.75522209810927], - [233.69840302886462], [233.69840302886462], [233.56754384846963], - [233.4030426703855], [233.35949436934354], [233.35949436934354], - [233.28100688012185], [233.21763783283092], [233.20729273195352], - [233.20729273195352], [233.19770503012535], [233.1994910278998], - [233.19937577943125], [233.19937577943125], [233.19406389286172], - [233.19249705434646], [233.197992365194]] - expected_thrusts_N = [ - [46413.113617016286], [43959.216926887144], [42322.72191477366], - [42001.69876565644], [42001.698765656445], [41407.67462380937], - [40737.526959795134], [40531.856232353945], [40531.85623235394], - [40132.12747513132], [39640.66854140172], [39484.44541552107], - [39484.44541552107], [39159.04488467944], [38737.64967064259], - [38601.13281610918], [38601.13281610918], [38291.27389642972], - [37859.254019237305], [37729.15520426054], [37729.155204260525], - [37427.990254061384], [37028.69942780689], [36907.64255965252], - [36907.64255965253], [36649.2311134786], [36319.78199219473], - [36213.12929993469], [36213.12929994349], [35988.316744926444], - [35701.59647680185], [35614.98883340026]] - - expected_times_s = np.array(expected_times_s) - expected_altitudes_m = np.array(expected_altitudes_m) - expected_masses_kg = np.array(expected_masses_kg) - expected_ranges_m = np.array(expected_ranges_m) - expected_velocities_ms = np.array(expected_velocities_ms) - expected_thrusts_N = np.array(expected_thrusts_N) - - # Objective - - rtol = 1e-2 - - assert_near_equal(times[-1], expected_times_s[-1], tolerance=rtol) - - # Flight path - - # NOTE rtol = 0.01 = 1% different from truth (first timeseries) - # atol = 1 = no more than +/-1 meter difference between values - atol = 1e-2 - rtol = 1e-3 - - assert_timeseries_near_equal( - expected_times_s, expected_masses_kg, times, masses, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_ranges_m, times, ranges, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_velocities_ms, times, velocities, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_thrusts_N, times, thrusts, - abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_large_single_aisle_1.py b/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_large_single_aisle_1.py deleted file mode 100644 index b9925cf5b..000000000 --- a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_large_single_aisle_1.py +++ /dev/null @@ -1,253 +0,0 @@ -''' -NOTES: -Includes: -Cruise -Computed Aero -Large Single Aisle 1 data -''' - -import unittest - -import dymos as dm -# Suppress the annoying warnings from matplotlib when running dymos. -import matplotlib as mpl -import numpy as np -import openmdao.api as om -import scipy.constants as _units - -from dymos.utils.testing_utils import assert_timeseries_near_equal -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import use_tempdirs -from packaging import version -from aviary.variable_info.variables_in import VariablesIn - -from aviary.utils.aviary_values import AviaryValues -from aviary.mission.flops_based.phases.cruise_phase import Cruise -from aviary.interface.default_phase_info.height_energy import prop, aero, geom -from aviary.subsystems.premission import CorePreMission -from aviary.utils.functions import set_aviary_initial_values -from aviary.utils.functions import get_path -from aviary.validation_cases.validation_tests import (get_flops_inputs, - get_flops_outputs) -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Aircraft, Dynamic - -mpl.rc('figure', max_open_warning=0) - -try: - import pyoptsparse - -except ImportError: - pyoptsparse = None - - -# benchmark based on Large Single Aisle 1 (fixed cruise alt) FLOPS model - -def run_trajectory(): - prob = om.Problem(model=om.Group()) - - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 50 - driver.opt_settings["Major optimality tolerance"] = 1e-5 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-6 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - # TODO enable coloring once issue has been resolved: - # https://github.com/OpenMDAO/OpenMDAO/issues/2507 - # driver.declare_coloring() - - ########################## - # Problem Settings # - ########################## - alt_i_cruise = 35000*_units.foot # m - alt_f_cruise = 35000*_units.foot # m - alt_min_cruise = 35000*_units.foot # m - alt_max_cruise = 35000*_units.foot # m - mass_i_cruise = 176765*_units.lb # kg - mass_f_cruise = 143521*_units.lb # kg - v_i_cruise = 455.49*_units.knot # m/s - v_f_cruise = 455.49*_units.knot # m/s - mach_i_cruise = 0.79 - mach_f_cruise = 0.79 - mach_min_cruise = 0.79 - mach_max_cruise = 0.79 - range_i_cruise = 160.3*_units.nautical_mile # m - range_f_cruise = 3243.9*_units.nautical_mile # m - t_i_cruise = 26.20*_units.minute # sec - t_f_cruise = 432.38*_units.minute # sec - t_duration_cruise = t_f_cruise - t_i_cruise - - prob.set_solver_print(level=2) - - transcription = dm.Radau(num_segments=1, order=3, compressed=True) - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle1FLOPS') - aviary_outputs = get_flops_outputs('LargeSingleAisle1FLOPS') - - # replace debug_no_mass flag with list of subsystem builders without mass - core_subsystems = [prop, geom, aero] - - cruise_options = Cruise( - 'test_cruise', - user_options=AviaryValues({ - 'min_altitude': (alt_min_cruise, 'm'), - 'max_altitude': (alt_max_cruise, 'm'), - 'min_mach': (mach_min_cruise, 'unitless'), - 'max_mach': (mach_max_cruise, 'unitless'), - 'required_available_climb_rate': (300, 'ft/min'), - 'fix_initial': (True, 'unitless'), - 'fix_final': (True, 'unitless'), - }), - core_subsystems=core_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription, - ) - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'core_premission', - CorePreMission(aviary_options=aviary_inputs, subsystems=core_subsystems), - promotes_inputs=['aircraft:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - cruise = cruise_options.build_phase(aviary_options=aviary_inputs) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - traj.add_phase('cruise', cruise) - - cruise.timeseries_options['use_prefix'] = True - - cruise.set_time_options( - fix_initial=True, fix_duration=False, units='s', - duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise) - - cruise.add_objective(Dynamic.Mission.MASS, loc='final', ref=-1e4) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs, phases=['cruise']) - - prob.model.add_subsystem( - 'input_sink', - VariablesIn(aviary_options=aviary_inputs), - promotes_inputs=['*'], - promotes_outputs=['*'] - ) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - key = Aircraft.Engine.THRUST_REVERSERS_MASS - val, units = aviary_outputs.get_item(key) - prob.model.set_input_defaults(key, val, units) - - prob.setup() - - prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s') - prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s') - - prob.set_val('traj.cruise.states:altitude', cruise.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m') - prob.set_val('traj.cruise.states:velocity', cruise.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_cruise, v_f_cruise]), units='m/s') - prob.set_val('traj.cruise.states:mass', cruise.interp( - Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg') - prob.set_val('traj.cruise.states:range', cruise.interp( - Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi - - prob.set_val('traj.cruise.controls:velocity_rate', - cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.cruise.controls:throttle', - cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=False, make_plots=False, - solution_record_file='cruise_max.db') - # TODO Simulate=True is crashing here so turned it off - - prob.cleanup() - prob.record("final") - - return prob - - -@use_tempdirs -class CruisePhaseTestCase(unittest.TestCase): - def bench_test_cruise_large_single_aisle_1(self): - - prob = run_trajectory() - - times = prob.get_val('traj.cruise.timeseries.time', units='s') - altitudes = prob.get_val('traj.cruise.timeseries.states:altitude', units='m') - masses = prob.get_val('traj.cruise.timeseries.states:mass', units='kg') - ranges = prob.get_val('traj.cruise.timeseries.states:range', units='m') - velocities = prob.get_val('traj.cruise.timeseries.states:velocity', units='m/s') - thrusts = prob.get_val('traj.cruise.timeseries.thrust_net_total', units='N') - - expected_times_s = [[1572.], [10227.56555775], - [22170.47940152], [25950.37079939]] - expected_altitudes_m = [[10668.], [10668.], [10668.], [10668.]] - expected_masses_kg = [[80179.25528305], [ - 74612.30190345], [67357.05992796], [65154.3328912]] - expected_ranges_m = [[296875.6], [2324510.6550793], - [5122233.1849208], [6007702.8]] - expected_velocities_ms = [[234.25795132], [ - 234.25795132], [234.25795132], [234.25795132]] - expected_thrusts_N = [[41817.82877662], [ - 39634.49609004], [36930.60549609], [36149.38784885]] - - expected_times_s = np.array(expected_times_s) - expected_altitudes_m = np.array(expected_altitudes_m) - expected_masses_kg = np.array(expected_masses_kg) - expected_ranges_m = np.array(expected_ranges_m) - expected_velocities_ms = np.array(expected_velocities_ms) - expected_thrusts_N = np.array(expected_thrusts_N) - - # NOTE rtol = 0.01 = 1% different from truth (first timeseries) - # atol = 1 = no more than +/-1 meter difference between values - atol = 1e-2 - rtol = 1e-3 - - assert_near_equal(times[-1], expected_times_s[-1], tolerance=rtol) - - # Flight path - - assert_timeseries_near_equal( - expected_times_s, expected_masses_kg, times, masses, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_ranges_m, times, ranges, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_velocities_ms, times, velocities, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_thrusts_N, times, thrusts, - abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_large_single_aisle_2.py b/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_large_single_aisle_2.py deleted file mode 100644 index 8e1cfae23..000000000 --- a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_cruise_large_single_aisle_2.py +++ /dev/null @@ -1,247 +0,0 @@ -''' -NOTES: -Includes: -Cruise -Computed Aero -Large Single Aisle 2 data -CURRENTLY REMOVED FROM BENCHMARK TEST SUITE -''' - -import unittest - -import dymos as dm -# Suppress the annoying warnings from matplotlib when running dymos. -import matplotlib as mpl -import numpy as np -import openmdao.api as om -import scipy.constants as _units - -from dymos.utils.testing_utils import assert_timeseries_near_equal -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.flops_based.ode.mission_ODE import MissionODE -from aviary.mission.flops_based.phases.cruise_phase import Cruise -from aviary.interface.default_phase_info.height_energy import prop, aero, geom -from aviary.subsystems.premission import CorePreMission -from aviary.utils.functions import set_aviary_initial_values -from aviary.validation_cases.validation_tests import (get_flops_inputs, - get_flops_outputs) -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Aircraft, Dynamic - -mpl.rc('figure', max_open_warning=0) - -try: - import pyoptsparse - -except ImportError: - pyoptsparse = None - - -''' -NOTE benchmark currently hits iteration limit - problem only converges when velocity ref & ref0 in energyphase is changed to 1e3. - Changing velocity ref breaks some full-mission benchmarks, so is currently not - implemented -''' - - -def run_trajectory(): - prob = om.Problem(model=om.Group()) - - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 50 - driver.opt_settings["Major optimality tolerance"] = 1e-5 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-6 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - # TODO enable coloring once issue has been resolved: - # https://github.com/OpenMDAO/OpenMDAO/issues/2507 - # driver.declare_coloring() - - ########################## - # Problem Settings # - ########################## - alt_i_cruise = 35000*_units.foot # m - alt_f_cruise = 35000*_units.foot # m - alt_min_cruise = 35000*_units.foot # m - alt_max_cruise = 35000*_units.foot # m - mass_i_cruise = 169730*_units.lb # kg - mass_f_cruise = 139810*_units.lb # kg - v_i_cruise = 452.61*_units.knot # m/s - v_f_cruise = 452.61*_units.knot # m/s - mach_i_cruise = 0.785 - mach_f_cruise = 0.785 - mach_min_cruise = 0.785 - mach_max_cruise = 0.785 - range_i_cruise = 116.6*_units.nautical_mile # m - range_f_cruise = 2558.3*_units.nautical_mile # m - t_i_cruise = 19.08*_units.minute # sec - t_f_cruise = 342.77*_units.minute # sec - t_duration_cruise = t_f_cruise - t_i_cruise # sec - - prob.set_solver_print(level=2) - - transcription = dm.Radau(num_segments=1, order=3, compressed=True) - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle2FLOPS') - aviary_outputs = get_flops_outputs('LargeSingleAisle2FLOPS') - - cruise_options = Cruise( - 'test_cruise', - min_altitude=alt_min_cruise, # m - max_altitude=alt_max_cruise, # m - # note, Mach = 0.0 causes an error in aero, perhaps in other code - min_mach=mach_min_cruise, - max_mach=mach_max_cruise, - required_available_climb_rate=300*_units.foot/_units.minute, # ft/min to m/s - fix_initial=True, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - aviary_options=aviary_inputs, - ) - - # replace debug_no_mass flag with list of subsystem builders without mass - core_subsystems = [prop, geom, aero] - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'core_premission', - CorePreMission(aviary_options=aviary_inputs, subsystems=core_subsystems), - promotes_inputs=['aircraft:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - cruise = cruise_options.build_phase(MissionODE, transcription) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - traj.add_phase('cruise', cruise) - - cruise.set_time_options( - fix_initial=True, fix_duration=False, units='s', - duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise) - - cruise.add_objective(Dynamic.Mission.MASS, loc='final', ref=-1e4) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - key = Aircraft.Engine.THRUST_REVERSERS_MASS - val, units = aviary_outputs.get_item(key) - prob.model.set_input_defaults(key, val, units) - - prob.setup() - - prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s') - prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s') - - prob.set_val('traj.cruise.states:altitude', cruise.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m') - prob.set_val('traj.cruise.states:velocity', cruise.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_cruise, v_f_cruise]), units='m/s') - prob.set_val('traj.cruise.states:mass', cruise.interp( - Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg') - prob.set_val('traj.cruise.states:range', cruise.interp( - Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi - - prob.set_val('traj.cruise.controls:velocity_rate', - cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.cruise.controls:throttle', - cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=False, make_plots=False, - solution_record_file='cruise_large_single_aisle_2.db') - # TODO Simulate=True is crashing here so turned it off - - prob.cleanup() - prob.record("final") - - return prob - - -@unittest.skip('benchmark_cruise_large_single_aisle_2 currently broken') -class CruisePhaseTestCase(unittest.TestCase): - def bench_test_cruise_large_single_asile_2(self): - - prob = run_trajectory() - - times = prob.get_val('traj.cruise.timeseries.time', units='s') - altitudes = prob.get_val('traj.cruise.timeseries.states:altitude', units='m') - masses = prob.get_val('traj.cruise.timeseries.states:mass', units='kg') - ranges = prob.get_val('traj.cruise.timeseries.states:range', units='m') - velocities = prob.get_val('traj.cruise.timeseries.states:velocity', units='m/s') - thrusts = prob.get_val('traj.cruise.timeseries.thrust_net', units='N') - - expected_times_s = [[1144.8], [8042.22760495], - [17559.26991489], [20571.38126654]] - expected_altitudes_m = [[10668.], [10668.], [10668.], [10668.]] - expected_masses_kg = [[76988.2329601], [ - 72122.85057432], [65811.40723995], [63888.48266274]] - expected_ranges_m = [[215943.2], [1821494.02176252], - [4036826.45823737], [4737971.6]] - expected_velocities_ms = [[232.77530606], [ - 232.77530606], [232.77530606], [232.77530606]] - expected_thrusts_N = [[40492.90002165], [ - 38252.99661427], [35778.79636585], [35144.49009256]] - - expected_times_s = np.array(expected_times_s) - expected_altitudes_m = np.array(expected_altitudes_m) - expected_masses_kg = np.array(expected_masses_kg) - expected_ranges_m = np.array(expected_ranges_m) - expected_velocities_ms = np.array(expected_velocities_ms) - expected_thrusts_N = np.array(expected_thrusts_N) - - # NOTE rtol = 0.01 = 1% different from truth (first timeseries) - # atol = 1 = no more than +/-1 meter difference between values - atol = 1e-2 - rtol = 1e-3 - - # Objective - assert_near_equal(times[-1], expected_times_s[-1], tolerance=rtol) - - # Flight path - - assert_timeseries_near_equal( - expected_times_s, expected_masses_kg, times, masses, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_ranges_m, times, ranges, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_velocities_ms, times, velocities, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_thrusts_N, times, thrusts, - abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == "__main__": - # Changes to hardcoded tabular aero data changed this benchmark. Update benchmark - # test's expected values when aircraft-specific tabluar aero is avaliable - unittest.main() diff --git a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_descent_large_single_aisle_1.py b/aviary/mission/flops_based/phases/benchmark_tests/test_bench_descent_large_single_aisle_1.py deleted file mode 100644 index c1dba6168..000000000 --- a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_descent_large_single_aisle_1.py +++ /dev/null @@ -1,255 +0,0 @@ -''' -NOTES: -Includes: -Descent -Computed Aero -Large Single Aisle 1 data -''' - -import unittest - -import dymos as dm -# Suppress the annoying warnings from matplotlib when running dymos. -import matplotlib as mpl -import numpy as np -import openmdao.api as om -import scipy.constants as _units - -from dymos.utils.testing_utils import assert_timeseries_near_equal -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.flops_based.ode.mission_ODE import MissionODE -from aviary.mission.flops_based.phases.descent_phase import Descent -from aviary.interface.default_phase_info.height_energy import prop, aero, geom -from aviary.subsystems.premission import CorePreMission -from aviary.utils.functions import set_aviary_initial_values -from aviary.utils.functions import get_path -from aviary.validation_cases.validation_tests import get_flops_inputs -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Dynamic - -mpl.rc('figure', max_open_warning=0) - - -try: - import pyoptsparse - -except ImportError: - pyoptsparse = None - - -''' -NOTE descent profile is improved when velocity ref in EnergyPhase change to 1e3 - Implementing that velocity ref change breaks several mission benchmarks -''' -# benchmark based on Large Single Aisle 1 (fixed cruise alt) FLOPS model - - -def run_trajectory(): - prob = om.Problem(model=om.Group()) - - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 50 - driver.opt_settings["Major optimality tolerance"] = 5e-3 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-3 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - # TODO enable coloring once issue has been resolved: - # https://github.com/OpenMDAO/OpenMDAO/issues/2507 - # driver.declare_coloring() - - ########################## - # Problem Settings # - ########################## - alt_i_descent = 35000*_units.foot - alt_f_descent = 0*_units.foot - v_i_descent = 455.49*_units.knot - v_f_descent = 198.44*_units.knot - mach_i_descent = 0.79 - mach_f_descent = 0.3 - mass_i_descent = 143521*_units.pound - mass_f_descent = 143035*_units.pound - range_i_descent = 3243.9*_units.nautical_mile - range_f_descent = 3378.7*_units.nautical_mile - t_i_descent = 432.38*_units.minute - t_f_descent = 461.62*_units.minute - t_duration_descent = t_f_descent - t_i_descent - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle1FLOPS') - - descent_options = Descent( - 'test_descent', - final_altitude=alt_f_descent, - initial_altitude=alt_i_descent, - # note, Mach = 0.0 causes an error in aero, perhaps in other code - final_mach=mach_f_descent, - initial_mach=mach_i_descent, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - aviary_options=aviary_inputs, - ) - - # replace debug_no_mass flag with list of subsystem builders without mass - core_subsystems = [prop, geom, aero] - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'core_premission', - CorePreMission(aviary_options=aviary_inputs, subsystems=core_subsystems), - promotes_inputs=['aircraft:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - transcription = dm.Radau(num_segments=5, order=3, compressed=True) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - descent = descent_options.build_phase(MissionODE, transcription) - - # descent.add_objective(Dynamic.Mission.MASS, ref=-1e4, loc='final') - descent.add_objective(Dynamic.Mission.RANGE, ref=-1e5, loc='final') - - traj.add_phase('descent', descent) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - - prob.setup() - - # set initial conditions - prob.set_val('traj.descent.t_initial', t_i_descent, units='s') - prob.set_val('traj.descent.t_duration', t_duration_descent, units='s') - - prob.set_val('traj.descent.states:altitude', descent.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m') - prob.set_val('traj.descent.states:velocity', descent.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_descent, v_f_descent]), units='m/s') - prob.set_val('traj.descent.states:mass', descent.interp( - Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg') - prob.set_val('traj.descent.states:range', descent.interp( - Dynamic.Mission.RANGE, ys=[range_i_descent, range_f_descent]), units='m') - - prob.set_val('traj.descent.controls:velocity_rate', - descent.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.descent.controls:throttle', - descent.interp(Dynamic.Mission.THROTTLE, ys=[0.0, 0.0]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=False, make_plots=False, - solution_record_file='descent_max.db') - prob.record("final") - prob.cleanup() - - return prob - - -@unittest.skip('benchmark_descent_large_single_aisle_1 currently broken') -class DescentPhaseTestCase(unittest.TestCase): - def bench_test_descent_large_single_aisle_1(self): - - prob = run_trajectory() - - times = prob.get_val('traj.descent.timeseries.time', units='s') - altitudes = prob.get_val('traj.descent.timeseries.states:altitude', units='m') - masses = prob.get_val('traj.descent.timeseries.states:mass', units='kg') - ranges = prob.get_val('traj.descent.timeseries.states:range', units='m') - velocities = prob.get_val('traj.descent.timeseries.states:velocity', units='m/s') - thrusts = prob.get_val('traj.descent.timeseries.thrust_net', units='N') - - expected_times_s = [ - [25942.8], [26067.38110599], [26239.2776049], [26293.68225907], - [26293.68225907], [26418.26336507], [26590.15986397], [26644.56451815], - [26644.56451815], [26769.14562414], [26941.04212305], [26995.44677722], - [26995.44677722], [27120.02788321], [27291.92438212], [27346.3290363], - [27346.3290363], [27470.91014229], [27642.80664119], [27697.21129537]] - expected_altitudes_m = [ - [10668.], [9630.58727675], [8607.44716937], [8325.35146852], - [8325.35146852], [7618.26503578], [6519.12597312], [6144.16296733], - [6144.16296733], [5222.63590813], [3906.74184382], [3501.58245681], - [3501.58245681], [2693.56449806], [1613.14062128], [1227.34540813], - [1227.34540813], [472.05630168], [0.], [0.]] - expected_masses_kg = [ - [65100.03053477], [65068.4860871], [65009.01063053], [64988.47429787], - [64988.47429787], [64942.20142996], [64875.19680192], [64851.91888669], - [64851.91888669], [64793.85853886], [64706.58329093], [64678.30905669], - [64678.30905669], [64614.11135757], [64531.47572247], [64507.94243863], - [64507.94243863], [64464.03072407], [64420.28475648], [64408.7201039]] - expected_ranges_m = [ - [6007702.8], [6036868.3987895], [6077125.1955833], - [6089867.31136598], [6089867.31136598], [6119045.21397484], - [6159302.44709504], [6172042.94318126], [6172042.94318126], - [6201214.75820313], [6241466.48424821], [6254207.73380962], - [6254207.73380962], [6283402.65691348], [6322851.72600445], - [6334884.03220066], [6334884.03220066], [6360443.88994895], - [6388386.24507732], [6394712.53233535]] - expected_velocities_ms = [ - [234.25795132], [234.25795132], [234.25795132], [234.25795132], - [234.25795132], [234.25795132], [234.25795132], [234.25795132], - [234.25795132], [234.25795132], [234.25795132], [234.25795132], - [234.25795132], [233.47298378], [223.67831268], [216.71997882], - [216.71997882], [190.9857791], [129.0266439], [102.08605905]] - expected_thrusts_N = [ - [9516.07730964], [18867.54655988], [26134.27560127], [26569.54809341], - [26569.54809341], [26513.67959312], [27279.65638053], [27558.344662], - [27558.344662], [29558.29975054], [37141.0454636], [40960.56830135], - [40960.56830135], [46197.31464087], [36817.32495137], [29341.51748247], - [29341.51748247], [14369.16584473], [0.], [2.27381718e-11]] - - expected_times_s = np.array(expected_times_s) - expected_altitudes_m = np.array(expected_altitudes_m) - expected_masses_kg = np.array(expected_masses_kg) - expected_ranges_m = np.array(expected_ranges_m) - expected_velocities_ms = np.array(expected_velocities_ms) - expected_thrusts_N = np.array(expected_thrusts_N) - - # NOTE rtol = 0.01 = 1% different from truth (first timeseries) - # atol = 1 = no more than +/-1 meter difference between values - atol = 1e-2 - rtol = 1e-3 - - # Objective - assert_near_equal(times[-1], expected_times_s[-1], tolerance=rtol) - - # Flight path - - assert_timeseries_near_equal( - expected_times_s, expected_masses_kg, times, masses, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_ranges_m, times, ranges, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_velocities_ms, times, velocities, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_thrusts_N, times, thrusts, - abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_descent_large_single_aisle_2.py b/aviary/mission/flops_based/phases/benchmark_tests/test_bench_descent_large_single_aisle_2.py deleted file mode 100644 index e6378c4a2..000000000 --- a/aviary/mission/flops_based/phases/benchmark_tests/test_bench_descent_large_single_aisle_2.py +++ /dev/null @@ -1,256 +0,0 @@ -''' -NOTES: -Includes: -Descent -Computed Aero -Large Single Aisle 2 data -CURRENTLY REMOVED FROM BENCHMARK TEST SUITE -''' - -import unittest - -import dymos as dm -# Suppress the annoying warnings from matplotlib when running dymos. -import matplotlib as mpl -import numpy as np -import openmdao.api as om -import scipy.constants as _units - -from dymos.utils.testing_utils import assert_timeseries_near_equal -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.flops_based.ode.mission_ODE import MissionODE -from aviary.mission.flops_based.phases.descent_phase import Descent -from aviary.interface.default_phase_info.height_energy import prop, aero, geom -from aviary.subsystems.premission import CorePreMission -from aviary.utils.functions import set_aviary_initial_values -from aviary.utils.functions import get_path -from aviary.validation_cases.validation_tests import get_flops_inputs -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Dynamic - -mpl.rc('figure', max_open_warning=0) - - -try: - import pyoptsparse - -except ImportError: - pyoptsparse = None - - -''' -NOTE benchmark only reaches 0-3 after 50 iterations with bad plots - problem easily converges with 0-1 when velocity ref & ref0 in energyphase is changed - to 1e3. - Changing velocity ref breaks some full-mission benchmarks, so is currently not - implemented -''' - - -def run_trajectory(): - prob = om.Problem(model=om.Group()) - - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 50 - driver.opt_settings["Major optimality tolerance"] = 5e-3 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-3 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - # TODO enable coloring once issue has been resolved: - # https://github.com/OpenMDAO/OpenMDAO/issues/2507 - # driver.declare_coloring() - - ########################## - # Problem Settings # - ########################## - alt_i = 35000*_units.foot - alt_f = 35*_units.foot - v_i = 452.61*_units.knot - v_f = 198.44*_units.knot - mach_i = 0.785 - mach_f = 0.3 - mass_i = 140515*_units.pound - mass_f = 140002*_units.pound - range_i = 2830.8*_units.nautical_mile - range_f = 2960.0*_units.nautical_mile - t_i_descent = 0.0 - t_f_descent = 2000.0 - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle2FLOPS') - - descent_options = Descent( - 'test_descent', - final_altitude=alt_f, - initial_altitude=alt_i, - # note, Mach = 0.0 causes an error in aero, perhaps in other code - final_mach=mach_f, - initial_mach=mach_i, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - aviary_options=aviary_inputs, - ) - - # replace debug_no_mass flag with list of subsystem builders without mass - core_subsystems = [prop, geom, aero] - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'core_premission', - CorePreMission(aviary_options=aviary_inputs, subsystems=core_subsystems), - promotes_inputs=['aircraft:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - transcription = dm.Radau(num_segments=5, order=3, compressed=True) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - descent = descent_options.build_phase(MissionODE, transcription) - - descent.add_objective(Dynamic.Mission.RANGE, ref=-1e5, loc='final') - traj.add_phase('descent', descent) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - - prob.setup() - - # set initial conditions - prob.set_val('traj.descent.t_initial', t_i_descent, units='s') - prob.set_val('traj.descent.t_duration', t_f_descent, units='s') - - prob.set_val('traj.descent.states:altitude', descent.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i, alt_f]), units='m') - prob.set_val('traj.descent.states:velocity', descent.interp( - Dynamic.Mission.VELOCITY, ys=[v_i, v_f]), units='m/s') - prob.set_val('traj.descent.states:mass', descent.interp( - Dynamic.Mission.MASS, ys=[mass_i, mass_f]), units='kg') - prob.set_val('traj.descent.states:range', descent.interp( - Dynamic.Mission.RANGE, ys=[range_i, range_f]), units='m') - - prob.set_val('traj.descent.controls:velocity_rate', - descent.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.descent.controls:throttle', - descent.interp(Dynamic.Mission.THROTTLE, ys=[0.0, 0.0]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=False, make_plots=False, - solution_record_file='descent_large_single_aisle_2.db') - prob.record("final") - prob.cleanup() - - return prob - - -@unittest.skip('benchmark_descent_large_single_aisle_2 currently broken') -class DescentPhaseTestCase(unittest.TestCase): - def bench_test_descent_large_single_aisle_2(self): - - prob = run_trajectory() - - times = prob.get_val('traj.descent.timeseries.time', units='s') - altitudes = prob.get_val('traj.descent.timeseries.states:altitude', units='m') - masses = prob.get_val('traj.descent.timeseries.states:mass', units='kg') - ranges = prob.get_val('traj.descent.timeseries.states:range', units='m') - velocities = prob.get_val('traj.descent.timeseries.states:velocity', units='m/s') - thrusts = prob.get_val('traj.descent.timeseries.thrust_net', units='N') - - expected_times_s = [ - [0.], [142.02539503], [337.99145239], [400.01403952], - [400.01403952], [542.03943455], [738.0054919], [800.02807903], - [800.02807903], [942.05347406], [1138.01953142], [1200.04211855], - [1200.04211855], [1342.06751358], [1538.03357093], [1600.05615806], - [1600.05615806], [1742.08155309], [1938.04761045], [2000.07019758]] - expected_altitudes_m = [ - [10668.], [9548.97609287], [8538.42241675], [8275.01276129], - [8275.01276129], [7601.31902636], [6519.47805911], [6141.13701298], - [6141.13701298], [5205.26802003], [3963.90368243], [3634.87695679], - [3634.87695679], [3055.62551952], [2221.91470683], [1840.14771729], - [1840.14771729], [870.95857222], [10.668], [10.668]] - expected_masses_kg = [ - [63736.53187055], [63688.55056043], [63592.93766122], [63558.62366657], - [63558.62366657], [63480.12720033], [63368.68894184], [63331.87300229], - [63331.87300229], [63243.53017169], [63103.08546544], [63051.9162013], - [63051.9162013], [62922.36463692], [62748.94842397], [62704.12656931], - [62704.12656931], [62627.76510694], [62566.74093186], [62554.55364275]] - expected_ranges_m = [ - [5242641.6], [5275682.08024547], [5321287.43849558], - [5335722.05453227], [5335722.05453227], [5368775.18998604], - [5414378.40648958], [5428810.78044196], [5428810.78044196], - [5461857.5756481], [5507456.7101919], [5521890.39254914], - [5521890.39254914], [5554951.82532665], [5599686.46586361], - [5613370.90489204], [5613370.90489204], [5642618.8601419], - [5674799.0551216], [5682083.57281856]] - expected_velocities_ms = [ - [232.77530606], [232.77530606], [232.77530606], [232.77530606], - [232.77530606], [232.77530606], [232.77530606], [232.77530606], - [232.77530606], [232.77530606], [232.77530606], [232.77530606], - [232.77530606], [232.05265856], [223.03560479], [216.62970611], - [216.62970611], [192.53473927], [130.45545087], [102.07377559]] - expected_thrusts_N = [ - [10348.85799036], [20322.69938272], [27901.58834456], - [28262.62341568], [28262.62341568], [28062.43245824], - [28590.47833586], [28815.02361611], [28815.02361611], - [30869.45928917], [38383.90751338], [41973.2222718], - [41973.2222718], [46287.80542225], [36032.4169276], - [28755.10323868], [28755.10323868], [13691.34754408], [0.], [0.]] - - expected_times_s = np.array(expected_times_s) - expected_altitudes_m = np.array(expected_altitudes_m) - expected_masses_kg = np.array(expected_masses_kg) - expected_ranges_m = np.array(expected_ranges_m) - expected_velocities_ms = np.array(expected_velocities_ms) - expected_thrusts_N = np.array(expected_thrusts_N) - - # NOTE rtol = 0.01 = 1% different from truth (first timeseries) - # atol = 1 = no more than +/-1 meter difference between values - atol = 1e-2 - rtol = 1e-3 - - # Objective - assert_near_equal(times[-1], expected_times_s[-1], tolerance=rtol) - - # Flight path - - assert_timeseries_near_equal( - expected_times_s, expected_masses_kg, times, masses, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_ranges_m, times, ranges, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_velocities_ms, times, velocities, - abs_tolerance=atol, rel_tolerance=rtol) - assert_timeseries_near_equal( - expected_times_s, expected_thrusts_N, times, thrusts, - abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/flops_based/phases/climb_phase.py b/aviary/mission/flops_based/phases/climb_phase.py deleted file mode 100644 index 977240d90..000000000 --- a/aviary/mission/flops_based/phases/climb_phase.py +++ /dev/null @@ -1,102 +0,0 @@ -from aviary.mission.flops_based.phases.energy_phase import EnergyPhase, register - - -Climb = None # forward declaration for type hints - - -@register -class Climb(EnergyPhase): - ''' - Define a phase builder class for a typical FLOPS based climb phase. - - Attributes - ---------- - name : str ('climb') - object label - - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - - user_options : AviaryValues () - state/path constraint values and flags - - supported options: - - num_segments : int (5) - transcription: number of segments - - order : int (3) - transcription: order of the state transcription; the order of the control - transcription is `order - 1` - - fix_initial : bool (True) - - fix_initial_time : bool (None) - - initial_ref : float (1.0, 's') - note: applied if, and only if, "fix_initial_time" is unspecified - - initial_bounds : pair ((0.0, 100.0) 's') - note: applied if, and only if, "fix_initial_time" is unspecified - - duration_ref : float (1.0, 's') - - duration_bounds : pair ((0.0, 100.0) 's') - - initial_altitude : float (0.0, 'ft) - starting true altitude from mean sea level - - final_altitude : float - ending true altitude from mean sea level - - initial_mach : float (0.0, 'ft) - starting Mach number - - final_mach : float - ending Mach number - - required_altitude_rate : float (None) - minimum avaliable climb rate - - no_climb : bool (False) - aircraft is not allowed to climb during phase - - no_descent : bool (False) - aircraft is not allowed to descend during phase - - fix_range : bool (False) - - input_initial : bool (False) - - initial_guesses : AviaryValues () - state/path beginning values to be set on the problem - - supported options: - - times - - range - - altitude - - velocity - - velocity_rate - - mass - - throttle - - ode_class : type (None) - advanced: the type of system defining the ODE - - transcription : "Dymos transcription object" (None) - advanced: an object providing the transcription technique of the - optimal control problem - - external_subsystems : Sequence["subsystem builder"] () - advanced - - meta_data : dict (<"builtin" meta data>) - advanced: meta data associated with variables in the Aviary data hierarchy - - default_name : str - class attribute: derived type customization point; the default value - for name - - default_ode_class : type - class attribute: derived type customization point; the default value - for ode_class used by build_phase - - default_meta_data : dict - class attribute: derived type customization point; the default value for - meta_data - - Methods - ------- - build_phase - make_default_transcription - validate_options - assign_default_options - ''' - # region : derived type customization points - __slots__ = () - - default_name = 'climb' - # endregion : derived type customization points diff --git a/aviary/mission/flops_based/phases/cruise_phase.py b/aviary/mission/flops_based/phases/cruise_phase.py deleted file mode 100644 index 2f861c585..000000000 --- a/aviary/mission/flops_based/phases/cruise_phase.py +++ /dev/null @@ -1,437 +0,0 @@ -from math import isclose - -import dymos as dm - -from aviary.mission.flops_based.phases.phase_builder_base import ( - register, PhaseBuilderBase, InitialGuessControl, InitialGuessParameter, - InitialGuessPolynomialControl, InitialGuessState, InitialGuessTime) - -from aviary.utils.aviary_values import AviaryValues - -from aviary.variable_info.variable_meta_data import _MetaData -from aviary.variable_info.variables import Dynamic -from aviary.mission.flops_based.phases.phase_utils import add_subsystem_variables_to_phase, get_initial - - -Cruise = None # forward declaration for type hints - - -# TODO: support/handle the following in the base class -# - phase.set_time_options() -# - currently handled in level 3 interface implementation -# - self.external_subsystems -# - self.meta_data, with cls.default_meta_data customization point -class Cruise(PhaseBuilderBase): - ''' - Define a phase builder base class for typical energy phases such as climb and - descent. - - Attributes - ---------- - name : str ('energy_phase') - object label - - subsystem_options (None) - dictionary of parameters to be passed to the subsystem builders - - user_options : AviaryValues () - state/path constraint values and flags - - supported options: - - num_segments : int (5) - transcription: number of segments - - order : int (3) - transcription: order of the state transcription; the order of the control - transcription is `order - 1` - - fix_initial : bool (True) - - fix_initial_time : bool (None) - - initial_ref : float (1.0, 's') - note: applied if, and only if, "fix_initial_time" is unspecified - - initial_bounds : pair ((0.0, 100.0) 's') - note: applied if, and only if, "fix_initial_time" is unspecified - - duration_ref : float (1.0, 's') - - duration_bounds : pair ((0.0, 100.0) 's') - - min_altitude : float (0.0, 'ft) - starting true altitude from mean sea level - - max_altitude : float - ending true altitude from mean sea level - - min_mach : float (0.0, 'ft) - starting Mach number - - max_mach : float - ending Mach number - - required_available_climb_rate : float (None) - minimum avaliable climb rate - - fix_final : bool (False) - - input_initial : bool (False) - - polynomial_control_order : None or int - When set to an integer value, replace controls with - polynomial controls of that specified order. - - initial_guesses : AviaryValues () - state/path beginning values to be set on the problem - - supported options: - - times - - range - - altitude - - velocity - - velocity_rate - - mass - - throttle - - ode_class : type (None) - advanced: the type of system defining the ODE - - transcription : "Dymos transcription object" (None) - advanced: an object providing the transcription technique of the - optimal control problem - - external_subsystems : Sequence["subsystem builder"] () - advanced - - meta_data : dict (<"builtin" meta data>) - advanced: meta data associated with variables in the Aviary data hierarchy - - default_name : str - class attribute: derived type customization point; the default value - for name - - default_ode_class : type - class attribute: derived type customization point; the default value - for ode_class used by build_phase - - default_meta_data : dict - class attribute: derived type customization point; the default value for - meta_data - - Methods - ------- - build_phase - make_default_transcription - validate_options - assign_default_options - ''' - __slots__ = ('external_subsystems', 'meta_data') - - # region : derived type customization points - _meta_data_ = {} - - _initial_guesses_meta_data_ = {} - - default_name = 'cruise_phase' - - # default_ode_class = MissionODE - - default_meta_data = _MetaData - - # endregion : derived type customization points - - def __init__( - self, name=None, subsystem_options=None, user_options=None, initial_guesses=None, - ode_class=None, transcription=None, core_subsystems=None, - external_subsystems=None, meta_data=None - ): - super().__init__( - name=name, core_subsystems=core_subsystems, subsystem_options=subsystem_options, user_options=user_options, initial_guesses=initial_guesses, ode_class=ode_class, transcription=transcription) - - # TODO: support external_subsystems and meta_data in the base class - if external_subsystems is None: - external_subsystems = [] - - self.external_subsystems = external_subsystems - - if meta_data is None: - meta_data = self.default_meta_data - - self.meta_data = meta_data - - def build_phase(self, aviary_options: AviaryValues = None): - ''' - Return a new cruise phase for analysis using these constraints. - - If ode_class is None, default_ode_class is used. - - If transcription is None, the return value from calling - make_default_transcription is used. - - Parameters - ---------- - aviary_options : AviaryValues () - collection of Aircraft/Mission specific options - - Returns - ------- - dymos.Phase - ''' - phase: dm.Phase = super().build_phase(aviary_options) - - user_options: AviaryValues = self.user_options - - fix_initial = user_options.get_val('fix_initial') - min_mach = user_options.get_val('min_mach') - max_mach = user_options.get_val('max_mach') - min_altitude = user_options.get_val('min_altitude', units='m') - max_altitude = user_options.get_val('max_altitude', units='m') - fix_final = user_options.get_val('fix_final') - input_initial = user_options.get_val('input_initial') - num_engines = len(aviary_options.get_val('engine_models')) - input_initial = user_options.get_val('input_initial') - mass_f_cruise = user_options.get_val('mass_f_cruise', units='kg') - range_f_cruise = user_options.get_val('range_f_cruise', units='m') - polynomial_control_order = user_options.get_item('polynomial_control_order')[0] - - ############## - # Add States # - ############## - - input_initial_alt = get_initial(input_initial, Dynamic.Mission.ALTITUDE) - fix_initial_alt = get_initial(fix_initial, Dynamic.Mission.ALTITUDE) - phase.add_state( - Dynamic.Mission.ALTITUDE, fix_initial=fix_initial_alt, fix_final=False, - lower=0.0, ref=max_altitude, defect_ref=max_altitude, units='m', - rate_source=Dynamic.Mission.ALTITUDE_RATE, targets=Dynamic.Mission.ALTITUDE, - input_initial=input_initial_alt - ) - - input_initial_vel = get_initial(input_initial, Dynamic.Mission.VELOCITY) - fix_initial_vel = get_initial(fix_initial, Dynamic.Mission.VELOCITY) - phase.add_state( - Dynamic.Mission.VELOCITY, fix_initial=fix_initial_vel, fix_final=False, - lower=0.0, ref=1e2, defect_ref=1e2, units='m/s', - rate_source=Dynamic.Mission.VELOCITY_RATE, targets=Dynamic.Mission.VELOCITY, - input_initial=input_initial_vel - ) - - input_initial_mass = get_initial(input_initial, Dynamic.Mission.MASS) - fix_initial_mass = get_initial(fix_initial, Dynamic.Mission.MASS, True) - phase.add_state( - Dynamic.Mission.MASS, fix_initial=fix_initial_mass, fix_final=False, - lower=0.0, ref=mass_f_cruise, defect_ref=mass_f_cruise, units='kg', - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.Mission.MASS, - input_initial=input_initial_mass - ) - - input_initial_range = get_initial(input_initial, Dynamic.Mission.RANGE) - fix_initial_range = get_initial(fix_initial, Dynamic.Mission.RANGE, True) - phase.add_state( - Dynamic.Mission.RANGE, fix_initial=fix_initial_range, fix_final=fix_final, - lower=0.0, ref=range_f_cruise, defect_ref=range_f_cruise, units='m', - rate_source=Dynamic.Mission.RANGE_RATE, - input_initial=input_initial_range - ) - - phase = add_subsystem_variables_to_phase( - phase, self.name, self.external_subsystems) - - ################ - # Add Controls # - ################ - if polynomial_control_order is not None: - phase.add_polynomial_control( - Dynamic.Mission.VELOCITY_RATE, - targets=Dynamic.Mission.VELOCITY_RATE, units='m/s**2', - opt=True, lower=-2.12, upper=2.12, - order=polynomial_control_order, - ) - else: - phase.add_control( - Dynamic.Mission.VELOCITY_RATE, - targets=Dynamic.Mission.VELOCITY_RATE, units='m/s**2', - opt=True, lower=-2.12, upper=2.12, - ) - - if num_engines > 0: - if polynomial_control_order is not None: - phase.add_polynomial_control( - Dynamic.Mission.THROTTLE, - targets=Dynamic.Mission.THROTTLE, units='unitless', - opt=True, lower=0.0, upper=1.0, scaler=1, - order=polynomial_control_order, - ) - else: - phase.add_control( - Dynamic.Mission.THROTTLE, - targets=Dynamic.Mission.THROTTLE, units='unitless', - opt=True, lower=0.0, upper=1.0, scaler=1, - ) - - ################## - # Add Timeseries # - ################## - phase.add_timeseries_output( - Dynamic.Mission.MACH, output_name=Dynamic.Mission.MACH, units='unitless' - ) - - phase.add_timeseries_output( - Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' - ) - - phase.add_timeseries_output( - Dynamic.Mission.THRUST_MAX_TOTAL, - output_name=Dynamic.Mission.THRUST_MAX_TOTAL, units='lbf' - ) - - phase.add_timeseries_output( - Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' - ) - - phase.add_timeseries_output( - Dynamic.Mission.SPECIFIC_ENERGY_RATE, - output_name=Dynamic.Mission.SPECIFIC_ENERGY_RATE, units='m/s' - ) - - phase.add_timeseries_output( - Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS, - output_name=Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS, units='m/s' - ) - - phase.add_timeseries_output( - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - output_name=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, units='lbm/h' - ) - - phase.add_timeseries_output( - Dynamic.Mission.ALTITUDE_RATE, - output_name=Dynamic.Mission.ALTITUDE_RATE, units='ft/s' - ) - - phase.add_timeseries_output( - Dynamic.Mission.ALTITUDE_RATE_MAX, - output_name=Dynamic.Mission.ALTITUDE_RATE_MAX, units='ft/min' - ) - - ################### - # Add Constraints # - ################### - required_available_climb_rate = user_options.get_val( - 'required_available_climb_rate', 'm/s') - - if required_available_climb_rate is not None: - phase.add_path_constraint( - Dynamic.Mission.ALTITUDE_RATE_MAX, - ref=required_available_climb_rate, - constraint_name='altitude_rate_minimum', - lower=required_available_climb_rate, units='m/s' - ) - - if isclose(min_mach, max_mach): - phase.add_path_constraint( - Dynamic.Mission.MACH, - equals=min_mach, units='unitless', - # ref=1.e4, - ) - - else: - phase.add_path_constraint( - Dynamic.Mission.MACH, - lower=min_mach, upper=max_mach, units='unitless', - # ref=1.e4, - ) - - # avoid scaling constraints by zero - ref = max_altitude - if ref == 0: - ref = None - - if isclose(min_altitude, max_altitude): - phase.add_path_constraint( - Dynamic.Mission.ALTITUDE, - equals=min_altitude, units='m', ref=ref, - ) - - else: - phase.add_path_constraint( - Dynamic.Mission.ALTITUDE, - lower=min_altitude, upper=max_altitude, - units='m', ref=ref, - ) - - return phase - - def _extra_ode_init_kwargs(self): - """ - Return extra kwargs required for initializing the ODE. - """ - # TODO: support external_subsystems and meta_data in the base class - return { - 'external_subsystems': self.external_subsystems, - 'meta_data': self.meta_data} - - -Cruise._add_meta_data( - 'num_segments', val=1, desc='transcription: number of segments') - -Cruise._add_meta_data( - 'order', val=3, - desc='transcription: order of the state transcription; the order of the control' - ' transcription is `order - 1`') - -Cruise._add_meta_data('fix_initial', val=True) - -Cruise._add_meta_data('fix_initial_time', val=None) - -Cruise._add_meta_data('initial_ref', val=1., units='s') - -Cruise._add_meta_data('initial_bounds', val=(0., 100.), units='s') - -Cruise._add_meta_data('duration_ref', val=1., units='s') - -Cruise._add_meta_data('duration_bounds', val=(0., 100.), units='s') - -Cruise._add_meta_data( - 'min_altitude', val=0., units='m', - desc='starting true altitude from mean sea level') - -Cruise._add_meta_data( - 'max_altitude', val=None, units='m', - desc='ending true altitude from mean sea level') - -Cruise._add_meta_data('min_mach', val=0., desc='starting Mach number') - -Cruise._add_meta_data('max_mach', val=None, desc='ending Mach number') - -Cruise._add_meta_data('mass_i_cruise', val=1.e4, units='kg') - -Cruise._add_meta_data('mass_f_cruise', val=1.e4, units='kg') - -Cruise._add_meta_data('range_f_cruise', val=1.e6, units='m') - -Cruise._add_meta_data( - 'required_available_climb_rate', val=None, units='m/s', - desc='minimum avaliable climb rate') - -Cruise._add_meta_data('fix_final', val=True) - -Cruise._add_meta_data('input_initial', val=False) - -Cruise._add_meta_data('polynomial_control_order', val=None) - -Cruise._add_initial_guess_meta_data( - InitialGuessTime(), - desc='initial guess for initial time and duration specified as a tuple') - -Cruise._add_initial_guess_meta_data( - InitialGuessState('range'), - desc='initial guess for horizontal distance traveled') - -Cruise._add_initial_guess_meta_data( - InitialGuessState('altitude'), - desc='initial guess for vertical distances') - -Cruise._add_initial_guess_meta_data( - InitialGuessState('velocity'), - desc='initial guess for speed') - -Cruise._add_initial_guess_meta_data( - InitialGuessControl('velocity_rate'), - desc='initial guess for acceleration') - -Cruise._add_initial_guess_meta_data( - InitialGuessState('mass'), - desc='initial guess for mass') - -Cruise._add_initial_guess_meta_data( - InitialGuessControl('throttle'), - desc='initial guess for throttle') diff --git a/aviary/mission/flops_based/phases/descent_phase.py b/aviary/mission/flops_based/phases/descent_phase.py deleted file mode 100644 index 78ad93a90..000000000 --- a/aviary/mission/flops_based/phases/descent_phase.py +++ /dev/null @@ -1,103 +0,0 @@ -from aviary.mission.flops_based.phases.energy_phase import EnergyPhase, register - - -Descent = None # forward declaration for type hints - - -@register -class Descent(EnergyPhase): - ''' - Define a phase builder class for a typical FLOPS based descent phase. - - Attributes - ---------- - name : str ('descent') - object label - - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - - user_options : AviaryValues () - state/path constraint values and flags - - supported options: - - num_segments : int (5) - transcription: number of segments - - order : int (3) - transcription: order of the state transcription; the order of the control - transcription is `order - 1` - - fix_initial : bool (True) - - fix_initial_time : bool (None) - - initial_ref : float (1.0, 's') - note: applied if, and only if, "fix_initial_time" is unspecified - - initial_bounds : pair ((0.0, 100.0) 's') - note: applied if, and only if, "fix_initial_time" is unspecified - - duration_ref : float (1.0, 's') - - duration_bounds : pair ((0.0, 100.0) 's') - - initial_altitude : float (0.0, 'ft) - starting true altitude from mean sea level - - final_altitude : float - ending true altitude from mean sea level - - initial_mach : float (0.0, 'ft) - starting Mach number - - final_mach : float - ending Mach number - - required_altitude_rate : float (None) - minimum avaliable descent rate - - no_descent : bool (False) - aircraft is not allowed to descent during phase - - no_descent : bool (False) - aircraft is not allowed to descend during phase - - fix_range : bool (False) - - input_initial : bool (False) - - initial_guesses : AviaryValues () - state/path beginning values to be set on the problem - - supported options: - - times - - range - - altitude - - velocity - - velocity_rate - - mass - - throttle - - ode_class : type (None) - advanced: the type of system defining the ODE - - transcription : "Dymos transcription object" (None) - advanced: an object providing the transcription technique of the - optimal control problem - - external_subsystems : Sequence["subsystem builder"] () - advanced - - meta_data : dict (<"builtin" meta data>) - advanced: meta data associated with variables in the Aviary data hierarchy - - default_name : str - class attribute: derived type customization point; the default value - for name - - default_ode_class : type - class attribute: derived type customization point; the default value - for ode_class used by build_phase - - default_meta_data : dict - class attribute: derived type customization point; the default value for - meta_data - - Methods - ------- - build_phase - make_default_transcription - validate_options - assign_default_options - ''' - # region : derived type customization points - __slots__ = () - - default_name = 'descent' - - # endregion : derived type customization points diff --git a/aviary/mission/flops_based/phases/detailed_landing_phases.py b/aviary/mission/flops_based/phases/detailed_landing_phases.py index e71d350c5..0f5b255c5 100644 --- a/aviary/mission/flops_based/phases/detailed_landing_phases.py +++ b/aviary/mission/flops_based/phases/detailed_landing_phases.py @@ -32,15 +32,11 @@ TakeoffTrajectory as _TakeoffTrajectory from aviary.mission.flops_based.phases.detailed_takeoff_phases import \ _init_initial_guess_meta_data -from aviary.mission.flops_based.phases.phase_builder_base import ( - InitialGuessControl, InitialGuessParameter, InitialGuessPolynomialControl, - InitialGuessState, PhaseBuilderBase) +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessControl, InitialGuessParameter, InitialGuessPolynomialControl, InitialGuessState, InitialGuessTime from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Dynamic, Mission @_init_initial_guess_meta_data @@ -54,9 +50,6 @@ class LandingApproachToMicP3(PhaseBuilderBase): name : str ('landing_approach') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -64,7 +57,7 @@ class LandingApproachToMicP3(PhaseBuilderBase): - max_duration (100.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - lower_angle_of_attack (-10.0, 'deg') @@ -148,42 +141,42 @@ def build_phase(self, aviary_options: AviaryValues = None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, fix_final=False, - upper=0, ref=max_range, - defect_ref=max_range, units=units, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, fix_final=False, + upper=0, ref=distance_max, + defect_ref=distance_max, units=units, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=False, fix_final=False, + Dynamic.Mission.ALTITUDE, fix_initial=False, fix_final=False, ref=altitude_ref, defect_ref=altitude_ref, units=units, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, fix_final=False, + Dynamic.Mission.VELOCITY, fix_initial=False, fix_final=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) - phase.add_control(Dynamic.FLIGHT_PATH_ANGLE, opt=False, fix_initial=True) + phase.add_control(Dynamic.Mission.FLIGHT_PATH_ANGLE, opt=False, fix_initial=True) phase.add_state( - Dynamic.MASS, fix_initial=True, fix_final=False, + Dynamic.Mission.MASS, fix_initial=True, fix_final=False, lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -199,12 +192,12 @@ def build_phase(self, aviary_options: AviaryValues = None): ) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) initial_height, units = user_options.get_item('initial_height') @@ -215,7 +208,7 @@ def build_phase(self, aviary_options: AviaryValues = None): h = initial_height + airport_altitude phase.add_boundary_constraint( - Dynamic.ALTITUDE, loc='initial', equals=h, ref=h, units=units, linear=True) + Dynamic.Mission.ALTITUDE, loc='initial', equals=h, ref=h, units=units, linear=True) return phase @@ -242,7 +235,7 @@ def _extra_ode_init_kwargs(self): LandingApproachToMicP3._add_meta_data('initial_ref', val=10.0, units='s') -LandingApproachToMicP3._add_meta_data('max_range', val=1000., units='ft') +LandingApproachToMicP3._add_meta_data('distance_max', val=1000., units='ft') LandingApproachToMicP3._add_meta_data('max_velocity', val=100., units='ft/s') @@ -262,7 +255,7 @@ def _extra_ode_init_kwargs(self): LandingApproachToMicP3._add_initial_guess_meta_data(InitialGuessState('altitude')) LandingApproachToMicP3._add_initial_guess_meta_data( - InitialGuessControl(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessControl(Dynamic.Mission.FLIGHT_PATH_ANGLE)) # @_init_initial_guess_meta_data # <--- inherited from base class @@ -277,9 +270,6 @@ class LandingMicP3ToObstacle(LandingApproachToMicP3): name : str ('landing_approach') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -287,7 +277,7 @@ class LandingMicP3ToObstacle(LandingApproachToMicP3): - max_duration (100.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - lower_angle_of_attack (-10.0, 'deg') @@ -360,9 +350,9 @@ def build_phase(self, aviary_options: AviaryValues = None): # at the moment, these state options are the only differences between phases of # this class and phases of its base class - phase.set_state_options(Dynamic.RANGE, fix_final=True) - phase.set_state_options(Dynamic.VELOCITY, fix_final=True) - phase.set_state_options(Dynamic.MASS, fix_initial=False) + phase.set_state_options(Dynamic.Mission.DISTANCE, fix_final=True) + phase.set_state_options(Dynamic.Mission.VELOCITY, fix_final=True) + phase.set_state_options(Dynamic.Mission.MASS, fix_initial=False) return phase @@ -379,15 +369,12 @@ class LandingObstacleToFlare(PhaseBuilderBase): name : str ('landing_obstacle') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags supported options: - max_duration (100.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') @@ -464,51 +451,52 @@ def build_phase(self, aviary_options: AviaryValues = None): fix_initial=True, duration_bounds=(1, max_duration), units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=True, lower=0, ref=max_range, - defect_ref=max_range, units=units, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=True, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, + Dynamic.Mission.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, defect_ref=altitude_ref, units=units, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=True, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=True, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) - phase.add_control(Dynamic.FLIGHT_PATH_ANGLE, opt=False, fix_initial=False) + phase.add_control(Dynamic.Mission.FLIGHT_PATH_ANGLE, + opt=False, fix_initial=False) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) phase.add_control('angle_of_attack', opt=False, units='deg') phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) obstacle_height, units = aviary_options.get_item( @@ -524,7 +512,7 @@ def build_phase(self, aviary_options: AviaryValues = None): h = obstacle_height + airport_altitude phase.add_boundary_constraint( - Dynamic.ALTITUDE, loc='initial', equals=h, ref=h, units=units, linear=True) + Dynamic.Mission.ALTITUDE, loc='initial', equals=h, ref=h, units=units, linear=True) return phase @@ -547,7 +535,7 @@ def _extra_ode_init_kwargs(self): LandingObstacleToFlare._add_meta_data('max_duration', val=100., units='s') -LandingObstacleToFlare._add_meta_data('max_range', val=1000., units='ft') +LandingObstacleToFlare._add_meta_data('distance_max', val=1000., units='ft') LandingObstacleToFlare._add_meta_data('max_velocity', val=100., units='ft/s') @@ -559,7 +547,7 @@ def _extra_ode_init_kwargs(self): LandingObstacleToFlare._add_initial_guess_meta_data(InitialGuessState('altitude')) LandingObstacleToFlare._add_initial_guess_meta_data( - InitialGuessControl(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessControl(Dynamic.Mission.FLIGHT_PATH_ANGLE)) @_init_initial_guess_meta_data @@ -573,9 +561,6 @@ class LandingFlareToTouchdown(PhaseBuilderBase): name : str ('landing_flare') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -583,7 +568,7 @@ class LandingFlareToTouchdown(PhaseBuilderBase): - max_duration (100.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - lower_angle_of_attack (-10.0, 'deg') @@ -666,42 +651,43 @@ def build_phase(self, aviary_options: AviaryValues = None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=False, fix_final=True, + Dynamic.Mission.ALTITUDE, fix_initial=False, fix_final=True, lower=0, ref=altitude_ref, defect_ref=altitude_ref, units=units, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) - phase.add_control(Dynamic.FLIGHT_PATH_ANGLE, fix_initial=False, opt=False) + phase.add_control(Dynamic.Mission.FLIGHT_PATH_ANGLE, + fix_initial=False, opt=False) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) # TODO: Upper limit is a bit of a hack. It hopefully won't be needed if we # can get some other constraints working. phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', lower=0.0, upper=0.2, opt=True ) @@ -719,12 +705,12 @@ def build_phase(self, aviary_options: AviaryValues = None): ) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) phase.add_timeseries_output( @@ -765,7 +751,7 @@ def _extra_ode_init_kwargs(self): LandingFlareToTouchdown._add_meta_data('initial_ref', val=10.0, units='s') -LandingFlareToTouchdown._add_meta_data('max_range', val=1000., units='ft') +LandingFlareToTouchdown._add_meta_data('distance_max', val=1000., units='ft') LandingFlareToTouchdown._add_meta_data('max_velocity', val=100., units='ft/s') @@ -783,7 +769,7 @@ def _extra_ode_init_kwargs(self): LandingFlareToTouchdown._add_initial_guess_meta_data(InitialGuessState('altitude')) LandingFlareToTouchdown._add_initial_guess_meta_data( - InitialGuessControl(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessControl(Dynamic.Mission.FLIGHT_PATH_ANGLE)) @_init_initial_guess_meta_data @@ -796,9 +782,6 @@ class LandingTouchdownToNoseDown(PhaseBuilderBase): name : str ('landing_touchdown') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -806,7 +789,7 @@ class LandingTouchdownToNoseDown(PhaseBuilderBase): - max_duration (5.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - max_angle_of_attack (10.0, 'deg') @@ -884,30 +867,30 @@ def build_phase(self, aviary_options=None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, + rate_source=Dynamic.Mission.DISTANCE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -920,12 +903,12 @@ def build_phase(self, aviary_options=None): fix_initial=False, ref=max_angle_of_attack) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) return phase @@ -953,7 +936,7 @@ def _extra_ode_init_kwargs(self): LandingTouchdownToNoseDown._add_meta_data('initial_ref', val=10.0, units='s') -LandingTouchdownToNoseDown._add_meta_data('max_range', val=1000.0, units='ft') +LandingTouchdownToNoseDown._add_meta_data('distance_max', val=1000.0, units='ft') LandingTouchdownToNoseDown._add_meta_data('max_velocity', val=100.0, units='ft/s') @@ -973,16 +956,13 @@ class LandingNoseDownToStop(PhaseBuilderBase): name : str ('landing_stop') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags supported options: - max_duration - duration_ref - - max_range + - distance_max - max_velocity initial_guesses : AviaryValues () @@ -1059,45 +1039,45 @@ def build_phase(self, aviary_options=None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, fix_final=False, - lower=0, ref=max_range, - defect_ref=max_range, units=units, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, fix_final=False, + lower=0, ref=distance_max, + defect_ref=distance_max, units=units, + rate_source=Dynamic.Mission.DISTANCE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, fix_final=True, + Dynamic.Mission.VELOCITY, fix_initial=False, fix_final=True, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) # TODO: Energy phase places this under an if num_engines > 0. phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) phase.add_parameter('angle_of_attack', val=0.0, opt=False, units='deg') phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) return phase @@ -1125,7 +1105,7 @@ def _extra_ode_init_kwargs(self): LandingNoseDownToStop._add_meta_data('initial_ref', val=10.0, units='s') -LandingNoseDownToStop._add_meta_data('max_range', val=1000.0, units='ft') +LandingNoseDownToStop._add_meta_data('distance_max', val=1000.0, units='ft') LandingNoseDownToStop._add_meta_data('max_velocity', val=100.0, units='ft/s') @@ -1314,7 +1294,7 @@ def _add_phases(self, aviary_options: AviaryValues): def _link_phases(self): traj: dm.Trajectory = self._traj - basic_vars = ['time', 'range', 'velocity', 'mass'] + basic_vars = ['time', 'distance', 'velocity', 'mass'] ext_vars = basic_vars + ['angle_of_attack'] full_vars = ext_vars + ['altitude'] @@ -1329,7 +1309,7 @@ def _link_phases(self): obstacle_vars = ['mass', 'time', 'altitude', 'angle_of_attack'] traj.link_phases([p3_obstacle_name, obstacle_name], vars=obstacle_vars) - pre_obs_vars = obstacle_vars + ['range', 'velocity'] + pre_obs_vars = obstacle_vars + ['distance', 'velocity'] traj.link_phases([approach_p3_name, p3_obstacle_name], vars=pre_obs_vars) flare_name = self._flare_to_touchdown.name diff --git a/aviary/mission/flops_based/phases/detailed_takeoff_phases.py b/aviary/mission/flops_based/phases/detailed_takeoff_phases.py index 742c774f8..ea9dc73b5 100644 --- a/aviary/mission/flops_based/phases/detailed_takeoff_phases.py +++ b/aviary/mission/flops_based/phases/detailed_takeoff_phases.py @@ -46,15 +46,11 @@ import openmdao.api as om from aviary.mission.flops_based.ode.takeoff_ode import TakeoffODE -from aviary.mission.flops_based.phases.phase_builder_base import ( - InitialGuessControl, InitialGuessParameter, InitialGuessPolynomialControl, - InitialGuessState, InitialGuessTime, PhaseBuilderBase) +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessControl, InitialGuessParameter, InitialGuessPolynomialControl, InitialGuessState, InitialGuessTime from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Dynamic, Mission def _init_initial_guess_meta_data(cls: PhaseBuilderBase): @@ -68,7 +64,7 @@ def _init_initial_guess_meta_data(cls: PhaseBuilderBase): desc='initial guess for initial time and duration specified as a tuple') cls._add_initial_guess_meta_data( - InitialGuessState('range'), + InitialGuessState('distance'), desc='initial guess for horizontal distance traveled') cls._add_initial_guess_meta_data( @@ -98,16 +94,13 @@ class TakeoffBrakeReleaseToDecisionSpeed(PhaseBuilderBase): name : str ('takeoff_brake_release') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags supported options: - max_duration (1000.0, 's') - duration_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') initial_guesses : AviaryValues () @@ -182,43 +175,43 @@ def build_phase(self, aviary_options=None): fix_initial=True, duration_bounds=(1, max_duration), duration_ref=duration_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=True, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=True, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=True, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=True, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) phase.add_state( - Dynamic.MASS, fix_initial=True, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=True, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) # TODO: Energy phase places this under an if num_engines > 0. phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) phase.add_parameter('angle_of_attack', val=0.0, opt=False, units='deg') phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) return phase @@ -244,7 +237,7 @@ def _extra_ode_init_kwargs(self): TakeoffBrakeReleaseToDecisionSpeed._add_meta_data('duration_ref', val=10.0, units='s') -TakeoffBrakeReleaseToDecisionSpeed._add_meta_data('max_range', val=1000.0, units='ft') +TakeoffBrakeReleaseToDecisionSpeed._add_meta_data('distance_max', val=1000.0, units='ft') TakeoffBrakeReleaseToDecisionSpeed._add_meta_data( 'max_velocity', val=100.0, units='ft/s') @@ -264,9 +257,6 @@ class TakeoffDecisionSpeedToRotate(PhaseBuilderBase): name : str ('takeoff_decision_speed') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -274,7 +264,7 @@ class TakeoffDecisionSpeedToRotate(PhaseBuilderBase): - max_duration (1000.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') initial_guesses : AviaryValues () @@ -352,31 +342,31 @@ def build_phase(self, aviary_options=None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) # TODO: Energy phase places this under an if num_engines > 0. phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -385,12 +375,12 @@ def build_phase(self, aviary_options=None): phase.add_parameter('angle_of_attack', val=0.0, opt=False, units='deg') phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( @@ -422,7 +412,7 @@ def _extra_ode_init_kwargs(self): TakeoffDecisionSpeedToRotate._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffDecisionSpeedToRotate._add_meta_data('max_range', val=1000.0, units='ft') +TakeoffDecisionSpeedToRotate._add_meta_data('distance_max', val=1000.0, units='ft') TakeoffDecisionSpeedToRotate._add_meta_data('max_velocity', val=100.0, units='ft/s') @@ -441,9 +431,6 @@ class TakeoffDecisionSpeedBrakeDelay(TakeoffDecisionSpeedToRotate): name : str ('takeoff_decision_speed') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -451,7 +438,7 @@ class TakeoffDecisionSpeedBrakeDelay(TakeoffDecisionSpeedToRotate): - max_duration (1000.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') initial_guesses : AviaryValues () @@ -526,7 +513,7 @@ def build_phase(self, aviary_options=None): TakeoffDecisionSpeedBrakeDelay._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffDecisionSpeedBrakeDelay._add_meta_data('max_range', val=1000.0, units='ft') +TakeoffDecisionSpeedBrakeDelay._add_meta_data('distance_max', val=1000.0, units='ft') TakeoffDecisionSpeedBrakeDelay._add_meta_data('max_velocity', val=100.0, units='ft/s') @@ -544,9 +531,6 @@ class TakeoffRotateToLiftoff(PhaseBuilderBase): name : str ('takeoff_rotate') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -554,7 +538,7 @@ class TakeoffRotateToLiftoff(PhaseBuilderBase): - max_duration (5.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - max_angle_of_attack (10.0, 'deg') @@ -633,32 +617,32 @@ def build_phase(self, aviary_options=None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) max_angle_of_attack, units = user_options.get_item('max_angle_of_attack') phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -668,12 +652,12 @@ def build_phase(self, aviary_options=None): ref=max_angle_of_attack) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) phase.add_timeseries_output( @@ -705,7 +689,7 @@ def _extra_ode_init_kwargs(self): TakeoffRotateToLiftoff._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffRotateToLiftoff._add_meta_data('max_range', val=1000.0, units='ft') +TakeoffRotateToLiftoff._add_meta_data('distance_max', val=1000.0, units='ft') TakeoffRotateToLiftoff._add_meta_data('max_velocity', val=100.0, units='ft/s') @@ -726,9 +710,6 @@ class TakeoffLiftoffToObstacle(PhaseBuilderBase): name : str ('takeoff_liftoff') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -736,7 +717,7 @@ class TakeoffLiftoffToObstacle(PhaseBuilderBase): - max_duration (100.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - flight_path_angle_ref (5., 'deg') @@ -821,45 +802,45 @@ def build_phase(self, aviary_options: AviaryValues = None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=True, lower=0, ref=altitude_ref, + Dynamic.Mission.ALTITUDE, fix_initial=True, lower=0, ref=altitude_ref, defect_ref=altitude_ref, units=units, upper=altitude_ref, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) flight_path_angle_ref, units = user_options.get_item('flight_path_angle_ref') phase.add_state( - Dynamic.FLIGHT_PATH_ANGLE, fix_initial=True, lower=0, + Dynamic.Mission.FLIGHT_PATH_ANGLE, fix_initial=True, lower=0, ref=flight_path_angle_ref, upper=flight_path_angle_ref, defect_ref=flight_path_angle_ref, units=units, - rate_source=Dynamic.FLIGHT_PATH_ANGLE_RATE) + rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -873,12 +854,12 @@ def build_phase(self, aviary_options: AviaryValues = None): ref=angle_of_attack_ref) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) obstacle_height, units = aviary_options.get_item( @@ -894,7 +875,7 @@ def build_phase(self, aviary_options: AviaryValues = None): h = obstacle_height + airport_altitude phase.add_boundary_constraint( - Dynamic.ALTITUDE, loc='final', equals=h, ref=h, units=units, linear=True) + Dynamic.Mission.ALTITUDE, loc='final', equals=h, ref=h, units=units, linear=True) phase.add_path_constraint( 'v_over_v_stall', lower=1.25, ref=2.0) @@ -927,7 +908,7 @@ def _extra_ode_init_kwargs(self): TakeoffLiftoffToObstacle._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffLiftoffToObstacle._add_meta_data('max_range', val=1000., units='ft') +TakeoffLiftoffToObstacle._add_meta_data('distance_max', val=1000., units='ft') TakeoffLiftoffToObstacle._add_meta_data('max_velocity', val=100., units='ft/s') @@ -947,7 +928,7 @@ def _extra_ode_init_kwargs(self): TakeoffLiftoffToObstacle._add_initial_guess_meta_data(InitialGuessState('altitude')) TakeoffLiftoffToObstacle._add_initial_guess_meta_data( - InitialGuessState(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessState(Dynamic.Mission.FLIGHT_PATH_ANGLE)) @_init_initial_guess_meta_data @@ -961,9 +942,6 @@ class TakeoffObstacleToMicP2(PhaseBuilderBase): name : str ('takeoff_climb') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -971,7 +949,7 @@ class TakeoffObstacleToMicP2(PhaseBuilderBase): - max_duration (100.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - flight_path_angle_ref (5., 'deg') @@ -1057,45 +1035,45 @@ def build_phase(self, aviary_options: AviaryValues = None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, + Dynamic.Mission.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, defect_ref=altitude_ref, units=units, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) flight_path_angle_ref, units = user_options.get_item('flight_path_angle_ref') phase.add_state( - Dynamic.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, + Dynamic.Mission.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, ref=flight_path_angle_ref, defect_ref=flight_path_angle_ref, units=units, - rate_source=Dynamic.FLIGHT_PATH_ANGLE_RATE) + rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -1109,23 +1087,23 @@ def build_phase(self, aviary_options: AviaryValues = None): ref=angle_of_attack_ref) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) - final_alt, units = user_options.get_item('mic_altitude') + final_altitude, units = user_options.get_item('mic_altitude') airport_altitude = aviary_options.get_val( Mission.Takeoff.AIRPORT_ALTITUDE, units) - h = final_alt + airport_altitude + h = final_altitude + airport_altitude phase.add_boundary_constraint( - Dynamic.ALTITUDE, loc='final', equals=h, ref=h, units=units, linear=True) + Dynamic.Mission.ALTITUDE, loc='final', equals=h, ref=h, units=units, linear=True) phase.add_boundary_constraint( 'v_over_v_stall', loc='final', lower=1.25, ref=1.25) @@ -1157,7 +1135,7 @@ def _extra_ode_init_kwargs(self): TakeoffObstacleToMicP2._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffObstacleToMicP2._add_meta_data('max_range', val=1000., units='ft') +TakeoffObstacleToMicP2._add_meta_data('distance_max', val=1000., units='ft') TakeoffObstacleToMicP2._add_meta_data('max_velocity', val=100., units='ft/s') @@ -1179,7 +1157,7 @@ def _extra_ode_init_kwargs(self): TakeoffObstacleToMicP2._add_initial_guess_meta_data(InitialGuessState('altitude')) TakeoffObstacleToMicP2._add_initial_guess_meta_data( - InitialGuessState(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessState(Dynamic.Mission.FLIGHT_PATH_ANGLE)) @_init_initial_guess_meta_data @@ -1193,9 +1171,6 @@ class TakeoffMicP2ToEngineCutback(PhaseBuilderBase): name : str ('takeoff_climb') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -1203,7 +1178,7 @@ class TakeoffMicP2ToEngineCutback(PhaseBuilderBase): - max_duration (100.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - flight_path_angle_ref (5., 'deg') @@ -1289,45 +1264,45 @@ def build_phase(self, aviary_options: AviaryValues = None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, + Dynamic.Mission.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, defect_ref=altitude_ref, units=units, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) flight_path_angle_ref, units = user_options.get_item('flight_path_angle_ref') phase.add_state( - Dynamic.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, + Dynamic.Mission.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, ref=flight_path_angle_ref, defect_ref=flight_path_angle_ref, units=units, - rate_source=Dynamic.FLIGHT_PATH_ANGLE_RATE) + rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -1341,22 +1316,22 @@ def build_phase(self, aviary_options: AviaryValues = None): ref=angle_of_attack_ref) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) # start engine cutback phase at this range, where this phase ends - # TODO: what is the difference between max_range and final_range? - # - should final_range replace max_range? + # TODO: what is the difference between distance_max and final_range? + # - should final_range replace distance_max? # - is there any reason to support both in this phase? final_range, units = user_options.get_item('final_range') phase.add_boundary_constraint( - Dynamic.RANGE, loc='final', equals=final_range, ref=final_range, + Dynamic.Mission.DISTANCE, loc='final', equals=final_range, ref=final_range, units=units, linear=True) phase.add_boundary_constraint( @@ -1389,7 +1364,7 @@ def _extra_ode_init_kwargs(self): TakeoffMicP2ToEngineCutback._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffMicP2ToEngineCutback._add_meta_data('max_range', val=1000., units='ft') +TakeoffMicP2ToEngineCutback._add_meta_data('distance_max', val=1000., units='ft') TakeoffMicP2ToEngineCutback._add_meta_data('max_velocity', val=100., units='ft/s') @@ -1412,7 +1387,7 @@ def _extra_ode_init_kwargs(self): TakeoffMicP2ToEngineCutback._add_initial_guess_meta_data(InitialGuessState('altitude')) TakeoffMicP2ToEngineCutback._add_initial_guess_meta_data( - InitialGuessState(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessState(Dynamic.Mission.FLIGHT_PATH_ANGLE)) @_init_initial_guess_meta_data @@ -1426,15 +1401,12 @@ class TakeoffEngineCutback(PhaseBuilderBase): name : str ('takeoff_climb') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags supported options: - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - flight_path_angle_ref (5., 'deg') @@ -1517,45 +1489,45 @@ def build_phase(self, aviary_options: AviaryValues = None): initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, + Dynamic.Mission.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, defect_ref=altitude_ref, units=units, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) flight_path_angle_ref, units = user_options.get_item('flight_path_angle_ref') phase.add_state( - Dynamic.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, + Dynamic.Mission.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, ref=flight_path_angle_ref, defect_ref=flight_path_angle_ref, units=units, - rate_source=Dynamic.FLIGHT_PATH_ANGLE_RATE) + rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -1569,12 +1541,12 @@ def build_phase(self, aviary_options: AviaryValues = None): ref=angle_of_attack_ref) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) phase.add_boundary_constraint( @@ -1603,7 +1575,7 @@ def _extra_ode_init_kwargs(self): TakeoffEngineCutback._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffEngineCutback._add_meta_data('max_range', val=1000., units='ft') +TakeoffEngineCutback._add_meta_data('distance_max', val=1000., units='ft') TakeoffEngineCutback._add_meta_data('max_velocity', val=100., units='ft/s') @@ -1623,7 +1595,7 @@ def _extra_ode_init_kwargs(self): TakeoffEngineCutback._add_initial_guess_meta_data(InitialGuessState('altitude')) TakeoffEngineCutback._add_initial_guess_meta_data( - InitialGuessState(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessState(Dynamic.Mission.FLIGHT_PATH_ANGLE)) @_init_initial_guess_meta_data @@ -1637,9 +1609,6 @@ class TakeoffEngineCutbackToMicP1(PhaseBuilderBase): name : str ('takeoff_climb') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -1647,7 +1616,7 @@ class TakeoffEngineCutbackToMicP1(PhaseBuilderBase): - max_duration (100.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - flight_path_angle_ref (5., 'deg') @@ -1733,45 +1702,45 @@ def build_phase(self, aviary_options: AviaryValues = None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, + Dynamic.Mission.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, defect_ref=altitude_ref, units=units, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) flight_path_angle_ref, units = user_options.get_item('flight_path_angle_ref') phase.add_state( - Dynamic.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, + Dynamic.Mission.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, ref=flight_path_angle_ref, defect_ref=flight_path_angle_ref, units=units, - rate_source=Dynamic.FLIGHT_PATH_ANGLE_RATE) + rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -1785,18 +1754,18 @@ def build_phase(self, aviary_options: AviaryValues = None): ref=angle_of_attack_ref) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) mic_range, units = user_options.get_item('mic_range') phase.add_boundary_constraint( - Dynamic.RANGE, loc='final', equals=mic_range, ref=mic_range, + Dynamic.Mission.DISTANCE, loc='final', equals=mic_range, ref=mic_range, units=units, linear=True) phase.add_boundary_constraint( @@ -1829,7 +1798,7 @@ def _extra_ode_init_kwargs(self): TakeoffEngineCutbackToMicP1._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffEngineCutbackToMicP1._add_meta_data('max_range', val=1000., units='ft') +TakeoffEngineCutbackToMicP1._add_meta_data('distance_max', val=1000., units='ft') TakeoffEngineCutbackToMicP1._add_meta_data('max_velocity', val=100., units='ft/s') @@ -1852,7 +1821,7 @@ def _extra_ode_init_kwargs(self): TakeoffEngineCutbackToMicP1._add_initial_guess_meta_data(InitialGuessState('altitude')) TakeoffEngineCutbackToMicP1._add_initial_guess_meta_data( - InitialGuessState(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessState(Dynamic.Mission.FLIGHT_PATH_ANGLE)) @_init_initial_guess_meta_data @@ -1866,9 +1835,6 @@ class TakeoffMicP1ToClimb(PhaseBuilderBase): name : str ('takeoff_climb') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -1876,7 +1842,7 @@ class TakeoffMicP1ToClimb(PhaseBuilderBase): - max_duration (100.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') - altitude_ref (1.0, 'ft') - flight_path_angle_ref (5., 'deg') @@ -1962,45 +1928,45 @@ def build_phase(self, aviary_options: AviaryValues = None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) altitude_ref, units = user_options.get_item('altitude_ref') phase.add_state( - Dynamic.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, + Dynamic.Mission.ALTITUDE, fix_initial=False, lower=0, ref=altitude_ref, defect_ref=altitude_ref, units=units, - rate_source=Dynamic.ALTITUDE_RATE) + rate_source=Dynamic.Mission.ALTITUDE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, + Dynamic.Mission.VELOCITY, fix_initial=False, lower=0, ref=max_velocity, defect_ref=max_velocity, units=units, upper=max_velocity, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) flight_path_angle_ref, units = user_options.get_item('flight_path_angle_ref') phase.add_state( - Dynamic.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, + Dynamic.Mission.FLIGHT_PATH_ANGLE, fix_initial=False, lower=0, ref=flight_path_angle_ref, defect_ref=flight_path_angle_ref, units=units, - rate_source=Dynamic.FLIGHT_PATH_ANGLE_RATE) + rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -2014,18 +1980,18 @@ def build_phase(self, aviary_options: AviaryValues = None): ref=angle_of_attack_ref) phase.add_timeseries_output( - Dynamic.DRAG, output_name=Dynamic.DRAG, units='lbf' + Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) phase.add_timeseries_output( - Dynamic.THRUST_TOTAL, - output_name=Dynamic.THRUST_TOTAL, units='lbf' + Dynamic.Mission.THRUST_TOTAL, + output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) mic_range, units = user_options.get_item('mic_range') phase.add_boundary_constraint( - Dynamic.RANGE, loc='final', equals=mic_range, ref=mic_range, + Dynamic.Mission.DISTANCE, loc='final', equals=mic_range, ref=mic_range, units=units, linear=True) phase.add_boundary_constraint( @@ -2058,7 +2024,7 @@ def _extra_ode_init_kwargs(self): TakeoffMicP1ToClimb._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffMicP1ToClimb._add_meta_data('max_range', val=1000., units='ft') +TakeoffMicP1ToClimb._add_meta_data('distance_max', val=1000., units='ft') TakeoffMicP1ToClimb._add_meta_data('max_velocity', val=100., units='ft/s') @@ -2080,7 +2046,7 @@ def _extra_ode_init_kwargs(self): TakeoffMicP1ToClimb._add_initial_guess_meta_data(InitialGuessState('altitude')) TakeoffMicP1ToClimb._add_initial_guess_meta_data( - InitialGuessState(Dynamic.FLIGHT_PATH_ANGLE)) + InitialGuessState(Dynamic.Mission.FLIGHT_PATH_ANGLE)) @_init_initial_guess_meta_data @@ -2094,9 +2060,6 @@ class TakeoffBrakeToAbort(PhaseBuilderBase): name : str ('takeoff_abort') object label - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -2104,7 +2067,7 @@ class TakeoffBrakeToAbort(PhaseBuilderBase): - max_duration (1000.0, 's') - duration_ref (1.0, 's') - initial_ref (10.0, 's') - - max_range (1000.0, 'ft') + - distance_max (1000.0, 'ft') - max_velocity (100.0, 'ft/s') initial_guesses : AviaryValues () @@ -2182,31 +2145,31 @@ def build_phase(self, aviary_options=None): duration_ref=duration_ref, initial_ref=initial_ref, units=units) - max_range, units = user_options.get_item('max_range') + distance_max, units = user_options.get_item('distance_max') phase.add_state( - Dynamic.RANGE, fix_initial=False, lower=0, ref=max_range, - defect_ref=max_range, units=units, upper=max_range, - rate_source=Dynamic.RANGE_RATE) + Dynamic.Mission.DISTANCE, fix_initial=False, lower=0, ref=distance_max, + defect_ref=distance_max, units=units, upper=distance_max, + rate_source=Dynamic.Mission.DISTANCE_RATE) max_velocity, units = user_options.get_item('max_velocity') phase.add_state( - Dynamic.VELOCITY, fix_initial=False, fix_final=True, + Dynamic.Mission.VELOCITY, fix_initial=False, fix_final=True, lower=0, ref=max_velocity, upper=max_velocity, defect_ref=max_velocity, units=units, - rate_source=Dynamic.VELOCITY_RATE) + rate_source=Dynamic.Mission.VELOCITY_RATE) phase.add_state( - Dynamic.MASS, fix_initial=False, fix_final=False, - lower=0.0, ref=5e4, defect_ref=5e4, units='kg', - rate_source=Dynamic.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.MASS, + Dynamic.Mission.MASS, fix_initial=False, fix_final=False, + lower=0.0, upper=1e9, ref=5e4, defect_ref=5e4, units='kg', + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, ) phase.add_control( - Dynamic.THROTTLE, - targets=Dynamic.THROTTLE, units='unitless', + Dynamic.Mission.THROTTLE, + targets=Dynamic.Mission.THROTTLE, units='unitless', opt=False ) @@ -2237,7 +2200,7 @@ def _extra_ode_init_kwargs(self): TakeoffBrakeToAbort._add_meta_data('initial_ref', val=10.0, units='s') -TakeoffBrakeToAbort._add_meta_data('max_range', val=1000.0, units='ft') +TakeoffBrakeToAbort._add_meta_data('distance_max', val=1000.0, units='ft') TakeoffBrakeToAbort._add_meta_data('max_velocity', val=100.0, units='ft/s') @@ -2519,7 +2482,7 @@ def _link_phases(self): brake_release_name = self._brake_release_to_decision_speed.name decision_speed_name = self._decision_speed_to_rotate.name - basic_vars = ['time', 'range', 'velocity', 'mass'] + basic_vars = ['time', 'distance', 'velocity', 'mass'] traj.link_phases([brake_release_name, decision_speed_name], vars=basic_vars) @@ -2542,7 +2505,7 @@ def _link_phases(self): engine_cutback_to_mic_p1_name = self._engine_cutback_to_mic_p1.name mic_p1_to_climb_name = self._mic_p1_to_climb.name - acoustics_vars = ext_vars + [Dynamic.FLIGHT_PATH_ANGLE, 'altitude'] + acoustics_vars = ext_vars + [Dynamic.Mission.FLIGHT_PATH_ANGLE, 'altitude'] traj.link_phases( [liftoff_name, obstacle_to_mic_p2_name], @@ -2574,8 +2537,8 @@ def _link_phases(self): traj.link_phases([brake_name, abort_name], vars=basic_vars) traj.add_linkage_constraint( - phase_a=abort_name, var_a='range', loc_a='final', - phase_b=liftoff_name, var_b='range', loc_b='final', + phase_a=abort_name, var_a='distance', loc_a='final', + phase_b=liftoff_name, var_b='distance', loc_b='final', ref=self._balanced_field_ref) def _add_phase(self, phase_builder: PhaseBuilderBase, aviary_options: AviaryValues): diff --git a/aviary/mission/flops_based/phases/energy_phase.py b/aviary/mission/flops_based/phases/energy_phase.py index 94ac79e4a..0b637e75a 100644 --- a/aviary/mission/flops_based/phases/energy_phase.py +++ b/aviary/mission/flops_based/phases/energy_phase.py @@ -1,15 +1,13 @@ -from math import isclose - import dymos as dm -from aviary.mission.flops_based.phases.phase_builder_base import ( - register, PhaseBuilderBase, InitialGuessControl, InitialGuessParameter, - InitialGuessPolynomialControl, InitialGuessState, InitialGuessTime) +from aviary.mission.phase_builder_base import PhaseBuilderBase, register +from aviary.mission.initial_guess_builders import InitialGuessState, InitialGuessTime, InitialGuessControl from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.variable_meta_data import _MetaData from aviary.mission.flops_based.phases.phase_utils import add_subsystem_variables_to_phase, get_initial from aviary.variable_info.variables import Dynamic +from aviary.mission.flops_based.ode.mission_ODE import MissionODE # TODO: support/handle the following in the base class @@ -17,10 +15,10 @@ # - currently handled in level 3 interface implementation # - self.external_subsystems # - self.meta_data, with cls.default_meta_data customization point +@register class EnergyPhase(PhaseBuilderBase): ''' - Define a phase builder base class for typical energy phases such as climb and - descent. + A phase builder for a simple energy phase. Attributes ---------- @@ -40,40 +38,21 @@ class EnergyPhase(PhaseBuilderBase): transcription: order of the state transcription; the order of the control transcription is `order - 1` - fix_initial : bool (True) - - fix_initial_time : bool (None) - initial_ref : float (1.0, 's') - note: applied if, and only if, "fix_initial_time" is unspecified - initial_bounds : pair ((0.0, 100.0) 's') - note: applied if, and only if, "fix_initial_time" is unspecified - duration_ref : float (1.0, 's') - duration_bounds : pair ((0.0, 100.0) 's') - - initial_altitude : float (0.0, 'ft) - starting true altitude from mean sea level - - final_altitude : float - ending true altitude from mean sea level - - initial_mach : float (0.0, 'ft) - starting Mach number - - final_mach : float - ending Mach number - - required_altitude_rate : float (None) + - required_available_climb_rate : float (None) minimum avaliable climb rate - - no_climb : bool (False) - aircraft is not allowed to climb during phase - - no_descent : bool (False) - aircraft is not allowed to descend during phase - - fix_range : bool (False) + - constrain_final : bool (False) - input_initial : bool (False) - - polynomial_control_order : None or int - When set to an integer value, replace controls with - polynomial controls of that specified order. - initial_guesses : AviaryValues () state/path beginning values to be set on the problem supported options: - times - - range + - distance - altitude - velocity - velocity_rate @@ -119,9 +98,9 @@ class attribute: derived type customization point; the default value for _initial_guesses_meta_data_ = {} - default_name = 'energy_phase' + default_name = 'cruise' - # default_ode_class = MissionODE + default_ode_class = MissionODE default_meta_data = _MetaData # endregion : derived type customization points @@ -168,60 +147,28 @@ def build_phase(self, aviary_options: AviaryValues = None): user_options: AviaryValues = self.user_options fix_initial = user_options.get_val('fix_initial') - initial_mach = user_options.get_val('initial_mach') - final_mach = user_options.get_val('final_mach') - initial_altitude = user_options.get_val('initial_altitude', 'm') - final_altitude = user_options.get_val('final_altitude', 'm') - no_descent = user_options.get_val('no_descent') - no_climb = user_options.get_val('no_climb') - fix_range = user_options.get_val('fix_range') + constrain_final = user_options.get_val('constrain_final') + optimize_mach = user_options.get_val('optimize_mach') + optimize_altitude = user_options.get_val('optimize_altitude') input_initial = user_options.get_val('input_initial') polynomial_control_order = user_options.get_item('polynomial_control_order')[0] - num_engines = len(aviary_options.get_val('engine_models')) - - # NOTE currently defaulting to climb if altitudes match. Review at future date - climb = final_altitude >= initial_altitude - descent = initial_altitude > final_altitude - - max_altitude = max(initial_altitude, final_altitude) - min_altitude = min(initial_altitude, final_altitude) - max_mach = max(initial_mach, final_mach) - min_mach = min(initial_mach, final_mach) - - if climb: - lower_accel = 0.0 - else: - lower_accel = -2.12 - - if descent: - upper_accel = 0.0 - else: - upper_accel = 2.12 + use_polynomial_control = user_options.get_val('use_polynomial_control') + throttle_enforcement = user_options.get_val('throttle_enforcement') + mach_bounds = user_options.get_item('mach_bounds') + altitude_bounds = user_options.get_item('altitude_bounds') + initial_mach = user_options.get_item('initial_mach')[0] + final_mach = user_options.get_item('final_mach')[0] + initial_altitude = user_options.get_item('initial_altitude')[0] + final_altitude = user_options.get_item('final_altitude')[0] + solve_for_distance = user_options.get_val('solve_for_distance') + no_descent = user_options.get_val('no_descent') + no_climb = user_options.get_val('no_climb') ############## # Add States # ############## # TODO: critically think about how we should handle fix_initial and input_initial defaults. # In keeping with Dymos standards, the default should be False instead of True. - - input_initial_alt = get_initial(input_initial, Dynamic.Mission.ALTITUDE) - fix_initial_alt = get_initial(fix_initial, Dynamic.Mission.ALTITUDE) - phase.add_state( - Dynamic.Mission.ALTITUDE, fix_initial=fix_initial_alt, fix_final=False, - lower=0.0, ref=1e4, defect_ref=1e4, units='m', - rate_source=Dynamic.Mission.ALTITUDE_RATE, targets=Dynamic.Mission.ALTITUDE, - input_initial=input_initial_alt - ) - - input_initial_vel = get_initial(input_initial, Dynamic.Mission.VELOCITY) - fix_initial_vel = get_initial(fix_initial, Dynamic.Mission.VELOCITY) - phase.add_state( - Dynamic.Mission.VELOCITY, fix_initial=fix_initial_vel, fix_final=False, - lower=0.0, ref=1e2, defect_ref=1e2, units='m/s', - rate_source=Dynamic.Mission.VELOCITY_RATE, targets=Dynamic.Mission.VELOCITY, - input_initial=input_initial_vel - ) - input_initial_mass = get_initial(input_initial, Dynamic.Mission.MASS) fix_initial_mass = get_initial(fix_initial, Dynamic.Mission.MASS, True) @@ -236,19 +183,21 @@ def build_phase(self, aviary_options: AviaryValues = None): phase.add_state( Dynamic.Mission.MASS, fix_initial=fix_initial_mass, fix_final=False, - lower=0.0, ref=1e4, defect_ref=1e4, units='kg', + lower=0.0, ref=1e4, defect_ref=1e6, units='kg', rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, targets=Dynamic.Mission.MASS, - input_initial=input_initial_mass + input_initial=input_initial_mass, + # solve_segments='forward', ) - input_initial_range = get_initial(input_initial, Dynamic.Mission.RANGE) - fix_initial_range = get_initial(fix_initial, Dynamic.Mission.RANGE, True) + input_initial_distance = get_initial(input_initial, Dynamic.Mission.DISTANCE) + fix_initial_distance = get_initial(fix_initial, Dynamic.Mission.DISTANCE, True) phase.add_state( - Dynamic.Mission.RANGE, fix_initial=fix_initial_range, fix_final=fix_range, - lower=0.0, ref=1e6, defect_ref=1e6, units='m', - rate_source=Dynamic.Mission.RANGE_RATE, - input_initial=input_initial_range + Dynamic.Mission.DISTANCE, fix_initial=fix_initial_distance, fix_final=False, + lower=0.0, ref=1e6, defect_ref=1e8, units='m', + rate_source=Dynamic.Mission.DISTANCE_RATE, + input_initial=input_initial_distance, + solve_segments='forward' if solve_for_distance else None, ) phase = add_subsystem_variables_to_phase( @@ -257,62 +206,60 @@ def build_phase(self, aviary_options: AviaryValues = None): ################ # Add Controls # ################ - if polynomial_control_order is not None: + if use_polynomial_control: phase.add_polynomial_control( - Dynamic.Mission.VELOCITY_RATE, - targets=Dynamic.Mission.VELOCITY_RATE, units='m/s**2', - opt=True, lower=lower_accel, upper=upper_accel, - order=polynomial_control_order, + Dynamic.Mission.MACH, + targets=Dynamic.Mission.MACH, units=mach_bounds[1], + opt=optimize_mach, lower=mach_bounds[0][0], upper=mach_bounds[0][1], + rate_targets=[Dynamic.Mission.MACH_RATE], + order=polynomial_control_order, ref=0.5, ) else: phase.add_control( - Dynamic.Mission.VELOCITY_RATE, - targets=Dynamic.Mission.VELOCITY_RATE, units='m/s**2', - opt=True, lower=lower_accel, upper=upper_accel + Dynamic.Mission.MACH, + targets=Dynamic.Mission.MACH, units=mach_bounds[1], + opt=optimize_mach, lower=mach_bounds[0][0], upper=mach_bounds[0][1], + rate_targets=[Dynamic.Mission.MACH_RATE], + ref=0.5, ) - if num_engines > 0: - if polynomial_control_order is not None: - phase.add_polynomial_control( - Dynamic.Mission.THROTTLE, - targets=Dynamic.Mission.THROTTLE, units='unitless', - opt=True, lower=0.0, upper=1.0, scaler=1, - order=polynomial_control_order, - ) - else: - phase.add_control( - Dynamic.Mission.THROTTLE, - targets=Dynamic.Mission.THROTTLE, units='unitless', - opt=True, lower=0.0, upper=1.0, scaler=1, - ) - if climb: - phase.add_path_constraint( - Dynamic.Mission.THROTTLE, - upper=1.0, - ) - - if descent: - phase.add_path_constraint( - Dynamic.Mission.THROTTLE, - lower=0.0, - ) - - # check if engine has use_hybrid_throttle - engine_models = aviary_options.get_val('engine_models') - if any([engine.use_hybrid_throttle for engine in engine_models]): - if polynomial_control_order is not None: - phase.add_polynomial_control( - Dynamic.Mission.HYBRID_THROTTLE, - targets=Dynamic.Mission.HYBRID_THROTTLE, units='unitless', - opt=True, lower=0.0, upper=1.0, scaler=1, - order=polynomial_control_order, - ) - else: - phase.add_control( - Dynamic.Mission.HYBRID_THROTTLE, - targets=Dynamic.Mission.HYBRID_THROTTLE, units='unitless', - opt=True, lower=0.0, upper=1.0, scaler=1, - ) + if optimize_mach and fix_initial: + phase.add_boundary_constraint( + Dynamic.Mission.MACH, loc='initial', equals=initial_mach, + ) + + if optimize_mach and constrain_final: + phase.add_boundary_constraint( + Dynamic.Mission.MACH, loc='final', equals=final_mach, + ) + + # Add altitude rate as a control + if use_polynomial_control: + phase.add_polynomial_control( + Dynamic.Mission.ALTITUDE, + targets=Dynamic.Mission.ALTITUDE, units=altitude_bounds[1], + opt=optimize_altitude, lower=altitude_bounds[0][0], upper=altitude_bounds[0][1], + rate_targets=[Dynamic.Mission.ALTITUDE_RATE], + order=polynomial_control_order, ref=altitude_bounds[0][1], + ) + else: + phase.add_control( + Dynamic.Mission.ALTITUDE, + targets=Dynamic.Mission.ALTITUDE, units=altitude_bounds[1], + opt=optimize_altitude, lower=altitude_bounds[0][0], upper=altitude_bounds[0][1], + rate_targets=[Dynamic.Mission.ALTITUDE_RATE], + ref=altitude_bounds[0][1], + ) + + if optimize_altitude and fix_initial: + phase.add_boundary_constraint( + Dynamic.Mission.ALTITUDE, loc='initial', equals=initial_altitude, units=altitude_bounds[1], ref=1.e4, + ) + + if optimize_altitude and constrain_final: + phase.add_boundary_constraint( + Dynamic.Mission.ALTITUDE, loc='final', equals=final_altitude, units=altitude_bounds[1], ref=1.e4, + ) ################## # Add Timeseries # @@ -326,20 +273,10 @@ def build_phase(self, aviary_options: AviaryValues = None): output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' ) - phase.add_timeseries_output( - Dynamic.Mission.THRUST_MAX_TOTAL, - output_name=Dynamic.Mission.THRUST_MAX_TOTAL, units='lbf' - ) - phase.add_timeseries_output( Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' ) - phase.add_timeseries_output( - Dynamic.Mission.SPECIFIC_ENERGY_RATE, - output_name=Dynamic.Mission.SPECIFIC_ENERGY_RATE, units='m/s' - ) - phase.add_timeseries_output( Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS, output_name=Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS, units='m/s' @@ -356,79 +293,45 @@ def build_phase(self, aviary_options: AviaryValues = None): ) phase.add_timeseries_output( - Dynamic.Mission.ALTITUDE_RATE_MAX, - output_name=Dynamic.Mission.ALTITUDE_RATE_MAX, units='ft/min' + Dynamic.Mission.THROTTLE, + output_name=Dynamic.Mission.THROTTLE, units='unitless' + ) + + phase.add_timeseries_output( + Dynamic.Mission.VELOCITY, + output_name=Dynamic.Mission.VELOCITY, units='m/s' ) ################### # Add Constraints # ################### - if climb and no_descent: - phase.add_path_constraint( - Dynamic.Mission.ALTITUDE_RATE, constraint_name='no_descent', - lower=0.0 - ) - - if descent and no_climb: - phase.add_path_constraint( - Dynamic.Mission.ALTITUDE_RATE, constraint_name='no_climb', - upper=0.0 - ) + if no_descent: + phase.add_path_constraint(Dynamic.Mission.ALTITUDE_RATE, lower=0.0) - if descent and num_engines > 0: - phase.add_boundary_constraint( - Dynamic.Mission.THROTTLE, - loc='final', - upper=1.0, - lower=0.0, - units='unitless') + if no_climb: + phase.add_path_constraint(Dynamic.Mission.ALTITUDE_RATE, upper=0.0) - required_altitude_rate, units = user_options.get_item('required_altitude_rate') + required_available_climb_rate, units = user_options.get_item( + 'required_available_climb_rate') - if required_altitude_rate is not None: + if required_available_climb_rate is not None: phase.add_path_constraint( Dynamic.Mission.ALTITUDE_RATE_MAX, - lower=required_altitude_rate, units=units + lower=required_available_climb_rate, units=units ) - phase.add_boundary_constraint( - Dynamic.Mission.MACH, loc='final', equals=final_mach - ) - - if isclose(initial_mach, final_mach): - phase.add_path_constraint( - Dynamic.Mission.MACH, - equals=min_mach, units='unitless', - # ref=1.e4, + if throttle_enforcement == 'boundary_constraint': + phase.add_boundary_constraint( + Dynamic.Mission.THROTTLE, loc='initial', lower=0.0, upper=1.0, units='unitless', ) - - else: + phase.add_boundary_constraint( + Dynamic.Mission.THROTTLE, loc='final', lower=0.0, upper=1.0, units='unitless', + ) + elif throttle_enforcement == 'path_constraint': phase.add_path_constraint( - Dynamic.Mission.MACH, - lower=min_mach, upper=max_mach, units='unitless', - # ref=1.e4, + Dynamic.Mission.THROTTLE, lower=0.0, upper=1.0, units='unitless', ) - # avoid scaling constraints by zero - ref = final_altitude - if ref == 0: - ref = None - - phase.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, loc='final', equals=final_altitude, ref=ref - ) - - # avoid scaling constraints by zero - ref = max_altitude - if ref == 0: - ref = None - - phase.add_path_constraint( - Dynamic.Mission.ALTITUDE, - lower=min_altitude, upper=max_altitude, - units='m', ref=ref - ) - return phase def make_default_transcription(self): @@ -457,6 +360,7 @@ def _extra_ode_init_kwargs(self): 'external_subsystems': self.external_subsystems, 'meta_data': self.meta_data, 'subsystem_options': self.subsystem_options, + 'throttle_enforcement': self.user_options.get_val('throttle_enforcement'), } @@ -468,36 +372,24 @@ def _extra_ode_init_kwargs(self): desc='transcription: order of the state transcription; the order of the control' ' transcription is `order - 1`') +EnergyPhase._add_meta_data('polynomial_control_order', val=3) + +EnergyPhase._add_meta_data('use_polynomial_control', val=True) + EnergyPhase._add_meta_data('add_initial_mass_constraint', val=False) EnergyPhase._add_meta_data('fix_initial', val=True) -EnergyPhase._add_meta_data('fix_initial_time', val=None) +EnergyPhase._add_meta_data('optimize_mach', val=False) -EnergyPhase._add_meta_data('initial_ref', val=1., units='s') +EnergyPhase._add_meta_data('optimize_altitude', val=False) EnergyPhase._add_meta_data('initial_bounds', val=(0., 100.), units='s') -EnergyPhase._add_meta_data('duration_ref', val=1., units='s') - EnergyPhase._add_meta_data('duration_bounds', val=(0., 100.), units='s') -EnergyPhase._add_meta_data('cruise_alt', val=11.e3, units='m') - -EnergyPhase._add_meta_data( - 'initial_altitude', val=0., units='m', - desc='starting true altitude from mean sea level') - -EnergyPhase._add_meta_data( - 'final_altitude', val=None, units='m', - desc='ending true altitude from mean sea level') - -EnergyPhase._add_meta_data('initial_mach', val=0., desc='starting Mach number') - -EnergyPhase._add_meta_data('final_mach', val=None, desc='ending Mach number') - EnergyPhase._add_meta_data( - 'required_altitude_rate', val=None, units='m/s', + 'required_available_climb_rate', val=None, units='m/s', desc='minimum avaliable climb rate') EnergyPhase._add_meta_data( @@ -506,36 +398,42 @@ def _extra_ode_init_kwargs(self): EnergyPhase._add_meta_data( 'no_descent', val=False, desc='aircraft is not allowed to descend during phase') -EnergyPhase._add_meta_data('fix_range', val=False) +EnergyPhase._add_meta_data('constrain_final', val=False) EnergyPhase._add_meta_data('input_initial', val=False) -EnergyPhase._add_meta_data('polynomial_control_order', val=None) +EnergyPhase._add_meta_data('initial_mach', val=None, units='unitless') + +EnergyPhase._add_meta_data('final_mach', val=None, units='unitless') + +EnergyPhase._add_meta_data('initial_altitude', val=None, units='ft') + +EnergyPhase._add_meta_data('final_altitude', val=None, units='ft') + +EnergyPhase._add_meta_data('throttle_enforcement', val=None) + +EnergyPhase._add_meta_data('mach_bounds', val=(0., 2.), units='unitless') + +EnergyPhase._add_meta_data('altitude_bounds', val=(0., 60.e3), units='ft') + +EnergyPhase._add_meta_data('solve_for_distance', val=False) EnergyPhase._add_initial_guess_meta_data( InitialGuessTime(), desc='initial guess for initial time and duration specified as a tuple') EnergyPhase._add_initial_guess_meta_data( - InitialGuessState('range'), + InitialGuessState('distance'), desc='initial guess for horizontal distance traveled') EnergyPhase._add_initial_guess_meta_data( - InitialGuessState('altitude'), + InitialGuessControl('altitude'), desc='initial guess for vertical distances') EnergyPhase._add_initial_guess_meta_data( - InitialGuessState('velocity'), + InitialGuessControl('mach'), desc='initial guess for speed') -EnergyPhase._add_initial_guess_meta_data( - InitialGuessControl('velocity_rate'), - desc='initial guess for acceleration') - EnergyPhase._add_initial_guess_meta_data( InitialGuessState('mass'), desc='initial guess for mass') - -EnergyPhase._add_initial_guess_meta_data( - InitialGuessControl('throttle'), - desc='initial guess for throttle') diff --git a/aviary/mission/flops_based/phases/simple_energy_phase.py b/aviary/mission/flops_based/phases/simple_energy_phase.py deleted file mode 100644 index e5ffd1928..000000000 --- a/aviary/mission/flops_based/phases/simple_energy_phase.py +++ /dev/null @@ -1,401 +0,0 @@ -from math import isclose - -import dymos as dm - -from aviary.mission.flops_based.phases.phase_builder_base import ( - register, PhaseBuilderBase, InitialGuessControl, InitialGuessParameter, - InitialGuessPolynomialControl, InitialGuessState, InitialGuessTime) - -from aviary.utils.aviary_values import AviaryValues -from aviary.variable_info.variable_meta_data import _MetaData -from aviary.mission.flops_based.phases.phase_utils import add_subsystem_variables_to_phase, get_initial -from aviary.variable_info.variables import Dynamic -from aviary.mission.flops_based.ode.simple_mission_ODE import MissionODE - - -# TODO: support/handle the following in the base class -# - phase.set_time_options() -# - currently handled in level 3 interface implementation -# - self.external_subsystems -# - self.meta_data, with cls.default_meta_data customization point -class EnergyPhase(PhaseBuilderBase): - ''' - A phase builder for a simple energy phase. - - Attributes - ---------- - name : str ('energy_phase') - object label - - subsystem_options (None) - dictionary of parameters to be passed to the subsystem builders - - user_options : AviaryValues () - state/path constraint values and flags - - supported options: - - num_segments : int (5) - transcription: number of segments - - order : int (3) - transcription: order of the state transcription; the order of the control - transcription is `order - 1` - - fix_initial : bool (True) - - initial_ref : float (1.0, 's') - - initial_bounds : pair ((0.0, 100.0) 's') - - duration_ref : float (1.0, 's') - - duration_bounds : pair ((0.0, 100.0) 's') - - required_available_climb_rate : float (None) - minimum avaliable climb rate - - constrain_final : bool (False) - - input_initial : bool (False) - - initial_guesses : AviaryValues () - state/path beginning values to be set on the problem - - supported options: - - times - - range - - altitude - - velocity - - velocity_rate - - mass - - throttle - - ode_class : type (None) - advanced: the type of system defining the ODE - - transcription : "Dymos transcription object" (None) - advanced: an object providing the transcription technique of the - optimal control problem - - external_subsystems : Sequence["subsystem builder"] () - advanced - - meta_data : dict (<"builtin" meta data>) - advanced: meta data associated with variables in the Aviary data hierarchy - - default_name : str - class attribute: derived type customization point; the default value - for name - - default_ode_class : type - class attribute: derived type customization point; the default value - for ode_class used by build_phase - - default_meta_data : dict - class attribute: derived type customization point; the default value for - meta_data - - Methods - ------- - build_phase - make_default_transcription - validate_options - assign_default_options - ''' - __slots__ = ('external_subsystems', 'meta_data') - - # region : derived type customization points - _meta_data_ = {} - - _initial_guesses_meta_data_ = {} - - default_name = 'energy_phase' - - default_ode_class = MissionODE - - default_meta_data = _MetaData - # endregion : derived type customization points - - def __init__( - self, name=None, subsystem_options=None, user_options=None, initial_guesses=None, - ode_class=None, transcription=None, core_subsystems=None, - external_subsystems=None, meta_data=None - ): - super().__init__( - name=name, core_subsystems=core_subsystems, subsystem_options=subsystem_options, user_options=user_options, initial_guesses=initial_guesses, ode_class=ode_class, transcription=transcription) - - # TODO: support external_subsystems and meta_data in the base class - if external_subsystems is None: - external_subsystems = [] - - self.external_subsystems = external_subsystems - - if meta_data is None: - meta_data = self.default_meta_data - - self.meta_data = meta_data - - def build_phase(self, aviary_options: AviaryValues = None): - ''' - Return a new energy phase for analysis using these constraints. - - If ode_class is None, default_ode_class is used. - - If transcription is None, the return value from calling - make_default_transcription is used. - - Parameters - ---------- - aviary_options : AviaryValues () - collection of Aircraft/Mission specific options - - Returns - ------- - dymos.Phase - ''' - phase: dm.Phase = super().build_phase(aviary_options) - - user_options: AviaryValues = self.user_options - - fix_initial = user_options.get_val('fix_initial') - constrain_final = user_options.get_val('constrain_final') - optimize_mach = user_options.get_val('optimize_mach') - optimize_altitude = user_options.get_val('optimize_altitude') - input_initial = user_options.get_val('input_initial') - polynomial_control_order = user_options.get_item('polynomial_control_order')[0] - throttle_enforcement = user_options.get_val('throttle_enforcement') - mach_bounds = user_options.get_item('mach_bounds') - altitude_bounds = user_options.get_item('altitude_bounds') - initial_mach = user_options.get_item('initial_mach')[0] - final_mach = user_options.get_item('final_mach')[0] - initial_altitude = user_options.get_item('initial_altitude')[0] - final_altitude = user_options.get_item('final_altitude')[0] - solve_for_range = user_options.get_val('solve_for_range') - - ############## - # Add States # - ############## - # TODO: critically think about how we should handle fix_initial and input_initial defaults. - # In keeping with Dymos standards, the default should be False instead of True. - input_initial_mass = get_initial(input_initial, Dynamic.Mission.MASS) - fix_initial_mass = get_initial(fix_initial, Dynamic.Mission.MASS, True) - - # Experiment: use a constraint for mass instead of connected initial. - # This is due to some problems in mpi. - # This is needed for the cutting edge full subsystem integration. - # TODO: when a Dymos fix is in and we've verified that full case works with the fix, - # remove this argument. - if user_options.get_val('add_initial_mass_constraint'): - phase.add_constraint('rhs_all.initial_mass_residual', equals=0.0, ref=1e4) - input_initial_mass = False - - phase.add_state( - Dynamic.Mission.MASS, fix_initial=fix_initial_mass, fix_final=False, - lower=0.0, ref=1e4, defect_ref=1e6, units='kg', - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.Mission.MASS, - input_initial=input_initial_mass, - # solve_segments='forward', - ) - - input_initial_range = get_initial(input_initial, Dynamic.Mission.RANGE) - fix_initial_range = get_initial(fix_initial, Dynamic.Mission.RANGE, True) - phase.add_state( - Dynamic.Mission.RANGE, fix_initial=fix_initial_range, fix_final=False, - lower=0.0, ref=1e6, defect_ref=1e8, units='m', - rate_source=Dynamic.Mission.RANGE_RATE, - input_initial=input_initial_range, - solve_segments='forward' if solve_for_range else None, - ) - - phase = add_subsystem_variables_to_phase( - phase, self.name, self.external_subsystems) - - ################ - # Add Controls # - ################ - phase.add_polynomial_control( - Dynamic.Mission.MACH, - targets=Dynamic.Mission.MACH, units=mach_bounds[1], - opt=optimize_mach, lower=mach_bounds[0][0], upper=mach_bounds[0][1], - rate_targets=[Dynamic.Mission.MACH_RATE], - order=polynomial_control_order, ref=0.5, - ) - - if optimize_mach and fix_initial: - phase.add_boundary_constraint( - Dynamic.Mission.MACH, loc='initial', equals=initial_mach, - ) - - if optimize_mach and constrain_final: - phase.add_boundary_constraint( - Dynamic.Mission.MACH, loc='final', equals=final_mach, - ) - - # Add altitude rate as a control - phase.add_polynomial_control( - Dynamic.Mission.ALTITUDE, - targets=Dynamic.Mission.ALTITUDE, units=altitude_bounds[1], - opt=optimize_altitude, lower=altitude_bounds[0][0], upper=altitude_bounds[0][1], - rate_targets=[Dynamic.Mission.ALTITUDE_RATE], - order=polynomial_control_order, ref=altitude_bounds[0][1], - ) - - if optimize_altitude and fix_initial: - phase.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, loc='initial', equals=initial_altitude, units=altitude_bounds[1], ref=1.e4, - ) - - if optimize_altitude and constrain_final: - phase.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, loc='final', equals=final_altitude, units=altitude_bounds[1], ref=1.e4, - ) - - ################## - # Add Timeseries # - ################## - phase.add_timeseries_output( - Dynamic.Mission.MACH, output_name=Dynamic.Mission.MACH, units='unitless' - ) - - phase.add_timeseries_output( - Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, units='lbf' - ) - - phase.add_timeseries_output( - Dynamic.Mission.DRAG, output_name=Dynamic.Mission.DRAG, units='lbf' - ) - - phase.add_timeseries_output( - Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS, - output_name=Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS, units='m/s' - ) - - phase.add_timeseries_output( - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - output_name=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, units='lbm/h' - ) - - phase.add_timeseries_output( - Dynamic.Mission.ALTITUDE_RATE, - output_name=Dynamic.Mission.ALTITUDE_RATE, units='ft/s' - ) - - phase.add_timeseries_output( - Dynamic.Mission.THROTTLE, - output_name=Dynamic.Mission.THROTTLE, units='unitless' - ) - - ################### - # Add Constraints # - ################### - required_available_climb_rate, units = user_options.get_item( - 'required_available_climb_rate') - - if required_available_climb_rate is not None: - phase.add_path_constraint( - Dynamic.Mission.ALTITUDE_RATE_MAX, - lower=required_available_climb_rate, units=units - ) - - if throttle_enforcement == 'boundary_constraint': - phase.add_boundary_constraint( - Dynamic.Mission.THROTTLE, loc='initial', lower=0.0, upper=1.0, units='unitless', - ) - phase.add_boundary_constraint( - Dynamic.Mission.THROTTLE, loc='final', lower=0.0, upper=1.0, units='unitless', - ) - elif throttle_enforcement == 'path_constraint': - phase.add_path_constraint( - Dynamic.Mission.THROTTLE, lower=0.0, upper=1.0, units='unitless', - ) - - return phase - - def make_default_transcription(self): - ''' - Return a transcription object to be used by default in build_phase. - ''' - user_options = self.user_options - - num_segments, _ = user_options.get_item('num_segments') - order, _ = user_options.get_item('order') - - seg_ends, _ = dm.utils.lgl.lgl(num_segments + 1) - - transcription = dm.Radau( - num_segments=num_segments, order=order, compressed=True, - segment_ends=seg_ends) - - return transcription - - def _extra_ode_init_kwargs(self): - """ - Return extra kwargs required for initializing the ODE. - """ - # TODO: support external_subsystems and meta_data in the base class - return { - 'external_subsystems': self.external_subsystems, - 'meta_data': self.meta_data, - 'subsystem_options': self.subsystem_options, - 'throttle_enforcement': self.user_options.get_val('throttle_enforcement'), - } - - -EnergyPhase._add_meta_data( - 'num_segments', val=5, desc='transcription: number of segments') - -EnergyPhase._add_meta_data( - 'order', val=3, - desc='transcription: order of the state transcription; the order of the control' - ' transcription is `order - 1`') - -EnergyPhase._add_meta_data('polynomial_control_order', val=None) - -EnergyPhase._add_meta_data('add_initial_mass_constraint', val=False) - -EnergyPhase._add_meta_data('fix_initial', val=True) - -EnergyPhase._add_meta_data('optimize_mach', val=False) - -EnergyPhase._add_meta_data('optimize_altitude', val=False) - -EnergyPhase._add_meta_data('initial_bounds', val=(0., 100.), units='s') - -EnergyPhase._add_meta_data('duration_bounds', val=(0., 100.), units='s') - -EnergyPhase._add_meta_data( - 'required_available_climb_rate', val=None, units='m/s', - desc='minimum avaliable climb rate') - -EnergyPhase._add_meta_data('constrain_final', val=False) - -EnergyPhase._add_meta_data('input_initial', val=False) - -EnergyPhase._add_meta_data('initial_mach', val=None, units='unitless') - -EnergyPhase._add_meta_data('final_mach', val=None, units='unitless') - -EnergyPhase._add_meta_data('initial_altitude', val=None, units='ft') - -EnergyPhase._add_meta_data('final_altitude', val=None, units='ft') - -EnergyPhase._add_meta_data('throttle_enforcement', val=None) - -EnergyPhase._add_meta_data('mach_bounds', val=(0., 2.), units='unitless') - -EnergyPhase._add_meta_data('altitude_bounds', val=(0., 60.e3), units='ft') - -EnergyPhase._add_meta_data('solve_for_range', val=False) - -EnergyPhase._add_initial_guess_meta_data( - InitialGuessTime(), - desc='initial guess for initial time and duration specified as a tuple') - -EnergyPhase._add_initial_guess_meta_data( - InitialGuessState('range'), - desc='initial guess for horizontal distance traveled') - -EnergyPhase._add_initial_guess_meta_data( - InitialGuessState('altitude'), - desc='initial guess for vertical distances') - -EnergyPhase._add_initial_guess_meta_data( - InitialGuessState('mach'), - desc='initial guess for speed') - -EnergyPhase._add_initial_guess_meta_data( - InitialGuessState('mass'), - desc='initial guess for mass') diff --git a/aviary/mission/flops_based/phases/simplified_landing.py b/aviary/mission/flops_based/phases/simplified_landing.py index 07391f77a..5e893b693 100644 --- a/aviary/mission/flops_based/phases/simplified_landing.py +++ b/aviary/mission/flops_based/phases/simplified_landing.py @@ -117,10 +117,10 @@ def setup(self): self.add_subsystem( "calcs", LandingCalc(), + promotes_inputs=[Mission.Landing.TOUCHDOWN_MASS, "rho", + Aircraft.Wing.AREA, Mission.Landing.LIFT_COEFFICIENT_MAX], promotes_outputs=[ Mission.Landing.GROUND_DISTANCE, Mission.Landing.INITIAL_VELOCITY, ], - promotes_inputs=[Mission.Landing.TOUCHDOWN_MASS, "rho", - Aircraft.Wing.AREA, Mission.Landing.LIFT_COEFFICIENT_MAX], ) diff --git a/aviary/mission/flops_based/phases/simplified_takeoff.py b/aviary/mission/flops_based/phases/simplified_takeoff.py index 2f7848a8c..5060f0c04 100644 --- a/aviary/mission/flops_based/phases/simplified_takeoff.py +++ b/aviary/mission/flops_based/phases/simplified_takeoff.py @@ -103,12 +103,6 @@ def setup(self): add_aviary_input(self, Mission.Takeoff.LIFT_OVER_DRAG, val=2) - # we don't actually need this? It seems like we should from the equation in the - # paper, but we are using the equation from LEAPS1 because it doesn't require L - # or D - add_aviary_input(self, Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, - val=0.00175) - add_aviary_output(self, Mission.Takeoff.GROUND_DISTANCE, val=0) add_aviary_output(self, Mission.Takeoff.FINAL_VELOCITY, @@ -133,7 +127,6 @@ def setup_partials(self): Aircraft.Wing.AREA, Mission.Takeoff.LIFT_COEFFICIENT_MAX, Mission.Design.THRUST_TAKEOFF_PER_ENG, - Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, Mission.Takeoff.LIFT_OVER_DRAG, ], ) @@ -160,7 +153,6 @@ def compute(self, inputs, outputs): Cl_max = inputs[Mission.Takeoff.LIFT_COEFFICIENT_MAX] thrust = inputs[Mission.Design.THRUST_TAKEOFF_PER_ENG] L_over_D = inputs[Mission.Takeoff.LIFT_OVER_DRAG] - mu = inputs[Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT] num_engines = self.options['num_engines'] rho_ratio = rho / rho_SL @@ -205,17 +197,14 @@ def compute(self, inputs, outputs): outputs[Mission.Takeoff.FINAL_ALTITUDE] = 35 def compute_partials(self, inputs, J): - rho_SL = RHO_SEA_LEVEL_METRIC - v_stall = inputs["v_stall"] ramp_weight = inputs[Mission.Design.GROSS_MASS] * GRAV_ENGLISH_LBM rho = inputs["rho"] S = inputs[Aircraft.Wing.AREA] Cl_max = inputs[Mission.Takeoff.LIFT_COEFFICIENT_MAX] thrust = inputs[Mission.Design.THRUST_TAKEOFF_PER_ENG] L_over_D = inputs[Mission.Takeoff.LIFT_OVER_DRAG] - mu = inputs[Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT] num_engines = self.options['num_engines'] rho_ratio = rho / rho_SL @@ -379,7 +368,7 @@ def setup(self): "USatm", USatm1976Comp(num_nodes=1), promotes_inputs=[("h", Dynamic.Mission.ALTITUDE)], - promotes_outputs=["rho"], + promotes_outputs=["rho", ("sos", "speed_of_sound")], ) self.add_subsystem( @@ -408,7 +397,6 @@ def setup(self): Mission.Takeoff.LIFT_COEFFICIENT_MAX, Mission.Design.THRUST_TAKEOFF_PER_ENG, Mission.Takeoff.LIFT_OVER_DRAG, - Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, ], promotes_outputs=[ Mission.Takeoff.GROUND_DISTANCE, @@ -417,3 +405,11 @@ def setup(self): Mission.Takeoff.FINAL_ALTITUDE, ], ) + + self.add_subsystem('compute_mach', om.ExecComp('final_mach = final_velocity / speed_of_sound', + final_mach={'units': 'unitless'}, + final_velocity={'units': 'm/s'}, + speed_of_sound={'units': 'm/s'}), + promotes_inputs=[('speed_of_sound', 'speed_of_sound'), + ('final_velocity', Mission.Takeoff.FINAL_VELOCITY)], + promotes_outputs=[('final_mach', Mission.Takeoff.FINAL_MACH)]) diff --git a/aviary/mission/flops_based/phases/test/test_simplified_landing.py b/aviary/mission/flops_based/phases/test/test_simplified_landing.py index 11df1420e..3485dfd36 100644 --- a/aviary/mission/flops_based/phases/test/test_simplified_landing.py +++ b/aviary/mission/flops_based/phases/test/test_simplified_landing.py @@ -49,14 +49,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - # def test_spec(self): - - # subsystem = self.prob.model - - # assert_match_spec( - # subsystem, "landing_specs/landing_calcs.json" - # ) - class LandingGroupTest(unittest.TestCase): def setUp(self): @@ -98,14 +90,6 @@ def test_case1(self): out_stream=None, excludes=['*.USatm'], method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - # def test_spec(self): - - # subsystem = self.prob.model - - # assert_match_spec( - # subsystem, "landing_specs/landing_group.json" - # ) - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/flops_based/phases/test/test_simplified_takeoff.py b/aviary/mission/flops_based/phases/test/test_simplified_takeoff.py index c011af727..57be758c9 100644 --- a/aviary/mission/flops_based/phases/test/test_simplified_takeoff.py +++ b/aviary/mission/flops_based/phases/test/test_simplified_takeoff.py @@ -2,9 +2,6 @@ Test file to test the outputs, derivatives, and IO of each sample component/group. The name of this file needs to start with 'test' so that the testflo command will find and run the file. - -Before this file is run, the sample_XDSM.py file in the XDSM folder must be run in -order to generate the spec files which are used in the test_spec tests. """ import unittest @@ -53,14 +50,6 @@ def test_case1(self): partial_data, atol=1e-12, rtol=1e-12 ) # check the partial derivatives - # def test_spec(self): - - # subsystem = self.prob.model - - # assert_match_spec( - # subsystem, "takeoff_specs/vstall.json" - # ) - class FinalConditionsTest(unittest.TestCase): def setUp(self): @@ -91,8 +80,6 @@ def setUp(self): Mission.Design.THRUST_TAKEOFF_PER_ENG, val=28928.0, units="lbf") # check self.prob.model.set_input_defaults( Mission.Takeoff.LIFT_OVER_DRAG, val=17.354, units='unitless') # check - self.prob.model.set_input_defaults( - Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, val=0.0175, units='unitless') # check self.prob.setup(check=False, force_alloc_complex=True) @@ -115,12 +102,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - # def test_spec(self): - - # subsystem = self.prob.model - - # assert_match_spec(subsystem, "takeoff_specs/final_conds.json") - class TakeoffGroupTest(unittest.TestCase): def setUp(self): @@ -145,8 +126,6 @@ def setUp(self): Mission.Design.THRUST_TAKEOFF_PER_ENG, val=28928.0, units="lbf") # check self.prob.model.set_input_defaults( Mission.Takeoff.LIFT_OVER_DRAG, val=17.354, units='unitless') # check - self.prob.model.set_input_defaults( - Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, val=0.0175, units='unitless') # check self.prob.model.set_input_defaults( Dynamic.Mission.ALTITUDE, val=0, units="ft") # check diff --git a/aviary/mission/gasp_based/idle_descent_estimation.py b/aviary/mission/gasp_based/idle_descent_estimation.py index 0371183fa..50fb3759d 100644 --- a/aviary/mission/gasp_based/idle_descent_estimation.py +++ b/aviary/mission/gasp_based/idle_descent_estimation.py @@ -34,8 +34,11 @@ def descent_range_and_fuel( traj = FlexibleTraj( Phases=phases, - traj_final_state_output=[Dynamic.Mission.MASS, - Dynamic.Mission.DISTANCE, Dynamic.Mission.ALTITUDE], + traj_final_state_output=[ + Dynamic.Mission.MASS, + Dynamic.Mission.DISTANCE, + Dynamic.Mission.ALTITUDE, + ], traj_initial_state_input=[ Dynamic.Mission.MASS, Dynamic.Mission.DISTANCE, diff --git a/aviary/mission/gasp_based/ode/accel_eom.py b/aviary/mission/gasp_based/ode/accel_eom.py index ca8939964..db3adba94 100644 --- a/aviary/mission/gasp_based/ode/accel_eom.py +++ b/aviary/mission/gasp_based/ode/accel_eom.py @@ -56,7 +56,7 @@ def setup(self): desc="rate of change of true air speed", ) self.add_output( - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, val=np.zeros(nn), units="ft/s", desc="rate of change of horizontal distance covered", @@ -65,7 +65,8 @@ def setup(self): self.declare_partials( "TAS_rate", [ Dynamic.Mission.MASS, Dynamic.Mission.DRAG, Dynamic.Mission.THRUST_TOTAL], rows=arange, cols=arange) - self.declare_partials("distance_rate", ["TAS"], rows=arange, cols=arange, val=1.) + self.declare_partials(Dynamic.Mission.DISTANCE_RATE, [ + "TAS"], rows=arange, cols=arange, val=1.) if analysis_scheme is AnalysisScheme.SHOOTING: self.add_input("t_curr", val=np.ones(nn), desc="time", units="s") @@ -83,7 +84,7 @@ def compute(self, inputs, outputs): TAS = inputs["TAS"] outputs["TAS_rate"] = (GRAV_ENGLISH_GASP / weight) * (thrust - drag) - outputs["distance_rate"] = TAS + outputs[Dynamic.Mission.DISTANCE_RATE] = TAS if analysis_scheme is AnalysisScheme.SHOOTING: outputs[Dynamic.Mission.ALTITUDE_RATE] = 0 diff --git a/aviary/mission/gasp_based/ode/accel_ode.py b/aviary/mission/gasp_based/ode/accel_ode.py index eae09fe28..90d2b9cc2 100644 --- a/aviary/mission/gasp_based/ode/accel_ode.py +++ b/aviary/mission/gasp_based/ode/accel_ode.py @@ -9,6 +9,8 @@ from aviary.subsystems.mass.mass_to_weight import MassToWeight from aviary.variable_info.enums import AnalysisScheme, SpeedType from aviary.variable_info.variables import Aircraft, Dynamic, Mission +from aviary.mission.ode.specific_energy_rate import SpecificEnergyRate +from aviary.mission.ode.altitude_rate import AltitudeRate class AccelODE(BaseODE): @@ -89,12 +91,36 @@ def setup(self): + sgm_inputs, promotes_outputs=[ "TAS_rate", - "distance_rate", ] + Dynamic.Mission.DISTANCE_RATE, ] + sgm_outputs, ) + self.add_subsystem( + name='SPECIFIC_ENERGY_RATE_EXCESS', + subsys=SpecificEnergyRate(num_nodes=nn), + promotes_inputs=[(Dynamic.Mission.VELOCITY, "TAS"), Dynamic.Mission.MASS, + (Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL), + Dynamic.Mission.DRAG], + promotes_outputs=[(Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS)] + ) + + self.add_subsystem( + name='ALTITUDE_RATE_MAX', + subsys=AltitudeRate(num_nodes=nn), + promotes_inputs=[ + (Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS), + (Dynamic.Mission.VELOCITY_RATE, "TAS_rate"), + (Dynamic.Mission.VELOCITY, "TAS")], + promotes_outputs=[ + (Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.ALTITUDE_RATE_MAX)]) + ParamPort.set_default_vals(self) self.set_input_defaults(Dynamic.Mission.MASS, val=14e4 * np.ones(nn), units="lbm") self.set_input_defaults(Dynamic.Mission.ALTITUDE, val=500 * np.ones(nn), units="ft") + self.set_input_defaults("TAS", val=200*np.ones(nn), + units="m/s") # val here is nominal diff --git a/aviary/mission/gasp_based/ode/ascent_eom.py b/aviary/mission/gasp_based/ode/ascent_eom.py index aea1ab632..212354599 100644 --- a/aviary/mission/gasp_based/ode/ascent_eom.py +++ b/aviary/mission/gasp_based/ode/ascent_eom.py @@ -49,7 +49,7 @@ def setup(self): desc="altitude rate", units="ft/s") self.add_output( - "distance_rate", val=np.ones(nn), desc="distance rate", units="ft/s" + Dynamic.Mission.DISTANCE_RATE, val=np.ones(nn), desc="distance rate", units="ft/s" ) self.add_output( "normal_force", val=np.ones(nn), desc="normal forces", units="lbf" @@ -102,7 +102,7 @@ def setup_partials(self): Dynamic.Mission.ALTITUDE_RATE, [ "TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange) self.declare_partials( - "distance_rate", ["TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange + Dynamic.Mission.DISTANCE_RATE, ["TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange ) self.declare_partials( "normal_force", @@ -156,7 +156,7 @@ def compute(self, inputs, outputs): ) outputs[Dynamic.Mission.ALTITUDE_RATE] = TAS * np.sin(gamma) - outputs["distance_rate"] = TAS * np.cos(gamma) + outputs[Dynamic.Mission.DISTANCE_RATE] = TAS * np.cos(gamma) outputs["normal_force"] = normal_force outputs["fuselage_pitch"] = gamma * 180 / np.pi - i_wing + alpha @@ -281,8 +281,9 @@ def compute_partials(self, inputs, J): J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE] = TAS * np.cos(gamma) - J["distance_rate", "TAS"] = np.cos(gamma) - J["distance_rate", Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) + J[Dynamic.Mission.DISTANCE_RATE, "TAS"] = np.cos(gamma) + J[Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) J["normal_force", Dynamic.Mission.MASS] = dNF_dWeight * GRAV_ENGLISH_LBM J["normal_force", Dynamic.Mission.LIFT] = dNF_dLift diff --git a/aviary/mission/gasp_based/ode/ascent_ode.py b/aviary/mission/gasp_based/ode/ascent_ode.py index 51223c42d..1176e62d5 100644 --- a/aviary/mission/gasp_based/ode/ascent_ode.py +++ b/aviary/mission/gasp_based/ode/ascent_ode.py @@ -13,6 +13,8 @@ from aviary.subsystems.propulsion.propulsion_mission import PropulsionMission from aviary.variable_info.enums import AlphaModes from aviary.variable_info.variables import Aircraft, Dynamic, Mission +from aviary.mission.ode.specific_energy_rate import SpecificEnergyRate +from aviary.mission.ode.altitude_rate import AltitudeRate class AscentODE(BaseODE): @@ -116,6 +118,28 @@ def setup(self): ], ) + self.add_subsystem( + name='SPECIFIC_ENERGY_RATE_EXCESS', + subsys=SpecificEnergyRate(num_nodes=nn), + promotes_inputs=[(Dynamic.Mission.VELOCITY, "TAS"), Dynamic.Mission.MASS, + (Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL), + Dynamic.Mission.DRAG], + promotes_outputs=[(Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS)] + ) + + self.add_subsystem( + name='ALTITUDE_RATE_MAX', + subsys=AltitudeRate(num_nodes=nn), + promotes_inputs=[ + (Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS), + (Dynamic.Mission.VELOCITY_RATE, "TAS_rate"), + (Dynamic.Mission.VELOCITY, "TAS")], + promotes_outputs=[ + (Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.ALTITUDE_RATE_MAX)]) + ParamPort.set_default_vals(self) self.set_input_defaults("t_init_flaps", val=47.5) self.set_input_defaults("t_init_gear", val=37.3) @@ -129,3 +153,5 @@ def setup(self): self.set_input_defaults('aero_ramps.gear_factor:final_val', val=0.) self.set_input_defaults('aero_ramps.flap_factor:initial_val', val=1.) self.set_input_defaults('aero_ramps.gear_factor:initial_val', val=1.) + self.set_input_defaults(Dynamic.Mission.MASS, val=np.ones( + nn), units='kg') # val here is nominal diff --git a/aviary/mission/gasp_based/ode/base_ode.py b/aviary/mission/gasp_based/ode/base_ode.py index 2884c1d4b..c74ba6773 100644 --- a/aviary/mission/gasp_based/ode/base_ode.py +++ b/aviary/mission/gasp_based/ode/base_ode.py @@ -72,6 +72,7 @@ def AddAlphaControl( name="alpha", val=np.full(nn, 10), # initial guess units="deg", + eq_units="unitless", lhs_name="load_factor", rhs_val=target_load_factor, upper=25., @@ -99,6 +100,7 @@ def AddAlphaControl( lhs_name="TAS_rate", rhs_name='target_tas_rate', rhs_val=target_tas_rate, + eq_units="kn/s", upper=25., lower=-2., ) diff --git a/aviary/mission/gasp_based/ode/breguet_cruise_ode.py b/aviary/mission/gasp_based/ode/breguet_cruise_ode.py index 8097210ac..5cdbef52f 100644 --- a/aviary/mission/gasp_based/ode/breguet_cruise_ode.py +++ b/aviary/mission/gasp_based/ode/breguet_cruise_ode.py @@ -10,6 +10,8 @@ from aviary.subsystems.propulsion.propulsion_builder import PropulsionBuilderBase from aviary.variable_info.enums import SpeedType from aviary.variable_info.variables import Dynamic +from aviary.mission.ode.specific_energy_rate import SpecificEnergyRate +from aviary.mission.ode.altitude_rate import AltitudeRate class BreguetCruiseODESolution(BaseODE): @@ -125,7 +127,7 @@ def setup(self): "eom", RangeComp(num_nodes=nn), promotes_inputs=[ - ("cruise_range_initial", "initial_distance"), + ("cruise_distance_initial", "initial_distance"), ("cruise_time_initial", "initial_time"), "mass", Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, @@ -135,6 +137,28 @@ def setup(self): ("cruise_time", "time")], ) + self.add_subsystem( + name='SPECIFIC_ENERGY_RATE_EXCESS', + subsys=SpecificEnergyRate(num_nodes=nn), + promotes_inputs=[(Dynamic.Mission.VELOCITY, "TAS"), Dynamic.Mission.MASS, + (Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL), + Dynamic.Mission.DRAG], + promotes_outputs=[(Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS)] + ) + + self.add_subsystem( + name='ALTITUDE_RATE_MAX', + subsys=AltitudeRate(num_nodes=nn), + promotes_inputs=[ + (Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS), + (Dynamic.Mission.VELOCITY_RATE, "TAS_rate"), + (Dynamic.Mission.VELOCITY, "TAS")], + promotes_outputs=[ + (Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.ALTITUDE_RATE_MAX)]) + ParamPort.set_default_vals(self) self.set_input_defaults( Dynamic.Mission.ALTITUDE, diff --git a/aviary/mission/gasp_based/ode/climb_eom.py b/aviary/mission/gasp_based/ode/climb_eom.py index 436fd6fc2..50a06bab6 100644 --- a/aviary/mission/gasp_based/ode/climb_eom.py +++ b/aviary/mission/gasp_based/ode/climb_eom.py @@ -53,7 +53,7 @@ def setup(self): desc="rate of change of altitude", ) self.add_output( - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, val=np.zeros(nn), units="ft/s", desc="rate of change of horizontal distance covered", @@ -79,7 +79,7 @@ def setup(self): rows=arange, cols=arange) self.declare_partials( - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, ["TAS", Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.DRAG, Dynamic.Mission.MASS], rows=arange, cols=arange, @@ -113,7 +113,7 @@ def compute(self, inputs, outputs): gamma = np.arcsin((thrust - drag) / weight) outputs[Dynamic.Mission.ALTITUDE_RATE] = TAS * np.sin(gamma) - outputs["distance_rate"] = TAS * np.cos(gamma) + outputs[Dynamic.Mission.DISTANCE_RATE] = TAS * np.cos(gamma) outputs["required_lift"] = weight * np.cos(gamma) outputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] = gamma @@ -142,11 +142,12 @@ def compute_partials(self, inputs, J): J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.MASS] = \ TAS * np.cos(gamma) * dGamma_dWeight * GRAV_ENGLISH_LBM - J["distance_rate", "TAS"] = np.cos(gamma) - J["distance_rate", Dynamic.Mission.THRUST_TOTAL] = - \ + J[Dynamic.Mission.DISTANCE_RATE, "TAS"] = np.cos(gamma) + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.THRUST_TOTAL] = - \ TAS * np.sin(gamma) * dGamma_dThrust - J["distance_rate", Dynamic.Mission.DRAG] = -TAS * np.sin(gamma) * dGamma_dDrag - J["distance_rate", Dynamic.Mission.MASS] = \ + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.DRAG] = - \ + TAS * np.sin(gamma) * dGamma_dDrag + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.MASS] = \ -TAS * np.sin(gamma) * dGamma_dWeight * GRAV_ENGLISH_LBM J["required_lift", Dynamic.Mission.MASS] = ( diff --git a/aviary/mission/gasp_based/ode/climb_ode.py b/aviary/mission/gasp_based/ode/climb_ode.py index e9182e92b..6a16f4153 100644 --- a/aviary/mission/gasp_based/ode/climb_ode.py +++ b/aviary/mission/gasp_based/ode/climb_ode.py @@ -11,9 +11,10 @@ from aviary.mission.gasp_based.ode.params import ParamPort from aviary.subsystems.aerodynamics.aerodynamics_builder import AerodynamicsBuilderBase from aviary.subsystems.propulsion.propulsion_builder import PropulsionBuilderBase -from aviary.subsystems.propulsion.propulsion_mission import PropulsionMission from aviary.variable_info.enums import AnalysisScheme, AlphaModes, SpeedType from aviary.variable_info.variables import Dynamic +from aviary.mission.ode.specific_energy_rate import SpecificEnergyRate +from aviary.mission.ode.altitude_rate import AltitudeRate class ClimbODE(BaseODE): @@ -191,7 +192,7 @@ def setup(self): integration_states, promotes_outputs=[ Dynamic.Mission.ALTITUDE_RATE, - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, "required_lift", Dynamic.Mission.FLIGHT_PATH_ANGLE, ], @@ -218,9 +219,33 @@ def setup(self): + constraint_inputs, promotes_outputs=["theta", "TAS_violation"], ) + + # the last two subsystems will also be used for constraints + self.add_subsystem( + name='SPECIFIC_ENERGY_RATE_EXCESS', + subsys=SpecificEnergyRate(num_nodes=nn), + promotes_inputs=[(Dynamic.Mission.VELOCITY, "TAS"), Dynamic.Mission.MASS, + (Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL), + Dynamic.Mission.DRAG], + promotes_outputs=[(Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS)] + ) + + self.add_subsystem( + name='ALTITUDE_RATE_MAX', + subsys=AltitudeRate(num_nodes=nn), + promotes_inputs=[ + (Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS), + (Dynamic.Mission.VELOCITY_RATE, "TAS_rate"), + (Dynamic.Mission.VELOCITY, "TAS")], + promotes_outputs=[ + (Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.ALTITUDE_RATE_MAX)]) + if analysis_scheme is AnalysisScheme.COLLOCATION: self.set_order(['params', 'USatm', 'mach_balance_group'] + prop_groups + - ['lift_balance_group', 'constraints']) + ['lift_balance_group', 'constraints', 'SPECIFIC_ENERGY_RATE_EXCESS', 'ALTITUDE_RATE_MAX']) ParamPort.set_default_vals(self) self.set_input_defaults("CL_max", val=5 * np.ones(nn), units="unitless") diff --git a/aviary/mission/gasp_based/ode/constraints/test/test_climb_constraints.py b/aviary/mission/gasp_based/ode/constraints/test/test_climb_constraints.py index af4f9cb16..1b675c514 100644 --- a/aviary/mission/gasp_based/ode/constraints/test/test_climb_constraints.py +++ b/aviary/mission/gasp_based/ode/constraints/test/test_climb_constraints.py @@ -8,7 +8,6 @@ from aviary.mission.gasp_based.ode.constraints.speed_constraints import \ SpeedConstraints -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Dynamic @@ -43,13 +42,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('climb_specs/speeds.json') - def test_speed_constraints_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "climb_specs/speeds.json") - class SpeedConstraintTestCase2(unittest.TestCase): def setUp(self): @@ -82,13 +74,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('descent_specs/speeds.json') - def test_speed_constraints_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "descent1_specs/speeds.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/constraints/test/test_flight_constraints.py b/aviary/mission/gasp_based/ode/constraints/test/test_flight_constraints.py index 6a32352ae..2d37c0bab 100644 --- a/aviary/mission/gasp_based/ode/constraints/test/test_flight_constraints.py +++ b/aviary/mission/gasp_based/ode/constraints/test/test_flight_constraints.py @@ -8,7 +8,6 @@ from aviary.mission.gasp_based.ode.constraints.flight_constraints import \ FlightConstraints -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Dynamic @@ -50,11 +49,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=3e-11, rtol=1e-12) - @skipIfMissingXDSM('climb_specs/constraints.json') - def test_flight_constraints_spec(self): - subsystem = self.prob.model - assert_match_spec(subsystem, "climb_specs/constraints.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/descent_eom.py b/aviary/mission/gasp_based/ode/descent_eom.py index 32a37c0bc..02e9ee5bc 100644 --- a/aviary/mission/gasp_based/ode/descent_eom.py +++ b/aviary/mission/gasp_based/ode/descent_eom.py @@ -51,7 +51,7 @@ def setup(self): desc="rate of change of altitude", ) self.add_output( - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, val=np.zeros(nn), units="ft/s", desc="rate of change of horizontal distance covered", @@ -77,7 +77,7 @@ def setup(self): rows=arange, cols=arange) self.declare_partials( - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, ["TAS", Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.DRAG, Dynamic.Mission.MASS], rows=arange, cols=arange, @@ -112,7 +112,7 @@ def compute(self, inputs, outputs): gamma = (thrust - drag) / weight outputs[Dynamic.Mission.ALTITUDE_RATE] = alt_rate = TAS * np.sin(gamma) - outputs["distance_rate"] = TAS * np.cos(gamma) + outputs[Dynamic.Mission.DISTANCE_RATE] = TAS * np.cos(gamma) outputs["required_lift"] = weight * np.cos(gamma) - thrust * np.sin(alpha) outputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] = gamma @@ -134,10 +134,12 @@ def compute_partials(self, inputs, J): J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.MASS] = TAS * \ np.cos(gamma) * (-(thrust - drag) / weight**2) * GRAV_ENGLISH_LBM - J["distance_rate", "TAS"] = np.cos(gamma) - J["distance_rate", Dynamic.Mission.THRUST_TOTAL] = -TAS * np.sin(gamma) / weight - J["distance_rate", Dynamic.Mission.DRAG] = -TAS * np.sin(gamma) * (-1 / weight) - J["distance_rate", Dynamic.Mission.MASS] = ( + J[Dynamic.Mission.DISTANCE_RATE, "TAS"] = np.cos(gamma) + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.THRUST_TOTAL] = - \ + TAS * np.sin(gamma) / weight + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.DRAG] = - \ + TAS * np.sin(gamma) * (-1 / weight) + J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.MASS] = ( -TAS * np.sin(gamma) * (-(thrust - drag) / weight**2) * GRAV_ENGLISH_LBM ) diff --git a/aviary/mission/gasp_based/ode/descent_ode.py b/aviary/mission/gasp_based/ode/descent_ode.py index 3257fbb14..80d3e68e5 100644 --- a/aviary/mission/gasp_based/ode/descent_ode.py +++ b/aviary/mission/gasp_based/ode/descent_ode.py @@ -19,6 +19,8 @@ from aviary.variable_info.variables import Dynamic from aviary.subsystems.aerodynamics.aerodynamics_builder import AerodynamicsBuilderBase from aviary.subsystems.propulsion.propulsion_builder import PropulsionBuilderBase +from aviary.mission.ode.specific_energy_rate import SpecificEnergyRate +from aviary.mission.ode.altitude_rate import AltitudeRate class DescentODE(BaseODE): @@ -181,7 +183,7 @@ def setup(self): integration_states, promotes_outputs=[ Dynamic.Mission.ALTITUDE_RATE, - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, "required_lift", Dynamic.Mission.FLIGHT_PATH_ANGLE, ], @@ -232,6 +234,29 @@ def setup(self): add_default_solver=False, num_nodes=nn) + # the last two subsystems will also be used for constraints + self.add_subsystem( + name='SPECIFIC_ENERGY_RATE_EXCESS', + subsys=SpecificEnergyRate(num_nodes=nn), + promotes_inputs=[(Dynamic.Mission.VELOCITY, "TAS"), Dynamic.Mission.MASS, + (Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL), + Dynamic.Mission.DRAG], + promotes_outputs=[(Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS)] + ) + + self.add_subsystem( + name='ALTITUDE_RATE_MAX', + subsys=AltitudeRate(num_nodes=nn), + promotes_inputs=[ + (Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS), + (Dynamic.Mission.VELOCITY_RATE, "TAS_rate"), + (Dynamic.Mission.VELOCITY, "TAS")], + promotes_outputs=[ + (Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.ALTITUDE_RATE_MAX)]) + if analysis_scheme is AnalysisScheme.COLLOCATION: fc_loc = ['fc'] if input_speed_type is SpeedType.MACH: @@ -239,8 +264,13 @@ def setup(self): fc_loc = ['mach_balance_group'] # TODO this assumes the name of propulsion subsystem, need to pull from # the subsystem itself - self.set_order(['params', 'USatm',]+fc_loc+prop_groups+['lift_balance_group', - 'constraints']) + self.set_order(['params', + 'USatm',] + + fc_loc+prop_groups + + ['lift_balance_group', + 'constraints', + 'SPECIFIC_ENERGY_RATE_EXCESS', + 'ALTITUDE_RATE_MAX']) ParamPort.set_default_vals(self) self.set_input_defaults(Dynamic.Mission.ALTITUDE, diff --git a/aviary/mission/gasp_based/ode/flight_path_eom.py b/aviary/mission/gasp_based/ode/flight_path_eom.py index 7eb2e7624..57f1ae6a2 100644 --- a/aviary/mission/gasp_based/ode/flight_path_eom.py +++ b/aviary/mission/gasp_based/ode/flight_path_eom.py @@ -80,7 +80,7 @@ def setup(self): self.add_input("alpha", val=np.ones(nn), desc="angle of attack", units="deg") self.add_output( - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, val=np.ones(nn), desc="distance rate", units="ft/s", @@ -157,7 +157,7 @@ def setup_partials(self): ) self.declare_partials( - "distance_rate", ["TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange + Dynamic.Mission.DISTANCE_RATE, ["TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange ) # self.declare_partials("alpha_rate", ["*"], val=0.0) self.declare_partials( @@ -211,7 +211,7 @@ def compute(self, inputs, outputs): ) outputs[Dynamic.Mission.ALTITUDE_RATE] = TAS * np.sin(gamma) - outputs["distance_rate"] = TAS * np.cos(gamma) + outputs[Dynamic.Mission.DISTANCE_RATE] = TAS * np.cos(gamma) outputs["normal_force"] = normal_force @@ -343,8 +343,9 @@ def compute_partials(self, inputs, J): J["load_factor", Aircraft.Wing.INCIDENCE] = dTAcF_dIwing / \ (weight * np.cos(gamma)) - J["distance_rate", "TAS"] = np.cos(gamma) - J["distance_rate", Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) + J[Dynamic.Mission.DISTANCE_RATE, "TAS"] = np.cos(gamma) + J[Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) J["normal_force", Dynamic.Mission.MASS] = dNF_dWeight * GRAV_ENGLISH_LBM J["normal_force", Dynamic.Mission.LIFT] = dNF_dLift diff --git a/aviary/mission/gasp_based/ode/flight_path_ode.py b/aviary/mission/gasp_based/ode/flight_path_ode.py index 64542100b..1ec3fab63 100644 --- a/aviary/mission/gasp_based/ode/flight_path_ode.py +++ b/aviary/mission/gasp_based/ode/flight_path_ode.py @@ -8,11 +8,10 @@ from aviary.mission.gasp_based.ode.base_ode import BaseODE from aviary.mission.gasp_based.ode.flight_path_eom import FlightPathEOM from aviary.mission.gasp_based.ode.params import ParamPort -from aviary.subsystems.aerodynamics.gasp_based.gaspaero import (CruiseAero, - LowSpeedAero) -from aviary.subsystems.propulsion.propulsion_mission import PropulsionMission from aviary.subsystems.propulsion.propulsion_builder import PropulsionBuilderBase from aviary.variable_info.variables import Aircraft, Dynamic, Mission +from aviary.mission.ode.specific_energy_rate import SpecificEnergyRate +from aviary.mission.ode.altitude_rate import AltitudeRate class FlightPathODE(BaseODE): @@ -198,7 +197,7 @@ def setup(self): promotes_inputs=EOM_inputs, promotes_outputs=[ "TAS_rate", - "distance_rate", + Dynamic.Mission.DISTANCE_RATE, "normal_force", "fuselage_pitch", "load_factor", @@ -209,6 +208,28 @@ def setup(self): self.promotes('eoms', outputs=[ Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE]) + self.add_subsystem( + name='SPECIFIC_ENERGY_RATE_EXCESS', + subsys=SpecificEnergyRate(num_nodes=nn), + promotes_inputs=[(Dynamic.Mission.VELOCITY, "TAS"), Dynamic.Mission.MASS, + (Dynamic.Mission.THRUST_TOTAL, Dynamic.Mission.THRUST_MAX_TOTAL), + Dynamic.Mission.DRAG], + promotes_outputs=[(Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS)] + ) + + self.add_subsystem( + name='ALTITUDE_RATE_MAX', + subsys=AltitudeRate(num_nodes=nn), + promotes_inputs=[ + (Dynamic.Mission.SPECIFIC_ENERGY_RATE, + Dynamic.Mission.SPECIFIC_ENERGY_RATE_EXCESS), + (Dynamic.Mission.VELOCITY_RATE, "TAS_rate"), + (Dynamic.Mission.VELOCITY, "TAS")], + promotes_outputs=[ + (Dynamic.Mission.ALTITUDE_RATE, + Dynamic.Mission.ALTITUDE_RATE_MAX)]) + # Example of how to use a print_comp debug_comp = [] if False: @@ -261,7 +282,9 @@ def setup(self): 'alpha_comp', 'prop_group', 'eoms', - 'mass_trigger' + 'mass_trigger', + 'SPECIFIC_ENERGY_RATE_EXCESS', + 'ALTITUDE_RATE_MAX', ] + debug_comp) diff --git a/aviary/mission/gasp_based/ode/groundroll_eom.py b/aviary/mission/gasp_based/ode/groundroll_eom.py index 34643768e..610c818b6 100644 --- a/aviary/mission/gasp_based/ode/groundroll_eom.py +++ b/aviary/mission/gasp_based/ode/groundroll_eom.py @@ -39,7 +39,7 @@ def setup(self): self.add_output(Dynamic.Mission.ALTITUDE_RATE, val=np.ones(nn), desc="altitude rate", units="ft/s") self.add_output( - "distance_rate", val=np.ones(nn), desc="distance rate", units="ft/s" + Dynamic.Mission.DISTANCE_RATE, val=np.ones(nn), desc="distance rate", units="ft/s" ) self.add_output( "normal_force", val=np.ones(nn), desc="normal forces", units="lbf" @@ -60,7 +60,7 @@ def setup(self): self.declare_partials(Dynamic.Mission.ALTITUDE_RATE, [ "TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange) self.declare_partials( - "distance_rate", ["TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange + Dynamic.Mission.DISTANCE_RATE, ["TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange ) self.declare_partials( "normal_force", @@ -121,7 +121,7 @@ def compute(self, inputs, outputs): outputs[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE] = np.zeros(nn) outputs[Dynamic.Mission.ALTITUDE_RATE] = TAS * np.sin(gamma) - outputs["distance_rate"] = TAS * np.cos(gamma) + outputs[Dynamic.Mission.DISTANCE_RATE] = TAS * np.cos(gamma) outputs["normal_force"] = normal_force outputs["fuselage_pitch"] = gamma * 180 / np.pi - i_wing + alpha @@ -204,8 +204,9 @@ def compute_partials(self, inputs, J): J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE] = TAS * np.cos(gamma) - J["distance_rate", "TAS"] = np.cos(gamma) - J["distance_rate", Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) + J[Dynamic.Mission.DISTANCE_RATE, "TAS"] = np.cos(gamma) + J[Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) J["normal_force", Dynamic.Mission.MASS] = dNF_dWeight * GRAV_ENGLISH_LBM J["normal_force", Dynamic.Mission.LIFT] = dNF_dLift diff --git a/aviary/mission/gasp_based/ode/rotation_eom.py b/aviary/mission/gasp_based/ode/rotation_eom.py index c42d922e7..f721a86e0 100644 --- a/aviary/mission/gasp_based/ode/rotation_eom.py +++ b/aviary/mission/gasp_based/ode/rotation_eom.py @@ -39,7 +39,7 @@ def setup(self): self.add_output(Dynamic.Mission.ALTITUDE_RATE, val=np.ones(nn), desc="altitude rate", units="ft/s") self.add_output( - "distance_rate", val=np.ones(nn), desc="distance rate", units="ft/s" + Dynamic.Mission.DISTANCE_RATE, val=np.ones(nn), desc="distance rate", units="ft/s" ) self.add_output( "normal_force", val=np.ones(nn), desc="normal forces", units="lbf" @@ -74,7 +74,7 @@ def setup_partials(self): self.declare_partials(Dynamic.Mission.ALTITUDE_RATE, [ "TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange) self.declare_partials( - "distance_rate", ["TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange + Dynamic.Mission.DISTANCE_RATE, ["TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE], rows=arange, cols=arange ) self.declare_partials( @@ -125,7 +125,7 @@ def compute(self, inputs, outputs): outputs[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE] = np.zeros(nn) outputs[Dynamic.Mission.ALTITUDE_RATE] = TAS * np.sin(gamma) - outputs["distance_rate"] = TAS * np.cos(gamma) + outputs[Dynamic.Mission.DISTANCE_RATE] = TAS * np.cos(gamma) outputs["normal_force"] = normal_force outputs["fuselage_pitch"] = gamma * 180 / np.pi - i_wing + alpha @@ -209,8 +209,9 @@ def compute_partials(self, inputs, J): J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE] = TAS * np.cos(gamma) - J["distance_rate", "TAS"] = np.cos(gamma) - J["distance_rate", Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) + J[Dynamic.Mission.DISTANCE_RATE, "TAS"] = np.cos(gamma) + J[Dynamic.Mission.DISTANCE_RATE, + Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) J["normal_force", Dynamic.Mission.MASS] = dNF_dWeight * GRAV_ENGLISH_LBM J["normal_force", Dynamic.Mission.LIFT] = dNF_dLift diff --git a/aviary/mission/gasp_based/ode/test/test_accel_eom.py b/aviary/mission/gasp_based/ode/test/test_accel_eom.py index b1f42e91a..4b3308162 100644 --- a/aviary/mission/gasp_based/ode/test/test_accel_eom.py +++ b/aviary/mission/gasp_based/ode/test/test_accel_eom.py @@ -7,7 +7,6 @@ assert_near_equal) from aviary.mission.gasp_based.ode.accel_eom import AccelerationRates -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Dynamic @@ -48,20 +47,14 @@ def test_case1(self): # note: this was finite differenced from GASP. The fd value is: np.array([5.2353365, 5.2353365]) ) assert_near_equal( - self.prob["distance_rate"], np.array([425.32808399, 425.32808399]), tol + self.prob[Dynamic.Mission.DISTANCE_RATE], np.array( + [425.32808399, 425.32808399]), tol # note: this was finite differenced from GASP. The fd value is: np.array([441.6439, 441.6439]) ) partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('accel_specs/eom.json') - def test_accel_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "accel_specs/eom.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_accel_ode.py b/aviary/mission/gasp_based/ode/test/test_accel_ode.py index 98a613b58..ed71fda96 100644 --- a/aviary/mission/gasp_based/ode/test/test_accel_ode.py +++ b/aviary/mission/gasp_based/ode/test/test_accel_ode.py @@ -6,9 +6,7 @@ from aviary.mission.gasp_based.ode.accel_ode import AccelODE from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import (assert_match_spec, - check_prob_outputs, - skipIfMissingXDSM) +from aviary.utils.test_utils.IO_test_util import check_prob_outputs from aviary.variable_info.variables import Dynamic from aviary.interface.default_phase_info.two_dof import default_mission_subsystems @@ -45,12 +43,6 @@ def test_accel(self): ) assert_check_partials(partial_data, rtol=1e-10) - @skipIfMissingXDSM('statics_specs/accelerate.json') - def test_accel_spec(self): - """Test accel ODE spec""" - subsystem = self.prob.model - assert_match_spec(subsystem, "statics_specs/accelerate.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_ascent_eom.py b/aviary/mission/gasp_based/ode/test/test_ascent_eom.py index d13beee6c..c6f19ea29 100644 --- a/aviary/mission/gasp_based/ode/test/test_ascent_eom.py +++ b/aviary/mission/gasp_based/ode/test/test_ascent_eom.py @@ -6,7 +6,6 @@ from openmdao.utils.assert_utils import assert_check_partials from aviary.mission.gasp_based.ode.ascent_eom import AscentEOM -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Dynamic @@ -41,13 +40,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('ascent_specs/eom.json') - def test_ascent_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "ascent_specs/eom.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_ascent_ode.py b/aviary/mission/gasp_based/ode/test/test_ascent_ode.py index 540899d70..4b972bbb3 100644 --- a/aviary/mission/gasp_based/ode/test/test_ascent_ode.py +++ b/aviary/mission/gasp_based/ode/test/test_ascent_ode.py @@ -5,7 +5,6 @@ from openmdao.utils.assert_utils import assert_check_partials from aviary.mission.gasp_based.ode.ascent_ode import AscentODE -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.interface.default_phase_info.two_dof import default_mission_subsystems from aviary.variable_info.options import get_option_defaults @@ -31,12 +30,6 @@ def test_ascent_partials(self): ) assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('statics_specs/ascent.json') - def test_ascent_spec(self): - """Test ascent ODE spec""" - subsystem = self.prob.model - assert_match_spec(subsystem, "statics_specs/ascent.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_climb_eom.py b/aviary/mission/gasp_based/ode/test/test_climb_eom.py index d9539a24e..dea987d97 100644 --- a/aviary/mission/gasp_based/ode/test/test_climb_eom.py +++ b/aviary/mission/gasp_based/ode/test/test_climb_eom.py @@ -7,7 +7,6 @@ assert_near_equal) from aviary.mission.gasp_based.ode.climb_eom import ClimbRates -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Dynamic @@ -45,7 +44,8 @@ def test_case1(self): [6.24116612, 6.24116612]), tol ) # note: values from GASP are: np.array([5.9667, 5.9667]) assert_near_equal( - self.prob["distance_rate"], np.array([774.679584, 774.679584]), tol + self.prob[Dynamic.Mission.DISTANCE_RATE], np.array( + [774.679584, 774.679584]), tol # note: these values are finite differenced and lose accuracy. Fd values are: np.array([799.489, 799.489]) ) assert_near_equal( @@ -61,13 +61,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('climb_specs/eom.json') - def test_climb_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "climb_specs/eom.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_climb_ode.py b/aviary/mission/gasp_based/ode/test/test_climb_ode.py index 9094977a5..47eb53f5f 100644 --- a/aviary/mission/gasp_based/ode/test/test_climb_ode.py +++ b/aviary/mission/gasp_based/ode/test/test_climb_ode.py @@ -6,9 +6,7 @@ from openmdao.utils.assert_utils import assert_check_partials from aviary.mission.gasp_based.ode.climb_ode import ClimbODE -from aviary.utils.test_utils.IO_test_util import (assert_match_spec, - check_prob_outputs, - skipIfMissingXDSM) +from aviary.utils.test_utils.IO_test_util import check_prob_outputs from aviary.variable_info.options import get_option_defaults from aviary.variable_info.variables import Aircraft, Dynamic from aviary.interface.default_phase_info.two_dof import default_mission_subsystems @@ -48,7 +46,7 @@ def test_start_of_climb(self): "CD": 0.0307, Dynamic.Mission.ALTITUDE_RATE: 3186 / 60, # TAS (kts -> ft/s) * cos(gamma) - "distance_rate": (254 * 1.68781) * np.cos(np.deg2rad(7.12)), + Dynamic.Mission.DISTANCE_RATE: (254 * 1.68781) * np.cos(np.deg2rad(7.12)), Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: -13505, # lbm/h "theta": np.deg2rad(12.31), Dynamic.Mission.FLIGHT_PATH_ANGLE: np.deg2rad(7.12), @@ -84,7 +82,7 @@ def test_end_of_climb(self): "CD": [0.0270, 0.0326], Dynamic.Mission.ALTITUDE_RATE: [3054 / 60, 453 / 60], # TAS (kts -> ft/s) * cos(gamma) - "distance_rate": [ + Dynamic.Mission.DISTANCE_RATE: [ (319 * 1.68781) * np.cos(np.deg2rad(5.42)), (459 * 1.68781) * np.cos(np.deg2rad(0.56)), ], @@ -95,16 +93,6 @@ def test_end_of_climb(self): } check_prob_outputs(self.prob, testvals, 1e-1) # TODO tighten - @skipIfMissingXDSM('statics_specs/climb1.json') - def test_climb1_spec(self): - """Test climb1 phase spec""" - assert_match_spec(self.sys, "statics_specs/climb1.json") - - @skipIfMissingXDSM('statics_specs/climb2.json') - def test_climb2_spec(self): - """Test climb2 phase spec""" - assert_match_spec(self.sys, "statics_specs/climb2.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_descent_eom.py b/aviary/mission/gasp_based/ode/test/test_descent_eom.py index 00dfc13c9..34c974895 100644 --- a/aviary/mission/gasp_based/ode/test/test_descent_eom.py +++ b/aviary/mission/gasp_based/ode/test/test_descent_eom.py @@ -7,7 +7,6 @@ assert_near_equal) from aviary.mission.gasp_based.ode.descent_eom import DescentRates -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Dynamic @@ -47,7 +46,8 @@ def test_case1(self): [-39.41011217, -39.41011217]), tol ) # note: values from GASP are: np.array([-39.75, -39.75]) assert_near_equal( - self.prob["distance_rate"], np.array([773.70165638, 773.70165638]), tol + self.prob[Dynamic.Mission.DISTANCE_RATE], np.array( + [773.70165638, 773.70165638]), tol # note: these values are finite differenced and lose accuracy. Fd values are:np.array([964.4634921, 964.4634921]) ) assert_near_equal( @@ -63,13 +63,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('descent1_specs/eom.json') - def test_descent_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "descent1_specs/eom.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_descent_ode.py b/aviary/mission/gasp_based/ode/test/test_descent_ode.py index b7a64e408..7d642f6e8 100644 --- a/aviary/mission/gasp_based/ode/test/test_descent_ode.py +++ b/aviary/mission/gasp_based/ode/test/test_descent_ode.py @@ -9,9 +9,7 @@ from aviary.mission.gasp_based.ode.descent_ode import DescentODE from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import (assert_match_spec, - check_prob_outputs, - skipIfMissingXDSM) +from aviary.utils.test_utils.IO_test_util import check_prob_outputs from aviary.variable_info.enums import SpeedType from aviary.variable_info.variables import Dynamic from aviary.interface.default_phase_info.two_dof import default_mission_subsystems @@ -48,7 +46,7 @@ def test_high_alt(self): "CD": np.array([0.0279, 0.0197]), Dynamic.Mission.ALTITUDE_RATE: np.array([-2385, -3076]) / 60, # TAS (ft/s) * cos(gamma) - "distance_rate": [ + Dynamic.Mission.DISTANCE_RATE: [ (459 * 1.68781) * np.cos(np.deg2rad(-2.94)), (437 * 1.68781) * np.cos(np.deg2rad(-3.98)), ], @@ -85,7 +83,7 @@ def test_low_alt(self): "CD": 0.0271, Dynamic.Mission.ALTITUDE_RATE: -1158 / 60, # TAS (ft/s) * cos(gamma) - "distance_rate": (255 * 1.68781) * np.cos(np.deg2rad(-2.56)), + Dynamic.Mission.DISTANCE_RATE: (255 * 1.68781) * np.cos(np.deg2rad(-2.56)), Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: -1294, Dynamic.Mission.FLIGHT_PATH_ANGLE: np.deg2rad(-2.56), } @@ -96,22 +94,6 @@ def test_low_alt(self): ) assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('statics_specs/descent1.json') - def test_descent1_ode_spec(self): - """Test descent1 phase spec""" - self.sys.options["input_speed_type"] = SpeedType.MACH - self.prob.setup() - subsystem = self.prob.model - assert_match_spec(subsystem, "statics_specs/descent1.json") - - @skipIfMissingXDSM('statics_specs/descent2.json') - def test_descent2_ode_spec(self): - """Test descent2 phase spec""" - self.sys.options["input_speed_type"] = SpeedType.EAS - self.prob.setup() - subsystem = self.prob.model - assert_match_spec(subsystem, "statics_specs/descent2.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_groundroll_eom.py b/aviary/mission/gasp_based/ode/test/test_groundroll_eom.py index af344571c..bcb055227 100644 --- a/aviary/mission/gasp_based/ode/test/test_groundroll_eom.py +++ b/aviary/mission/gasp_based/ode/test/test_groundroll_eom.py @@ -6,7 +6,6 @@ from openmdao.utils.assert_utils import assert_check_partials from aviary.mission.gasp_based.ode.groundroll_eom import GroundrollEOM -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Dynamic @@ -43,13 +42,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('groundroll_specs/eom.json') - def test_groundroll_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "groundroll_specs/eom.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_groundroll_ode.py b/aviary/mission/gasp_based/ode/test/test_groundroll_ode.py index d70a26b4b..147eb0fa9 100644 --- a/aviary/mission/gasp_based/ode/test/test_groundroll_ode.py +++ b/aviary/mission/gasp_based/ode/test/test_groundroll_ode.py @@ -5,7 +5,6 @@ from openmdao.utils.assert_utils import assert_check_partials from aviary.mission.gasp_based.ode.groundroll_ode import GroundrollODE -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.options import get_option_defaults from aviary.interface.default_phase_info.two_dof import default_mission_subsystems @@ -31,12 +30,6 @@ def test_groundroll_partials(self): ) assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('statics_specs/groundroll.json') - def test_groundroll_spec(self): - """Test groundroll ODE spec""" - subsystem = self.prob.model - assert_match_spec(subsystem, "statics_specs/groundroll.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_rotation_eom.py b/aviary/mission/gasp_based/ode/test/test_rotation_eom.py index 3d48f22a8..f8a30e4c1 100644 --- a/aviary/mission/gasp_based/ode/test/test_rotation_eom.py +++ b/aviary/mission/gasp_based/ode/test/test_rotation_eom.py @@ -6,7 +6,6 @@ from openmdao.utils.assert_utils import assert_check_partials from aviary.mission.gasp_based.ode.rotation_eom import RotationEOM -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Dynamic @@ -41,13 +40,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('rotation_specs/eom.json') - def test_rotation_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "rotation_specs/eom.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/test/test_rotation_ode.py b/aviary/mission/gasp_based/ode/test/test_rotation_ode.py index 891c21b20..ecad9de8b 100644 --- a/aviary/mission/gasp_based/ode/test/test_rotation_ode.py +++ b/aviary/mission/gasp_based/ode/test/test_rotation_ode.py @@ -5,7 +5,6 @@ from openmdao.utils.assert_utils import assert_check_partials from aviary.mission.gasp_based.ode.rotation_ode import RotationODE -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.options import get_option_defaults from aviary.variable_info.variables import Aircraft, Dynamic from aviary.interface.default_phase_info.two_dof import default_mission_subsystems @@ -35,12 +34,6 @@ def test_rotation_partials(self): ) assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('statics_specs/rotation.json') - def test_rotation_spec(self): - """Test rotation ODE spec""" - subsystem = self.prob.model - assert_match_spec(subsystem, "statics_specs/rotation.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/ode/time_integration_base_classes.py b/aviary/mission/gasp_based/ode/time_integration_base_classes.py index d612c1618..b3a8d0e17 100644 --- a/aviary/mission/gasp_based/ode/time_integration_base_classes.py +++ b/aviary/mission/gasp_based/ode/time_integration_base_classes.py @@ -8,6 +8,16 @@ from aviary.mission.gasp_based.ode.params import ParamPort +def add_SGM_required_inputs(group: om.Group, inputs_to_add: dict): + blank_execcomp = om.ExecComp() + for input, details in inputs_to_add.items(): + blank_execcomp.add_input(input, **details) + group.add_subsystem('SGM_required_inputs', + blank_execcomp, + promotes_inputs=list(inputs_to_add.keys()), + ) + + # Subproblem used as a basis for forward in time integration phases. class SimuPyProblem(SimulationMixin): def __init__( @@ -15,19 +25,13 @@ def __init__( ode, time_independent=False, t_name="t_curr", - state_names=None, + states=None, alternate_state_names=None, blocked_state_names=None, - state_units=None, - state_rate_names=None, alternate_state_rate_names=None, - state_rate_units=None, - parameter_names=None, - parameter_units=None, - output_names=None, - output_units=None, - control_names=None, - control_units=None, + parameters=None, + outputs=None, + controls=None, include_state_outputs=False, rate_suffix="_rate", DEBUG=False, @@ -36,9 +40,21 @@ def __init__( ): """ + states: a dictionary of the form {state_name:{'units':unit, 'rate':state_rate_name, 'rate_units':state_rate_units}} + alternate_state_names: a dictionary of the form {state_to_replace:new_state_name} + blocked_state_names: a list of the form [state_name_to_block] + alternate_state_rate_names: a dictionary of the form {state_to_modify:new_state_rate_name} + parameters: a dictionary of the form {parameter_name:unit} + outputs: a dictionary of the form {output_name:unit} + controls: a dictionary of the form {control_name:unit} include_state_outputs : automatically add the state to the input works well for auto-parsed naming, does not check for duplication before adding + states, parameters, outputs, and controls can also be input as a list of keys for the dictionary """ + + default_om_list_args = dict(prom_name=True, val=False, + out_stream=None, units=True) + self.DEBUG = DEBUG self.max_allowable_time = max_allowable_time self.adjoint_int_opts = adjoint_int_opts @@ -59,140 +75,122 @@ def __init__( prob.final_setup() self.time_independent = time_independent - if control_names is None: - control_names = [] - self.control_names = control_names - if control_units is not None: - self.control_units = control_units - else: - self.control_units = [ - om_dict["units"] - for om_name, om_dict in prob.model.list_inputs( - includes=control_names, - out_stream=False, - val=False, - units=True, - ) - ] + if type(states) is list: + states = {state: {'units': None, 'rate': state+rate_suffix, 'rate_units': None} + for state in states} + if type(parameters) is list: + parameters = {parameter: None for parameter in parameters} + if type(outputs) is list: + outputs = {output: None for output in outputs} + if controls is None: + controls = {} + elif type(controls) is list: + controls = {control: None for control in controls} + if alternate_state_names is None: + alternate_state_names = {} + if alternate_state_rate_names is None: + alternate_state_rate_names = {} + + data = prob.model.list_inputs(includes=controls.keys(), **default_om_list_args) + control_data = {val.pop('prom_name'): val for key, val in data} + for control_name, control_units in controls.items(): + if control_units is None: + controls[control_name] = control_data[control_name]["units"] + self.controls = controls if ( - state_names is None - or parameter_names is None - or output_names is None # or + states is None + or parameters is None + or outputs is None # or # event_names is None ): - data = prob.model.list_outputs(prom_name=True, val=False, out_stream=None) - outputs = [val['prom_name'] for key, val in data] - data = prob.model.list_inputs(prom_name=True, val=False, out_stream=None) - inputs = [val['prom_name'] for key, val in data] - - if state_names is None: - state_names = [ - output.replace(rate_suffix, "") - for output in outputs + data = prob.model.list_outputs(**default_om_list_args) + model_outputs = [val['prom_name'] for key, val in data] + data = prob.model.list_inputs(**default_om_list_args) + model_inputs = [val['prom_name'] for key, val in data] + + if states is None: + states = { + output.replace(rate_suffix, ""): {'units': None, 'rate': output} + for output in model_outputs if output.endswith(rate_suffix) or output in alternate_state_names.keys() - ] + } + if blocked_state_names is not None: for name in blocked_state_names: - if name in state_names: - state_names.remove(name) - if alternate_state_names is not None: - for key, val in alternate_state_names.items(): - state_key = key.replace(rate_suffix, "") - if state_key in state_names: # Used to rename an existing state - state_names[state_names.index(state_key)] = val + if name in states.keys(): + states.pop(name) + if alternate_state_names: + for state_key, val in alternate_state_names.items(): + if state_key in states.keys(): # Used to rename an existing state + states[val] = states.pop(state_key) else: # Used to add a new state - state_names.append(val) - - if state_rate_names is None: - state_rate_names = [state_name + rate_suffix for state_name in state_names] - if alternate_state_names is not None: - state_rate_names = [name if name not in alternate_state_rate_names.keys( - ) else alternate_state_rate_names[name] for name in state_rate_names] - - if state_rate_units is not None: - self.state_rate_units = state_rate_units - else: - self.state_rate_units = [ - om_dict["units"] - for om_name, om_dict in prob.model.list_outputs( - includes=state_rate_names, - out_stream=False, - val=False, - units=True, - ) - ] - - if state_units is not None: - self.state_units = state_units - else: - self.state_units = [ - units.simplify_unit("s*" + rate_unit) - for rate_unit in self.state_rate_units - ] + states[val]: {'units': None, 'rate': val+rate_suffix} + + if alternate_state_rate_names: + for state_name, val in alternate_state_rate_names.items(): + if state_name in states.keys(): + states[state_name]['rate'] = val + + state_rate_names = [val['rate'] for _, val in states.items()] + data = prob.model.list_outputs(includes=state_rate_names, **default_om_list_args) + rate_data = {val.pop('prom_name'): val for key, val in data} + for state_name, state_data in states.items(): + if state_data['rate_units'] is None: + state_data['rate_units'] = rate_data[state_data['rate']]["units"] + if state_data['units'] is None: + rate_unit = rate_data[state_data['rate']]["units"] + states[state_name]['units'] = units.simplify_unit("s*" + rate_unit) + + if parameters is None: + parameters = { + inp: None + for inp in set(model_inputs) + if inp not in list(states.keys()) + list(controls.keys()) + [t_name] + } - if parameter_names is None: - parameter_names = [ - inp - for inp in set(inputs) - if inp not in state_names + control_names + [t_name] - ] - if parameter_units is not None: - self.parameter_units = parameter_units - else: - self.parameter_units = [ - om_dict["units"] - for om_name, om_dict in prob.model.list_inputs( - includes=parameter_names, - out_stream=False, - val=False, - units=True, - ) - ] + data = prob.model.list_inputs(includes=parameters.keys(), **default_om_list_args) + parameter_data = {val.pop('prom_name'): val for key, val in data} + for parameter_name, parameter_units in parameters.items(): + if parameter_units is None: + parameters[parameter_name] = parameter_data[parameter_name]["units"] - if output_names is None: - output_names = [ - outp + if outputs is None: + outputs = { + outp: None for outp in outputs if outp not in state_rate_names # + event_names - ] + } - if output_units is not None: - self.output_units = output_units - else: - self.output_units = [ - next(iter(prob.model.get_io_metadata( + for output_name, output_units in outputs.items(): + if output_units is None: + outputs[output_name] = next(iter(prob.model.get_io_metadata( includes=[output_name], metadata_keys=["units"] ).values()))["units"] - for output_name in output_names - ] - if include_state_outputs: - output_names = state_names + output_names - self.output_units = self.state_units + self.output_units + if include_state_outputs or outputs == {}: # prevent empty outputs + outputs = states + outputs self.t_name = t_name - self.state_names = state_names + self.states = states - self.state_rate_names = state_rate_names - self.parameter_names = parameter_names - self.output_names = output_names + self.parameters = parameters + self.outputs = outputs - self.dim_state = len(state_names) - self.dim_output = len(output_names) - self.dim_input = len(control_names) - self.dim_parameters = len(parameter_names) + self.dim_state = len(states) + self.dim_output = len(outputs) + self.dim_input = len(controls) + self.dim_parameters = len(parameters) # TODO: add defensive checks to make sure dimensions match in both setup and # calls - if DEBUG: + if DEBUG or True: om.n2(prob, outfile="n2_simupy_problem.html", show_browser=False) - print(state_names) - print(self.state_units) - print(state_rate_names) - print(self.state_rate_units) + with open('input_list_simupy.txt', 'w') as outfile: + prob.model.list_inputs(out_stream=outfile,) + print(states) @property def time(self): @@ -208,8 +206,8 @@ def time(self, value): def state(self): return np.array( [ - self.prob.get_val(state_name, units=unit)[0] - for state_name, unit in zip(self.state_names, self.state_units) + self.prob.get_val(state_name, units=state_data['units'])[0] + for state_name, state_data in self.states.items() ] ) @@ -217,15 +215,17 @@ def state(self): def state(self, value): if np.all(self.state == value): return - for state_name, elem_val, unit in zip( - self.state_names, value, self.state_units + for state_name, elem_val in zip( + self.states.keys(), value ): - self.prob.set_val(state_name, elem_val, units=unit) + self.prob.set_val(state_name, elem_val, + units=self.states[state_name]['units']) def compute_along_traj(self, ts, xs): self.prob.set_val(self.t_name, ts) - for state_name, elem_val, unit in zip(self.state_names, xs.T, self.state_units): - self.prob.set_val(state_name, elem_val, units=unit) + for state_name, elem_val in zip(self.states.keys(), xs.T): + self.prob.set_val(state_name, elem_val, + units=self.states[state_name]['units']) self.prob.run_model() @@ -234,7 +234,7 @@ def control(self): return np.array( [ self.prob.get_val(control_name, units=unit)[0] - for control_name, unit in zip(self.control_names, self.control_units) + for control_name, unit in self.controls.items() ] ) @@ -244,19 +244,17 @@ def control(self, value): value = np.array([]) if (self.control.size == value.size) and np.all(self.control == value): return - for control_name, elem_val, unit in zip( - self.control_names, value, self.control_units + for control_name, elem_val in zip( + self.controls, value ): - self.prob.set_val(control_name, elem_val, units=unit) + self.prob.set_val(control_name, elem_val, units=self.controls[control_name]) @property def parameter(self): return np.array( [ self.prob.get_val(parameter_name, units=unit)[0] - for parameter_name, unit in zip( - self.parameter_names, self.parameter_units - ) + for parameter_name, unit in self.parameters.items() ] ) @@ -264,8 +262,8 @@ def parameter(self): def parameter(self, value): if np.all(self.parameter == value): return - for parameter_name, elem_val, unit in zip( - self.parameter_names, value, self.parameter_units + for parameter_name, elem_val in zip( + self.paramaters, value ): self.prob.set_val(parameter_name, elem_val) @@ -273,10 +271,8 @@ def parameter(self, value): def state_rate(self): return np.array( [ - self.prob.get_val(state_rate_name, units=unit)[0] - for state_rate_name, unit in zip( - self.state_rate_names, self.state_rate_units - ) + self.prob.get_val(state_data['rate'], units=state_data['rate_units'])[0] + for _, state_data in self.states.items() ] ) @@ -285,7 +281,7 @@ def output(self): return np.array( [ self.prob.get_val(output_name, units=unit)[0] - for output_name, unit in zip(self.output_names, self.output_units) + for output_name, unit in self.outputs.items() ] ) @@ -360,8 +356,10 @@ def initialize(self): def setup_params( self, ODEs, + promote_all_auto_ivc=False, traj_final_state_output=None, traj_promote_final_output=None, + traj_promote_initial_input=None, traj_initial_state_input=None, traj_event_trigger_input=None, ): @@ -381,13 +379,29 @@ def setup_params( traj_final_state_output = [] if traj_promote_final_output is None: traj_promote_final_output = [] + if traj_promote_initial_input is None: + traj_promote_initial_input = {} if traj_initial_state_input is None: traj_initial_state_input = [] if traj_event_trigger_input is None: traj_event_trigger_input = [] - for name, kwargs in self.options["param_dict"].items(): + if promote_all_auto_ivc: + for ode in ODEs: + ode: SimuPyProblem + ode.auto_ivc_vars = ode.prob.model.list_inputs( + is_indep_var=True, units=True, out_stream=None) + for abs_name, data in ode.auto_ivc_vars: + prom_name = data.pop('prom_name') + if '.' in prom_name: + continue + traj_promote_initial_input[prom_name] = data + + self.traj_promote_initial_input = { + **self.options["param_dict"], **traj_promote_initial_input} + for name, kwargs in self.traj_promote_initial_input.items(): self.add_input(name, **kwargs) + final_suffix = "_final" self.traj_final_state_output = { final_state_output: { @@ -397,9 +411,7 @@ def setup_params( ), **self.add_output( final_state_output+final_suffix, - units=ODEs[-1].state_units[ - ODEs[-1].state_names.index(final_state_output) - ], + units=ODEs[-1].states[final_state_output]['units'], ) } for final_state_output in traj_final_state_output @@ -412,9 +424,7 @@ def setup_params( ), **self.add_output( promoted_final_output+final_suffix, - units=ODEs[-1].output_units[ - ODEs[-1].output_names.index(promoted_final_output) - ], + units=ODEs[-1].outputs[promoted_final_output], ), } for promoted_final_output in traj_promote_final_output @@ -429,9 +439,7 @@ def setup_params( **dict(name=initial_state_input+initial_suffix), **self.add_input( initial_state_input+initial_suffix, - units=ODEs[0].state_units[ - ODEs[0].state_names.index(initial_state_input) - ], + units=ODEs[0].states[initial_state_input]['units'], ) } for initial_state_input in traj_initial_state_input @@ -452,9 +460,7 @@ def setup_params( event_trigger_input[1], trigger_suffix ]), - units=event_trigger_input[0].state_units[ - event_trigger_input[0].state_names.index(event_trigger_input[1]) - ], + units=event_trigger_input[0].states[event_trigger_input[1]]['units'], ) } for event_trigger_input in traj_event_trigger_input @@ -463,17 +469,16 @@ def setup_params( self.declare_partials(["*"], ["*"],) def compute_params(self, inputs): - # parameter pass-through setup - for param_input in self.options["param_dict"].keys(): + for input in self.traj_promote_initial_input.keys(): for ode in self.ODEs: try: - ode.set_val(param_input, inputs[param_input]) + ode.set_val(input, inputs[input]) except KeyError: if self.DEBUG: print( - "*** ParamPort input not found:", + "*** Input not found:", ode, - param_input + input ) pass @@ -490,7 +495,7 @@ def compute_traj_loop(self, first_problem, inputs, outputs, t0=0., state0=None): inputs[state_name+"_initial"].squeeze() if state_name in self.traj_initial_state_input else 0. - for state_name in first_problem.state_names + for state_name in first_problem.states.keys() ]).squeeze() while True: @@ -533,10 +538,8 @@ def compute_traj_loop(self, first_problem, inputs, outputs, t0=0., state0=None): current_problem.output_equation_function(t, x) state = np.array( [ - current_problem.prob.get_val(state_name, units=unit) - for state_name, unit in zip( - next_problem.state_names, next_problem.state_units - ) + current_problem.prob.get_val(state_name, units=state_data['units']) + for state_name, state_data in next_problem.states.items() ] ).squeeze() sim_problems.append(next_problem) @@ -555,7 +558,7 @@ def compute_traj_loop(self, first_problem, inputs, outputs, t0=0., state0=None): outputs[output_name] = sim_results[-1].x[ -1, - sim_problems[-1].state_names.index(state_name) + list(sim_problems[-1].states.keys()).index(state_name) ] for output in self.traj_promote_final_output: @@ -564,7 +567,7 @@ def compute_traj_loop(self, first_problem, inputs, outputs, t0=0., state0=None): outputs[promoted_name] = sim_results[-1].y[ -1, - sim_problems[-1].output_names.index(output_name) + list(sim_problems[-1].outputs.keys()).index(output_name) ] self.last_inputs = np.array(list(inputs.values())) @@ -612,13 +615,13 @@ def compute_partials(self, inputs, J): param_deriv = np.zeros(len(param_dict)) if output in self.traj_final_state_output: - costate[next_prob.state_names.index(output)] = 1. + costate[list(next_prob.states.keys()).index(output)] = 1. else: # in self.traj_promote_final_output next_prob.state_equation_function(next_res.t[-1], next_res.x[-1, :]) costate[:] = next_prob.compute_totals( output, - next_prob.state_names, + next_prob.states.keys(), return_format='array' ).squeeze() @@ -659,12 +662,12 @@ def compute_partials(self, inputs, J): num_active_event_channels += 1 dg_dx = np.zeros((1, prob.dim_state)) - if channel_name in prob.state_names: - dg_dx[0, prob.state_names.index(channel_name)] = 1. + if channel_name in prob.states.keys(): + dg_dx[0, list(prob.states.keys()).index(channel_name)] = 1. else: dg_dx[0, :] = prob.compute_totals( [channel_name], - prob.state_names, + prob.states.keys(), return_format='array' ) @@ -700,17 +703,17 @@ def compute_partials(self, inputs, J): # here and co-state assume number of states is only decreasing # forward in time - for state_name in next_prob.state_names: - state_idx = next_prob.state_names.index(state_name) + for state_name in next_prob.states.keys(): + state_idx = list(next_prob.states.keys()).index(state_name) - if state_name in prob.state_names: + if state_name in prob.states.keys(): f_plus[ state_idx - ] = plus_rate[prob.state_names.index(state_name)] + ] = plus_rate[list(prob.states.keys()).index(state_name)] # state_update[ - # next_prob.state_names.index(state_name) - # ] = x[prob.state_names.index(state_name)] + # list(next_prob.states.keys()).index(state_name) + # ] = x[list(prob.states.keys()).index(state_name)] # TODO: make sure index multiplying next_pronb costate # lines up -- since costate is pre-filled to next_prob's @@ -718,14 +721,14 @@ def compute_partials(self, inputs, J): # column should map to dh_dx[state_idx, state_idx] = 1. - elif state_name in prob.output_names: + elif state_name in prob.outputs.keys(): state_update[ state_idx - ] = res.y[-1, prob.output_names.index(state_name)] + ] = res.y[-1, list(prob.outputs.keys()).index(state_name)] dh_j_dx = prob.compute_totals( [state_name], - prob.state_names, + prob.states.keys(), return_format='array').squeeze() dh_dparam[state_idx, :] = prob.compute_totals( @@ -734,15 +737,15 @@ def compute_partials(self, inputs, J): return_format='array' ).squeeze() - for state_name_2 in prob.state_names: + for state_name_2 in prob.states.keys(): # I'm actually computing dh_dx.T # dh_dx rows are new state, columns are old state # now, dh_dx.T rows are old state, columns are new # so I think this is right dh_dx[ - next_prob.state_names.index(state_name_2), + list(next_prob.states.keys()).index(state_name_2), state_idx, - ] = dh_j_dx[prob.state_names.index(state_name_2)] + ] = dh_j_dx[list(prob.states.keys()).index(state_name_2)] else: state_update[ @@ -754,12 +757,13 @@ def compute_partials(self, inputs, J): dh_dxs.append(dh_dx) dh_dparams.append(dh_dparam) - df_dx_data[idx, :, :] = prob.compute_totals(prob.state_rate_names, - prob.state_names, + state_rate_names = [val['rate'] for _, val in prob.states.items()] + df_dx_data[idx, :, :] = prob.compute_totals(state_rate_names, + prob.states.keys(), return_format='array').T if param_dict: df_dparam_data[idx, ...] = prob.compute_totals( - prob.state_rate_names, + state_rate_names, list(param_dict.keys()), return_format='array' ) @@ -870,7 +874,7 @@ def compute_partials(self, inputs, J): # lamda_dot_plus = lamda_dot if self.DEBUG: if np.any(state_disc): - print("update is non-zero!", prob, prob.state_names, + print("update is non-zero!", prob, prob.states.keys(), state_disc, costate, lamda_dot) print( "inner product becomes...", @@ -879,7 +883,7 @@ def compute_partials(self, inputs, J): state_disc[None, :] @ dh_dx.T @ lamda_dot_plus[:, None] ) - print("dh_dx for", prob, prob.state_names, "\n", dh_dx) + print("dh_dx for", prob, prob.states.keys(), "\n", dh_dx) print("costate", costate) costate_update_terms = [ dh_dx.T @ costate[:, None], @@ -976,17 +980,17 @@ def co_state_rate(t, costate, *args): # TODO: do co-states need unit changes? probably not... for state_name in prob.state_names: - costate[next_prob.state_names.index(state_name)] = co_res.x[-1, - prob.state_names.index(state_name)] + costate[list(next_prob.states.keys()).index( + state_name)] = co_res.x[-1, list(prob.states.keys()).index(state_name)] lamda_dot_plus[ - next_prob.state_names.index(state_name) - ] = lamda_dot_plus_rate[prob.state_names.index(state_name)] + list(next_prob.states.keys()).index(state_name) + ] = lamda_dot_plus_rate[list(prob.states.keys()).index(state_name)] for state_to_deriv, metadata in self.traj_initial_state_input.items(): param_name = metadata["name"] J[output_name, param_name] = costate_reses[output][-1].x[ -1, - prob.state_names.index(state_to_deriv) + list(prob.states.keys()).index(state_to_deriv) ] for param_deriv_val, param_deriv_name in zip(param_deriv, param_dict): J[output_name, param_deriv_name] = param_deriv_val diff --git a/aviary/mission/gasp_based/ode/unsteady_solved/gamma_comp.py b/aviary/mission/gasp_based/ode/unsteady_solved/gamma_comp.py index 7155905ea..527a049d8 100644 --- a/aviary/mission/gasp_based/ode/unsteady_solved/gamma_comp.py +++ b/aviary/mission/gasp_based/ode/unsteady_solved/gamma_comp.py @@ -15,16 +15,16 @@ def initialize(self): def setup(self): nn = self.options["num_nodes"] - self.add_input("dh_dr", shape=nn, units="m/range_units", + self.add_input("dh_dr", shape=nn, units="m/distance_units", desc="change in altitude wrt range") - self.add_input("d2h_dr2", shape=nn, units="m/range_units**2", + self.add_input("d2h_dr2", shape=nn, units="m/distance_units**2", desc="second derivative of altitude wrt range") self.add_output(Dynamic.Mission.FLIGHT_PATH_ANGLE, shape=nn, units="rad", desc="flight path angle") - self.add_output("dgam_dr", shape=nn, units="rad/range_units", + self.add_output("dgam_dr", shape=nn, units="rad/distance_units", desc="change in flight path angle per unit range traversed") def setup_partials(self): diff --git a/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_eom.py b/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_eom.py index 8a87a968d..aefd0f5e5 100644 --- a/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_eom.py +++ b/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_eom.py @@ -46,12 +46,12 @@ def setup(self): self.add_input(Dynamic.Mission.FLIGHT_PATH_ANGLE, val=np.zeros( nn), desc="flight path angle", units="rad") self.add_input("dh_dr", val=np.zeros( - nn), desc="d(alt)/d(range)", units="m/range_units") + nn), desc="d(alt)/d(range)", units="m/distance_units") self.add_input("d2h_dr2", val=np.zeros( - nn), desc="d(climb_rate)/d(range)", units="1/range_units") + nn), desc="d(climb_rate)/d(range)", units="1/distance_units") # Outputs - self.add_output("dt_dr", shape=nn, units="s/range_units", + self.add_output("dt_dr", shape=nn, units="s/distance_units", desc="Seconds passed per each meter of range covered.", tags=['dymos.state_rate_source:time', 'dymos.state_units:s']) self.add_output("normal_force", val=np.ones(nn), units="N", diff --git a/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_flight_conditions.py b/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_flight_conditions.py index 008393c10..f1cdbbc31 100644 --- a/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_flight_conditions.py +++ b/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_flight_conditions.py @@ -107,7 +107,7 @@ def setup(self): self.add_input( "dTAS_dr", val=np.zeros(nn), - units="m/s/range_units", + units="m/s/distance_units", desc="change in true air speed per unit range", ) @@ -206,7 +206,7 @@ def setup(self): self.add_input( "dmach_dr", val=np.zeros(nn), - units="unitless/range_units", + units="unitless/distance_units", desc="change in mach number per unit range", ) self.add_input( diff --git a/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_ode.py b/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_ode.py index 591f0c7a7..5ad1015b5 100644 --- a/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_ode.py +++ b/aviary/mission/gasp_based/ode/unsteady_solved/unsteady_solved_ode.py @@ -214,8 +214,8 @@ def setup(self): self.add_subsystem("mass_rate", om.ExecComp("dmass_dr = fuelflow * dt_dr", fuelflow={"units": "lbm/s", "shape": nn}, - dt_dr={"units": "s/range_units", "shape": nn}, - dmass_dr={"units": "lbm/range_units", + dt_dr={"units": "s/distance_units", "shape": nn}, + dmass_dr={"units": "lbm/distance_units", "shape": nn, "tags": ['dymos.state_rate_source:mass', 'dymos.state_units:lbm']}, @@ -241,5 +241,6 @@ def setup(self): name=Dynamic.Mission.ALTITUDE, val=10000. * onn, units="ft") - self.set_input_defaults(name="dh_dr", val=0. * onn, units="ft/range_units") - self.set_input_defaults(name="d2h_dr2", val=0. * onn, units="ft/range_units**2") + self.set_input_defaults(name="dh_dr", val=0. * onn, units="ft/distance_units") + self.set_input_defaults(name="d2h_dr2", val=0. * onn, + units="ft/distance_units**2") diff --git a/aviary/mission/gasp_based/phases/2DOF_phase_builder.py b/aviary/mission/gasp_based/phases/2DOF_phase_builder.py deleted file mode 100644 index 1951b48ad..000000000 --- a/aviary/mission/gasp_based/phases/2DOF_phase_builder.py +++ /dev/null @@ -1,263 +0,0 @@ -""" -This file is a work in progress and is currently not functional. It is a builder that -will have the ability to build any of the 2DOF phases in Aviary, and should be used in -conjunction with a mission builder. When complete, this file is intended to be the only -phase builder for 2DOF phases. -""" - -import dymos as dm -import openmdao.api as om - -from aviary.variable_info.variables import Dynamic - - -class Phase2DOF: - def __init__( - self, - aviary_options, - initials=None, - bounds=None, - scalers=None, - type=None, - transcription=None, - methods=None, - ): - self.aviary_options = aviary_options - self.initials = initials - self.bounds = bounds - self.scalers = scalers - self.type = type - self.transcription = transcription - self.methods = methods - - def pre_process(self): - if self.type == None: - raise om.AnalysisError( - "You have not provided a type of phase. Providing a type of phase is" - " required." - ) - elif self.type not in ["takeoff", Dynamic.Mission.VELOCITY_RATE, "climb", "cruise", "descent"]: - raise om.AnalysisError( - f'You have provided an invalid phase type to the 2DOF phase builder.' - ' Your phase type is {self.type} which is not on of ["takeoff", Dynamic.Mission.VELOCITY_RATE,' - ' "climb", "cruise", "descent"].' - ) - - if ( - self.initials.get_val("fix_initial_mass")["value"] - and self.initials.get_val("connect_initial_mass")["value"] - ): - raise om.AnalysisError( - "You have both fixed and connected the initial phase mass, which is" - " not allowed." - ) - - if ( - self.initials.get_val("fix_initial_time")["value"] - and self.initials.get_val("connect_initial_time")["value"] - ): - raise om.AnalysisError( - "You have both fixed and connected the initial phase time, which is not" - " allowed." - ) - - def fix_phase_value( - self, variable, units, val - ): # very thin wrapper around dymos to protect user from add parameter function - - self.phase.add_parameter(variable, opt=False, units=units, val=val) - - def build_phase(self): - self.pre_process() - - self.phase = dm.Phase( - ode_class=ODE2DOF, # pass aero and prop methods here - transcription=self.transcription, - ode_init_kwargs=self.aviary_options, - ) - - self.phase.set_time_options( - initial_bounds=self.bounds.get_val("initial_time_bounds")["value"], - duration_bounds=self.bounds.get_val("duration_bounds")["value"], - fix_initial=self.initials.get_val("fix_initial_time")["value"], - input_initial=self.initials.get_val("connect_initial_time")["value"], - units=self.bounds.get_val("initial_time_bounds")["units"], - duration_ref=self.scalers.get_val("duration_ref")["value"], - initial_ref=self.scalers.get_val("initial_time_ref")["value"], - ) - - self.phase.add_state( - Dynamic.Mission.MASS, - fix_initial=self.initials.get_val("fix_initial_mass")["value"], - input_initial=self.initials.get_val("connect_initial_mass")["value"], - fix_final=False, - lower=self.bounds.get_val("mass_bounds")["value"][0], - upper=self.bounds.get_val("mass_bounds")["value"][1], - units=self.bounds.get_val("mass_bounds")["units"], - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.Mission.MASS, - ref=self.scalers.get_val("mass_ref")["value"], - ref0=self.scalers.get_val("mass_ref0")["value"], - defect_ref=self.scalers.get_val("mass_defect_ref")["value"], - ) - self.phase.add_state( - Dynamic.Mission.DISTANCE, - fix_initial=self.initials.get_val("fix_initial_distance")["value"], - input_initial=False, - fix_final=False, # should this be controllable? - lower=self.bounds.get_val("distance_bounds")["value"][0], - upper=self.bounds.get_val("distance_bounds")["value"][1], - units=self.bounds.get_val("distance_bounds")["units"], - rate_source="distance_rate", - targets=Dynamic.Mission.DISTANCE, - ref=self.scalers.get_val("distance_ref")["value"], - ref0=self.scalers.get_val("distance_ref0")["value"], - defect_ref=self.scalers.get_val("distance_defect_ref")["value"], - ) - - if self.type == "takeoff" or self.type == "climb" or self.type == "descent": - self.phase.add_state( - Dynamic.Mission.ALTITUDE, - fix_initial=self.initials.get_val("fix_initial_flight_condition")[ - "value" - ], - input_initial=False, - fix_final=False, - lower=self.bounds.get_val("altitude_bounds")["value"][0], - upper=self.bounds.get_val("altitude_bounds")["value"][1], - units=self.bounds.get_val("altitude_bounds")["units"], - rate_source=Dynamic.Mission.ALTITUDE_RATE, - targets=Dynamic.Mission.ALTITUDE, - ref=self.scalers.get_val("altitude_ref")["value"], - ref0=self.scalers.get_val("altitude_ref0")["value"], - defect_ref=self.scalers.get_val("altitude_defect_ref")["value"], - ) - - if self.type == "takeoff" or self.type == Dynamic.Mission.VELOCITY_RATE: - self.phase.add_state( - "TAS", - fix_initial=self.initials.get_val( - "fix_initial_flight_condition")["value"], - input_initial=False, - fix_final=False, - lower=self.bounds.get_val("TAS_bounds")["value"][0], - upper=self.bounds.get_val("TAS_bounds")["value"][1], - units=self.bounds.get_val("TAS_bounds")["units"], - rate_source="TAS_rate", - targets="TAS", - ref=self.scalers.get_val("TAS_ref")["value"], - ref0=self.scalers.get_val("TAS_ref0")["value"], - defect_ref=self.scalers.get_val("TAS_defect_ref")["value"], - ) - - if self.type == "takeoff": - self.phase.add_state( - "angle_of_attack", # alpha - fix_initial=self.initials.get_val("fix_initial_angles")["value"], - input_initial=False, - fix_final=False, - lower=self.bounds.get_val("angle_of_attack_bounds")["value"][0], - upper=self.bounds.get_val("angle_of_attack_bounds")["value"][1], - units=self.bounds.get_val("angle_of_attack_bounds")["units"], - rate_source="angle_of_attack_rate", - targets="angle_of_attack", - ref=self.scalers.get_val("angle_of_attack_ref")["value"], - ref0=self.scalers.get_val("angle_of_attack_ref0")["value"], - defect_ref=self.scalers.get_val("angle_of_attack_defect_ref")["value"], - ) - self.phase.add_state( - Dynamic.Mission.FLIGHT_PATH_ANGLE, # gamma - fix_initial=self.initials.get_val("fix_initial_angles")["value"], - input_initial=False, - fix_final=False, - lower=self.bounds.get_val("flight_path_angle_bounds")["value"][0], - upper=self.bounds.get_val("flight_path_angle_bounds")["value"][1], - units=self.bounds.get_val("flight_path_angle_bounds")["units"], - rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - targets=Dynamic.Mission.FLIGHT_PATH_ANGLE, - ref=self.scalers.get_val("angle_of_attack_ref")["value"], - ref0=self.scalers.get_val("angle_of_attack_ref0")["value"], - defect_ref=self.scalers.get_val("angle_of_attack_defect_ref")["value"], - ) - - self.phase.add_timeseries_output( - Dynamic.Mission.MACH, output_name=Dynamic.Mission.MACH, units="unitless") - self.phase.add_timeseries_output("EAS", output_name="EAS", units="unitless") - self.phase.add_timeseries_output( - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, output_name=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, units="unitless" - ) - self.phase.add_timeseries_output( - "fuselage_pitch", output_name="fuselage_pitch", units="unitless" - ) - self.phase.add_timeseries_output( - "angle_of_attack", output_name="angle_of_attack", units="unitless" - ) - self.phase.add_timeseries_output( - Dynamic.Mission.FLIGHT_PATH_ANGLE, output_name=Dynamic.Mission.FLIGHT_PATH_ANGLE, units="unitless" - ) - self.phase.add_timeseries_output( - "TAS_violation", output_name="TAS_violation", units="unitless" - ) - self.phase.add_timeseries_output("TAS", output_name="TAS", units="unitless") - self.phase.add_timeseries_output("CL", output_name="CL", units="unitless") - self.phase.add_timeseries_output( - Dynamic.Mission.THRUST_TOTAL, output_name=Dynamic.Mission.THRUST_TOTAL, units="unitless") - self.phase.add_timeseries_output("CD", output_name="CD", units="unitless") - self.phase.add_timeseries_output( - Dynamic.Mission.LIFT, output_name=Dynamic.Mission.LIFT, units="unitless") - self.phase.add_timeseries_output( - "normal_force", output_name="normal_force", units="unitless" - ) - - return self.phase - - -def link_trajectory(phases=None, aviary_options=None): - if phases == None: - raise om.AnalysisError("You have not provided any phases to link_trajectory.") - - traj = dm.Trajectory() - phase_names = [] - - for phase in phases: - num_usage = len([phase.type for check in phase_names if phase.type in check]) - new_name = phase.type + str(num_usage + 1) - phase_names.append(new_name) - - traj.add_phase(new_name, phase, **aviary_options) - - if len(phase_names) > 1: - traj.link_phases( - phases=[phase_names[-1], phase_names[-2]], - vars=linked_vars(phase_names[-1], phase_names[-2]), - ) - - return traj - - -def linked_vars(phase1, phase2): # TODO: add other combinations of phases - type1 = "".join((char for char in phase1 if not char.isdigit())) - type2 = "".join((char for char in phase2 if not char.isdigit())) - - linked_vars = [] - - if type1 == "takeoff" and type2 == Dynamic.Mission.VELOCITY_RATE: - linked_vars = [Dynamic.Mission.DISTANCE, "time", Dynamic.Mission.MASS, "TAS"] - elif type1 == Dynamic.Mission.VELOCITY_RATE and type2 == "climb": - linked_vars = ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MASS, Dynamic.Mission.DISTANCE] - elif type1 == "climb" and type2 == "climb": - linked_vars = ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MASS, Dynamic.Mission.DISTANCE] - elif type1 == "climb" and type2 == "cruise": - linked_vars = [] # TODO: update this - elif type1 == "cruise" and type2 == "descent": - linked_vars = [] # TODO: update this - elif type1 == "descent" and type2 == "descent": - linked_vars = ["time", Dynamic.Mission.ALTITUDE, "mass", "distance"] - else: - raise om.AnalysisError( - "You have provided a phase order which is not yet supported." - ) - - return linked_vars diff --git a/aviary/mission/gasp_based/phases/accel_phase.py b/aviary/mission/gasp_based/phases/accel_phase.py index ed938768a..d9a46d4c9 100644 --- a/aviary/mission/gasp_based/phases/accel_phase.py +++ b/aviary/mission/gasp_based/phases/accel_phase.py @@ -1,104 +1,121 @@ -import dymos as dm - -from aviary.mission.gasp_based.ode.accel_ode import AccelODE +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessState, InitialGuessTime, InitialGuessControl +from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.variables import Dynamic +from aviary.mission.gasp_based.ode.accel_ode import AccelODE + + +class AccelPhase(PhaseBuilderBase): + """ + A phase builder for an acceleration phase in a mission simulation. + + This class extends the PhaseBuilderBase class, providing specific implementations for + the acceleration phase of a flight mission. + + Attributes + ---------- + Inherits all attributes from PhaseBuilderBase. + + Methods + ------- + Inherits all methods from PhaseBuilderBase. + Additional method overrides and new methods specific to the acceleration phase are included. + """ + default_name = 'accel_phase' + default_ode_class = AccelODE + + _meta_data_ = {} + _initial_guesses_meta_data_ = {} + + def build_phase(self, aviary_options: AviaryValues = None): + """ + Return a new acceleration phase for analysis using these constraints. + + Parameters + ---------- + aviary_options : AviaryValues + Collection of Aircraft/Mission specific options + + Returns + ------- + dymos.Phase + """ + phase = self.phase = super().build_phase(aviary_options) + user_options = self.user_options + + # Extracting and setting options + EAS_constraint_eq = user_options.get_val('EAS_constraint_eq', units='kn') + alt = user_options.get_val('alt', units='ft') + + self.set_time_options(user_options) + + # States + self.add_TAS_state(user_options) + + self.add_mass_state(user_options) + + self.add_distance_state(user_options) + + # Boundary Constraints + phase.add_boundary_constraint( + "EAS", loc="final", equals=EAS_constraint_eq, units="kn", ref=EAS_constraint_eq + ) + + phase.add_parameter(Dynamic.Mission.ALTITUDE, opt=False, units="ft", val=alt) + + # Timeseries Outputs + phase.add_timeseries_output("EAS", output_name="EAS", units="kn") + phase.add_timeseries_output( + Dynamic.Mission.MACH, output_name=Dynamic.Mission.MACH, units="unitless") + phase.add_timeseries_output("alpha", output_name="alpha", units="deg") + phase.add_timeseries_output("aero.CL", output_name="CL", units="unitless") + phase.add_timeseries_output( + Dynamic.Mission.THRUST_TOTAL, output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") + phase.add_timeseries_output("aero.CD", output_name="CD", units="unitless") + + return phase + + +# Adding metadata for the AccelPhase +AccelPhase._add_meta_data('fix_initial', val=False) +AccelPhase._add_meta_data('EAS_constraint_eq', val=250, units='kn') +AccelPhase._add_meta_data('duration_bounds', val=(0, 0), units='s') +AccelPhase._add_meta_data('duration_ref', val=1, units='s') +AccelPhase._add_meta_data('TAS_lower', val=0, units='kn') +AccelPhase._add_meta_data('TAS_upper', val=0, units='kn') +AccelPhase._add_meta_data('TAS_ref', val=1, units='kn') +AccelPhase._add_meta_data('TAS_ref0', val=0, units='kn') +AccelPhase._add_meta_data('TAS_defect_ref', val=None, units='kn') +AccelPhase._add_meta_data('mass_lower', val=0, units='lbm') +AccelPhase._add_meta_data('mass_upper', val=0, units='lbm') +AccelPhase._add_meta_data('mass_ref', val=1, units='lbm') +AccelPhase._add_meta_data('mass_ref0', val=0, units='lbm') +AccelPhase._add_meta_data('mass_defect_ref', val=None, units='lbm') +AccelPhase._add_meta_data('distance_lower', val=0, units='NM') +AccelPhase._add_meta_data('distance_upper', val=0, units='NM') +AccelPhase._add_meta_data('distance_ref', val=1, units='NM') +AccelPhase._add_meta_data('distance_ref0', val=0, units='NM') +AccelPhase._add_meta_data('distance_defect_ref', val=None, units='NM') +AccelPhase._add_meta_data('alt', val=500, units='ft') +AccelPhase._add_meta_data('num_segments', val=None, units='unitless') +AccelPhase._add_meta_data('order', val=None, units='unitless') + +AccelPhase._add_initial_guess_meta_data( + InitialGuessTime(), + desc='initial guess for initial time and duration specified as a tuple') + +AccelPhase._add_initial_guess_meta_data( + InitialGuessState('TAS'), + desc='initial guess for true airspeed') + +AccelPhase._add_initial_guess_meta_data( + InitialGuessState('mass'), + desc='initial guess for mass') +AccelPhase._add_initial_guess_meta_data( + InitialGuessState('distance'), + desc='initial guess for horizontal distance traveled') -def get_accel( - ode_args=None, - transcription=None, - fix_initial=False, - alt=500, - EAS_constraint_eq=250, - time_initial_bounds=(0, 0), - duration_bounds=(0, 0), - duration_ref=1, - TAS_lower=0, - TAS_upper=0, - TAS_ref=1, - TAS_ref0=0, - TAS_defect_ref=None, - mass_lower=0, - mass_upper=0, - mass_ref=1, - mass_ref0=0, - mass_defect_ref=None, - distance_lower=0, - distance_upper=0, - distance_ref=1, - distance_ref0=0, - distance_defect_ref=None, -): - accel = dm.Phase( - ode_class=AccelODE, - transcription=transcription, - ode_init_kwargs=ode_args, - ) - - accel.set_time_options( - fix_initial=fix_initial, - initial_bounds=time_initial_bounds, - duration_bounds=duration_bounds, - units="s", - duration_ref=duration_ref, - ) - - accel.add_state( - "TAS", - fix_initial=fix_initial, - fix_final=False, - lower=TAS_lower, - upper=TAS_upper, - units="kn", - rate_source="TAS_rate", - targets="TAS", - ref=TAS_ref, - ref0=TAS_ref0, - defect_ref=TAS_defect_ref, - ) - - accel.add_state( - Dynamic.Mission.MASS, - fix_initial=fix_initial, - fix_final=False, - lower=mass_lower, - upper=mass_upper, - units="lbm", - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.Mission.MASS, - ref=mass_ref, - ref0=mass_ref0, - defect_ref=mass_defect_ref, - ) - - accel.add_state( - Dynamic.Mission.DISTANCE, - fix_initial=fix_initial, - fix_final=False, - lower=distance_lower, - upper=distance_upper, - units="NM", - rate_source="distance_rate", - ref=distance_ref, - ref0=distance_ref0, - defect_ref=distance_defect_ref, - ) - - accel.add_boundary_constraint( - "EAS", loc="final", equals=EAS_constraint_eq, units="kn", ref=EAS_constraint_eq - ) - - accel.add_parameter(Dynamic.Mission.ALTITUDE, opt=False, units="ft", val=alt) - - accel.add_timeseries_output("EAS", output_name="EAS", units="kn") - accel.add_timeseries_output( - Dynamic.Mission.MACH, - output_name=Dynamic.Mission.MACH, - units="unitless") - accel.add_timeseries_output("alpha", output_name="alpha", units="deg") - accel.add_timeseries_output("aero.CL", output_name="CL", units="unitless") - accel.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") - accel.add_timeseries_output("aero.CD", output_name="CD", units="unitless") - - return accel +AccelPhase._add_initial_guess_meta_data( + InitialGuessControl('throttle'), + desc='initial guess for throttle') diff --git a/aviary/mission/gasp_based/phases/ascent_phase.py b/aviary/mission/gasp_based/phases/ascent_phase.py index 6b9ed6747..7a181aebc 100644 --- a/aviary/mission/gasp_based/phases/ascent_phase.py +++ b/aviary/mission/gasp_based/phases/ascent_phase.py @@ -1,167 +1,169 @@ -import dymos as dm import numpy as np -from aviary.mission.gasp_based.ode.ascent_ode import AscentODE +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessState, InitialGuessTime, InitialGuessControl +from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.variables import Dynamic +from aviary.mission.gasp_based.ode.ascent_ode import AscentODE + + +class AscentPhase(PhaseBuilderBase): + default_name = 'ascent_phase' + default_ode_class = AscentODE + + _meta_data_ = {} + _initial_guesses_meta_data_ = {} + + def build_phase(self, aviary_options: AviaryValues = None): + phase = self.phase = super().build_phase(aviary_options) + + # Retrieve user options values + user_options = self.user_options + + pitch_constraint_lower = user_options.get_val( + 'pitch_constraint_lower', units='deg') + pitch_constraint_upper = user_options.get_val( + 'pitch_constraint_upper', units='deg') + pitch_constraint_ref = user_options.get_val('pitch_constraint_ref', units='deg') + alpha_constraint_lower = user_options.get_val( + 'alpha_constraint_lower', units='rad') + alpha_constraint_upper = user_options.get_val( + 'alpha_constraint_upper', units='rad') + alpha_constraint_ref = user_options.get_val('alpha_constraint_ref', units='rad') + + phase.set_time_options( + units="s", + targets="t_curr", + input_initial=True, + input_duration=True, + ) + + self.add_flight_path_angle_state(user_options) + self.add_altitude_state(user_options) + self.add_TAS_state(user_options) + self.add_mass_state(user_options) + self.add_distance_state(user_options) + + self.add_altitude_constraint(user_options) + + phase.add_path_constraint( + "load_factor", + upper=1.10, + lower=0.0 + ) + + phase.add_path_constraint( + "fuselage_pitch", + "theta", + lower=pitch_constraint_lower, + upper=pitch_constraint_upper, + units="deg", + ref=pitch_constraint_ref, + ) + + phase.add_control( + "alpha", + val=0, + lower=alpha_constraint_lower, + upper=alpha_constraint_upper, + units="rad", + ref=alpha_constraint_ref, + opt=True, + ) + + phase.add_parameter("t_init_gear", units="s", + static_target=True, opt=False, val=38.25) + + phase.add_parameter("t_init_flaps", units="s", + static_target=True, opt=False, val=48.21) + + phase.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") + phase.add_timeseries_output("normal_force") + phase.add_timeseries_output(Dynamic.Mission.MACH) + phase.add_timeseries_output("EAS", units="kn") + phase.add_timeseries_output(Dynamic.Mission.LIFT) + phase.add_timeseries_output("CL") + phase.add_timeseries_output("CD") + + return phase + + +# Adding metadata for the AscentPhase +AscentPhase._add_meta_data('fix_initial', val=False) +AscentPhase._add_meta_data('angle_lower', val=-15 * np.pi / 180, units='rad') +AscentPhase._add_meta_data('angle_upper', val=25 * np.pi / 180, units='rad') +AscentPhase._add_meta_data('angle_ref', val=np.deg2rad(1), units='rad') +AscentPhase._add_meta_data('angle_ref0', val=0, units='rad') +AscentPhase._add_meta_data('angle_defect_ref', val=0.01, units='rad') +AscentPhase._add_meta_data('alt_lower', val=0, units='ft') +AscentPhase._add_meta_data('alt_upper', val=700, units='ft') +AscentPhase._add_meta_data('alt_ref', val=100, units='ft') +AscentPhase._add_meta_data('alt_ref0', val=0, units='ft') +AscentPhase._add_meta_data('alt_defect_ref', val=100, units='ft') +AscentPhase._add_meta_data('final_altitude', val=500, units='ft') +AscentPhase._add_meta_data('alt_constraint_ref', val=100, units='ft') +AscentPhase._add_meta_data('alt_constraint_ref0', val=0, units='ft') +AscentPhase._add_meta_data('TAS_lower', val=0, units='kn') +AscentPhase._add_meta_data('TAS_upper', val=1000, units='kn') +AscentPhase._add_meta_data('TAS_ref', val=1e2, units='kn') +AscentPhase._add_meta_data('TAS_ref0', val=0, units='kn') +AscentPhase._add_meta_data('TAS_defect_ref', val=None, units='kn') +AscentPhase._add_meta_data('mass_lower', val=0, units='lbm') +AscentPhase._add_meta_data('mass_upper', val=190_000, units='lbm') +AscentPhase._add_meta_data('mass_ref', val=100_000, units='lbm') +AscentPhase._add_meta_data('mass_ref0', val=0, units='lbm') +AscentPhase._add_meta_data('mass_defect_ref', val=1e2, units='lbm') +AscentPhase._add_meta_data('distance_lower', val=0, units='ft') +AscentPhase._add_meta_data('distance_upper', val=10.e3, units='ft') +AscentPhase._add_meta_data('distance_ref', val=3000, units='ft') +AscentPhase._add_meta_data('distance_ref0', val=0, units='ft') +AscentPhase._add_meta_data('distance_defect_ref', val=3000, units='ft') +AscentPhase._add_meta_data('pitch_constraint_lower', val=0, units='deg') +AscentPhase._add_meta_data('pitch_constraint_upper', val=15, units='deg') +AscentPhase._add_meta_data('pitch_constraint_ref', val=1, units='deg') +AscentPhase._add_meta_data('alpha_constraint_lower', val=np.deg2rad(-30), units='rad') +AscentPhase._add_meta_data('alpha_constraint_upper', val=np.deg2rad(30), units='rad') +AscentPhase._add_meta_data('alpha_constraint_ref', val=np.deg2rad(5), units='rad') +AscentPhase._add_meta_data('num_segments', val=None, units='unitless') +AscentPhase._add_meta_data('order', val=None, units='unitless') + +# Adding initial guess metadata +AscentPhase._add_initial_guess_meta_data( + InitialGuessTime(), + desc='initial guess for time options') + +AscentPhase._add_initial_guess_meta_data( + InitialGuessState('flight_path_angle'), + desc='initial guess for flight path angle state') + +AscentPhase._add_initial_guess_meta_data( + InitialGuessState('altitude'), + desc='initial guess for altitude state') + +AscentPhase._add_initial_guess_meta_data( + InitialGuessState('TAS'), + desc='initial guess for true airspeed state') + +AscentPhase._add_initial_guess_meta_data( + InitialGuessState('mass'), + desc='initial guess for mass state') + +AscentPhase._add_initial_guess_meta_data( + InitialGuessState('distance'), + desc='initial guess for distance state') + +AscentPhase._add_initial_guess_meta_data( + InitialGuessControl('alpha'), + desc='initial guess for angle of attack control') + +AscentPhase._add_initial_guess_meta_data( + InitialGuessControl('tau_gear'), + desc='when the gear is retracted') +AscentPhase._add_initial_guess_meta_data( + InitialGuessControl('tau_flaps'), + desc='when the flaps are retracted') -def get_ascent( - ode_args=None, - transcription=None, - fix_initial=False, - angle_lower=-15 * np.pi / 180, - angle_upper=25 * np.pi / 180, - angle_ref=np.deg2rad(1), - angle_ref0=0, - angle_defect_ref=0.01, - alt_lower=0, - alt_upper=700, - alt_ref=100, - alt_ref0=0, - alt_defect_ref=100, - alt_constraint_eq=500, - alt_constraint_ref=100, - alt_constraint_ref0=0, - TAS_lower=0, - TAS_upper=1000, - TAS_ref=1e2, - TAS_ref0=0, - TAS_defect_ref=None, - mass_lower=0, - mass_upper=190_000, - mass_ref=100_000, - mass_ref0=0, - mass_defect_ref=1e2, - distance_lower=0, - distance_upper=10.e3, - distance_ref=3000, - distance_ref0=0, - distance_defect_ref=3000, - pitch_constraint_lower=0, - pitch_constraint_upper=15, - pitch_constraint_ref=1, - alpha_constraint_lower=np.deg2rad(-30), - alpha_constraint_upper=np.deg2rad(30), - alpha_constraint_ref=np.deg2rad(5), -): - phase = dm.Phase( - ode_class=AscentODE, - ode_init_kwargs=ode_args, - transcription=transcription, - ) - - phase.set_time_options( - units="s", - targets="t_curr", - input_initial=True, - input_duration=True, - ) - - phase.add_state( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=True, - fix_final=False, - lower=angle_lower, - upper=angle_upper, - units="rad", - rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - ref=angle_ref, - defect_ref=angle_defect_ref, - ref0=angle_ref0, - ) - - phase.add_state( - Dynamic.Mission.ALTITUDE, - fix_initial=True, - fix_final=False, - lower=alt_lower, - upper=alt_upper, - units="ft", - rate_source=Dynamic.Mission.ALTITUDE_RATE, - ref=alt_ref, - defect_ref=alt_defect_ref, - ref0=alt_ref0, - ) - - phase.add_state( - "TAS", - fix_initial=fix_initial, - fix_final=False, - lower=TAS_lower, - upper=TAS_upper, - units="kn", - rate_source="TAS_rate", - ref=TAS_ref, - defect_ref=TAS_defect_ref, - ref0=TAS_ref0, - ) - - phase.add_state( - Dynamic.Mission.MASS, - fix_initial=fix_initial, - fix_final=False, - lower=mass_lower, - upper=mass_upper, - units="lbm", - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - ref=mass_ref, - defect_ref=mass_defect_ref, - ref0=mass_ref0, - ) - - phase.add_state( - Dynamic.Mission.DISTANCE, - fix_initial=fix_initial, - fix_final=False, - lower=distance_lower, - upper=distance_upper, - units="ft", - rate_source="distance_rate", - ref=distance_ref, - defect_ref=distance_defect_ref, - ref0=distance_ref0, - ) - - # boundary/path constraints + controls - phase.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, - loc="final", - equals=alt_constraint_eq, - units="ft", - ref=alt_constraint_ref, - ref0=alt_constraint_ref0, - ) - phase.add_path_constraint("load_factor", upper=1.10, lower=0.0) - phase.add_path_constraint( - "fuselage_pitch", - "theta", - lower=pitch_constraint_lower, - upper=pitch_constraint_upper, - units="deg", - ref=pitch_constraint_ref, - ) - - phase.add_control( - "alpha", - val=0, - lower=alpha_constraint_lower, - upper=alpha_constraint_upper, - units="rad", - ref=alpha_constraint_ref, - opt=True, - ) - phase.add_parameter("t_init_gear", units="s", - static_target=True, opt=False, val=38.25) - phase.add_parameter("t_init_flaps", units="s", - static_target=True, opt=False, val=48.21) - phase.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") - - phase.add_timeseries_output("normal_force") - phase.add_timeseries_output(Dynamic.Mission.MACH) - phase.add_timeseries_output("EAS", units="kn") - - phase.add_timeseries_output(Dynamic.Mission.LIFT) - phase.add_timeseries_output("CL") - phase.add_timeseries_output("CD") - - return phase +AscentPhase._add_initial_guess_meta_data( + InitialGuessControl('throttle'), + desc='initial guess for throttle') diff --git a/aviary/mission/gasp_based/phases/benchmark/test_accel.py b/aviary/mission/gasp_based/phases/benchmark/test_accel.py deleted file mode 100644 index 3ac9323b0..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_accel.py +++ /dev/null @@ -1,179 +0,0 @@ -import unittest - -import dymos as dm -import numpy as np -import openmdao.api as om -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs - -from aviary.constants import GRAV_ENGLISH_LBM -from aviary.mission.gasp_based.ode.flight_path_ode import FlightPathODE -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Aircraft, Dynamic -from aviary.interface.default_phase_info.two_dof import default_mission_subsystems - - -def make_accel_problem( - optimizer='IPOPT', - print_opt_iters=False, - constant_quantity=Dynamic.Mission.MACH): - prob = om.Problem(model=om.Group()) - - prob.driver = om.pyOptSparseDriver() - - if optimizer == "SNOPT": - prob.driver.options["optimizer"] = "SNOPT" - prob.driver.opt_settings['Major iterations limit'] = 100 - # prob.driver.opt_settings['Major step limit'] = 0.05 - prob.driver.opt_settings['Major feasibility tolerance'] = 1.0E-6 - prob.driver.opt_settings['Major optimality tolerance'] = 1.0E-6 - if print_opt_iters: - prob.driver.opt_settings["iSumm"] = 6 - elif optimizer == "IPOPT": - prob.driver.options["optimizer"] = "IPOPT" - prob.driver.opt_settings["max_iter"] = 500 - prob.driver.opt_settings["tol"] = 1e-5 - prob.driver.opt_settings["print_level"] = 5 - # prob.driver.opt_settings["nlp_scaling_method"] = "gradient-based" - prob.driver.opt_settings["alpha_for_y"] = "safer-min-dual-infeas" - prob.driver.opt_settings["mu_strategy"] = "monotone" - prob.driver.opt_settings["bound_mult_init_method"] = "mu-based" - - transcription = dm.Radau(num_segments=5, order=3, compressed=True) - - ode_args = dict( - clean=True, - aviary_options=get_option_defaults(), - core_subsystems=default_mission_subsystems - ) - - accel = dm.Phase(ode_class=FlightPathODE, transcription=transcription, - ode_init_kwargs=ode_args) - - accel.set_time_options(fix_initial=True, duration_bounds=( - 10, 5000), units="s", duration_ref=100) - - accel.set_state_options("TAS", - fix_initial=True, fix_final=False, lower=1, upper=1000, units="kn", ref=250, defect_ref=250) - - accel.set_state_options("mass", - fix_initial=True, fix_final=False, lower=1, upper=None, units="lbm", ref=200000, defect_ref=200000, rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL) - - accel.set_state_options(Dynamic.Mission.DISTANCE, - fix_initial=True, fix_final=False, lower=0, upper=None, units="ft", ref=10000, defect_ref=10000) - - accel.set_state_options(Dynamic.Mission.ALTITUDE, - fix_initial=True, fix_final=False, lower=490, upper=510, units="ft", ref=100, defect_ref=100) - - accel.set_state_options(Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=True, fix_final=False, units="rad", lower=0.0, ref=1.0, defect_ref=1.0) - - accel.add_control("alpha", opt=True, units="rad", val=0.0, - lower=np.radians(-14), upper=np.radians(14), rate_continuity=True, rate2_continuity=False) - - accel.add_boundary_constraint( - "EAS", loc="final", equals=250, ref=250, units="kn") - - throttle_climb = 0.956 - accel.add_parameter( - Dynamic.Mission.THROTTLE, - opt=False, - units="unitless", - val=throttle_climb, - static_target=False) - - accel.add_parameter( - Aircraft.Wing.AREA, opt=False, units="ft**2", val=1370.0, static_target=True - ) - - accel.add_timeseries_output("EAS", output_name="EAS", units="kn") - accel.add_timeseries_output( - Dynamic.Mission.MACH, - output_name=Dynamic.Mission.MACH, - units="unitless") - accel.add_timeseries_output("alpha", output_name="alpha", units="deg") - accel.add_timeseries_output("CL", output_name="CL", units="unitless") - accel.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") - accel.add_timeseries_output("CD", output_name="CD", units="unitless") - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - traj.add_phase('accel', accel) - - accel.add_objective("mass", loc="final", ref=-1.e4) - # accel.add_objective("time", loc="final", ref=1.e2) - - prob.model.linear_solver.options["iprint"] = 0 - prob.model.nonlinear_solver.options["iprint"] = 0 - - traj.nonlinear_solver = om.NonlinearBlockGS(iprint=0) - traj.options['assembled_jac_type'] = 'csc' - traj.linear_solver = om.DirectSolver() - - prob.set_solver_print(level=0) - - for phase_name, phase in traj._phases.items(): - phase.add_timeseries_output('normal_force') - phase.add_timeseries_output('fuselage_pitch') - phase.add_timeseries_output(Dynamic.Mission.ALTITUDE, units='ft') - phase.add_timeseries_output('mass', units='lbm') - phase.add_timeseries_output(Dynamic.Mission.LIFT, units='lbf') - phase.add_timeseries_output(Dynamic.Mission.FLIGHT_PATH_ANGLE, units='deg') - phase.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units='lbf') - phase.add_timeseries_output('alpha', units='deg') - - prob.setup() - - prob.set_val("traj.accel.states:TAS", accel.interp( - "TAS", ys=[200., 250.]), units='kn') - prob.set_val("traj.accel.states:altitude", accel.interp( - Dynamic.Mission.ALTITUDE, ys=[500., 500.]), units='ft') - prob.set_val("traj.accel.states:flight_path_angle", accel.interp( - Dynamic.Mission.FLIGHT_PATH_ANGLE, ys=[0, 0]), units='deg') - prob.set_val("traj.accel.states:mass", accel.interp( - "mass", ys=[174219, 170000]), units='lbm') - - prob.set_val("traj.accel.states:distance", accel.interp( - Dynamic.Mission.DISTANCE, ys=[0, 154]), units='NM') - prob.set_val("traj.accel.t_duration", 10, units='s') - prob.set_val("traj.accel.t_initial", 0, units='s') - - return prob - - -@use_tempdirs -class Testaccel(unittest.TestCase): - - def assert_constant_EAS_result(self, p): - final_mass = p.get_val( - "traj.accel.timeseries.mass", units="lbm")[-1, ...] - assert_near_equal(final_mass, 174.15124423509e3, tolerance=0.0001) - - @require_pyoptsparse(optimizer='IPOPT') - def test_accel_ipopt(self): - p = make_accel_problem(optimizer='IPOPT', constant_quantity='EAS') - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False) - - lift = p.get_val("traj.accel.timeseries.lift") - weight = p.get_val("traj.accel.timeseries.mass") * GRAV_ENGLISH_LBM - gamma = p.get_val("traj.accel.timeseries.flight_path_angle", units="rad") - thrust = p.get_val("traj.accel.timeseries.thrust_net_total") - alpha = p.get_val("traj.accel.timeseries.alpha", units="rad") - - alpha_constant_gamma = np.arcsin((lift - weight * np.cos(gamma)) / thrust) - - with np.printoptions(linewidth=1024): - print(lift.T) - print(weight.T) - print(gamma.T) - print(thrust.T) - print(alpha.T) - print(alpha_constant_gamma.T) - self.assert_constant_EAS_result(p) - - @require_pyoptsparse(optimizer='SNOPT') - def test_accel_snopt(self): - p = make_accel_problem( - optimizer='SNOPT', constant_quantity='EAS', print_opt_iters=True) - dm.run_problem(p, run_driver=True, simulate=True, make_plots=False) - self.assert_constant_EAS_result(p) diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_accel.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_accel.py deleted file mode 100644 index ac1f1294e..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_accel.py +++ /dev/null @@ -1,32 +0,0 @@ -import unittest - -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.gasp_based.phases.run_phases.run_accel import run_accel - - -class AccelPhaseTestCase(unittest.TestCase): - def bench_test_accel(self): - - prob = run_accel() - - TAS = prob.get_val("accel.timeseries.TAS", units="kn") - weight = prob.get_val("accel.timeseries.mass", units="lbm") - distance = prob.get_val("accel.timeseries.distance", units="ft") - time = prob.get_val("accel.timeseries.time", units="s") - - assert_near_equal(TAS[0], 185, 1e-4) - assert_near_equal(TAS[-1], 251.82436866, 1e-4) - - assert_near_equal(weight[0], 174974, 1e-4) - assert_near_equal(weight[-1], 174886.73023817, 1e-4) - - assert_near_equal(distance[0], 0, 1e-5) - assert_near_equal(distance[-1], 7519.70802292, 1e-5) - - assert_near_equal(time[0], 0, 1e-3) - assert_near_equal(time[-1], 20.36335559, 1e-4) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_ascent.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_ascent.py deleted file mode 100644 index 7d6b31633..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_ascent.py +++ /dev/null @@ -1,57 +0,0 @@ -import unittest - -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.gasp_based.phases.run_phases.run_ascent import run_ascent - - -@unittest.skip("this wasn't being run and needs to be updated") -class AccelPhaseTestCase(unittest.TestCase): - def bench_test_ascent(self): - - prob = run_ascent(make_plots=False) - - gamma = prob.get_val( - "traj.ascent.timeseries.states:flight_path_angle", units="deg") - alt = prob.get_val("traj.ascent.timeseries.states:altitude", units="ft") - TAS = prob.get_val("traj.ascent.timeseries.states:TAS", units="kn") - distance = prob.get_val("traj.ascent.timeseries.states:distance", units="ft") - load_factor = prob.get_val( - "traj.ascent.timeseries.load_factor", units="unitless") - fuselage_pitch = prob.get_val( - "traj.ascent.timeseries.fuselage_pitch", units="deg" - ) - alpha = prob.get_val("traj.ascent.timeseries.controls:alpha", units="deg") - weight = prob.get_val("traj.ascent.timeseries.states:weight", units="lbm") - time = prob.get_val("traj.ascent.timeseries.time", units="s") - - assert_near_equal(gamma[0], 11.4591559, 1e-3) - assert_near_equal(gamma[-1], 6.12703133, 1e-3) - - assert_near_equal(alt[0], 0, 1e-4) - assert_near_equal(alt[-1], 500, 1e-4) - - assert_near_equal(TAS[0], 153.3196491, 1e-4) - assert_near_equal(TAS[-1], 176.63794989, 1e-4) - - assert_near_equal(distance[0], 4330.83393029, 1e-5) - assert_near_equal(distance[-1], 8847.37626223, 1e-5) - - assert_near_equal(load_factor[0], 0.67732548, 1e-2) - assert_near_equal(load_factor[-1], 0.84455828, 1e-2) - - assert_near_equal(fuselage_pitch[0], 15, 1e-3) - assert_near_equal(fuselage_pitch[-1], 14.92380341, 1e-3) - - assert_near_equal(alpha[0], 3.5408441, 1e-3) - assert_near_equal(alpha[-1], 8.79677208, 1e-3) - - assert_near_equal(weight[0], 174963.74211336, 1e-5) - assert_near_equal(weight[-1], 174892.40464724, 1e-5) - - assert_near_equal(time[0], 31.2, 1e-3) - assert_near_equal(time[-1], 47.5236357, 1e-3) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_desc1.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_desc1.py deleted file mode 100644 index c61bc24c6..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_desc1.py +++ /dev/null @@ -1,33 +0,0 @@ -import unittest - -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.gasp_based.phases.run_phases.run_desc1 import run_desc1 - - -@unittest.skip('this benchmark is defunct and needs to be updated') -class Desc1PhaseTestCase(unittest.TestCase): - def bench_test_desc1(self): - - prob = run_desc1(make_plots=False) - - alt = prob.get_val("desc1.timeseries.states:altitude", units="ft") - weight = prob.get_val("desc1.timeseries.states:weight", units="lbm") - distance = prob.get_val("desc1.timeseries.states:distance", units="ft") - time = prob.get_val("desc1.timeseries.time", units="s") - - assert_near_equal(alt[0], 37500, 1e-5) - assert_near_equal(alt[-1], 10.e3, 1e-5) - - assert_near_equal(weight[0], 147664, 1e-5) - assert_near_equal(weight[-1], 147539.71665978, 1e-5) - - assert_near_equal(distance[0], 15129527.55905512, 1e-5) - assert_near_equal(distance[-1], 15595089.68838349, 1e-5) - - assert_near_equal(time[0], 0, 1e-4) - assert_near_equal(time[-1], 598.6264441, 1e-5) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_desc2.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_desc2.py deleted file mode 100644 index f47f6a4d6..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_desc2.py +++ /dev/null @@ -1,33 +0,0 @@ -import unittest - -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.gasp_based.phases.run_phases.run_desc2 import run_desc2 - - -@unittest.skip('this benchmark is defunct and needs to be updated') -class Desc2PhaseTestCase(unittest.TestCase): - def bench_test_desc2(self): - - prob = run_desc2(make_plots=False) - - alt = prob.get_val("desc2.timeseries.states:altitude", units="ft") - weight = prob.get_val("desc2.timeseries.states:weight", units="lbm") - distance = prob.get_val("desc2.timeseries.states:distance", units="ft") - time = prob.get_val("desc2.timeseries.time", units="s") - - assert_near_equal(alt[0], 10.e3, 1e-5) - assert_near_equal(alt[-1], 1000, 1e-4) - - assert_near_equal(weight[0], 147541.5, 1e-5) - assert_near_equal(weight[-1], 147401.60702539, 1e-5) - - assert_near_equal(distance[0], 15576122.0472441, 1e-5) - assert_near_equal(distance[-1], 15776177.53801624, 1e-5) - - assert_near_equal(time[0], 0, 1e-4) - assert_near_equal(time[-1], 437.62278284, 1e-5) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_groundroll.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_groundroll.py deleted file mode 100644 index 8203b429c..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_groundroll.py +++ /dev/null @@ -1,36 +0,0 @@ -import unittest - -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.gasp_based.phases.run_phases.run_groundroll import \ - run_groundroll - - -@unittest.skip('this benchmark is defunct and needs to be updated') -class GroundrollPhaseTestCase(unittest.TestCase): - def bench_test_groundroll(self): - - prob = run_groundroll(make_plots=False) - - TAS = prob.get_val("traj.groundroll.timeseries.states:TAS", units="kn") - mass = prob.get_val("traj.groundroll.timeseries.states:mass", units="lbm") - distance = prob.get_val( - "traj.groundroll.timeseries.states:distance", units="ft" - ) - time = prob.get_val("traj.groundroll.timeseries.time", units="s") - - assert_near_equal(TAS[0], 0, 1e-4) - assert_near_equal(TAS[-1], 145.37726917, 1e-4) - - assert_near_equal(mass[0], 175100, 1e-6) - assert_near_equal(mass[-1], 174978.19460438, 1e-6) - - assert_near_equal(distance[0], 0, 1e-4) - assert_near_equal(distance[-1], 3547.50615602, 1e-4) - - assert_near_equal(time[0], 0, 1e-3) - assert_near_equal(time[-1], 28.03023543, 1e-3) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_new_ascent.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_new_ascent.py deleted file mode 100644 index 684c88728..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_new_ascent.py +++ /dev/null @@ -1,427 +0,0 @@ -import unittest - -import dymos as dm -import numpy as np -import openmdao.api as om -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs - -from aviary.mission.gasp_based.ode.flight_path_ode import FlightPathODE -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Dynamic -from aviary.interface.default_phase_info.two_dof import default_mission_subsystems - - -def make_ascent_problem(optimizer='IPOPT', print_opt_iters=False): - - ode_args = dict( - aviary_options=get_option_defaults(), - core_subsystems=default_mission_subsystems - ) - - # - # ASCENT TO GEAR RETRACTION - # - # Initial States Fixed - # Final States Free - # - # Controls: - # alpha - # - # Boundary Constraints: - # alt(final) = 50 ft - # - # Path Constraints: - # 0.0 < load_factor < 1.10 - # 0.0 < fuselage_pitch < 15 deg - # - ascent0_tx = dm.Radau(num_segments=5, order=3, compressed=True, solve_segments=False) - ascent_to_gear_retract = dm.Phase( - ode_class=FlightPathODE, ode_init_kwargs=ode_args, transcription=ascent0_tx) - - ascent_to_gear_retract.set_time_options( - units="s", targets="t_curr", fix_initial=True, - duration_bounds=(0, 10)) - - # Rate sources and units of states are set with tags in AscentEOM - ascent_to_gear_retract.set_state_options(Dynamic.Mission.FLIGHT_PATH_ANGLE, units="rad", - fix_initial=True, fix_final=False, lower=0, upper=0.30, ref=0.1, defect_ref=0.1) - - ascent_to_gear_retract.set_state_options(Dynamic.Mission.ALTITUDE, - fix_initial=True, fix_final=False, lower=0, upper=500, ref=100, defect_ref=100) - - ascent_to_gear_retract.set_state_options("TAS", - fix_initial=True, fix_final=False, lower=0, upper=500, ref=100, defect_ref=100) - - ascent_to_gear_retract.set_state_options("mass", - fix_initial=True, fix_final=False, lower=100.e3, upper=None, ref=175_000, defect_ref=175_000, rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL) - - ascent_to_gear_retract.set_state_options(Dynamic.Mission.DISTANCE, - fix_initial=True, fix_final=False, lower=0, upper=15_000, ref=5280, defect_ref=5280) - - # Note that t_init_gear and t_init_flaps are taking values of 100 s here while not being connected to the - # those trajectory-level parameters. This means that we'll have some small discontinuity in the aerodynamics - # across these phases, but making the assumption of time here removes the feedback loop, removes the need for - # a nonlinear solver on the trajectory, and makes things happen faster. - ascent_to_gear_retract.add_parameter( - "t_init_gear", units="s", static_target=True, opt=False, val=100.0) - ascent_to_gear_retract.add_parameter( - "t_init_flaps", units="s", static_target=True, opt=False, val=100.0) - ascent_to_gear_retract.add_parameter( - "wing_area", units="ft**2", val=1370, static_target=True, opt=False) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are in degrees. - # ascent_to_gear_retract.add_control("alpha", - # units="rad", val=0.0, continuity=True, rate_continuity=False, - # opt=True, lower=np.radians(-30), upper=np.radians(30), ref=0.01) - ascent_to_gear_retract.add_polynomial_control("alpha", order=1, - units="rad", val=0.0, opt=True, lower=np.radians(-14), upper=np.radians(14), ref=0.1) - - # boundary/path constraints + controls - # Final altitude can be a linear constraint, since the final value of altitude is a design variable. - ascent_to_gear_retract.add_boundary_constraint(Dynamic.Mission.ALTITUDE, - loc="final", equals=50, units="ft", ref=1.0, ref0=0.0, linear=True - ) - - # Load factor can be treated as a linear constraint as long as i_wing is not a design variable. - ascent_to_gear_retract.add_path_constraint("load_factor", - lower=0.0, upper=1.10, linear=False) - - ascent_to_gear_retract.add_path_constraint("fuselage_pitch", constraint_name="theta", - lower=0, upper=15, units="deg", ref=15, linear=False) - - # - # ASCENT TO FLAP RETRACTION - # - # Initial States Free - # Final States Free - # - # Controls: - # alpha - # - # Boundary Constraints: - # alt(final) = 400 ft - # - # Path Constraints: - # 0.0 < load_factor < 1.10 - # 0.0 < fuselage_pitch < 15 deg - # - ascent1_tx = dm.Radau(num_segments=5, order=3, compressed=True, solve_segments=False) - ascent_to_flap_retract = dm.Phase( - ode_class=FlightPathODE, ode_init_kwargs=ode_args, transcription=ascent1_tx) - - ascent_to_flap_retract.set_time_options( - units="s", targets="t_curr", fix_initial=False, - initial_bounds=(5, 50), duration_bounds=(5, 50)) - - # Rate sources and units of states are set with tags in AscentEOM - ascent_to_flap_retract.set_state_options(Dynamic.Mission.FLIGHT_PATH_ANGLE, units="rad", - fix_initial=False, fix_final=False, lower=0, upper=0.5, ref=0.1, defect_ref=0.1) - - ascent_to_flap_retract.set_state_options(Dynamic.Mission.ALTITUDE, - fix_initial=False, fix_final=False, lower=0, upper=500, ref=100, defect_ref=100) - - ascent_to_flap_retract.set_state_options("TAS", - fix_initial=False, fix_final=False, lower=0, upper=1000, ref=100, defect_ref=100) - - ascent_to_flap_retract.set_state_options("mass", - fix_initial=False, fix_final=False, lower=1, upper=None, ref=175_000, defect_ref=175_000, rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL) - - ascent_to_flap_retract.set_state_options(Dynamic.Mission.DISTANCE, - fix_initial=False, fix_final=False, lower=0, upper=15_000, ref=5280, defect_ref=5280) - - # Targets are not needed when there is a top-level ODE input with the same name as the parameter, state, or control - ascent_to_flap_retract.add_parameter( - "t_init_gear", units="s", static_target=True, opt=False, val=100) - ascent_to_flap_retract.add_parameter( - "t_init_flaps", units="s", static_target=True, opt=False, val=100) - ascent_to_flap_retract.add_parameter( - "wing_area", units="ft**2", val=1370, static_target=True, opt=False) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are in degrees. - ascent_to_flap_retract.add_polynomial_control("alpha", order=1, - units="rad", val=0.0, - opt=True, lower=np.radians(-14), upper=np.radians(14), ref=0.01) - - # boundary/path constraints + controls - # Final altitude can be a linear constraint, since the final value of altitude is a design variable. - ascent_to_flap_retract.add_boundary_constraint(Dynamic.Mission.ALTITUDE, - loc="final", equals=400, units="ft", ref=1.0, ref0=0.0, linear=True - ) - - # Load factor can be treated as a linear constraint as long as i_wing is not a design variable. - ascent_to_flap_retract.add_path_constraint("load_factor", - lower=0.0, upper=1.10, linear=False) - - ascent_to_flap_retract.add_path_constraint("fuselage_pitch", constraint_name="theta", - lower=0, upper=15, units="deg", ref=15, linear=False) - - # - # ASCENT TO CLEAN AERO CONFIG - # - # Initial States Free - # Final States Free - # - # Controls: - # alpha - # - # Boundary Constraints: - # alt(final) = 500 ft - # flap_factor(final) = 0 (fully retracted) - # - # Path Constraints: - # 0.0 < load_factor < 1.10 - # 0.0 < fuselage_pitch < 15 deg - # - ascent2_tx = dm.Radau(num_segments=5, order=3, compressed=True, solve_segments=False) - ascent_to_clean_aero = dm.Phase( - ode_class=FlightPathODE, ode_init_kwargs=ode_args, transcription=ascent2_tx) - - ascent_to_clean_aero.set_time_options( - units="s", targets="t_curr", fix_initial=False, - initial_bounds=(10, 100), duration_bounds=(1, 50)) - - # Rate sources and units of states are set with tags in AscentEOM - ascent_to_clean_aero.set_state_options(Dynamic.Mission.FLIGHT_PATH_ANGLE, units="rad", - fix_initial=False, fix_final=False, lower=0, upper=0.5, ref=0.1, defect_ref=0.1) - - ascent_to_clean_aero.set_state_options(Dynamic.Mission.ALTITUDE, - fix_initial=False, fix_final=False, lower=0, upper=500, ref=100, defect_ref=100) - - ascent_to_clean_aero.set_state_options("TAS", - fix_initial=False, fix_final=False, lower=0, upper=1000, ref=100, defect_ref=100) - - ascent_to_clean_aero.set_state_options("mass", - fix_initial=False, fix_final=False, lower=1, upper=None, ref=175_000, defect_ref=175_000, rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL) - - ascent_to_clean_aero.set_state_options(Dynamic.Mission.DISTANCE, - fix_initial=False, fix_final=False, lower=0, upper=15_000, ref=5_280, defect_ref=5_280) - - # Targets are not needed when there is a top-level ODE input with the same name as the parameter, state, or control - ascent_to_clean_aero.add_parameter( - "t_init_gear", units="s", static_target=True, opt=False) - ascent_to_clean_aero.add_parameter( - "t_init_flaps", units="s", static_target=True, opt=False) - ascent_to_clean_aero.add_parameter( - "wing_area", units="ft**2", val=1370, static_target=True, opt=False) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are in degrees. - ascent_to_clean_aero.add_polynomial_control("alpha", order=1, - units="rad", val=0.0, # continuity=True, rate_continuity=True, - opt=True, lower=np.radians(-14), upper=np.radians(14), ref=0.01) - - # boundary/path constraints + controls - # Final altitude can be a linear constraint, since the final value of altitude is a design variable. - ascent_to_clean_aero.add_boundary_constraint(Dynamic.Mission.ALTITUDE, - loc="final", equals=500, units="ft", ref=1.0, ref0=0.0, linear=True - ) - - # Ensure gear and flaps are fully retracted - # Note setting equals=0.0 here will result in a failure of the optimization, since flap_factor approaches - # zero asymptotically. - ascent_to_clean_aero.add_boundary_constraint("gear_factor", - loc="final", upper=1.0E-3, ref=1.0, ref0=0.0, linear=False - ) - - ascent_to_clean_aero.add_boundary_constraint("flap_factor", - loc="final", upper=1.0E-3, ref=1.0, ref0=0.0, linear=False - ) - - # Load factor can be treated as a linear constraint as long as i_wing is not a design variable. - ascent_to_clean_aero.add_path_constraint("load_factor", - lower=0.0, upper=1.10, linear=False) - - ascent_to_clean_aero.add_path_constraint("fuselage_pitch", constraint_name="theta", - lower=0, upper=15, units="deg", ref=15, linear=False) - - # - # PROBLEM DEFINITION - # - p = om.Problem() - traj = p.model.add_subsystem("traj", dm.Trajectory()) - traj.add_phase("ascent_to_gear_retract", ascent_to_gear_retract) - traj.add_phase("ascent_to_flap_retract", ascent_to_flap_retract) - traj.add_phase("ascent_to_clean_aero", ascent_to_clean_aero) - - traj.add_parameter('wing_area', units='ft**2', static_target=True, opt=False) - - # Inter-phase connections - # 1. Connect final time of ascent_to_gear_retract to the trajectory as t_init_gear - # traj.connect(src_name='ascent_to_gear_retract.timeseries.time', - # tgt_name=('ascent_to_flap_retract.parameters:t_init_gear', - # 'ascent_to_clean_aero.parameters:t_init_gear'), - # src_indices=om.slicer[-1, ...]) - - # 2. Connect final time of ascent_to_flap_retract to the trajectory as t_init_flaps - traj.connect(src_name='ascent_to_flap_retract.timeseries.time', - tgt_name='ascent_to_clean_aero.parameters:t_init_flaps', - src_indices=om.slicer[-1, ...]) - - # 3. Enforce value continuity between all phases in ascent for time, states, and alpha control - traj.link_phases(['ascent_to_gear_retract', 'ascent_to_flap_retract', 'ascent_to_clean_aero'], - vars=['time', Dynamic.Mission.FLIGHT_PATH_ANGLE, Dynamic.Mission.ALTITUDE, 'TAS', Dynamic.Mission.MASS, Dynamic.Mission.DISTANCE, 'alpha']) - - traj.add_linkage_constraint(phase_a='ascent_to_gear_retract', - phase_b='ascent_to_flap_retract', - var_a='time', - var_b='t_init_gear', - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='ascent_to_gear_retract', - phase_b='ascent_to_clean_aero', - var_a='time', - var_b='t_init_gear', - loc_a='final', - loc_b='initial', - connected=True) - - # traj.nonlinear_solver = om.NonlinearBlockGS(iprint=0) - # traj.options['assembled_jac_type'] = 'csc' - # traj.linear_solver = om.DirectSolver() - - for phase_name, phase in traj._phases.items(): - phase.add_timeseries_output('flap_factor') - phase.add_timeseries_output('normal_force') - # Add alpha to the timeseries as 'alpha' regardless of whether it is a control or polynomial control. - phase.add_timeseries_output('alpha', units='deg') - - p.driver = om.pyOptSparseDriver() - - p.driver.options["optimizer"] = optimizer - - if optimizer == 'SNOPT': - p.driver.opt_settings["iSumm"] = 6 - p.driver.opt_settings['Major step limit'] = 1 - p.driver.opt_settings['Major feasibility tolerance'] = 1.0E-6 - p.driver.opt_settings['Major optimality tolerance'] = 1.0E-6 - elif optimizer == 'IPOPT': - p.driver.options['optimizer'] = 'IPOPT' - p.driver.opt_settings['tol'] = 1.0E-6 - p.driver.opt_settings['mu_init'] = 1e-5 - p.driver.opt_settings['max_iter'] = 100 - p.driver.opt_settings['print_level'] = 5 - # for faster convergence - p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based' - p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas' - p.driver.opt_settings['mu_strategy'] = 'monotone' - - # p.driver.options['debug_print'] = ['desvars'] - - # Uncomment declare_coloring when the coloring needs to be reset (number of nodes have changed, constraints changed, - # or underlying models have changed) - # p.driver.use_fixed_coloring() - # p.model.use_fixed_coloring(recurse=True) - p.driver.declare_coloring(tol=1.0E-12) - - # p.model.add_objective("traj.ascent_to_gear_retract.timeseries.states:mass", index=-1, ref=1.4e5, ref0=1.3e5) - ascent_to_clean_aero.add_objective("mass", loc="final", ref=-1.e3) - - # p.model.connect('wing_area','traj.ascent_to_gear_retract.parameters:wing_area') - # p.model.set_input_defaults("gross_wt_initial", val=174000, units="lbf") - p.setup() - - # SET INITIAL TIMES GUESSES - # p.set_val('traj.parameters:t_init_gear', 40.0, units='s') - # p.set_val('traj.parameters:t_init_flaps', 47.5, units='s') - p.set_val('traj.parameters:wing_area', 1370, units='ft**2') - - # SET ALTITUDE GUESSES - p.set_val("traj.ascent_to_gear_retract.states:altitude", - ascent_to_gear_retract.interp(Dynamic.Mission.ALTITUDE, ys=[0, 50]), units="ft") - p.set_val("traj.ascent_to_flap_retract.states:altitude", - ascent_to_flap_retract.interp(Dynamic.Mission.ALTITUDE, ys=[50, 400]), units="ft") - p.set_val("traj.ascent_to_clean_aero.states:altitude", - ascent_to_clean_aero.interp(Dynamic.Mission.ALTITUDE, ys=[400, 500]), units="ft") - - # SET FLIGHT PATH ANGLE GUESSES - p.set_val("traj.ascent_to_gear_retract.states:flight_path_angle", 0.2, units="rad") - p.set_val("traj.ascent_to_flap_retract.states:flight_path_angle", 10, units="deg") - p.set_val("traj.ascent_to_clean_aero.states:flight_path_angle", 10, units="deg") - - # SET TRUE AIRSPEED GUESSES - p.set_val("traj.ascent_to_gear_retract.states:TAS", - ascent_to_gear_retract.interp("TAS", [153.3196491, 170]), units="kn") - p.set_val("traj.ascent_to_flap_retract.states:TAS", - ascent_to_flap_retract.interp("TAS", [170, 190]), units="kn") - p.set_val("traj.ascent_to_clean_aero.states:TAS", - ascent_to_clean_aero.interp("TAS", [190, 195]), units="kn") - - # SET MASS GUESSES - p.set_val("traj.ascent_to_gear_retract.states:mass", ascent_to_gear_retract.interp( - "mass", [174963.74211336, 174000]), units="lbm") - p.set_val("traj.ascent_to_flap_retract.states:mass", - ascent_to_flap_retract.interp("mass", [174000., 173900]), units="lbm") - p.set_val("traj.ascent_to_clean_aero.states:mass", - ascent_to_clean_aero.interp("mass", [173900., 173800]), units="lbm") - - # SET RANGE GUESSES - p.set_val("traj.ascent_to_gear_retract.states:distance", ascent_to_gear_retract.interp( - Dynamic.Mission.DISTANCE, [4330.83393029, 5000]), units="ft") - p.set_val("traj.ascent_to_flap_retract.states:distance", - ascent_to_flap_retract.interp(Dynamic.Mission.DISTANCE, [5000., 6000]), units="ft") - p.set_val("traj.ascent_to_clean_aero.states:distance", - ascent_to_clean_aero.interp(Dynamic.Mission.DISTANCE, [6000., 7000]), units="ft") - - # SET TIME GUESSES - p.set_val("traj.ascent_to_gear_retract.t_initial", 31.2) - p.set_val("traj.ascent_to_gear_retract.t_duration", 3.0) - - p.set_val("traj.ascent_to_flap_retract.t_initial", 35.0) - p.set_val("traj.ascent_to_flap_retract.t_duration", 5.0) - - p.set_val("traj.ascent_to_clean_aero.t_initial", 44.0) - p.set_val("traj.ascent_to_clean_aero.t_duration", 10.0) - - # SET ALPHA GUESSES - p.set_val("traj.ascent_to_gear_retract.polynomial_controls:alpha", - ascent_to_gear_retract.interp('alpha', ys=[0, 0]), units="deg") - p.set_val("traj.ascent_to_flap_retract.polynomial_controls:alpha", - ascent_to_flap_retract.interp('alpha', ys=[0, 0]), units="deg") - p.set_val("traj.ascent_to_clean_aero.polynomial_controls:alpha", - ascent_to_clean_aero.interp('alpha', ys=[0, 0]), units="deg") - - p.set_solver_print(level=2, depth=1e99) - - return p - - -@use_tempdirs -class TestAscent(unittest.TestCase): - - def assert_result(self, p): - t_init_gear = p.get_val( - "traj.ascent_to_gear_retract.timeseries.time", units="s")[-1, ...] - t_init_flaps = p.get_val( - "traj.ascent_to_flap_retract.timeseries.time", units="s")[-1, ...] - - print("t_init_gear (s)", t_init_gear) - print("t_init_flaps (s)", t_init_flaps) - - assert_near_equal(t_init_gear, 32.3, tolerance=0.01) - assert_near_equal(t_init_flaps, 50.93320588, tolerance=0.01) - - @require_pyoptsparse(optimizer='IPOPT') - def bench_test_ascent_result_ipopt(self): - p = make_ascent_problem(optimizer='IPOPT') - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False, - solution_record_file='ascent_solution_IPOPT.db') - self.assert_result(p) - - @require_pyoptsparse(optimizer='SNOPT') - def bench_test_ascent_result_snopt(self): - p = make_ascent_problem(optimizer='SNOPT') - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False, - solution_record_file='ascent_solution_SNOPT.db') - self.assert_result(p) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_new_groundroll.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_new_groundroll.py deleted file mode 100644 index 7b9feb768..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_new_groundroll.py +++ /dev/null @@ -1,183 +0,0 @@ -import unittest - -import dymos as dm -import openmdao.api as om -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs - -from aviary.constants import RHO_SEA_LEVEL_ENGLISH -from aviary.mission.gasp_based.ode.flight_path_ode import FlightPathODE -from aviary.mission.gasp_based.ode.params import ParamPort -from aviary.mission.gasp_based.phases.v_rotate_comp import VRotateComp -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Aircraft, Dynamic -from aviary.interface.default_phase_info.two_dof import default_mission_subsystems - - -def make_groundroll_problem(optimizer='IPOPT', print_opt_iters=False, solve_segments=False): - groundroll_trans = dm.Radau( - num_segments=5, order=3, compressed=True, solve_segments=solve_segments - ) - - ode_args = dict( - aviary_options=get_option_defaults(), - core_subsystems=default_mission_subsystems - ) - - groundroll = dm.Phase( - ode_class=FlightPathODE, - ode_init_kwargs=dict(ode_args, ground_roll=True), - transcription=groundroll_trans, - ) - - groundroll.set_time_options(fix_initial=True, fix_duration=False, - units="s", targets="t_curr", - duration_bounds=(20, 100), duration_ref=1) - - groundroll.set_state_options("TAS", - fix_initial=True, fix_final=False, lower=1.0E-6, upper=1000, ref=1, defect_ref=1) - - groundroll.set_state_options("mass", - fix_initial=True, fix_final=False, lower=1, upper=195_000, ref=1000, defect_ref=1000, rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL) - - groundroll.set_state_options(Dynamic.Mission.DISTANCE, - fix_initial=True, fix_final=False, lower=0, upper=100_000, ref=1, defect_ref=1) - - groundroll.add_parameter("t_init_gear", units="s", - static_target=True, opt=False, val=32.3) - groundroll.add_parameter("t_init_flaps", units="s", - static_target=True, opt=False, val=44.0) - - groundroll.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") - groundroll.add_timeseries_output("normal_force") - groundroll.add_timeseries_output(Dynamic.Mission.MACH) - groundroll.add_timeseries_output("EAS", units="kn") - groundroll.add_timeseries_output(Dynamic.Mission.LIFT) - groundroll.add_timeseries_output("CL") - groundroll.add_timeseries_output("CD") - groundroll.add_timeseries_output("fuselage_pitch", output_name="theta", units="deg") - - p = om.Problem() - - traj = p.model.add_subsystem("traj", dm.Trajectory()) - groundroll = traj.add_phase("groundroll", groundroll) - - groundroll.add_objective('time', loc='final', ref=1.0) - - p.model.add_subsystem("vrot_comp", VRotateComp()) - p.model.connect('traj.groundroll.states:mass', - 'vrot_comp.mass', src_indices=om.slicer[0, ...]) - - vrot_eq_comp = p.model.add_subsystem("vrot_eq_comp", om.EQConstraintComp()) - vrot_eq_comp.add_eq_output("v_rotate_error", eq_units="kn", - lhs_name="v_rot_computed", rhs_name="groundroll_v_final", add_constraint=True) - - p.model.connect('vrot_comp.Vrot', 'vrot_eq_comp.v_rot_computed') - p.model.connect('traj.groundroll.states:TAS', - 'vrot_eq_comp.groundroll_v_final', src_indices=om.slicer[-1, ...]) - - p.driver = om.pyOptSparseDriver() - p.driver.declare_coloring() - p.driver.options["print_results"] = p.comm.rank == 0 - - p.driver.options["optimizer"] = optimizer - - if optimizer == "SNOPT": - p.driver.opt_settings["Major optimality tolerance"] = 3e-4 - p.driver.opt_settings["Major feasibility tolerance"] = 1e-6 - p.driver.opt_settings["Function precision"] = 1e-6 - p.driver.opt_settings["Linesearch tolerance"] = 0.99 - if print_opt_iters: - p.driver.opt_settings["iSumm"] = 6 - p.driver.opt_settings["Major iterations limit"] = 75 - elif optimizer == "IPOPT": - p.driver.opt_settings["max_iter"] = 500 - p.driver.opt_settings["tol"] = 1e-5 - if print_opt_iters: - p.driver.opt_settings["print_level"] = 5 - p.driver.opt_settings[ - "nlp_scaling_method" - ] = "gradient-based" # for faster convergence - p.driver.opt_settings["alpha_for_y"] = "safer-min-dual-infeas" - p.driver.opt_settings["mu_strategy"] = "monotone" - p.driver.opt_settings["bound_mult_init_method"] = "mu-based" - - p.set_solver_print(level=-1) - - p.setup() - - # TODO: paramport - params = ParamPort.param_data - p.set_val('vrot_comp.' + Aircraft.Wing.AREA, - params[Aircraft.Wing.AREA]["val"], units=params[Aircraft.Wing.AREA]["units"]) - - p.set_val("vrot_comp.dV1", val=10, units="kn") - p.set_val("vrot_comp.dVR", val=5, units="kn") - p.set_val("vrot_comp.rho", val=RHO_SEA_LEVEL_ENGLISH, units="slug/ft**3") - p.set_val("vrot_comp.CL_max", val=2.1886, units="unitless") - - p.set_val("traj.groundroll.states:TAS", - groundroll.interp("TAS", [0, 146]), - units="kn") - - p.set_val( - "traj.groundroll.states:mass", - groundroll.interp("mass", [175100, 174000]), - units="lbm", - ) - - p.set_val( - "traj.groundroll.states:distance", - groundroll.interp(Dynamic.Mission.DISTANCE, [0, 3000]), - units="ft", - ) - - p.set_val("traj.groundroll.t_duration", 20.0) - - return p - - -@use_tempdirs -class TestGroundRoll(unittest.TestCase): - - # @require_pyoptsparse(optimizer='IPOPT') - # def benchmark_groundroll_result_ipopt(self): - # p = make_groundroll_problem(optimizer='IPOPT') - # dm.run_problem(p, run_driver=True, simulate=False, make_plots=False) - - # tf = p.get_val('traj.groundroll.timeseries.time', units='s')[-1, 0] - # vrot = p.get_val('vrot_comp.Vrot', units='kn')[0] - - # print(f't_final: {tf:8.3f} s') - # print(f'v_rotate: {vrot:8.3f} knots') - - # assert_near_equal(tf, 29.6, tolerance=0.01) - # assert_near_equal(vrot, 146.4, tolerance=0.02) - - @require_pyoptsparse(optimizer='IPOPT') - def bench_test_groundroll_result_ipopt_solve(self): - p = make_groundroll_problem(optimizer='IPOPT', solve_segments='forward') - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False) - - tf = p.get_val('traj.groundroll.timeseries.time', units='s')[-1, 0] - vrot = p.get_val('vrot_comp.Vrot', units='kn')[0] - - print(f't_final: {tf:8.3f} s') - print(f'v_rotate: {vrot:8.3f} knots') - - assert_near_equal(tf, 30.99546852, tolerance=0.01) - assert_near_equal(vrot, 146.4, tolerance=0.02) - - # @require_pyoptsparse(optimizer='SNOPT') - # def benchmark_groundroll_result_snopt(self): - # p = make_groundroll_problem(optimizer='SNOPT') - # dm.run_problem(p, run_driver=True, simulate=False, make_plots=False) - - # tf = p.get_val('traj.groundroll.timeseries.time', units='s')[-1, 0] - # vrot = p.get_val('vrot_comp.Vrot', units='kn')[0] - - # print(f't_final: {tf:8.3f} s') - # print(f'v_rotate: {vrot:8.3f} knots') - - # assert_near_equal(tf, 29.6, tolerance=0.01) - # assert_near_equal(vrot, 146.4, tolerance=0.02) diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_new_rotation.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_new_rotation.py deleted file mode 100644 index c26080b6d..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_new_rotation.py +++ /dev/null @@ -1,176 +0,0 @@ -import unittest - -import dymos as dm -import numpy as np -import openmdao.api as om -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs -from packaging import version - -from aviary.mission.gasp_based.ode.flight_path_ode import FlightPathODE -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Dynamic -from aviary.interface.default_phase_info.two_dof import default_mission_subsystems - - -def make_rotation_problem(optimizer='IPOPT', print_opt_iters=False): - # - # ROTATION TO TAKEOFF - # - # Initial States Fixed - # Final States Free - # - # Controls: - # None - # - # Boundary Constraints: - # normal_force(final) = 0 lbf - # - # Path Constraints: - # None - # - - rotation_trans = dm.Radau( - num_segments=5, order=3, compressed=True, solve_segments=False - ) - - ode_args = dict( - aviary_options=get_option_defaults(), - core_subsystems=default_mission_subsystems - ) - - rotation = dm.Phase( - ode_class=FlightPathODE, - # Use the standard ode_args and update it for ground_roll dynamics - ode_init_kwargs=dict(ode_args, ground_roll=True), - transcription=rotation_trans, - ) - - rotation.set_time_options( - fix_initial=True, - fix_duration=False, - units="s", - targets="t_curr", - duration_bounds=(1, 100), - duration_ref=1.0, - ) - - rotation.add_parameter('alpha_rate', opt=False, - static_target=True, val=3.33, units='deg/s') - rotation.add_parameter("t_init_gear", units="s", - static_target=True, opt=False, val=32.3) - rotation.add_parameter("t_init_flaps", units="s", - static_target=True, opt=False, val=44.0) - rotation.add_parameter("wing_area", units="ft**2", - static_target=True, opt=False, val=1370) - - # State alpha is not defined in the ODE, taken from the parameter "alpha_rate" - rotation.add_state("alpha", units="rad", rate_source="alpha_rate", - fix_initial=True, fix_final=False, lower=0.0, upper=np.radians(25), ref=1.0) - - rotation.set_state_options("TAS", - fix_initial=True, fix_final=False, lower=0.0, upper=1000.0, ref=100.0, defect_ref=100.0) - - rotation.set_state_options("mass", - fix_initial=True, fix_final=False, lower=1.0, upper=190_000.0, ref=1000.0, - defect_ref=1000.0, rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - ) - - rotation.set_state_options(Dynamic.Mission.DISTANCE, - fix_initial=True, fix_final=False, lower=0, upper=10.e3, ref=100, defect_ref=100) - - # boundary/path constraints + controls - rotation.add_boundary_constraint( - "normal_force", loc="final", equals=0, units="lbf", ref=1000.0) - - rotation.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") - rotation.add_timeseries_output("normal_force") - rotation.add_timeseries_output(Dynamic.Mission.MACH) - rotation.add_timeseries_output("EAS", units="kn") - rotation.add_timeseries_output(Dynamic.Mission.LIFT) - rotation.add_timeseries_output("CL") - rotation.add_timeseries_output("CD") - rotation.add_timeseries_output("fuselage_pitch", output_name="theta", units="deg") - - p = om.Problem() - traj = p.model.add_subsystem("traj", dm.Trajectory()) - traj.add_phase("rotation", rotation) - - rotation.timeseries_options['use_prefix'] = True - - p.driver = om.pyOptSparseDriver() - p.driver.options["optimizer"] = optimizer - - if optimizer == 'SNOPT': - if print_opt_iters: - p.driver.opt_settings["iSumm"] = 6 - p.driver.opt_settings['Major step limit'] = 1 - p.driver.opt_settings['Major feasibility tolerance'] = 1.0E-6 - p.driver.opt_settings['Major optimality tolerance'] = 1.0E-6 - elif optimizer == 'IPOPT': - p.driver.options['optimizer'] = 'IPOPT' - p.driver.opt_settings['tol'] = 1.0E-6 - p.driver.opt_settings['mu_init'] = 1e-3 - p.driver.opt_settings['max_iter'] = 100 - if print_opt_iters: - p.driver.opt_settings['print_level'] = 5 - # for faster convergence - p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based' - p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas' - p.driver.opt_settings['mu_strategy'] = 'monotone' - - p.model.add_objective( - "traj.rotation.timeseries.states:mass", index=-1, ref=1.4e5, ref0=1.3e5 - ) - p.set_solver_print(level=-1) - - p.setup() - - # SET INITIAL ALPHA INITIAL GUESS - p.set_val("traj.rotation.states:alpha", - rotation.interp("alpha", [0, 2.5]), units="deg") - - # SET TRUE AIRSPEED INITIAL GUESS - p.set_val("traj.rotation.states:TAS", 143, units="kn") - - # SET MASS INITIAL GUESS - p.set_val("traj.rotation.states:mass", rotation.interp( - "mass", [174975.12776915, 174900]), units="lbm") - - # SET RANGE INITIAL GUESS - p.set_val("traj.rotation.states:distance", rotation.interp( - Dynamic.Mission.DISTANCE, [3680.37217765, 4000]), units="ft") - - # SET TIME INITIAL GUESS - p.set_val("traj.rotation.t_initial", 30.0) - p.set_val("traj.rotation.t_duration", 4.0) - - return p - - -@use_tempdirs -class TestRotation(unittest.TestCase): - - def assert_result(self, p): - tf = p.get_val('traj.rotation.timeseries.time', units='s')[-1, 0] - vf = p.get_val('traj.rotation.timeseries.states:TAS', units='kn')[-1, 0] - - print(f't_final: {tf:8.3f} s') - print(f'v_final: {vf:8.3f} knots') - - assert_near_equal(tf, 32.993, tolerance=0.01) - assert_near_equal(vf, 155.246, tolerance=0.01) - - @require_pyoptsparse(optimizer='IPOPT') - def bench_test_rotation_result_ipopt(self): - p = make_rotation_problem(optimizer='IPOPT') - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False, - solution_record_file='rotation_IPOPT.db') - self.assert_result(p) - - @require_pyoptsparse(optimizer='SNOPT') - def bench_test_rotation_result_snopt(self): - p = make_rotation_problem(optimizer='SNOPT') - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False, - solution_record_file='rotation_SNOPT.db') - self.assert_result(p) diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_rotation.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_rotation.py deleted file mode 100644 index dfb96ab8c..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_rotation.py +++ /dev/null @@ -1,43 +0,0 @@ -import unittest - -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.gasp_based.phases.run_phases.run_rotation import run_rotation - - -@unittest.skip('this benchmark is defunct and needs to be updated') -class RotationPhaseTestCase(unittest.TestCase): - def bench_test_rotation(self): - - prob = run_rotation(make_plots=False) - - alpha = prob.get_val("traj.rotation.timeseries.states:alpha", units="deg") - TAS = prob.get_val("traj.rotation.timeseries.states:TAS", units="kn") - weight = prob.get_val("traj.rotation.timeseries.states:weight", units="lbm") - distance = prob.get_val("traj.rotation.timeseries.states:distance", units="ft") - normal_force = prob.get_val( - "traj.rotation.timeseries.normal_force", units="lbf" - ) - time = prob.get_val("traj.rotation.timeseries.time", units="s") - - assert_near_equal(alpha[0], 0, 1e-4) - assert_near_equal(alpha[-1], 25, 1e-4) - - assert_near_equal(TAS[0], 143, 1e-4) - assert_near_equal(TAS[-1], 173.4636624, 1e-4) - - assert_near_equal(weight[0], 174975.12776915, 1e-6) - assert_near_equal(weight[-1], 174942.19649927, 1e-6) - - assert_near_equal(distance[0], 3680.37217765, 1e-4) - assert_near_equal(distance[-1], 5693.05954272, 1e-4) - - assert_near_equal(normal_force[0], 118093.50754183, 1e-5) - assert_near_equal(normal_force[-1], 0, 1e-5) - - assert_near_equal(time[0], 0, 1e-3) - assert_near_equal(time[-1], 7.50750751, 1e-3) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_bench_takeoff.py b/aviary/mission/gasp_based/phases/benchmark/test_bench_takeoff.py deleted file mode 100644 index 44fe17859..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_bench_takeoff.py +++ /dev/null @@ -1,59 +0,0 @@ -import unittest - -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.mission.gasp_based.phases.run_phases.run_takeoff import run_takeoff - - -@unittest.skip('this benchmark is defunct and needs to be updated') -class TakeoffPhaseTestCase(unittest.TestCase): - def bench_test_takeoff(self): - - prob = run_takeoff(make_plots=False) - - TAS0 = prob.get_val("traj.groundroll.timeseries.states:TAS", units="kn") - distance0 = prob.get_val( - "traj.groundroll.timeseries.states:distance", units="ft" - ) - weight0 = prob.get_val("traj.groundroll.timeseries.states:weight", units="lbm") - time0 = prob.get_val("traj.groundroll.timeseries.time", units="s") - - gamma1 = prob.get_val( - "traj.ascent.timeseries.states:flight_path_angle", units="deg") - alt1 = prob.get_val("traj.ascent.timeseries.states:altitude", units="ft") - TAS1 = prob.get_val("traj.ascent.timeseries.states:TAS", units="kn") - distance1 = prob.get_val("traj.ascent.timeseries.states:distance", units="ft") - load_factor1 = prob.get_val( - "traj.ascent.timeseries.load_factor", units="unitless") - fuselage_pitch1 = prob.get_val( - "traj.ascent.timeseries.fuselage_pitch", units="deg" - ) - alpha1 = prob.get_val("traj.ascent.timeseries.controls:alpha", units="deg") - weight1 = prob.get_val("traj.ascent.timeseries.states:weight", units="lbm") - time1 = prob.get_val("traj.ascent.timeseries.time", units="s") - - assert_near_equal(gamma1[-1], -0.62446459, 1e-3) - - assert_near_equal(alt1[-1], 500, 1e-4) - - assert_near_equal(TAS0[0], 0, 1e-4) - assert_near_equal(TAS1[-1], 191.15818794, 1e-4) - - assert_near_equal(distance0[0], 0, 1e-5) - assert_near_equal(distance1[-1], 10000.0, 1e-5) - - assert_near_equal(load_factor1[-1], 0.16259711, 1e-2) - - assert_near_equal(fuselage_pitch1[-1], 0, 1e-3) - - assert_near_equal(alpha1[-1], 0.62446459, 1e-3) - - assert_near_equal(weight0[0], 175100, 1e-5) - assert_near_equal(weight1[-1], 174879.40581381, 1e-5) - - assert_near_equal(time0[0], 0, 1e-3) - assert_near_equal(time1[-1], 50.5763888, 1e-3) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_climb.py b/aviary/mission/gasp_based/phases/benchmark/test_climb.py deleted file mode 100644 index 736d7ff39..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_climb.py +++ /dev/null @@ -1,248 +0,0 @@ -import unittest - -import dymos as dm -import numpy as np -import openmdao.api as om -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs - -from aviary.mission.gasp_based.ode.flight_path_ode import FlightPathODE -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Aircraft, Dynamic -from aviary.interface.default_phase_info.two_dof import default_mission_subsystems - - -def make_climb_problem( - optimizer='IPOPT', - print_opt_iters=False, - constant_quantity=Dynamic.Mission.MACH): - prob = om.Problem(model=om.Group()) - - prob.driver = om.pyOptSparseDriver() - - if optimizer == "SNOPT": - prob.driver.options["optimizer"] = "SNOPT" - prob.driver.opt_settings['Major iterations limit'] = 100 - # prob.driver.opt_settings['Major step limit'] = 0.05 - prob.driver.opt_settings['Major feasibility tolerance'] = 1.0E-6 - prob.driver.opt_settings['Major optimality tolerance'] = 1.0E-6 - if print_opt_iters: - prob.driver.opt_settings["iSumm"] = 6 - elif optimizer == "IPOPT": - prob.driver.options["optimizer"] = "IPOPT" - prob.driver.opt_settings["max_iter"] = 500 - prob.driver.opt_settings["tol"] = 1e-5 - prob.driver.opt_settings["print_level"] = 5 - prob.driver.opt_settings[ - "nlp_scaling_method" - ] = "gradient-based" # for faster convergence - prob.driver.opt_settings["alpha_for_y"] = "safer-min-dual-infeas" - prob.driver.opt_settings["mu_strategy"] = "monotone" - prob.driver.opt_settings["bound_mult_init_method"] = "mu-based" - - transcription = dm.Radau(num_segments=11, order=3, compressed=True) - - ode_args = dict( - clean=True, - aviary_options=get_option_defaults(), - core_subsystems=default_mission_subsystems - ) - - climb = dm.Phase(ode_class=FlightPathODE, transcription=transcription, - ode_init_kwargs=ode_args) - - climb.set_time_options(fix_initial=True, duration_bounds=( - 10, 5000), units="s", duration_ref=200) - - climb.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=1, - upper=1000, - units="kn", - ref=250, - defect_ref=250) - - climb.set_state_options( - "mass", - fix_initial=True, - fix_final=False, - lower=1, - upper=None, - units="lbm", - ref=200000, - defect_ref=200000, - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL) - - climb.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=True, - fix_final=False, - lower=0, - upper=None, - units="ft", - ref=10000, - defect_ref=10000) - - if constant_quantity == 'EAS': - climb.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=True, - fix_final=False, - lower=400, - upper=20000, - units="ft", - ref=20.e3, - defect_ref=20.e3) - else: - climb.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=True, - fix_final=False, - lower=9000, - upper=50000, - units="ft", - ref=40000, - defect_ref=40000) - - climb.set_state_options( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=False, - fix_final=False, - units="rad", - ref=1.0, - defect_ref=1.0) - - # climb.add_polynomial_control( - # "alpha", order=3, opt=True, units="rad", val=0.0, lower=np.radians(-4), upper=np.radians(14)) - climb.add_control("alpha", - opt=True, - units="rad", - val=0.0, - lower=np.radians(-14), - upper=np.radians(14), - rate_continuity=True, - rate2_continuity=False) - - if constant_quantity == 'EAS': - climb.add_path_constraint("EAS", lower=249.9, upper=250.1, ref=250., units="kn") - # climb.add_path_constraint("EAS", equals=350., ref=350., units="kn") - climb.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, loc="final", equals=10.e3, ref=10.e3, units="ft") - else: - climb.add_path_constraint(Dynamic.Mission.MACH, - lower=0.799, upper=0.801) - climb.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, loc="final", equals=37.5e3, ref=40.e3, units="ft") - - throttle_climb = 0.956 - climb.add_parameter( - Dynamic.Mission.THROTTLE, opt=False, units="unitless", val=throttle_climb, static_target=False - ) - - climb.add_parameter( - Aircraft.Wing.AREA, opt=False, units="ft**2", val=1370.0, static_target=True - ) - - climb.add_timeseries_output("EAS", output_name="EAS", units="kn") - climb.add_timeseries_output( - Dynamic.Mission.MACH, output_name=Dynamic.Mission.MACH, units="unitless") - climb.add_timeseries_output("alpha", output_name="alpha", units="deg") - climb.add_timeseries_output("CL", output_name="CL", units="unitless") - climb.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") - climb.add_timeseries_output("CD", output_name="CD", units="unitless") - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - traj.add_phase('climb', climb) - - climb.add_objective("mass", loc="final", ref=-1.e4) - # climb.add_objective("time", loc="final", ref=1.e2) - - prob.model.linear_solver.options["iprint"] = 0 - prob.model.nonlinear_solver.options["iprint"] = 0 - - traj.nonlinear_solver = om.NonlinearBlockGS(iprint=0) - traj.options['assembled_jac_type'] = 'csc' - traj.linear_solver = om.DirectSolver() - - prob.set_solver_print(level=0) - - for phase_name, phase in traj._phases.items(): - phase.add_timeseries_output('normal_force') - phase.add_timeseries_output('fuselage_pitch') - - phase.add_timeseries_output(Dynamic.Mission.ALTITUDE, units='ft') - phase.add_timeseries_output('mass', units='lbm') - - prob.setup() - - if constant_quantity == 'EAS': - prob.set_val("traj.climb.states:TAS", climb.interp( - "TAS", ys=[250., 250.]), units='kn') - prob.set_val("traj.climb.states:altitude", climb.interp( - Dynamic.Mission.ALTITUDE, ys=[500., 10.e3]), units='ft') - prob.set_val("traj.climb.states:flight_path_angle", climb.interp( - Dynamic.Mission.FLIGHT_PATH_ANGLE, ys=[0, 1]), units='deg') - prob.set_val("traj.climb.states:mass", climb.interp( - "mass", ys=[174219, 170000]), units='lbm') - else: - prob.set_val("traj.climb.states:TAS", climb.interp( - "TAS", ys=[490.5, 500]), units='kn') - prob.set_val("traj.climb.states:altitude", climb.interp( - Dynamic.Mission.ALTITUDE, ys=[20.e3, 37.5e3]), units='ft') - prob.set_val("traj.climb.states:flight_path_angle", climb.interp( - Dynamic.Mission.FLIGHT_PATH_ANGLE, ys=[1, 0.5]), units='deg') - prob.set_val("traj.climb.states:mass", climb.interp( - "mass", ys=[172.83e3, 171.e3]), units='lbm') - - prob.set_val("traj.climb.states:distance", climb.interp( - Dynamic.Mission.DISTANCE, ys=[15, 154]), units='NM') - prob.set_val("traj.climb.controls:alpha", - climb.interp("alpha", ys=[3, 2]), units='deg') - prob.set_val("traj.climb.t_duration", 200, units='s') - prob.set_val("traj.climb.t_initial", 0, units='s') - - return prob - - -@use_tempdirs -class Testclimb(unittest.TestCase): - - def assert_constant_EAS_result(self, p): - final_mass = p.get_val("traj.climb.timeseries.mass", units="lbm")[-1, ...] - assert_near_equal(final_mass, 173_558, tolerance=0.0001) - - @require_pyoptsparse(optimizer='IPOPT') - def test_constant_EAS_climb_ipopt(self): - p = make_climb_problem(optimizer='IPOPT', constant_quantity='EAS') - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False) - self.assert_constant_EAS_result(p) - - @require_pyoptsparse(optimizer='SNOPT') - def test_constant_EAS_climb_snopt(self): - p = make_climb_problem( - optimizer='SNOPT', constant_quantity='EAS', print_opt_iters=True) - dm.run_problem(p, run_driver=True, simulate=True, make_plots=False) - self.assert_constant_EAS_result(p) - - def assert_constant_mach_result(self, p): - final_mass = p.get_val("traj.climb.timeseries.mass", units="lbm")[-1, ...] - assert_near_equal(final_mass, 171_073, tolerance=0.0001) - - @require_pyoptsparse(optimizer='IPOPT') - def test_constant_mach_climb_ipopt(self): - p = make_climb_problem(optimizer='IPOPT', constant_quantity=Dynamic.Mission.MACH) - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False) - self.assert_constant_mach_result(p) - - @require_pyoptsparse(optimizer='SNOPT') - def test_constant_mach_climb_snopt(self): - p = make_climb_problem(optimizer='SNOPT', constant_quantity=Dynamic.Mission.MACH) - dm.run_problem(p, run_driver=True, simulate=False, make_plots=False) - self.assert_constant_mach_result(p) - - -if __name__ == "__main__": - unittest.main() diff --git a/aviary/mission/gasp_based/phases/benchmark/test_conventional_problem.py b/aviary/mission/gasp_based/phases/benchmark/test_conventional_problem.py deleted file mode 100644 index 261702430..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_conventional_problem.py +++ /dev/null @@ -1,27 +0,0 @@ -import unittest - -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import require_pyoptsparse - -from aviary.mission.gasp_based.phases.run_phases.run_conventional_problem import \ - run_conventional_problem - - -@unittest.skip('Currently skipping the new conventional problem setup due to formulation instabilities') -class TestConventionalProblem(unittest.TestCase): - - def assert_result(self, p): - mass_after_climbs = p.get_val( - "traj.climb_to_cruise.states:mass", units="lbm")[-1, ...] - - assert_near_equal(mass_after_climbs, 171.329e3, tolerance=1.e-4) - - @require_pyoptsparse(optimizer="IPOPT") - def test_conventional_problem_ipopt(self): - p = run_conventional_problem(optimizer="IPOPT") - self.assert_result(p) - - @require_pyoptsparse(optimizer="SNOPT") - def test_conventional_problem_snopt(self): - p = run_conventional_problem(optimizer="SNOPT") - self.assert_result(p) diff --git a/aviary/mission/gasp_based/phases/benchmark/test_cruise.py b/aviary/mission/gasp_based/phases/benchmark/test_cruise.py deleted file mode 100644 index d3c33ad84..000000000 --- a/aviary/mission/gasp_based/phases/benchmark/test_cruise.py +++ /dev/null @@ -1,106 +0,0 @@ -import unittest - -import dymos as dm -import openmdao.api as om -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import use_tempdirs - -from aviary.mission.gasp_based.ode.breguet_cruise_ode import BreguetCruiseODESolution -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Dynamic - - -def make_cruise_problem(): - # - # CRUISE UNDER BREGUET RANGE ASSUMPTION - # - # Initial States Fixed - # Final States Free - # - # Controls: - # None - # - # Boundary Constraints: - # None - # - # Path Constraints: - # None - # - ode_args = dict( - aviary_options=get_option_defaults(), - ) - - cruise = dm.AnalyticPhase( - ode_class=BreguetCruiseODESolution, - # Use the standard ode_args and update it for ground_roll dynamics - ode_init_kwargs=ode_args, - num_nodes=2 - ) - - # Time here is really the independent variable through which we are integrating. - # In the case of the Breguet Range ODE, it's mass. - # We rely on mass being monotonically non-increasing across the phase. - cruise.set_time_options( - fix_initial=True, - fix_duration=False, - units="lbm", - name="mass", - duration_bounds=(-20000, 0), - duration_ref=10000, - ) - - throttle_cruise = 0.930 - cruise.add_parameter( - Dynamic.Mission.THROTTLE, opt=False, units="unitless", val=throttle_cruise, static_target=False - ) - cruise.add_parameter(Dynamic.Mission.ALTITUDE, opt=False, val=37500.0, units='ft') - cruise.add_parameter(Dynamic.Mission.MACH, opt=False, val=0.8, units="unitless") - cruise.add_parameter("wing_area", opt=False, val=1370, units="ft**2") - cruise.add_parameter("initial_cruise_range", opt=False, val=0.0, units="NM") - cruise.add_parameter("initial_cruise_time", opt=False, val=0.0, units="s") - - cruise.add_timeseries_output("time", output_name="time", units="s") - - p = om.Problem() - traj = p.model.add_subsystem("traj", dm.Trajectory()) - traj.add_phase("cruise", cruise) - - p.set_solver_print(level=-1) - - p.setup(force_alloc_complex=True) - - # SET TIME INITIAL GUESS - p.set_val("traj.cruise.t_initial", 171481, units="lbm") # Initial mass in cruise - p.set_val("traj.cruise.t_duration", -10000, units="lbm") # Mass of fuel consumed - - p.set_val("traj.cruise.parameters:altitude", val=37500.0, units="ft") - p.set_val("traj.cruise.parameters:mach", val=0.8, units="unitless") - p.set_val("traj.cruise.parameters:wing_area", val=1370, units="ft**2") - p.set_val("traj.cruise.parameters:initial_cruise_range", val=0.0, units="NM") - p.set_val("traj.cruise.parameters:initial_cruise_time", val=0.0, units="s") - - return p - - -@use_tempdirs -class TestCruise(unittest.TestCase): - - def assert_result(self, p): - tf = p.get_val('traj.cruise.timeseries.states:cruise_time', units='s')[-1, 0] - rf = p.get_val('traj.cruise.timeseries.states:distance', units='NM')[-1, 0] - wf = p.get_val('traj.cruise.timeseries.mass', units='lbm')[-1, 0] - - print(f't_final: {tf:8.3f} s') - print(f'w_final: {wf:8.3f} lbm') - print(f'r_final: {rf:8.3f} NM') - - assert_near_equal(wf, 161481.0, tolerance=0.01) - assert_near_equal(tf, 7368.56, tolerance=0.01) - assert_near_equal(rf, 939.178, tolerance=0.01) - - @unittest.skip('Skipping this benchmark for now as the analytic cruise is not used in GASP currently.') - def test_cruise_result(self): - p = make_cruise_problem() - dm.run_problem(p, run_driver=False, simulate=False) - - self.assert_result(p) diff --git a/aviary/mission/gasp_based/phases/breguet.py b/aviary/mission/gasp_based/phases/breguet.py index 762dd8278..f798d3735 100644 --- a/aviary/mission/gasp_based/phases/breguet.py +++ b/aviary/mission/gasp_based/phases/breguet.py @@ -15,7 +15,7 @@ def setup(self): self.add_input("cruise_time_initial", val=0.0, units="s", desc="time at which cruise begins") - self.add_input("cruise_range_initial", val=0.0, units="NM", + self.add_input("cruise_distance_initial", val=0.0, units="NM", desc="range reference at which cruise begins") self.add_input("TAS_cruise", val=0.8 * np.ones(nn), @@ -64,7 +64,7 @@ def setup_partials(self): self.declare_partials( "cruise_time", [Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, "mass", "TAS_cruise"], rows=rs, cols=cs) - self.declare_partials("cruise_range", "cruise_range_initial", val=1.0) + self.declare_partials("cruise_range", "cruise_distance_initial", val=1.0) self.declare_partials("cruise_time", "cruise_time_initial", val=1.0) # Allocated memory so we don't have to repeatedly do it in compute_partials @@ -78,7 +78,7 @@ def compute(self, inputs, outputs): v_x = inputs["TAS_cruise"] m = inputs["mass"] FF = -inputs[Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL] - r0 = inputs["cruise_range_initial"] + r0 = inputs["cruise_distance_initial"] t0 = inputs["cruise_time_initial"] FF_1 = FF[:-1] # Initial fuel flow across each two-node pair diff --git a/aviary/mission/gasp_based/phases/climb_phase.py b/aviary/mission/gasp_based/phases/climb_phase.py index 08a28330f..1264ce94c 100644 --- a/aviary/mission/gasp_based/phases/climb_phase.py +++ b/aviary/mission/gasp_based/phases/climb_phase.py @@ -1,140 +1,165 @@ -import dymos as dm - +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessState, InitialGuessTime, InitialGuessControl +from aviary.utils.aviary_values import AviaryValues +from aviary.variable_info.variables import Dynamic from aviary.mission.gasp_based.ode.climb_ode import ClimbODE -from aviary.variable_info.variables import Mission, Dynamic -from aviary.interface.default_phase_info.two_dof import default_mission_subsystems - - -def get_climb( - ode_args=None, - transcription=None, - fix_initial=False, - EAS_target=0, - mach_cruise=0, - target_mach=False, - final_alt=0, - required_available_climb_rate=None, - time_initial_bounds=(0, 0), - duration_bounds=(0, 0), - duration_ref=1, - alt_lower=0, - alt_upper=0, - alt_ref=1, - alt_ref0=0, - alt_defect_ref=None, - mass_lower=0, - mass_upper=0, - mass_ref=1, - mass_ref0=0, - mass_defect_ref=None, - distance_lower=0, - distance_upper=0, - distance_ref=1, - distance_ref0=0, - distance_defect_ref=None, -): - ode_init_kwargs = dict( - EAS_target=EAS_target, - mach_cruise=mach_cruise, - core_subsystems=default_mission_subsystems - ) - if ode_args: - ode_init_kwargs.update(ode_args) - - climb = dm.Phase( - ode_class=ClimbODE, - transcription=transcription, - ode_init_kwargs=ode_init_kwargs, - ) - - climb.set_time_options( - fix_initial=fix_initial, - initial_bounds=time_initial_bounds, - duration_bounds=duration_bounds, - duration_ref=duration_ref, - units="s", - ) - - climb.add_state( - Dynamic.Mission.ALTITUDE, - fix_initial=fix_initial, - fix_final=False, - lower=alt_lower, - upper=alt_upper, - units="ft", - rate_source=Dynamic.Mission.ALTITUDE_RATE, - targets=Dynamic.Mission.ALTITUDE, - ref=alt_ref, - ref0=alt_ref0, - defect_ref=alt_defect_ref, - ) - - climb.add_state( - Dynamic.Mission.MASS, - fix_initial=fix_initial, - fix_final=False, - lower=mass_lower, - upper=mass_upper, - units="lbm", - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.Mission.MASS, - ref=mass_ref, - ref0=mass_ref0, - defect_ref=mass_defect_ref, - ) - - climb.add_state( - Dynamic.Mission.DISTANCE, - fix_initial=fix_initial, - fix_final=False, - lower=distance_lower, - upper=distance_upper, - units="NM", - rate_source="distance_rate", - ref=distance_ref, - ref0=distance_ref0, - defect_ref=distance_defect_ref, - ) - - climb.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, - loc="final", - equals=final_alt, - units="ft", - ref=final_alt) - if required_available_climb_rate: - climb.add_boundary_constraint( - Dynamic.Mission.ALTITUDE_RATE, + + +class ClimbPhase(PhaseBuilderBase): + """ + A phase builder for a climb phase in a mission simulation. + + This class extends the PhaseBuilderBase class, providing specific implementations for + the climb phase of a flight mission. + + Attributes + ---------- + Inherits all attributes from PhaseBuilderBase. + + Methods + ------- + Inherits all methods from PhaseBuilderBase. + Additional method overrides and new methods specific to the climb phase are included. + """ + default_name = 'climb_phase' + default_ode_class = ClimbODE + + _meta_data_ = {} + _initial_guesses_meta_data_ = {} + + def build_phase(self, aviary_options: AviaryValues = None): + """ + Return a new climb phase for analysis using these constraints. + + If ode_class is None, ClimbODE is used as the default. + + Parameters + ---------- + aviary_options : AviaryValues + Collection of Aircraft/Mission specific options + + Returns + ------- + dymos.Phase + """ + phase = self.phase = super().build_phase(aviary_options) + + # Custom configurations for the climb phase + user_options = self.user_options + + mach_cruise = user_options.get_val('mach_cruise') + target_mach = user_options.get_val('target_mach') + final_altitude = user_options.get_val('final_altitude', units='ft') + required_available_climb_rate = user_options.get_val( + 'required_available_climb_rate', units='ft/min') + + self.set_time_options(user_options) + + # States + self.add_altitude_state(user_options) + + self.add_mass_state(user_options) + + self.add_distance_state(user_options) + + # Boundary Constraints + phase.add_boundary_constraint( + Dynamic.Mission.ALTITUDE, loc="final", - lower=required_available_climb_rate, - units="ft/min", - ref=1, + equals=final_altitude, + units="ft", + ref=final_altitude, ) - if target_mach is True: - climb.add_boundary_constraint( - Dynamic.Mission.MACH, loc="final", equals=mach_cruise, units="unitless" - ) + if required_available_climb_rate is not None: + # TODO: this should be altitude rate max + phase.add_boundary_constraint( + Dynamic.Mission.ALTITUDE_RATE, + loc="final", + lower=required_available_climb_rate, + units="ft/min", + ref=1, + ) + + if target_mach: + phase.add_boundary_constraint( + Dynamic.Mission.MACH, loc="final", equals=mach_cruise, + ) + + # Timeseries Outputs + phase.add_timeseries_output( + Dynamic.Mission.MACH, output_name=Dynamic.Mission.MACH, units="unitless") + phase.add_timeseries_output("EAS", output_name="EAS", units="kn") + phase.add_timeseries_output( + Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, units="lbm/s") + phase.add_timeseries_output("theta", output_name="theta", units="deg") + phase.add_timeseries_output("alpha", output_name="alpha", units="deg") + phase.add_timeseries_output(Dynamic.Mission.FLIGHT_PATH_ANGLE, + output_name=Dynamic.Mission.FLIGHT_PATH_ANGLE, units="deg") + phase.add_timeseries_output( + "TAS_violation", output_name="TAS_violation", units="kn") + phase.add_timeseries_output("TAS", output_name="TAS", units="kn") + phase.add_timeseries_output("aero.CL", output_name="CL", units="unitless") + phase.add_timeseries_output( + Dynamic.Mission.THRUST_TOTAL, output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") + phase.add_timeseries_output("aero.CD", output_name="CD", units="unitless") + + return phase + + def _extra_ode_init_kwargs(self): + """ + Return extra kwargs required for initializing the ODE. + """ + # TODO: support external_subsystems and meta_data in the base class + return { + 'EAS_target': self.user_options.get_val('EAS_target', units='kn'), + 'mach_cruise': self.user_options.get_val('mach_cruise'), + } + + +# Adding metadata for the ClimbPhase +ClimbPhase._add_meta_data('fix_initial', val=False) +ClimbPhase._add_meta_data('EAS_target', val=0) +ClimbPhase._add_meta_data('mach_cruise', val=0) +ClimbPhase._add_meta_data('target_mach', val=False) +ClimbPhase._add_meta_data('final_altitude', val=0) +ClimbPhase._add_meta_data('required_available_climb_rate', val=None, units='ft/min') +ClimbPhase._add_meta_data('duration_bounds', val=(0, 0), units='s') +ClimbPhase._add_meta_data('duration_ref', val=1, units='s') +ClimbPhase._add_meta_data('alt_lower', val=0, units='ft') +ClimbPhase._add_meta_data('alt_upper', val=0, units='ft') +ClimbPhase._add_meta_data('alt_ref', val=1, units='ft') +ClimbPhase._add_meta_data('alt_ref0', val=0, units='ft') +ClimbPhase._add_meta_data('alt_defect_ref', val=None, units='ft') +ClimbPhase._add_meta_data('mass_lower', val=0, units='lbm') +ClimbPhase._add_meta_data('mass_upper', val=0, units='lbm') +ClimbPhase._add_meta_data('mass_ref', val=1, units='lbm') +ClimbPhase._add_meta_data('mass_ref0', val=0, units='lbm') +ClimbPhase._add_meta_data('mass_defect_ref', val=None, units='lbm') +ClimbPhase._add_meta_data('distance_lower', val=0, units='NM') +ClimbPhase._add_meta_data('distance_upper', val=0, units='NM') +ClimbPhase._add_meta_data('distance_ref', val=1, units='NM') +ClimbPhase._add_meta_data('distance_ref0', val=0, units='NM') +ClimbPhase._add_meta_data('distance_defect_ref', val=None, units='NM') +ClimbPhase._add_meta_data('num_segments', val=None, units='unitless') +ClimbPhase._add_meta_data('order', val=None, units='unitless') + +ClimbPhase._add_initial_guess_meta_data( + InitialGuessTime(), + desc='initial guess for initial time and duration specified as a tuple') + +ClimbPhase._add_initial_guess_meta_data( + InitialGuessState('distance'), + desc='initial guess for horizontal distance traveled') + +ClimbPhase._add_initial_guess_meta_data( + InitialGuessState('altitude'), + desc='initial guess for vertical distances') + +ClimbPhase._add_initial_guess_meta_data( + InitialGuessState('mass'), + desc='initial guess for mass') - climb.add_timeseries_output( - Dynamic.Mission.MACH, - output_name=Dynamic.Mission.MACH, - units="unitless") - climb.add_timeseries_output("EAS", output_name="EAS", units="kn") - climb.add_timeseries_output( - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, units="lbm/s" - ) - climb.add_timeseries_output("theta", output_name="theta", units="deg") - climb.add_timeseries_output("alpha", output_name="alpha", units="deg") - climb.add_timeseries_output(Dynamic.Mission.FLIGHT_PATH_ANGLE, - output_name=Dynamic.Mission.FLIGHT_PATH_ANGLE, units="deg") - climb.add_timeseries_output( - "TAS_violation", output_name="TAS_violation", units="kn" - ) - climb.add_timeseries_output("TAS", output_name="TAS", units="kn") - climb.add_timeseries_output("aero.CL", output_name="CL", units="unitless") - climb.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") - climb.add_timeseries_output("aero.CD", output_name="CD", units="unitless") - - return climb +ClimbPhase._add_initial_guess_meta_data( + InitialGuessControl('throttle'), + desc='initial guess for throttle') diff --git a/aviary/mission/gasp_based/phases/cruise_phase.py b/aviary/mission/gasp_based/phases/cruise_phase.py new file mode 100644 index 000000000..4d29720d7 --- /dev/null +++ b/aviary/mission/gasp_based/phases/cruise_phase.py @@ -0,0 +1,120 @@ +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessState, InitialGuessTime +from aviary.utils.aviary_values import AviaryValues +from aviary.variable_info.variables import Dynamic +from aviary.mission.gasp_based.ode.breguet_cruise_ode import BreguetCruiseODESolution +from aviary.variable_info.variable_meta_data import _MetaData + + +class CruisePhase(PhaseBuilderBase): + """ + A phase builder for a climb phase in a mission simulation. + + This class extends the PhaseBuilderBase class, providing specific implementations for + the climb phase of a flight mission. + + Attributes + ---------- + Inherits all attributes from PhaseBuilderBase. + + Methods + ------- + Inherits all methods from PhaseBuilderBase. + Additional method overrides and new methods specific to the climb phase are included. + """ + default_name = 'cruise_phase' + default_ode_class = BreguetCruiseODESolution + + _meta_data_ = {} + _initial_guesses_meta_data_ = {} + + def __init__( + self, name=None, subsystem_options=None, user_options=None, initial_guesses=None, + ode_class=None, transcription=None, core_subsystems=None, + external_subsystems=None, meta_data=None + ): + super().__init__( + name=name, subsystem_options=subsystem_options, user_options=user_options, + initial_guesses=initial_guesses, ode_class=ode_class, transcription=transcription, + core_subsystems=core_subsystems, is_analytic_phase=True, + ) + + def build_phase(self, aviary_options: AviaryValues = None): + """ + Return a new climb phase for analysis using these constraints. + + If ode_class is None, ClimbODE is used as the default. + + Parameters + ---------- + aviary_options : AviaryValues + Collection of Aircraft/Mission specific options + + Returns + ------- + dymos.Phase + """ + phase = super().build_phase(aviary_options) + + # Custom configurations for the climb phase + user_options = self.user_options + + mach_cruise = user_options.get_val('mach_cruise') + alt_cruise, alt_units = user_options.get_item('alt_cruise') + + # Time here is really the independent variable through which we are integrating. + # In the case of the Breguet Range ODE, it's mass. + # We rely on mass being monotonically non-increasing across the phase. + phase.set_time_options( + name='mass', + fix_initial=False, + fix_duration=False, + units="lbm", + targets="mass", + initial_bounds=(0., 1.e7), + initial_ref=100.e3, + duration_bounds=(-1.e7, -1), + duration_ref=50000, + ) + + phase.add_parameter(Dynamic.Mission.ALTITUDE, opt=False, + val=alt_cruise, units=alt_units) + phase.add_parameter(Dynamic.Mission.MACH, opt=False, + val=mach_cruise) + phase.add_parameter("initial_distance", opt=False, val=0.0, + units="NM", static_target=True) + phase.add_parameter("initial_time", opt=False, val=0.0, + units="s", static_target=True) + + phase.add_timeseries_output("time", units="s") + + return phase + + +# Adding metadata for the CruisePhase +CruisePhase._add_meta_data('alt_cruise', val=0) +CruisePhase._add_meta_data('mach_cruise', val=0) + +CruisePhase._add_initial_guess_meta_data( + InitialGuessTime(), + desc='initial guess for initial time and duration specified as a tuple') + +CruisePhase._add_initial_guess_meta_data( + InitialGuessState('mass'), + desc='initial guess for mass') + +CruisePhase._add_initial_guess_meta_data( + InitialGuessState('initial_distance'), + desc='initial guess for initial_distance') + +CruisePhase._add_initial_guess_meta_data( + InitialGuessState('initial_time'), + desc='initial guess for initial_time') + +CruisePhase._add_initial_guess_meta_data( + InitialGuessState('altitude'), + desc='initial guess for altitude') + +CruisePhase._add_initial_guess_meta_data( + InitialGuessState('mach'), + desc='initial guess for mach') diff --git a/aviary/mission/gasp_based/phases/desc_phase.py b/aviary/mission/gasp_based/phases/desc_phase.py deleted file mode 100644 index 6f2eab9aa..000000000 --- a/aviary/mission/gasp_based/phases/desc_phase.py +++ /dev/null @@ -1,135 +0,0 @@ -import dymos as dm - -from aviary.mission.gasp_based.ode.descent_ode import DescentODE -from aviary.variable_info.enums import SpeedType -from aviary.variable_info.variables import Dynamic - - -def get_descent( - ode_args=None, - transcription=None, - fix_initial=False, - input_initial=False, - EAS_limit=0, - mach_cruise=0, - input_speed_type=SpeedType.MACH, - final_alt=0, - time_initial_bounds=(0, 0), - time_initial_ref=1, - duration_bounds=(0, 0), - duration_ref=1, - alt_lower=0, - alt_upper=0, - alt_ref=1, - alt_ref0=0, - alt_defect_ref=None, - alt_constraint_ref=None, - mass_lower=0, - mass_upper=0, - mass_ref=1, - mass_ref0=0, - mass_defect_ref=None, - distance_lower=0, - distance_upper=0, - distance_ref=1, - distance_ref0=0, - distance_defect_ref=None, -): - ode_init_kwargs = dict( - EAS_limit=EAS_limit, - input_speed_type=input_speed_type, - mach_cruise=mach_cruise, - ) - if ode_args: - ode_init_kwargs.update(ode_args) - - desc = dm.Phase( - ode_class=DescentODE, - transcription=transcription, - ode_init_kwargs=ode_init_kwargs, - ) - - desc.set_time_options( - initial_bounds=time_initial_bounds, - duration_bounds=duration_bounds, - fix_initial=fix_initial, - input_initial=input_initial, - units="s", - duration_ref=duration_ref, - initial_ref=time_initial_ref, - ) - - desc.add_state( - Dynamic.Mission.ALTITUDE, - fix_initial=True, - fix_final=False, - lower=alt_lower, - upper=alt_upper, - units="ft", - rate_source=Dynamic.Mission.ALTITUDE_RATE, - targets=Dynamic.Mission.ALTITUDE, - ref=alt_ref, - ref0=alt_ref0, - defect_ref=alt_defect_ref, - ) - - if fix_initial and input_initial: - raise ValueError( - "ERROR in desc_phase: fix_initial and input_initial are both True" - ) - - desc.add_state( - Dynamic.Mission.MASS, - fix_initial=fix_initial, - input_initial=input_initial, - fix_final=False, - lower=mass_lower, - upper=mass_upper, - units="lbm", - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - targets=Dynamic.Mission.MASS, - ref=mass_ref, - ref0=mass_ref0, - defect_ref=mass_defect_ref, - ) - - desc.add_state( - Dynamic.Mission.DISTANCE, - fix_initial=fix_initial, - input_initial=input_initial, - fix_final=False, - lower=distance_lower, - upper=distance_upper, - units="NM", - rate_source="distance_rate", - ref=distance_ref, - ref0=distance_ref0, - defect_ref=distance_defect_ref, - ) - - desc.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, - loc="final", - equals=final_alt, - units="ft", - ref=alt_constraint_ref) - - if input_speed_type is SpeedType.EAS: - desc.add_parameter("EAS", opt=False, units="kn", val=EAS_limit) - - desc.add_timeseries_output( - Dynamic.Mission.MACH, - output_name=Dynamic.Mission.MACH, - units="unitless") - desc.add_timeseries_output("EAS", output_name="EAS", units="kn") - desc.add_timeseries_output("TAS", output_name="TAS", units="kn") - desc.add_timeseries_output(Dynamic.Mission.FLIGHT_PATH_ANGLE, - output_name=Dynamic.Mission.FLIGHT_PATH_ANGLE, units="deg") - desc.add_timeseries_output("alpha", output_name="alpha", units="deg") - desc.add_timeseries_output("theta", output_name="theta", units="deg") - desc.add_timeseries_output("aero.CL", output_name="CL", units="unitless") - desc.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") - desc.add_timeseries_output("aero.CD", output_name="CD", units="unitless") - - return desc diff --git a/aviary/mission/gasp_based/phases/descent_phase.py b/aviary/mission/gasp_based/phases/descent_phase.py new file mode 100644 index 000000000..e5a999d91 --- /dev/null +++ b/aviary/mission/gasp_based/phases/descent_phase.py @@ -0,0 +1,122 @@ +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessState, InitialGuessTime, InitialGuessControl +from aviary.utils.aviary_values import AviaryValues +from aviary.variable_info.variables import Dynamic +from aviary.variable_info.enums import SpeedType +from aviary.mission.gasp_based.ode.descent_ode import DescentODE + + +class DescentPhase(PhaseBuilderBase): + default_name = 'descent_phase' + default_ode_class = DescentODE + + _meta_data_ = {} + _initial_guesses_meta_data_ = {} + + def build_phase(self, aviary_options: AviaryValues = None): + phase = self.phase = super().build_phase(aviary_options) + + # Retrieve user options values + user_options = self.user_options + duration_bounds = user_options.get_val('duration_bounds', units='s') + fix_initial = user_options.get_val('fix_initial') + input_initial = user_options.get_val('input_initial') + duration_ref = user_options.get_val('duration_ref', units='s') + input_speed_type = user_options.get_val('input_speed_type') + EAS_limit = user_options.get_val('EAS_limit', units='kn') + + # Time options + phase.set_time_options( + duration_bounds=duration_bounds, + fix_initial=fix_initial, + input_initial=input_initial, + units="s", + duration_ref=duration_ref, + ) + + # Add states + self.add_altitude_state(user_options) + + self.add_mass_state(user_options) + + self.add_distance_state(user_options) + + # Add boundary constraint + self.add_altitude_constraint(user_options) + + # Add parameter if necessary + if input_speed_type == SpeedType.EAS: + phase.add_parameter("EAS", opt=False, units="kn", val=EAS_limit) + + # Add timeseries outputs + phase.add_timeseries_output( + Dynamic.Mission.MACH, output_name=Dynamic.Mission.MACH, units="unitless") + phase.add_timeseries_output("EAS", output_name="EAS", units="kn") + phase.add_timeseries_output("TAS", output_name="TAS", units="kn") + phase.add_timeseries_output(Dynamic.Mission.FLIGHT_PATH_ANGLE, + output_name=Dynamic.Mission.FLIGHT_PATH_ANGLE, units="deg") + phase.add_timeseries_output("alpha", output_name="alpha", units="deg") + phase.add_timeseries_output("theta", output_name="theta", units="deg") + phase.add_timeseries_output("aero.CL", output_name="CL", units="unitless") + phase.add_timeseries_output( + Dynamic.Mission.THRUST_TOTAL, output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") + phase.add_timeseries_output("aero.CD", output_name="CD", units="unitless") + + return phase + + def _extra_ode_init_kwargs(self): + """ + Return extra kwargs required for initializing the ODE. + """ + # TODO: support external_subsystems and meta_data in the base class + return { + 'input_speed_type': self.user_options.get_val('input_speed_type'), + 'mach_cruise': self.user_options.get_val('mach_cruise'), + 'EAS_limit': self.user_options.get_val('EAS_limit', 'kn'), + } + + +# Adding metadata for the DescentPhase +DescentPhase._add_meta_data('fix_initial', val=False) +DescentPhase._add_meta_data('input_initial', val=False) +DescentPhase._add_meta_data('EAS_limit', val=0, units='kn') +DescentPhase._add_meta_data('mach_cruise', val=0) +DescentPhase._add_meta_data('input_speed_type', val=SpeedType.MACH) +DescentPhase._add_meta_data('final_altitude', val=0, units='ft') +DescentPhase._add_meta_data('duration_bounds', val=(0, 0), units='s') +DescentPhase._add_meta_data('duration_ref', val=1, units='s') +DescentPhase._add_meta_data('alt_lower', val=0, units='ft') +DescentPhase._add_meta_data('alt_upper', val=0, units='ft') +DescentPhase._add_meta_data('alt_ref', val=1, units='ft') +DescentPhase._add_meta_data('alt_ref0', val=0, units='ft') +DescentPhase._add_meta_data('alt_defect_ref', val=None, units='ft') +DescentPhase._add_meta_data('alt_constraint_ref', val=None, units='ft') +DescentPhase._add_meta_data('mass_lower', val=0, units='lbm') +DescentPhase._add_meta_data('mass_upper', val=0, units='lbm') +DescentPhase._add_meta_data('mass_ref', val=1, units='lbm') +DescentPhase._add_meta_data('mass_ref0', val=0, units='lbm') +DescentPhase._add_meta_data('mass_defect_ref', val=None, units='lbm') +DescentPhase._add_meta_data('distance_lower', val=0, units='NM') +DescentPhase._add_meta_data('distance_upper', val=0, units='NM') +DescentPhase._add_meta_data('distance_ref', val=1, units='NM') +DescentPhase._add_meta_data('distance_ref0', val=0, units='NM') +DescentPhase._add_meta_data('distance_defect_ref', val=None, units='NM') +DescentPhase._add_meta_data('num_segments', val=None, units='unitless') +DescentPhase._add_meta_data('order', val=None, units='unitless') + +# Adding initial guess metadata +DescentPhase._add_initial_guess_meta_data( + InitialGuessTime(), + desc='initial guess for time options') +DescentPhase._add_initial_guess_meta_data( + InitialGuessState('altitude'), + desc='initial guess for altitude state') +DescentPhase._add_initial_guess_meta_data( + InitialGuessState('mass'), + desc='initial guess for mass state') +DescentPhase._add_initial_guess_meta_data( + InitialGuessState('distance'), + desc='initial guess for distance state') +DescentPhase._add_initial_guess_meta_data( + InitialGuessControl('throttle'), + desc='initial guess for throttle') diff --git a/aviary/mission/gasp_based/phases/groundroll_phase.py b/aviary/mission/gasp_based/phases/groundroll_phase.py index aa5a61df2..45c5b5a91 100644 --- a/aviary/mission/gasp_based/phases/groundroll_phase.py +++ b/aviary/mission/gasp_based/phases/groundroll_phase.py @@ -1,106 +1,131 @@ -import dymos as dm - -from aviary.mission.gasp_based.ode.groundroll_ode import GroundrollODE +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessState, InitialGuessTime, InitialGuessControl +from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.variables import Dynamic +from aviary.mission.gasp_based.ode.groundroll_ode import GroundrollODE + + +class GroundrollPhase(PhaseBuilderBase): + default_name = 'groundroll_phase' + default_ode_class = GroundrollODE + + _meta_data_ = {} + _initial_guesses_meta_data_ = {} + + def build_phase(self, aviary_options: AviaryValues = None): + phase = self.phase = super().build_phase(aviary_options) + + # Retrieve user options values + user_options = self.user_options + # Add the necessary get_val calls for each parameter, e.g., + fix_initial = user_options.get_val('fix_initial') + fix_initial_mass = user_options.get_val('fix_initial_mass') + connect_initial_mass = user_options.get_val('connect_initial_mass') + mass_lower = user_options.get_val('mass_lower', units='lbm') + mass_upper = user_options.get_val('mass_upper', units='lbm') + mass_ref = user_options.get_val('mass_ref', units='lbm') + mass_ref0 = user_options.get_val('mass_ref0', units='lbm') + mass_defect_ref = user_options.get_val('mass_defect_ref', units='lbm') + distance_lower = user_options.get_val('distance_lower', units='ft') + distance_upper = user_options.get_val('distance_upper', units='ft') + distance_ref = user_options.get_val('distance_ref', units='ft') + distance_ref0 = user_options.get_val('distance_ref0', units='ft') + distance_defect_ref = user_options.get_val('distance_defect_ref', units='ft') + + self.set_time_options(user_options, targets='t_curr') + + # Add states + self.add_TAS_state(user_options) + + phase.add_state( + Dynamic.Mission.MASS, + fix_initial=fix_initial_mass, + input_initial=connect_initial_mass, + fix_final=False, + lower=mass_lower, + upper=mass_upper, + units="lbm", + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + ref=mass_ref, + defect_ref=mass_defect_ref, + ref0=mass_ref0, + ) + + phase.add_state( + Dynamic.Mission.DISTANCE, + fix_initial=fix_initial, + fix_final=False, + lower=distance_lower, + upper=distance_upper, + units="ft", + rate_source="distance_rate", + ref=distance_ref, + defect_ref=distance_defect_ref, + ref0=distance_ref0, + ) + + phase.add_parameter("t_init_gear", units="s", + static_target=True, opt=False, val=100) + phase.add_parameter("t_init_flaps", units="s", + static_target=True, opt=False, val=100) + + # boundary/path constraints + controls + # the final TAS is constrained externally to define the transition to the rotation + # phase + + phase.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") + + phase.add_timeseries_output("normal_force") + phase.add_timeseries_output(Dynamic.Mission.MACH) + phase.add_timeseries_output("EAS", units="kn") + + phase.add_timeseries_output(Dynamic.Mission.LIFT) + phase.add_timeseries_output("CL") + phase.add_timeseries_output("CD") + phase.add_timeseries_output("fuselage_pitch", output_name="theta", units="deg") + + return phase + +# Adding metadata for the GroundrollPhase +GroundrollPhase._add_meta_data('fix_initial', val=True) +GroundrollPhase._add_meta_data('fix_initial_mass', val=False) +GroundrollPhase._add_meta_data('connect_initial_mass', val=True) +GroundrollPhase._add_meta_data('duration_bounds', val=(1, 100), units='s') +GroundrollPhase._add_meta_data('duration_ref', val=1, units='s') +GroundrollPhase._add_meta_data('TAS_lower', val=0, units='kn') +GroundrollPhase._add_meta_data('TAS_upper', val=1000, units='kn') +GroundrollPhase._add_meta_data('TAS_ref', val=100, units='kn') +GroundrollPhase._add_meta_data('TAS_ref0', val=0, units='kn') +GroundrollPhase._add_meta_data('TAS_defect_ref', val=None, units='kn') +GroundrollPhase._add_meta_data('mass_lower', val=0, units='lbm') +GroundrollPhase._add_meta_data('mass_upper', val=200_000, units='lbm') +GroundrollPhase._add_meta_data('mass_ref', val=100_000, units='lbm') +GroundrollPhase._add_meta_data('mass_ref0', val=0, units='lbm') +GroundrollPhase._add_meta_data('mass_defect_ref', val=100, units='lbm') +GroundrollPhase._add_meta_data('distance_lower', val=0, units='ft') +GroundrollPhase._add_meta_data('distance_upper', val=4000, units='ft') +GroundrollPhase._add_meta_data('distance_ref', val=3000, units='ft') +GroundrollPhase._add_meta_data('distance_ref0', val=0, units='ft') +GroundrollPhase._add_meta_data('distance_defect_ref', val=3000, units='ft') +GroundrollPhase._add_meta_data('t_init_gear', val=100, units='s') +GroundrollPhase._add_meta_data('t_init_flaps', val=100, units='s') +GroundrollPhase._add_meta_data('num_segments', val=None, units='unitless') +GroundrollPhase._add_meta_data('order', val=None, units='unitless') -def get_groundroll( - ode_args=None, - transcription=None, - fix_initial=True, - fix_initial_mass=False, - connect_initial_mass=True, - duration_bounds=(1, 100), - duration_ref=1, - TAS_lower=0, - TAS_upper=1000, - TAS_ref=100, - TAS_ref0=0, - TAS_defect_ref=None, - mass_lower=0, - mass_upper=200_000, - mass_ref=100_000, - mass_ref0=0, - mass_defect_ref=100, - distance_lower=0, - distance_upper=4000, - distance_ref=3000, - distance_ref0=0, - distance_defect_ref=3000, -): - phase = dm.Phase( - ode_class=GroundrollODE, - ode_init_kwargs=ode_args, - transcription=transcription, - ) - - phase.set_time_options( - fix_initial=fix_initial, - fix_duration=False, - units="s", - targets="t_curr", - duration_bounds=duration_bounds, - duration_ref=duration_ref, - ) - - phase.add_state( - "TAS", - fix_initial=fix_initial, - fix_final=False, - lower=TAS_lower, - upper=TAS_upper, - units="kn", - rate_source="TAS_rate", - ref=TAS_ref, - defect_ref=TAS_defect_ref, - ref0=TAS_ref0, - ) - - phase.add_state( - Dynamic.Mission.MASS, - fix_initial=fix_initial_mass, - input_initial=connect_initial_mass, - fix_final=False, - lower=mass_lower, - upper=mass_upper, - units="lbm", - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - ref=mass_ref, - defect_ref=mass_defect_ref, - ref0=mass_ref0, - ) - - phase.add_state( - Dynamic.Mission.DISTANCE, - fix_initial=fix_initial, - fix_final=False, - lower=distance_lower, - upper=distance_upper, - units="ft", - rate_source="distance_rate", - ref=distance_ref, - defect_ref=distance_defect_ref, - ref0=distance_ref0, - ) - - phase.add_parameter("t_init_gear", units="s", - static_target=True, opt=False, val=100) - phase.add_parameter("t_init_flaps", units="s", - static_target=True, opt=False, val=100) - - # boundary/path constraints + controls - # the final TAS is constrained externally to define the transition to the rotation - # phase - - phase.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") - - phase.add_timeseries_output("normal_force") - phase.add_timeseries_output(Dynamic.Mission.MACH) - phase.add_timeseries_output("EAS", units="kn") - - phase.add_timeseries_output(Dynamic.Mission.LIFT) - phase.add_timeseries_output("CL") - phase.add_timeseries_output("CD") - phase.add_timeseries_output("fuselage_pitch", output_name="theta", units="deg") - - return phase +# Adding initial guess metadata +GroundrollPhase._add_initial_guess_meta_data( + InitialGuessTime(), + desc='initial guess for time options') +GroundrollPhase._add_initial_guess_meta_data( + InitialGuessState('TAS'), + desc='initial guess for true airspeed state') +GroundrollPhase._add_initial_guess_meta_data( + InitialGuessState('mass'), + desc='initial guess for mass state') +GroundrollPhase._add_initial_guess_meta_data( + InitialGuessState('distance'), + desc='initial guess for distance state') +GroundrollPhase._add_initial_guess_meta_data( + InitialGuessControl('throttle'), + desc='initial guess for throttle') diff --git a/aviary/mission/gasp_based/phases/rotation_phase.py b/aviary/mission/gasp_based/phases/rotation_phase.py index a0c4441db..3ef1e869e 100644 --- a/aviary/mission/gasp_based/phases/rotation_phase.py +++ b/aviary/mission/gasp_based/phases/rotation_phase.py @@ -1,132 +1,147 @@ -import dymos as dm import numpy as np -from aviary.mission.gasp_based.ode.rotation_ode import RotationODE +from aviary.mission.phase_builder_base import PhaseBuilderBase +from aviary.mission.initial_guess_builders import InitialGuessState, InitialGuessTime, InitialGuessControl +from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.variables import Dynamic +from aviary.mission.gasp_based.ode.rotation_ode import RotationODE + + +class RotationPhase(PhaseBuilderBase): + default_name = 'rotation_phase' + default_ode_class = RotationODE + + _meta_data_ = {} + _initial_guesses_meta_data_ = {} + + def build_phase(self, aviary_options: AviaryValues = None): + phase = self.phase = super().build_phase(aviary_options) + + # Retrieve user options values + user_options = self.user_options + fix_initial = user_options.get_val('fix_initial') + angle_lower = user_options.get_val('angle_lower', units='rad') + angle_upper = user_options.get_val('angle_upper', units='rad') + angle_ref = user_options.get_val('angle_ref', units='rad') + angle_ref0 = user_options.get_val('angle_ref0', units='rad') + angle_defect_ref = user_options.get_val('angle_defect_ref', units='rad') + distance_lower = user_options.get_val('distance_lower', units='ft') + distance_upper = user_options.get_val('distance_upper', units='ft') + distance_ref = user_options.get_val('distance_ref', units='ft') + distance_ref0 = user_options.get_val('distance_ref0', units='ft') + distance_defect_ref = user_options.get_val('distance_defect_ref', units='ft') + normal_ref = user_options.get_val('normal_ref', units='lbf') + normal_ref0 = user_options.get_val('normal_ref0', units='lbf') + + self.set_time_options(user_options, targets='t_curr') + + # Add states + phase.add_state( + "alpha", + fix_initial=True, + fix_final=False, + lower=angle_lower, + upper=angle_upper, + units="rad", + rate_source="alpha_rate", + ref=angle_ref, + ref0=angle_ref0, + defect_ref=angle_defect_ref, + ) + + self.add_TAS_state(user_options) + + self.add_mass_state(user_options) + + phase.add_state( + Dynamic.Mission.DISTANCE, + fix_initial=fix_initial, + fix_final=False, + lower=distance_lower, + upper=distance_upper, + units="ft", + rate_source="distance_rate", + ref=distance_ref, + ref0=distance_ref0, + defect_ref=distance_defect_ref, + ) + + # Add parameters + phase.add_parameter("t_init_gear", units="s", + static_target=True, opt=False, val=100) + phase.add_parameter("t_init_flaps", units="s", + static_target=True, opt=False, val=100) + + # Add boundary constraints + phase.add_boundary_constraint( + "normal_force", + loc="final", + equals=0, + units="lbf", + ref=normal_ref, + ref0=normal_ref0, + ) + + # Add timeseries outputs + phase.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") + phase.add_timeseries_output("normal_force") + phase.add_timeseries_output(Dynamic.Mission.MACH) + phase.add_timeseries_output("EAS", units="kn") + phase.add_timeseries_output(Dynamic.Mission.LIFT) + phase.add_timeseries_output("CL") + phase.add_timeseries_output("CD") + phase.add_timeseries_output("fuselage_pitch", output_name="theta", units="deg") + + return phase + +# Adding metadata for the RotationPhase +RotationPhase._add_meta_data('fix_initial', val=False) +RotationPhase._add_meta_data('duration_bounds', val=(1, 100), units='s') +RotationPhase._add_meta_data('duration_ref', val=1, units='s') +RotationPhase._add_meta_data('angle_lower', val=0.0, units='rad') # rad +RotationPhase._add_meta_data('angle_upper', val=25 * np.pi / 180, units='rad') # rad +RotationPhase._add_meta_data('angle_ref', val=1, units='rad') +RotationPhase._add_meta_data('angle_ref0', val=0, units='rad') +RotationPhase._add_meta_data('angle_defect_ref', val=0.01, units='rad') +RotationPhase._add_meta_data('TAS_lower', val=0, units='kn') +RotationPhase._add_meta_data('TAS_upper', val=1000, units='kn') +RotationPhase._add_meta_data('TAS_ref', val=100, units='kn') +RotationPhase._add_meta_data('TAS_ref0', val=0, units='kn') +RotationPhase._add_meta_data('TAS_defect_ref', val=None, units='kn') +RotationPhase._add_meta_data('mass_lower', val=0, units='lbm') +RotationPhase._add_meta_data('mass_upper', val=190_000, units='lbm') +RotationPhase._add_meta_data('mass_ref', val=100_000, units='lbm') +RotationPhase._add_meta_data('mass_ref0', val=0, units='lbm') +RotationPhase._add_meta_data('mass_defect_ref', val=None, units='lbm') +RotationPhase._add_meta_data('distance_lower', val=0, units='ft') +RotationPhase._add_meta_data('distance_upper', val=10.e3, units='ft') +RotationPhase._add_meta_data('distance_ref', val=3000, units='ft') +RotationPhase._add_meta_data('distance_ref0', val=0, units='ft') +RotationPhase._add_meta_data('distance_defect_ref', val=3000, units='ft') +RotationPhase._add_meta_data('normal_ref', val=1, units='lbf') +RotationPhase._add_meta_data('normal_ref0', val=0, units='lbf') +RotationPhase._add_meta_data('t_init_gear', val=100, units='s') +RotationPhase._add_meta_data('t_init_flaps', val=100, units='s') +RotationPhase._add_meta_data('num_segments', val=None, units='unitless') +RotationPhase._add_meta_data('order', val=None, units='unitless') -def get_rotation( - ode_args=None, - transcription=None, - fix_initial=False, - initial_ref=1, - duration_bounds=(1, 100), - duration_ref=1, - angle_lower=0.0, # rad - angle_upper=25 * np.pi / 180, # rad - angle_ref=1, - angle_ref0=0, - angle_defect_ref=0.01, - TAS_lower=0, - TAS_upper=1000, - TAS_ref=100, - TAS_ref0=0, - TAS_defect_ref=None, - mass_lower=0, - mass_upper=190_000, - mass_ref=100_000, - mass_ref0=0, - mass_defect_ref=None, - distance_lower=0, - distance_upper=10.e3, - distance_ref=3000, - distance_ref0=0, - distance_defect_ref=3000, - normal_ref=1, - normal_ref0=0, -): - phase = dm.Phase( - ode_class=RotationODE, - ode_init_kwargs=ode_args, - transcription=transcription, - ) - - phase.set_time_options( - fix_initial=fix_initial, - fix_duration=False, - units="s", - targets="t_curr", - initial_ref=initial_ref, - duration_bounds=duration_bounds, - duration_ref=duration_ref, - ) - - phase.add_state( - "alpha", - fix_initial=True, - fix_final=False, - lower=angle_lower, - upper=angle_upper, - units="rad", - rate_source="alpha_rate", - ref=angle_ref, - ref0=angle_ref0, - defect_ref=angle_defect_ref, - ) - - phase.add_state( - "TAS", - fix_initial=fix_initial, - fix_final=False, - lower=TAS_lower, - upper=TAS_upper, - units="kn", - rate_source="TAS_rate", - ref=TAS_ref, - ref0=TAS_ref0, - defect_ref=TAS_defect_ref, - ) - - phase.add_state( - Dynamic.Mission.MASS, - fix_initial=fix_initial, - fix_final=False, - lower=mass_lower, - upper=mass_upper, - units="lbm", - rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - ref=mass_ref, - ref0=mass_ref0, - defect_ref=mass_defect_ref, - ) - - phase.add_state( - Dynamic.Mission.DISTANCE, - fix_initial=fix_initial, - fix_final=False, - lower=distance_lower, - upper=distance_upper, - units="ft", - rate_source="distance_rate", - ref=distance_ref, - ref0=distance_ref0, - defect_ref=distance_defect_ref, - ) - - phase.add_parameter("t_init_gear", units="s", - static_target=True, opt=False, val=100) - phase.add_parameter("t_init_flaps", units="s", - static_target=True, opt=False, val=100) - - # boundary/path constraints + controls - phase.add_boundary_constraint( - "normal_force", - loc="final", - equals=0, - units="lbf", - ref=normal_ref, - ref0=normal_ref0, - ) - - phase.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") - - phase.add_timeseries_output("normal_force") - phase.add_timeseries_output(Dynamic.Mission.MACH) - phase.add_timeseries_output("EAS", units="kn") - - phase.add_timeseries_output(Dynamic.Mission.LIFT) - phase.add_timeseries_output("CL") - phase.add_timeseries_output("CD") - phase.add_timeseries_output("fuselage_pitch", output_name="theta", units="deg") - - return phase +# Adding initial guess metadata +RotationPhase._add_initial_guess_meta_data( + InitialGuessTime(), + desc='initial guess for time options') +RotationPhase._add_initial_guess_meta_data( + InitialGuessState('alpha'), + desc='initial guess for angle of attack state') +RotationPhase._add_initial_guess_meta_data( + InitialGuessState('TAS'), + desc='initial guess for true airspeed state') +RotationPhase._add_initial_guess_meta_data( + InitialGuessState('mass'), + desc='initial guess for mass state') +RotationPhase._add_initial_guess_meta_data( + InitialGuessState('distance'), + desc='initial guess for distance state') +RotationPhase._add_initial_guess_meta_data( + InitialGuessControl('throttle'), + desc='initial guess for throttle') diff --git a/aviary/mission/gasp_based/phases/run_phases/__init__.py b/aviary/mission/gasp_based/phases/run_phases/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/aviary/mission/gasp_based/phases/run_phases/run_accel.py b/aviary/mission/gasp_based/phases/run_phases/run_accel.py deleted file mode 100644 index ce26a879e..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_accel.py +++ /dev/null @@ -1,96 +0,0 @@ -import dymos as dm -import matplotlib.pyplot as plt -import openmdao.api as om -import numpy as np - -from aviary.mission.gasp_based.phases.accel_phase import get_accel -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Aircraft, Dynamic -from aviary.interface.default_phase_info.two_dof import default_mission_subsystems -from packaging import version - - -def run_accel(): - # Column-wise data from CSV - time_GASP = np.array([0.182, 0.188]) - distance_GASP = np.array([0.00, 1.57]) - weight_GASP = np.array([174974., 174878.]) - TAS_GASP = np.array([185., 252.]) - EAS_GASP = np.array([184., 250.]) - - time_GASP = time_GASP * 3600 - time_GASP = time_GASP - time_GASP[0] - - prob = om.Problem(model=om.Group()) - - prob.driver = om.pyOptSparseDriver() - prob.driver.options["optimizer"] = "SNOPT" - prob.driver.opt_settings["Major iterations limit"] = 100 - prob.driver.opt_settings["Major optimality tolerance"] = 5.0e-3 - prob.driver.opt_settings["Major feasibility tolerance"] = 1e-6 - prob.driver.opt_settings["iSumm"] = 6 - prob.driver.declare_coloring() - - transcription = dm.Radau(num_segments=15, order=4, compressed=False) - - ode_args = dict( - aviary_options=get_option_defaults(), - core_subsystems=default_mission_subsystems, - ) - - accel = get_accel( - ode_args=ode_args, - transcription=transcription, - alt=500, - EAS_constraint_eq=250, - fix_initial=True, - time_initial_bounds=(0, 0), - duration_bounds=(0, 36_000), - duration_ref=1000, - TAS_lower=0, - TAS_upper=1000, - TAS_ref=250, - TAS_ref0=150, - mass_lower=0, - mass_upper=None, - mass_ref=175_500, - distance_lower=0, - distance_upper=150, - distance_ref=1, - distance_defect_ref=1, - ) # min GASP time is 18 seconds, max is 25.2 - - accel.add_parameter( - Aircraft.Wing.AREA, opt=False, units="ft**2", val=1370.0, static_target=True - ) - - prob.model.add_subsystem(name='accel', subsys=accel) - accel.add_timeseries_output("EAS", output_name="EAS", units="kn") - accel.add_timeseries_output("TAS", output_name="TAS", units="kn") - accel.add_timeseries_output("mass", output_name="mass", units="lbm") - accel.add_timeseries_output("distance", output_name="distance", units="NM") - accel.timeseries_options['use_prefix'] = True - - accel.add_objective("time", loc="final") - prob.model.linear_solver.options["iprint"] = 0 - prob.model.nonlinear_solver.options["iprint"] = 0 - - prob.model.options["assembled_jac_type"] = "csc" - prob.model.linear_solver = om.DirectSolver(assemble_jac=True) - - prob.set_solver_print(level=0) - - prob.setup() - prob.set_val("accel.states:mass", accel.interp("mass", ys=[174974, 174878])) - prob.set_val("accel.states:TAS", accel.interp("TAS", ys=[185, 252])) - prob.set_val("accel.states:distance", accel.interp( - Dynamic.Mission.DISTANCE, ys=[0, 1.57])) - prob.set_val("accel.t_duration", 1000) - prob.set_val("accel.t_initial", 0) - - dm.run_problem(problem=prob, simulate=True) - print() - print("Done Running Model, Now Simulating") - print() - - return prob diff --git a/aviary/mission/gasp_based/phases/run_phases/run_ascent.py b/aviary/mission/gasp_based/phases/run_phases/run_ascent.py deleted file mode 100644 index fdd3c0384..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_ascent.py +++ /dev/null @@ -1,123 +0,0 @@ -import dymos as dm -import openmdao.api as om - -from aviary.mission.gasp_based.phases.ascent_phase import get_ascent -from aviary.mission.gasp_based.polynomial_fit import PolynomialFit -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Aircraft, Dynamic, Mission -from aviary.interface.default_phase_info.height_energy import default_mission_subsystems - - -def run_ascent(make_plots=False): - - solve_segments = False # changed from "forward" because got a singular matrix error - ascent_trans = dm.Radau( - num_segments=2, order=5, compressed=True, solve_segments=solve_segments - ) - ascent = get_ascent( - fix_initial=True, - transcription=ascent_trans, - TAS_ref=1, - alt_ref=1, - alt_defect_ref=1, - ode_args=get_option_defaults(), - core_subsystems=default_mission_subsystems - ) - - p = om.Problem() - traj = p.model.add_subsystem("traj", dm.Trajectory()) - traj.add_phase("ascent", ascent) - - p.model.promotes( - "traj", - inputs=[ - ("ascent.parameters:t_init_gear", "t_init_gear"), - ("ascent.parameters:t_init_flaps", "t_init_flaps"), - ( - "ascent.parameters:" + Mission.Design.GROSS_MASS, - Mission.Design.GROSS_MASS, - ), - ("ascent.t_duration", "t_duration"), - ], - ) - - ascent_tx = ascent.options["transcription"] - ascent_num_nodes = ascent_tx.grid_data.num_nodes - - p.model.add_subsystem( - "h_fit", - PolynomialFit(N_cp=ascent_num_nodes), - promotes_inputs=["t_init_gear", "t_init_flaps"], - ) - p.model.connect("traj.ascent.timeseries.time", "h_fit.time_cp") - p.model.connect("traj.ascent.timeseries.states:altitude", "h_fit.h_cp") - - # TODO: re-parameterize time to be 0-1 (i.e. add a component that offsets by t_initial/t_duration) - p.model.add_design_var("t_init_gear", lower=32.0, upper=45.0, units="s", ref=5.0) - p.model.add_constraint("h_fit.h_init_gear", equals=50.0, units="ft", ref=50.0) - - p.model.add_design_var("t_init_flaps", lower=45.0, upper=100.0, units="s", ref=50.0) - p.model.add_constraint("h_fit.h_init_flaps", equals=400.0, units="ft", ref=400.0) - - p.model.add_design_var("t_duration", lower=5, upper=100, units="s") - p.model.set_input_defaults("t_duration", val=25, units="s") - - p.driver = om.pyOptSparseDriver() - p.driver.options["optimizer"] = "SNOPT" - p.driver.opt_settings["iSumm"] = 6 - # p.driver.options['debug_print'] = ['desvars'] - p.driver.declare_coloring() - - # p.model.add_objective("traj.ascent.timeseries.states:mass", index=-1, ref=1.4e5, ref0=1.3e5) - ascent.add_objective("time", loc="final") - p.set_solver_print(level=-1) - - ascent.add_parameter( - Aircraft.Wing.AREA, - opt=False, - units="ft**2", - val=1370, - static_target=True, - targets=[Aircraft.Wing.AREA], - ) - p.model.set_input_defaults(Mission.Design.GROSS_MASS, val=174000, units="lbm") - p.setup() - - p.set_val( - "traj.ascent.states:altitude", - ascent.interp(Dynamic.Mission.ALTITUDE, ys=[0, 100, 500], xs=[0, 1, 10]), - units="ft", - ) - p.set_val("traj.ascent.states:flight_path_angle", 0.2, units="rad") - - p.set_val( - "traj.ascent.states:TAS", ascent.interp("TAS", [153.3196491, 500]), units="kn" - ) - p.set_val( - "traj.ascent.states:mass", - ascent.interp("mass", [174963.74211336, 174000]), - units="lbm", - ) - p.set_val( - "traj.ascent.states:distance", - ascent.interp(Dynamic.Mission.DISTANCE, [4330.83393029, 5000]), - units="ft", - ) - p.set_val("traj.ascent.t_initial", 31.2) - # p.set_val("traj.ascent.t_duration", 10.0) - - p.set_val("t_init_gear", 40.0) # initial guess - p.set_val("t_init_flaps", 47.5) # initial guess - - p.set_val( - "traj.ascent.controls:alpha", - ascent.interpolate(ys=[1, 1], nodes="control_input"), - units="deg", - ) - - dm.run_problem(p, simulate=True, make_plots=make_plots) - - print("t_init_gear (s)", p.get_val("t_init_gear", units="s")) - print("t_init_flaps (s)", p.get_val("t_init_flaps", units="s")) - - return p diff --git a/aviary/mission/gasp_based/phases/run_phases/run_climb.py b/aviary/mission/gasp_based/phases/run_phases/run_climb.py deleted file mode 100644 index 90d3a5376..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_climb.py +++ /dev/null @@ -1,277 +0,0 @@ -import argparse -import os - -import dymos as dm -import matplotlib.pyplot as plt -import openmdao.api as om -import pandas as pd - -from aviary.mission.gasp_based.ode.params import ParamPort -from aviary.mission.gasp_based.phases.climb_phase import get_climb -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Dynamic - -thisdir = os.path.dirname(__file__) -def_outdir = os.path.join(thisdir, "output") - -gaspdata_dir = os.path.join(thisdir, "..", "..", "problem", - "large_single_aisle_validation_data") - -theta_max = 15 -TAS_violation_max = 0 -use_surrogate = True - -ode_args = {} - -# name: (gaspname, path, units) -varinfo = { - "time": ("TIME", "time", "s"), - Dynamic.Mission.ALTITUDE: ("ALT", "states:altitude", "ft"), - "mass": ("MASS", "states:mass", "lbm"), - Dynamic.Mission.DISTANCE: ("RANGE", "states:distance", "NM"), - Dynamic.Mission.MACH: ("MACH", Dynamic.Mission.MACH, None), - "EAS": ("EAS", "EAS", "kn"), - "alpha": ("ALPHA", "alpha", "deg"), - Dynamic.Mission.FLIGHT_PATH_ANGLE: ("GAMMA", Dynamic.Mission.FLIGHT_PATH_ANGLE, "deg"), - "theta": ("FUSANGLE", "theta", "deg"), - "TAS_violation": (None, "TAS_violation", "kn"), - "CL": ("CL", "CL", None), - "CD": ("CD", "CD", None), - Dynamic.Mission.THRUST_TOTAL: ("THRUST", Dynamic.Mission.THRUST_TOTAL, "lbf"), -} - - -def setup_climb1(): - prob = om.Problem(model=om.Group(), name="main") - setup_driver(prob) - - ode_args["aviary_options"] = get_option_defaults() - - transcription = dm.Radau(num_segments=1, order=5, compressed=False) - climb1 = get_climb( - ode_args=ode_args, - transcription=transcription, - fix_initial=True, - alt_lower=0, - alt_upper=11000, - mass_lower=0, - mass_upper=200_000, - distance_lower=0, - distance_upper=300, - time_initial_bounds=(0, 0), - duration_bounds=(1.1, 36_000), - EAS_target=250, - mach_cruise=0.8, - target_mach=False, - final_alt=10000, - alt_ref=10000, - mass_ref=200000, - distance_ref=300, - ) - prob.model.add_subsystem(name="climb1", subsys=climb1) - - # add all params and promote them to prob.model level - ParamPort.promote_params(prob.model, phases=["climb1"]) - ParamPort.set_default_vals(prob.model) - - climb1.add_objective("time", loc="final") - - prob.model.options["assembled_jac_type"] = "csc" - prob.model.linear_solver = om.LinearRunOnce() - - prob.setup() - - prob.set_val("climb1.controls:throttle", - climb1.interp("throttle", ys=[1.0, 1.0])) - prob.set_val("climb1.states:mass", climb1.interp("mass", ys=[174878, 174219])) - prob.set_val( - "climb1.timeseries:altitude", climb1.interp( - Dynamic.Mission.ALTITUDE, ys=[ - 500, 10000])) - prob.set_val("climb1.states:distance", climb1.interp( - Dynamic.Mission.DISTANCE, ys=[2, 15])) - prob.set_val("climb1.t_duration", 1000) - prob.set_val("climb1.t_initial", 0) - - return prob - - -def setup_climb2(): - prob = om.Problem(model=om.Group(), name="main") - - setup_driver(prob) - - ode_args["aviary_options"] = get_option_defaults() - - transcription = dm.Radau(num_segments=3, order=3, compressed=False) - climb2 = get_climb( - ode_args=ode_args, - transcription=transcription, - fix_initial=True, - alt_lower=9000, - alt_upper=60000, - mass_lower=0, - mass_upper=200_000, - distance_lower=0, - distance_upper=300, - time_initial_bounds=(0, 0), - duration_bounds=(2, 36_000), - EAS_target=270, - mach_cruise=0.8, - target_mach=True, - final_alt=37500, - alt_ref=40000, - mass_ref=200000, - distance_ref=300, - required_available_climb_rate=300, - ) - prob.model.add_subsystem(name="climb2", subsys=climb2) - - # fixed initial mass, so max final mass is equivalent to min fuel burn - climb2.add_objective("mass", loc="final", ref=-1) - - # add all params and promote them to prob.model level - ParamPort.promote_params(prob.model, phases=["climb2"]) - ParamPort.set_default_vals(prob.model) - - prob.model.options["assembled_jac_type"] = "csc" - prob.model.linear_solver = om.LinearRunOnce() - - prob.setup() - prob.set_val("climb2.states:mass", climb2.interp("mass", ys=[174219, 171481])) - prob.set_val("climb2.controls:throttle", - climb2.interp("throttle", ys=[1.0, 1.0])) - prob.set_val( - "climb2.states:altitude", climb2.interp( - Dynamic.Mission.ALTITUDE, ys=[ - 10000, 37500])) - prob.set_val("climb2.states:distance", climb2.interp( - Dynamic.Mission.DISTANCE, ys=[15, 154])) - prob.set_val("climb2.t_duration", 1500) - prob.set_val("climb2.t_initial", 0) - - return prob - - -def setup_driver(prob): - prob.driver = om.pyOptSparseDriver() - prob.driver.options["optimizer"] = "SNOPT" - prob.driver.opt_settings["Major iterations limit"] = 100 - prob.driver.opt_settings["Major optimality tolerance"] = 5.0e-3 - prob.driver.opt_settings["Major feasibility tolerance"] = 1e-6 - prob.driver.opt_settings["iSumm"] = 6 - prob.driver.declare_coloring() - prob.set_solver_print(level=0) - - -def make_recorder_filepaths(phasename, outdir): - return ( - os.path.join(outdir, f"{phasename}_prob.sql"), - os.path.join(outdir, f"{phasename}_sim.sql"), - ) - - -def run_phase(phasename, prob, outdir=def_outdir): - os.makedirs(outdir, exist_ok=True) - prob_rec_fp = make_recorder_filepaths(phasename, outdir)[0] - - recorder = om.SqliteRecorder(prob_rec_fp) - prob.driver.add_recorder(recorder) - prob.driver.recording_options["includes"] = ["*timeseries*"] - prob.add_recorder(recorder) - - print("\nRunning driver...\n") - prob.run_driver() - - prob.record("final") - prob.cleanup() - - return prob - - -def sim_phase(phasename, prob, outdir=def_outdir): - os.makedirs(outdir, exist_ok=True) - sim_rec_fp = make_recorder_filepaths(phasename, outdir)[1] - - print("\nSimulating...\n") - sys = getattr(prob.model, phasename) - prob.set_solver_print(level=-1) - sys.rhs_all.prop.set_solver_print(level=-1, depth=1e99) - simprob = sys.simulate(atol=1e-6, rtol=1e-6, record_file=sim_rec_fp) - - return simprob - - -def load_phase(phasename, outdir=def_outdir): - for fp in make_recorder_filepaths(phasename, outdir): - cr = om.CaseReader(fp) - yield cr.get_case("final") - - -def load_phase_nosim(phasename, outdir=def_outdir): - fp = make_recorder_filepaths(phasename, outdir)[0] - cr = om.CaseReader(fp) - return cr.get_case("final") - - -def gen_plots(phasename, probdata, simdata, plot_dir, show=False): - gaspdata = pd.read_csv(os.path.join(gaspdata_dir, f"{phasename}_data.csv")) - # gasp data in hours, convert to seconds, reference to 0 - gaspdata["TIME"] = 3600 * (gaspdata["TIME"] - gaspdata["TIME"][0]) - - def gen_plot(title, varnames): - fig, axs = plt.subplots(len(varnames), 1, sharex=True) - fig.suptitle(title) - - if len(varnames) == 1: - axs = [axs] - - gaspname, path, units = varinfo["time"] - path = f"{phasename}.timeseries.{path}" - t_solved = probdata.get_val(path, units=units) - if simdata: - t_sim = simdata.get_val(path, units=units) - t_gasp = gaspdata[gaspname] - - for ax, varname in zip(axs, varnames): - gaspname, path, units = varinfo[varname] - path = f"{phasename}.timeseries.{path}" - ax.plot(t_solved, probdata.get_val(path, units=units), "bo", label="opt") - if simdata: - ax.plot(t_sim, simdata.get_val(path, units=units), "b--", label="sim") - if gaspname is not None: - ax.plot(t_gasp, gaspdata[gaspname], "k-", label="GASP") - ylabel = f"{varname}" - if units: - ylabel += f" ({units})" - ax.set_ylabel(ylabel) - - axs[0].legend() - axs[-1].set_xlabel("time (s)") - - plt.tight_layout() - - return fig - - gen_plot("States", [Dynamic.Mission.ALTITUDE, "mass", Dynamic.Mission.DISTANCE]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_states.pdf")) - - gen_plot("Speeds", [Dynamic.Mission.MACH, "EAS"]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_speeds.pdf")) - - gen_plot("Angles", ["alpha", Dynamic.Mission.FLIGHT_PATH_ANGLE]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_angles.pdf")) - - fig = gen_plot("Constraints", ["TAS_violation", "theta"]) - fig.axes[0].axhline(theta_max, color="r", linestyle=":") - fig.axes[1].axhline(TAS_violation_max, color="r", linestyle=":") - plt.savefig(os.path.join(plot_dir, f"{phasename}_constraints.pdf")) - - gen_plot("Aero", ["CL", "CD"]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_aero.pdf")) - - gen_plot("Thrust", [Dynamic.Mission.THRUST_TOTAL]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_thrust.pdf")) - - if show: - plt.show() diff --git a/aviary/mission/gasp_based/phases/run_phases/run_climb_test_FLOPS_prop.py b/aviary/mission/gasp_based/phases/run_phases/run_climb_test_FLOPS_prop.py deleted file mode 100644 index d9197c50b..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_climb_test_FLOPS_prop.py +++ /dev/null @@ -1,297 +0,0 @@ -import os - -import dymos as dm -import matplotlib.pyplot as plt -import openmdao.api as om -import pandas as pd - -from aviary.mission.gasp_based.ode.params import ParamPort -from aviary.mission.gasp_based.phases.climb_phase import get_climb -from aviary.subsystems.propulsion.engine_deck import EngineDeck -from aviary.variable_info.options import get_option_defaults -from aviary.utils.preprocessors import preprocess_propulsion -from aviary.utils.functions import get_path -from aviary.variable_info.variables import Dynamic - -thisdir = os.path.dirname(__file__) -def_outdir = os.path.join(thisdir, "output") - -gaspdata_dir = os.path.join(thisdir, "..", "..", "problem", - "large_single_aisle_validation_data") - -theta_max = 15 -TAS_violation_max = 0 -use_surrogate = False - -ode_args = {} - -# name: (gaspname, path, units) -varinfo = { - "time": ("TIME", "time", "s"), - Dynamic.Mission.ALTITUDE: ("ALT", "states:altitude", "ft"), - "mass": ("MASS", "states:mass", "lbm"), - Dynamic.Mission.DISTANCE: ("RANGE", "states:distance", "NM"), - Dynamic.Mission.MACH: ("MACH", Dynamic.Mission.MACH, None), - "EAS": ("EAS", "EAS", "kn"), - "alpha": ("ALPHA", "alpha", "deg"), - Dynamic.Mission.FLIGHT_PATH_ANGLE: ("GAMMA", Dynamic.Mission.FLIGHT_PATH_ANGLE, "deg"), - "theta": ("FUSANGLE", "theta", "deg"), - "TAS_violation": (None, "TAS_violation", "kn"), - "CL": ("CL", "CL", None), - "CD": ("CD", "CD", None), - Dynamic.Mission.THRUST_TOTAL: ("THRUST", Dynamic.Mission.THRUST_TOTAL, "lbf"), -} - - -def setup_climb1(): - prob = om.Problem(model=om.Group(), name="main") - setup_driver(prob) - - filename = 'models/engines/turbofan_28k.deck' - filename = get_path(filename) - option_defaults = get_option_defaults() - engine = EngineDeck(name='engine', - options=option_defaults - ) - preprocess_propulsion(option_defaults, [engine]) - - ode_args["aviary_options"] = option_defaults - - transcription = dm.Radau(num_segments=1, order=5, compressed=False) - climb1 = get_climb( - ode_args=ode_args, - transcription=transcription, - fix_initial=True, - alt_lower=0, - alt_upper=11000, - mass_lower=0, - mass_upper=200_000, - mass_ref=175.e3, - mass_ref0=174.e3, - distance_lower=0, - distance_upper=300, - time_initial_bounds=(0, 0), - duration_bounds=(1.1, 36_000), - duration_ref=1000., - EAS_target=250, - mach_cruise=0.8, - target_mach=False, - final_alt=10.e3, - alt_ref=10.e3, - distance_ref=300, - ) - prob.model.add_subsystem(name="climb1", subsys=climb1) - - # add all params and promote them to prob.model level - # TODO: paramport - ParamPort.promote_params(prob.model, phases=["climb1"]) - ParamPort.set_default_vals(prob.model) - - climb1.add_objective("time", loc="final", ref=1000.) - # climb1.add_design_var("rhs_all.throttle", lower=0., upper=1.) - - prob.model.options["assembled_jac_type"] = "csc" - prob.model.linear_solver = om.LinearRunOnce() - - prob.setup() - - prob.set_val("climb1.states:mass", climb1.interp("mass", ys=[174878, 174219])) - prob.set_val( - "climb1.states:altitude", climb1.interp( - Dynamic.Mission.ALTITUDE, ys=[ - 500, 10000])) - # prob.set_val( - # "climb1.rhs_all.throttle", climb1.interp( - # Dynamic.Mission.ALTITUDE, ys=[ - # 0.40, 1.0])) - prob.set_val("climb1.states:distance", climb1.interp( - Dynamic.Mission.DISTANCE, ys=[2, 15])) - prob.set_val("climb1.t_duration", 1000) - prob.set_val("climb1.t_initial", 0) - - return prob - - -def setup_climb2(): - prob = om.Problem(model=om.Group(), name="main") - - setup_driver(prob) - - ode_args["aviary_options"] = get_option_defaults() - - transcription = dm.Radau(num_segments=3, order=3, compressed=False) - climb2 = get_climb( - ode_args=ode_args, - transcription=transcription, - fix_initial=True, - alt_lower=9000, - alt_upper=60000, - mass_lower=0, - mass_upper=200_000, - distance_lower=0, - distance_upper=300, - time_initial_bounds=(0, 0), - duration_bounds=(2, 36_000), - EAS_target=270, - mach_cruise=0.8, - target_mach=True, - final_alt=37500, - alt_ref=40000, - mass_ref=200000, - distance_ref=300, - required_available_climb_rate=300, - ) - prob.model.add_subsystem(name="climb2", subsys=climb2) - - # fixed initial mass, so max final mass is equivalent to min fuel burn - climb2.add_objective("mass", loc="final", ref=-1) - - # add all params and promote them to prob.model level - ParamPort.promote_params(prob.model, phases=["climb2"]) - ParamPort.set_default_vals(prob.model) - - prob.model.options["assembled_jac_type"] = "csc" - prob.model.linear_solver = om.LinearRunOnce() - - prob.setup() - prob.set_val("climb2.states:mass", climb2.interp("mass", ys=[174219, 171481])) - prob.set_val( - "climb2.states:altitude", climb2.interp( - Dynamic.Mission.ALTITUDE, ys=[ - 10000, 37500])) - prob.set_val("climb2.states:distance", climb2.interp( - Dynamic.Mission.DISTANCE, ys=[15, 154])) - prob.set_val("climb2.t_duration", 1500) - prob.set_val("climb2.t_initial", 0) - - return prob - - -def setup_driver(prob): - prob.driver = om.pyOptSparseDriver() - prob.driver.options["optimizer"] = "SNOPT" - prob.driver.opt_settings["Major iterations limit"] = 50 - prob.driver.opt_settings["Major optimality tolerance"] = 5.0e-3 - prob.driver.opt_settings["Major feasibility tolerance"] = 1e-6 - prob.driver.opt_settings["iSumm"] = 6 - prob.driver.declare_coloring() - prob.set_solver_print(level=0) - - -def make_recorder_filepaths(phasename, outdir): - return ( - os.path.join(outdir, f"{phasename}_prob.sql"), - os.path.join(outdir, f"{phasename}_sim.sql"), - ) - - -def run_phase(phasename, prob, outdir=def_outdir): - os.makedirs(outdir, exist_ok=True) - prob_rec_fp = make_recorder_filepaths(phasename, outdir)[0] - - recorder = om.SqliteRecorder(prob_rec_fp) - prob.driver.add_recorder(recorder) - prob.driver.recording_options["includes"] = ["*timeseries*"] - prob.add_recorder(recorder) - - print("\nRunning driver...\n") - prob.run_driver() - - prob.record("final") - prob.cleanup() - - return prob - - -def sim_phase(phasename, prob, outdir=def_outdir): - os.makedirs(outdir, exist_ok=True) - sim_rec_fp = make_recorder_filepaths(phasename, outdir)[1] - - print("\nSimulating...\n") - sys = getattr(prob.model, phasename) - prob.set_solver_print(level=-1) - sys.rhs_all.prop.set_solver_print(level=-1, depth=1e99) - simprob = sys.simulate(atol=1e-6, rtol=1e-6, record_file=sim_rec_fp) - - return simprob - - -def load_phase(phasename, outdir=def_outdir): - for fp in make_recorder_filepaths(phasename, outdir): - cr = om.CaseReader(fp) - yield cr.get_case("final") - - -def load_phase_nosim(phasename, outdir=def_outdir): - fp = make_recorder_filepaths(phasename, outdir)[0] - cr = om.CaseReader(fp) - return cr.get_case("final") - - -def gen_plots(phasename, probdata, simdata, plot_dir, show=False): - gaspdata = pd.read_csv(os.path.join(gaspdata_dir, f"{phasename}_data.csv")) - # gasp data in hours, convert to seconds, reference to 0 - gaspdata["TIME"] = 3600 * (gaspdata["TIME"] - gaspdata["TIME"][0]) - - def gen_plot(title, varnames): - fig, axs = plt.subplots(len(varnames), 1, sharex=True) - fig.suptitle(title) - - if len(varnames) == 1: - axs = [axs] - - gaspname, path, units = varinfo["time"] - path = f"{phasename}.timeseries.{path}" - t_solved = probdata.get_val(path, units=units) - if simdata: - t_sim = simdata.get_val(path, units=units) - t_gasp = gaspdata[gaspname] - - for ax, varname in zip(axs, varnames): - gaspname, path, units = varinfo[varname] - path = f"{phasename}.timeseries.{path}" - ax.plot(t_solved, probdata.get_val(path, units=units), "bo", label="opt") - if simdata: - ax.plot(t_sim, simdata.get_val(path, units=units), "b--", label="sim") - if gaspname is not None: - ax.plot(t_gasp, gaspdata[gaspname], "k-", label="GASP") - ylabel = f"{varname}" - if units: - ylabel += f" ({units})" - ax.set_ylabel(ylabel) - - axs[0].legend() - axs[-1].set_xlabel("time (s)") - - plt.tight_layout() - - return fig - - gen_plot("States", [Dynamic.Mission.ALTITUDE, "mass", Dynamic.Mission.DISTANCE]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_states.pdf")) - - gen_plot("Speeds", [Dynamic.Mission.MACH, "EAS"]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_speeds.pdf")) - - gen_plot("Angles", ["alpha", Dynamic.Mission.FLIGHT_PATH_ANGLE]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_angles.pdf")) - - fig = gen_plot("Constraints", ["TAS_violation", "theta"]) - fig.axes[0].axhline(theta_max, color="r", linestyle=":") - fig.axes[1].axhline(TAS_violation_max, color="r", linestyle=":") - plt.savefig(os.path.join(plot_dir, f"{phasename}_constraints.pdf")) - - gen_plot("Aero", ["CL", "CD"]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_aero.pdf")) - - gen_plot("Thrust", [Dynamic.Mission.THRUST_TOTAL]) - plt.savefig(os.path.join(plot_dir, f"{phasename}_thrust.pdf")) - - if show: - plt.show() - - -if __name__ == "__main__": - climb1 = setup_climb1() - setup_driver(climb1) - run_phase('climb1', climb1) diff --git a/aviary/mission/gasp_based/phases/run_phases/run_conventional_problem.py b/aviary/mission/gasp_based/phases/run_phases/run_conventional_problem.py deleted file mode 100644 index 603c5bc95..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_conventional_problem.py +++ /dev/null @@ -1,1255 +0,0 @@ -import os - -import dymos as dm -import numpy as np -import openmdao.api as om -from packaging import version - -if version.parse(dm.__version__) <= version.parse("1.6.1"): - updated_dymos = False -else: - updated_dymos = True - -from aviary.constants import RHO_SEA_LEVEL_ENGLISH -from aviary.mission.gasp_based.ode.breguet_cruise_ode import \ - BreguetCruiseODESolution -from aviary.mission.gasp_based.ode.flight_path_ode import FlightPathODE -from aviary.mission.gasp_based.ode.params import ParamPort -from aviary.mission.gasp_based.phases.v_rotate_comp import VRotateComp -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Aircraft, Dynamic - -TARGET_RANGE = 3000 -CRUISE_ALT = 37500. - - -def run_conventional_problem(make_plots=True, optimizer="IPOPT"): - - options = get_option_defaults() - - ode_args = dict( - aviary_options=options - ) - - # - # GROUNDROLL TO VROTATE - # - # Note: - # VRotate is computed in a post-trajectory analysis, and the difference between computed Vrot and - # the final speed in the groundroll phase and enforced downstream of the trajectory. - # - groundroll_trans = dm.Radau( - num_segments=5, order=3, compressed=True, solve_segments=False - ) - - groundroll = dm.Phase( - ode_class=FlightPathODE, - ode_init_kwargs=dict(ode_args, ground_roll=True), - transcription=groundroll_trans, - ) - - groundroll.set_time_options( - fix_initial=True, - fix_duration=False, - units="s", - targets="t_curr", - duration_bounds=(20, 100), - duration_ref=1) - - groundroll.set_state_options( - "TAS", - fix_initial=True, - fix_final=False, - lower=1.0E-6, - upper=1000, - ref=1, - defect_ref=1) - - groundroll.set_state_options( - "mass", - fix_initial=True, - fix_final=False, - lower=1, - upper=195_000, - ref=1000, - defect_ref=1000) - - groundroll.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=True, - fix_final=False, - lower=0, - upper=100_000, - ref=1, - defect_ref=1) - - groundroll.add_parameter(Dynamic.Mission.ALTITUDE, opt=False, - static_target=False, val=0.0, units='ft') - groundroll.add_parameter( - Dynamic.Mission.FLIGHT_PATH_ANGLE, opt=False, static_target=False, val=0.0, units='rad') - groundroll.add_parameter("t_init_gear", units="s", - static_target=True, opt=False, val=50) - groundroll.add_parameter("t_init_flaps", units="s", - static_target=True, opt=False, val=50) - - # - # ROTATION TO TAKEOFF - # - - rotation_trans = dm.Radau( - num_segments=5, order=3, compressed=True, solve_segments=False - ) - - rotation = dm.Phase( - ode_class=FlightPathODE, - # Use the standard ode_args and update it for ground_roll dynamics - ode_init_kwargs=dict(ode_args, ground_roll=True), - transcription=rotation_trans, - ) - - rotation.set_time_options( - fix_initial=False, - fix_duration=False, - units="s", - targets="t_curr", - duration_bounds=(1, 100), - duration_ref=1.0, - ) - - rotation.add_parameter(Dynamic.Mission.ALTITUDE, opt=False, - static_target=False, val=0.0, units='ft') - rotation.add_parameter(Dynamic.Mission.FLIGHT_PATH_ANGLE, - opt=False, static_target=False, val=0.0, units='rad') - rotation.add_parameter('alpha_rate', opt=False, - static_target=True, val=3.33, units='deg/s') - rotation.add_parameter("t_init_gear", units="s", - static_target=True, opt=False, val=50) - rotation.add_parameter("t_init_flaps", units="s", - static_target=True, opt=False, val=50) - - # State alpha is not defined in the ODE, taken from the parameter "alpha_rate" - rotation.add_state( - "alpha", - units="rad", - rate_source="alpha_rate", - fix_initial=False, - fix_final=False, - lower=0.0, - upper=np.radians(25), - ref=1.0) - - rotation.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=1.0, - upper=1000.0, - ref=100.0, - defect_ref=100.0) - - rotation.set_state_options( - "mass", - fix_initial=False, - fix_final=False, - lower=1.0, - upper=190_000.0, - ref=1000.0, - defect_ref=1000.0,) - - rotation.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - fix_final=False, - lower=0, - upper=10.e3, - ref=100, - defect_ref=100) - - # boundary/path constraints + controls - rotation.add_boundary_constraint( - "normal_force", loc="final", equals=0, units="lbf", ref=1000.0) - - rotation.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, units="lbf") - rotation.add_timeseries_output("normal_force") - rotation.add_timeseries_output(Dynamic.Mission.MACH) - rotation.add_timeseries_output("EAS", units="kn") - rotation.add_timeseries_output(Dynamic.Mission.LIFT) - rotation.add_timeseries_output("CL") - rotation.add_timeseries_output("CD") - rotation.add_timeseries_output("fuselage_pitch", output_name="theta", units="deg") - - # - # ASCENT TO GEAR RETRACTION - # - ascent0_tx = dm.Radau(num_segments=3, order=3, compressed=True, solve_segments=False) - ascent_to_gear_retract = dm.Phase( - ode_class=FlightPathODE, ode_init_kwargs=ode_args, transcription=ascent0_tx) - - ascent_to_gear_retract.set_time_options( - units="s", - targets="t_curr", - fix_initial=False, - fix_duration=False, - initial_bounds=(10, 50), - duration_bounds=(1, 50)) - - # Rate sources and units of states are set with tags in AscentEOM - ascent_to_gear_retract.set_state_options( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=False, - fix_final=False, - lower=0, - upper=0.5, - ref=1.0, - defect_ref=100.0) - - ascent_to_gear_retract.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=False, - fix_final=False, - lower=0, - upper=500, - ref=100, - defect_ref=10000) - - ascent_to_gear_retract.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=1, - upper=1000, - ref=100, - defect_ref=1000) - - ascent_to_gear_retract.set_state_options( - "mass", - fix_initial=False, - fix_final=False, - lower=1, - upper=None, - ref=175_000, - defect_ref=175_000) - - ascent_to_gear_retract.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - fix_final=False, - lower=0, - upper=15_000, - ref=5280, - defect_ref=5280) - - # Targets are not needed when there is a top-level ODE input with the same - # name as the parameter, state, or control - ascent_to_gear_retract.add_parameter( - "t_init_gear", units="s", static_target=True, opt=False, val=50) - ascent_to_gear_retract.add_parameter( - "t_init_flaps", units="s", static_target=True, opt=False, val=50) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are - # in degrees. - if updated_dymos: - ascent_to_gear_retract.add_control("alpha", - continuity_scaler=1.0E-2, - units="rad", - val=0.0, # continuity=True, rate_continuity=False, - opt=True, - lower=np.radians(-30), - upper=np.radians(30), - ref=0.01) - else: - ascent_to_gear_retract.add_control("alpha", - units="rad", - val=0.0, # continuity=True, rate_continuity=False, - opt=True, - lower=np.radians(-30), - upper=np.radians(30), - ref=0.01) - - # boundary/path constraints + controls - # Final altitude can be a linear constraint, since the final value of - # altitude is a design variable. - ascent_to_gear_retract.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, - loc="final", - equals=50, - units="ft", - ref=1.0, - ref0=0.0, - linear=False) - - # Load factor can be treated as a linear constraint as long as i_wing is - # not a design variable. - ascent_to_gear_retract.add_path_constraint( - "load_factor", - lower=0.0, - upper=1.10, - scaler=1.0E-6 / 0.001, - linear=False) - - ascent_to_gear_retract.add_path_constraint( - "fuselage_pitch", - constraint_name="theta", - lower=0, - upper=15, - units="deg", - scaler=1.0E-6 / 0.001, - linear=False) - - # - # ASCENT TO FLAP RETRACTION - # - ascent1_tx = dm.Radau(num_segments=5, order=3, - compressed=True, solve_segments=False) - ascent_to_flap_retract = dm.Phase( - ode_class=FlightPathODE, ode_init_kwargs=ode_args, transcription=ascent1_tx) - - ascent_to_flap_retract.set_time_options(units="s", targets="t_curr", - fix_initial=False, fix_duration=False, - initial_bounds=(1, 500), - duration_bounds=(0.5, 100)) - - # Rate sources and units of states are set with tags in AscentEOM - ascent_to_flap_retract.set_state_options( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=False, - fix_final=False, - lower=-0.2618, - upper=0.43633, - ref=0.43633, - defect_ref=0.43633) - - ascent_to_flap_retract.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=False, - fix_final=False, - lower=0, - upper=500, - ref=100, - defect_ref=100) - - ascent_to_flap_retract.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=1, - upper=1000, - ref=100, - defect_ref=100) - - ascent_to_flap_retract.set_state_options( - "mass", - fix_initial=False, - fix_final=False, - lower=1, - upper=None, - ref=175_000, - defect_ref=175_000) - - ascent_to_flap_retract.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - fix_final=False, - lower=0, - upper=50_000, - ref=5280, - defect_ref=5280) - - # Targets are not needed when there is a top-level ODE input with the same - # name as the parameter, state, or control - ascent_to_flap_retract.add_parameter( - "t_init_gear", units="s", static_target=True, opt=False, val=100) - ascent_to_flap_retract.add_parameter( - "t_init_flaps", units="s", static_target=True, opt=False, val=100) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are - # in degrees. - if updated_dymos: - ascent_to_flap_retract.add_control("alpha", - continuity_scaler=1.0E-2, - units="rad", - val=0.0, - continuity=True, - rate_continuity=False, - opt=True, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - else: - ascent_to_flap_retract.add_control("alpha", - units="rad", - val=0.0, - continuity=True, - rate_continuity=False, - opt=True, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - - # boundary/path constraints + controls - # Final altitude can be a linear constraint, since the final value of - # altitude is a design variable. - ascent_to_flap_retract.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, - loc="final", - equals=400, - units="ft", - ref=1.0, - ref0=0.0, - linear=False, - ) - - # Load factor can be treated as a linear constraint as long as i_wing is - # not a design variable. - ascent_to_flap_retract.add_path_constraint( - "load_factor", - lower=0.0, - upper=1.10, - scaler=1.0E-6 / 0.001, - linear=False) - - ascent_to_flap_retract.add_path_constraint( - "fuselage_pitch", - constraint_name="theta", - lower=0, - upper=15, - units="deg", - scaler=1.0E-6 / 0.001, - linear=False) - - # - # ASCENT TO CLEAN AERO CONFIG - # - ascent2_tx = dm.Radau(num_segments=3, order=3, compressed=True, solve_segments=False) - ascent_to_clean_aero = dm.Phase( - ode_class=FlightPathODE, ode_init_kwargs=ode_args, transcription=ascent2_tx) - - ascent_to_clean_aero.set_time_options(units="s", targets="t_curr", - fix_initial=False, fix_duration=False, - initial_bounds=(1, 500), - duration_bounds=(0.5, 100)) - - # Rate sources and units of states are set with tags in AscentEOM - ascent_to_clean_aero.set_state_options( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=False, - fix_final=False, - lower=-0.2618, - upper=0.43633, - ref=0.43633, - defect_ref=0.43633) - - ascent_to_clean_aero.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=False, - fix_final=False, - lower=0, - upper=500, - ref=100, - defect_ref=100) - - ascent_to_clean_aero.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=0, - upper=1000, - ref=100, - defect_ref=100) - - ascent_to_clean_aero.set_state_options( - "mass", - fix_initial=False, - fix_final=False, - lower=1, - upper=None, - ref=175_000, - defect_ref=175_000) - - ascent_to_clean_aero.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - fix_final=False, - lower=0, - upper=30.e3, - ref=5_280, - defect_ref=5_280) - - # Targets are not needed when there is a top-level ODE input with the same - # name as the parameter, state, or control - ascent_to_clean_aero.add_parameter( - "t_init_gear", units="s", static_target=True, opt=False) - ascent_to_clean_aero.add_parameter( - "t_init_flaps", units="s", static_target=True, opt=False) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are - # in degrees. - ascent_to_clean_aero.add_polynomial_control( - "alpha", order=1, - units="rad", - val=0.0, # continuity=True, rate_continuity=True, - opt=True, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - - # boundary/path constraints + controls - # Final altitude can be a linear constraint, since the final value of - # altitude is a design variable. - ascent_to_clean_aero.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, - loc="final", - equals=500, - units="ft", - ref=1.0, - ref0=0.0, - linear=False) - - # Ensure flaps are fully retracted - # Note setting equals=0.0 here will result in a failure of the optimization, since flap_factor approaches - # zero asymptotically. - ascent_to_clean_aero.add_boundary_constraint( - "flap_factor", - loc="final", - upper=1.0E-3, - ref=1.0, - ref0=0.0, - linear=False) - - # Load factor can be treated as a linear constraint as long as i_wing is - # not a design variable. - ascent_to_clean_aero.add_path_constraint( - "load_factor", - lower=0.0, - upper=1.10, - scaler=1.0E-6 / 0.001, - linear=False) - - ascent_to_clean_aero.add_path_constraint( - "fuselage_pitch", - constraint_name="theta", - lower=0, - upper=15, - units="deg", - scaler=1.0E-6 / 0.001, - linear=False) - - # - # ACCEL CONFIG - # - accel2_tx = dm.Radau(num_segments=5, order=3, compressed=True, solve_segments=False) - accel_ode_args = ode_args.deepcopy() - accel = dm.Phase(ode_class=FlightPathODE, - ode_init_kwargs=accel_ode_args, transcription=accel2_tx) - - accel.set_time_options(units="s", targets="t_curr", - fix_initial=False, fix_duration=False, - initial_bounds=(1, 500), - duration_bounds=(0.5, 10)) - - # Rate sources and units of states are set with tags in AscentEOM - accel.set_state_options( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=False, - fix_final=False, - lower=0.0, - ref=1.0e1, - defect_ref=1.0e1) - - accel.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=False, - fix_final=False, - units="ft", - ref0=495, - ref=520e1, - defect_ref=500e1, - lower=495, - upper=520) - - accel.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=1, - upper=1000, - units="kn", - ref=250e1, - defect_ref=250e1) - - accel.set_state_options( - "mass", - fix_initial=False, - fix_final=False, - lower=1, - upper=500_000, - ref=175_000e1, - defect_ref=175_000e1) - - accel.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - lower=0, - upper=None, - units="ft", - ref=1000e1, - defect_ref=1000e1) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are - # in degrees. - accel.add_polynomial_control("alpha", - order=2, - opt=True, - units="rad", - val=0.0, - lower=np.radians(-8), - upper=np.radians(8)) - - # boundary/path constraints + controls - # Final altitude can be a linear constraint, since the final value of - # altitude is a design variable. - accel.add_boundary_constraint("EAS", loc="final", equals=250., ref=250, units="kn") - - accel.add_parameter("t_init_gear", units="s", - static_target=True, opt=False, val=32.3) - accel.add_parameter("t_init_flaps", units="s", - static_target=True, opt=False, val=44.0) - - climb_ode_args = ode_args.deepcopy() - climb_ode_args['clean'] = True - transcription = dm.Radau(num_segments=11, order=3, compressed=True) - constant_quantity = 'EAS' - constant_EAS_climb = dm.Phase( - ode_class=FlightPathODE, - ode_init_kwargs=climb_ode_args, - transcription=transcription) - - constant_EAS_climb.set_time_options( - fix_initial=False, initial_bounds=( - 0, 1000), duration_bounds=( - 20, 1000), units="s", duration_ref=100) - - constant_EAS_climb.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=1, - upper=1000, - units="kn", - ref=250, - defect_ref=250) - - constant_EAS_climb.set_state_options( - "mass", - fix_initial=False, - fix_final=False, - lower=1, - upper=None, - units="lbm", - ref=200000, - defect_ref=200000) - - constant_EAS_climb.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - fix_final=False, - lower=0, - upper=None, - units="ft", - ref=10000, - defect_ref=10000) - - if constant_quantity == 'EAS': - constant_EAS_climb.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=False, - fix_final=False, - lower=400, - upper=20000, - units="ft", - ref=20.e3, - defect_ref=20.e3) - else: - constant_EAS_climb.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=False, - fix_final=False, - lower=9000, - upper=50000, - units="ft", - ref=40000, - defect_ref=40000) - - constant_EAS_climb.set_state_options( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=False, - fix_final=False, - units="rad", - lower=0.0, - ref=1.0, - defect_ref=1.0) - - constant_EAS_climb.add_control("alpha", opt=True, units="rad", val=0.0, - lower=np.radians(-14), upper=np.radians(14), ref=0.01, - rate_continuity=True, rate2_continuity=False) - - if updated_dymos: - constant_EAS_climb.add_control("alpha", - continuity_scaler=1.0E-2, - rate_continuity=False, - rate2_continuity=False, - opt=True, - units="rad", - val=0.0, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - else: - constant_EAS_climb.add_control("alpha", - rate_continuity=False, - rate2_continuity=False, - opt=True, - units="rad", - val=0.0, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - - # constant_EAS_climb.add_polynomial_control("alpha", order=1, opt=True, units="rad", lower=-np.radians(14), upper=np.radians(14), ref=0.01) - - if constant_quantity == 'EAS': - constant_EAS_climb.add_path_constraint( - "EAS", lower=249.9, upper=250.1, ref=350., units="kn") - # constant_EAS_climb.add_path_constraint("EAS", equals=350., ref=350., units="kn") - constant_EAS_climb.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, loc="final", equals=10.e3, ref=10.e3, units="ft") - else: - constant_EAS_climb.add_path_constraint( - Dynamic.Mission.MACH, lower=0.799, upper=0.801, ref=1.e3) - constant_EAS_climb.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, loc="final", equals=37.5e3, ref=10.e3, units="ft") - - constant_EAS_climb.add_timeseries_output("EAS", output_name="EAS", units="kn") - constant_EAS_climb.add_timeseries_output( - Dynamic.Mission.MACH, output_name=Dynamic.Mission.MACH, units="unitless") - constant_EAS_climb.add_timeseries_output("alpha", output_name="alpha", units="deg") - constant_EAS_climb.add_timeseries_output("CL", output_name="CL", units="unitless") - constant_EAS_climb.add_timeseries_output( - Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, - units="lbf") - constant_EAS_climb.add_timeseries_output("CD", output_name="CD", units="unitless") - - # - # CLIMB 2 (constrained EAS) - # - climb_ode_args = ode_args.deepcopy() - climb_ode_args['clean'] = True - transcription = dm.Radau(num_segments=5, order=3, compressed=True) - climb_to_mach = dm.Phase(ode_class=FlightPathODE, - ode_init_kwargs=climb_ode_args, transcription=transcription) - - climb_to_mach.set_time_options( - units="s", fix_initial=False, - initial_bounds=(1, 1.e3), - duration_bounds=(10., 1.e3)) - - # Rate sources and units of states are set with tags in AscentEOM - climb_to_mach.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=1, - upper=1000, - units="kn", - ref=250, - defect_ref=250) - - climb_to_mach.set_state_options( - "mass", - fix_initial=False, - fix_final=False, - lower=1, - upper=None, - units="lbm", - ref=200000, - defect_ref=200000) - - climb_to_mach.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - fix_final=False, - lower=0, - upper=None, - units="ft", - ref=10000, - defect_ref=10000) - - climb_to_mach.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=False, - fix_final=False, - lower=9.e3, - upper=40.e3, - units="ft", - ref=20.e3, - defect_ref=40000) - - climb_to_mach.set_state_options( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=False, - fix_final=False, - units="rad", - ref=1.0, - defect_ref=1.0) - - climb_to_mach.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - lower=0, - upper=None, - units="ft", - ref=1000, - defect_ref=1000) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are in degrees. - # climb_to_mach.add_polynomial_control("alpha", order=5, opt=True, units="rad", val=0.0, lower=np.radians(-14), upper=np.radians(14)) - if updated_dymos: - climb_to_mach.add_control("alpha", - continuity_scaler=1.0E-2, - rate_continuity=False, - rate2_continuity=False, - opt=True, - units="rad", - val=0.0, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - else: - climb_to_mach.add_control("alpha", - rate_continuity=False, - rate2_continuity=False, - opt=True, - units="rad", - val=0.0, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - - # boundary/path constraints + controls - climb_to_mach.add_path_constraint( - "EAS", lower=270., upper=270., ref=270., units='kn') - climb_to_mach.add_boundary_constraint(Dynamic.Mission.MACH, equals=0.8, loc="final") - - # - # CLIMB 3 (constant Mach) - # - climb_ode_args = ode_args.deepcopy() - climb_ode_args['clean'] = True - transcription = dm.Radau(num_segments=5, order=3, compressed=True) - climb_to_cruise = dm.Phase( - ode_class=FlightPathODE, - ode_init_kwargs=climb_ode_args, - transcription=transcription) - - climb_to_cruise.set_time_options( - units="s", fix_initial=False, - initial_bounds=(400, 1.5e3), - duration_bounds=(10., 1.e3)) - - # Rate sources and units of states are set with tags in AscentEOM - climb_to_cruise.set_state_options( - "TAS", - fix_initial=False, - fix_final=False, - lower=1, - upper=1000, - units="kn", - ref=250, - defect_ref=250) - - climb_to_cruise.set_state_options( - "mass", - fix_initial=False, - fix_final=False, - lower=1, - upper=None, - units="lbm", - ref=200000, - defect_ref=200000) - - climb_to_cruise.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - fix_final=False, - lower=0, - upper=None, - units="ft", - ref=10000, - defect_ref=10000) - - climb_to_cruise.set_state_options( - Dynamic.Mission.ALTITUDE, - fix_initial=False, - fix_final=False, - lower=15.e3, - upper=40.e3, - units="ft", - ref=20.e3, - defect_ref=40000) - - climb_to_cruise.set_state_options( - Dynamic.Mission.FLIGHT_PATH_ANGLE, - fix_initial=False, - fix_final=False, - units="rad", - lower=0, - ref=1.0, - defect_ref=1.0) - - climb_to_cruise.set_state_options( - Dynamic.Mission.DISTANCE, - fix_initial=False, - lower=0, - upper=None, - units="ft", - ref=1000, - defect_ref=1000) - - # Using Radau, we need to apply rate continuity if desired for controls even when the transcription is compressed. - # The final value of a control in a segment is interpolated, not a design variable. - # Note we specify units as radians here, since alpha inputs in the ODE are in degrees. - # climb_to_cruise.add_polynomial_control("alpha", order=5, opt=True, units="rad", val=0.0, lower=np.radians(-14), upper=np.radians(14)) - if updated_dymos: - climb_to_cruise.add_control("alpha", - continuity_scaler=1.0E-2, - rate_continuity=False, - rate2_continuity=False, - opt=True, - units="rad", - val=0.0, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - else: - climb_to_cruise.add_control("alpha", - rate_continuity=False, - rate2_continuity=False, - opt=True, - units="rad", - val=0.0, - lower=np.radians(-14), - upper=np.radians(14), - ref=0.01) - - # boundary/path constraints + controls - climb_to_cruise.add_path_constraint(Dynamic.Mission.MACH, equals=0.8, ref=1.) - climb_to_cruise.add_boundary_constraint( - Dynamic.Mission.ALTITUDE, loc="final", equals=37.5e3, ref=30.e3, units="ft") - - # - # CRUISE: Analytic Breguet Range Phase - # - cruise_ode_args = ode_args.deepcopy() - cruise = dm.AnalyticPhase( - ode_class=BreguetCruiseODESolution, - ode_init_kwargs=cruise_ode_args, - num_nodes=20 - ) - - # Time here is really the independent variable through which we are integrating. - # In the case of the Breguet Range ODE, it's mass. - # We rely on mass being monotonically non-increasing across the phase. - cruise.set_time_options( - name='mass', - fix_initial=False, - fix_duration=False, - units="lbm", - targets="mass", - initial_bounds=(10.e3, 500_000), - initial_ref=100_000, - duration_bounds=(-50000, -10), - duration_ref=10000, - ) - - cruise.add_parameter(Dynamic.Mission.ALTITUDE, opt=False, val=CRUISE_ALT, units='ft') - cruise.add_parameter(Dynamic.Mission.MACH, opt=False, val=0.8) - cruise.add_parameter("wing_area", opt=False, val=1370, units="ft**2") - cruise.add_parameter("initial_distance", opt=False, val=0.0, - units="NM", static_target=True) - cruise.add_parameter("initial_time", opt=False, val=0.0, - units="s", static_target=True) - - cruise.add_timeseries_output("time", units="s") - - cruise.add_boundary_constraint( - Dynamic.Mission.DISTANCE, loc="final", equals=TARGET_RANGE, ref=TARGET_RANGE, units="NM") - - # - # PROBLEM DEFINITION - # - p = om.Problem() - traj = p.model.add_subsystem("traj", dm.Trajectory()) - traj.add_phase("groundroll", groundroll) - traj.add_phase("rotation", rotation) - traj.add_phase("ascent_to_gear_retract", ascent_to_gear_retract) - traj.add_phase("ascent_to_flap_retract", ascent_to_flap_retract) - traj.add_phase("ascent_to_clean_aero", ascent_to_clean_aero) - traj.add_phase(Dynamic.Mission.VELOCITY_RATE, accel) - traj.add_phase("constant_EAS_climb", constant_EAS_climb) - traj.add_phase("climb_to_mach", climb_to_mach) - traj.add_phase("climb_to_cruise", climb_to_cruise) - traj.add_phase("cruise", cruise) - - traj.add_linkage_constraint(phase_a='ascent_to_gear_retract', - phase_b='ascent_to_flap_retract', - var_a='time', - var_b='t_init_gear', - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='ascent_to_gear_retract', - phase_b='ascent_to_clean_aero', - var_a='time', - var_b='t_init_gear', - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='ascent_to_gear_retract', - phase_b=Dynamic.Mission.VELOCITY_RATE, - var_a='time', - var_b='t_init_gear', - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='ascent_to_flap_retract', - phase_b='ascent_to_clean_aero', - var_a='time', - var_b='t_init_flaps', - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='ascent_to_flap_retract', - phase_b=Dynamic.Mission.VELOCITY_RATE, - var_a='time', - var_b='t_init_flaps', - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='climb_to_cruise', - phase_b='cruise', - var_a='time', - var_b='initial_time', - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='climb_to_cruise', - phase_b='cruise', - var_a=Dynamic.Mission.DISTANCE, - var_b='initial_distance', - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='climb_to_cruise', - phase_b='cruise', - var_a=Dynamic.Mission.MACH, - var_b=Dynamic.Mission.MACH, - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='climb_to_cruise', - phase_b='cruise', - var_a=Dynamic.Mission.ALTITUDE, - var_b=Dynamic.Mission.ALTITUDE, - loc_a='final', - loc_b='initial', - connected=True) - - traj.add_linkage_constraint(phase_a='climb_to_cruise', - phase_b='cruise', - var_a='mass', - var_b='mass', - loc_a='final', - loc_b='initial', - connected=False) - - # 2. Continuity for rotation to first ascent phase. Altitude and flight - # path angle are not states on the ground. - traj.link_phases(['groundroll', 'rotation'], - vars=['time', 'TAS', 'mass', Dynamic.Mission.DISTANCE]) - - # Continuity for rotation to first ascent phase. Altitude and flight path - # angle are not states on the ground. - traj.link_phases(['rotation', 'ascent_to_gear_retract'], vars=[ - 'time', 'TAS', 'mass', Dynamic.Mission.DISTANCE, 'alpha', Dynamic.Mission.FLIGHT_PATH_ANGLE, Dynamic.Mission.ALTITUDE]) - - # 3. Enforce value continuity between all phases in ascent for time, - # states, and alpha control - traj.link_phases(['ascent_to_gear_retract', - 'ascent_to_flap_retract', - 'ascent_to_clean_aero', - Dynamic.Mission.VELOCITY_RATE], - vars=['time', - Dynamic.Mission.FLIGHT_PATH_ANGLE, - Dynamic.Mission.ALTITUDE, - 'TAS', - Dynamic.Mission.MASS, - Dynamic.Mission.DISTANCE, - 'alpha']) - - traj.link_phases([Dynamic.Mission.VELOCITY_RATE, - 'constant_EAS_climb', - 'climb_to_mach', - 'climb_to_cruise'], - vars=['time', - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MASS, - Dynamic.Mission.DISTANCE]) - - # Post trajectory analyses - p.model.add_subsystem("vrot_comp", VRotateComp()) - p.model.connect('traj.groundroll.states:mass', - 'vrot_comp.mass', src_indices=om.slicer[0, ...]) - - vrot_eq_comp = p.model.add_subsystem("vrot_eq_comp", om.EQConstraintComp()) - vrot_eq_comp.add_eq_output( - "v_rotate_error", - eq_units="kn", - lhs_name="v_rot_computed", - rhs_name="groundroll_v_final", - add_constraint=True) - - p.model.connect('vrot_comp.Vrot', 'vrot_eq_comp.v_rot_computed') - p.model.connect('traj.groundroll.states:TAS', - 'vrot_eq_comp.groundroll_v_final', src_indices=om.slicer[-1, ...]) - - for phase_name, phase in traj._phases.items(): - if "clean" in phase.options["ode_init_kwargs"] and not phase.options["ode_init_kwargs"]["clean"]: - phase.add_timeseries_output('gear_factor') - phase.add_timeseries_output('flap_factor') - phase.add_timeseries_output('normal_force') - phase.add_timeseries_output('fuselage_pitch') - # Add alpha to the timeseries as 'alpha' regardless of whether it is a - # control or polynomial control. - phase.add_timeseries_output('alpha', units='deg') - phase.add_timeseries_output(Dynamic.Mission.ALTITUDE, units='ft') - phase.add_timeseries_output(Dynamic.Mission.MACH) - phase.add_timeseries_output('EAS', units="kn") - phase.add_timeseries_output('TAS', units="kn") - phase.add_timeseries_output('mass', units="lbm") - phase.add_timeseries_output(Dynamic.Mission.DISTANCE, units="NM") - - # Setup the solvers - # We're roughtly twice as fast per iteration with a direct solver at the phase level. - for phase_name, phase in traj._phases.items(): - phase.linear_solver = om.DirectSolver(iprint=0, assemble_jac=True) - p.driver = om.pyOptSparseDriver() - - if optimizer == "SNOPT": - p.driver.options["optimizer"] = "SNOPT" - p.driver.opt_settings["iSumm"] = 6 - p.driver.opt_settings['Major step limit'] = 0.5 - p.driver.opt_settings['Major iterations limit'] = 100 - p.driver.opt_settings['Major feasibility tolerance'] = 1.0E-5 - p.driver.opt_settings['Major optimality tolerance'] = 1.0E-5 - p.driver.opt_settings['Hessian updates'] = 5 - elif optimizer == "IPOPT": - p.driver.options['optimizer'] = 'IPOPT' - p.driver.opt_settings['tol'] = 1.0E-4 - # p.driver.opt_settings['dual_inf_tol'] = 1.0E-4 - p.driver.opt_settings['acceptable_tol'] = 5.0E-4 - p.driver.opt_settings['mu_init'] = 1e-5 - p.driver.opt_settings['max_iter'] = 100 - p.driver.opt_settings['print_level'] = 5 - # for faster convergence - p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based' - p.driver.opt_settings['nlp_scaling_max_gradient'] = 1000.0 - - # p.driver.opt_settings['nlp_scaling_method'] = 'none' - p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas' - p.driver.opt_settings['bound_mult_init_method'] = 'mu-based' - p.driver.opt_settings['limited_memory_update_type'] = 'bfgs' - p.driver.opt_settings['mu_strategy'] = 'monotone' - # p.driver.opt_settings['mu_strategy'] = 'adaptive' - # p.driver.opt_settings['grad_f_constant'] = "yes" - - # Uncomment declare_coloring when the coloring needs to be reset (number - # of nodes have changed, constraints changed, or underlying models have - # changed) - p.driver.declare_coloring(tol=1.0E-12, num_full_jacs=1) - - obj_comp = om.ExecComp("obj = -final_mass / 175191 + final_time / 10", - final_mass={"units": "lbm"}, - final_time={"units": "h"}) - p.model.add_subsystem("obj_comp", obj_comp) - - p.model.connect("traj.cruise.timeseries.mass", - "obj_comp.final_mass", src_indices=[-1]) - p.model.connect("traj.cruise.timeseries.time", - "obj_comp.final_time", src_indices=[-1]) - - p.model.add_objective("obj_comp.obj", ref=1.0) - - p.setup() - - # SET INPUTS FOR POST TRAJECTORY ANALYSES - # TODO: paramport - params = ParamPort.param_data - p.set_val('vrot_comp.' + Aircraft.Wing.AREA, - params[Aircraft.Wing.AREA]["val"], - units=params[Aircraft.Wing.AREA]["units"]) - p.set_val("vrot_comp.dV1", val=10, units="kn") - p.set_val("vrot_comp.dVR", val=5, units="kn") - p.set_val("vrot_comp.rho", val=RHO_SEA_LEVEL_ENGLISH, units="slug/ft**3") - p.set_val("vrot_comp.CL_max", val=2.1886, units="unitless") - - # SET TIME INITIAL GUESS - p.set_val("traj.cruise.t_initial", 171481, units="lbm") # Initial mass in cruise - p.set_val("traj.cruise.t_duration", -10000, units="lbm") # Mass of fuel consumed - - p.set_val("traj.cruise.parameters:altitude", val=37500.0, units="ft") - p.set_val("traj.cruise.parameters:mach", val=0.8, units="unitless") - p.set_val("traj.cruise.parameters:wing_area", val=1370, units="ft**2") - p.set_val("traj.cruise.parameters:initial_distance", val=175.0, units="NM") - p.set_val("traj.cruise.parameters:initial_time", val=1600.0, units="s") - - # RUN THE DRIVER AND EXPLICITLY SIMULATE THE CONTROL SOLUTION - this_dir = os.path.dirname(os.path.realpath(__file__)) - restart_file = this_dir + os.sep + "dymos_solution_IPOPT.db" - dm.run_problem(p, run_driver=True, simulate=False, make_plots=make_plots, - restart=restart_file, - simulation_record_file=f'dymos_simulation_{optimizer}.db', - solution_record_file=f'dymos_solution_{optimizer}.db') - - print("t_init_gear (s)", p.get_val( - "traj.ascent_to_gear_retract.timeseries.time", units="s")[-1, ...]) - print("t_init_flaps (s)", p.get_val( - "traj.ascent_to_flap_retract.timeseries.time", units="s")[-1, ...]) - print("initial cruise time (s)", p.get_val( - "traj.cruise.parameter_vals:initial_time", units="s")) - print("cruise time (s)", p.get_val( - "traj.cruise.timeseries.time", units="s")[-1, ...]) - print("cruise range (NM)", p.get_val( - "traj.cruise.timeseries.distance", units="NM")[-1, ...]) - print("cruise equivalent airspeed", p.get_val( - "traj.cruise.timeseries.EAS", units="kn")[0, ...]) - - return p diff --git a/aviary/mission/gasp_based/phases/run_phases/run_desc1.py b/aviary/mission/gasp_based/phases/run_phases/run_desc1.py deleted file mode 100644 index 9b18c15ea..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_desc1.py +++ /dev/null @@ -1,330 +0,0 @@ -# separated ODE into 2 balances and removed top level solver - -import dymos as dm -import matplotlib.pyplot as plt -import numpy as np -import openmdao.api as om -import pandas as pd - -from aviary.mission.gasp_based.ode.descent_ode import DescentODE -from aviary.mission.gasp_based.phases.desc_phase import get_descent -from aviary.variable_info.enums import SpeedType -from aviary.variable_info.variables import Aircraft, Dynamic - - -def run_desc1(make_plots=False): - - theta_max_val = 15 - plot_over_alt = False - - prob = om.Problem(model=om.Group()) - - prob.driver = om.pyOptSparseDriver() - prob.driver.options["optimizer"] = "SNOPT" - prob.driver.opt_settings["Major iterations limit"] = 100 - prob.driver.opt_settings["Major optimality tolerance"] = 5.0e-3 - prob.driver.opt_settings["Major feasibility tolerance"] = 1e-6 - prob.driver.opt_settings["iSumm"] = 6 - prob.driver.declare_coloring() - - transcription = dm.Radau(num_segments=20, order=4, compressed=False) - desc1 = get_descent( - ODE=DescentODE, - fix_initial=True, - input_initial=False, - alt_lb=1000, - alt_ub=60000, - mass_lb=0, - mass_ub=200000, - distance_lb=2000, - distance_ub=3000, - time_lb=(0, 0), - time_ub=(2, 36000), - EAS_limit=350, - mach_cruise=0.8, - transcription=transcription, - final_alt=10000, - input_speed_type=SpeedType.MACH, - ) - - desc1.add_parameter( - Dynamic.Mission.THROTTLE, opt=False, units="unitless", val=0.0, static_target=False - ) - - desc1.add_parameter( - Aircraft.Wing.AREA, opt=False, units="ft**2", val=1370.0, static_target=True - ) - - prob.model.add_subsystem(name="desc1", subsys=desc1) - desc1.add_timeseries_output( - Dynamic.Mission.MACH, - output_name=Dynamic.Mission.MACH, - units="unitless") - desc1.add_timeseries_output("EAS", output_name="EAS", units="kn") - desc1.add_timeseries_output(Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, units="lbf") - desc1.add_timeseries_output("aero.CD", output_name="CD", units="unitless") - desc1.add_timeseries_output(Dynamic.Mission.FLIGHT_PATH_ANGLE, - output_name=Dynamic.Mission.FLIGHT_PATH_ANGLE, units="deg") - desc1.add_timeseries_output("alpha", output_name="alpha", units="deg") - desc1.add_timeseries_output("aero.CL", output_name="CL", units="unitless") - desc1.add_timeseries_output( - "required_lift", output_name="required_lift", units="lbf" - ) - desc1.add_timeseries_output("theta", output_name="theta", units="deg") - - desc1.add_objective("time", loc="final") - prob.model.linear_solver.options["iprint"] = 0 - prob.model.nonlinear_solver.options["iprint"] = 0 - - prob.model.options["assembled_jac_type"] = "csc" - # prob.model.linear_solver = om.DirectSolver(assemble_jac=True) - # prob.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=True) - # prob.model.nonlinear_solver.options["err_on_non_converge"] = True - - prob.setup() - prob.model.nonlinear_solver.options["iprint"] = 1 - prob.set_val("desc1.states:mass", desc1.interp("mass", ys=[147664, 147541.5])) - prob.set_val( - "desc1.states:altitude", desc1.interp( - Dynamic.Mission.ALTITUDE, ys=[ - 37500, 10000])) - prob.set_val("desc1.states:distance", desc1.interp( - Dynamic.Mission.DISTANCE, ys=[2490, 2563.5])) - prob.set_val("desc1.t_duration", 350) - prob.set_val("desc1.t_initial", 0) - - prob.set_solver_print(level=0) - prob.run_driver() - - if make_plots == True: - print() - print("Done Running Model, Now Simulating") - print() - - simout4 = desc1.simulate(atol=1e-6, rtol=1e-6) - simout4.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=True) - simout4.model.options["assembled_jac_type"] = "csc" - simout4.model.linear_solver = om.DirectSolver(assemble_jac=True) - simout4.model.nonlinear_solver.options["err_on_non_converge"] = True - t_solved = prob.get_val("desc1.timeseries.time") - t_simulate = simout4.get_val("desc1.timeseries.time") - - alt_solved = prob.get_val("desc1.timeseries.states:altitude", units="ft") - alt_simulate = simout4.get_val("desc1.timeseries.states:altitude", units="ft") - - mass_solved = prob.get_val("desc1.timeseries.states:mass", units="lbm") - mass_simulate = simout4.get_val("desc1.timeseries.states:mass", units="lbm") - - distance_solved = prob.get_val("desc1.timeseries.states:distance", units="NM") - distance_simulate = simout4.get_val( - "desc1.timeseries.states:distance", units="NM" - ) - - mach_solved = prob.get_val("desc1.timeseries.mach", units="unitless") - mach_simulate = simout4.get_val("desc1.timeseries.mach", units="unitless") - - EAS_solved = prob.get_val("desc1.timeseries.EAS", units="kn") - EAS_simulate = simout4.get_val("desc1.timeseries.EAS", units="kn") - - EAS_solved = prob.get_val("desc1.timeseries.EAS", units="kn") - EAS_simulate = simout4.get_val("desc1.timeseries.EAS", units="kn") - - gamma_solved = prob.get_val("desc1.timeseries.flight_path_angle", units="deg") - gamma_simulate = simout4.get_val( - "desc1.timeseries.flight_path_angle", units="deg") - - thrust_solved = prob.get_val("desc1.timeseries.thrust", units="unitless") - thrust_simulate = simout4.get_val("desc1.timeseries.thrust", units="unitless") - - CD_solved = prob.get_val("desc1.timeseries.CD", units="unitless") - CD_simulate = simout4.get_val("desc1.timeseries.CD", units="unitless") - - alpha_solved = prob.get_val("desc1.timeseries.alpha", units="deg") - alpha_simulate = simout4.get_val("desc1.timeseries.alpha", units="deg") - - CL_solved = prob.get_val("desc1.timeseries.CL", units="unitless") - CL_simulate = simout4.get_val("desc1.timeseries.CL", units="unitless") - - theta_solved = prob.get_val("desc1.timeseries.theta", units="deg") - theta_simulate = simout4.get_val("desc1.timeseries.theta", units="deg") - theta_max = np.ones(np.shape(theta_simulate)) * theta_max_val - - fig, ax = plt.subplots(3, 1, sharex=True) - fig.suptitle("State Values") - - if plot_over_alt is False: - l1 = ax[0].plot(t_solved, alt_solved, "bo") - l2 = ax[0].plot(t_simulate, alt_simulate, "b-") - - ax[1].plot(t_solved, mass_solved, "bo") - ax[1].plot(t_simulate, mass_simulate, "b-") - - ax[2].plot(t_solved, distance_solved, "bo") - ax[2].plot(t_simulate, distance_simulate, "b-") - - ax[0].set_xlabel("time (s)") - ax[1].set_xlabel("time (s)") - ax[2].set_xlabel("time (s)") - ax[0].set_ylabel("alt (ft)") - - plt_name = "plots/desc1_states_time.pdf" - - else: - l1 = ax[0].plot(alt_solved, t_solved, "bo") - l2 = ax[0].plot(alt_simulate, t_simulate, "b-") - - ax[1].plot(alt_solved, mass_solved, "bo") - ax[1].plot(alt_simulate, mass_simulate, "b-") - - ax[2].plot(alt_solved, distance_solved, "bo") - ax[2].plot(alt_simulate, distance_simulate, "b-") - - ax[0].set_xlabel("alt (ft)") - ax[1].set_xlabel("alt (ft)") - ax[2].set_xlabel("alt (ft)") - ax[0].set_ylabel("time (s)") - - plt_name = "plots/desc1_states_alt.pdf" - - ax[1].set_ylabel("mass (lbm)") - ax[2].set_ylabel("distance (NM)") - fig.legend([l1, l2, l3], labels=["Optimized", "Simulated"]) - plt.savefig(plt_name) - - fig2, ax2 = plt.subplots(2, 1, sharex=True) - fig2.suptitle("Speed Values") - - if plot_over_alt is False: - a1 = ax2[0].plot(t_solved, mach_solved, "ro") - a2 = ax2[0].plot(t_simulate, mach_simulate, "r-") - - ax2[1].plot(t_solved, EAS_solved, "ro") - ax2[1].plot(t_simulate, EAS_simulate, "r-") - - ax2[0].set_xlabel("time (s)") - ax2[1].set_xlabel("time (s)") - - plt_name = "plots/desc1_speeds_time.pdf" - - else: - a1 = ax2[0].plot(alt_solved, mach_solved, "ro") - a2 = ax2[0].plot(alt_simulate, mach_simulate, "r-") - - ax2[1].plot(alt_solved, EAS_solved, "ro") - ax2[1].plot(alt_simulate, EAS_simulate, "r-") - - ax2[0].set_xlabel("alt (ft)") - ax2[1].set_xlabel("alt (ft)") - - plt_name = "plots/desc1_speeds_alt.pdf" - - ax2[0].set_ylabel(Dynamic.Mission.MACH) - ax2[1].set_ylabel("EAS (knots)") - fig2.legend([a1, a2], labels=["Optimized", "Simulated"]) - plt.savefig(plt_name) - - fig3, ax3 = plt.subplots(3, 1, sharex=True) - fig3.suptitle("Force Values") - - if plot_over_alt is False: - c1 = ax3[0].plot(t_solved, thrust_solved, "ro") - c2 = ax3[0].plot(t_simulate, thrust_simulate, "r-") - - ax3[1].plot(t_solved, CD_solved, "ro") - ax3[1].plot(t_simulate, CD_simulate, "r-") - - ax3[2].plot(t_solved, CL_solved, "ro") - ax3[2].plot(t_simulate, CL_simulate, "r-") - - ax3[0].set_xlabel("time (s)") - ax3[1].set_xlabel("time (s)") - ax3[2].set_xlabel("time (s)") - - plt_name = "plots/desc1_forces_time.pdf" - - else: - c1 = ax3[0].plot(alt_solved, thrust_solved, "ro") - c2 = ax3[0].plot(alt_simulate, thrust_simulate, "r-") - - ax3[1].plot(alt_solved, CD_solved, "ro") - ax3[1].plot(alt_simulate, CD_simulate, "r-") - - ax3[2].plot(alt_solved, CL_solved, "ro") - ax3[2].plot(alt_simulate, CL_simulate, "r-") - - ax3[0].set_xlabel("alt (ft)") - ax3[1].set_xlabel("alt (ft)") - ax3[2].set_xlabel("alt (ft)") - - plt_name = "plots/desc1_forces_alt.pdf" - - ax3[0].set_ylabel("thrust (lbf)") - ax3[1].set_ylabel("CD") - ax3[2].set_ylabel("CL") - fig3.legend([c1, c2], labels=["Optimized", "Simulated"]) - plt.savefig(plt_name) - - fig4, ax4 = plt.subplots(3, 1, sharex=True) - fig4.suptitle("Angle Values") - - if plot_over_alt is False: - - ax4[0].plot(t_solved, alpha_solved, "go") - ax4[0].plot(t_simulate, alpha_simulate, "g-") - - ax4[1].plot(t_solved, gamma_solved, "go") - ax4[1].plot(t_simulate, gamma_simulate, "g-") - - d1 = ax4[2].plot(t_solved, theta_solved, "go") - d2 = ax4[2].plot(t_simulate, theta_simulate, "g-") - d3 = ax4[2].plot(t_simulate, theta_max, "r-") - - ax4[0].set_xlabel("time (s)") - ax4[1].set_xlabel("time (s)") - ax4[1].set_xlabel("time (s)") - - plt_name = "plots/desc1_angles_time.pdf" - - else: - - ax4[0].plot(alt_solved, alpha_solved, "go") - ax4[0].plot(alt_simulate, alpha_simulate, "g-") - - ax4[1].plot(alt_solved, gamma_solved, "go") - ax4[1].plot(alt_simulate, gamma_simulate, "g-") - - d1 = ax4[2].plot(alt_solved, theta_solved, "go") - d2 = ax4[2].plot(alt_simulate, theta_simulate, "g-") - d3 = ax4[2].plot(alt_simulate, theta_max, "r-") - - ax4[0].set_xlabel("alt (ft)") - ax4[1].set_xlabel("alt (ft)") - ax4[2].set_xlabel("alt (ft)") - - plt_name = "plots/desc1_angles_alt.pdf" - - ax4[0].set_ylabel("alpha (deg)") - ax4[1].set_ylabel("gamma (deg)") - ax4[2].set_ylabel("theta (deg)") - fig4.legend( - [d1, d2, d3], labels=["Optimized", "Simulated", "maximum"] - ) - plt.savefig(plt_name) - - fig5, ax5 = plt.subplots(2, 1, sharex=True) - fig5.suptitle("Alpha Values") - - f1 = ax5[0].plot(alpha_solved, CL_solved, "ro") - - ax5[1].plot(alpha_solved, CD_solved, "ro") - - ax5[0].set_xlabel("alpha (deg)") - ax5[1].set_xlabel("alpha (deg)") - - ax5[0].set_ylabel("CL") - ax5[1].set_ylabel("CD") - fig5.legend([f1, f2], labels=["Optimized"]) - plt.savefig("plots/desc1_alphas.pdf") - - return prob diff --git a/aviary/mission/gasp_based/phases/run_phases/run_desc2.py b/aviary/mission/gasp_based/phases/run_phases/run_desc2.py deleted file mode 100644 index d7d30deb3..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_desc2.py +++ /dev/null @@ -1,328 +0,0 @@ -import dymos as dm -import matplotlib.pyplot as plt -import numpy as np -import openmdao.api as om -import pandas as pd - -from aviary.mission.gasp_based.ode.descent_ode import DescentODE -from aviary.mission.gasp_based.phases.desc_phase import get_descent -from aviary.variable_info.enums import SpeedType -from aviary.variable_info.variables import Aircraft, Dynamic - - -def run_desc2(): - theta_max_val = 15 - plot_over_alt = False - - prob = om.Problem(model=om.Group()) - - prob.driver = om.pyOptSparseDriver() - prob.driver.options["optimizer"] = "SNOPT" - prob.driver.opt_settings["Major iterations limit"] = 100 - prob.driver.opt_settings["Major optimality tolerance"] = 5.0e-3 - prob.driver.opt_settings["Major feasibility tolerance"] = 1e-6 - prob.driver.opt_settings["iSumm"] = 6 - prob.driver.declare_coloring() - - transcription = dm.Radau(num_segments=15, order=4, compressed=False) - desc2 = get_descent( - DescentODE, - fix_initial=True, - input_initial=False, - alt_lb=1000, - alt_ub=60000, - mass_lb=0, - mass_ub=200000, - distance_lb=2000, - distance_ub=3000, - time_lb=(0, 0), - time_ub=(2, 36000), - EAS_limit=250, - mach_cruise=0.8, - transcription=transcription, - final_alt=1000, - input_speed_type=SpeedType.EAS, - ) - - desc2.add_parameter( - Dynamic.Mission.THROTTLE, - opt=False, - units="unitless", - val=0.0, - static_target=False) - - desc2.add_parameter( - Aircraft.Wing.AREA, opt=False, units="ft**2", val=1370.0, static_target=True - ) - - prob.model.add_subsystem(name="desc2", subsys=desc2) - desc2.add_timeseries_output( - Dynamic.Mission.MACH, - output_name=Dynamic.Mission.MACH, - units="unitless") - desc2.add_timeseries_output("EAS", output_name="EAS", units="kn") - desc2.add_timeseries_output( - Dynamic.Mission.THRUST_TOTAL, - output_name=Dynamic.Mission.THRUST_TOTAL, - units="lbf") - desc2.add_timeseries_output("aero.CD", output_name="CD", units="unitless") - desc2.add_timeseries_output(Dynamic.Mission.FLIGHT_PATH_ANGLE, - output_name=Dynamic.Mission.FLIGHT_PATH_ANGLE, units="deg") - desc2.add_timeseries_output("alpha", output_name="alpha", units="deg") - desc2.add_timeseries_output("aero.CL", output_name="CL", units="unitless") - desc2.add_timeseries_output( - "required_lift", output_name="required_lift", units="lbf" - ) - desc2.add_timeseries_output("theta", output_name="theta", units="deg") - desc2.add_timeseries_output("TAS", output_name="TAS", units="kn") - - desc2.add_objective("time", loc="final") - prob.model.linear_solver.options["iprint"] = 0 - prob.model.nonlinear_solver.options["iprint"] = 0 - - prob.model.options["assembled_jac_type"] = "csc" - prob.model.linear_solver = om.DirectSolver(assemble_jac=True) - - prob.setup() - prob.set_val("desc2.states:mass", desc2.interp("mass", ys=[147541.5, 147390])) - prob.set_val( - "desc2.states:altitude", desc2.interp( - Dynamic.Mission.ALTITUDE, ys=[ - 10000, 1000])) - prob.set_val("desc2.states:distance", desc2.interp( - Dynamic.Mission.DISTANCE, ys=[2563.5, 2598])) - prob.set_val("desc2.t_duration", 500) - prob.set_val("desc2.t_initial", 0) - - prob.set_solver_print(level=0) - prob.run_driver() - print() - print("Done Running Model, Now Simulating") - print() - - simout5 = desc2.simulate(atol=1e-6, rtol=1e-6) - - if make_plots == True: - - ## make plots ## - - t_solved = prob.get_val("desc2.timeseries.time") - t_simulate = simout5.get_val("desc2.timeseries.time") - - alt_solved = prob.get_val("desc2.timeseries.states:altitude", units="ft") - alt_simulate = simout5.get_val("desc2.timeseries.states:altitude", units="ft") - - mass_solved = prob.get_val("desc2.timeseries.states:mass", units="lbm") - mass_simulate = simout5.get_val("desc2.timeseries.states:mass", units="lbm") - - distance_solved = prob.get_val("desc2.timeseries.states:distance", units="NM") - distance_simulate = simout5.get_val( - "desc2.timeseries.states:distance", units="NM" - ) - - mach_solved = prob.get_val("desc2.timeseries.mach", units="unitless") - mach_simulate = simout5.get_val("desc2.timeseries.mach", units="unitless") - - TAS_solved = prob.get_val("desc2.timeseries.TAS", units="kn") - TAS_simulate = simout5.get_val("desc2.timeseries.TAS", units="kn") - - gamma_solved = prob.get_val("desc2.timeseries.flight_path_angle", units="deg") - gamma_simulate = simout5.get_val( - "desc2.timeseries.flight_path_angle", units="deg") - - thrust_solved = prob.get_val("desc2.timeseries.thrust", units="unitless") - thrust_simulate = simout5.get_val("desc2.timeseries.thrust", units="unitless") - - CD_solved = prob.get_val("desc2.timeseries.CD", units="unitless") - CD_simulate = simout5.get_val("desc2.timeseries.CD", units="unitless") - - alpha_solved = prob.get_val("desc2.timeseries.alpha", units="deg") - alpha_simulate = simout5.get_val("desc2.timeseries.alpha", units="deg") - - CL_solved = prob.get_val("desc2.timeseries.CL", units="unitless") - CL_simulate = simout5.get_val("desc2.timeseries.CL", units="unitless") - - theta_solved = prob.get_val("desc2.timeseries.theta", units="deg") - theta_simulate = simout5.get_val("desc2.timeseries.theta", units="deg") - theta_max = np.ones(np.shape(theta_simulate)) * theta_max_val - - fig, ax = plt.subplots(3, 1, sharex=True) - fig.suptitle("State Values") - - if plot_over_alt is False: - l1 = ax[0].plot(t_solved, alt_solved, "go") - l2 = ax[0].plot(t_simulate, alt_simulate, "g-") - - ax[1].plot(t_solved, mass_solved, "go") - ax[1].plot(t_simulate, mass_simulate, "g-") - - ax[2].plot(t_solved, distance_solved, "go") - ax[2].plot(t_simulate, distance_simulate, "g-") - - ax[0].set_xlabel("time (s)") - ax[1].set_xlabel("time (s)") - ax[2].set_xlabel("time (s)") - ax[0].set_ylabel("alt (ft)") - - plt_name = "plots/desc2_states_time.pdf" - - else: - l1 = ax[0].plot(alt_solved, t_solved, "go") - l2 = ax[0].plot(alt_simulate, t_simulate, "g-") - - ax[1].plot(alt_solved, mass_solved, "go") - ax[1].plot(alt_simulate, mass_simulate, "g-") - - ax[2].plot(alt_solved, distance_solved, "go") - ax[2].plot(alt_simulate, distance_simulate, "g-") - - ax[0].set_xlabel("alt (ft)") - ax[1].set_xlabel("alt (ft)") - ax[2].set_xlabel("alt (ft)") - ax[0].set_ylabel("time (s)") - - plt_name = "plots/desc2_states_alt.pdf" - - ax[1].set_ylabel("mass (lbm)") - ax[2].set_ylabel("distance (NM)") - fig.legend([l1, l2], labels=["Optimized", "Simulated"]) - plt.savefig(plt_name) - - fig2, ax2 = plt.subplots(2, 1, sharex=True) - fig2.suptitle("Speed Values") - - if plot_over_alt is False: - a1 = ax2[0].plot(t_solved, mach_solved, "ro") - a2 = ax2[0].plot(t_simulate, mach_simulate, "r-") - - ax2[1].plot(t_solved, TAS_solved, "ro") - ax2[1].plot(t_simulate, TAS_simulate, "r-") - - ax2[0].set_xlabel("time (s)") - ax2[1].set_xlabel("time (s)") - - plt_name = "plots/desc2_speeds_time.pdf" - - else: - a1 = ax2[0].plot(alt_solved, mach_solved, "ro") - a2 = ax2[0].plot(alt_simulate, mach_simulate, "r-") - - ax2[1].plot(alt_solved, TAS_solved, "ro") - ax2[1].plot(alt_simulate, TAS_simulate, "r-") - - ax2[0].set_xlabel("alt (ft)") - ax2[1].set_xlabel("alt (ft)") - - plt_name = "plots/desc2_speeds_alt.pdf" - - ax2[0].set_ylabel(Dynamic.Mission.MACH) - ax2[1].set_ylabel("TAS (knots)") - fig2.legend([a1, a2], labels=["Optimized", "Simulated"]) - plt.savefig(plt_name) - - fig3, ax3 = plt.subplots(3, 1, sharex=True) - fig3.suptitle("Force Values") - - if plot_over_alt is False: - - d1 = ax3[0].plot(t_solved, thrust_solved, "bo") - d2 = ax3[0].plot(t_simulate, thrust_simulate, "b-") - - ax3[1].plot(t_solved, CD_solved, "bo") - ax3[1].plot(t_simulate, CD_simulate, "b-") - - ax3[2].plot(t_solved, CL_solved, "bo") - ax3[2].plot(t_simulate, CL_simulate, "b-") - - ax3[0].set_xlabel("time (s)") - ax3[1].set_xlabel("time (s)") - ax3[2].set_xlabel("time (s)") - - plt_name = "plots/desc2_forces_time.pdf" - - else: - - d1 = ax3[0].plot(alt_solved, thrust_solved, "bo") - d2 = ax3[0].plot(alt_simulate, thrust_simulate, "b-") - - ax3[1].plot(alt_solved, CD_solved, "bo") - ax3[1].plot(alt_simulate, CD_simulate, "b-") - - ax3[2].plot(alt_solved, CL_solved, "bo") - ax3[2].plot(alt_simulate, CL_simulate, "b-") - - ax3[0].set_xlabel("alt (ft)") - ax3[1].set_xlabel("alt (ft)") - ax3[2].set_xlabel("alt (ft)") - - plt_name = "plots/desc2_forces_alt.pdf" - - ax3[0].set_ylabel("thrust (lbf)") - ax3[1].set_ylabel("CD") - ax3[2].set_ylabel("CL") - fig3.legend([d1, d2], labels=["Optimized", "Simulated"]) - plt.savefig(plt_name) - - fig4, ax4 = plt.subplots(3, 1, sharex=True) - fig4.suptitle("Angle Values") - - if plot_over_alt is False: - - ax4[0].plot(t_solved, alpha_solved, "go") - ax4[0].plot(t_simulate, alpha_simulate, "g-") - - ax4[1].plot(t_solved, gamma_solved, "go") - ax4[1].plot(t_simulate, gamma_simulate, "g-") - - c1 = ax4[2].plot(t_solved, theta_solved, "go") - c2 = ax4[2].plot(t_simulate, theta_simulate, "g-") - c3 = ax4[2].plot(t_simulate, theta_max, "r-") - - ax4[0].set_xlabel("time (s)") - ax4[1].set_xlabel("time (s)") - ax4[1].set_xlabel("time (s)") - - plt_name = "plots/desc2_angles_time.pdf" - - else: - - ax4[0].plot(alt_solved, alpha_solved, "go") - ax4[0].plot(alt_simulate, alpha_simulate, "g-") - - ax4[1].plot(alt_solved, gamma_solved, "go") - ax4[1].plot(alt_simulate, gamma_simulate, "g-") - - c1 = ax4[2].plot(alt_solved, theta_solved, "go") - c2 = ax4[2].plot(alt_simulate, theta_simulate, "g-") - c3 = ax4[2].plot(alt_simulate, theta_max, "r-") - - ax4[0].set_xlabel("alt (ft)") - ax4[1].set_xlabel("alt (ft)") - ax4[2].set_xlabel("alt (ft)") - - plt_name = "plots/desc2_angles_alt.pdf" - - ax4[0].set_ylabel("alpha (deg)") - ax4[1].set_ylabel("gamma (deg)") - ax4[2].set_ylabel("theta (deg)") - fig4.legend( - [c1, c2, c3], labels=["Optimized", "Simulated", "maximum"] - ) - plt.savefig(plt_name) - - fig5, ax5 = plt.subplots(2, 1, sharex=True) - fig5.suptitle("Alpha Values") - - f1 = ax5[0].plot(alpha_solved, CL_solved, "ro") - - ax5[1].plot(alpha_solved, CD_solved, "ro") - - ax5[0].set_xlabel("alpha (deg)") - ax5[1].set_xlabel("alpha (deg)") - - ax5[0].set_ylabel("CL") - ax5[1].set_ylabel("CD") - fig5.legend([f1, f2], labels=["Optimized"]) - plt.savefig("plots/desc2_alphas.pdf") - - return prob diff --git a/aviary/mission/gasp_based/phases/run_phases/run_groundroll.py b/aviary/mission/gasp_based/phases/run_phases/run_groundroll.py deleted file mode 100644 index 3f57333d4..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_groundroll.py +++ /dev/null @@ -1,136 +0,0 @@ -import dymos as dm -import openmdao.api as om - -from aviary.constants import GRAV_ENGLISH_LBM, RHO_SEA_LEVEL_ENGLISH -from aviary.mission.gasp_based.phases.groundroll_phase import get_groundroll -from aviary.variable_info.variables import Aircraft, Mission -from aviary.variable_info.options import get_option_defaults - - -def run_groundroll(): - num_segments = 5 - transcription = dm.Radau( - num_segments=num_segments, order=3, compressed=True, solve_segments=False - ) - - ode_args = dict( - aviary_options=get_option_defaults() - ) - - groundroll = get_groundroll( - fix_initial=True, - fix_initial_mass=True, - connect_initial_mass=False, - ode_args=ode_args, - transcription=transcription, - ) - - p = om.Problem() - - # calculate speed at which to initiate rotation - p.model.add_subsystem( - "vrot", - om.ExecComp( - "Vrot = ((2 * mass * g) / (rho * wing_area * CLmax))**0.5 + dV1 + dVR", - Vrot={"units": "ft/s"}, - mass={"units": "lbm"}, - g={"units": "lbf/lbm", "val": GRAV_ENGLISH_LBM}, - rho={"units": "slug/ft**3", "val": RHO_SEA_LEVEL_ENGLISH}, - wing_area={"units": "ft**2"}, - dV1={ - "units": "ft/s", - "desc": "Increment of engine failure decision speed above stall", - }, - dVR={ - "units": "ft/s", - "desc": "Increment of takeoff rotation speed above engine failure " - "decision speed", - }, - ), - promotes_inputs=[ - ("wing_area", Aircraft.Wing.AREA), - "mass", - "dV1", - "dVR", - ("CLmax", Mission.Takeoff.LIFT_COEFFICIENT_MAX), - ], - promotes_outputs=[("Vrot", Mission.Takeoff.ROTATION_VELOCITY)] - ) - - traj = p.model.add_subsystem("traj", dm.Trajectory()) - traj.add_phase("groundroll", groundroll) - - p.model.add_subsystem( - "groundroll_boundary", - om.EQConstraintComp( - "TAS", - eq_units="ft/s", - normalize=True, - add_constraint=True, - ), - ) - p.model.connect(Mission.Takeoff.ROTATION_VELOCITY, "groundroll_boundary.rhs:TAS") - p.model.connect("traj.groundroll.states:TAS", "groundroll_boundary.lhs:TAS", - src_indices=[-1], - flat_src_indices=True, - ) - - groundroll.add_objective('time', ref=1.0) - - p.driver = om.pyOptSparseDriver() - p.driver.declare_coloring() - p.driver.options["print_results"] = p.comm.rank == 0 - - optimizer = 'IPOPT' - print_opt_iters = True - - p.driver.options["optimizer"] = optimizer - - if optimizer == "SNOPT": - p.driver.opt_settings["Major optimality tolerance"] = 3e-4 - p.driver.opt_settings["Major feasibility tolerance"] = 1e-6 - p.driver.opt_settings["Function precision"] = 1e-6 - p.driver.opt_settings["Linesearch tolerance"] = 0.99 - if print_opt_iters: - p.driver.opt_settings["iSumm"] = 6 - p.driver.opt_settings["Major iterations limit"] = 75 - elif optimizer == "IPOPT": - p.driver.opt_settings["max_iter"] = 500 - p.driver.opt_settings["tol"] = 1e-5 - if print_opt_iters: - p.driver.opt_settings["print_level"] = 5 - p.driver.opt_settings[ - "nlp_scaling_method" - ] = "gradient-based" # for faster convergence - p.driver.opt_settings["alpha_for_y"] = "safer-min-dual-infeas" - p.driver.opt_settings["mu_strategy"] = "monotone" - p.driver.opt_settings["bound_mult_init_method"] = "mu-based" - - p.set_solver_print(level=-1) - - p.setup() - - p.set_val("traj.groundroll.states:TAS", 0, units="kn") - p.set_val( - "traj.groundroll.states:mass", - groundroll.interp("mass", [175100, 174000]), - units="lbm", - ) - p.set_val('mass', 175100, units='lbm') - p.set_val("dV1", 10, units="kn") - p.set_val("dVR", 5, units="kn") - p.set_val(Aircraft.Wing.AREA, units="ft**2", val=1370) - p.set_val(Mission.Takeoff.LIFT_COEFFICIENT_MAX, 2.1886) - - p.set_val( - "traj.groundroll.states:distance", - groundroll.interp("distance", [0, 1000]), - units="ft", - ) - - p.set_val("traj.groundroll.t_duration", 27.7) - - p.run_driver() - # simout = traj.simulate(atol=1e-6, rtol=1e-6) - - return p diff --git a/aviary/mission/gasp_based/phases/run_phases/run_rotation.py b/aviary/mission/gasp_based/phases/run_phases/run_rotation.py deleted file mode 100644 index ce2fb8130..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_rotation.py +++ /dev/null @@ -1,53 +0,0 @@ -import dymos as dm -import openmdao.api as om - -from aviary.mission.gasp_based.phases.rotation_phase import get_rotation -from aviary.variable_info.options import get_option_defaults - - -def run_rotation(make_plots=False): - rotation_trans = dm.Radau( - num_segments=1, order=3, compressed=True, solve_segments=False - ) - rotation = get_rotation( - ode_args=get_option_defaults(), - fix_initial=True, - connect_initial_mass=False, - transcription=rotation_trans, - ) - - p = om.Problem() - traj = p.model.add_subsystem("traj", dm.Trajectory()) - traj.add_phase("rotation", rotation) - - p.driver = om.pyOptSparseDriver() - p.driver.options["optimizer"] = "SNOPT" - p.driver.opt_settings["iSumm"] = 6 - p.driver.declare_coloring() - - p.model.add_objective( - "traj.rotation.timeseries.states:mass", index=-1, ref=1.4e5, ref0=1.3e5 - ) - p.set_solver_print(level=-1) - - p.setup() - - p.set_val( - "traj.rotation.states:alpha", rotation.interp("alpha", [0, 2.5]), units="deg" - ) - p.set_val("traj.rotation.states:TAS", 143, units="kn") - p.set_val( - "traj.rotation.states:mass", - rotation.interp("mass", [174975.12776915, 174000]), - units="lbm", - ) - p.set_val( - "traj.rotation.states:distance", - rotation.interp("distance", [3680.37217765, 4000]), - units="ft", - ) - p.set_val("traj.rotation.t_duration", 50.0) - - dm.run_problem(p, simulate=True, make_plots=make_plots) - - return p diff --git a/aviary/mission/gasp_based/phases/run_phases/run_takeoff.py b/aviary/mission/gasp_based/phases/run_phases/run_takeoff.py deleted file mode 100644 index 1b859eb1c..000000000 --- a/aviary/mission/gasp_based/phases/run_phases/run_takeoff.py +++ /dev/null @@ -1,196 +0,0 @@ -import dymos as dm -import openmdao.api as om - -from aviary.mission.gasp_based.phases.ascent_phase import get_ascent -from aviary.mission.gasp_based.phases.groundroll_phase import get_groundroll -from aviary.mission.gasp_based.phases.rotation_phase import get_rotation -from aviary.mission.gasp_based.polynomial_fit import PolynomialFit -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Dynamic, Mission - - -def run_takeoff(make_plots=False): - option_defaults = get_option_defaults() - groundroll_trans = dm.Radau( - num_segments=1, order=3, compressed=True, solve_segments=False - ) - groundroll = get_groundroll( - ode_args=option_defaults, - fix_initial=True, - connect_initial_mass=False, - transcription=groundroll_trans, - ) - - p = om.Problem() - - p.model.add_subsystem( - "event_xform", - om.ExecComp( - ["t_init_gear=m*tau_gear+b", "t_init_flaps=m*tau_flaps+b"], units="s" - ), - promotes_inputs=[ - "tau_gear", - "tau_flaps", - ("m", Mission.Takeoff.ASCENT_DURATION), - ("b", "ascent:t_initial"), - ], - promotes_outputs=["t_init_gear", "t_init_flaps"], - ) - - p.model.add_design_var("ascent:t_initial", lower=0, upper=100) - p.model.add_design_var(Mission.Takeoff.ASCENT_DURATION, lower=1, upper=1000) - - traj = p.model.add_subsystem("traj", dm.Trajectory()) - traj.add_phase("groundroll", groundroll) - - rotation_trans = dm.Radau( - num_segments=1, order=3, compressed=True, solve_segments=False - ) - rotation = get_rotation( - ode_args=option_defaults, - fix_initial=False, - connect_initial_mass=False, - transcription=rotation_trans, - ) - traj.add_phase("rotation", rotation) - traj.link_phases(["groundroll", "rotation"], ["time", "TAS", "mass", "distance"]) - - ascent_trans = dm.Radau( - num_segments=2, order=5, compressed=True, solve_segments=False - ) - ascent = get_ascent( - ode_args=option_defaults, - fix_initial=False, - connect_initial_mass=False, - transcription=ascent_trans, - ) - traj.add_phase("ascent", ascent) - traj.link_phases( - ["rotation", "ascent"], ["time", "TAS", "mass", "distance", "alpha"] - ) - p.model.promotes( - "traj", - inputs=[ - ("ascent.parameters:t_init_gear", "t_init_gear"), - ("ascent.parameters:t_init_flaps", "t_init_flaps"), - ("ascent.t_initial", "ascent:t_initial"), - ("ascent.t_duration", Mission.Takeoff.ASCENT_DURATION), - ], - ) - - p.model.promotes( - "traj", - inputs=[ - ( - "groundroll.parameters:" + Mission.Design.GROSS_MASS, - Mission.Design.GROSS_MASS, - ), - ( - "rotation.parameters:" + Mission.Design.GROSS_MASS, - Mission.Design.GROSS_MASS, - ), - ( - "ascent.parameters:" + Mission.Design.GROSS_MASS, - Mission.Design.GROSS_MASS, - ), - ], - ) - p.model.set_input_defaults(Mission.Design.GROSS_MASS, 175400, units="lbm") - - p.model.set_input_defaults("ascent:t_initial", val=10.0) - p.model.set_input_defaults(Mission.Takeoff.ASCENT_DURATION, val=30.0) - - ascent_tx = ascent.options["transcription"] - ascent_num_nodes = ascent_tx.grid_data.num_nodes - p.model.add_subsystem( - "h_fit", - PolynomialFit(N_cp=ascent_num_nodes), - promotes_inputs=["t_init_gear", "t_init_flaps"], - ) - p.model.connect("traj.ascent.timeseries.time", "h_fit.time_cp") - p.model.connect("traj.ascent.timeseries.states:altitude", "h_fit.h_cp") - - # TODO: re-parameterize time to be 0-1 (i.e. add a component that offsets by t_initial/t_duration) - p.model.add_design_var("tau_gear", lower=0.01, upper=0.75, units="s", ref=1) - p.model.add_constraint("h_fit.h_init_gear", equals=50.0, units="ft", ref=50.0) - - p.model.add_design_var("tau_flaps", lower=0.3, upper=1.0, units="s", ref=1) - p.model.add_constraint("h_fit.h_init_flaps", equals=400.0, units="ft", ref=400.0) - - p.model.add_objective( - "traj.ascent.timeseries.states:mass", index=-1, ref0=-1.747e5, ref=-1.749e5 - ) - - p.driver = om.pyOptSparseDriver() - p.driver.options["optimizer"] = "SNOPT" - p.driver.opt_settings["Major feasibility tolerance"] = 1.0e-7 - p.driver.opt_settings["Major optimality tolerance"] = 1.0e-2 - p.driver.opt_settings["Function precision"] = 1e-6 - p.driver.opt_settings["Linesearch tolerance"] = 0.95 - p.driver.opt_settings["iSumm"] = 6 - p.driver.declare_coloring() - - p.model.linear_solver = om.DirectSolver() - - p.setup() - - p.set_val("traj.groundroll.states:TAS", 0, units="kn") - p.set_val( - "traj.groundroll.states:mass", - groundroll.interp("mass", [175100, 174000]), - units="lbm", - ) - p.set_val( - "traj.groundroll.states:distance", - groundroll.interp("distance", [0, 1000]), - units="ft", - ) - p.set_val("traj.groundroll.t_duration", 50.0) - - p.set_val( - "traj.rotation.states:alpha", rotation.interp("alpha", [0, 2.5]), units="deg" - ) - p.set_val("traj.rotation.states:TAS", 143, units="kn") - p.set_val( - "traj.rotation.states:mass", - rotation.interp("mass", [174975.12776915, 174000]), - units="lbm", - ) - p.set_val( - "traj.rotation.states:distance", - rotation.interp("distance", [3680.37217765, 4000]), - units="ft", - ) - p.set_val("traj.rotation.t_duration", 50.0) - - p.set_val( - "traj.ascent.states:altitude", - ascent.interp(Dynamic.Mission.ALTITUDE, ys=[0, 100, 500], xs=[0, 1, 10]), - units="ft", - ) - p.set_val("traj.ascent.states:flight_path_angle", 0.0, units="rad") - p.set_val( - "traj.ascent.states:TAS", ascent.interp("TAS", [153.3196491, 500]), units="kn" - ) - p.set_val( - "traj.ascent.states:mass", - ascent.interp("mass", [174963.74211336, 174000]), - units="lbm", - ) - p.set_val( - "traj.ascent.states:distance", - ascent.interp("distance", [4330.83393029, 5000]), - units="ft", - ) - p.set_val("traj.ascent.t_initial", 31.2) - p.set_val("traj.ascent.t_duration", 10.0) - - # event trigger times - p.set_val("tau_gear", 0.2) # initial guess - p.set_val("tau_flaps", 0.5) # initial guess - - p.set_solver_print(level=-1) - - dm.run_problem(p, simulate=True, make_plots=make_plots) - - return p diff --git a/aviary/mission/gasp_based/phases/test/test_breguet.py b/aviary/mission/gasp_based/phases/test/test_breguet.py index 23b4238ab..0f5e208d7 100644 --- a/aviary/mission/gasp_based/phases/test/test_breguet.py +++ b/aviary/mission/gasp_based/phases/test/test_breguet.py @@ -8,7 +8,6 @@ from aviary.constants import GRAV_ENGLISH_LBM from aviary.mission.gasp_based.phases.breguet import RangeComp -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Dynamic @@ -46,11 +45,6 @@ def test_case1(self): partial_data = self.prob.check_partials(method="cs") # , out_stream=None) assert_check_partials(partial_data, atol=tol, rtol=tol) - @skipIfMissingXDSM('cruise_specs/breguet_eom.json') - def test_climb_spec(self): - subsystem = self.prob.model - assert_match_spec(subsystem, "cruise_specs/breguet_eom.json") - class TestBreguetPartials(unittest.TestCase): def setUp(self): diff --git a/aviary/mission/gasp_based/phases/test/test_landing_components.py b/aviary/mission/gasp_based/phases/test/test_landing_components.py index ee3cd40da..173e593d8 100644 --- a/aviary/mission/gasp_based/phases/test/test_landing_components.py +++ b/aviary/mission/gasp_based/phases/test/test_landing_components.py @@ -8,7 +8,6 @@ from aviary.mission.gasp_based.phases.landing_components import ( GlideConditionComponent, LandingAltitudeComponent, LandingGroundRollComponent) -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Mission @@ -38,11 +37,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('landing_specs/landing_alt.json') - def test_alt_spec(self): - subsystem = self.prob.model - assert_match_spec(subsystem, "landing_specs/landing_alt.json") - class GlideTestCase(unittest.TestCase): def setUp(self): @@ -116,11 +110,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-10, rtol=1e-12) - @skipIfMissingXDSM('landing_specs/glide.json') - def test_alt_spec(self): - subsystem = self.prob.model - assert_match_spec(subsystem, "landing_specs/glide.json") - class GroundRollTestCase(unittest.TestCase): def setUp(self): @@ -168,11 +157,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=5e-12, rtol=1e-12) - @skipIfMissingXDSM('landing_specs/groundroll.json') - def test_alt_spec(self): - subsystem = self.prob.model - assert_match_spec(subsystem, "landing_specs/groundroll.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/phases/test/test_landing_group.py b/aviary/mission/gasp_based/phases/test/test_landing_group.py index 4d3339362..6ed7c3265 100644 --- a/aviary/mission/gasp_based/phases/test/test_landing_group.py +++ b/aviary/mission/gasp_based/phases/test/test_landing_group.py @@ -9,9 +9,7 @@ from aviary.mission.gasp_based.phases.landing_group import LandingSegment from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import (assert_match_spec, - check_prob_outputs, - skipIfMissingXDSM) +from aviary.utils.test_utils.IO_test_util import check_prob_outputs from aviary.variable_info.variables import Dynamic, Mission @@ -55,11 +53,6 @@ def test_dland(self): ) assert_check_partials(partial_data, atol=1e-6, rtol=1e-6) - @skipIfMissingXDSM('statics_specs/landing.json') - def test_dland_spec(self): - subsystem = self.prob.model - assert_match_spec(subsystem, "statics_specs/landing.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/phases/test/test_taxi_component.py b/aviary/mission/gasp_based/phases/test/test_taxi_component.py index 37e3a0e56..01339e7be 100644 --- a/aviary/mission/gasp_based/phases/test/test_taxi_component.py +++ b/aviary/mission/gasp_based/phases/test/test_taxi_component.py @@ -7,7 +7,6 @@ from aviary.utils.aviary_values import AviaryValues from aviary.mission.gasp_based.phases.taxi_component import TaxiFuelComponent -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Dynamic, Mission @@ -33,11 +32,6 @@ def test_fuel_consumed(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - @skipIfMissingXDSM('taxi_specs/fuel.json') - def test_fuel_spec(self): - subsystem = self.prob.model - assert_match_spec(subsystem, "taxi_specs/fuel.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/phases/test/test_taxi_group.py b/aviary/mission/gasp_based/phases/test/test_taxi_group.py index 4d3339362..6ed7c3265 100644 --- a/aviary/mission/gasp_based/phases/test/test_taxi_group.py +++ b/aviary/mission/gasp_based/phases/test/test_taxi_group.py @@ -9,9 +9,7 @@ from aviary.mission.gasp_based.phases.landing_group import LandingSegment from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import (assert_match_spec, - check_prob_outputs, - skipIfMissingXDSM) +from aviary.utils.test_utils.IO_test_util import check_prob_outputs from aviary.variable_info.variables import Dynamic, Mission @@ -55,11 +53,6 @@ def test_dland(self): ) assert_check_partials(partial_data, atol=1e-6, rtol=1e-6) - @skipIfMissingXDSM('statics_specs/landing.json') - def test_dland_spec(self): - subsystem = self.prob.model - assert_match_spec(subsystem, "statics_specs/landing.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/gasp_based/phases/time_integration_phases.py b/aviary/mission/gasp_based/phases/time_integration_phases.py index 1d4a784c7..a972070cd 100644 --- a/aviary/mission/gasp_based/phases/time_integration_phases.py +++ b/aviary/mission/gasp_based/phases/time_integration_phases.py @@ -9,8 +9,7 @@ from aviary.mission.gasp_based.phases.landing_group import LandingSegment from aviary.mission.gasp_based.ode.groundroll_ode import GroundrollODE from aviary.mission.gasp_based.ode.rotation_ode import RotationODE -from aviary.mission.gasp_based.ode.time_integration_base_classes import ( - SGMTrajBase, SimuPyProblem) +from aviary.mission.gasp_based.ode.time_integration_base_classes import SimuPyProblem from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.enums import AlphaModes, AnalysisScheme, SpeedType from aviary.variable_info.variables import Aircraft, Dynamic, Mission @@ -36,11 +35,16 @@ def __init__( super().__init__( GroundrollODE(analysis_scheme=AnalysisScheme.SHOOTING, **ode_args), - output_names=["normal_force"], - alternate_state_names={Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: Dynamic.Mission.MASS, - 'TAS': 'TAS'}, + outputs=["normal_force"], + states=[ + Dynamic.Mission.MASS, + Dynamic.Mission.DISTANCE, + Dynamic.Mission.ALTITUDE, + 'TAS' + ], + # state_units=['lbm','nmi','ft','ft/s'], alternate_state_rate_names={ - Dynamic.Mission.MASS_RATE: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, + Dynamic.Mission.MASS: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, **simupy_args, ) @@ -72,11 +76,15 @@ def __init__( ): super().__init__( RotationODE(analysis_scheme=AnalysisScheme.SHOOTING, **ode_args), - output_names=["normal_force", "alpha"], - alternate_state_names={ - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: Dynamic.Mission.MASS}, + outputs=["normal_force", "alpha"], + states=[ + Dynamic.Mission.MASS, + Dynamic.Mission.DISTANCE, + Dynamic.Mission.ALTITUDE, + ], + # state_units=['lbm','nmi','ft'], alternate_state_rate_names={ - Dynamic.Mission.MASS_RATE: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, + Dynamic.Mission.MASS: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, **simupy_args, ) @@ -117,21 +125,25 @@ def __init__( ode_args={}, simupy_args={}, ): - control_names = None + controls = None super().__init__( AscentODE(analysis_scheme=AnalysisScheme.SHOOTING, alpha_mode=alpha_mode, **ode_args), - output_names=[ + outputs=[ "load_factor", "fuselage_pitch", "normal_force", "alpha", ], - alternate_state_names={ - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: Dynamic.Mission.MASS}, + states=[ + Dynamic.Mission.MASS, + Dynamic.Mission.DISTANCE, + Dynamic.Mission.ALTITUDE, + ], + # state_units=['lbm','nmi','ft'], alternate_state_rate_names={ - Dynamic.Mission.MASS_RATE: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, - control_names=control_names, + Dynamic.Mission.MASS: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, + controls=controls, **simupy_args, ) @@ -246,7 +258,7 @@ def set_val(self, *args, **kwargs): ode.set_val(*args, **kwargs) def compute_alpha(self, ode, t, x): - return ode.output_equation_function(t, x)[ode.output_names.index("alpha")] + return ode.output_equation_function(t, x)[list(ode.outputs.keys()).index("alpha")] def get_alpha(self, t, x): a_key = (t,) + tuple(x) @@ -365,11 +377,15 @@ def __init__( ode = AccelODE(analysis_scheme=AnalysisScheme.SHOOTING, **ode_args) super().__init__( ode, - output_names=["EAS", "mach", "alpha"], - alternate_state_names={ - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: Dynamic.Mission.MASS}, + outputs=["EAS", "mach", "alpha"], + states=[ + Dynamic.Mission.MASS, + Dynamic.Mission.DISTANCE, + Dynamic.Mission.ALTITUDE, + ], + # state_units=['lbm','nmi','ft'], alternate_state_rate_names={ - Dynamic.Mission.MASS_RATE: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, + Dynamic.Mission.MASS: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, **simupy_args, ) self.phase_name = phase_name @@ -420,7 +436,7 @@ def __init__( self.alt_trigger_units = alt_trigger_units super().__init__( ode, - output_names=[ + outputs=[ "alpha", Dynamic.Mission.FLIGHT_PATH_ANGLE, "required_lift", @@ -432,10 +448,14 @@ def __init__( "drag", Dynamic.Mission.ALTITUDE_RATE, ], - alternate_state_names={ - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: Dynamic.Mission.MASS}, + states=[ + Dynamic.Mission.MASS, + Dynamic.Mission.DISTANCE, + Dynamic.Mission.ALTITUDE, + ], + # state_units=['lbm','nmi','ft'], alternate_state_rate_names={ - Dynamic.Mission.MASS_RATE: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, + Dynamic.Mission.MASS: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, **simupy_args, ) self.phase_name = phase_name @@ -487,7 +507,7 @@ def __init__( super().__init__( ode, - output_names=[ + outputs=[ "alpha", # ? "lift", "EAS", @@ -496,10 +516,14 @@ def __init__( "drag", Dynamic.Mission.ALTITUDE_RATE, ], - alternate_state_names={ - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: Dynamic.Mission.MASS}, + states=[ + Dynamic.Mission.MASS, + Dynamic.Mission.DISTANCE, + Dynamic.Mission.ALTITUDE, + ], + # state_units=['lbm','nmi','ft'], alternate_state_rate_names={ - Dynamic.Mission.MASS_RATE: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, + Dynamic.Mission.MASS: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, **simupy_args, ) @@ -561,7 +585,7 @@ def __init__( self.alt_trigger_units = alt_trigger_units super().__init__( ode, - output_names=[ + outputs=[ "alpha", "required_lift", "lift", @@ -571,10 +595,14 @@ def __init__( "drag", Dynamic.Mission.ALTITUDE_RATE, ], - alternate_state_names={ - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL: Dynamic.Mission.MASS}, + states=[ + Dynamic.Mission.MASS, + Dynamic.Mission.DISTANCE, + Dynamic.Mission.ALTITUDE, + ], + # state_units=['lbm','nmi','ft'], alternate_state_rate_names={ - Dynamic.Mission.MASS_RATE: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, + Dynamic.Mission.MASS: Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL}, **simupy_args, ) diff --git a/aviary/mission/gasp_based/phases/time_integration_traj.py b/aviary/mission/gasp_based/phases/time_integration_traj.py index cc136d943..5e1884012 100644 --- a/aviary/mission/gasp_based/phases/time_integration_traj.py +++ b/aviary/mission/gasp_based/phases/time_integration_traj.py @@ -29,8 +29,10 @@ class FlexibleTraj(TimeIntegrationTrajBase): def initialize(self): super().initialize() self.options.declare('Phases', default=None) + self.options.declare('promote_all_auto_ivc', default=False) self.options.declare('traj_final_state_output', default=None) self.options.declare('traj_promote_final_output', default=None) + self.options.declare('traj_promote_initial_input', default=None) self.options.declare('traj_initial_state_input', default=None) self.options.declare('traj_event_trigger_input', default=None) @@ -44,9 +46,11 @@ def setup(self): self.setup_params( ODEs=ODEs, + promote_all_auto_ivc=self.options['promote_all_auto_ivc'], traj_final_state_output=self.options['traj_final_state_output'], traj_promote_final_output=self.options['traj_promote_final_output'], + traj_promote_initial_input=self.options['traj_promote_initial_input'], traj_initial_state_input=self.options['traj_initial_state_input'], traj_event_trigger_input=self.options['traj_event_trigger_input'], ) @@ -99,9 +103,7 @@ def compute(self, inputs, outputs): print('t_final', t_final) print('x_final', x_final) - print('states', self.ODEs[-1].state_names) - print('units', self.ODEs[-1].state_units) - + print(self.ODEs[-1].states) # class SGMTraj1(TimeIntegrationTrajBase): # ''' diff --git a/aviary/mission/gasp_based/test/test_flight_conditions.py b/aviary/mission/gasp_based/test/test_flight_conditions.py index 6cc358937..b99db957a 100644 --- a/aviary/mission/gasp_based/test/test_flight_conditions.py +++ b/aviary/mission/gasp_based/test/test_flight_conditions.py @@ -7,7 +7,6 @@ assert_near_equal) from aviary.mission.gasp_based.flight_conditions import FlightConditions -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.enums import SpeedType from aviary.variable_info.variables import Dynamic @@ -48,13 +47,6 @@ def test_case1(self): assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('accel_specs/fc.json') - def test_fc_spec1(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "accel_specs/fc.json") - class FlightConditionsTestCase2(unittest.TestCase): def setUp(self): @@ -91,13 +83,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('climb_specs/fc.json') - def test_fc_spec2(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "climb_specs/fc.json") - class FlightConditionsTestCase3(unittest.TestCase): def setUp(self): @@ -135,13 +120,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('cruise_specs/fc.json') - def test_fc_spec3(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "cruise_specs/fc.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/mission/initial_guess_builders.py b/aviary/mission/initial_guess_builders.py new file mode 100644 index 000000000..4de2e9a02 --- /dev/null +++ b/aviary/mission/initial_guess_builders.py @@ -0,0 +1,179 @@ +''' +Define utilities for building phases. + +Classes +------- +InitialGuess : a utility for setting an initial guess on a problem + +InitialGuessControl : a utility for setting an initial guess for a control on a problem + +InitialGuessParameter : a utility for setting an initial guess for a parameter on a +problem + +InitialGuessPolynomialControl : a utility for setting an initial guess for a polynomial +control on a problem + +InitialGuessState : a utility for setting an initial guess for a state on a problem + +InitialGuessTime : a utility for setting guesses for initial time and duration on a +problem +''' +from collections.abc import Sequence + +import dymos as dm +import numpy as np +import openmdao.api as om + + +class InitialGuess: + ''' + Define a utility for setting an initial guess on a problem. + + Attributes + ---------- + key : str + base name for the initial guess + ''' + __slots__ = ('key',) + + def __init__(self, key): + self.key = key + + def apply_initial_guess( + self, prob: om.Problem, traj_name, phase: dm.Phase, phase_name, val, units + ): + ''' + Set the initial guess on the problem. + ''' + complete_key = self._get_complete_key(traj_name, phase_name) + + # TODO: this is a short term hack in need of an appropriate long term solution + # - to interpolate, or not to interpolate: that is the question + # - the solution should probably be a value decoration (a wrapper) that is + # both lightweight and easy to check and unpack + if ( + isinstance(val, np.ndarray) + or (isinstance(val, Sequence) and not isinstance(val, str)) + ): + val = phase.interp(self.key, val) + + prob.set_val(complete_key, val, units) + + def _get_complete_key(self, traj_name, phase_name): + ''' + Compose the complete key for setting the initial guess. + ''' + _ = traj_name + _ = phase_name + + return self.key + + +class InitialGuessControl(InitialGuess): + ''' + Define a utility for setting an initial guess for a control on a problem. + + Attributes + ---------- + key : str + base name for the control + ''' + __slots__ = () + + def _get_complete_key(self, traj_name, phase_name): + ''' + Compose the complete key for setting the control initial guess. + ''' + key = f'{traj_name}.{phase_name}.controls:{self.key}' + + return key + + +class InitialGuessParameter(InitialGuess): + ''' + Define a utility for setting an initial guess for a parameter on a problem. + + Attributes + ---------- + key : str + base name for the parameter + ''' + __slots__ = () + + def _get_complete_key(self, traj_name, phase_name): + ''' + Compose the complete key for setting the parameter initial guess. + ''' + key = f'{traj_name}.{phase_name}.parameters:{self.key}' + + return key + + +class InitialGuessPolynomialControl(InitialGuess): + ''' + Define a utility for setting an initial guess for a polynomial control on a problem. + + Attributes + ---------- + key : str + base name for the polynomial control + ''' + __slots__ = () + + def _get_complete_key(self, traj_name, phase_name): + ''' + Compose the complete key for setting the polynomial control initial guess. + ''' + key = f'{traj_name}.{phase_name}.polynomial_controls:{self.key}' + + return key + + +class InitialGuessState(InitialGuess): + ''' + Define a utility for setting an initial guess for a state on a problem. + + Attributes + ---------- + key : str + base name for the state + ''' + __slots__ = () + + def _get_complete_key(self, traj_name, phase_name): + ''' + Compose the complete key for setting the state initial guess. + ''' + key = f'{traj_name}.{phase_name}.states:{self.key}' + + return key + + +class InitialGuessTime(InitialGuess): + ''' + Define a utility for setting guesses for initial time and duration on a problem. + + Attributes + ---------- + key : str ('times') + the group identifier for guesses for initial time and duration + ''' + __slots__ = () + + def __init__(self, key='times'): + super().__init__(key) + + def apply_initial_guess( + self, prob: om.Problem, traj_name, phase: dm.Phase, phase_name, val, units + ): + ''' + Set the guesses for initial time and duration on the problem. + ''' + _ = phase + + name = f'{traj_name}.{phase_name}.t_initial' + t_initial, t_duration = val + prob.set_val(name, t_initial, units) + + name = f'{traj_name}.{phase_name}.t_duration' + prob.set_val(name, t_duration, units) diff --git a/aviary/mission/flops_based/phases/benchmark_tests/__init__.py b/aviary/mission/ode/__init__.py similarity index 100% rename from aviary/mission/flops_based/phases/benchmark_tests/__init__.py rename to aviary/mission/ode/__init__.py diff --git a/aviary/mission/flops_based/ode/altitude_rate.py b/aviary/mission/ode/altitude_rate.py similarity index 100% rename from aviary/mission/flops_based/ode/altitude_rate.py rename to aviary/mission/ode/altitude_rate.py diff --git a/aviary/mission/flops_based/ode/specific_energy_rate.py b/aviary/mission/ode/specific_energy_rate.py similarity index 100% rename from aviary/mission/flops_based/ode/specific_energy_rate.py rename to aviary/mission/ode/specific_energy_rate.py diff --git a/aviary/mission/gasp_based/phases/benchmark/__init__.py b/aviary/mission/ode/test/__init__.py similarity index 100% rename from aviary/mission/gasp_based/phases/benchmark/__init__.py rename to aviary/mission/ode/test/__init__.py diff --git a/aviary/mission/flops_based/ode/test/test_altitude_rate.py b/aviary/mission/ode/test/test_altitude_rate.py similarity index 95% rename from aviary/mission/flops_based/ode/test/test_altitude_rate.py rename to aviary/mission/ode/test/test_altitude_rate.py index 5e6958b39..e6d33d548 100644 --- a/aviary/mission/flops_based/ode/test/test_altitude_rate.py +++ b/aviary/mission/ode/test/test_altitude_rate.py @@ -2,7 +2,7 @@ import openmdao.api as om -from aviary.mission.flops_based.ode.altitude_rate import AltitudeRate +from aviary.mission.ode.altitude_rate import AltitudeRate from aviary.utils.test_utils.variable_test import assert_match_varnames from aviary.validation_cases.validation_data.flops_data.full_mission_test_data import \ data diff --git a/aviary/mission/flops_based/ode/test/test_specific_energy_rate.py b/aviary/mission/ode/test/test_specific_energy_rate.py similarity index 94% rename from aviary/mission/flops_based/ode/test/test_specific_energy_rate.py rename to aviary/mission/ode/test/test_specific_energy_rate.py index 4c5afa7ec..19a2eca32 100644 --- a/aviary/mission/flops_based/ode/test/test_specific_energy_rate.py +++ b/aviary/mission/ode/test/test_specific_energy_rate.py @@ -2,7 +2,7 @@ import openmdao.api as om -from aviary.mission.flops_based.ode.specific_energy_rate import SpecificEnergyRate +from aviary.mission.ode.specific_energy_rate import SpecificEnergyRate from aviary.utils.test_utils.variable_test import assert_match_varnames from aviary.validation_cases.validation_data.flops_data.full_mission_test_data import \ data diff --git a/aviary/mission/flops_based/phases/phase_builder_base.py b/aviary/mission/phase_builder_base.py similarity index 65% rename from aviary/mission/flops_based/phases/phase_builder_base.py rename to aviary/mission/phase_builder_base.py index b299689c5..ea9007fa4 100644 --- a/aviary/mission/flops_based/phases/phase_builder_base.py +++ b/aviary/mission/phase_builder_base.py @@ -3,33 +3,20 @@ Classes ------- -InitialGuess : a utility for setting an initial guess on a problem - -InitialGuessControl : a utility for setting an initial guess for a control on a problem - -InitialGuessParameter : a utility for setting an initial guess for a parameter on a -problem - -InitialGuessPolynomialControl : a utility for setting an initial guess for a polynomial -control on a problem - -InitialGuessState : a utility for setting an initial guess for a state on a problem - -InitialGuessTime : a utility for setting guesses for initial time and duration on a -problem - PhaseBuilderBase : the interface for a phase builder ''' -from abc import ABC, abstractmethod +from abc import ABC from collections import namedtuple -from collections.abc import Sequence + +from aviary.mission.initial_guess_builders import InitialGuess +from aviary.variable_info.variables import Dynamic import dymos as dm -import numpy as np import openmdao.api as om from aviary.mission.flops_based.ode.mission_ODE import MissionODE from aviary.utils.aviary_values import AviaryValues, get_keys +from aviary.variable_info.variable_meta_data import _MetaData _require_new_meta_data_class_attr_ = \ namedtuple('_require_new_meta_data_class_attr_', ()) @@ -39,160 +26,6 @@ namedtuple('_require_new_initial_guesses_meta_data_class_attr_', ()) -class InitialGuess: - ''' - Define a utility for setting an initial guess on a problem. - - Attributes - ---------- - key : str - base name for the initial guess - ''' - __slots__ = ('key',) - - def __init__(self, key): - self.key = key - - def apply_initial_guess( - self, prob: om.Problem, traj_name, phase: dm.Phase, phase_name, val, units - ): - ''' - Set the initial guess on the problem. - ''' - complete_key = self._get_complete_key(traj_name, phase_name) - - # TODO: this is a short term hack in need of an appropriate long term solution - # - to interpolate, or not to interpolate: that is the question - # - the solution should probably be a value decoration (a wrapper) that is - # both lightweight and easy to check and unpack - if ( - isinstance(val, np.ndarray) - or (isinstance(val, Sequence) and not isinstance(val, str)) - ): - val = phase.interp(self.key, val) - - prob.set_val(complete_key, val, units) - - def _get_complete_key(self, traj_name, phase_name): - ''' - Compose the complete key for setting the initial guess. - ''' - _ = traj_name - _ = phase_name - - return self.key - - -class InitialGuessControl(InitialGuess): - ''' - Define a utility for setting an initial guess for a control on a problem. - - Attributes - ---------- - key : str - base name for the control - ''' - __slots__ = () - - def _get_complete_key(self, traj_name, phase_name): - ''' - Compose the complete key for setting the control initial guess. - ''' - key = f'{traj_name}.{phase_name}.controls:{self.key}' - - return key - - -class InitialGuessParameter(InitialGuess): - ''' - Define a utility for setting an initial guess for a parameter on a problem. - - Attributes - ---------- - key : str - base name for the parameter - ''' - __slots__ = () - - def _get_complete_key(self, traj_name, phase_name): - ''' - Compose the complete key for setting the parameter initial guess. - ''' - key = f'{traj_name}.{phase_name}.parameters:{self.key}' - - return key - - -class InitialGuessPolynomialControl(InitialGuess): - ''' - Define a utility for setting an initial guess for a polynomial control on a problem. - - Attributes - ---------- - key : str - base name for the polynomial control - ''' - __slots__ = () - - def _get_complete_key(self, traj_name, phase_name): - ''' - Compose the complete key for setting the polynomial control initial guess. - ''' - key = f'{traj_name}.{phase_name}.polynomial_controls:{self.key}' - - return key - - -class InitialGuessState(InitialGuess): - ''' - Define a utility for setting an initial guess for a state on a problem. - - Attributes - ---------- - key : str - base name for the state - ''' - __slots__ = () - - def _get_complete_key(self, traj_name, phase_name): - ''' - Compose the complete key for setting the state initial guess. - ''' - key = f'{traj_name}.{phase_name}.states:{self.key}' - - return key - - -class InitialGuessTime(InitialGuess): - ''' - Define a utility for setting guesses for initial time and duration on a problem. - - Attributes - ---------- - key : str ('times') - the group identifier for guesses for initial time and duration - ''' - __slots__ = () - - def __init__(self, key='times'): - super().__init__(key) - - def apply_initial_guess( - self, prob: om.Problem, traj_name, phase: dm.Phase, phase_name, val, units - ): - ''' - Set the guesses for initial time and duration on the problem. - ''' - _ = phase - - name = f'{traj_name}.{phase_name}.t_initial' - t_initial, t_duration = val - prob.set_val(name, t_initial, units) - - name = f'{traj_name}.{phase_name}.t_duration' - prob.set_val(name, t_duration, units) - - class PhaseBuilderBase(ABC): ''' Define the interface for a phase builder. @@ -205,9 +38,6 @@ class PhaseBuilderBase(ABC): core_subsystems : (None) list of SubsystemBuilderBase objects that will be added to the phase ODE - aero_builder (None) - utility for building and connecting a dynamic aerodynamics analysis component - user_options : AviaryValues () state/path constraint values and flags @@ -232,6 +62,14 @@ class attribute: derived type customization point; the default value class attribute: derived type customization point; the default value for ode_class used by build_phase + is_analytic_phase : bool (False) + class attribute: derived type customization point; if True, build_phase + will return an AnalyticPhase instead of a Phase + + num_nodes : int (5) + class attribute: derived type customization point; the default value + for num_nodes used by build_phase, only for AnalyticPhases + Methods ------- build_phase @@ -241,7 +79,8 @@ class attribute: derived type customization point; the default value ''' __slots__ = ( 'name', 'core_subsystems', 'subsystem_options', 'user_options', - 'initial_guesses', 'ode_class', 'transcription', 'aero_builder' + 'initial_guesses', 'ode_class', 'transcription', + 'is_analytic_phase', 'num_nodes', 'external_subsystems', 'meta_data', ) # region : derived type customization points @@ -252,11 +91,13 @@ class attribute: derived type customization point; the default value default_name = '' default_ode_class = MissionODE + + default_meta_data = _MetaData # endregion : derived type customization points def __init__( - self, name=None, core_subsystems=None, aero_builder=None, user_options=None, initial_guesses=None, - ode_class=None, transcription=None, subsystem_options=None, + self, name=None, core_subsystems=None, user_options=None, initial_guesses=None, + ode_class=None, transcription=None, subsystem_options=None, is_analytic_phase=False, num_nodes=5, external_subsystems=None, meta_data=None, ): if name is None: name = self.default_name @@ -271,9 +112,6 @@ def __init__( if subsystem_options is None: subsystem_options = {} - if aero_builder is not None: - self.aero_builder = aero_builder - self.subsystem_options = subsystem_options self.user_options = user_options @@ -288,6 +126,18 @@ def __init__( self.ode_class = ode_class self.transcription = transcription + self.is_analytic_phase = is_analytic_phase + self.num_nodes = num_nodes + + if external_subsystems is None: + external_subsystems = [] + + self.external_subsystems = external_subsystems + + if meta_data is None: + meta_data = self.default_meta_data + + self.meta_data = meta_data def build_phase(self, aviary_options=None): ''' @@ -314,7 +164,7 @@ def build_phase(self, aviary_options=None): transcription = self.transcription - if transcription is None: + if transcription is None and not self.is_analytic_phase: transcription = self.make_default_transcription() if aviary_options is None: @@ -334,10 +184,17 @@ def build_phase(self, aviary_options=None): kwargs['core_subsystems'] = self.core_subsystems - phase = dm.Phase( - ode_class=ode_class, transcription=transcription, - ode_init_kwargs=kwargs - ) + if self.is_analytic_phase: + phase = dm.AnalyticPhase( + ode_class=ode_class, + ode_init_kwargs=kwargs, + num_nodes=self.num_nodes, + ) + else: + phase = dm.Phase( + ode_class=ode_class, transcription=transcription, + ode_init_kwargs=kwargs + ) # overrides should add state, controls, etc. return phase @@ -481,7 +338,7 @@ def to_phase_info(self): return (self.name, phase_info) @classmethod - def from_phase_info(cls, name, phase_info: dict, core_subsystems=None, meta_data=None): + def from_phase_info(cls, name, phase_info: dict, core_subsystems=None, meta_data=None, transcription=None): ''' Return a new phase builder based on the specified phase info. @@ -518,7 +375,7 @@ def from_phase_info(cls, name, phase_info: dict, core_subsystems=None, meta_data phase_builder = cls( name, subsystem_options=subsystem_options, user_options=user_options, initial_guesses=initial_guesses, meta_data=meta_data, - core_subsystems=core_subsystems, external_subsystems=external_subsystems) + core_subsystems=core_subsystems, external_subsystems=external_subsystems, transcription=transcription) return phase_builder @@ -562,6 +419,127 @@ def _add_initial_guess_meta_data(cls, initial_guess: InitialGuess, desc=None): meta_data[name] = dict( apply_initial_guess=initial_guess.apply_initial_guess, desc=desc) + def set_time_options(self, user_options, targets=[]): + fix_initial = user_options.get_val('fix_initial') + duration_bounds = user_options.get_val('duration_bounds', units='s') + duration_ref = user_options.get_val('duration_ref', units='s') + + self.phase.set_time_options( + fix_initial=fix_initial, + duration_bounds=duration_bounds, + units="s", + targets=targets, + duration_ref=duration_ref, + ) + + def add_TAS_state(self, user_options): + TAS_lower = user_options.get_val('TAS_lower', units='kn') + TAS_upper = user_options.get_val('TAS_upper', units='kn') + TAS_ref = user_options.get_val('TAS_ref', units='kn') + TAS_ref0 = user_options.get_val('TAS_ref0', units='kn') + TAS_defect_ref = user_options.get_val('TAS_defect_ref', units='kn') + self.phase.add_state( + "TAS", + fix_initial=user_options.get_val('fix_initial'), + fix_final=False, + lower=TAS_lower, + upper=TAS_upper, + units="kn", + rate_source="TAS_rate", + targets="TAS", + ref=TAS_ref, + ref0=TAS_ref0, + defect_ref=TAS_defect_ref, + ) + + def add_mass_state(self, user_options): + mass_lower = user_options.get_val('mass_lower', units='lbm') + mass_upper = user_options.get_val('mass_upper', units='lbm') + mass_ref = user_options.get_val('mass_ref', units='lbm') + mass_ref0 = user_options.get_val('mass_ref0', units='lbm') + mass_defect_ref = user_options.get_val('mass_defect_ref', units='lbm') + self.phase.add_state( + Dynamic.Mission.MASS, + fix_initial=user_options.get_val('fix_initial'), + fix_final=False, + lower=mass_lower, + upper=mass_upper, + units="lbm", + rate_source=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, + targets=Dynamic.Mission.MASS, + ref=mass_ref, + ref0=mass_ref0, + defect_ref=mass_defect_ref, + ) + + def add_distance_state(self, user_options): + distance_lower = user_options.get_val('distance_lower', units='NM') + distance_upper = user_options.get_val('distance_upper', units='NM') + distance_ref = user_options.get_val('distance_ref', units='NM') + distance_ref0 = user_options.get_val('distance_ref0', units='NM') + distance_defect_ref = user_options.get_val('distance_defect_ref', units='NM') + self.phase.add_state( + Dynamic.Mission.DISTANCE, + fix_initial=user_options.get_val('fix_initial'), + fix_final=False, + lower=distance_lower, + upper=distance_upper, + units="NM", + rate_source=Dynamic.Mission.DISTANCE_RATE, + ref=distance_ref, + ref0=distance_ref0, + defect_ref=distance_defect_ref, + ) + + def add_flight_path_angle_state(self, user_options): + angle_lower = user_options.get_val('angle_lower', units='rad') + angle_upper = user_options.get_val('angle_upper', units='rad') + angle_ref = user_options.get_val('angle_ref', units='rad') + angle_ref0 = user_options.get_val('angle_ref0', units='rad') + angle_defect_ref = user_options.get_val('angle_defect_ref', units='rad') + self.phase.add_state( + Dynamic.Mission.FLIGHT_PATH_ANGLE, + fix_initial=True, + fix_final=False, + lower=angle_lower, + upper=angle_upper, + units="rad", + rate_source=Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, + ref=angle_ref, + defect_ref=angle_defect_ref, + ref0=angle_ref0, + ) + + def add_altitude_state(self, user_options): + alt_lower = user_options.get_val('alt_lower', units='ft') + alt_upper = user_options.get_val('alt_upper', units='ft') + alt_ref = user_options.get_val('alt_ref', units='ft') + alt_ref0 = user_options.get_val('alt_ref0', units='ft') + alt_defect_ref = user_options.get_val('alt_defect_ref', units='ft') + self.phase.add_state( + Dynamic.Mission.ALTITUDE, + fix_initial=True, + fix_final=False, + lower=alt_lower, + upper=alt_upper, + units="ft", + rate_source=Dynamic.Mission.ALTITUDE_RATE, + ref=alt_ref, + defect_ref=alt_defect_ref, + ref0=alt_ref0, + ) + + def add_altitude_constraint(self, user_options): + final_altitude = user_options.get_val('final_altitude', units='ft') + alt_constraint_ref = user_options.get_val('alt_constraint_ref', units='ft') + self.phase.add_boundary_constraint( + Dynamic.Mission.ALTITUDE, + loc="final", + equals=final_altitude, + units="ft", + ref=alt_constraint_ref, + ) + _registered_phase_builder_types = [] @@ -622,10 +600,3 @@ def phase_info_to_builder(name: str, phase_info: dict) -> PhaseBuilderBase: except Exception as error: print(error) - - try: - PhaseBuilderBase._add_initial_guess_meta_data( - InitialGuessTime(), desc='test initial guess') - - except Exception as error: - print(error) diff --git a/aviary/models/N3CC/N3CC_data.py b/aviary/models/N3CC/N3CC_data.py index a163553ea..b73505170 100644 --- a/aviary/models/N3CC/N3CC_data.py +++ b/aviary/models/N3CC/N3CC_data.py @@ -21,14 +21,10 @@ from aviary.utils.aviary_values import AviaryValues from aviary.utils.preprocessors import preprocess_propulsion from aviary.utils.functions import get_path -from aviary.variable_info.variables import Aircraft -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission -from aviary.variable_info.variables import Settings +from aviary.variable_info.variables import Aircraft, Dynamic, Mission, Settings from aviary.variable_info.enums import EquationsOfMotion, LegacyCode from aviary.interface.default_phase_info.height_energy import default_mission_subsystems -Dynamic = _Dynamic.Mission N3CC = {} inputs = N3CC['inputs'] = AviaryValues() @@ -295,6 +291,7 @@ inputs.set_val(Mission.Design.RANGE, 3500, 'NM') inputs.set_val(Mission.Constraints.MAX_MACH, 0.785) inputs.set_val(Mission.Landing.DRAG_COEFFICIENT, 0.045, 'unitless') +inputs.set_val(Mission.Landing.LIFT_COEFFICIENT_MAX, 2.0, 'unitless') inputs.set_val(Mission.Takeoff.AIRPORT_ALTITUDE, 0., 'ft') inputs.set_val(Mission.Takeoff.DRAG_COEFFICIENT_MIN, 0.05, 'unitless') inputs.set_val(Mission.Takeoff.LIFT_COEFFICIENT_MAX, 2.0, 'unitless') @@ -312,6 +309,7 @@ inputs.set_val(Mission.Landing.SPOILER_DRAG_COEFFICIENT, 0.085000) inputs.set_val(Mission.Landing.SPOILER_LIFT_COEFFICIENT, -0.810000) inputs.set_val(Mission.Takeoff.THRUST_INCIDENCE, 0., 'deg') +inputs.set_val(Mission.Takeoff.FUEL_SIMPLE, 577., 'lbm') # Settings # --------------------------- @@ -484,13 +482,13 @@ takeoff_brake_release_user_options.set_val('max_duration', val=60.0, units='s') takeoff_brake_release_user_options.set_val('duration_ref', val=60.0, units='s') -takeoff_brake_release_user_options.set_val('max_range', val=7500.0, units='ft') +takeoff_brake_release_user_options.set_val('distance_max', val=7500.0, units='ft') takeoff_brake_release_user_options.set_val('max_velocity', val=167.85, units='kn') takeoff_brake_release_initial_guesses = AviaryValues() takeoff_brake_release_initial_guesses.set_val('times', [0., 30.], 's') -takeoff_brake_release_initial_guesses.set_val('range', [0., 4100.], 'ft') +takeoff_brake_release_initial_guesses.set_val('distance', [0., 4100.], 'ft') takeoff_brake_release_initial_guesses.set_val('velocity', [0.01, 150.], 'kn') gross_mass_units = 'lbm' @@ -517,13 +515,13 @@ takeoff_decision_speed_user_options.set_val('max_duration', val=60.0, units='s') takeoff_decision_speed_user_options.set_val('duration_ref', val=60.0, units='s') takeoff_decision_speed_user_options.set_val('initial_ref', val=35.0, units='s') -takeoff_decision_speed_user_options.set_val('max_range', val=7500.0, units='ft') +takeoff_decision_speed_user_options.set_val('distance_max', val=7500.0, units='ft') takeoff_decision_speed_user_options.set_val('max_velocity', val=167.85, units='kn') takeoff_decision_speed_initial_guesses = AviaryValues() takeoff_decision_speed_initial_guesses.set_val('times', [30., 2.], 's') -takeoff_decision_speed_initial_guesses.set_val('range', [4100., 4500.], 'ft') +takeoff_decision_speed_initial_guesses.set_val('distance', [4100., 4500.], 'ft') takeoff_decision_speed_initial_guesses.set_val('velocity', [150., 160.], 'kn') takeoff_decision_speed_initial_guesses.set_val('mass', gross_mass, gross_mass_units) takeoff_decision_speed_initial_guesses.set_val('throttle', 1.) @@ -545,14 +543,14 @@ takeoff_rotate_user_options.set_val('max_duration', val=60.0, units='s') takeoff_rotate_user_options.set_val('duration_ref', val=60.0, units='s') takeoff_rotate_user_options.set_val('initial_ref', val=38.0, units='s') -takeoff_rotate_user_options.set_val('max_range', val=7500.0, units='ft') +takeoff_rotate_user_options.set_val('distance_max', val=7500.0, units='ft') takeoff_rotate_user_options.set_val('max_velocity', val=167.85, units='kn') takeoff_rotate_user_options.set_val('max_angle_of_attack', val=10.0, units='deg') takeoff_rotate_initial_guesses = AviaryValues() takeoff_rotate_initial_guesses.set_val('times', [32., 1.], 's') -takeoff_rotate_initial_guesses.set_val('range', [4500, 4800.0], 'ft') +takeoff_rotate_initial_guesses.set_val('distance', [4500, 4800.0], 'ft') takeoff_rotate_initial_guesses.set_val('velocity', [160., 160.0], 'kn') takeoff_rotate_initial_guesses.set_val('throttle', 1.) takeoff_rotate_initial_guesses.set_val('angle_of_attack', [0., 8.], 'deg') @@ -574,7 +572,7 @@ takeoff_liftoff_user_options.set_val('max_duration', val=12., units='s') takeoff_liftoff_user_options.set_val('duration_ref', val=12., units='s') takeoff_liftoff_user_options.set_val('initial_ref', val=39.0, units='s') -takeoff_liftoff_user_options.set_val('max_range', val=7500., units='ft') +takeoff_liftoff_user_options.set_val('distance_max', val=7500., units='ft') takeoff_liftoff_user_options.set_val('max_velocity', val=167.85, units='kn') takeoff_liftoff_user_options.set_val('altitude_ref', val=35., units='ft') takeoff_liftoff_user_options.set_val('flight_path_angle_ref', val=10., units='deg') @@ -585,11 +583,12 @@ takeoff_liftoff_initial_guesses = AviaryValues() takeoff_liftoff_initial_guesses.set_val('times', [33., 4.], 's') -takeoff_liftoff_initial_guesses.set_val('range', [4800, 5700.0], 'ft') +takeoff_liftoff_initial_guesses.set_val('distance', [4800, 5700.0], 'ft') takeoff_liftoff_initial_guesses.set_val('velocity', [160, 167.0], 'kn') takeoff_liftoff_initial_guesses.set_val('throttle', 1.) takeoff_liftoff_initial_guesses.set_val('altitude', [0, 35.0], 'ft') -takeoff_liftoff_initial_guesses.set_val(Dynamic.FLIGHT_PATH_ANGLE, [0, 6.0], 'deg') +takeoff_liftoff_initial_guesses.set_val( + Dynamic.Mission.FLIGHT_PATH_ANGLE, [0, 6.0], 'deg') takeoff_liftoff_initial_guesses.set_val('angle_of_attack', 8.117, 'deg') takeoff_liftoff_initial_guesses.set_val('mass', gross_mass, gross_mass_units) @@ -609,7 +608,7 @@ takeoff_mic_p2_user_options.set_val('max_duration', val=25., units='s') takeoff_mic_p2_user_options.set_val('duration_ref', val=25., units='s') takeoff_mic_p2_user_options.set_val('initial_ref', val=50.0, units='s') -takeoff_mic_p2_user_options.set_val('max_range', val=12000., units='ft') +takeoff_mic_p2_user_options.set_val('distance_max', val=12000., units='ft') takeoff_mic_p2_user_options.set_val('max_velocity', val=167.85, units='kn') takeoff_mic_p2_user_options.set_val('altitude_ref', val=1500., units='ft') takeoff_mic_p2_user_options.set_val('mic_altitude', val=985., units='ft') @@ -627,11 +626,12 @@ takeoff_mic_p2_initial_guesses = AviaryValues() takeoff_mic_p2_initial_guesses.set_val('times', [36., 18], 's') -takeoff_mic_p2_initial_guesses.set_val('range', [5700, 10000.0], 'ft') +takeoff_mic_p2_initial_guesses.set_val('distance', [5700, 10000.0], 'ft') takeoff_mic_p2_initial_guesses.set_val('velocity', [167, 167.0], 'kn') takeoff_mic_p2_initial_guesses.set_val('throttle', 1.) takeoff_mic_p2_initial_guesses.set_val('altitude', [35, 985.0], 'ft') -takeoff_mic_p2_initial_guesses.set_val(Dynamic.FLIGHT_PATH_ANGLE, [7.0, 10.0], 'deg') +takeoff_mic_p2_initial_guesses.set_val( + Dynamic.Mission.FLIGHT_PATH_ANGLE, [7.0, 10.0], 'deg') takeoff_mic_p2_initial_guesses.set_val('angle_of_attack', 8.117, 'deg') takeoff_mic_p2_initial_guesses.set_val('mass', gross_mass, gross_mass_units) @@ -653,7 +653,7 @@ takeoff_mic_p2_to_engine_cutback_user_options.set_val('initial_ref', val=65.0, units='s') takeoff_mic_p2_to_engine_cutback_user_options.set_val( - 'max_range', val=20000., units='ft') + 'distance_max', val=20000., units='ft') takeoff_mic_p2_to_engine_cutback_user_options.set_val( 'max_velocity', val=167.85, units='kn') @@ -679,13 +679,14 @@ takeoff_mic_p2_to_engine_cutback_initial_guesses = AviaryValues() takeoff_mic_p2_to_engine_cutback_initial_guesses.set_val('times', [53., 27], 's') -takeoff_mic_p2_to_engine_cutback_initial_guesses.set_val('range', [10000, 19000.0], 'ft') +takeoff_mic_p2_to_engine_cutback_initial_guesses.set_val( + 'distance', [10000, 19000.0], 'ft') takeoff_mic_p2_to_engine_cutback_initial_guesses.set_val('velocity', [167, 167.0], 'kn') takeoff_mic_p2_to_engine_cutback_initial_guesses.set_val('throttle', 1.0) takeoff_mic_p2_to_engine_cutback_initial_guesses.set_val('altitude', [985, 2500.0], 'ft') takeoff_mic_p2_to_engine_cutback_initial_guesses.set_val( - Dynamic.FLIGHT_PATH_ANGLE, [11.0, 10.0], 'deg') + Dynamic.Mission.FLIGHT_PATH_ANGLE, [11.0, 10.0], 'deg') takeoff_mic_p2_to_engine_cutback_initial_guesses.set_val('angle_of_attack', 5.0, 'deg') @@ -718,7 +719,7 @@ cutback_duration = (1.0 - cutback_throttle) / cutback_rate takeoff_engine_cutback_user_options.set_val('initial_ref', val=95.0, units='s') -takeoff_engine_cutback_user_options.set_val('max_range', val=21000., units='ft') +takeoff_engine_cutback_user_options.set_val('distance_max', val=21000., units='ft') takeoff_engine_cutback_user_options.set_val('max_velocity', val=167.85, units='kn') takeoff_engine_cutback_user_options.set_val('altitude_ref', val=4000., units='ft') @@ -734,13 +735,13 @@ takeoff_engine_cutback_initial_guesses = AviaryValues() takeoff_engine_cutback_initial_guesses.set_val('times', [84., cutback_duration], 's') -takeoff_engine_cutback_initial_guesses.set_val('range', [19000, 20000.0], 'ft') +takeoff_engine_cutback_initial_guesses.set_val('distance', [19000, 20000.0], 'ft') takeoff_engine_cutback_initial_guesses.set_val('velocity', [167, 167.0], 'kn') takeoff_engine_cutback_initial_guesses.set_val('throttle', [1.0, cutback_throttle]) takeoff_engine_cutback_initial_guesses.set_val('altitude', [2500.0, 2600.0], 'ft') takeoff_engine_cutback_initial_guesses.set_val( - Dynamic.FLIGHT_PATH_ANGLE, [10.0, 10.0], 'deg') + Dynamic.Mission.FLIGHT_PATH_ANGLE, [10.0, 10.0], 'deg') takeoff_engine_cutback_initial_guesses.set_val('angle_of_attack', 5.0, 'deg') takeoff_engine_cutback_initial_guesses.set_val('mass', gross_mass, gross_mass_units) @@ -763,7 +764,7 @@ takeoff_engine_cutback_to_mic_p1_user_options.set_val('initial_ref', val=97.0, units='s') takeoff_engine_cutback_to_mic_p1_user_options.set_val( - 'max_range', val=22000., units='ft') + 'distance_max', val=22000., units='ft') takeoff_engine_cutback_to_mic_p1_user_options.set_val( 'max_velocity', val=167.85, units='kn') @@ -792,7 +793,8 @@ takeoff_engine_cutback_to_mic_p1_initial_guesses = AviaryValues() takeoff_engine_cutback_to_mic_p1_initial_guesses.set_val('times', [87., 10], 's') -takeoff_engine_cutback_to_mic_p1_initial_guesses.set_val('range', [20000, 21325.0], 'ft') +takeoff_engine_cutback_to_mic_p1_initial_guesses.set_val( + 'distance', [20000, 21325.0], 'ft') takeoff_engine_cutback_to_mic_p1_initial_guesses.set_val('velocity', [167, 167.0], 'kn') takeoff_engine_cutback_to_mic_p1_initial_guesses.set_val('throttle', cutback_throttle) @@ -800,7 +802,7 @@ 'altitude', [2600, 2700.0], 'ft') takeoff_engine_cutback_to_mic_p1_initial_guesses.set_val( - Dynamic.FLIGHT_PATH_ANGLE, 2.29, 'deg') + Dynamic.Mission.FLIGHT_PATH_ANGLE, 2.29, 'deg') takeoff_engine_cutback_to_mic_p1_initial_guesses.set_val('angle_of_attack', 5.0, 'deg') takeoff_engine_cutback_to_mic_p1_initial_guesses.set_val( @@ -823,7 +825,7 @@ takeoff_mic_p1_to_climb_user_options.set_val('max_duration', val=40., units='s') takeoff_mic_p1_to_climb_user_options.set_val('duration_ref', val=40., units='s') takeoff_mic_p1_to_climb_user_options.set_val('initial_ref', val=100.0, units='s') -takeoff_mic_p1_to_climb_user_options.set_val('max_range', val=30000., units='ft') +takeoff_mic_p1_to_climb_user_options.set_val('distance_max', val=30000., units='ft') takeoff_mic_p1_to_climb_user_options.set_val('max_velocity', val=167.85, units='kn') takeoff_mic_p1_to_climb_user_options.set_val('altitude_ref', val=4000., units='ft') @@ -842,11 +844,12 @@ takeoff_mic_p1_to_climb_initial_guesses = AviaryValues() takeoff_mic_p1_to_climb_initial_guesses.set_val('times', [95., 32], 's') -takeoff_mic_p1_to_climb_initial_guesses.set_val('range', [21325, 30000.0], 'ft') +takeoff_mic_p1_to_climb_initial_guesses.set_val('distance', [21325, 30000.0], 'ft') takeoff_mic_p1_to_climb_initial_guesses.set_val('velocity', [167, 167.0], 'kn') takeoff_mic_p1_to_climb_initial_guesses.set_val('throttle', cutback_throttle) takeoff_mic_p1_to_climb_initial_guesses.set_val('altitude', [2700, 3200.0], 'ft') -takeoff_mic_p1_to_climb_initial_guesses.set_val(Dynamic.FLIGHT_PATH_ANGLE, 2.29, 'deg') +takeoff_mic_p1_to_climb_initial_guesses.set_val( + Dynamic.Mission.FLIGHT_PATH_ANGLE, 2.29, 'deg') takeoff_mic_p1_to_climb_initial_guesses.set_val('angle_of_attack', 5.0, 'deg') takeoff_mic_p1_to_climb_initial_guesses.set_val('mass', gross_mass, gross_mass_units) @@ -867,51 +870,54 @@ detailed_takeoff = AviaryValues() detailed_takeoff.set_val('time', [0.77, 32.01, 33.00, 35.40], 's') -detailed_takeoff.set_val(Dynamic.RANGE, [3.08, 4626.88, 4893.40, 5557.61], 'ft') -detailed_takeoff.set_val(Dynamic.ALTITUDE, [0.00, 0.00, 0.64, 27.98], 'ft') +detailed_takeoff.set_val(Dynamic.Mission.DISTANCE, [ + 3.08, 4626.88, 4893.40, 5557.61], 'ft') +detailed_takeoff.set_val(Dynamic.Mission.ALTITUDE, [0.00, 0.00, 0.64, 27.98], 'ft') velocity = np.array([4.74, 157.58, 160.99, 166.68]) -detailed_takeoff.set_val(Dynamic.VELOCITY, velocity, 'kn') -detailed_takeoff.set_val(Dynamic.MACH, [0.007, 0.2342, 0.2393, 0.2477]) +detailed_takeoff.set_val(Dynamic.Mission.VELOCITY, velocity, 'kn') +detailed_takeoff.set_val(Dynamic.Mission.MACH, [0.007, 0.2342, 0.2393, 0.2477]) detailed_takeoff.set_val( - Dynamic.THRUST_TOTAL, [44038.8, 34103.4, 33929.0, 33638.2], 'lbf') + Dynamic.Mission.THRUST_TOTAL, [44038.8, 34103.4, 33929.0, 33638.2], 'lbf') detailed_takeoff.set_val('angle_of_attack', [0.000, 3.600, 8.117, 8.117], 'deg') -detailed_takeoff.set_val(Dynamic.FLIGHT_PATH_ANGLE, [0.000, 0.000, 0.612, 4.096], 'deg') +detailed_takeoff.set_val(Dynamic.Mission.FLIGHT_PATH_ANGLE, [ + 0.000, 0.000, 0.612, 4.096], 'deg') # missing from the default FLOPS output generated by hand # RANGE_RATE = VELOCITY * cos(flight_path_angle) range_rate = np.array([4.74, 157.58, 160.98, 166.25]) -detailed_takeoff.set_val(Dynamic.RANGE_RATE, range_rate, 'kn') +detailed_takeoff.set_val(Dynamic.Mission.DISTANCE_RATE, range_rate, 'kn') # ALTITUDE_RATE = VELOCITY * sin(flight_path_angle) altitude_rate = np.array([0.00, 0.00, 1.72, 11.91]) -detailed_takeoff.set_val(Dynamic.ALTITUDE_RATE, altitude_rate, 'kn') +detailed_takeoff.set_val(Dynamic.Mission.ALTITUDE_RATE, altitude_rate, 'kn') # NOTE FLOPS output is horizontal acceleration only # - divide the FLOPS values by the cos(flight_path_angle) -# detailed_takeoff.set_val(Dynamic.VELOCITY_RATE, [10.36, 6.20, 5.23, 2.69], 'ft/s**2') +# detailed_takeoff.set_val(Dynamic.Mission.VELOCITY_RATE, [10.36, 6.20, 5.23, 2.69], 'ft/s**2') velocity_rate = [10.36, 6.20, 5.23, 2.70] -detailed_takeoff.set_val(Dynamic.VELOCITY_RATE, velocity_rate, 'ft/s**2') +detailed_takeoff.set_val(Dynamic.Mission.VELOCITY_RATE, velocity_rate, 'ft/s**2') # NOTE FLOPS output is based on "constant" takeoff mass - assume gross weight # - currently neglecting taxi -detailed_takeoff.set_val(Dynamic.MASS, [129734., 129734., 129734., 129734.], 'lbm') +detailed_takeoff.set_val(Dynamic.Mission.MASS, [ + 129734., 129734., 129734., 129734.], 'lbm') lift_coeff = np.array([0.5580, 0.9803, 1.4831, 1.3952]) drag_coeff = np.array([0.0801, 0.0859, 0.1074, 0.1190]) S = inputs.get_val(Aircraft.Wing.AREA, 'm**2') -v = detailed_takeoff.get_val(Dynamic.VELOCITY, 'm/s') +v = detailed_takeoff.get_val(Dynamic.Mission.VELOCITY, 'm/s') # NOTE sea level; includes effect of FLOPS &TOLIN DTCT 10 DEG C rho = 1.18391 # kg/m**3 RHV2 = 0.5 * rho * v * v * S lift = RHV2 * lift_coeff # N -detailed_takeoff.set_val(Dynamic.LIFT, lift, 'N') +detailed_takeoff.set_val(Dynamic.Mission.LIFT, lift, 'N') drag = RHV2 * drag_coeff # N -detailed_takeoff.set_val(Dynamic.DRAG, drag, 'N') +detailed_takeoff.set_val(Dynamic.Mission.DRAG, drag, 'N') def _split_aviary_values(aviary_values, slicing): @@ -935,13 +941,13 @@ def _split_aviary_values(aviary_values, slicing): balanced_brake_release_user_options.set_val('max_duration', val=60.0, units='s') balanced_brake_release_user_options.set_val('duration_ref', val=60.0, units='s') -balanced_brake_release_user_options.set_val('max_range', val=7500.0, units='ft') +balanced_brake_release_user_options.set_val('distance_max', val=7500.0, units='ft') balanced_brake_release_user_options.set_val('max_velocity', val=167.85, units='kn') balanced_brake_release_initial_guesses = AviaryValues() balanced_brake_release_initial_guesses.set_val('times', [0., 30.], 's') -balanced_brake_release_initial_guesses.set_val('range', [0., 4100.], 'ft') +balanced_brake_release_initial_guesses.set_val('distance', [0., 4100.], 'ft') balanced_brake_release_initial_guesses.set_val('velocity', [0.01, 150.], 'kn') balanced_brake_release_initial_guesses.set_val('mass', gross_mass, gross_mass_units) balanced_brake_release_initial_guesses.set_val('throttle', 1.) @@ -962,7 +968,7 @@ def _split_aviary_values(aviary_values, slicing): balanced_decision_speed_user_options.set_val('max_duration', val=60.0, units='s') balanced_decision_speed_user_options.set_val('duration_ref', val=5.0, units='s') balanced_decision_speed_user_options.set_val('initial_ref', val=35.0, units='s') -balanced_decision_speed_user_options.set_val('max_range', val=7500.0, units='ft') +balanced_decision_speed_user_options.set_val('distance_max', val=7500.0, units='ft') balanced_decision_speed_user_options.set_val('max_velocity', val=167.85, units='kn') balanced_decision_speed_initial_guesses = AviaryValues() @@ -971,7 +977,7 @@ def _split_aviary_values(aviary_values, slicing): engine_out_throttle = (num_engines - 1) / num_engines balanced_decision_speed_initial_guesses.set_val('times', [30., 2.], 's') -balanced_decision_speed_initial_guesses.set_val('range', [4100., 4500.], 'ft') +balanced_decision_speed_initial_guesses.set_val('distance', [4100., 4500.], 'ft') balanced_decision_speed_initial_guesses.set_val('velocity', [150., 160.], 'kn') balanced_decision_speed_initial_guesses.set_val('mass', gross_mass, gross_mass_units) balanced_decision_speed_initial_guesses.set_val('throttle', engine_out_throttle) @@ -992,14 +998,14 @@ def _split_aviary_values(aviary_values, slicing): balanced_rotate_user_options.set_val('max_duration', val=20.0, units='s') balanced_rotate_user_options.set_val('duration_ref', val=5.0, units='s') balanced_rotate_user_options.set_val('initial_ref', val=35.0, units='s') -balanced_rotate_user_options.set_val('max_range', val=7500.0, units='ft') +balanced_rotate_user_options.set_val('distance_max', val=7500.0, units='ft') balanced_rotate_user_options.set_val('max_velocity', val=167.85, units='kn') balanced_rotate_user_options.set_val('max_angle_of_attack', val=8.117, units='deg') balanced_rotate_initial_guesses = AviaryValues() balanced_rotate_initial_guesses.set_val('times', [32., 1.], 's') -balanced_rotate_initial_guesses.set_val('range', [4500., 4800.], 'ft') +balanced_rotate_initial_guesses.set_val('distance', [4500., 4800.], 'ft') balanced_rotate_initial_guesses.set_val('velocity', [160., 160.], 'kn') balanced_rotate_initial_guesses.set_val('throttle', engine_out_throttle) balanced_rotate_initial_guesses.set_val('angle_of_attack', [0., 8.], 'deg') @@ -1020,7 +1026,7 @@ def _split_aviary_values(aviary_values, slicing): balanced_liftoff_user_options.set_val('max_duration', val=20., units='s') balanced_liftoff_user_options.set_val('duration_ref', val=20., units='s') balanced_liftoff_user_options.set_val('initial_ref', val=40.0, units='s') -balanced_liftoff_user_options.set_val('max_range', val=7500., units='ft') +balanced_liftoff_user_options.set_val('distance_max', val=7500., units='ft') balanced_liftoff_user_options.set_val('max_velocity', val=167.85, units='kn') balanced_liftoff_user_options.set_val('altitude_ref', val=35., units='ft') balanced_liftoff_user_options.set_val('flight_path_angle_ref', val=5., units='deg') @@ -1031,11 +1037,12 @@ def _split_aviary_values(aviary_values, slicing): balanced_liftoff_initial_guesses = AviaryValues() balanced_liftoff_initial_guesses.set_val('times', [33., 4.], 's') -balanced_liftoff_initial_guesses.set_val('range', [4800., 7000.], 'ft') +balanced_liftoff_initial_guesses.set_val('distance', [4800., 7000.], 'ft') balanced_liftoff_initial_guesses.set_val('velocity', [160., 167.], 'kn') balanced_liftoff_initial_guesses.set_val('throttle', engine_out_throttle) balanced_liftoff_initial_guesses.set_val('altitude', [0., 35.], 'ft') -balanced_liftoff_initial_guesses.set_val(Dynamic.FLIGHT_PATH_ANGLE, [0., 5.], 'deg') +balanced_liftoff_initial_guesses.set_val( + Dynamic.Mission.FLIGHT_PATH_ANGLE, [0., 5.], 'deg') balanced_liftoff_initial_guesses.set_val('angle_of_attack', 8.117, 'deg') balanced_liftoff_initial_guesses.set_val('mass', gross_mass, gross_mass_units) @@ -1053,13 +1060,13 @@ def _split_aviary_values(aviary_values, slicing): balanced_delayed_brake_user_options.set_val('duration_ref', val=4.0, units='s') balanced_delayed_brake_user_options.set_val('initial_ref', val=35.0, units='s') -balanced_delayed_brake_user_options.set_val('max_range', val=7500.0, units='ft') +balanced_delayed_brake_user_options.set_val('distance_max', val=7500.0, units='ft') balanced_delayed_brake_user_options.set_val('max_velocity', val=167.85, units='kn') balanced_delayed_brake_initial_guesses = AviaryValues() balanced_delayed_brake_initial_guesses.set_val('times', [30., 3.], 's') -balanced_delayed_brake_initial_guesses.set_val('range', [4100., 4600.], 'ft') +balanced_delayed_brake_initial_guesses.set_val('distance', [4100., 4600.], 'ft') balanced_delayed_brake_initial_guesses.set_val('velocity', [150., 150.], 'kn') balanced_delayed_brake_initial_guesses.set_val('mass', gross_mass, gross_mass_units) balanced_delayed_brake_initial_guesses.set_val('throttle', engine_out_throttle) @@ -1080,13 +1087,13 @@ def _split_aviary_values(aviary_values, slicing): balanced_abort_user_options.set_val('max_duration', val=60.0, units='s') balanced_abort_user_options.set_val('initial_ref', val=35.0, units='s') balanced_abort_user_options.set_val('duration_ref', val=60.0, units='s') -balanced_abort_user_options.set_val('max_range', val=7500.0, units='ft') +balanced_abort_user_options.set_val('distance_max', val=7500.0, units='ft') balanced_abort_user_options.set_val('max_velocity', val=167.85, units='kn') balanced_abort_initial_guesses = AviaryValues() balanced_abort_initial_guesses.set_val('times', [32., 22.], 's') -balanced_abort_initial_guesses.set_val('range', [4600., 7000.], 'ft') +balanced_abort_initial_guesses.set_val('distance', [4600., 7000.], 'ft') balanced_abort_initial_guesses.set_val('velocity', [150., 0.01], 'kn') balanced_abort_initial_guesses.set_val('mass', gross_mass, gross_mass_units) balanced_abort_initial_guesses.set_val('throttle', 0.) @@ -1099,10 +1106,10 @@ def _split_aviary_values(aviary_values, slicing): user_options=balanced_abort_user_options, initial_guesses=balanced_abort_initial_guesses) -max_range = balanced_liftoff_user_options.get_val('max_range', 'ft') +distance_max = balanced_liftoff_user_options.get_val('distance_max', 'ft') balanced_trajectory_builder.set_brake_to_abort( - balanced_abort_builder, balanced_field_ref=max_range) + balanced_abort_builder, balanced_field_ref=distance_max) # endregion balanced field length # region - detailed landing @@ -1132,16 +1139,6 @@ def _split_aviary_values(aviary_values, slicing): 'spoiler_lift_coefficient': -0.81, 'spoiler_drag_coefficient': 0.085}} -# landing_aero_builder = TakeoffAero(**landing_aero_user_options) - -# fullstop_aero_user_options = dict(landing_aero_user_options) -# fullstop_aero_user_options['use_spoilers'] = True -# fullstop_aero_user_options['spoiler_lift_coefficient'] = -0.81 -# fullstop_aero_user_options['spoiler_drag_coefficient'] = 0.085 - -# fullstop_aero_builder = TakeoffAero(**fullstop_aero_user_options) -# endregion - landing aero - # VELOCITY TIME DISTANCE ALPHA ALTITUDE # KNOTS SEC. FEET DEG. FEET # 50. FOOT OBSTACLE 138.65 0.00 0.00 5.22 50.00 @@ -1179,10 +1176,10 @@ def _split_aviary_values(aviary_values, slicing): base = values[0] values = values - base -detailed_landing.set_val(Dynamic.RANGE, values, 'ft') +detailed_landing.set_val(Dynamic.Mission.DISTANCE, values, 'ft') detailed_landing.set_val( - Dynamic.ALTITUDE, + Dynamic.Mission.ALTITUDE, [ 100, 100, 98, 96, 94, 92, 90, 88, 86, 84, 82, 80, 78, 76, 74, 72, 70, 68, 66, 64, @@ -1194,7 +1191,7 @@ def _split_aviary_values(aviary_values, slicing): 'ft') detailed_landing.set_val( - Dynamic.VELOCITY, + Dynamic.Mission.VELOCITY, np.array([ 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, 138.65, @@ -1206,7 +1203,7 @@ def _split_aviary_values(aviary_values, slicing): 'kn') detailed_landing.set_val( - Dynamic.MACH, + Dynamic.Mission.MACH, [ 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, 0.2061, @@ -1217,7 +1214,7 @@ def _split_aviary_values(aviary_values, slicing): 0.086, 0.0756, 0.0653, 0.0551, 0.045, 0.035, 0.025, 0.015, 0.0051, 0]) detailed_landing.set_val( - Dynamic.THRUST_TOTAL, + Dynamic.Mission.THRUST_TOTAL, [ 7614, 7614, 7607.7, 7601, 7593.9, 7586.4, 7578.5, 7570.2, 7561.3, 7551.8, 7541.8, 7531.1, 7519.7, 7507.6, 7494.6, 7480.6, 7465.7, 7449.7, 7432.5, 7414, @@ -1242,7 +1239,7 @@ def _split_aviary_values(aviary_values, slicing): # glide slope == flight path angle? detailed_landing.set_val( - Dynamic.FLIGHT_PATH_ANGLE, + Dynamic.Mission.FLIGHT_PATH_ANGLE, np.array([ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, @@ -1255,13 +1252,13 @@ def _split_aviary_values(aviary_values, slicing): # missing from the default FLOPS output generated by script # RANGE_RATE = VELOCITY * cos(flight_path_angle) -velocity: np.ndarray = detailed_landing.get_val(Dynamic.VELOCITY, 'kn') -flight_path_angle = detailed_landing.get_val(Dynamic.FLIGHT_PATH_ANGLE, 'rad') +velocity: np.ndarray = detailed_landing.get_val(Dynamic.Mission.VELOCITY, 'kn') +flight_path_angle = detailed_landing.get_val(Dynamic.Mission.FLIGHT_PATH_ANGLE, 'rad') range_rate = velocity * np.cos(-flight_path_angle) -detailed_landing.set_val(Dynamic.RANGE_RATE, range_rate, 'kn') +detailed_landing.set_val(Dynamic.Mission.DISTANCE_RATE, range_rate, 'kn') # ALTITUDE_RATE = VELOCITY * sin(flight_path_angle) altitude_rate = velocity * np.sin(flight_path_angle) -detailed_landing.set_val(Dynamic.ALTITUDE_RATE, altitude_rate, 'kn') +detailed_landing.set_val(Dynamic.Mission.ALTITUDE_RATE, altitude_rate, 'kn') # NOTE FLOPS output is horizontal acceleration only, and virtually no acceleration while # airborne @@ -1272,7 +1269,7 @@ def _split_aviary_values(aviary_values, slicing): detailed_landing_mass = 106292. # units='lbm' detailed_landing.set_val( - Dynamic.MASS, np.full(velocity.shape, detailed_landing_mass), 'lbm') + Dynamic.Mission.MASS, np.full(velocity.shape, detailed_landing_mass), 'lbm') # lift/drag is calculated very close to landing altitude (sea level, in this case)... lift_coeff = np.array([ @@ -1294,17 +1291,17 @@ def _split_aviary_values(aviary_values, slicing): 0.1785, 0.1785, 0.1785, 0.1785, 0.1785, 0.1785, 0.1785, 0.1785, 0.1785, 0.1785]) S = inputs.get_val(Aircraft.Wing.AREA, 'm**2') -v = detailed_landing.get_val(Dynamic.VELOCITY, 'm/s') +v = detailed_landing.get_val(Dynamic.Mission.VELOCITY, 'm/s') # NOTE sea level; includes effect of FLOPS &TOLIN DTCT 10 DEG C rho = 1.18391 # kg/m**3 RHV2 = 0.5 * rho * v * v * S lift = RHV2 * lift_coeff # N -detailed_landing.set_val(Dynamic.LIFT, lift, 'N') +detailed_landing.set_val(Dynamic.Mission.LIFT, lift, 'N') drag = RHV2 * drag_coeff # N -detailed_landing.set_val(Dynamic.DRAG, drag, 'N') +detailed_landing.set_val(Dynamic.Mission.DRAG, drag, 'N') # Flops variable APRANG apr_angle = -3.0 # deg @@ -1320,7 +1317,7 @@ def _split_aviary_values(aviary_values, slicing): landing_approach_to_mic_p3_user_options.set_val('max_duration', val=50., units='s') landing_approach_to_mic_p3_user_options.set_val('duration_ref', val=50., units='s') landing_approach_to_mic_p3_user_options.set_val('initial_ref', val=50.0, units='s') -landing_approach_to_mic_p3_user_options.set_val('max_range', val=10000., units='ft') +landing_approach_to_mic_p3_user_options.set_val('distance_max', val=10000., units='ft') landing_approach_to_mic_p3_user_options.set_val('max_velocity', val=140., units='kn') landing_approach_to_mic_p3_user_options.set_val('altitude_ref', val=800., units='ft') @@ -1338,14 +1335,14 @@ def _split_aviary_values(aviary_values, slicing): landing_approach_to_mic_p3_initial_guesses = AviaryValues() landing_approach_to_mic_p3_initial_guesses.set_val('times', [-42., 15.], 's') -landing_approach_to_mic_p3_initial_guesses.set_val('range', [-4000., -2000.], 'ft') +landing_approach_to_mic_p3_initial_guesses.set_val('distance', [-4000., -2000.], 'ft') landing_approach_to_mic_p3_initial_guesses.set_val('velocity', 140., 'kn') landing_approach_to_mic_p3_initial_guesses.set_val('mass', detailed_landing_mass, 'lbm') landing_approach_to_mic_p3_initial_guesses.set_val('throttle', throttle) landing_approach_to_mic_p3_initial_guesses.set_val('altitude', [600., 394.], 'ft') landing_approach_to_mic_p3_initial_guesses.set_val( - Dynamic.FLIGHT_PATH_ANGLE, [apr_angle, apr_angle], 'deg') + Dynamic.Mission.FLIGHT_PATH_ANGLE, [apr_angle, apr_angle], 'deg') landing_approach_to_mic_p3_initial_guesses.set_val('angle_of_attack', 5.25, 'deg') @@ -1371,7 +1368,7 @@ def _split_aviary_values(aviary_values, slicing): landing_mic_p3_to_obstacle_user_options.set_val('max_duration', val=50., units='s') landing_mic_p3_to_obstacle_user_options.set_val('duration_ref', val=50., units='s') landing_mic_p3_to_obstacle_user_options.set_val('initial_ref', val=50.0, units='s') -landing_mic_p3_to_obstacle_user_options.set_val('max_range', val=6000., units='ft') +landing_mic_p3_to_obstacle_user_options.set_val('distance_max', val=6000., units='ft') landing_mic_p3_to_obstacle_user_options.set_val('max_velocity', val=140., units='kn') landing_mic_p3_to_obstacle_user_options.set_val('altitude_ref', val=400., units='ft') @@ -1389,14 +1386,14 @@ def _split_aviary_values(aviary_values, slicing): landing_mic_p3_to_obstacle_initial_guesses = AviaryValues() landing_mic_p3_to_obstacle_initial_guesses.set_val('times', [-27., 27.], 's') -landing_mic_p3_to_obstacle_initial_guesses.set_val('range', [-2000., 0.], 'ft') +landing_mic_p3_to_obstacle_initial_guesses.set_val('distance', [-2000., 0.], 'ft') landing_mic_p3_to_obstacle_initial_guesses.set_val('velocity', 140., 'kn') landing_mic_p3_to_obstacle_initial_guesses.set_val('mass', detailed_landing_mass, 'lbm') landing_mic_p3_to_obstacle_initial_guesses.set_val('throttle', throttle) landing_mic_p3_to_obstacle_initial_guesses.set_val('altitude', [394., 50.], 'ft') landing_mic_p3_to_obstacle_initial_guesses.set_val( - Dynamic.FLIGHT_PATH_ANGLE, [apr_angle, apr_angle], 'deg') + Dynamic.Mission.FLIGHT_PATH_ANGLE, [apr_angle, apr_angle], 'deg') landing_mic_p3_to_obstacle_initial_guesses.set_val('angle_of_attack', 5.25, 'deg') @@ -1420,21 +1417,21 @@ def _split_aviary_values(aviary_values, slicing): landing_obstacle_user_options = AviaryValues() landing_obstacle_user_options.set_val('max_duration', val=5., units='s') -landing_obstacle_user_options.set_val('max_range', val=800., units='ft') +landing_obstacle_user_options.set_val('distance_max', val=800., units='ft') landing_obstacle_user_options.set_val('max_velocity', val=140., units='kn') landing_obstacle_user_options.set_val('altitude_ref', val=50., units='ft') landing_obstacle_initial_guesses = AviaryValues() landing_obstacle_initial_guesses.set_val('times', [0., 4.], 's') -landing_obstacle_initial_guesses.set_val('range', [0., 800.], 'ft') +landing_obstacle_initial_guesses.set_val('distance', [0., 800.], 'ft') landing_obstacle_initial_guesses.set_val('velocity', 140., 'kn') landing_obstacle_initial_guesses.set_val('mass', detailed_landing_mass, 'lbm') landing_obstacle_initial_guesses.set_val('throttle', throttle) landing_obstacle_initial_guesses.set_val('altitude', [50., 15.], 'ft') landing_obstacle_initial_guesses.set_val( - Dynamic.FLIGHT_PATH_ANGLE, [apr_angle, apr_angle], 'deg') + Dynamic.Mission.FLIGHT_PATH_ANGLE, [apr_angle, apr_angle], 'deg') landing_obstacle_initial_guesses.set_val('angle_of_attack', 5.2, 'deg') @@ -1458,7 +1455,7 @@ def _split_aviary_values(aviary_values, slicing): landing_flare_user_options.set_val('max_duration', val=7., units='s') landing_flare_user_options.set_val('duration_ref', val=7., units='s') landing_flare_user_options.set_val('initial_ref', val=4., units='s') -landing_flare_user_options.set_val('max_range', val=1000., units='ft') +landing_flare_user_options.set_val('distance_max', val=1000., units='ft') landing_flare_user_options.set_val('max_velocity', val=140., units='kn') landing_flare_user_options.set_val('altitude_ref', val=15., units='ft') @@ -1469,12 +1466,13 @@ def _split_aviary_values(aviary_values, slicing): landing_flare_initial_guesses = AviaryValues() landing_flare_initial_guesses.set_val('times', [4., 6.], 's') -landing_flare_initial_guesses.set_val('range', [800., 1000.], 'ft') +landing_flare_initial_guesses.set_val('distance', [800., 1000.], 'ft') landing_flare_initial_guesses.set_val('velocity', 140., 'kn') landing_flare_initial_guesses.set_val('mass', detailed_landing_mass, 'lbm') landing_flare_initial_guesses.set_val('throttle', [throttle, throttle*4/7]) landing_flare_initial_guesses.set_val('altitude', [15., 0.], 'ft') -landing_flare_initial_guesses.set_val(Dynamic.FLIGHT_PATH_ANGLE, [apr_angle, 0.], 'deg') +landing_flare_initial_guesses.set_val( + Dynamic.Mission.FLIGHT_PATH_ANGLE, [apr_angle, 0.], 'deg') landing_flare_initial_guesses.set_val('angle_of_attack', [5.2, 7.5], 'deg') landing_flare_builder = LandingFlareToTouchdown( @@ -1497,14 +1495,14 @@ def _split_aviary_values(aviary_values, slicing): landing_touchdown_user_options.set_val('max_duration', val=10., units='s') landing_touchdown_user_options.set_val('duration_ref', val=10., units='s') landing_touchdown_user_options.set_val('initial_ref', val=6., units='s') -landing_touchdown_user_options.set_val('max_range', val=3000., units='ft') +landing_touchdown_user_options.set_val('distance_max', val=3000., units='ft') landing_touchdown_user_options.set_val('max_velocity', val=140., units='kn') landing_touchdown_user_options.set_val('max_angle_of_attack', val=8., units='deg') landing_touchdown_initial_guesses = AviaryValues() landing_touchdown_initial_guesses.set_val('times', [6., 9.], 's') -landing_touchdown_initial_guesses.set_val('range', [1000., 1400.], 'ft') +landing_touchdown_initial_guesses.set_val('distance', [1000., 1400.], 'ft') landing_touchdown_initial_guesses.set_val('velocity', [140., 135.], 'kn') landing_touchdown_initial_guesses.set_val('mass', detailed_landing_mass, 'lbm') landing_touchdown_initial_guesses.set_val('throttle', 0.) @@ -1530,13 +1528,13 @@ def _split_aviary_values(aviary_values, slicing): landing_fullstop_user_options.set_val('max_duration', val=30., units='s') landing_fullstop_user_options.set_val('duration_ref', val=30., units='s') landing_fullstop_user_options.set_val('initial_ref', val=14., units='s') -landing_fullstop_user_options.set_val('max_range', val=4400., units='ft') +landing_fullstop_user_options.set_val('distance_max', val=4400., units='ft') landing_fullstop_user_options.set_val('max_velocity', val=140., units='kn') landing_fullstop_initial_guesses = AviaryValues() landing_fullstop_initial_guesses.set_val('times', [9., 29.], 's') -landing_fullstop_initial_guesses.set_val('range', [1400., 3500.], 'ft') +landing_fullstop_initial_guesses.set_val('distance', [1400., 3500.], 'ft') landing_fullstop_initial_guesses.set_val('velocity', [135., 0.01], 'kn') landing_fullstop_initial_guesses.set_val('mass', detailed_landing_mass, 'lbm') landing_fullstop_initial_guesses.set_val('throttle', 0.) diff --git a/aviary/models/large_single_aisle_1/large_single_aisle_1_GwGm.csv b/aviary/models/large_single_aisle_1/large_single_aisle_1_GwGm.csv index f2492d9fb..5226249f0 100644 --- a/aviary/models/large_single_aisle_1/large_single_aisle_1_GwGm.csv +++ b/aviary/models/large_single_aisle_1/large_single_aisle_1_GwGm.csv @@ -14,7 +14,8 @@ aircraft:design:drag_increment,0.00175,unitless aircraft:design:equipment_mass_coefficients,928,0.0736,0.112,0.14,1959,1.65,551,11192,5,3,50,7.6,12,unitless aircraft:design:max_structural_speed,402.5,mi/h aircraft:design:part25_structural_category,3,unitless -aircraft:design:reserves,4998,unitless +aircraft:design:reserve_fuel_additional,4998,lbm +aircraft:design:reserve_fuel_fraction,0,unitless aircraft:design:static_margin,0.03,unitless aircraft:design:structural_mass_increment,0,lbm aircraft:design:supercritical_drag_shift,0.033,unitless diff --git a/aviary/models/small_single_aisle/small_single_aisle_GwGm.csv b/aviary/models/small_single_aisle/small_single_aisle_GwGm.csv index 040e2391d..3a83018d0 100644 --- a/aviary/models/small_single_aisle/small_single_aisle_GwGm.csv +++ b/aviary/models/small_single_aisle/small_single_aisle_GwGm.csv @@ -14,7 +14,8 @@ aircraft:design:drag_increment,0.0018,unitless aircraft:design:equipment_mass_coefficients,778,0.06,0.112,0.14,1349,1.65,397,9071,7.6,3,50,6,12,unitless aircraft:design:max_structural_speed,402.5,mi/h aircraft:design:part25_structural_category,3,unitless -aircraft:design:reserves,-0.125,unitless +aircraft:design:reserve_fuel_additional,0,lbm +aircraft:design:reserve_fuel_fraction,0.125,unitless aircraft:design:static_margin,0.05,unitless aircraft:design:structural_mass_increment,0,lbm aircraft:design:supercritical_drag_shift,0.025,unitless diff --git a/aviary/models/small_single_aisle/small_single_aisle_GwGm.dat b/aviary/models/small_single_aisle/small_single_aisle_GwGm.dat index 143b405d4..622fbf4d1 100644 --- a/aviary/models/small_single_aisle/small_single_aisle_GwGm.dat +++ b/aviary/models/small_single_aisle/small_single_aisle_GwGm.dat @@ -205,7 +205,7 @@ OFEM=.78, ! off design specified mission Mach number ROSCAB=9999., ! cabin rate of sink during descent (500.) fpm RSMX=900., ! maximum allowable rate of sink during landing approach ft per min (1000) - FRESF=-.125, ! Reserve Fuel fraction of mission fuel (minus sign for code trigger) + FRESF=-0.125, ! Reserve Fuel fraction of mission fuel (minus sign for code trigger) RF(1)=2.0, ! Reserve Fuel Input: Time for Missed Approach (min) RF(2)=100., ! Reserve Fuel Input: Range to alternate RF(3)=25000., ! Reserve Fuel Input: Cruise altitude to alternate diff --git a/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv b/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv index 2d4ea4703..d18303b7f 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv @@ -25,7 +25,7 @@ aircraft:design:base_area,0.0,ft**2 aircraft:design:empty_mass_margin_scaler,0.0,unitless aircraft:design:lift_dependent_drag_coeff_factor,0.909839381134961,unitless aircraft:design:touchdown_mass,152800.0,lbm -aircraft:design:reserves,3000.,unitless +aircraft:design:reserve_fuel_additional,3000.,lbm aircraft:design:subsonic_drag_coeff_factor,1.0,unitless aircraft:design:supersonic_drag_coeff_factor,1.0,unitless aircraft:design:use_alt_mass,False,unitless @@ -154,6 +154,5 @@ mission:summary:fuel_flow_scaler,1.0,unitless mission:takeoff:fuel_simple,577,lbm mission:takeoff:lift_coefficient_max,3.0,unitless mission:takeoff:lift_over_drag,17.354,unitless -mission:takeoff:rolling_friction_coefficient,0.0175,unitless settings:equations_of_motion,height_energy settings:mass_method,FLOPS \ No newline at end of file diff --git a/aviary/models/test_aircraft/aircraft_for_bench_FwFm_simple.csv b/aviary/models/test_aircraft/aircraft_for_bench_FwFm_simple.csv deleted file mode 100644 index abdddb634..000000000 --- a/aviary/models/test_aircraft/aircraft_for_bench_FwFm_simple.csv +++ /dev/null @@ -1,159 +0,0 @@ -aircraft:air_conditioning:mass_scaler,1.0,unitless -aircraft:anti_icing:mass_scaler,1.0,unitless -aircraft:apu:mass_scaler,1.1,unitless -aircraft:avionics:mass_scaler,1.2,unitless -aircraft:canard:area,0.0,ft**2 -aircraft:canard:aspect_ratio,0.0,unitless -aircraft:canard:thickness_to_chord,0.0,unitless -aircraft:crew_and_payload:baggage_mass_per_passenger,45.0,lbm -aircraft:crew_and_payload:cargo_container_mass_scaler,1.0,unitless -aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless -aircraft:crew_and_payload:mass_per_passenger,180.0,lbm -aircraft:crew_and_payload:misc_cargo,0.0,lbm -aircraft:crew_and_payload:non_flight_crew_mass_scaler,1.0,unitless -aircraft:crew_and_payload:num_business_class,0,unitless -aircraft:crew_and_payload:num_first_class,11,unitless -aircraft:crew_and_payload:num_flight_attendants,3,unitless -aircraft:crew_and_payload:num_flight_crew,2,unitless -aircraft:crew_and_payload:num_galley_crew,0,unitless -aircraft:crew_and_payload:num_non_flight_crew,3,unitless -aircraft:crew_and_payload:num_passengers,169,unitless -aircraft:crew_and_payload:num_tourist_class,158,unitless -aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless -aircraft:crew_and_payload:wing_cargo,0.0,lbm -aircraft:design:base_area,0.0,ft**2 -aircraft:design:empty_mass_margin_scaler,0.0,unitless -aircraft:design:lift_dependent_drag_coeff_factor,0.909839381134961,unitless -aircraft:design:touchdown_mass,152800.0,lbm -aircraft:design:reserves,3000.,unitless -aircraft:design:subsonic_drag_coeff_factor,1.0,unitless -aircraft:design:supersonic_drag_coeff_factor,1.0,unitless -aircraft:design:use_alt_mass,False,unitless -aircraft:design:zero_lift_drag_coeff_factor,0.930890028006548,unitless -aircraft:electrical:mass_scaler,1.25,unitless -aircraft:engine:additional_mass_fraction,0.,unitless -aircraft:engine:constant_fuel_consumption,0.,lbm/h -aircraft:engine:data_file,models/engines/turbofan_28k.deck,unitless -aircraft:engine:flight_idle_thrust_fraction,0.0,unitless -aircraft:engine:flight_idle_max_fraction,1.0,unitless -aircraft:engine:flight_idle_min_fraction,0.08,unitless -aircraft:engine:fuel_flow_scaler_constant_term,0.,unitless -aircraft:engine:fuel_flow_scaler_linear_term,0.,unitless -aircraft:engine:generate_flight_idle,True,unitless -aircraft:engine:geopotential_alt,False,unitless -aircraft:engine:ignore_negative_thrust,False,unitless -aircraft:engine:interpolation_method,slinear,unitless -aircraft:engine:mass_scaler,1.15,unitless -aircraft:engine:mass,7400,lbm -aircraft:engine:num_engines,2,unitless -aircraft:engine:num_fuselage_engines,0,unitless -aircraft:engine:num_wing_engines,2,unitless -aircraft:engine:reference_mass,7400,lbm -aircraft:engine:reference_sls_thrust,28928.1,lbf -aircraft:engine:scale_mass,True,unitless -aircraft:engine:scale_performance,True,unitless -aircraft:engine:scaled_sls_thrust,28928.1,lbf -aircraft:engine:subsonic_fuel_flow_scaler,1.,unitless -aircraft:engine:supersonic_fuel_flow_scaler,1.,unitless -aircraft:engine:thrust_reversers_mass_scaler,0.0,unitless -aircraft:engine:wing_locations,[0.26869218],unitless -aircraft:fins:area,0.0,ft**2 -aircraft:fins:mass_scaler,1.0,unitless -aircraft:fins:mass,0.0,lbm -aircraft:fins:num_fins,0,unitless -aircraft:fins:taper_ratio,10.0,unitless -aircraft:fuel:auxiliary_fuel_capacity,0.0,lbm -aircraft:fuel:density_ratio,1.0,unitless -aircraft:fuel:fuel_system_mass_scaler,1.0,unitless -aircraft:fuel:fuselage_fuel_capacity,0.0,lbm -aircraft:fuel:num_tanks,7,unitless -aircraft:fuel:total_capacity,45694.0,lbm -aircraft:fuel:unusable_fuel_mass_scaler,1.0,unitless -aircraft:furnishings:mass_scaler,1.1,unitless -aircraft:fuselage:length,128.0,ft -aircraft:fuselage:mass_scaler,1.05,unitless -aircraft:fuselage:max_height,13.17,ft -aircraft:fuselage:max_width,12.33,ft -aircraft:fuselage:military_cargo_floor,False,unitless -aircraft:fuselage:num_fuselages,1,unitless -aircraft:fuselage:passenger_compartment_length,85.5,ft -aircraft:fuselage:planform_area,1578.24,ft**2 -aircraft:fuselage:wetted_area_scaler,1.0,unitless -aircraft:fuselage:wetted_area,4158.62,ft**2 -aircraft:horizontal_tail:area,355.0,ft**2 -aircraft:horizontal_tail:aspect_ratio,6.0,unitless -aircraft:horizontal_tail:mass_scaler,1.2,unitless -aircraft:horizontal_tail:taper_ratio,0.22,unitless -aircraft:horizontal_tail:thickness_to_chord,0.125,unitless -aircraft:horizontal_tail:vertical_tail_fraction,0.0,unitless -aircraft:horizontal_tail:wetted_area_scaler,1.0,unitless -aircraft:horizontal_tail:wetted_area,592.65,ft**2 -aircraft:hydraulics:mass_scaler,1.0,unitless -aircraft:hydraulics:system_pressure,3000.0,lbf/ft**2 -aircraft:instruments:mass_scaler,1.25,unitless -aircraft:landing_gear:carrier_based,False,unitless -aircraft:landing_gear:main_gear_mass_scaler,1.1,unitless -aircraft:landing_gear:main_gear_oleo_length,102.0,inch -aircraft:landing_gear:nose_gear_mass_scaler,1.0,unitless -aircraft:landing_gear:nose_gear_oleo_length,67.0,inch -aircraft:nacelle:avg_diameter,7.94,ft -aircraft:nacelle:avg_length,12.3,ft -aircraft:nacelle:count_factor,2,unitless -aircraft:nacelle:mass_scaler,1.0,unitless -aircraft:nacelle:wetted_area_scaler,1.0,unitless -aircraft:paint:mass_per_unit_area,0.037,lbm/ft**2 -aircraft:propulsion:engine_oil_mass_scaler,1.0,unitless -aircraft:propulsion:misc_mass_scaler,1.0,unitless -aircraft:vertical_tail:area,284.0,ft**2 -aircraft:vertical_tail:aspect_ratio,1.75,unitless -aircraft:vertical_tail:mass_scaler,1.0,unitless -aircraft:vertical_tail:num_tails,1,unitless -aircraft:vertical_tail:taper_ratio,0.33,unitless -aircraft:vertical_tail:thickness_to_chord,0.1195,unitless -aircraft:vertical_tail:wetted_area_scaler,1.0,unitless -aircraft:vertical_tail:wetted_area,581.13,ft**2 -aircraft:wing:aeroelastic_tailoring_factor,0.0,unitless -aircraft:wing:airfoil_technology,1.92669766647637,unitless -aircraft:wing:area,1370.0,ft**2 -aircraft:wing:aspect_ratio,11.22091,unitless -aircraft:wing:bending_mass_scaler,1.0,unitless -aircraft:wing:chord_per_semispan,0.31,0.23,0.084,unitless -aircraft:wing:composite_fraction,0.2,unitless -aircraft:wing:control_surface_area,137,ft**2 -aircraft:wing:control_surface_area_ratio,0.1,unitless -aircraft:wing:glove_and_bat,134.0,ft**2 -aircraft:wing:input_station_dist,0.,0.2759,0.9367,unitless -aircraft:wing:load_distribution_control,2.0,unitless -aircraft:wing:load_fraction,1.0,unitless -aircraft:wing:load_path_sweep_dist,0.,22.,deg -aircraft:wing:mass_scaler,1.23,unitless -aircraft:wing:max_camber_at_70_semispan,0.0,unitless -aircraft:wing:misc_mass_scaler,1.0,unitless -aircraft:wing:num_integration_stations,50,unitless -aircraft:wing:shear_control_mass_scaler,1.0,unitless -aircraft:wing:span_efficiency_reduction,False,unitless -aircraft:wing:span,117.83,ft -aircraft:wing:strut_bracing_factor,0.0,unitless -aircraft:wing:surface_ctrl_mass_scaler,1.0,unitless -aircraft:wing:sweep,25.0,deg -aircraft:wing:taper_ratio,0.278,unitless -aircraft:wing:thickness_to_chord_dist,0.145,0.115,0.104,unitless -aircraft:wing:thickness_to_chord,0.13,unitless -aircraft:wing:ultimate_load_factor,3.75,unitless -aircraft:wing:var_sweep_mass_penalty,0.0,unitless -aircraft:wing:wetted_area_scaler,1.0,unitless -aircraft:wing:wetted_area,2396.56,ft**2 -mission:constraints:max_mach,0.785,unitless -mission:design:cruise_altitude,35000,ft -mission:design:gross_mass,175400.0,lbm -mission:design:range,3500,NM -mission:design:thrust_takeoff_per_eng,28928.1,lbf -mission:landing:lift_coefficient_max,2.0,unitless -mission:summary:cruise_mach,0.785,unitless -mission:summary:fuel_flow_scaler,1.0,unitless -mission:takeoff:fuel_simple,577,lbm -mission:takeoff:lift_coefficient_max,3.0,unitless -mission:takeoff:lift_over_drag,17.354,unitless -mission:takeoff:rolling_friction_coefficient,0.0175,unitless -settings:equations_of_motion,simple -settings:mass_method,FLOPS \ No newline at end of file diff --git a/aviary/models/test_aircraft/aircraft_for_bench_FwGm.csv b/aviary/models/test_aircraft/aircraft_for_bench_FwGm.csv index 0d54c3528..d96ac4998 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_FwGm.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_FwGm.csv @@ -12,7 +12,7 @@ aircraft:design:drag_increment,0.00175,unitless aircraft:design:equipment_mass_coefficients,928,0.0736,0.112,0.14,1959,1.65,551,11192,5,3,50,7.6,12,unitless aircraft:design:max_structural_speed,402.5,mi/h aircraft:design:part25_structural_category,3,unitless -aircraft:design:reserves,3000.,unitless +aircraft:design:reserve_fuel_additional,3000.,lbm aircraft:design:static_margin,0.03,unitless aircraft:design:structural_mass_increment,0,lbm aircraft:design:supercritical_drag_shift,0.025,unitless diff --git a/aviary/models/test_aircraft/aircraft_for_bench_GwFm.csv b/aviary/models/test_aircraft/aircraft_for_bench_GwFm.csv index 5c0d0b8a0..a3bcfe007 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_GwFm.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_GwFm.csv @@ -22,7 +22,7 @@ aircraft:crew_and_payload:num_flight_crew,2,unitless aircraft:crew_and_payload:num_galley_crew,0,unitless aircraft:crew_and_payload:num_non_flight_crew,3,unitless aircraft:crew_and_payload:num_passengers,180,unitless -aircraft:crew_and_payload:num_tourist_class,158,unitless +aircraft:crew_and_payload:num_tourist_class,169,unitless aircraft:crew_and_payload:passenger_mass_with_bags,200,lbm aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless aircraft:crew_and_payload:wing_cargo,0.0,lbm @@ -39,7 +39,7 @@ aircraft:design:lift_dependent_drag_coeff_factor,0.909839381134961,unitless aircraft:design:touchdown_mass,152800.0,lbm aircraft:design:max_structural_speed,402.5,mi/h aircraft:design:part25_structural_category,3,unitless -aircraft:design:reserves,5000.,unitless +aircraft:design:reserve_fuel_additional,5000.,lbm aircraft:design:smooth_mass_discontinuities,False,unitless aircraft:design:static_margin,0.03,unitless aircraft:design:structural_mass_increment,0,lbm @@ -268,6 +268,5 @@ mission:takeoff:fuel_simple,577,lbm mission:takeoff:lift_coefficient_flap_increment,0.4182,unitless mission:takeoff:lift_coefficient_max,3.0,unitless mission:takeoff:lift_over_drag,17.354,unitless -mission:takeoff:rolling_friction_coefficient,0.0175,unitless settings:equations_of_motion,height_energy settings:mass_method,GASP \ No newline at end of file diff --git a/aviary/models/test_aircraft/aircraft_for_bench_GwGm.csv b/aviary/models/test_aircraft/aircraft_for_bench_GwGm.csv index a71a6e26f..85217169b 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_GwGm.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_GwGm.csv @@ -13,7 +13,7 @@ aircraft:design:drag_increment,0.00175,unitless aircraft:design:equipment_mass_coefficients,928,0.0736,0.112,0.14,1959,1.65,551,11192,5,3,50,7.6,12,unitless aircraft:design:max_structural_speed,402.5,mi/h aircraft:design:part25_structural_category,3,unitless -aircraft:design:reserves,4998,unitless +aircraft:design:reserve_fuel_additional,4998,lbm aircraft:design:static_margin,0.03,unitless aircraft:design:structural_mass_increment,0,lbm aircraft:design:supercritical_drag_shift,0.033,unitless diff --git a/aviary/models/test_aircraft/converter_configuration_test_data_GwGm.csv b/aviary/models/test_aircraft/converter_configuration_test_data_GwGm.csv index 94de39b61..8084c3a71 100644 --- a/aviary/models/test_aircraft/converter_configuration_test_data_GwGm.csv +++ b/aviary/models/test_aircraft/converter_configuration_test_data_GwGm.csv @@ -14,7 +14,8 @@ aircraft:design:drag_increment,0.0014,unitless aircraft:design:equipment_mass_coefficients,1014,0.0736,0.085,0.105,1504,1.65,126,9114,5,3,0,10,12,unitless aircraft:design:max_structural_speed,440,mi/h aircraft:design:part25_structural_category,3,unitless -aircraft:design:reserves,-0.15,unitless +aircraft:design:reserve_fuel_additional,0,lbm +aircraft:design:reserve_fuel_fraction,0.15,unitless aircraft:design:static_margin,0.05,unitless aircraft:design:structural_mass_increment,0,lbm aircraft:design:supercritical_drag_shift,0.033,unitless diff --git a/aviary/subsystems/aerodynamics/flops_based/ground_effect.py b/aviary/subsystems/aerodynamics/flops_based/ground_effect.py index 27aee4eee..8a6efb8dc 100644 --- a/aviary/subsystems/aerodynamics/flops_based/ground_effect.py +++ b/aviary/subsystems/aerodynamics/flops_based/ground_effect.py @@ -11,10 +11,7 @@ import openmdao.api as om from aviary.variable_info.functions import add_aviary_input -from aviary.variable_info.variables import Aircraft -from aviary.variable_info.variables import Dynamic as _Dynamic - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Aircraft, Dynamic class GroundEffect(om.ExplicitComponent): @@ -41,9 +38,10 @@ def setup(self): self.add_input('angle_of_attack', val=np.zeros(nn), units='rad') - add_aviary_input(self, Dynamic.ALTITUDE, np.zeros(nn), units='m') + add_aviary_input(self, Dynamic.Mission.ALTITUDE, np.zeros(nn), units='m') - add_aviary_input(self, Dynamic.FLIGHT_PATH_ANGLE, val=np.zeros(nn), units='rad') + add_aviary_input(self, Dynamic.Mission.FLIGHT_PATH_ANGLE, + val=np.zeros(nn), units='rad') self.add_input( 'minimum_drag_coefficient', 0.0, @@ -83,14 +81,14 @@ def setup_partials(self): ) self.declare_partials( - 'lift_coefficient', [Dynamic.ALTITUDE, 'base_lift_coefficient'], + 'lift_coefficient', [Dynamic.Mission.ALTITUDE, 'base_lift_coefficient'], rows=rows_cols, cols=rows_cols ) self.declare_partials( 'lift_coefficient', [ - 'angle_of_attack', Dynamic.FLIGHT_PATH_ANGLE, 'minimum_drag_coefficient', + 'angle_of_attack', Dynamic.Mission.FLIGHT_PATH_ANGLE, 'minimum_drag_coefficient', 'base_drag_coefficient', ], dependent=False @@ -104,7 +102,7 @@ def setup_partials(self): self.declare_partials( 'drag_coefficient', [ - 'angle_of_attack', Dynamic.ALTITUDE, Dynamic.FLIGHT_PATH_ANGLE, + 'angle_of_attack', Dynamic.Mission.ALTITUDE, Dynamic.Mission.FLIGHT_PATH_ANGLE, 'base_drag_coefficient', 'base_lift_coefficient' ], rows=rows_cols, cols=rows_cols @@ -121,8 +119,8 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): ground_altitude = options['ground_altitude'] angle_of_attack = inputs['angle_of_attack'] - altitude = inputs[Dynamic.ALTITUDE] - flight_path_angle = inputs[Dynamic.FLIGHT_PATH_ANGLE] + altitude = inputs[Dynamic.Mission.ALTITUDE] + flight_path_angle = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] minimum_drag_coefficient = inputs['minimum_drag_coefficient'] base_lift_coefficient = inputs['base_lift_coefficient'] base_drag_coefficient = inputs['base_drag_coefficient'] @@ -177,8 +175,8 @@ def compute_partials(self, inputs, J, discrete_inputs=None): ground_altitude = options['ground_altitude'] angle_of_attack = inputs['angle_of_attack'] - altitude = inputs[Dynamic.ALTITUDE] - flight_path_angle = inputs[Dynamic.FLIGHT_PATH_ANGLE] + altitude = inputs[Dynamic.Mission.ALTITUDE] + flight_path_angle = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] minimum_drag_coefficient = inputs['minimum_drag_coefficient'] base_lift_coefficient = inputs['base_lift_coefficient'] base_drag_coefficient = inputs['base_drag_coefficient'] @@ -224,7 +222,7 @@ def compute_partials(self, inputs, J, discrete_inputs=None): (d_hf_alt * lift_coeff_factor_denom) - (height_factor * d_lcfd_alt) ) / lift_coeff_factor_denom**2 - J['lift_coefficient', Dynamic.ALTITUDE] = base_lift_coefficient * d_lcf_alt + J['lift_coefficient', Dynamic.Mission.ALTITUDE] = base_lift_coefficient * d_lcf_alt J['lift_coefficient', 'base_lift_coefficient'] = lift_coeff_factor # endregion lift_coefficient wrt [altitude, base_lift_coefficient] @@ -306,7 +304,7 @@ def compute_partials(self, inputs, J, discrete_inputs=None): d_dc_fpa = base_lift_coefficient * (lift_coeff_factor - 1.) * d_ca_fpa - J['drag_coefficient', Dynamic.FLIGHT_PATH_ANGLE] = d_dc_fpa + J['drag_coefficient', Dynamic.Mission.FLIGHT_PATH_ANGLE] = d_dc_fpa # endregion drag_coefficient wrt flight_path_angle # region drag_coefficient wrt altitude @@ -336,7 +334,7 @@ def compute_partials(self, inputs, J, discrete_inputs=None): + combined_angle * base_lift_coefficient * d_lcf_alt ) - J['drag_coefficient', Dynamic.ALTITUDE] = d_dc_alt + J['drag_coefficient', Dynamic.Mission.ALTITUDE] = d_dc_alt # endregion drag_coefficient wrt altitude # region drag_coefficient wrt minimum_drag_coefficient @@ -401,7 +399,7 @@ def compute_partials(self, inputs, J, discrete_inputs=None): # Check for out of ground effect. idx = np.where(ground_effect_state > 1.1) if idx: - J['drag_coefficient', Dynamic.ALTITUDE][idx] = 0.0 + J['drag_coefficient', Dynamic.Mission.ALTITUDE][idx] = 0.0 J['drag_coefficient', 'minimum_drag_coefficient'][idx] = 0.0 J['drag_coefficient', 'base_lift_coefficient'][idx] = 0.0 J['drag_coefficient', 'base_drag_coefficient'][idx] = 1.0 @@ -409,9 +407,9 @@ def compute_partials(self, inputs, J, discrete_inputs=None): J['drag_coefficient', Aircraft.Wing.HEIGHT][idx] = 0.0 J['drag_coefficient', Aircraft.Wing.SPAN][idx] = 0.0 J['drag_coefficient', 'angle_of_attack'][idx] = 0.0 - J['drag_coefficient', Dynamic.FLIGHT_PATH_ANGLE][idx] = 0.0 + J['drag_coefficient', Dynamic.Mission.FLIGHT_PATH_ANGLE][idx] = 0.0 - J['lift_coefficient', Dynamic.ALTITUDE][idx] = 0.0 + J['lift_coefficient', Dynamic.Mission.ALTITUDE][idx] = 0.0 J['lift_coefficient', 'base_lift_coefficient'][idx] = 1.0 J['lift_coefficient', Aircraft.Wing.ASPECT_RATIO][idx] = 0.0 J['lift_coefficient', Aircraft.Wing.HEIGHT][idx] = 0.0 diff --git a/aviary/subsystems/aerodynamics/flops_based/solved_alpha_group.py b/aviary/subsystems/aerodynamics/flops_based/solved_alpha_group.py index 984fdac77..684c93612 100644 --- a/aviary/subsystems/aerodynamics/flops_based/solved_alpha_group.py +++ b/aviary/subsystems/aerodynamics/flops_based/solved_alpha_group.py @@ -76,7 +76,7 @@ def setup(self): promotes_outputs=[Dynamic.Mission.LIFT, Dynamic.Mission.DRAG]) balance = self.add_subsystem('balance', om.BalanceComp()) - balance.add_balance('alpha', val=np.ones(nn), units='deg') + balance.add_balance('alpha', val=np.ones(nn), units='deg', res_ref=1.0e6) self.connect('balance.alpha', 'tabular_aero.alpha') self.connect('needed_lift.lift_resid', 'balance.lhs:alpha') diff --git a/aviary/subsystems/aerodynamics/flops_based/takeoff_aero_group.py b/aviary/subsystems/aerodynamics/flops_based/takeoff_aero_group.py index d6245f60e..6185b25ca 100644 --- a/aviary/subsystems/aerodynamics/flops_based/takeoff_aero_group.py +++ b/aviary/subsystems/aerodynamics/flops_based/takeoff_aero_group.py @@ -11,11 +11,7 @@ GroundEffect from aviary.subsystems.aerodynamics.gasp_based.common import AeroForces from aviary.utils.aviary_values import AviaryValues -from aviary.variable_info.variables import Aircraft -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Aircraft, Dynamic, Mission class TakeoffAeroGroup(om.Group): @@ -119,7 +115,7 @@ def setup(self): } inputs = [ - 'angle_of_attack', Dynamic.ALTITUDE, Dynamic.FLIGHT_PATH_ANGLE, + 'angle_of_attack', Dynamic.Mission.ALTITUDE, Dynamic.Mission.FLIGHT_PATH_ANGLE, ('minimum_drag_coefficient', Mission.Takeoff.DRAG_COEFFICIENT_MIN), Aircraft.Wing.ASPECT_RATIO, Aircraft.Wing.HEIGHT, Aircraft.Wing.SPAN @@ -173,8 +169,8 @@ def setup(self): self.connect('ground_effect.drag_coefficient', 'ground_effect_drag') self.connect('climb_drag_coefficient', 'aero_forces.CD') - inputs = [Dynamic.DYNAMIC_PRESSURE, Aircraft.Wing.AREA] - outputs = [Dynamic.LIFT, Dynamic.DRAG] + inputs = [Dynamic.Mission.DYNAMIC_PRESSURE, Aircraft.Wing.AREA] + outputs = [Dynamic.Mission.LIFT, Dynamic.Mission.DRAG] self.add_subsystem( 'aero_forces', AeroForces(num_nodes=nn), diff --git a/aviary/subsystems/aerodynamics/flops_based/test/test_ground_effect.py b/aviary/subsystems/aerodynamics/flops_based/test/test_ground_effect.py index 940981305..bfcf6c008 100644 --- a/aviary/subsystems/aerodynamics/flops_based/test/test_ground_effect.py +++ b/aviary/subsystems/aerodynamics/flops_based/test/test_ground_effect.py @@ -6,10 +6,7 @@ from aviary.subsystems.aerodynamics.flops_based.ground_effect import GroundEffect from aviary.utils.aviary_values import AviaryValues, get_items -from aviary.variable_info.variables import Aircraft -from aviary.variable_info.variables import Dynamic as _Dynamic - -Dynamic = _Dynamic.Mission +from aviary.variable_info.variables import Aircraft, Dynamic class TestGroundEffect(unittest.TestCase): @@ -61,8 +58,8 @@ def make_problem(): inputs = AviaryValues({ 'angle_of_attack': (np.array([0., 2., 6]), 'deg'), - Dynamic.ALTITUDE: (np.array([100.0, 132, 155]), 'm'), - Dynamic.FLIGHT_PATH_ANGLE: (np.array([0., 0.5, 1.0]), 'deg'), + Dynamic.Mission.ALTITUDE: (np.array([100.0, 132, 155]), 'm'), + Dynamic.Mission.FLIGHT_PATH_ANGLE: (np.array([0., 0.5, 1.0]), 'deg'), 'minimum_drag_coefficient': minimum_drag_coefficient, 'base_lift_coefficient': base_lift_coefficient, 'base_drag_coefficient': base_drag_coefficient, diff --git a/aviary/subsystems/aerodynamics/flops_based/test/test_solved_aero_group.py b/aviary/subsystems/aerodynamics/flops_based/test/test_solved_aero_group.py index b24722b7a..6189f7572 100644 --- a/aviary/subsystems/aerodynamics/flops_based/test/test_solved_aero_group.py +++ b/aviary/subsystems/aerodynamics/flops_based/test/test_solved_aero_group.py @@ -6,7 +6,6 @@ import unittest import numpy as np import openmdao.api as om -import pkg_resources from openmdao.utils.assert_utils import assert_near_equal from aviary.interface.methods_for_level2 import AviaryProblem @@ -50,10 +49,8 @@ def test_solved_aero_pass_polar(self): prob = AviaryProblem() - csv_path = pkg_resources.resource_filename( - "aviary", "subsystems/aerodynamics/flops_based/test/data/high_wing_single_aisle.csv") - - prob.load_inputs(csv_path, local_phase_info) + prob.load_inputs( + "subsystems/aerodynamics/flops_based/test/data/high_wing_single_aisle.csv", local_phase_info) prob.add_pre_mission_systems() prob.add_phases() prob.add_post_mission_systems() @@ -91,6 +88,80 @@ def test_solved_aero_pass_polar(self): prob = AviaryProblem() + prob.load_inputs( + "subsystems/aerodynamics/flops_based/test/data/high_wing_single_aisle.csv", ph_in) + + prob.aviary_inputs.set_val(Aircraft.Design.LIFT_POLAR, + np.zeros_like(CL), units='unitless') + prob.aviary_inputs.set_val(Aircraft.Design.DRAG_POLAR, + np.zeros_like(CD), units='unitless') + + prob.add_pre_mission_systems() + prob.add_phases() + prob.add_post_mission_systems() + + prob.link_phases() + + prob.setup() + + prob.set_initial_guesses() + + prob.run_model() + + CL_pass = prob.get_val("traj.cruise.rhs_all.core_aerodynamics.tabular_aero.CL") + CD_pass = prob.get_val("traj.cruise.rhs_all.core_aerodynamics.tabular_aero.CD") + + assert_near_equal(CL_pass, CL_base, 1e-6) + assert_near_equal(CD_pass, CD_base, 1e-6) + + def test_solved_aero_pass_polar_unique_abscissa(self): + # Solved Aero with shortened lists of table abscissa. + local_phase_info = deepcopy(phase_info) + + prob = AviaryProblem() + + csv_path = "subsystems/aerodynamics/flops_based/test/data/high_wing_single_aisle.csv" + prob.load_inputs(csv_path, local_phase_info) + prob.add_pre_mission_systems() + prob.add_phases() + prob.add_post_mission_systems() + + prob.link_phases() + + prob.setup() + + prob.set_initial_guesses() + + prob.run_model() + + CL_base = prob.get_val("traj.cruise.rhs_all.core_aerodynamics.tabular_aero.CL") + CD_base = prob.get_val("traj.cruise.rhs_all.core_aerodynamics.tabular_aero.CD") + + # Lift and Drag polars passed from external component in static. + + ph_in = deepcopy(phase_info) + + alt = np.array([0., 3000., 6000., 9000., 12000., 15000., 18000., 21000., + 24000., 27000., 30000., 33000., 36000., 38000., 42000.]) + mach = np.array([0., 0.2, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9]) + alpha = np.array([-2., 0., 2., 4., 6., 8., 10.]) + + polar_builder = FakeDragPolarBuilder(name="aero", altitude=alt, mach=mach, + alpha=alpha) + aero_data = NamedValues() + aero_data.set_val('altitude', alt, 'ft') + aero_data.set_val('mach', mach, 'unitless') + aero_data.set_val('angle_of_attack', alpha, 'deg') + + subsystem_options = {'method': 'solved_alpha', + 'aero_data': aero_data, + 'training_data': True} + ph_in['pre_mission']['external_subsystems'] = [polar_builder] + + ph_in['cruise']['subsystem_options'] = {'core_aerodynamics': subsystem_options} + + prob = AviaryProblem() + prob.load_inputs(csv_path, ph_in) prob.aviary_inputs.set_val(Aircraft.Design.LIFT_POLAR, @@ -280,4 +351,4 @@ def build_pre_mission(self, aviary_inputs): if __name__ == "__main__": # unittest.main() test = TestSolvedAero() - test.test_solved_aero_pass_polar() + test.test_solved_aero_pass_polar_unique_abscissa() diff --git a/aviary/subsystems/aerodynamics/flops_based/test/test_takeoff_aero_group.py b/aviary/subsystems/aerodynamics/flops_based/test/test_takeoff_aero_group.py index e16c92fe4..076e84eaf 100644 --- a/aviary/subsystems/aerodynamics/flops_based/test/test_takeoff_aero_group.py +++ b/aviary/subsystems/aerodynamics/flops_based/test/test_takeoff_aero_group.py @@ -9,13 +9,9 @@ from aviary.utils.aviary_values import AviaryValues, get_items from aviary.models.N3CC.N3CC_data import ( N3CC, takeoff_subsystem_options, takeoff_subsystem_options_spoilers) -from aviary.variable_info.variables import Aircraft -from aviary.variable_info.variables import Dynamic as _Dynamic -from aviary.variable_info.variables import Mission +from aviary.variable_info.variables import Aircraft, Dynamic, Mission from aviary.variable_info.enums import LegacyCode -Dynamic = _Dynamic.Mission - class TestTakeoffAeroGroup(unittest.TestCase): @@ -80,18 +76,19 @@ def make_problem(subsystem_options={}): dynamic_inputs = AviaryValues({ 'angle_of_attack': (np.array([0., 2., 6.]), 'deg'), - Dynamic.ALTITUDE: (np.array([0., 32., 55.]), 'm'), - Dynamic.FLIGHT_PATH_ANGLE: (np.array([0., 0.5, 1.]), 'deg')}) + Dynamic.Mission.ALTITUDE: (np.array([0., 32., 55.]), 'm'), + Dynamic.Mission.FLIGHT_PATH_ANGLE: (np.array([0., 0.5, 1.]), 'deg')}) prob = om.Problem() prob.model.add_subsystem( "USatm", USatm1976Comp(num_nodes=nn), - promotes_inputs=[("h", Dynamic.ALTITUDE)], + promotes_inputs=[("h", Dynamic.Mission.ALTITUDE)], promotes_outputs=[ - "rho", ("sos", Dynamic.SPEED_OF_SOUND), ("temp", Dynamic.TEMPERATURE), - ("pres", Dynamic.STATIC_PRESSURE), "viscosity"]) + "rho", ("sos", Dynamic.Mission.SPEED_OF_SOUND), ("temp", + Dynamic.Mission.TEMPERATURE), + ("pres", Dynamic.Mission.STATIC_PRESSURE), "viscosity"]) aero_builder = CoreAerodynamicsBuilder(code_origin=LegacyCode.FLOPS) @@ -104,7 +101,7 @@ def make_problem(subsystem_options={}): **subsystem_options['core_aerodynamics']), promotes_outputs=aero_builder.mission_outputs(**subsystem_options['core_aerodynamics'])) - prob.model.set_input_defaults(Dynamic.ALTITUDE, np.zeros(nn), 'm') + prob.model.set_input_defaults(Dynamic.Mission.ALTITUDE, np.zeros(nn), 'm') prob.setup(force_alloc_complex=True) @@ -135,9 +132,9 @@ def make_problem(subsystem_options={}): # - generate new regression data if, and only if, takeoff aero group is updated with a # more trusted implementation _regression_data = AviaryValues({ - Dynamic.LIFT: ( + Dynamic.Mission.LIFT: ( [3028.138891962988, 4072.059743068957, 6240.85493286], _units_lift), - Dynamic.DRAG: ( + Dynamic.Mission.DRAG: ( [434.6285684000267, 481.5245555324278, 586.0976806512001], _units_drag)}) # NOTE: @@ -146,9 +143,9 @@ def make_problem(subsystem_options={}): # - generate new regression data if, and only if, takeoff aero group is updated with a # more trusted implementation _regression_data_spoiler = AviaryValues({ - Dynamic.LIFT: ( + Dynamic.Mission.LIFT: ( [-1367.5937129210124, -323.67286181504335, 1845.1223279759993], _units_lift), - Dynamic.DRAG: ( + Dynamic.Mission.DRAG: ( [895.9091503940268, 942.8051375264279, 1047.3782626452], _units_drag)}) @@ -204,8 +201,8 @@ def _generate_regression_data(subsystem_options={}): prob.run_model() - lift = prob.get_val(Dynamic.LIFT, _units_lift) - drag = prob.get_val(Dynamic.DRAG, _units_drag) + lift = prob.get_val(Dynamic.Mission.LIFT, _units_lift) + drag = prob.get_val(Dynamic.Mission.DRAG, _units_drag) prob.check_partials(compact_print=True, method="cs") diff --git a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/meta_model.py b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/meta_model.py index 84e4cf277..41df6b4a6 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/meta_model.py +++ b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/meta_model.py @@ -21,7 +21,7 @@ def setup(self): # VDEL1 VDEL1_interp = self.add_subsystem( "VDEL1_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ "aircraft:*", ], @@ -61,7 +61,7 @@ def setup(self): # VDEL2 VDEL2_interp = self.add_subsystem( "VDEL2_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ "flap_defl_ratio", ], @@ -149,7 +149,7 @@ def setup(self): # VLAM1 VLAM1_interp = self.add_subsystem( "VLAM1_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ "aircraft:*", ], @@ -215,7 +215,7 @@ def setup(self): # VLAM2 VLAM2_interp = self.add_subsystem( "VLAM2_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, ], @@ -275,7 +275,7 @@ def setup(self): # VLAM3 VLAM3_interp = self.add_subsystem( "VLAM3_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ "aircraft:*", ], @@ -339,7 +339,7 @@ def setup(self): # VLAM4 VLAM4_interp = self.add_subsystem( "VLAM4_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, ], @@ -430,7 +430,7 @@ def setup(self): # VLAM5 VLAM5_interp = self.add_subsystem( "VLAM5_intep", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ "aircraft:*", ], @@ -482,7 +482,7 @@ def setup(self): # VLAM6 VLAM6_interp = self.add_subsystem( "VLAM6_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ "flap_defl", ], @@ -603,7 +603,7 @@ def setup(self): # VLAM7 VLAM7_interp = self.add_subsystem( "VLAM7_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ Aircraft.Wing.FLAP_SPAN_RATIO, ], @@ -631,7 +631,7 @@ def setup(self): # VLAM10 VLAM10_interp = self.add_subsystem( "VLAM10_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ "slat_defl_ratio", ], @@ -689,7 +689,7 @@ def setup(self): # VLAM11 VLAM11_interp = self.add_subsystem( "VLAM11_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ Aircraft.Wing.SLAT_SPAN_RATIO, ], @@ -717,7 +717,7 @@ def setup(self): # VLAM13 VLAM13_interp = self.add_subsystem( "VLAM13_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ "reynolds", ], @@ -775,7 +775,7 @@ def setup(self): # VLAM14 VLAM14_interp = self.add_subsystem( "VLAM14_interp", - om.MetaModelStructuredComp(method="scipy_slinear", extrapolate=True), + om.MetaModelStructuredComp(method="1D-slinear", extrapolate=True), promotes_inputs=[ Dynamic.Mission.MACH, ], diff --git a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_Clmax.py b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_Clmax.py index 793185e94..cc96894ec 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_Clmax.py +++ b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_Clmax.py @@ -7,7 +7,6 @@ from aviary.subsystems.aerodynamics.gasp_based.flaps_model.Cl_max import \ CLmaxCalculation -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Dynamic """ @@ -75,13 +74,6 @@ def test_case(self): data, atol=6400, rtol=0.007 ) # large to account for large magnitude value, discrepancies are acceptable - @skipIfMissingXDSM('flaps_specs/CL_max.json') - def test_CLmax_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "flaps_specs/CL_max.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_basic_calculations.py b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_basic_calculations.py index ce2223692..16a7e427d 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_basic_calculations.py +++ b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_basic_calculations.py @@ -7,7 +7,6 @@ from aviary.subsystems.aerodynamics.gasp_based.flaps_model.basic_calculations import \ BasicFlapsCalculations -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft """ @@ -85,13 +84,6 @@ def test_case(self): data = self.prob.check_partials(out_stream=None, method="fd") assert_check_partials(data, atol=1e-6, rtol=4e-6) - @skipIfMissingXDSM('flaps_specs/basic.json') - def test_basic_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "flaps_specs/basic.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_increments.py b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_increments.py index 87b97a474..8e9fa1ebb 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_increments.py +++ b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_increments.py @@ -6,7 +6,6 @@ from aviary.subsystems.aerodynamics.gasp_based.flaps_model.L_and_D_increments import \ LiftAndDragIncrements -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft """ @@ -57,13 +56,6 @@ def test_case(self): data = self.prob.check_partials(out_stream=None, method="fd") assert_check_partials(data, atol=1e-4, rtol=1e-4) - @skipIfMissingXDSM('flaps_specs/increments.json') - def test_increment_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "flaps_specs/increments.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_metamodel.py b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_metamodel.py index 725b5ae60..84581a8ea 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_metamodel.py +++ b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/test/test_metamodel.py @@ -7,7 +7,6 @@ from aviary.subsystems.aerodynamics.gasp_based.flaps_model.meta_model import \ MetaModelGroup -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.enums import FlapType from aviary.variable_info.options import get_option_defaults from aviary.variable_info.variables import Aircraft, Dynamic @@ -109,13 +108,6 @@ def test_case(self): data = self.prob.check_partials(out_stream=None, method="fd") assert_check_partials(data, atol=1e-4, rtol=1e-4) - @skipIfMissingXDSM('flaps_specs/tables.json') - def test_lookup_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "flaps_specs/tables.json") - class MetaModelTestCaseSingleSlotted(unittest.TestCase): def setUp(self): @@ -155,13 +147,6 @@ def test_case(self): data = self.prob.check_partials(out_stream=None, method="fd") assert_check_partials(data, atol=1e-4, rtol=1e-4) - @skipIfMissingXDSM('flaps_specs/tables.json') - def test_lookup_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "flaps_specs/tables.json") - class MetaModelTestCaseFowler(unittest.TestCase): def setUp(self): @@ -191,13 +176,6 @@ def test_case(self): data = self.prob.check_partials(out_stream=None, method="fd") assert_check_partials(data, atol=1e-4, rtol=1e-4) - @skipIfMissingXDSM('flaps_specs/tables.json') - def test_lookup_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "flaps_specs/tables.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/aerodynamics/gasp_based/gaspaero.py b/aviary/subsystems/aerodynamics/gasp_based/gaspaero.py index a5ca197fe..0ff9b7ceb 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/gaspaero.py +++ b/aviary/subsystems/aerodynamics/gasp_based/gaspaero.py @@ -789,7 +789,7 @@ def setup(self): self.add_subsystem("xlifts", Xlifts(num_nodes=nn), promotes=["*"]) # implements EAERO - interp = om.MetaModelStructuredComp(method="slinear") + interp = om.MetaModelStructuredComp(method="2D-slinear") interp.add_input("bbar", 0.0, units="unitless", training_data=xbbar) interp.add_input("hbar", 0.0, units="unitless", training_data=xhbar) interp.add_output("sigma", 0.0, units="unitless", training_data=sig1) @@ -1044,7 +1044,7 @@ def compute(self, inputs, outputs): outputs["CD"] = cd0 + cdi + delcdm -class LiftCoef(om.ExplicitComponent): +class LiftCoeff(om.ExplicitComponent): """GASP lift coefficient calculation for low-speed near-ground flight""" def initialize(self): @@ -1117,7 +1117,6 @@ def setup_partials(self): self.declare_partials("CL_base", ["*"], method="cs") self.declare_partials("CL_base", dynvars, rows=ar, cols=ar, method="cs") - self.declare_partials("CL_base", ["CL_max_flaps"], val=0) self.declare_partials("dCL_flaps_full", ["dCL_flaps_model"], method="cs") self.declare_partials( @@ -1125,7 +1124,6 @@ def setup_partials(self): self.declare_partials("alpha_stall", ["*"], method="cs") self.declare_partials("alpha_stall", dynvars, rows=ar, cols=ar, method="cs") - # self.declare_partials("alpha_stall", ["lift_ratio"], val=0) self.declare_partials("CL_max", ["CL_max_flaps"], method="cs") self.declare_partials("CL_max", ["lift_ratio"], rows=ar, cols=ar, method="cs") @@ -1180,7 +1178,7 @@ def compute(self, inputs, outputs): outputs["CL_max"] = CL_max_flaps * (1 + lift_ratio) -class LiftCoefClean(om.ExplicitComponent): +class LiftCoeffClean(om.ExplicitComponent): """Clean wing lift coefficient for high-speed flight""" def initialize(self): @@ -1297,7 +1295,7 @@ def setup(self): self.add_subsystem("lift2cl", CLFromLift(num_nodes=nn), promotes=["*"]) self.add_subsystem( "lift_coef", - LiftCoefClean(output_alpha=self.options["output_alpha"], num_nodes=nn), + LiftCoeffClean(output_alpha=self.options["output_alpha"], num_nodes=nn), promotes=["*"], ) self.add_subsystem("drag_coef", DragCoefClean(num_nodes=nn), promotes=["*"]) @@ -1379,7 +1377,7 @@ def setup(self): # alpha be useful? an issue is ground effects depend on alpha self.add_subsystem( "lift_coef", - LiftCoef(num_nodes=nn), + LiftCoeff(num_nodes=nn), promotes_inputs=["*"], promotes_outputs=["*"] ) diff --git a/aviary/subsystems/aerodynamics/gasp_based/table_based.py b/aviary/subsystems/aerodynamics/gasp_based/table_based.py index c9fc4d397..d333fe16f 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/table_based.py +++ b/aviary/subsystems/aerodynamics/gasp_based/table_based.py @@ -588,11 +588,12 @@ def _structure_special_grid(aero_data): # units don't matter, not using values aoa = aero_data.get_val('angle_of_attack', 'deg') - # special case alpha - this format saturates alpha at its max - # get unique alphas at zero alt mach 0, should cover the full range - # TODO this does not work with data that is already in structured grid format - mask = (data[0] == x0[0]) & (data[1] == x1[0]) - aoa = np.unique(aoa[mask]) + if data[0].shape[0] > x0.shape[0]: + # special case alpha - this format saturates alpha at its max + # get unique alphas at zero alt mach 0, should cover the full range + mask = (data[0] == x0[0]) & (data[1] == x1[0]) + aoa = np.unique(aoa[mask]) + _, _, aoa = np.meshgrid(x0, x1, aoa) # put the aoa data back in the NamedValues object diff --git a/aviary/subsystems/aerodynamics/gasp_based/test/test_table_based.py b/aviary/subsystems/aerodynamics/gasp_based/test/test_table_based.py index bc449e2da..a70027ff0 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/test/test_table_based.py +++ b/aviary/subsystems/aerodynamics/gasp_based/test/test_table_based.py @@ -10,7 +10,6 @@ from aviary.subsystems.aerodynamics.gasp_based.table_based import ( CruiseAero, LowSpeedAero) -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Dynamic @@ -88,14 +87,6 @@ class TestLowSpeedAero(unittest.TestCase): ground_data = pkg_resources.resource_filename( "aviary", f"subsystems/aerodynamics/gasp_based/data/large_single_aisle_1_aero_ground.txt") - @skipIfMissingXDSM('rotation_specs/aero.json') - def test_spec(self): - comp = LowSpeedAero(free_aero_data=self.free_data, - flaps_aero_data=self.flaps_data, - ground_aero_data=self.ground_data, - extrapolate=True) - assert_match_spec(comp, "rotation_specs/aero.json") - @unittest.skipIf(version.parse(openmdao.__version__) < version.parse("3.26"), "Older version of OpenMDAO does not properly skip Metamodel.") def test_groundroll(self): diff --git a/aviary/subsystems/geometry/gasp_based/test/test_empennage.py b/aviary/subsystems/geometry/gasp_based/test/test_empennage.py index 12bbaec6a..8b5cf6ad1 100644 --- a/aviary/subsystems/geometry/gasp_based/test/test_empennage.py +++ b/aviary/subsystems/geometry/gasp_based/test/test_empennage.py @@ -7,7 +7,6 @@ from aviary.subsystems.geometry.gasp_based.empennage import (EmpennageSize, TailSize, TailVolCoef) -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.options import get_option_defaults from aviary.variable_info.variables import Aircraft @@ -185,16 +184,6 @@ def test_large_sinle_aisle_1_defaults(self): self.prob[Aircraft.VerticalTail.MOMENT_ARM], 49.87526, tol ) # note: slightly different from GASP output value, likely numerical diff.s, this value is from Kenny - @skipIfMissingXDSM('size_both1_specs/empennage.json') - def test_io_emp_spec_defaults(self): - self.prob.model.emp.options["aviary_options"] = get_option_defaults() - - self.prob.setup(check=False, force_alloc_complex=True) - - subsystem = self.prob.model - - assert_match_spec(subsystem, "size_both1_specs/empennage.json") - def test_large_sinle_aisle_1_calc_volcoefs(self): options = get_option_defaults() options.set_val(Aircraft.Design.COMPUTE_HTAIL_VOLUME_COEFF, @@ -218,20 +207,6 @@ def test_large_sinle_aisle_1_calc_volcoefs(self): self.prob[Aircraft.VerticalTail.VOLUME_COEFFICIENT], 0.11623, tol ) # not actual GASP value - @skipIfMissingXDSM('size_both2_specs/empennage.json') - def test_io_emp_spec_vol_coefs(self): - options = get_option_defaults() - options.set_val(Aircraft.Design.COMPUTE_HTAIL_VOLUME_COEFF, - val=True, units='unitless') - options.set_val(Aircraft.Design.COMPUTE_VTAIL_VOLUME_COEFF, - val=True, units='unitless') - self.prob.model.emp.options["aviary_options"] = options - self.prob.setup(check=False, force_alloc_complex=True) - - subsystem = self.prob.model - - assert_match_spec(subsystem, "size_both2_specs/empennage.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/geometry/gasp_based/test/test_size_group.py b/aviary/subsystems/geometry/gasp_based/test/test_size_group.py index b2418dd5e..9549599df 100644 --- a/aviary/subsystems/geometry/gasp_based/test/test_size_group.py +++ b/aviary/subsystems/geometry/gasp_based/test/test_size_group.py @@ -6,7 +6,6 @@ from aviary.subsystems.geometry.gasp_based.size_group import SizeGroup from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Mission # this is the GASP test case, input and output values based on large single aisle 1 v3 without bug fix @@ -159,13 +158,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=2e-12, rtol=1e-12) - @skipIfMissingXDSM('mass_and_sizing_basic_specs/size.json') - def test_io_wing_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_basic_specs/size.json") - class SizeGroupTestCase2(unittest.TestCase): def setUp(self): @@ -390,13 +382,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=3e-10, rtol=1e-12) - @skipIfMissingXDSM('mass_and_sizing_both_specs/size.json') - def test_io_wing_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_both_specs/size.json") - class SizeGroupTestCase3(unittest.TestCase): def setUp(self): diff --git a/aviary/subsystems/geometry/gasp_based/test/test_wing.py b/aviary/subsystems/geometry/gasp_based/test/test_wing.py index 7a706e2d8..0154e0d1f 100644 --- a/aviary/subsystems/geometry/gasp_based/test/test_wing.py +++ b/aviary/subsystems/geometry/gasp_based/test/test_wing.py @@ -8,7 +8,6 @@ WingParameters, WingSize) from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Mission @@ -310,13 +309,6 @@ def setUp(self): self.prob.setup(check=False, force_alloc_complex=True) - @skipIfMissingXDSM('size_basic_specs/wing.json') - def test_io_wing_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "size_basic_specs/wing.json") - def test_case1(self): self.prob.run_model() @@ -394,13 +386,6 @@ def setUp(self): self.prob.setup(check=False, force_alloc_complex=True) - @skipIfMissingXDSM('size_both1_specs/wing.json') - def test_io_wing_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "size_both1_specs/wing.json") - def test_case1(self): self.prob.run_model() @@ -563,13 +548,6 @@ def setUp(self): self.prob.setup(check=False, force_alloc_complex=True) - @skipIfMissingXDSM('size_both2_specs/wing.json') - def test_io_wing_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "size_both2_specs/wing.json") - class WingGroupTestCase5(unittest.TestCase): def setUp(self): diff --git a/aviary/subsystems/mass/gasp_based/test/test_design_load.py b/aviary/subsystems/mass/gasp_based/test/test_design_load.py index 6f374712d..29af24256 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_design_load.py +++ b/aviary/subsystems/mass/gasp_based/test/test_design_load.py @@ -9,7 +9,6 @@ LoadParameters, LoadSpeeds) from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Mission @@ -880,13 +879,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-7, rtol=2e-7) - @skipIfMissingXDSM('mass_and_sizing_basic_specs/design_load.json') - def test_io_fixed_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_basic_specs/design_load.json") - # this is the large single aisle 1 V3 test case class DesignLoadGroupTestCase2smooth(unittest.TestCase): @@ -932,13 +924,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-7, rtol=2e-7) - @skipIfMissingXDSM('mass_and_sizing_basic_specs/design_load.json') - def test_io_fixed_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_basic_specs/design_load.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/mass/gasp_based/test/test_equipment_and_useful_load.py b/aviary/subsystems/mass/gasp_based/test/test_equipment_and_useful_load.py index 0762fe771..392e23325 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_equipment_and_useful_load.py +++ b/aviary/subsystems/mass/gasp_based/test/test_equipment_and_useful_load.py @@ -7,7 +7,6 @@ from aviary.subsystems.mass.gasp_based.equipment_and_useful_load import \ EquipAndUsefulLoadMass from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.enums import GASPEngineType from aviary.variable_info.variables import Aircraft, Mission @@ -605,13 +604,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=8e-12, rtol=1e-12) - @skipIfMissingXDSM('mass_and_sizing_basic_specs/equip.json') - def test_io_equip_and_useful_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_basic_specs/equip.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/mass/gasp_based/test/test_fixed.py b/aviary/subsystems/mass/gasp_based/test/test_fixed.py index 562f15fae..295517684 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_fixed.py +++ b/aviary/subsystems/mass/gasp_based/test/test_fixed.py @@ -14,7 +14,6 @@ PayloadMass, TailMass) from aviary.utils.aviary_values import AviaryValues, get_keys from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Mission @@ -1116,13 +1115,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=3e-11, rtol=1e-12) - @skipIfMissingXDSM('mass_and_sizing_both_specs/fixed_mass.json') - def test_io_fixed_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_both_specs/fixed_mass.json") - class FixedMassGroupTestCase2(unittest.TestCase): def setUp(self): diff --git a/aviary/subsystems/mass/gasp_based/test/test_fuel.py b/aviary/subsystems/mass/gasp_based/test/test_fuel.py index fe3c123f8..099be4d60 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_fuel.py +++ b/aviary/subsystems/mass/gasp_based/test/test_fuel.py @@ -10,7 +10,6 @@ FuelSysAndFullFuselageMass, FuselageAndStructMass) from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Mission @@ -611,13 +610,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=2e-11, rtol=1e-12) - @skipIfMissingXDSM('mass_and_sizing_basic_specs/fuel_mass.json') - def test_io_fuel_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_basic_specs/fuel_mass.json") - class FuelMassGroupTestCase3( unittest.TestCase @@ -746,13 +738,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=3e-9, rtol=6e-11) - @skipIfMissingXDSM('mass_and_sizing_basic_specs/fuel_mass.json') - def test_io_fuel_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_basic_specs/fuel_mass.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/mass/gasp_based/test/test_mass_summation.py b/aviary/subsystems/mass/gasp_based/test/test_mass_summation.py index c1e8a6d6a..41a0f90c4 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_mass_summation.py +++ b/aviary/subsystems/mass/gasp_based/test/test_mass_summation.py @@ -8,7 +8,6 @@ from aviary.subsystems.mass.gasp_based.mass_premission import MassPremission from aviary.utils.aviary_values import get_items from aviary.variable_info.options import get_option_defaults, is_option -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.models.large_single_aisle_1.V3_bug_fixed_IO import ( V3_bug_fixed_non_metadata, V3_bug_fixed_options) from aviary.variable_info.variables import Aircraft, Mission @@ -158,13 +157,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=3e-10, rtol=1e-12) - @skipIfMissingXDSM('statics_specs/mass.json') - def test_mass_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "statics_specs/mass.json") - class MassSummationTestCase2(unittest.TestCase): """ diff --git a/aviary/subsystems/mass/gasp_based/test/test_wing.py b/aviary/subsystems/mass/gasp_based/test/test_wing.py index 7a275644c..956ca4339 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_wing.py +++ b/aviary/subsystems/mass/gasp_based/test/test_wing.py @@ -8,7 +8,6 @@ WingMassSolve, WingMassTotal) from aviary.variable_info.options import get_option_defaults -from aviary.utils.test_utils.IO_test_util import assert_match_spec, skipIfMissingXDSM from aviary.variable_info.variables import Aircraft, Mission @@ -271,13 +270,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('mass_and_sizing_basic_specs/wing_mass.json') - def test_io_equip_and_useful_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_basic_specs/wing_mass.json") - class WingMassGroupTestCase2(unittest.TestCase): def setUp(self): @@ -472,13 +464,6 @@ def test_case1(self): partial_data = self.prob.check_partials(out_stream=None, method="cs") assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - @skipIfMissingXDSM('mass_and_sizing_both_specs/wing_mass.json') - def test_io_equip_and_useful_group_spec(self): - - subsystem = self.prob.model - - assert_match_spec(subsystem, "mass_and_sizing_both_specs/wing_mass.json") - if __name__ == "__main__": unittest.main() diff --git a/aviary/subsystems/propulsion/test/test_custom_engine_model.py b/aviary/subsystems/propulsion/test/test_custom_engine_model.py index 7dace05cf..2222168a5 100644 --- a/aviary/subsystems/propulsion/test/test_custom_engine_model.py +++ b/aviary/subsystems/propulsion/test/test_custom_engine_model.py @@ -1,16 +1,12 @@ import unittest -import openmdao import dymos as dm import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal -from packaging import version from aviary.subsystems.propulsion.engine_model import EngineModel from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.variables import Dynamic -import dymos as dm -import unittest from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.testing_utils import use_tempdirs @@ -126,13 +122,8 @@ def get_initial_guesses(self): @use_tempdirs class CustomEngineTest(unittest.TestCase): def test_custom_engine(self): - - import pkg_resources - from aviary.interface.methods_for_level2 import AviaryProblem - aero_data = "subsystems/aerodynamics/gasp_based/data/large_single_aisle_1_aero_free.txt" - phase_info = { 'pre_mission': { 'include_takeoff': False, @@ -140,38 +131,28 @@ def test_custom_engine(self): 'optimize_mass': True, }, 'cruise': { - 'subsystem_options': { - 'core_aerodynamics': {'method': 'solved_alpha', 'aero_data': aero_data, 'training_data': False} + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + "optimize_mach": False, + "optimize_altitude": False, + "polynomial_control_order": 1, + "num_segments": 2, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.72, "unitless"), + "final_mach": (0.72, "unitless"), + "mach_bounds": ((0.7, 0.74), "unitless"), + "initial_altitude": (35000.0, "ft"), + "final_altitude": (35000.0, "ft"), + "altitude_bounds": ((23000.0, 38000.0), "ft"), + "throttle_enforcement": "boundary_constraint", + "fix_initial": False, + "constrain_final": False, + "fix_duration": False, + "initial_bounds": ((0.0, 0.0), "min"), + "duration_bounds": ((10., 30.), "min"), }, - 'external_subsystems': [], - 'user_options': { - 'fix_initial': False, - 'fix_final': False, - 'fix_duration': False, - 'num_segments': 2, - 'order': 3, - 'initial_ref': (1., 's'), - 'initial_bounds': ((0., 0.), 's'), - 'duration_ref': (21.e3, 's'), - 'duration_bounds': ((1.e3, 10.e3), 's'), - 'min_altitude': (10.668e3, 'm'), - 'max_altitude': (10.668e3, 'm'), - 'min_mach': 0.8, - 'max_mach': 0.8, - 'required_available_climb_rate': (1.524, 'm/s'), - 'input_initial': False, - 'mass_f_cruise': (1.e4, 'lbm'), - 'range_f_cruise': (1.e6, 'm'), - }, - 'initial_guesses': { - 'times': ([0., 30.], 'min'), - 'altitude': ([35.e3, 35.e3], 'ft'), - 'velocity': ([455.49, 455.49], 'kn'), - 'mass': ([130.e3, 128.e3], 'lbm'), - 'range': ([0., 300.], 'nmi'), - 'velocity_rate': ([0., 0.], 'm/s**2'), - 'throttle': ([0.6, 0.6], 'unitless'), - } + "initial_guesses": {"times": ([0, 30], "min")}, }, 'post_mission': { 'include_landing': False, @@ -179,18 +160,15 @@ def test_custom_engine(self): } } - csv_path = pkg_resources.resource_filename( - "aviary", "models/test_aircraft/aircraft_for_bench_GwFm.csv") - prob = AviaryProblem(reports=False) # Load aircraft and options data from user # Allow for user overrides here - prob.load_inputs(csv_path, phase_info, engine_builder=SimpleTestEngine()) + prob.load_inputs("models/test_aircraft/aircraft_for_bench_GwFm.csv", + phase_info, engine_builder=SimpleTestEngine()) - # Have checks for clashing user inputs - # Raise warnings or errors depending on how clashing the issues are - prob.check_inputs() + # Preprocess inputs + prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() @@ -225,34 +203,6 @@ def test_custom_engine(self): assert_near_equal(float(prob.get_val('traj.cruise.rhs_all.y')), 4., tol) - prob_vars = prob.list_problem_vars( - print_arrays=True, driver_scaling=False, out_stream=None) - - design_vars_dict = dict(prob_vars['design_vars']) - - # List of all expected variable names in design_vars - expected_var_names = [ - 'traj.phases.cruise.indep_states.states:altitude', - 'traj.phases.cruise.indep_states.states:velocity', - 'traj.phases.cruise.indep_states.states:mass', - 'traj.phases.cruise.indep_states.states:range', - ] - - # Check that all expected variable names are present in design_vars - for var_name in expected_var_names: - self.assertIn(var_name, design_vars_dict) - - # Check values - assert_near_equal(design_vars_dict['traj.phases.cruise.indep_states.states:altitude']['val'], [ - 10668.] * 7, tolerance=tol) - assert_near_equal(design_vars_dict['traj.phases.cruise.indep_states.states:velocity']['val'], [ - 234.3243] * 7, tolerance=tol) - # print the mass and range - assert_near_equal(design_vars_dict['traj.phases.cruise.indep_states.states:mass']['val'], [ - 58967.0081, 58805.95966377, 58583.74569223, 58513.41573, 58352.36729377, 58130.15332223, 58059.82336], tolerance=tol) - assert_near_equal(design_vars_dict['traj.phases.cruise.indep_states.states:range']['val'], [ - 0., 98633.17494548, 234726.82505452, 277800., 376433.17494548, 512526.82505452, 555600.], tolerance=tol) - if __name__ == '__main__': unittest.main() diff --git a/aviary/subsystems/test/subsystem_tester.py b/aviary/subsystems/test/subsystem_tester.py index 4a65edb0d..0f16f37db 100644 --- a/aviary/subsystems/test/subsystem_tester.py +++ b/aviary/subsystems/test/subsystem_tester.py @@ -23,7 +23,10 @@ def import_builder(path_to_builder: str, base_package='aviary.examples.external_ ''' try: package, method = path_to_builder.rsplit('.', 1) - module = import_module(package, base_package) + package_path, package_name = package.rsplit('.', 1) + module_path = '.'.join([path_to_builder, package_path]) if package_path \ + else path_to_builder + module = import_module(package_name, module_path) builder = getattr(module, method) except ImportError: builder = 'Skipping due to missing dependencies' diff --git a/aviary/subsystems/test/test_external_subsystem_bus.py b/aviary/subsystems/test/test_external_subsystem_bus.py index 92b8756c0..a5a329468 100644 --- a/aviary/subsystems/test/test_external_subsystem_bus.py +++ b/aviary/subsystems/test/test_external_subsystem_bus.py @@ -1,8 +1,7 @@ """ - Test external subsystem bus API. +Test external subsystem bus API. """ from copy import deepcopy -import pkg_resources import unittest import numpy as np @@ -10,7 +9,6 @@ import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal -import aviary.api as av from aviary.interface.default_phase_info.height_energy import phase_info as ph_in from aviary.interface.methods_for_level2 import AviaryProblem from aviary.subsystems.subsystem_builder_base import SubsystemBuilderBase @@ -23,7 +21,7 @@ def setup(self): self.add_output('for_cruise', np.ones((2, 1)), units='ft') self.add_output('for_descent', np.ones((2, 1)), units='ft') - def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): + def compute(self, inputs, outputs): outputs['for_climb'] = np.array([[3.1], [1.7]]) outputs['for_cruise'] = np.array([[1.2], [4.1]]) outputs['for_descent'] = np.array([[3.], [8.]]) @@ -39,7 +37,7 @@ def setup(self): self.add_input('xx', shape=shape, units='ft') self.add_output('yy', shape=shape, units='ft') - def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): + def compute(self, inputs, outputs): outputs['yy'] = 2.0 * inputs['xx'] @@ -92,10 +90,9 @@ def test_external_subsystem_bus(self): prob = AviaryProblem() - csv_path = pkg_resources.resource_filename( - "aviary", "models/test_aircraft/aircraft_for_bench_FwFm.csv") + csv_path = "models/test_aircraft/aircraft_for_bench_FwFm.csv" prob.load_inputs(csv_path, phase_info) - prob.check_inputs() + prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() prob.add_phases() diff --git a/aviary/utils/Fortran_to_Aviary.py b/aviary/utils/Fortran_to_Aviary.py index 3273f0ee5..f8b9d9a77 100644 --- a/aviary/utils/Fortran_to_Aviary.py +++ b/aviary/utils/Fortran_to_Aviary.py @@ -352,7 +352,7 @@ def update_gasp_options(vehicle_data): ## PROBLEM TYPE ## # if multiple values of target_range are specified, use the one that corresponds to the problem_type - design_range, range_units = input_values.get_item(Mission.Design.RANGE) + design_range, distance_units = input_values.get_item(Mission.Design.RANGE) try: problem_type = input_values.get_val('problem_type')[0] except KeyError: @@ -372,7 +372,7 @@ def update_gasp_options(vehicle_data): else: if design_range == 0: input_values.set_val('problem_type', ['fallout']) - input_values.set_val(Mission.Design.RANGE, [design_range], range_units) + input_values.set_val(Mission.Design.RANGE, [design_range], distance_units) ## STRUT AND FOLD ## strut_loc = input_values.get_val(Aircraft.Strut.ATTACHMENT_LOCATION, 'ft')[0] @@ -437,6 +437,17 @@ def update_gasp_options(vehicle_data): input_values.set_val(Aircraft.Wing.FLAP_DRAG_INCREMENT_OPTIMUM, [[.12, .23, .13, .23, .23, .1, .15][flap_ind]]) + res = input_values.get_val(Aircraft.Design.RESERVE_FUEL_ADDITIONAL, units='lbm')[0] + if res <= 0: + input_values.set_val(Aircraft.Design.RESERVE_FUEL_ADDITIONAL, [0], units='lbm') + input_values.set_val(Aircraft.Design.RESERVE_FUEL_FRACTION, + [-res], units='unitless') + elif res >= 10: + input_values.set_val(Aircraft.Design.RESERVE_FUEL_FRACTION, + [0], units='unitless') + else: + ValueError('"FRESF" is not valid between 0 and 10.') + vehicle_data['input_values'] = input_values return vehicle_data diff --git a/aviary/utils/legacy_code_data/gasp_default_values.dat b/aviary/utils/legacy_code_data/gasp_default_values.dat index 57a3de66c..686f5f1d7 100644 --- a/aviary/utils/legacy_code_data/gasp_default_values.dat +++ b/aviary/utils/legacy_code_data/gasp_default_values.dat @@ -83,7 +83,7 @@ EYEW=1.5,deg FLAPN=1,unitless FN_REF=28690,lbf FPYL=0.7,unitless -FRESF=4998,unitless +FRESF=4998,lbm FUELD=6.687,lbm/galUS FVOL_MRG=0,unitless HAPP=50,ft diff --git a/aviary/utils/preprocessors.py b/aviary/utils/preprocessors.py index 28f222f7b..57cd37f84 100644 --- a/aviary/utils/preprocessors.py +++ b/aviary/utils/preprocessors.py @@ -55,9 +55,10 @@ def preprocess_crewpayload(aviary_options: AviaryValues): passenger_check += aviary_options.get_val( Aircraft.CrewPayload.NUM_BUSINESS_CLASS) passenger_check += aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS) - # if passenger_count != passenger_check: - # raise om.AnalysisError( - # "ERROR: In preprocesssors.py: passenger_count does not equal the sum of firt class + business class + tourist class passengers.") + # only perform check if at least one passenger class is entered + if passenger_check > 0 and passenger_count != passenger_check: + raise om.AnalysisError( + f"ERROR: In preprocesssors.py: passenger_count ({passenger_count}) does not equal the sum of first class + business class + tourist class passengers (total of {passenger_check}).") if Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS not in aviary_options: flight_attendants_count = 0 # assume no passengers diff --git a/aviary/utils/process_input_decks.py b/aviary/utils/process_input_decks.py index 3705f18c7..0250a2a2f 100644 --- a/aviary/utils/process_input_decks.py +++ b/aviary/utils/process_input_decks.py @@ -51,6 +51,7 @@ def create_vehicle(vehicle_deck=''): """ aircraft_values = get_option_defaults(engine=False) + # TODO temporary, needed until debug_mode retired in favor of new verbosity flag # TODO remove all hardcoded GASP values here, find appropriate place for them aircraft_values.set_val('debug_mode', val=False) aircraft_values.set_val('INGASP.JENGSZ', val=4) @@ -59,13 +60,13 @@ def create_vehicle(vehicle_deck=''): aircraft_values.set_val('mass_defect', val=10000, units='lbm') aircraft_values.set_val('problem_type', val=ProblemType.SIZING) aircraft_values.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False) - aircraft_values.set_val(Aircraft.Design.RESERVES, val=4998) - vehicle_deck = get_path(vehicle_deck) - - parse_inputs(vehicle_deck, aircraft_values) - # update the dependent options with the current values - update_options(aircraft_values, initial_guesses) + if isinstance(vehicle_deck, AviaryValues): + aircraft_values.update(vehicle_deck) + initial_guesses = {} + else: + vehicle_deck = get_path(vehicle_deck) + aircraft_values, initial_guesses = parse_inputs(vehicle_deck, aircraft_values) return aircraft_values, initial_guesses @@ -84,6 +85,18 @@ def parse_inputs(vehicle_deck, aircraft_values: AviaryValues(), meta_data=_MetaD ------- tuple: Updated aircraft values and initial guesses. """ + initial_guesses = { + # initial_guesses is a dictionary that contains values used to initialize the trajectory + 'actual_takeoff_mass': 0, + 'rotation_mass': .99, + 'operating_empty_mass': 0, + 'fuel_burn_per_passenger_mile': 0.1, + 'cruise_mass_final': 0, + 'flight_duration': 0, + 'time_to_climb': 0, + 'climb_range': 0, + 'reserves': 0 + } guess_names = list(initial_guesses.keys()) with open(vehicle_deck, newline='') as f_in: @@ -120,7 +133,7 @@ def parse_inputs(vehicle_deck, aircraft_values: AviaryValues(), meta_data=_MetaD aircraft_values = set_value(var_name, var_values, aircraft_values) continue - elif var_name in meta_data.keys(): + if var_name in meta_data.keys(): aircraft_values = set_value( var_name, var_values, aircraft_values, units=data_units, is_array=is_array, meta_data=meta_data) continue @@ -135,8 +148,11 @@ def parse_inputs(vehicle_deck, aircraft_values: AviaryValues(), meta_data=_MetaD return aircraft_values, initial_guesses +# TODO this should be a preprocessor, and tasks split to be specific to subsystem +# e.g. aero preprocessor, mass preprocessor, 2DOF preprocessor, etc. + -def update_options(aircraft_values: AviaryValues(), initial_guesses): +def update_GASP_options(aircraft_values: AviaryValues()): """ Updates options based on the current values in aircraft_values. This function also handles special cases and prints debug information if the debug mode is active. @@ -144,7 +160,6 @@ def update_options(aircraft_values: AviaryValues(), initial_guesses): Parameters ---------- aircraft_values (AviaryValues): An instance of AviaryValues containing current aircraft values. - initial_guesses (dict): A dictionary of initial guesses for various parameters. Returns ------- @@ -153,8 +168,6 @@ def update_options(aircraft_values: AviaryValues(), initial_guesses): # update the options that depend on variables update_dependent_options(aircraft_values, dependent_options) - # TODO this is GASP only, don't always run it! These should go in a GASP-only options - # preprocessor ## STRUT AND FOLD ## if not aircraft_values.get_val(Aircraft.Wing.HAS_STRUT): aircraft_values.set_val( @@ -174,18 +187,13 @@ def update_options(aircraft_values: AviaryValues(), initial_guesses): aircraft_values.set_val( Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, val=False) - initial_guessing(aircraft_values) - if aircraft_values.get_val('debug_mode'): print('\nOptions') for key in get_keys(aircraft_values): val, units = aircraft_values.get_item(key) - print(key, val) - print('\nInitial Guesses') - for key, value in initial_guesses.items(): - print(key, value) + print(key, val, units) - return aircraft_values, initial_guesses + return aircraft_values def update_dependent_options(aircraft_values: AviaryValues(), dependent_options): @@ -220,7 +228,7 @@ def update_dependent_options(aircraft_values: AviaryValues(), dependent_options) return aircraft_values -def initial_guessing(aircraft_values: AviaryValues()): +def initial_guessing(aircraft_values: AviaryValues(), initial_guesses): """ Sets initial guesses for various aircraft parameters based on the current problem type, aircraft values, and other factors. It calculates and sets values like takeoff mass, cruise mass, flight duration, etc. @@ -234,9 +242,25 @@ def initial_guessing(aircraft_values: AviaryValues()): tuple: Updated aircraft values and initial guesses. """ problem_type = aircraft_values.get_val('problem_type') - reserves = aircraft_values.get_val( - Aircraft.Design.RESERVES) if initial_guesses['reserves'] == 0 else initial_guesses['reserves'] num_pax = aircraft_values.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) + reserve_val = aircraft_values.get_val( + Aircraft.Design.RESERVE_FUEL_ADDITIONAL, units='lbm') + reserve_frac = aircraft_values.get_val( + Aircraft.Design.RESERVE_FUEL_FRACTION, units='unitless') + + reserves = initial_guesses['reserves'] + if reserves < 0.0: + raise ValueError( + 'initial_guesses["reserves"] must be greater than or equal to 0.') + elif reserves == 0: + reserves += reserve_val + reserves += (reserve_frac * (num_pax * initial_guesses['fuel_burn_per_passenger_mile'] * + aircraft_values.get_val(Mission.Design.RANGE, units='NM'))) + elif reserves < 10: + reserves *= (num_pax * initial_guesses['fuel_burn_per_passenger_mile'] * + aircraft_values.get_val(Mission.Design.RANGE, units='NM')) + + initial_guesses['reserves'] = reserves if Mission.Summary.GROSS_MASS in aircraft_values: mission_mass = aircraft_values.get_val(Mission.Summary.GROSS_MASS, units='lbm') @@ -249,11 +273,6 @@ def initial_guessing(aircraft_values: AviaryValues()): else: cruise_mass_final = initial_guesses['cruise_mass_final'] - if reserves < 0: - reserves *= -(num_pax * - initial_guesses['fuel_burn_per_passenger_mile'] * aircraft_values.get_val(Mission.Design.RANGE, units='NM')) - initial_guesses['reserves'] = reserves - # takeoff mass not given if mission_mass <= 0: if problem_type == ProblemType.ALTERNATE: @@ -318,7 +337,12 @@ def initial_guessing(aircraft_values: AviaryValues()): initial_guesses['climb_range'] = initial_guesses['time_to_climb'] / \ (60 * 60) * (avg_speed_guess * np.cos(gamma_guess)) - return aircraft_values, initial_guesses + if aircraft_values.get_val('debug_mode'): + print('\nInitial Guesses') + for key, value in initial_guesses.items(): + print(key, value) + + return initial_guesses dependent_options = [ @@ -360,16 +384,3 @@ def initial_guessing(aircraft_values: AviaryValues()): [Aircraft.VerticalTail.VOLUME_COEFFICIENT, { 'val': 0, 'relation': '==', 'target': Aircraft.Design.COMPUTE_VTAIL_VOLUME_COEFF, 'result': True, 'alternate': False}], ] - -initial_guesses = { - # initial_guesses is a dictionary that contains values used to initialize the trajectory - 'actual_takeoff_mass': 0, - 'rotation_mass': .99, - 'operating_empty_mass': 0, - 'fuel_burn_per_passenger_mile': 0.1, - 'cruise_mass_final': 0, - 'flight_duration': 0, - 'time_to_climb': 0, - 'climb_range': 0, - 'reserves': 0 -} diff --git a/aviary/utils/report.py b/aviary/utils/report.py index df0e2b08e..2e4c07b74 100644 --- a/aviary/utils/report.py +++ b/aviary/utils/report.py @@ -108,7 +108,7 @@ def report_gasp_comparison(prob, rtol=0.1, remote=False, file=None): "flight distance (NM)", 3675.0, prob.get_val( - "traj.desc2.timeseries.states:distance", units="NM", get_remote=remote + "traj.desc2.timeseries.distance", units="NM", get_remote=remote )[-1][0], ), # ROC @ 49 seconds to get from 456.3 ft to 500 ft minus liftoff time @@ -234,10 +234,10 @@ def report_benchmark_comparison( # check_val = no_size_yes_pyCycle if base == 'FLOPS': check_val = FLOPS_base - distance_name = 'traj.descent.timeseries.states:range' + distance_name = 'traj.descent.timeseries.distance' landing_dist_name = Mission.Landing.GROUND_DISTANCE else: - distance_name = "traj.desc2.timeseries.states:distance" + distance_name = "traj.desc2.timeseries.distance" landing_dist_name = 'landing.'+Mission.Landing.GROUND_DISTANCE expected_vals = [ diff --git a/aviary/utils/test_utils/IO_test_util.py b/aviary/utils/test_utils/IO_test_util.py index b4aacde5b..0ec0c6d38 100644 --- a/aviary/utils/test_utils/IO_test_util.py +++ b/aviary/utils/test_utils/IO_test_util.py @@ -1,150 +1,6 @@ -import json -import os -from unittest import skipIf - import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal -XDSM_PATH = os.path.abspath(os.path.join(__file__, "../../..", "xdsm")) - - -def skipIfMissingXDSM(filename): - return skipIf(not os.path.isfile(os.path.join(XDSM_PATH, filename)), "`"+filename+"` does not exist") - - -def _load_spec(spec_path, path_prefix=XDSM_PATH): - with open(os.path.join(path_prefix, spec_path), "rb") as f: - spec_data = json.load(f) - - spec_data["inputs"] = set(spec_data["inputs"]) - spec_data["outputs"] = set(spec_data["outputs"]) - return spec_data - - -def assert_match_spec(system, spec_path, path_prefix=XDSM_PATH): - - spec_data = _load_spec(spec_path, path_prefix) - - prob = om.Problem() - prob.model = system - - prob.setup() - prob.final_setup() - - sys_inputs = prob.model.list_inputs(out_stream=None, prom_name=True) - sys_outputs = prob.model.list_outputs(out_stream=None, prom_name=True) - - input_set = set([inp[1]["prom_name"] for inp in sys_inputs]) - output_set = set([out[1]["prom_name"] for out in sys_outputs]) - - # this checks if there are any vars in the spec, that aren't in the model - missing_inputs = spec_data["inputs"] - input_set - if len(missing_inputs) > 0: - raise ValueError( - f"inputs {missing_inputs} are in {spec_path}, but are missing in the provided system." - ) - - # filter the inputs into connected and unconnected sets - connect_dict = system._conn_global_abs_in2out - unconnected_inputs = set() - connected_inputs = set() - for abs_name, in_data in sys_inputs: - if abs_name in connect_dict and (not "auto_ivc" in connect_dict[abs_name]): - connected_inputs.add(in_data["prom_name"]) - else: - unconnected_inputs.add(in_data["prom_name"]) - - # now we need to check if there are any unconnected inputs - # in the model that aren't in the spec - extra_inputs = unconnected_inputs - spec_data["inputs"] - if len(extra_inputs) > 0: - raise ValueError( - f"unconnected inputs {extra_inputs} are in your model, but are missing as inputs in the {spec_path} spec." - ) - - # last we need to check if any of the spec inputs have - # internal connections (e.g. from stray IVCs) - blocked_inputs = connected_inputs.intersection(spec_data["inputs"]) - if len(blocked_inputs) > 0: - raise ValueError( - f"inputs {blocked_inputs} are connected inside your model, but are listed as required" - f" inputs in the {spec_path} spec so they should be unconnected." - ) - - # check if any missing required outputs are missing from the model, based on the spec - missing_outputs = spec_data["outputs"] - output_set - if len(missing_outputs) > 0: - raise ValueError( - f"outputs {missing_outputs} are in {spec_path}, but are missing in the provided system." - ) - - -def assert_match_vals( - test_case, system, spec_path, path_prefix=XDSM_PATH, tolerance=1e-5, test_names=None -): - - spec_data = _load_spec(spec_path, path_prefix) - - if test_names: - # User passed a collection of test names to check. - verify_data = {name: system.verify_data[name] for name in test_names} - else: - # if there is just one case, then modify the dict to make auto-name the single case - try: - # Setup the verification case model - v_data_inputs = system.verify_data["inputs"] - verify_data = {"v_test1": system.verify_data} - except KeyError: # there is already more than one, use the fist one to figure things out - verify_data = system.verify_data - - for v_case_name, v_case in verify_data.items(): - v_data_inputs = v_case["inputs"] - v_data_outputs = v_case["outputs"] - - for v_name in v_data_inputs.keys(): - if not v_name in spec_data["inputs"]: - raise ValueError( - f'input value for "{v_name}" was given in verification data, ' - f'but this variable is not given in "{spec_path}"' - ) - - prob = om.Problem() - ivc = prob.model.add_subsystem("spec_inputs", om.IndepVarComp(), promotes=["*"]) - for v_name, v_val in v_data_inputs.items(): - if isinstance(v_val, (list, tuple)): - v_unit = v_val[1] - v_val = v_val[0] - else: - v_unit = None - - ivc.add_output(v_name, val=v_val, units=v_unit) - # note, can't set units, but don't want to hack it in. - # Unit data should be available from the system itself via the default-auto-ivc stuff - - prob.model.add_subsystem("sys", system, promotes=["*"]) - - prob.setup() - prob.final_setup() - - prob.run_model() - - for v_name, v_val in v_data_outputs.items(): - if isinstance(v_val, (list, tuple)): - v_unit = v_val[1] - v_val = v_val[0] - else: - v_unit = None - - try: - computed_val = prob.get_val(v_name, units=v_unit) - except KeyError: # hacky way to work around needing inputs/vs outputs - computed_val = prob.get_val(f"sys.{v_name}", units=v_unit) - - try: - assert_near_equal(computed_val, v_val, tolerance=tolerance) - except AssertionError as err: - raise ValueError(f'in case "{v_case_name}", for {v_name}: ' + str(err)) - def check_prob_outputs(prob, vals, rtol=1e-6): """Check multiple problem outputs and print all failures. diff --git a/aviary/validation_cases/benchmark_tests/test_0_iters.py b/aviary/validation_cases/benchmark_tests/test_0_iters.py index eaf0fcf0c..3686ca5c8 100644 --- a/aviary/validation_cases/benchmark_tests/test_0_iters.py +++ b/aviary/validation_cases/benchmark_tests/test_0_iters.py @@ -7,7 +7,7 @@ from aviary.interface.default_phase_info.two_dof import phase_info as two_dof_phase_info from aviary.interface.default_phase_info.height_energy import phase_info as height_energy_phase_info from aviary.interface.default_phase_info.solved import phase_info as solved_phase_info -from aviary.variable_info.enums import EquationsOfMotion +from aviary.models.N3CC.N3CC_data import inputs class BaseProblemPhaseTestCase(unittest.TestCase): @@ -18,7 +18,7 @@ def build_and_run_problem(self, input_filename, phase_info, objective_type=None) prob.load_inputs(input_filename, phase_info) - prob.check_inputs() + prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() prob.add_phases() prob.add_post_mission_systems() @@ -35,7 +35,7 @@ def build_and_run_problem(self, input_filename, phase_info, objective_type=None) class TwoDOFZeroItersTestCase(BaseProblemPhaseTestCase): @require_pyoptsparse(optimizer="IPOPT") - def test_gasp_zero_iters(self): + def test_zero_iters_2DOF(self): local_phase_info = deepcopy(two_dof_phase_info) self.build_and_run_problem('models/test_aircraft/aircraft_for_bench_GwGm.csv', local_phase_info) @@ -45,14 +45,18 @@ def test_gasp_zero_iters(self): class HEZeroItersTestCase(BaseProblemPhaseTestCase): @require_pyoptsparse(optimizer="IPOPT") - def test_height_energy_zero_iters(self): + def test_zero_iters_height_energy(self): local_phase_info = deepcopy(height_energy_phase_info) - self.build_and_run_problem('models/test_aircraft/aircraft_for_bench_FwFm.csv', - local_phase_info) + local_inputs = deepcopy(inputs) + local_phase_info['pre_mission']['include_takeoff'] = True + local_phase_info['post_mission']['include_landing'] = True + local_phase_info['climb']['user_options']['fix_initial'] = False + local_phase_info['climb']['user_options']['input_initial'] = True + self.build_and_run_problem(local_inputs, local_phase_info) @use_tempdirs -class SolvedProblemTestCase(BaseProblemPhaseTestCase): +class SolvedZeroItersTestCase(BaseProblemPhaseTestCase): @require_pyoptsparse(optimizer="IPOPT") def test_zero_iters_solved(self): @@ -64,5 +68,5 @@ def test_zero_iters_solved(self): if __name__ == "__main__": # unittest.main() - test = SolvedProblemTestCase() - test.test_zero_iters_solved() + test = HEZeroItersTestCase() + test.test_zero_iters_height_energy() diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_balanced_field_length.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_balanced_field_length.py index aff3bb72c..f30f6d812 100644 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_balanced_field_length.py +++ b/aviary/validation_cases/benchmark_tests/test_FLOPS_balanced_field_length.py @@ -18,13 +18,11 @@ balanced_trajectory_builder as _takeoff_trajectory_builder from aviary.models.N3CC.N3CC_data import \ inputs as _inputs -from aviary.variable_info.variables import Dynamic as _Dynamic +from aviary.variable_info.variables import Dynamic from aviary.variable_info.variables_in import VariablesIn from aviary.interface.default_phase_info.height_energy import default_premission_subsystems from aviary.subsystems.premission import CorePreMission -Dynamic = _Dynamic.Mission - @use_tempdirs class TestFLOPSBalancedFieldLength(unittest.TestCase): @@ -91,11 +89,11 @@ def _do_run(self, driver: Driver, optimizer, *args): takeoff_trajectory_builder.build_trajectory( aviary_options=aviary_options, model=takeoff.model, traj=traj) - max_range, units = takeoff_liftoff_user_options.get_item('max_range') + distance_max, units = takeoff_liftoff_user_options.get_item('distance_max') liftoff = takeoff_trajectory_builder.get_phase('balanced_liftoff') liftoff.add_objective( - Dynamic.RANGE, loc='final', ref=max_range, units=units) + Dynamic.Mission.DISTANCE, loc='final', ref=distance_max, units=units) takeoff.model.add_subsystem( 'input_sink', @@ -128,7 +126,7 @@ def _do_run(self, driver: Driver, optimizer, *args): # N3CC FLOPS output Line 2282 desired = 7032.65 actual = takeoff.model.get_val( - 'traj.balanced_liftoff.states:range', units='ft')[-1] + 'traj.balanced_liftoff.states:distance', units='ft')[-1] assert_near_equal(actual, desired, 2e-2) # Decision Time diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_N3CC.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_N3CC.py deleted file mode 100644 index 7041b7cb8..000000000 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_N3CC.py +++ /dev/null @@ -1,713 +0,0 @@ -''' -NOTES: -Includes: -Takeoff, Climb, Cruise, Descent, Landing -Computed Aero -N3CC data -''' -import unittest -import warnings - -import dymos as dm -import numpy as np -import openmdao.api as om -import scipy.constants as _units -from openmdao.core.driver import Driver -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs -from packaging import version - -from aviary.mission.flops_based.ode.mission_ODE import MissionODE -from aviary.mission.flops_based.phases.build_landing import Landing -from aviary.mission.flops_based.phases.build_takeoff import Takeoff -from aviary.mission.flops_based.phases.climb_phase import Climb -from aviary.mission.flops_based.phases.cruise_phase import Cruise -from aviary.mission.flops_based.phases.descent_phase import Descent -from aviary.utils.functions import set_aviary_initial_values -from aviary.utils.test_utils.assert_utils import warn_timeseries_near_equal -from aviary.utils.functions import get_path -from aviary.validation_cases.validation_tests import get_flops_inputs -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Aircraft, Dynamic, Mission -from aviary.variable_info.variables_in import VariablesIn -from aviary.subsystems.premission import CorePreMission -from aviary.interface.default_phase_info.height_energy import default_premission_subsystems, default_mission_subsystems -from aviary.utils.preprocessors import preprocess_crewpayload -from aviary.utils.aviary_values import AviaryValues - - -# benchmark based on N3CC (fixed cruise alt) FLOPS model - - -def run_trajectory(driver: Driver, sim=True): - prob = om.Problem(model=om.Group()) - prob.driver = driver - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('N3CC') - - aviary_inputs.set_val( - Mission.Landing.LIFT_COEFFICIENT_MAX, 2.4, units='unitless') - aviary_inputs.set_val( - Mission.Takeoff.LIFT_COEFFICIENT_MAX, 2.0, units='unitless') - aviary_inputs.set_val( - Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, val=.0175, - units='unitless') - - takeoff_fuel_burned = 577 # lbm TODO: where should this get connected from? - takeoff_thrust_per_eng = 24555.5 # lbf TODO: where should this get connected from? - takeoff_L_over_D = 17.35 # TODO: should this come from aero? - - aviary_inputs.set_val( - Mission.Takeoff.FUEL_SIMPLE, takeoff_fuel_burned, units='lbm') - aviary_inputs.set_val( - Mission.Takeoff.LIFT_OVER_DRAG, takeoff_L_over_D, units='unitless') - aviary_inputs.set_val( - Mission.Design.THRUST_TAKEOFF_PER_ENG, takeoff_thrust_per_eng, units='lbf') - - alt_airport = 0 # ft - cruise_mach = .785 - - alt_i_climb = 0*_units.foot # m - alt_f_climb = 35000.0*_units.foot # m - mass_i_climb = 131000*_units.lb # kg - mass_f_climb = 126000*_units.lb # kg - v_i_climb = 198.44*_units.knot # m/s - v_f_climb = 455.49*_units.knot # m/s - # initial mach set to lower value so it can intersect with takeoff end mach - # mach_i_climb = 0.3 - mach_i_climb = 0.2 - mach_f_climb = cruise_mach - range_i_climb = 0*_units.nautical_mile # m - range_f_climb = 160.3*_units.nautical_mile # m - t_i_climb = 2 * _units.minute # sec - t_f_climb = 26.20*_units.minute # sec - t_duration_climb = t_f_climb - t_i_climb - - alt_i_cruise = 35000*_units.foot # m - alt_f_cruise = 35000*_units.foot # m - alt_min_cruise = 35000*_units.foot # m - alt_max_cruise = 35000*_units.foot # m - mass_i_cruise = 126000*_units.lb # kg - mass_f_cruise = 102000*_units.lb # kg - v_i_cruise = 455.49*_units.knot # m/s - v_f_cruise = 455.49*_units.knot # m/s - mach_min_cruise = cruise_mach - mach_max_cruise = cruise_mach - range_i_cruise = 160.3*_units.nautical_mile # m - range_f_cruise = 3243.9*_units.nautical_mile # m - t_i_cruise = 26.20*_units.minute # sec - t_f_cruise = 432.38*_units.minute # sec - t_duration_cruise = t_f_cruise - t_i_cruise - - alt_i_descent = 35000*_units.foot - # final altitude set to 35 to ensure landing is feasible point - # alt_f_descent = 0*_units.foot - alt_f_descent = 35*_units.foot - v_i_descent = 455.49*_units.knot - v_f_descent = 198.44*_units.knot - mach_i_descent = cruise_mach - mach_f_descent = 0.3 - mass_i_descent = 102000*_units.pound - mass_f_descent = 101000*_units.pound - range_i_descent = 3243.9*_units.nautical_mile - range_f_descent = 3378.7*_units.nautical_mile - t_i_descent = 432.38*_units.minute - t_f_descent = 461.62*_units.minute - t_duration_descent = t_f_descent - t_i_descent - - ################## - # Define Phases # - ################## - - num_segments_climb = 6 - num_segments_cruise = 1 - num_segments_descent = 5 - - climb_seg_ends, _ = dm.utils.lgl.lgl(num_segments_climb + 1) - descent_seg_ends, _ = dm.utils.lgl.lgl(num_segments_descent + 1) - - transcription_climb = dm.Radau( - num_segments=num_segments_climb, order=3, compressed=True, - segment_ends=climb_seg_ends) - transcription_cruise = dm.Radau( - num_segments=num_segments_cruise, order=3, compressed=True) - transcription_descent = dm.Radau( - num_segments=num_segments_descent, order=3, compressed=True, - segment_ends=descent_seg_ends) - - takeoff_options = Takeoff( - airport_altitude=alt_airport, # ft - num_engines=aviary_inputs.get_val(Aircraft.Engine.NUM_ENGINES) - ) - - climb_options = Climb( - 'test_climb', - user_options=AviaryValues({ - 'initial_altitude': (alt_i_climb, 'm'), - 'final_altitude': (alt_f_climb, 'm'), - 'initial_mach': (mach_i_climb, 'unitless'), - 'final_mach': (mach_f_climb, 'unitless'), - 'fix_initial': (False, 'unitless'), - 'fix_range': (False, 'unitless'), - 'input_initial': (True, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_climb, - ) - - cruise_options = Cruise( - 'test_cruise', - user_options=AviaryValues({ - 'min_altitude': (alt_min_cruise, 'm'), - 'max_altitude': (alt_max_cruise, 'm'), - 'min_mach': (mach_min_cruise, 'unitless'), - 'max_mach': (mach_max_cruise, 'unitless'), - 'required_available_climb_rate': (300, 'ft/min'), - 'fix_initial': (False, 'unitless'), - 'fix_final': (False, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_cruise, - ) - - descent_options = Descent( - 'test_descent', - user_options=AviaryValues({ - 'final_altitude': (alt_f_descent, 'm'), - 'initial_altitude': (alt_i_descent, 'm'), - 'initial_mach': (mach_i_descent, 'unitless'), - 'final_mach': (mach_f_descent, 'unitless'), - 'fix_initial': (False, 'unitless'), - 'fix_range': (True, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_descent, - ) - - landing_options = Landing( - ref_wing_area=aviary_inputs.get_val(Aircraft.Wing.AREA, 'ft**2'), - Cl_max_ldg=aviary_inputs.get_val(Mission.Landing.LIFT_COEFFICIENT_MAX) - ) - - preprocess_crewpayload(aviary_inputs) - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'pre_mission', - CorePreMission(aviary_options=aviary_inputs, - subsystems=default_premission_subsystems), - promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - # directly connect phases (strong_couple = True), or use linkage constraints (weak - # coupling / strong_couple=False) - strong_couple = False - - takeoff = takeoff_options.build_phase(False) - - climb = climb_options.build_phase(aviary_options=aviary_inputs) - - cruise = cruise_options.build_phase(aviary_options=aviary_inputs) - - descent = descent_options.build_phase(aviary_options=aviary_inputs) - - landing = landing_options.build_phase(False) - - prob.model.add_subsystem( - 'takeoff', takeoff, promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['mission:*']) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - # if fix_initial is false, can we always set input_initial to be true for - # necessary states, and then ignore if we use a linkage? - climb.set_time_options( - fix_initial=True, fix_duration=False, units='s', - duration_bounds=(1, t_duration_climb*2), duration_ref=t_duration_climb) - cruise.set_time_options( - fix_initial=False, fix_duration=False, units='s', - duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise) - descent.set_time_options( - fix_initial=False, fix_duration=False, units='s', - duration_bounds=(1, t_duration_descent*2), duration_ref=t_duration_descent) - - traj.add_phase('climb', climb) - - traj.add_phase('cruise', cruise) - - traj.add_phase('descent', descent) - - climb.timeseries_options['use_prefix'] = True - cruise.timeseries_options['use_prefix'] = True - descent.timeseries_options['use_prefix'] = True - - prob.model.add_subsystem( - 'landing', landing, promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['mission:*']) - - traj.link_phases(["climb", "cruise"], ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple) - traj.link_phases(["cruise", "descent"], ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs) - - ################################## - # Connect in Takeoff and Landing # - ################################## - - prob.model.add_subsystem( - "takeoff_constraints", - om.ExecComp( - [ - "takeoff_mass_con=takeoff_mass-climb_start_mass", - "takeoff_range_con=takeoff_range-climb_start_range", - "takeoff_vel_con=takeoff_vel-climb_start_vel", - "takeoff_alt_con=takeoff_alt-climb_start_alt" - ], - takeoff_mass_con={'units': 'lbm'}, takeoff_mass={'units': 'lbm'}, - climb_start_mass={'units': 'lbm'}, - takeoff_range_con={'units': 'ft'}, takeoff_range={'units': 'ft'}, - climb_start_range={'units': 'ft'}, - takeoff_vel_con={'units': 'm/s'}, takeoff_vel={'units': 'm/s'}, - climb_start_vel={'units': 'm/s'}, - takeoff_alt_con={'units': 'ft'}, takeoff_alt={'units': 'ft'}, - climb_start_alt={'units': 'ft'} - ), - promotes_inputs=[ - ("takeoff_mass", Mission.Takeoff.FINAL_MASS), - ("takeoff_range", Mission.Takeoff.GROUND_DISTANCE), - ("takeoff_vel", Mission.Takeoff.FINAL_VELOCITY), - ("takeoff_alt", Mission.Takeoff.FINAL_ALTITUDE), - ], - promotes_outputs=["takeoff_mass_con", "takeoff_range_con", - "takeoff_vel_con", "takeoff_alt_con"], - ) - - prob.model.connect('traj.climb.states:mass', - 'takeoff_constraints.climb_start_mass', src_indices=[0]) - prob.model.connect('traj.climb.states:range', - 'takeoff_constraints.climb_start_range', src_indices=[0]) - prob.model.connect('traj.climb.states:velocity', - 'takeoff_constraints.climb_start_vel', src_indices=[0]) - prob.model.connect('traj.climb.states:altitude', - 'takeoff_constraints.climb_start_alt', src_indices=[0]) - - prob.model.connect(Mission.Takeoff.FINAL_MASS, - 'traj.climb.initial_states:mass') - prob.model.connect(Mission.Takeoff.GROUND_DISTANCE, - 'traj.climb.initial_states:range') - prob.model.connect(Mission.Takeoff.FINAL_VELOCITY, - 'traj.climb.initial_states:velocity') - prob.model.connect(Mission.Takeoff.FINAL_ALTITUDE, - 'traj.climb.initial_states:altitude') - - prob.model.connect('traj.descent.states:mass', - Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1]) - prob.model.connect('traj.descent.states:altitude', Mission.Landing.INITIAL_ALTITUDE, - src_indices=[-1]) - ########################## - # Add Objective Function # - ########################## - - # This is an example of a overall mission objective - # create a compound objective that minimizes climb time and maximizes final mass - # we are maxing final mass b/c we don't have an independent value for fuel_mass yet - # we are going to normalize these (makign each of the sub-objectives approx = 1 ) - prob.model.add_subsystem( - "regularization", - om.ExecComp( - # TODO: change the scaling on climb_duration - "reg_objective = - descent_mass_final/60000", - reg_objective=0.0, - descent_mass_final={"units": "kg", "shape": 1}, - ), - promotes_outputs=['reg_objective'] - ) - # connect the final mass from cruise into the objective - prob.model.connect("traj.descent.states:mass", - "regularization.descent_mass_final", src_indices=[-1]) - - prob.model.add_objective('reg_objective', ref=1) - - prob.model.add_subsystem( - 'input_sink', - VariablesIn(aviary_options=aviary_inputs), - promotes_inputs=['*'], - promotes_outputs=['*'] - ) - - # suppress warnings: - # "input variable '...' promoted using '*' was already promoted using 'aircraft:*' - with warnings.catch_warnings(): - - # Set initial default values for all LEAPS aircraft variables. - warnings.simplefilter("ignore", om.PromotionWarning) - set_aviary_initial_values(prob.model, aviary_inputs) - - warnings.simplefilter("ignore", om.PromotionWarning) - prob.setup() - - ########################################### - # Intial Settings for States and Controls # - ########################################### - - prob.set_val('traj.climb.t_initial', t_i_climb, units='s') - prob.set_val('traj.climb.t_duration', t_duration_climb, units='s') - - prob.set_val('traj.climb.states:altitude', climb.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m') - # prob.set_val( - # 'traj.climb.states:velocity', climb.interp(Dynamic.Mission.VELOCITY, ys=[170, v_f_climb]), - # units='m/s') - prob.set_val('traj.climb.states:velocity', climb.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_climb, v_f_climb]), units='m/s') - prob.set_val('traj.climb.states:mass', climb.interp( - Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg') - prob.set_val('traj.climb.states:range', climb.interp( - Dynamic.Mission.RANGE, ys=[range_i_climb, range_f_climb]), units='m') # nmi - - prob.set_val('traj.climb.controls:velocity_rate', - climb.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.25, 0.0]), - units='m/s**2') - prob.set_val('traj.climb.controls:throttle', - climb.interp(Dynamic.Mission.THROTTLE, ys=[1.0, 1.0]), - units='unitless') - - prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s') - prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s') - - prob.set_val('traj.cruise.states:altitude', cruise.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m') - prob.set_val('traj.cruise.states:velocity', cruise.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_cruise, v_f_cruise]), units='m/s') - prob.set_val('traj.cruise.states:mass', cruise.interp( - Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg') - prob.set_val('traj.cruise.states:range', cruise.interp( - Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi - - prob.set_val('traj.cruise.controls:velocity_rate', - cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.cruise.controls:throttle', - cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.8, 0.75]), - units='unitless') - - prob.set_val('traj.descent.t_initial', t_i_descent, units='s') - prob.set_val('traj.descent.t_duration', t_duration_descent, units='s') - - prob.set_val('traj.descent.states:altitude', descent.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m') - prob.set_val('traj.descent.states:velocity', descent.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_descent, v_f_descent]), units='m/s') - prob.set_val('traj.descent.states:mass', descent.interp( - Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg') - prob.set_val('traj.descent.states:range', descent.interp( - Dynamic.Mission.RANGE, ys=[range_i_descent, range_f_descent]), units='m') - - prob.set_val('traj.descent.controls:velocity_rate', - descent.interp(Dynamic.Mission.VELOCITY_RATE, ys=[-0.25, 0.0]), - units='m/s**2') - prob.set_val('traj.descent.controls:throttle', - descent.interp(Dynamic.Mission.THROTTLE, ys=[0.0, 0.0]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=sim, make_plots=False, simulate_kwargs={ - 'times_per_seg': 100, 'atol': 1e-9, 'rtol': 1e-9}, - solution_record_file='N3CC_full_mission.db') - prob.record("final") - prob.cleanup() - - return prob - - -@use_tempdirs -class ProblemPhaseTestCase(unittest.TestCase): - @require_pyoptsparse(optimizer='SNOPT') - def bench_test_full_mission_N3CC_SNOPT(self): - driver = _make_driver_SNOPT() - - self._do_run(driver) - - def _do_run(self, driver): - prob = run_trajectory(driver=driver, sim=False) - - times_climb = prob.get_val('traj.climb.timeseries.time', units='s') - altitudes_climb = prob.get_val( - 'traj.climb.timeseries.states:altitude', units='m') - masses_climb = prob.get_val('traj.climb.timeseries.states:mass', units='kg') - ranges_climb = prob.get_val('traj.climb.timeseries.states:range', units='m') - velocities_climb = prob.get_val( - 'traj.climb.timeseries.states:velocity', units='m/s') - thrusts_climb = prob.get_val('traj.climb.timeseries.thrust_net_total', units='N') - times_cruise = prob.get_val('traj.cruise.timeseries.time', units='s') - altitudes_cruise = prob.get_val( - 'traj.cruise.timeseries.states:altitude', units='m') - masses_cruise = prob.get_val('traj.cruise.timeseries.states:mass', units='kg') - ranges_cruise = prob.get_val('traj.cruise.timeseries.states:range', units='m') - velocities_cruise = prob.get_val( - 'traj.cruise.timeseries.states:velocity', units='m/s') - thrusts_cruise = prob.get_val( - 'traj.cruise.timeseries.thrust_net_total', units='N') - times_descent = prob.get_val('traj.descent.timeseries.time', units='s') - altitudes_descent = prob.get_val( - 'traj.descent.timeseries.states:altitude', units='m') - masses_descent = prob.get_val('traj.descent.timeseries.states:mass', units='kg') - ranges_descent = prob.get_val('traj.descent.timeseries.states:range', units='m') - velocities_descent = prob.get_val( - 'traj.descent.timeseries.states:velocity', units='m/s') - thrusts_descent = prob.get_val( - 'traj.descent.timeseries.thrust_net_total', units='N') - - expected_times_s_climb = [[120.], [163.76276502], [224.14644864], [243.25767805], - [243.25767805], [336.40844168], [464.93748315], - [505.61648533], [505.61648533], [626.47047555], - [793.2243154], [846.00133557], [846.00133557], - [966.85532579], [1133.60916564], [1186.38618581], - [1186.38618581], [1279.53694944], [1408.06599092], - [1448.74499309], [1448.74499309], [1492.50775811], - [1552.89144173], [1572.00267114]] - expected_altitudes_m_climb = [[10.668], [0.], [490.31464488], [720.34099124], - [720.34099124], [2288.74497628], [4816.40500694], - [5464.87951761], [5464.87951761], [7025.37127556], - [8437.78568802], [8802.40370192], [8802.40370192], - [9511.72639581], [10229.15884616], [10382.97088649], - [10382.97088649], [10560.94435598], [10660.37367157], - [10668.], [10668.], [10668.], [10666.66785248], [10668.]] - expected_masses_kg_climb = [[58584.62973209], [58553.30079295], [58486.19463517], - [58461.90146352], [58461.90146352], [58339.61987692], - [58184.14978151], [58144.99942835], [58144.99942835], - [58041.54301581], [57927.14314383], [57894.10156256], - [57894.10156256], [57825.05181264], [57740.72348501], - [57716.38705856], [57716.38705856], [57675.62128122], - [57622.73581734], [57606.53973509], [57606.53973509], - [57589.20468199], [57563.40774787], [57554.3064769]] - expected_ranges_m_climb = [[1469.37868509], [5605.65587129], [13572.59161311], - [16576.93576947], [16576.93576947], [33254.75438651], - [59051.61685726], [67185.62511739], [67185.62511739], - [91756.36399005], [126369.47577928], [137576.80855552], - [137576.80855552], [163427.52300246], [199432.46291748], - [210871.48231952], [210871.48231952], [231238.09772766], - [259750.51731468], [268890.90216347], [268890.90216347], - [278771.58755554], [292561.32064207], [296983.91583583]] - expected_velocities_ms_climb = [[77.35938331], [111.19901718], [151.93398907], - [162.39904508], [162.39904508], [193.79386032], - [202.92916455], [202.83097007], [202.83097007], - [204.9548312], [211.13800405], [212.81677487], - [212.81677487], [215.00414951], [216.65184841], - [217.5075073], [217.5075073], [219.8725371], - [223.95818114], [225.18659652], [225.18659652], - [226.57877443], [230.5555138], [232.77530606]] - expected_thrusts_N_climb = [[50269.53763097], [89221.79391729], [105944.78221328], - [104742.61384096], [104742.61384096], [102539.17538465], - [80894.5840539], [74287.02308141], [74287.02308141], - [59565.00613161], [48715.65056776], [46229.53984695], - [46229.53984695], [41551.41074213], [35939.62791236], - [34345.57880903], [34345.57880903], [32008.34687504], - [29668.0461344], [29061.17556918], [29061.17556918], - [29571.25377648], [34034.12209303], [36366.13242869]] - - expected_times_s_cruise = [[1572.00267114], [10224.88157184], - [22164.08837724], [25942.80651013]] - expected_altitudes_m_cruise = [[10668.], [10668.], [10668.], [10668.]] - expected_masses_kg_cruise = [[57554.3064769], [54168.16865196], - [49649.4641858], [48253.00754766]] - expected_ranges_m_cruise = [[296983.91583583], [2311160.45023764], - [5090312.96846673], [5969905.23836297]] - expected_velocities_ms_cruise = [[232.77530606], [232.77530606], - [232.77530606], [232.77530606]] - expected_thrusts_N_cruise = [[28599.34059358], [27655.863514], - [26491.25212162], [26155.81047559]] - - expected_times_s_descent = [[25942.80651013], [26025.25799534], [26139.02421633], - [26175.0308363], [26175.0308363], [26343.42020448], - [26575.76316382], [26649.29891605], [26649.29891605], - [26849.49721425], [27125.73000473], [27213.15673846], - [27213.15673846], [27381.54610664], [27613.88906597], - [27687.4248182], [27687.4248182], [27769.8763034], - [27883.6425244], [27919.64914437]] - expected_altitudes_m_descent = [[10668.], [10668.], [10142.61136584], - [9909.20496834], [9909.20496834], [8801.80844468], - [7273.17347505], [6802.45633892], [6802.45633892], - [5619.8230507], [4125.52995372], [3659.42225117], - [3659.42225117], [2735.99340162], [1425.57947265], - [1009.41898888], [1009.41898888], [561.99822911], - [94.31876192], [10.668]] - expected_masses_kg_descent = [[48253.00754766], [48247.14199102], [48243.08209003], - [48242.16864984], [48242.16864984], [48239.38631749], - [48238.09565644], [48238.10218909], [48238.10218909], - [48238.36804678], [48235.32226181], [48232.28685278], - [48232.28685278], [48219.23890964], [48194.77880065], - [48187.10389371], [48187.10389371], [48177.71262482], - [48164.36107545], [48160.10979474]] - expected_ranges_m_descent = [[5969905.23836297], [5987568.57791473], [6008764.81422574], - [6015233.86544087], [6015233.86544087], [ - 6044198.03874828], - [6081891.04303138], [6093386.547795], [6093386.547795], - [6123061.7076624], [6160342.11821306], [ - 6171307.0346796], - [6171307.0346796], [6192171.51704849], [ - 6220846.02761427], - [6230014.04117231], [6230014.04117231], [ - 6240198.28047116], - [6253503.06775949], [6257352.4]] - expected_velocities_ms_descent = [[232.77530606], [198.63654674], [179.66599067], - [176.95296882], [176.95296882], [167.50327408], - [157.55154249], [154.07756214], [154.07756214], - [142.57898741], [127.86775289], [125.11330664], - [125.11330664], [123.46196696], [124.28423824], - [124.03136002], [124.03136002], [122.19127487], - [109.94487807], [102.07377559]] - expected_thrusts_N_descent = [[4470.16874641], [2769.62977465], [1516.11359849], - [1315.10754789], [1315.10754789], [652.82279778], - [47.57054754], [-2.66626679e-13], [0.], - [4.84263343e-14], [1.42410958e-14], [-2.42743721e-14], - [0.], [0.], [1.77638636e-13], [2.84958986e-13], - [-1.39722436e-13], [0.], [0.], [-5.27816527e-14]] - - expected_times_s_climb = np.array(expected_times_s_climb) - expected_altitudes_m_climb = np.array(expected_altitudes_m_climb) - expected_masses_kg_climb = np.array(expected_masses_kg_climb) - expected_ranges_m_climb = np.array(expected_ranges_m_climb) - expected_velocities_ms_climb = np.array(expected_velocities_ms_climb) - expected_thrusts_N_climb = np.array(expected_thrusts_N_climb) - - expected_times_s_cruise = np.array(expected_times_s_cruise) - expected_altitudes_m_cruise = np.array(expected_altitudes_m_cruise) - expected_masses_kg_cruise = np.array(expected_masses_kg_cruise) - expected_ranges_m_cruise = np.array(expected_ranges_m_cruise) - expected_velocities_ms_cruise = np.array(expected_velocities_ms_cruise) - expected_thrusts_N_cruise = np.array(expected_thrusts_N_cruise) - - expected_times_s_descent = np.array(expected_times_s_descent) - expected_altitudes_m_descent = np.array(expected_altitudes_m_descent) - expected_masses_kg_descent = np.array(expected_masses_kg_descent) - expected_ranges_m_descent = np.array(expected_ranges_m_descent) - expected_velocities_ms_descent = np.array(expected_velocities_ms_descent) - expected_thrusts_N_descent = np.array(expected_thrusts_N_descent) - - # Check Objective and other key variables to a reasonably tight tolerance. - - rtol = 1e-2 - - # Mass at the end of Descent - assert_near_equal(masses_descent[-1], - expected_masses_kg_descent[-1], tolerance=rtol) - - # Range at the end of Descent - assert_near_equal(ranges_descent[-1], - expected_ranges_m_descent[-1], tolerance=rtol) - - # Flight time - assert_near_equal(times_descent[-1], - expected_times_s_descent[-1], tolerance=rtol) - - # Check mission values. - - # NOTE rtol = 0.05 = 5% different from truth (first timeseries) - # atol = 2 = no more than +/-2 meter/second/kg difference between values - # atol_altitude - 30 ft. There is occasional time-shifting with the N3CC - # model during climb and descent so we need a looser - # absolute tolerance for the points near the ground. - rtol = .05 - atol = 2.0 - atol_altitude = 30.0 - - # FLIGHT PATH - # CLIMB - warn_timeseries_near_equal( - times_climb, altitudes_climb, expected_times_s_climb, - expected_altitudes_m_climb, abs_tolerance=atol_altitude, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, masses_climb, expected_times_s_climb, - expected_masses_kg_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, ranges_climb, expected_times_s_climb, - expected_ranges_m_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, velocities_climb, expected_times_s_climb, - expected_velocities_ms_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, thrusts_climb, expected_times_s_climb, - expected_thrusts_N_climb, abs_tolerance=atol, rel_tolerance=rtol) - - # CRUISE - warn_timeseries_near_equal( - times_cruise, altitudes_cruise, expected_times_s_cruise, - expected_altitudes_m_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, masses_cruise, expected_times_s_cruise, - expected_masses_kg_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, ranges_cruise, expected_times_s_cruise, - expected_ranges_m_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, velocities_cruise, expected_times_s_cruise, - expected_velocities_ms_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, thrusts_cruise, expected_times_s_cruise, - expected_thrusts_N_cruise, abs_tolerance=atol, rel_tolerance=rtol) - - # DESCENT - warn_timeseries_near_equal( - times_descent, altitudes_descent, expected_times_s_descent, - expected_altitudes_m_descent, abs_tolerance=atol_altitude, - rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, masses_descent, expected_times_s_descent, - expected_masses_kg_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, ranges_descent, expected_times_s_descent, - expected_ranges_m_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, velocities_descent, expected_times_s_descent, - expected_velocities_ms_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, thrusts_descent, expected_times_s_descent, - expected_thrusts_N_descent, abs_tolerance=atol, rel_tolerance=rtol) - - -def _make_driver_IPOPT() -> Driver: - driver = om.pyOptSparseDriver(optimizer='IPOPT') - - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-3 - driver.opt_settings['print_level'] = 4 - - return driver - - -def _make_driver_SNOPT() -> Driver: - driver = om.pyOptSparseDriver(optimizer='SNOPT') - - driver.opt_settings["Major iterations limit"] = 45 - driver.opt_settings["Major optimality tolerance"] = 1e-4 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - - return driver - - -def _make_driver_SLSQP() -> Driver: - driver = om.ScipyOptimizeDriver(optimizer='SLSQP') - - driver.opt_settings['maxiter'] = 100 - driver.opt_settings['ftol'] = 5.0e-3 - driver.opt_settings['eps'] = 1e-2 - - return driver - - -if __name__ == '__main__': - temp = ProblemPhaseTestCase() - temp.bench_test_full_mission_N3CC_SNOPT() diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_large_single_aisle_1.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_large_single_aisle_1.py deleted file mode 100644 index 55bfd2fbd..000000000 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_large_single_aisle_1.py +++ /dev/null @@ -1,670 +0,0 @@ -''' -NOTES: -Includes: -Takeoff, Climb, Cruise, Descent, Landing -Computed Aero -Large Single Aisle 1 data -''' -import unittest - -import dymos as dm -import numpy as np -import openmdao.api as om -import scipy.constants as _units -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import use_tempdirs -from packaging import version - -from aviary.mission.flops_based.ode.mission_ODE import MissionODE -from aviary.mission.flops_based.phases.build_landing import Landing -from aviary.mission.flops_based.phases.build_takeoff import Takeoff -from aviary.mission.flops_based.phases.climb_phase import Climb -from aviary.mission.flops_based.phases.cruise_phase import Cruise -from aviary.mission.flops_based.phases.descent_phase import Descent -from aviary.subsystems.premission import CorePreMission -from aviary.utils.aviary_values import AviaryValues -from aviary.utils.functions import set_aviary_initial_values -from aviary.utils.test_utils.assert_utils import warn_timeseries_near_equal -from aviary.utils.functions import get_path -from aviary.validation_cases.validation_tests import get_flops_inputs, get_flops_outputs -from aviary.variable_info.functions import setup_trajectory_params -from aviary.utils.preprocessors import preprocess_options -from aviary.variable_info.variables import Aircraft, Dynamic, Mission -from aviary.variable_info.variables_in import VariablesIn -from aviary.interface.default_phase_info.height_energy import default_premission_subsystems, default_mission_subsystems - -try: - import pyoptsparse -except ImportError: - pyoptsparse = None - -# benchmark based on large single aisle 1 (fixed cruise alt) FLOPS model - - -def run_trajectory(sim=True): - prob = om.Problem() - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - # driver.declare_coloring() # currently disabled pending resolve of issue 2507 - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 45 - driver.opt_settings["Major optimality tolerance"] = 1e-4 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-3 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle1FLOPS') - aviary_outputs = get_flops_outputs('LargeSingleAisle1FLOPS') - - preprocess_options(aviary_inputs) - - alt_airport = 0 # ft - - alt_i_climb = 0*_units.foot # m - alt_f_climb = 35000.0*_units.foot # m - mass_i_climb = 180623*_units.lb # kg - mass_f_climb = 176765*_units.lb # kg - v_i_climb = 198.44*_units.knot # m/s - v_f_climb = 455.49*_units.knot # m/s - # initial mach set to lower value so it can intersect with takeoff end mach - # mach_i_climb = 0.3 - mach_i_climb = 0.2 - mach_f_climb = 0.79 - range_i_climb = 0*_units.nautical_mile # m - range_f_climb = 160.3*_units.nautical_mile # m - t_i_climb = 2 * _units.minute # sec - t_f_climb = 26.20*_units.minute # sec - t_duration_climb = t_f_climb - t_i_climb - - alt_i_cruise = 35000*_units.foot # m - alt_f_cruise = 35000*_units.foot # m - alt_min_cruise = 35000*_units.foot # m - alt_max_cruise = 35000*_units.foot # m - mass_i_cruise = 176765*_units.lb # kg - mass_f_cruise = 143521*_units.lb # kg - v_i_cruise = 455.49*_units.knot # m/s - v_f_cruise = 455.49*_units.knot # m/s - # mach_i_cruise = 0.79 - # mach_f_cruise = 0.79 - mach_min_cruise = 0.79 - mach_max_cruise = 0.79 - range_i_cruise = 160.3*_units.nautical_mile # m - range_f_cruise = 3243.9*_units.nautical_mile # m - t_i_cruise = 26.20*_units.minute # sec - t_f_cruise = 432.38*_units.minute # sec - t_duration_cruise = t_f_cruise - t_i_cruise - - alt_i_descent = 35000*_units.foot - # final altitude set to 35 to ensure landing is feasible point - # alt_f_descent = 0*_units.foot - alt_f_descent = 35*_units.foot - v_i_descent = 455.49*_units.knot - v_f_descent = 198.44*_units.knot - mach_i_descent = 0.79 - mach_f_descent = 0.3 - mass_i_descent = 143521*_units.pound - mass_f_descent = 143035*_units.pound - range_i_descent = 3243.9*_units.nautical_mile - range_f_descent = 3378.7*_units.nautical_mile - t_i_descent = 432.38*_units.minute - t_f_descent = 461.62*_units.minute - t_duration_descent = t_f_descent - t_i_descent - - ################## - # Define Phases # - ################## - - num_segments_climb = 6 - num_segments_cruise = 1 - num_segments_descent = 5 - - climb_seg_ends, _ = dm.utils.lgl.lgl(num_segments_climb + 1) - descent_seg_ends, _ = dm.utils.lgl.lgl(num_segments_descent + 1) - - transcription_climb = dm.Radau( - num_segments=num_segments_climb, order=3, compressed=True, - segment_ends=climb_seg_ends) - transcription_cruise = dm.Radau( - num_segments=num_segments_cruise, order=3, compressed=True) - transcription_descent = dm.Radau( - num_segments=num_segments_descent, order=3, compressed=True, - segment_ends=descent_seg_ends) - - takeoff_options = Takeoff( - airport_altitude=alt_airport, # ft - num_engines=aviary_inputs.get_val(Aircraft.Engine.NUM_ENGINES) - ) - - climb_options = Climb( - 'test_climb', - user_options=AviaryValues({ - 'initial_altitude': (alt_i_climb, 'm'), - 'final_altitude': (alt_f_climb, 'm'), - 'initial_mach': (mach_i_climb, 'unitless'), - 'final_mach': (mach_f_climb, 'unitless'), - 'fix_initial': (False, 'unitless'), - 'fix_range': (False, 'unitless'), - 'input_initial': (True, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_climb, - ) - - cruise_options = Cruise( - 'test_cruise', - user_options=AviaryValues({ - 'min_altitude': (alt_min_cruise, 'm'), - 'max_altitude': (alt_max_cruise, 'm'), - 'min_mach': (mach_min_cruise, 'unitless'), - 'max_mach': (mach_max_cruise, 'unitless'), - 'required_available_climb_rate': (300, 'ft/min'), - 'fix_initial': (False, 'unitless'), - 'fix_final': (False, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_cruise, - ) - - descent_options = Descent( - 'test_descent', - user_options=AviaryValues({ - 'final_altitude': (alt_f_descent, 'm'), - 'initial_altitude': (alt_i_descent, 'm'), - 'initial_mach': (mach_i_descent, 'unitless'), - 'final_mach': (mach_f_descent, 'unitless'), - 'fix_initial': (False, 'unitless'), - 'fix_range': (True, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_descent, - ) - - landing_options = Landing( - ref_wing_area=aviary_inputs.get_val(Aircraft.Wing.AREA, 'ft**2'), - Cl_max_ldg=aviary_inputs.get_val(Mission.Landing.LIFT_COEFFICIENT_MAX) - ) - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'pre_mission', - CorePreMission(aviary_options=aviary_inputs, - subsystems=default_premission_subsystems), - promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - # directly connect phases (strong_couple = True), or use linkage constraints (weak - # coupling / strong_couple=False) - strong_couple = False - - takeoff = takeoff_options.build_phase(False) - - climb = climb_options.build_phase(aviary_options=aviary_inputs) - - cruise = cruise_options.build_phase(aviary_options=aviary_inputs) - - descent = descent_options.build_phase(aviary_options=aviary_inputs) - - landing = landing_options.build_phase(False) - - prob.model.add_subsystem( - 'takeoff', takeoff, promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['mission:*']) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - # if fix_initial is false, can we always set input_initial to be true for - # necessary states, and then ignore if we use a linkage? - climb.set_time_options( - fix_initial=True, fix_duration=False, units='s', - duration_bounds=(1, t_duration_climb*2), duration_ref=t_duration_climb) - cruise.set_time_options( - fix_initial=False, fix_duration=False, units='s', - duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise) - descent.set_time_options( - fix_initial=False, fix_duration=False, units='s', - duration_bounds=(1, t_duration_descent*2), duration_ref=t_duration_descent) - - traj.add_phase('climb', climb) - - traj.add_phase('cruise', cruise) - - traj.add_phase('descent', descent) - - climb.timeseries_options['use_prefix'] = True - cruise.timeseries_options['use_prefix'] = True - descent.timeseries_options['use_prefix'] = True - - prob.model.add_subsystem( - 'landing', landing, promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['mission:*']) - - traj.link_phases(["climb", "cruise"], ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple) - traj.link_phases(["cruise", "descent"], ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs) - - ################################## - # Connect in Takeoff and Landing # - ################################## - - prob.model.add_subsystem( - "takeoff_constraints", - om.ExecComp( - [ - "takeoff_mass_con=takeoff_mass-climb_start_mass", - "takeoff_range_con=takeoff_range-climb_start_range", - "takeoff_vel_con=takeoff_vel-climb_start_vel", - "takeoff_alt_con=takeoff_alt-climb_start_alt" - ], - takeoff_mass_con={'units': 'lbm'}, takeoff_mass={'units': 'lbm'}, - climb_start_mass={'units': 'lbm'}, - takeoff_range_con={'units': 'ft'}, takeoff_range={'units': 'ft'}, - climb_start_range={'units': 'ft'}, - takeoff_vel_con={'units': 'm/s'}, takeoff_vel={'units': 'm/s'}, - climb_start_vel={'units': 'm/s'}, - takeoff_alt_con={'units': 'ft'}, takeoff_alt={'units': 'ft'}, - climb_start_alt={'units': 'ft'} - ), - promotes_inputs=[ - ("takeoff_mass", Mission.Takeoff.FINAL_MASS), - ("takeoff_range", Mission.Takeoff.GROUND_DISTANCE), - ("takeoff_vel", Mission.Takeoff.FINAL_VELOCITY), - ("takeoff_alt", Mission.Takeoff.FINAL_ALTITUDE), - ], - promotes_outputs=["takeoff_mass_con", "takeoff_range_con", - "takeoff_vel_con", "takeoff_alt_con"], - ) - - prob.model.connect('traj.climb.states:mass', - 'takeoff_constraints.climb_start_mass', src_indices=[0]) - prob.model.connect('traj.climb.states:range', - 'takeoff_constraints.climb_start_range', src_indices=[0]) - prob.model.connect('traj.climb.states:velocity', - 'takeoff_constraints.climb_start_vel', src_indices=[0]) - prob.model.connect('traj.climb.states:altitude', - 'takeoff_constraints.climb_start_alt', src_indices=[0]) - - prob.model.connect(Mission.Takeoff.FINAL_MASS, - 'traj.climb.initial_states:mass') - prob.model.connect(Mission.Takeoff.GROUND_DISTANCE, - 'traj.climb.initial_states:range') - prob.model.connect(Mission.Takeoff.FINAL_VELOCITY, - 'traj.climb.initial_states:velocity') - prob.model.connect(Mission.Takeoff.FINAL_ALTITUDE, - 'traj.climb.initial_states:altitude') - - prob.model.connect('traj.descent.states:mass', - Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1]) - prob.model.connect('traj.descent.states:altitude', Mission.Landing.INITIAL_ALTITUDE, - src_indices=[-1]) - - ########################## - # Add Objective Function # - ########################## - - # This is an example of a overall mission objective - # create a compound objective that minimizes climb time and maximizes final mass - # we are maxing final mass b/c we don't have an independent value for fuel_mass yet - # we are going to normalize these (makign each of the sub-objectives approx = 1 ) - prob.model.add_subsystem( - "regularization", - om.ExecComp( - # TODO: change the scaling on climb_duration - "reg_objective = - descent_mass_final/60000", - reg_objective=0.0, - descent_mass_final={"units": "kg", "shape": 1}, - ), - promotes_outputs=['reg_objective'] - ) - # connect the final mass from cruise into the objective - prob.model.connect("traj.descent.states:mass", - "regularization.descent_mass_final", src_indices=[-1]) - - prob.model.add_objective('reg_objective', ref=1) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - - # TODO: Why is this in outputs and not inputs? - key = Aircraft.Engine.THRUST_REVERSERS_MASS - val, units = aviary_outputs.get_item(key) - prob.model.set_input_defaults(key, val, units) - - prob.model.add_subsystem( - 'input_sink', - VariablesIn(aviary_options=aviary_inputs), - promotes_inputs=['*'], - promotes_outputs=['*'] - ) - - prob.setup() - - ########################################### - # Intial Settings for States and Controls # - ########################################### - - prob.set_val('traj.climb.t_initial', t_i_climb, units='s') - prob.set_val('traj.climb.t_duration', t_duration_climb, units='s') - - prob.set_val('traj.climb.states:altitude', climb.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m') - # prob.set_val( - # 'traj.climb.states:velocity', climb.interp(Dynamic.Mission.VELOCITY, ys=[170, v_f_climb]), - # units='m/s') - prob.set_val('traj.climb.states:velocity', climb.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_climb, v_f_climb]), units='m/s') - prob.set_val('traj.climb.states:mass', climb.interp( - Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg') - prob.set_val('traj.climb.states:range', climb.interp( - Dynamic.Mission.RANGE, ys=[range_i_climb, range_f_climb]), units='m') # nmi - - prob.set_val('traj.climb.controls:velocity_rate', - climb.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.25, 0.05]), - units='m/s**2') - prob.set_val('traj.climb.controls:throttle', - climb.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s') - prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s') - - prob.set_val('traj.cruise.states:altitude', cruise.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m') - prob.set_val('traj.cruise.states:velocity', cruise.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_cruise, v_f_cruise]), units='m/s') - prob.set_val('traj.cruise.states:mass', cruise.interp( - Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg') - prob.set_val('traj.cruise.states:range', cruise.interp( - Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi - - prob.set_val('traj.cruise.controls:velocity_rate', - cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.cruise.controls:throttle', - cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - prob.set_val('traj.descent.t_initial', t_i_descent, units='s') - prob.set_val('traj.descent.t_duration', t_duration_descent, units='s') - - prob.set_val('traj.descent.states:altitude', descent.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m') - prob.set_val('traj.descent.states:velocity', descent.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_descent, v_f_descent]), units='m/s') - prob.set_val('traj.descent.states:mass', descent.interp( - Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg') - prob.set_val('traj.descent.states:range', descent.interp( - Dynamic.Mission.RANGE, ys=[range_i_descent, range_f_descent]), units='m') - - prob.set_val('traj.descent.controls:velocity_rate', - descent.interp(Dynamic.Mission.VELOCITY_RATE, ys=[-0.25, 0.05]), - units='m/s**2') - prob.set_val('traj.descent.controls:throttle', - descent.interp(Dynamic.Mission.THROTTLE, ys=[0.0, 0.0]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=sim, make_plots=False, simulate_kwargs={ - 'times_per_seg': 100, 'atol': 1e-9, 'rtol': 1e-9}, - solution_record_file='large_single_aisle_1_solution.db') - prob.record("final") - prob.cleanup() - - return prob - - -@use_tempdirs -class ProblemPhaseTestCase(unittest.TestCase): - def bench_test_full_mission_large_single_aisle_1(self): - - prob = run_trajectory(sim=False) - - times_climb = prob.get_val('traj.climb.timeseries.time', units='s') - altitudes_climb = prob.get_val( - 'traj.climb.timeseries.states:altitude', units='m') - masses_climb = prob.get_val('traj.climb.timeseries.states:mass', units='kg') - ranges_climb = prob.get_val('traj.climb.timeseries.states:range', units='m') - velocities_climb = prob.get_val( - 'traj.climb.timeseries.states:velocity', units='m/s') - thrusts_climb = prob.get_val('traj.climb.timeseries.thrust_net_total', units='N') - times_cruise = prob.get_val('traj.cruise.timeseries.time', units='s') - altitudes_cruise = prob.get_val( - 'traj.cruise.timeseries.states:altitude', units='m') - masses_cruise = prob.get_val('traj.cruise.timeseries.states:mass', units='kg') - ranges_cruise = prob.get_val('traj.cruise.timeseries.states:range', units='m') - velocities_cruise = prob.get_val( - 'traj.cruise.timeseries.states:velocity', units='m/s') - thrusts_cruise = prob.get_val( - 'traj.cruise.timeseries.thrust_net_total', units='N') - times_descent = prob.get_val('traj.descent.timeseries.time', units='s') - altitudes_descent = prob.get_val( - 'traj.descent.timeseries.states:altitude', units='m') - masses_descent = prob.get_val('traj.descent.timeseries.states:mass', units='kg') - ranges_descent = prob.get_val('traj.descent.timeseries.states:range', units='m') - velocities_descent = prob.get_val( - 'traj.descent.timeseries.states:velocity', units='m/s') - thrusts_descent = prob.get_val( - 'traj.descent.timeseries.thrust_net_total', units='N') - - expected_times_s_climb = [ - [120.], [163.76325456], [224.14761365], [243.25905685], - [243.25905685], [336.41086249], [464.94134172], [505.62079895], - [505.62079895], [626.47614107], [793.23184628], [846.00945683], - [846.00945683], [966.86479896], [1133.62050417], [1186.39811471], - [1186.39811471], [1279.54992036], [1408.08039959], [1448.75985682], - [1448.75985682], [1492.52311138], [1552.90747047], [1572.01891366]] - expected_altitudes_m_climb = [ - [10.668], [0.], [1014.96376993], [1474.88490893], - [1474.88490893], [3450.8992731], [5549.58991137], [6073.60289567], - [6073.60289567], [7247.65102441], [8313.09660771], [8596.21261844], - [8596.21261844], [9175.1805832], [9807.17741861], [9956.37909202], - [9956.37909202], [10156.95278577], [10387.66964669], [10469.50089961], - [10469.50089961], [10565.21436037], [10659.11220277], [10668.]] - expected_masses_kg_climb = [ - [81929.21464651], [81845.89383716], [81698.07677997], [81651.72896811], - [81651.72896811], [81451.5201184], [81238.04517346], [81180.70309604], - [81180.70309604], [81030.90916993], [80852.63402635], [80799.37995098], - [80799.37995098], [80685.66441919], [80541.42480306], [80497.91293126], - [80497.91293126], [80422.58418106], [80320.79494098], [80289.0126768], - [80289.0126768], [80255.43650168], [80209.81935885], [80195.47487578]] - expected_ranges_m_climb = [ - [2023.13728418], [7309.29529844], [17625.33986498], [21042.26607802], - [21042.26607802], [38595.24976097], [63501.20931809], [71333.49759313], - [71333.49759313], [95066.70557732], [129238.42790298], [140520.59490531], - [140520.59490531], [166712.77513886], [203467.60377384], [215173.95317494], - [215173.95317494], [236133.6275649], [265602.08094631], [275049.96491681], - [275049.96491681], [285205.23036747], [299246.15306878], [303707.66300732]] - expected_velocities_ms_climb = [ - [86.27497122], [150.21937278], [181.89204952], [185.19401741], - [185.19401741], [193.05567218], [193.94250629], [194.2274308], - [194.2274308], [199.49589035], [211.4364855], [214.57351446], - [214.57351446], [218.7009295], [221.65884364], [223.09827293], - [223.09827293], [226.88054212], [231.57590395], [232.11980164], - [232.11980164], [232.10881873], [233.15980248], [234.25795132]] - expected_thrusts_N_climb = [ - [132715.85627327], [165245.22863773], [167448.73291722], [160900.8406642], - [160900.8406642], [127136.48651829], [98045.08452667], [90929.93404254], - [90929.93404254], [77481.47159379], [66945.17202854], [64341.32162839], - [64341.32162839], [59055.89956031], [54151.63303545], [53020.95829213], - [53020.95829213], [51622.0422889], [50045.94083626], [49417.41803104], - [49417.41803104], [48644.32827828], [47944.25167088], [47933.1465328]] - - expected_times_s_cruise = [[1572.01891366], [ - 10224.87387308], [22164.04764439], [25942.75532212]] - expected_altitudes_m_cruise = [[10668.], [10668.], [10668.], [10668.]] - expected_masses_kg_cruise = [[80195.47487578], [ - 74629.35731156], [67375.28255861], [65172.93890147]] - expected_ranges_m_cruise = [[303707.66300732], [ - 2330707.73887436], [5127554.12700399], [6012746.44622707]] - expected_velocities_ms_cruise = [[234.25795132], [ - 234.25795132], [234.25795132], [234.25795132]] - expected_thrusts_N_cruise = [[41824.96226832], [ - 39641.43620269], [36936.34901257], [36154.52769977]] - - expected_times_s_descent = [ - [25942.75532212], [26006.65447531], [26094.82226471], [26122.72706861], - [26122.72706861], [26253.22730164], [26433.29098775], [26490.28052876], - [26490.28052876], [26645.43239405], [26859.51030121], [26927.26522688], - [26927.26522688], [27057.76545992], [27237.82914603], [27294.81868703], - [27294.81868703], [27358.71784022], [27446.88562963], [27474.79043352]] - expected_altitudes_m_descent = [ - [1.06680000e+04], [1.06680000e+04], [1.01870034e+04], [9.98295734e+03], - [9.98295734e+03], [8.97976640e+03], [7.54858109e+03], [7.09637574e+03], - [7.09637574e+03], [5.88591590e+03], [4.22625069e+03], [3.69222380e+03], - [3.69222380e+03], [2.61250549e+03], [1.13888643e+03], [7.11625616e+02], - [7.11625616e+02], [3.06900655e+02], [1.06680000e+01], [1.06680000e+01]] - expected_masses_kg_descent = [ - [65172.93890147], [65166.80185527], [65159.70921571], [65157.42471584], - [65157.42471584], [65146.76382877], [65130.82732956], [65125.34524899], - [65125.34524899], [65109.28561058], [65083.64486622], [65074.44308601], - [65074.44308601], [65054.73517647], [65022.79856574], [65011.43379965], - [65011.43379965], [64998.13023434], [64979.19121811], [64973.1898506]] - expected_ranges_m_descent = [ - [6012746.44622707], [6026563.35875724], [6043156.61719684], - [6048186.41354773], [6048186.41354773], [6070643.2394269], - [6100144.8206876], [6109350.83226072], [6109350.83226072], - [6133985.78349596], [6167266.64464002], [6177671.71555852], - [6177671.71555852], [6197775.49290063], [6225243.66498946], - [6233748.78818454], [6233748.78818454], [6242933.61721041], - [6254285.51733338], [6257352.4]] - expected_velocities_ms_descent = [ - [234.25795132], [200.84994317], [180.71529791], [177.55751262], - [177.55751262], [167.72481926], [161.81636181], [160.6651682], - [160.6651682], [157.4025997], [154.14780084], [153.88934075], - [153.88934075], [154.23063023], [150.48828108], [146.76095672], - [146.76095672], [139.54419751], [115.31288595], [102.07377559]] - expected_thrusts_N_descent = [ - [0.00000000e+00], [8.47474799e-13], [1.05910816e-13], [-6.67104835e-13], - [0.00000000e+00], [2.74056170e-13], [6.93382542e-13], [8.33653923e-13], - [0.00000000e+00], [4.66122489e-14], [-1.43628962e-13], [-2.96621357e-13], - [0.00000000e+00], [0.00000000e+00], [-1.18597257e-14], [-1.95075855e-14], - [-9.53501847e-15], [0.00000000e+00], [0.00000000e+00], [-3.72692384e-15]] - - expected_times_s_climb = np.array(expected_times_s_climb) - expected_altitudes_m_climb = np.array(expected_altitudes_m_climb) - expected_masses_kg_climb = np.array(expected_masses_kg_climb) - expected_ranges_m_climb = np.array(expected_ranges_m_climb) - expected_velocities_ms_climb = np.array(expected_velocities_ms_climb) - expected_thrusts_N_climb = np.array(expected_thrusts_N_climb) - - expected_times_s_cruise = np.array(expected_times_s_cruise) - expected_altitudes_m_cruise = np.array(expected_altitudes_m_cruise) - expected_masses_kg_cruise = np.array(expected_masses_kg_cruise) - expected_ranges_m_cruise = np.array(expected_ranges_m_cruise) - expected_velocities_ms_cruise = np.array(expected_velocities_ms_cruise) - expected_thrusts_N_cruise = np.array(expected_thrusts_N_cruise) - - expected_times_s_descent = np.array(expected_times_s_descent) - expected_altitudes_m_descent = np.array(expected_altitudes_m_descent) - expected_masses_kg_descent = np.array(expected_masses_kg_descent) - expected_ranges_m_descent = np.array(expected_ranges_m_descent) - expected_velocities_ms_descent = np.array(expected_velocities_ms_descent) - expected_thrusts_N_descent = np.array(expected_thrusts_N_descent) - - # Check Objective and other key variables to a reasonably tight tolerance. - - rtol = 1e-2 - - # Mass at the end of Descent - assert_near_equal(masses_descent[-1], - expected_masses_kg_descent[-1], tolerance=rtol) - - # Range at the end of Descent - assert_near_equal(ranges_descent[-1], - expected_ranges_m_descent[-1], tolerance=rtol) - - # Flight time - assert_near_equal(times_descent[-1], - expected_times_s_descent[-1], tolerance=rtol) - - # Check mission values. - - # NOTE rtol = 0.05 = 5% different from truth (first timeseries) - # atol = 2 = no more than +/-2 meter/second/kg difference between values - # atol_altitude - 30 ft. There is occasional time-shifting with the N3CC - # model during climb and descent so we need a looser - # absolute tolerance for the points near the ground. - rtol = .05 - atol = 2.0 - atol_altitude = 30.0 - - # FLIGHT PATH - # CLIMB - warn_timeseries_near_equal( - times_climb, altitudes_climb, expected_times_s_climb, - expected_altitudes_m_climb, abs_tolerance=atol_altitude, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, masses_climb, expected_times_s_climb, - expected_masses_kg_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, ranges_climb, expected_times_s_climb, - expected_ranges_m_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, velocities_climb, expected_times_s_climb, - expected_velocities_ms_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, thrusts_climb, expected_times_s_climb, - expected_thrusts_N_climb, abs_tolerance=atol, rel_tolerance=rtol) - - # CRUISE - warn_timeseries_near_equal( - times_cruise, altitudes_cruise, expected_times_s_cruise, - expected_altitudes_m_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, masses_cruise, expected_times_s_cruise, - expected_masses_kg_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, ranges_cruise, expected_times_s_cruise, - expected_ranges_m_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, velocities_cruise, expected_times_s_cruise, - expected_velocities_ms_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, thrusts_cruise, expected_times_s_cruise, - expected_thrusts_N_cruise, abs_tolerance=atol, rel_tolerance=rtol) - - # DESCENT - warn_timeseries_near_equal( - times_descent, altitudes_descent, expected_times_s_descent, - expected_altitudes_m_descent, abs_tolerance=atol_altitude, - rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, masses_descent, expected_times_s_descent, - expected_masses_kg_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, ranges_descent, expected_times_s_descent, - expected_ranges_m_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, velocities_descent, expected_times_s_descent, - expected_velocities_ms_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, thrusts_descent, expected_times_s_descent, - expected_thrusts_N_descent, abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == '__main__': - unittest.main() diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_large_single_aisle_2.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_large_single_aisle_2.py deleted file mode 100644 index 7a8c71ba1..000000000 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_based_full_mission_large_single_aisle_2.py +++ /dev/null @@ -1,706 +0,0 @@ -''' -NOTES: -Includes: -Takeoff, Climb, Cruise, Descent, Landing -Computed Aero -Large Single Aisle 2 data -''' -import unittest - -import dymos as dm -import numpy as np -import openmdao.api as om -import scipy.constants as _units -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import use_tempdirs -from packaging import version - -from aviary.mission.flops_based.phases.build_landing import Landing -from aviary.mission.flops_based.phases.build_takeoff import Takeoff -from aviary.mission.flops_based.phases.climb_phase import Climb -from aviary.mission.flops_based.phases.cruise_phase import Cruise -from aviary.mission.flops_based.phases.descent_phase import Descent -from aviary.utils.aviary_values import AviaryValues -from aviary.utils.functions import set_aviary_initial_values -from aviary.utils.test_utils.assert_utils import warn_timeseries_near_equal -from aviary.utils.functions import get_path -from aviary.validation_cases.validation_tests import get_flops_inputs -from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Aircraft, Dynamic, Mission -from aviary.variable_info.variables_in import VariablesIn -from aviary.subsystems.premission import CorePreMission -from aviary.interface.default_phase_info.height_energy import default_premission_subsystems, default_mission_subsystems -from aviary.utils.preprocessors import preprocess_options - -try: - import pyoptsparse -except ImportError: - pyoptsparse = None - -# benchmark based on Large Single Aisle 2 (fixed cruise alt) FLOPS model - - -def run_trajectory(sim=True): - prob = om.Problem(model=om.Group()) - if pyoptsparse: - driver = prob.driver = om.pyOptSparseDriver() - driver.options["optimizer"] = "SNOPT" - # driver.declare_coloring() # currently disabled pending resolve of issue 2507 - if driver.options["optimizer"] == "SNOPT": - driver.opt_settings["Major iterations limit"] = 45 - driver.opt_settings["Major optimality tolerance"] = 1e-4 - driver.opt_settings["Major feasibility tolerance"] = 1e-6 - driver.opt_settings["iSumm"] = 6 - elif driver.options["optimizer"] == "IPOPT": - driver.opt_settings["max_iter"] = 100 - driver.opt_settings["tol"] = 1e-3 - driver.opt_settings['print_level'] = 4 - - else: - driver = prob.driver = om.ScipyOptimizeDriver() - opt_settings = prob.driver.opt_settings - - driver.options['optimizer'] = 'SLSQP' - opt_settings['maxiter'] = 100 - opt_settings['ftol'] = 5.0e-3 - opt_settings['eps'] = 1e-2 - - ########################################## - # Aircraft Input Variables and Options # - ########################################## - - aviary_inputs = get_flops_inputs('LargeSingleAisle2FLOPS') - - preprocess_options(aviary_inputs) - - alt_airport = 0 # ft - - ref_wing_area = 1370 # ft^2 TODO: where should this get connected from? - - alt_i_climb = 35.0*_units.foot # m (comes from takeoff) - alt_f_climb = 35000.0*_units.foot # m - mass_i_climb = 178172*_units.lb # kg (comes from takeoff) - mass_f_climb = 174160*_units.lb # kg - v_i_climb = 198.44*_units.knot # m/s (comes from takeoff) - v_f_climb = 452.61*_units.knot # m/s - mach_i_climb = 0.2 # TODO: (need to compute this in takeoff) - mach_f_climb = 0.785 - range_i_climb = 0*_units.nautical_mile # m (comes from takeoff) - range_f_climb = 124*_units.nautical_mile # m - t_i_climb = 0 - t_duration_climb = 20.14*_units.minute # sec - - alt_i_cruise = 35000*_units.foot # m - alt_f_cruise = 35000*_units.foot # m - alt_min_cruise = 35000*_units.foot # m - alt_max_cruise = 35000*_units.foot # m - mass_i_cruise = 174160*_units.lb # kg - mass_f_cruise = 140515*_units.lb # kg - v_i_cruise = 452.61*_units.knot # m/s - v_f_cruise = 452.61*_units.knot # m/s - mach_f_cruise = 0.785 - mach_min_cruise = 0.785 - mach_max_cruise = 0.785 - range_i_cruise = 124*_units.nautical_mile # m - range_f_cruise = 2830.8*_units.nautical_mile # m - t_i_cruise = t_i_climb + t_duration_climb # sec - t_duration_cruise = (378.95)*_units.minute - t_duration_climb # sec - - alt_i_descent = 35000*_units.foot # m - alt_f_descent = 35*_units.foot # m - v_i_descent = 452.61*_units.knot # m/s - v_f_descent = 198.44*_units.knot # m/s - mass_i_descent = 140515*_units.pound # kg - mass_f_descent = 140002*_units.pound # kg - mach_i_descent = mach_f_cruise - mach_f_descent = 0.3 - range_i_descent = 2830.8*_units.nautical_mile # m - range_f_descent = 2960.0*_units.nautical_mile # m - t_i_descent = t_duration_cruise+t_duration_climb # sec - t_duration_descent = 2000 # sec - - Cl_max_ldg = 3 # TODO: should this come from aero? - - ################## - # Define Phases # - ################## - - num_segments_climb = 6 - num_segments_cruise = 1 - num_segments_descent = 5 - - climb_seg_ends, _ = dm.utils.lgl.lgl(num_segments_climb + 1) - descent_seg_ends, _ = dm.utils.lgl.lgl(num_segments_descent + 1) - - transcription_climb = dm.Radau( - num_segments=num_segments_climb, order=3, compressed=True, - segment_ends=climb_seg_ends) - transcription_cruise = dm.Radau( - num_segments=num_segments_cruise, order=3, compressed=True) - transcription_descent = dm.Radau( - num_segments=num_segments_descent, order=3, compressed=True, - segment_ends=descent_seg_ends) - - takeoff_options = Takeoff( - airport_altitude=alt_airport, # ft - num_engines=aviary_inputs.get_val(Aircraft.Engine.NUM_ENGINES) - ) - - climb_options = Climb( - 'test_climb', - user_options=AviaryValues({ - 'initial_altitude': (alt_i_climb, 'm'), - 'final_altitude': (alt_f_climb, 'm'), - 'initial_mach': (mach_i_climb, 'unitless'), - 'final_mach': (mach_f_climb, 'unitless'), - 'fix_initial': (False, 'unitless'), - 'fix_range': (False, 'unitless'), - 'input_initial': (True, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_climb, - ) - - cruise_options = Cruise( - 'test_cruise', - user_options=AviaryValues({ - 'min_altitude': (alt_min_cruise, 'm'), - 'max_altitude': (alt_max_cruise, 'm'), - 'min_mach': (mach_min_cruise, 'unitless'), - 'max_mach': (mach_max_cruise, 'unitless'), - 'required_available_climb_rate': (300, 'ft/min'), - 'fix_initial': (False, 'unitless'), - 'fix_final': (False, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_cruise, - ) - - descent_options = Descent( - 'test_descent', - user_options=AviaryValues({ - 'final_altitude': (alt_f_descent, 'm'), - 'initial_altitude': (alt_i_descent, 'm'), - 'initial_mach': (mach_i_descent, 'unitless'), - 'final_mach': (mach_f_descent, 'unitless'), - 'fix_initial': (False, 'unitless'), - 'fix_range': (True, 'unitless'), - }), - core_subsystems=default_mission_subsystems, - subsystem_options={'core_aerodynamics': {'method': 'computed'}}, - transcription=transcription_descent, - ) - - landing_options = Landing( - ref_wing_area=ref_wing_area, # ft**2 - Cl_max_ldg=Cl_max_ldg # no units - ) - - # Upstream static analysis for aero - prob.model.add_subsystem( - 'pre_mission', - CorePreMission(aviary_options=aviary_inputs, - subsystems=default_premission_subsystems), - promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['aircraft:*', 'mission:*']) - - # directly connect phases (strong_couple = True), or use linkage constraints (weak - # coupling / strong_couple=False) - strong_couple = False - - takeoff = takeoff_options.build_phase(False) - - climb = climb_options.build_phase(aviary_options=aviary_inputs) - - cruise = cruise_options.build_phase(aviary_options=aviary_inputs) - - descent = descent_options.build_phase(aviary_options=aviary_inputs) - - landing = landing_options.build_phase(False) - - prob.model.add_subsystem( - 'takeoff', takeoff, promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['mission:*']) - - traj = prob.model.add_subsystem('traj', dm.Trajectory()) - - # if fix_initial is false, can we always set input_initial to be true for - # necessary states, and then ignore if we use a linkage? - climb.set_time_options( - fix_initial=True, fix_duration=False, units='s', - duration_bounds=(1, t_duration_climb*2), duration_ref=t_duration_climb) - cruise.set_time_options( - fix_initial=False, fix_duration=False, units='s', - duration_bounds=(1, t_duration_cruise*2), duration_ref=t_duration_cruise) - descent.set_time_options( - fix_initial=False, fix_duration=False, units='s', - duration_bounds=(1, t_duration_descent*2), duration_ref=t_duration_descent) - - traj.add_phase('climb', climb) - - traj.add_phase('cruise', cruise) - - traj.add_phase('descent', descent) - - climb.timeseries_options['use_prefix'] = True - cruise.timeseries_options['use_prefix'] = True - descent.timeseries_options['use_prefix'] = True - - prob.model.add_subsystem( - 'landing', landing, promotes_inputs=['aircraft:*', 'mission:*'], - promotes_outputs=['mission:*']) - - traj.link_phases(["climb", "cruise"], ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple) - traj.link_phases(["cruise", "descent"], ["time", Dynamic.Mission.ALTITUDE, - Dynamic.Mission.VELOCITY, Dynamic.Mission.MASS, Dynamic.Mission.RANGE], connected=strong_couple) - - traj = setup_trajectory_params(prob.model, traj, aviary_inputs) - - ################################## - # Connect in Takeoff and Landing # - ################################## - - prob.model.add_subsystem( - "takeoff_constraints", - om.ExecComp( - [ - "takeoff_mass_con=takeoff_mass-climb_start_mass", - "takeoff_range_con=takeoff_range-climb_start_range", - "takeoff_vel_con=takeoff_vel-climb_start_vel", - "takeoff_alt_con=takeoff_alt-climb_start_alt" - ], - takeoff_mass_con={'units': 'lbm'}, takeoff_mass={'units': 'lbm'}, - climb_start_mass={'units': 'lbm'}, - takeoff_range_con={'units': 'ft'}, takeoff_range={'units': 'ft'}, - climb_start_range={'units': 'ft'}, - takeoff_vel_con={'units': 'm/s'}, takeoff_vel={'units': 'm/s'}, - climb_start_vel={'units': 'm/s'}, - takeoff_alt_con={'units': 'ft'}, takeoff_alt={'units': 'ft'}, - climb_start_alt={'units': 'ft'} - ), - promotes_inputs=[ - ("takeoff_mass", Mission.Takeoff.FINAL_MASS), - ("takeoff_range", Mission.Takeoff.GROUND_DISTANCE), - ("takeoff_vel", Mission.Takeoff.FINAL_VELOCITY), - ("takeoff_alt", Mission.Takeoff.FINAL_ALTITUDE), - ], - promotes_outputs=["takeoff_mass_con", "takeoff_range_con", - "takeoff_vel_con", "takeoff_alt_con"], - ) - - prob.model.connect('traj.climb.states:mass', - 'takeoff_constraints.climb_start_mass', src_indices=[0]) - prob.model.connect('traj.climb.states:range', - 'takeoff_constraints.climb_start_range', src_indices=[0]) - prob.model.connect('traj.climb.states:velocity', - 'takeoff_constraints.climb_start_vel', src_indices=[0]) - prob.model.connect('traj.climb.states:altitude', - 'takeoff_constraints.climb_start_alt', src_indices=[0]) - - prob.model.connect(Mission.Takeoff.FINAL_MASS, - 'traj.climb.initial_states:mass') - prob.model.connect(Mission.Takeoff.GROUND_DISTANCE, - 'traj.climb.initial_states:range') - prob.model.connect(Mission.Takeoff.FINAL_VELOCITY, - 'traj.climb.initial_states:velocity') - prob.model.connect(Mission.Takeoff.FINAL_ALTITUDE, - 'traj.climb.initial_states:altitude') - - prob.model.connect('traj.descent.states:mass', - Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1]) - prob.model.connect('traj.descent.states:altitude', Mission.Landing.INITIAL_ALTITUDE, - src_indices=[-1]) - - ########################## - # Add Objective Function # - ########################## - - # This is an example of a overall mission objective - # create a compound objective that minimizes climb time and maximizes final mass - # we are maxing final mass b/c we don't have an independent value for fuel_mass yet - # we are going to normalize these (makign each of the sub-objectives approx = 1 ) - prob.model.add_subsystem( - "regularization", - om.ExecComp( - # TODO: change the scaling on climb_duration - "reg_objective = - descent_mass_final/60000", - reg_objective=0.0, - descent_mass_final={"units": "kg", "shape": 1}, - ), - promotes_outputs=['reg_objective'] - ) - # connect the final mass from cruise into the objective - prob.model.connect("traj.descent.states:mass", - "regularization.descent_mass_final", src_indices=[-1]) - - prob.model.add_objective('reg_objective', ref=1) - - # Set initial default values for all aircraft variables. - set_aviary_initial_values(prob.model, aviary_inputs) - - prob.model.add_subsystem( - 'input_sink', - VariablesIn(aviary_options=aviary_inputs), - promotes_inputs=['*'], - promotes_outputs=['*'] - ) - - prob.setup() - - ########################################### - # Intial Settings for States and Controls # - ########################################### - - prob.set_val('traj.climb.t_initial', t_i_climb, units='s') - prob.set_val('traj.climb.t_duration', t_duration_climb, units='s') - - prob.set_val('traj.climb.states:altitude', climb.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m') - prob.set_val('traj.climb.states:velocity', climb.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_climb, v_f_climb]), units='m/s') - prob.set_val('traj.climb.states:mass', climb.interp( - Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg') - prob.set_val('traj.climb.states:range', climb.interp( - Dynamic.Mission.RANGE, ys=[range_i_climb, range_f_climb]), units='m') # nmi - - prob.set_val('traj.climb.controls:velocity_rate', - climb.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.25, 0.05]), - units='m/s**2') - prob.set_val('traj.climb.controls:throttle', - climb.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s') - prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s') - - prob.set_val('traj.cruise.states:altitude', cruise.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m') - prob.set_val('traj.cruise.states:velocity', cruise.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_cruise, v_f_cruise]), units='m/s') - prob.set_val('traj.cruise.states:mass', cruise.interp( - Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg') - prob.set_val('traj.cruise.states:range', cruise.interp( - Dynamic.Mission.RANGE, ys=[range_i_cruise, range_f_cruise]), units='m') # nmi - - prob.set_val('traj.cruise.controls:velocity_rate', - cruise.interp(Dynamic.Mission.VELOCITY_RATE, ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.cruise.controls:throttle', - cruise.interp(Dynamic.Mission.THROTTLE, ys=[0.5, 0.5]), - units='unitless') - - prob.set_val('traj.descent.t_initial', t_i_descent, units='s') - prob.set_val('traj.descent.t_duration', t_duration_descent, units='s') - - prob.set_val('traj.descent.states:altitude', descent.interp( - Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m') - prob.set_val('traj.descent.states:velocity', descent.interp( - Dynamic.Mission.VELOCITY, ys=[v_i_descent, v_f_descent]), units='m/s') - prob.set_val('traj.descent.states:mass', descent.interp( - Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg') - prob.set_val('traj.descent.states:range', descent.interp( - Dynamic.Mission.RANGE, ys=[range_i_descent, range_f_descent]), units='m') - - prob.set_val('traj.descent.controls:velocity_rate', - descent.interp(Dynamic.Mission.VELOCITY_RATE, ys=[-0.25, 0.0]), - units='m/s**2') - prob.set_val('traj.descent.controls:throttle', - descent.interp(Dynamic.Mission.THROTTLE, ys=[0.0, 0.0]), - units='unitless') - - # Turn off solver printing so that the SNOPT output is readable. - prob.set_solver_print(level=0) - - dm.run_problem(prob, simulate=sim, make_plots=False, simulate_kwargs={ - 'times_per_seg': 100, 'atol': 1e-9, 'rtol': 1e-9}, - solution_record_file='large_single_aisle_2_solution.db') - prob.record("final") - prob.cleanup() - - return prob - - -@use_tempdirs -class ProblemPhaseTestCase(unittest.TestCase): - def bench_test_full_mission_large_single_aisle_2(self): - - prob = run_trajectory(sim=False) - - times_climb = prob.get_val('traj.climb.timeseries.time', units='s') - altitudes_climb = prob.get_val( - 'traj.climb.timeseries.states:altitude', units='m') - masses_climb = prob.get_val('traj.climb.timeseries.states:mass', units='kg') - ranges_climb = prob.get_val('traj.climb.timeseries.states:range', units='m') - velocities_climb = prob.get_val( - 'traj.climb.timeseries.states:velocity', units='m/s') - thrusts_climb = prob.get_val('traj.climb.timeseries.thrust_net_total', units='N') - times_cruise = prob.get_val('traj.cruise.timeseries.time', units='s') - altitudes_cruise = prob.get_val( - 'traj.cruise.timeseries.states:altitude', units='m') - masses_cruise = prob.get_val('traj.cruise.timeseries.states:mass', units='kg') - ranges_cruise = prob.get_val('traj.cruise.timeseries.states:range', units='m') - velocities_cruise = prob.get_val( - 'traj.cruise.timeseries.states:velocity', units='m/s') - thrusts_cruise = prob.get_val( - 'traj.cruise.timeseries.thrust_net_total', units='N') - times_descent = prob.get_val('traj.descent.timeseries.time', units='s') - altitudes_descent = prob.get_val( - 'traj.descent.timeseries.states:altitude', units='m') - masses_descent = prob.get_val('traj.descent.timeseries.states:mass', units='kg') - ranges_descent = prob.get_val('traj.descent.timeseries.states:range', units='m') - velocities_descent = prob.get_val( - 'traj.descent.timeseries.states:velocity', units='m/s') - thrusts_descent = prob.get_val( - 'traj.descent.timeseries.thrust_net_total', units='N') - - expected_times_s_climb = [[0.], [36.42164779], [86.67608797], [102.58144646], - [102.58144646], [180.10635382], [287.07490293], - [320.93008299], [320.93008299], [421.51105549], - [560.29226869], [604.21604816], [604.21604816], - [704.79702067], [843.57823386], [887.50201334], - [887.50201334], [965.0269207], [1071.9954698], - [1105.85064986], [1105.85064986], [1142.27229765], - [1192.52673783], [1208.43209632]] - expected_altitudes_m_climb = [[10.668], [10.668], [756.40579584], [1094.69377576], - [1094.69377576], [2638.335354], [4474.82351314], - [4956.42872513], [4956.42872513], [6093.2556723], - [7216.10719049], [7539.50807227], [7539.50807227], - [8246.0164048], [9133.44034449], [9376.84106855], - [9376.84106855], [9769.35335562], [10235.21824927], - [10366.38189087], [10366.38189087], [10496.4112199], - [10637.33808625], [10668.]] - expected_masses_kg_climb = [[78716.87348217], [78630.56839717], [78501.99380919], - [78461.17568729], [78461.17568729], [78282.66956759], - [78079.48002171], [78024.13525417], [78024.13525417], - [77874.53619474], [77690.00183752], [77632.85287533], - [77632.85287533], [77506.6235826], [77341.2312645], - [77291.7130525], [77291.7130525], [77206.37945353], - [77094.28764289], [77060.26735586], [77060.26735586], - [77024.49553459], [76976.26878085], [76961.2011732]] - expected_ranges_m_climb = [[2020.172677], [6250.78127734], [14408.93179369], - [17152.5734179], [17152.5734179], [31361.95647109], - [51731.27335637], [58135.07173792], [58135.07173792], - [77558.59088335], [105627.83510378], [114946.12296084], - [114946.12296084], [136784.80542027], [167833.78601896], - [177796.59419893], [177796.59419893], [195533.43046681], - [220220.00511814], [228064.41792222], [228064.41792222], - [236500.76050256], [248152.97050119], [251848.92128258]] - expected_velocities_ms_climb = [[85.50188638], [142.5264918], [174.18896469], - [178.36168293], [178.36168293], [189.05364242], - [190.8409755], [191.10004849], [191.10004849], - [196.39829028], [209.55408002], [213.5660258], - [213.5660258], [220.53005733], [226.3795119], - [227.73490457], [227.73490457], [229.83816653], - [231.58844935], [231.71451888], [231.71451888], - [231.66373105], [232.22063654], [232.77530606]] - expected_thrusts_N_climb = [[189058.83725077], - [170072.62422304], - [158048.98358529], - [154385.46295721], - [154385.46295721], - [136940.62457029], - [116116.73671672], - [110355.92368163], - [110355.92368163], - [97102.32305726], - [84436.60128586], - [81321.06998036], - [81321.06998036], - [75575.9138158], - [69929.77214168], - [68233.54819255], - [68233.54819255], - [66906.31800496], - [64990.02370683], - [64226.67297361], - [64226.67297361], - [63890.14810601], - [62804.25695388], - [61976.46190808]] - - expected_times_s_cruise = [[1208.43209632], [8852.14330109], - [19398.90466014], [22736.91857014]] - expected_altitudes_m_cruise = [[10668.], [10668.], [10668.], [10668.]] - expected_masses_kg_cruise = [[76961.2011732], [71368.01841021], - [64248.23842657], [62092.78518296]] - expected_ranges_m_cruise = [[251848.92128258], [2031116.13639915], - [4486141.73968515], [5263148.9492152]] - expected_velocities_ms_cruise = [[232.77530606], [232.77530606], - [232.77530606], [232.77530606]] - expected_thrusts_N_cruise = [[42313.90859903], - [39067.24395088], - [35950.87557908], - [35318.16856753]] - - expected_times_s_descent = [[22736.91857014], [22787.42540336], [22857.11452461], - [22879.17089321], [22879.17089321], [22982.32020233], - [23124.64519584], [23169.69056966], [23169.69056966], - [23292.32489254], [23461.53522808], [23515.08972074], - [23515.08972074], [23618.23902986], [23760.56402336], - [23805.60939719], [23805.60939719], [23856.11623041], - [23925.80535166], [24317.03614505]] - expected_altitudes_m_descent = [[10668.], [10434.7028388], [9960.79015066], - [9784.8421005], [9784.8421005], [8895.17761059], - [7537.33136949], [7090.56116653], [7090.56116653], - [5841.93815892], [3999.27122775], [3377.71804494], - [3377.71804494], [2138.48831166], [663.47208745], - [344.68854352], [344.68854352], [122.72341358], - [10.668], [10.668]] - expected_masses_kg_descent = [[62092.78518296], [62087.36440032], [62080.05896866], - [62077.72767582], [62077.72767582], [62066.62726287], - [62050.05526861], [62044.32456475], [62044.32456475], - [62027.35019169], [62000.0762637], [61990.36426104], - [61990.36426104], [61969.97789242], [61939.03538935], - [61928.82648685], [61928.82648685], [61917.76865363], - [61902.72792251], [60998.50259339]] - expected_ranges_m_descent = [[5263148.9492152], [5274432.80167853], [5288836.80724673], - [5293223.61558423], [ - 5293223.61558423], [5313013.5415642], - [5339348.96176189], [5347671.94897183], [ - 5347671.94897183], - [5370259.6353939], [5401419.10034695], [ - 5411278.66121681], - [5411278.66121681], [5430273.70362962], [ - 5455475.25800144], - [5462914.27715932], [5462914.27715932], [ - 5470553.18510276], - [5479541.42534675], [5481920.]] - expected_velocities_ms_descent = [[232.77530606], [215.12062535], [200.3306912], - [197.18540872], [197.18540872], [187.8799993], - [184.51577102], [184.46836756], [184.46836756], - [184.46836756], [184.46836756], [184.46836756], - [184.46836756], [183.31878177], [168.97447568], - [158.78398844], [158.78398844], [143.03675256], - [113.40966999], [102.07377559]] - expected_thrusts_N_descent = [[-10826.40055652], - [-4402.33172934], - [3254.14270948], - [5235.45930457], - [5235.45930457], - [12007.31061759], - [13032.5411879], - [10960.76992506], - [10960.76992506], - [5671.53535014], - [1824.89872243], - [1525.32410053], - [1525.32410053], - [-329.33574306], - [-9335.05085191], - [-13485.38001055], - [-13485.38001055], - [-17391.90310498], - [-14884.85752283], - [-12642.43964087]] - - expected_times_s_climb = np.array(expected_times_s_climb) - expected_altitudes_m_climb = np.array(expected_altitudes_m_climb) - expected_masses_kg_climb = np.array(expected_masses_kg_climb) - expected_ranges_m_climb = np.array(expected_ranges_m_climb) - expected_velocities_ms_climb = np.array(expected_velocities_ms_climb) - expected_thrusts_N_climb = np.array(expected_thrusts_N_climb) - - expected_times_s_cruise = np.array(expected_times_s_cruise) - expected_altitudes_m_cruise = np.array(expected_altitudes_m_cruise) - expected_masses_kg_cruise = np.array(expected_masses_kg_cruise) - expected_ranges_m_cruise = np.array(expected_ranges_m_cruise) - expected_velocities_ms_cruise = np.array(expected_velocities_ms_cruise) - expected_thrusts_N_cruise = np.array(expected_thrusts_N_cruise) - - expected_times_s_descent = np.array(expected_times_s_descent) - expected_altitudes_m_descent = np.array(expected_altitudes_m_descent) - expected_masses_kg_descent = np.array(expected_masses_kg_descent) - expected_ranges_m_descent = np.array(expected_ranges_m_descent) - expected_velocities_ms_descent = np.array(expected_velocities_ms_descent) - expected_thrusts_N_descent = np.array(expected_thrusts_N_descent) - - # Check Objective and other key variables to a reasonably tight tolerance. - - rtol = 1e-3 - - # Mass at the end of Descent - assert_near_equal(masses_descent[-1], - expected_masses_kg_descent[-1], tolerance=rtol) - - # Range at the end of Descent - assert_near_equal(ranges_descent[-1], - expected_ranges_m_descent[-1], tolerance=rtol) - - # Flight time - assert_near_equal(times_descent[-1], - expected_times_s_descent[-1], tolerance=rtol) - - # Check mission values. - - # NOTE rtol = 0.05 = 5% different from truth (first timeseries) - # atol = 2 = no more than +/-2 meter/second/kg difference between values - # atol_altitude - 30 ft. There is occasional time-shifting with the N3CC - # model during climb and descent so we need a looser - # absolute tolerance for the points near the ground. - rtol = .05 - atol = 2.0 - atol_altitude = 30.0 - - # FLIGHT PATH - # CLIMB - warn_timeseries_near_equal( - times_climb, altitudes_climb, expected_times_s_climb, - expected_altitudes_m_climb, abs_tolerance=atol_altitude, rel_tolerance=rtol) - - warn_timeseries_near_equal( - times_climb, masses_climb, expected_times_s_climb, - expected_masses_kg_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, ranges_climb, expected_times_s_climb, - expected_ranges_m_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, velocities_climb, expected_times_s_climb, - expected_velocities_ms_climb, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_climb, thrusts_climb, expected_times_s_climb, - expected_thrusts_N_climb, abs_tolerance=atol, rel_tolerance=rtol) - - # FLIGHT PATH - # CRUISE - warn_timeseries_near_equal( - times_cruise, altitudes_cruise, expected_times_s_cruise, - expected_altitudes_m_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, masses_cruise, expected_times_s_cruise, - expected_masses_kg_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, ranges_cruise, expected_times_s_cruise, - expected_ranges_m_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, velocities_cruise, expected_times_s_cruise, - expected_velocities_ms_cruise, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_cruise, thrusts_cruise, expected_times_s_cruise, - expected_thrusts_N_cruise, abs_tolerance=atol, rel_tolerance=rtol) - - # FLIGHT PATH - # DESCENT - warn_timeseries_near_equal( - times_descent, altitudes_descent, expected_times_s_descent, - expected_altitudes_m_descent, abs_tolerance=atol_altitude, - rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, masses_descent, expected_times_s_descent, - expected_masses_kg_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, ranges_descent, expected_times_s_descent, - expected_ranges_m_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, velocities_descent, expected_times_s_descent, - expected_velocities_ms_descent, abs_tolerance=atol, rel_tolerance=rtol) - warn_timeseries_near_equal( - times_descent, thrusts_descent, expected_times_s_descent, - expected_thrusts_N_descent, abs_tolerance=atol, rel_tolerance=rtol) - - -if __name__ == '__main__': - z = ProblemPhaseTestCase() - z.bench_test_full_mission_large_single_aisle_2() diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_based_sizing_N3CC.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_based_sizing_N3CC.py index ef2a355a1..d1f982f5d 100644 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_based_sizing_N3CC.py +++ b/aviary/validation_cases/benchmark_tests/test_FLOPS_based_sizing_N3CC.py @@ -13,21 +13,19 @@ import scipy.constants as _units from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.testing_utils import use_tempdirs +from openmdao.utils.testing_utils import require_pyoptsparse from aviary.mission.flops_based.phases.build_landing import Landing from aviary.mission.flops_based.phases.build_takeoff import Takeoff -from aviary.mission.flops_based.phases.climb_phase import Climb -from aviary.mission.flops_based.phases.cruise_phase import Cruise -from aviary.mission.flops_based.phases.descent_phase import Descent from aviary.utils.functions import set_aviary_initial_values from aviary.utils.test_utils.assert_utils import warn_timeseries_near_equal -from aviary.utils.functions import get_path from aviary.validation_cases.validation_tests import get_flops_inputs from aviary.variable_info.functions import setup_trajectory_params from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.variables_in import VariablesIn +from aviary.mission.flops_based.phases.energy_phase import EnergyPhase -from aviary.variable_info.variables import Aircraft, Mission +from aviary.variable_info.variables import Aircraft, Dynamic, Mission from aviary.subsystems.premission import CorePreMission from aviary.interface.default_phase_info.height_energy import default_premission_subsystems, default_mission_subsystems from aviary.utils.preprocessors import preprocess_crewpayload @@ -95,14 +93,12 @@ def run_trajectory(sim=True): alt_f_climb = 35000.0*_units.foot # m mass_i_climb = 131000*_units.lb # kg mass_f_climb = 126000*_units.lb # kg - v_i_climb = 198.44*_units.knot # m/s - v_f_climb = 455.49*_units.knot # m/s # initial mach set to lower value so it can intersect with takeoff end mach # mach_i_climb = 0.3 mach_i_climb = 0.2 mach_f_climb = 0.79 - range_i_climb = 0*_units.nautical_mile # m - range_f_climb = 160.3*_units.nautical_mile # m + distance_i_climb = 0*_units.nautical_mile # m + distance_f_climb = 160.3*_units.nautical_mile # m t_i_climb = 2 * _units.minute # sec t_f_climb = 26.20*_units.minute # sec t_duration_climb = t_f_climb - t_i_climb @@ -113,12 +109,9 @@ def run_trajectory(sim=True): alt_max_cruise = 35000*_units.foot # m mass_i_cruise = 126000*_units.lb # kg mass_f_cruise = 102000*_units.lb # kg - v_i_cruise = 455.49*_units.knot # m/s - v_f_cruise = 455.49*_units.knot # m/s - mach_min_cruise = 0.79 - mach_max_cruise = 0.79 - range_i_cruise = 160.3*_units.nautical_mile # m - range_f_cruise = 3243.9*_units.nautical_mile # m + cruise_mach = 0.79 + distance_i_cruise = 160.3*_units.nautical_mile # m + distance_f_cruise = 3243.9*_units.nautical_mile # m t_i_cruise = 26.20*_units.minute # sec t_f_cruise = 432.38*_units.minute # sec t_duration_cruise = t_f_cruise - t_i_cruise @@ -127,14 +120,12 @@ def run_trajectory(sim=True): # final altitude set to 35 to ensure landing is feasible point # alt_f_descent = 0*_units.foot alt_f_descent = 35*_units.foot - v_i_descent = 455.49*_units.knot - v_f_descent = 198.44*_units.knot mach_i_descent = 0.79 mach_f_descent = 0.3 mass_i_descent = 102000*_units.pound mass_f_descent = 101000*_units.pound - range_i_descent = 3243.9*_units.nautical_mile - range_f_descent = 3378.7*_units.nautical_mile + distance_i_descent = 3243.9*_units.nautical_mile + distance_f_descent = 3378.7*_units.nautical_mile t_i_descent = 432.38*_units.minute t_f_descent = 461.62*_units.minute t_duration_descent = t_f_descent - t_i_descent @@ -174,7 +165,7 @@ def run_trajectory(sim=True): num_segments=num_segments_descent, order=3, compressed=True, segment_ends=descent_seg_ends) - climb_options = Climb( + climb_options = EnergyPhase( 'test_climb', user_options=AviaryValues({ 'initial_altitude': (alt_i_climb, 'm'), @@ -182,31 +173,30 @@ def run_trajectory(sim=True): 'initial_mach': (mach_i_climb, 'unitless'), 'final_mach': (mach_f_climb, 'unitless'), 'fix_initial': (False, 'unitless'), - 'fix_range': (False, 'unitless'), 'input_initial': (True, 'unitless'), + 'use_polynomial_control': (False, 'unitless'), }), core_subsystems=default_mission_subsystems, subsystem_options={'core_aerodynamics': {'method': 'computed'}}, transcription=transcription_climb, ) - cruise_options = Cruise( + cruise_options = EnergyPhase( 'test_cruise', user_options=AviaryValues({ - 'min_altitude': (alt_min_cruise, 'm'), - 'max_altitude': (alt_max_cruise, 'm'), - 'min_mach': (mach_min_cruise, 'unitless'), - 'max_mach': (mach_max_cruise, 'unitless'), + 'initial_altitude': (alt_min_cruise, 'm'), + 'final_altitude': (alt_max_cruise, 'm'), + 'initial_mach': (cruise_mach, 'unitless'), + 'final_mach': (cruise_mach, 'unitless'), 'required_available_climb_rate': (300, 'ft/min'), 'fix_initial': (False, 'unitless'), - 'fix_final': (False, 'unitless'), }), core_subsystems=default_mission_subsystems, subsystem_options={'core_aerodynamics': {'method': 'computed'}}, transcription=transcription_cruise, ) - descent_options = Descent( + descent_options = EnergyPhase( 'test_descent', user_options=AviaryValues({ 'final_altitude': (alt_f_descent, 'm'), @@ -214,7 +204,7 @@ def run_trajectory(sim=True): 'initial_mach': (mach_i_descent, 'unitless'), 'final_mach': (mach_f_descent, 'unitless'), 'fix_initial': (False, 'unitless'), - 'fix_range': (True, 'unitless'), + 'use_polynomial_control': (False, 'unitless'), }), core_subsystems=default_mission_subsystems, subsystem_options={'core_aerodynamics': {'method': 'computed'}}, @@ -280,78 +270,27 @@ def run_trajectory(sim=True): traj.add_phase('descent', descent) - try: - climb.timeseries_options['use_prefix'] = True - cruise.timeseries_options['use_prefix'] = True - descent.timeseries_options['use_prefix'] = True - except KeyError: - pass - prob.model.add_subsystem( 'landing', landing, promotes_inputs=['aircraft:*', 'mission:*'], promotes_outputs=['mission:*']) - traj.link_phases(["climb", "cruise"], ["time", "altitude", - "velocity", "mass", "range"], connected=strong_couple) - traj.link_phases(["cruise", "descent"], ["time", "altitude", - "velocity", "mass", "range"], connected=strong_couple) + traj.link_phases(["climb", "cruise", "descent"], [ + "time", Dynamic.Mission.MASS, Dynamic.Mission.DISTANCE], connected=strong_couple) traj = setup_trajectory_params(prob.model, traj, aviary_inputs) ################################## # Connect in Takeoff and Landing # ################################## - - prob.model.add_subsystem( - "takeoff_constraints", - om.ExecComp( - [ - "takeoff_mass_con=takeoff_mass-climb_start_mass", - "takeoff_range_con=takeoff_range-climb_start_range", - "takeoff_vel_con=takeoff_vel-climb_start_vel", - "takeoff_alt_con=takeoff_alt-climb_start_alt" - ], - takeoff_mass_con={'units': 'lbm'}, takeoff_mass={'units': 'lbm'}, - climb_start_mass={'units': 'lbm'}, - takeoff_range_con={'units': 'ft'}, takeoff_range={'units': 'ft'}, - climb_start_range={'units': 'ft'}, - takeoff_vel_con={'units': 'm/s'}, takeoff_vel={'units': 'm/s'}, - climb_start_vel={'units': 'm/s'}, - takeoff_alt_con={'units': 'ft'}, takeoff_alt={'units': 'ft'}, - climb_start_alt={'units': 'ft'} - ), - promotes_inputs=[ - ("takeoff_mass", Mission.Takeoff.FINAL_MASS), - ("takeoff_range", Mission.Takeoff.GROUND_DISTANCE), - ("takeoff_vel", Mission.Takeoff.FINAL_VELOCITY), - ("takeoff_alt", Mission.Takeoff.FINAL_ALTITUDE), - ], - promotes_outputs=["takeoff_mass_con", "takeoff_range_con", - "takeoff_vel_con", "takeoff_alt_con"], - ) - - prob.model.connect('traj.climb.states:mass', - 'takeoff_constraints.climb_start_mass', src_indices=[0]) - prob.model.connect('traj.climb.states:range', - 'takeoff_constraints.climb_start_range', src_indices=[0]) - prob.model.connect('traj.climb.states:velocity', - 'takeoff_constraints.climb_start_vel', src_indices=[0]) - prob.model.connect('traj.climb.states:altitude', - 'takeoff_constraints.climb_start_alt', src_indices=[0]) - prob.model.connect(Mission.Takeoff.FINAL_MASS, 'traj.climb.initial_states:mass') prob.model.connect(Mission.Takeoff.GROUND_DISTANCE, - 'traj.climb.initial_states:range') - prob.model.connect(Mission.Takeoff.FINAL_VELOCITY, - 'traj.climb.initial_states:velocity') - prob.model.connect(Mission.Takeoff.FINAL_ALTITUDE, - 'traj.climb.initial_states:altitude') + 'traj.climb.initial_states:distance') prob.model.connect('traj.descent.states:mass', Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1]) # TODO: approach velocity should likely be connected - prob.model.connect('traj.descent.states:altitude', Mission.Landing.INITIAL_ALTITUDE, + prob.model.connect('traj.descent.control_values:altitude', Mission.Landing.INITIAL_ALTITUDE, src_indices=[-1]) ########################## @@ -446,60 +385,41 @@ def run_trajectory(sim=True): prob.set_val('traj.climb.t_initial', t_i_climb, units='s') prob.set_val('traj.climb.t_duration', t_duration_climb, units='s') - prob.set_val('traj.climb.states:altitude', climb.interp( - 'altitude', ys=[alt_i_climb, alt_f_climb]), units='m') - # prob.set_val('traj.climb.states:velocity', climb.interp('velocity', ys=[170, v_f_climb]), units='m/s') - prob.set_val('traj.climb.states:velocity', climb.interp( - 'velocity', ys=[v_i_climb, v_f_climb]), units='m/s') + prob.set_val('traj.climb.controls:altitude', climb.interp( + Dynamic.Mission.ALTITUDE, ys=[alt_i_climb, alt_f_climb]), units='m') + prob.set_val( + 'traj.climb.controls:mach', climb.interp( + Dynamic.Mission.MACH, ys=[mach_i_climb, mach_f_climb]), units='unitless') prob.set_val('traj.climb.states:mass', climb.interp( - 'mass', ys=[mass_i_climb, mass_f_climb]), units='kg') - prob.set_val('traj.climb.states:range', climb.interp( - 'range', ys=[range_i_climb, range_f_climb]), units='m') # nmi - - prob.set_val('traj.climb.controls:velocity_rate', - climb.interp('velocity_rate', ys=[0.25, 0.05]), - units='m/s**2') - prob.set_val('traj.climb.controls:throttle', - climb.interp('throttle', ys=[0.5, 0.5]), - units=None) + Dynamic.Mission.MASS, ys=[mass_i_climb, mass_f_climb]), units='kg') + prob.set_val('traj.climb.states:distance', climb.interp( + Dynamic.Mission.DISTANCE, ys=[distance_i_climb, distance_f_climb]), units='m') prob.set_val('traj.cruise.t_initial', t_i_cruise, units='s') prob.set_val('traj.cruise.t_duration', t_duration_cruise, units='s') - prob.set_val('traj.cruise.states:altitude', cruise.interp( - 'altitude', ys=[alt_i_cruise, alt_f_cruise]), units='m') - prob.set_val('traj.cruise.states:velocity', cruise.interp( - 'velocity', ys=[v_i_cruise, v_f_cruise]), units='m/s') + prob.set_val('traj.cruise.polynomial_controls:altitude', cruise.interp( + Dynamic.Mission.ALTITUDE, ys=[alt_i_cruise, alt_f_cruise]), units='m') + prob.set_val( + 'traj.cruise.polynomial_controls:mach', cruise.interp( + Dynamic.Mission.MACH, ys=[cruise_mach, cruise_mach]), units='unitless') prob.set_val('traj.cruise.states:mass', cruise.interp( - 'mass', ys=[mass_i_cruise, mass_f_cruise]), units='kg') - prob.set_val('traj.cruise.states:range', cruise.interp( - 'range', ys=[range_i_cruise, range_f_cruise]), units='m') # nmi - - prob.set_val('traj.cruise.controls:velocity_rate', - cruise.interp('velocity_rate', ys=[0.0, 0.0]), - units='m/s**2') - prob.set_val('traj.cruise.controls:throttle', - cruise.interp('throttle', ys=[0.5, 0.5]), - units=None) + Dynamic.Mission.MASS, ys=[mass_i_cruise, mass_f_cruise]), units='kg') + prob.set_val('traj.cruise.states:distance', cruise.interp( + Dynamic.Mission.DISTANCE, ys=[distance_i_cruise, distance_f_cruise]), units='m') prob.set_val('traj.descent.t_initial', t_i_descent, units='s') prob.set_val('traj.descent.t_duration', t_duration_descent, units='s') - prob.set_val('traj.descent.states:altitude', descent.interp( - 'altitude', ys=[alt_i_descent, alt_f_descent]), units='m') - prob.set_val('traj.descent.states:velocity', descent.interp( - 'velocity', ys=[v_i_descent, v_f_descent]), units='m/s') + prob.set_val('traj.descent.controls:altitude', descent.interp( + Dynamic.Mission.ALTITUDE, ys=[alt_i_descent, alt_f_descent]), units='m') + prob.set_val( + 'traj.descent.controls:mach', descent.interp( + Dynamic.Mission.MACH, ys=[mach_i_descent, mach_f_descent]), units='unitless') prob.set_val('traj.descent.states:mass', descent.interp( - 'mass', ys=[mass_i_descent, mass_f_descent]), units='kg') - prob.set_val('traj.descent.states:range', descent.interp( - 'range', ys=[range_i_descent, range_f_descent]), units='m') - - prob.set_val('traj.descent.controls:velocity_rate', - descent.interp('velocity_rate', ys=[-0.25, 0.05]), - units='m/s**2') - prob.set_val('traj.descent.controls:throttle', - descent.interp('throttle', ys=[0.0, 0.0]), - units=None) + Dynamic.Mission.MASS, ys=[mass_i_descent, mass_f_descent]), units='kg') + prob.set_val('traj.descent.states:distance', descent.interp( + Dynamic.Mission.DISTANCE, ys=[distance_i_descent, distance_f_descent]), units='m') # Turn off solver printing so that the SNOPT output is readable. prob.set_solver_print(level=0) @@ -518,34 +438,36 @@ def run_trajectory(sim=True): @use_tempdirs class ProblemPhaseTestCase(unittest.TestCase): + + @require_pyoptsparse(optimizer="SNOPT") def bench_test_sizing_N3CC(self): prob = run_trajectory(sim=False) times_climb = prob.get_val('traj.climb.timeseries.time', units='s') altitudes_climb = prob.get_val( - 'traj.climb.timeseries.states:altitude', units='m') - masses_climb = prob.get_val('traj.climb.timeseries.states:mass', units='kg') - ranges_climb = prob.get_val('traj.climb.timeseries.states:range', units='m') + 'traj.climb.timeseries.altitude', units='m') + masses_climb = prob.get_val('traj.climb.timeseries.mass', units='kg') + distances_climb = prob.get_val('traj.climb.timeseries.distance', units='m') velocities_climb = prob.get_val( - 'traj.climb.timeseries.states:velocity', units='m/s') + 'traj.climb.timeseries.velocity', units='m/s') thrusts_climb = prob.get_val('traj.climb.timeseries.thrust_net_total', units='N') times_cruise = prob.get_val('traj.cruise.timeseries.time', units='s') altitudes_cruise = prob.get_val( - 'traj.cruise.timeseries.states:altitude', units='m') - masses_cruise = prob.get_val('traj.cruise.timeseries.states:mass', units='kg') - ranges_cruise = prob.get_val('traj.cruise.timeseries.states:range', units='m') + 'traj.cruise.timeseries.altitude', units='m') + masses_cruise = prob.get_val('traj.cruise.timeseries.mass', units='kg') + distances_cruise = prob.get_val('traj.cruise.timeseries.distance', units='m') velocities_cruise = prob.get_val( - 'traj.cruise.timeseries.states:velocity', units='m/s') + 'traj.cruise.timeseries.velocity', units='m/s') thrusts_cruise = prob.get_val( 'traj.cruise.timeseries.thrust_net_total', units='N') times_descent = prob.get_val('traj.descent.timeseries.time', units='s') altitudes_descent = prob.get_val( - 'traj.descent.timeseries.states:altitude', units='m') - masses_descent = prob.get_val('traj.descent.timeseries.states:mass', units='kg') - ranges_descent = prob.get_val('traj.descent.timeseries.states:range', units='m') + 'traj.descent.timeseries.altitude', units='m') + masses_descent = prob.get_val('traj.descent.timeseries.mass', units='kg') + distances_descent = prob.get_val('traj.descent.timeseries.distance', units='m') velocities_descent = prob.get_val( - 'traj.descent.timeseries.states:velocity', units='m/s') + 'traj.descent.timeseries.velocity', units='m/s') thrusts_descent = prob.get_val( 'traj.descent.timeseries.thrust_net_total', units='N') @@ -572,14 +494,17 @@ def bench_test_sizing_N3CC(self): [57509.28816794], [57509.28816794], [57468.36184014], [57411.39726843], [57392.97463799], [57392.97463799], [57372.80054331], [57344.30023996], [57335.11578186]] - expected_ranges_m_climb = [[1459.66454213], [6138.11750563], [15269.84014039], - [18334.78691191], [18334.78691191], [34224.35398213], - [57056.15756331], [64259.82264908], [64259.82264908], - [85983.44219477], [116781.82372802], [126787.42506431], - [126787.42506431], [149770.216723], [181655.376959], - [191785.63505195], [191785.63505195], [210054.32022093], - [236459.08240245], [245218.76016711], [245218.76016711], - [254896.51199279], [268679.73823007], [273140.04867091]] + expected_distances_m_climb = [[1459.66454213], [6138.11750563], [15269.84014039], + [18334.78691191], [18334.78691191], [34224.35398213], + [57056.15756331], [64259.82264908], [64259.82264908], + [85983.44219477], [116781.82372802], [ + 126787.42506431], + [126787.42506431], [149770.216723], [181655.376959], + [191785.63505195], [191785.63505195], [ + 210054.32022093], + [236459.08240245], [245218.76016711], [ + 245218.76016711], + [254896.51199279], [268679.73823007], [273140.04867091]] expected_velocities_ms_climb = [[77.19291754], [132.35228283], [162.28279625], [166.11250634], [166.11250634], [176.30796789], [178.55049791], [178.67862048], [178.67862048], @@ -603,8 +528,8 @@ def bench_test_sizing_N3CC(self): [10668.], [10668.]] expected_masses_kg_cruise = [[57335.11578186], [53895.3524649], [49306.34176818], [47887.72131688]] - expected_ranges_m_cruise = [[273140.04867091], [2300136.11626779], - [5096976.9738027], [5982167.54261146]] + expected_distances_m_cruise = [[273140.04867091], [2300136.11626779], + [5096976.9738027], [5982167.54261146]] expected_velocities_ms_cruise = [[234.25795132], [234.25795132], [234.25795132], [234.25795132]] expected_thrusts_N_cruise = [[28998.46944214], [28027.44677784], @@ -631,18 +556,18 @@ def bench_test_sizing_N3CC(self): [47884.40804261], [47872.68009732], [47849.34258173], [47842.09391697], [47842.09391697], [47833.150133], [47820.60083267], [47816.69389115]] - expected_ranges_m_descent = [[5982167.54261146], [5998182.9986382], [6017437.15474761], - [6023441.80485498], [6023441.80485498], [ - 6050450.80904601], - [6085202.09693457], [6095472.55993089], [ - 6095472.55993089], - [6122427.64793619], [6158176.53029461], [ - 6169433.26612421], - [6169433.26612421], [6191073.79296936], [ - 6220839.65410345], - [6230195.89769052], [6230195.89769052], [ - 6240573.15704622], - [6253740.15118502], [6257352.4]] + expected_distances_m_descent = [[5982167.54261146], [5998182.9986382], [6017437.15474761], + [6023441.80485498], [6023441.80485498], [ + 6050450.80904601], + [6085202.09693457], [6095472.55993089], [ + 6095472.55993089], + [6122427.64793619], [6158176.53029461], [ + 6169433.26612421], + [6169433.26612421], [6191073.79296936], [ + 6220839.65410345], + [6230195.89769052], [6230195.89769052], [ + 6240573.15704622], + [6253740.15118502], [6257352.4]] expected_velocities_ms_descent = [[234.25795132], [197.64415171], [182.5029101], [181.15994177], [181.15994177], [172.42254637], [156.92424445], [152.68023428], [152.68023428], @@ -660,21 +585,21 @@ def bench_test_sizing_N3CC(self): expected_times_s_climb = np.array(expected_times_s_climb) expected_altitudes_m_climb = np.array(expected_altitudes_m_climb) expected_masses_kg_climb = np.array(expected_masses_kg_climb) - expected_ranges_m_climb = np.array(expected_ranges_m_climb) + expected_distances_m_climb = np.array(expected_distances_m_climb) expected_velocities_ms_climb = np.array(expected_velocities_ms_climb) expected_thrusts_N_climb = np.array(expected_thrusts_N_climb) expected_times_s_cruise = np.array(expected_times_s_cruise) expected_altitudes_m_cruise = np.array(expected_altitudes_m_cruise) expected_masses_kg_cruise = np.array(expected_masses_kg_cruise) - expected_ranges_m_cruise = np.array(expected_ranges_m_cruise) + expected_distances_m_cruise = np.array(expected_distances_m_cruise) expected_velocities_ms_cruise = np.array(expected_velocities_ms_cruise) expected_thrusts_N_cruise = np.array(expected_thrusts_N_cruise) expected_times_s_descent = np.array(expected_times_s_descent) expected_altitudes_m_descent = np.array(expected_altitudes_m_descent) expected_masses_kg_descent = np.array(expected_masses_kg_descent) - expected_ranges_m_descent = np.array(expected_ranges_m_descent) + expected_distances_m_descent = np.array(expected_distances_m_descent) expected_velocities_ms_descent = np.array(expected_velocities_ms_descent) expected_thrusts_N_descent = np.array(expected_thrusts_N_descent) @@ -688,8 +613,8 @@ def bench_test_sizing_N3CC(self): expected_masses_kg_descent[-1], tolerance=rtol) # Range at the end of Descent - assert_near_equal(ranges_descent[-1], - expected_ranges_m_descent[-1], tolerance=rtol) + assert_near_equal(distances_descent[-1], + expected_distances_m_descent[-1], tolerance=rtol) # Flight time assert_near_equal(times_descent[-1], @@ -715,8 +640,8 @@ def bench_test_sizing_N3CC(self): times_climb, masses_climb, expected_times_s_climb, expected_masses_kg_climb, abs_tolerance=atol, rel_tolerance=rtol) warn_timeseries_near_equal( - times_climb, ranges_climb, expected_times_s_climb, - expected_ranges_m_climb, abs_tolerance=atol, rel_tolerance=rtol) + times_climb, distances_climb, expected_times_s_climb, + expected_distances_m_climb, abs_tolerance=atol, rel_tolerance=rtol) warn_timeseries_near_equal( times_climb, velocities_climb, expected_times_s_climb, expected_velocities_ms_climb, abs_tolerance=atol, rel_tolerance=rtol) @@ -732,8 +657,8 @@ def bench_test_sizing_N3CC(self): times_cruise, masses_cruise, expected_times_s_cruise, expected_masses_kg_cruise, abs_tolerance=atol, rel_tolerance=rtol) warn_timeseries_near_equal( - times_cruise, ranges_cruise, expected_times_s_cruise, - expected_ranges_m_cruise, abs_tolerance=atol, rel_tolerance=rtol) + times_cruise, distances_cruise, expected_times_s_cruise, + expected_distances_m_cruise, abs_tolerance=atol, rel_tolerance=rtol) warn_timeseries_near_equal( times_cruise, velocities_cruise, expected_times_s_cruise, expected_velocities_ms_cruise, abs_tolerance=atol, rel_tolerance=rtol) @@ -750,8 +675,8 @@ def bench_test_sizing_N3CC(self): times_descent, masses_descent, expected_times_s_descent, expected_masses_kg_descent, abs_tolerance=atol, rel_tolerance=rtol) warn_timeseries_near_equal( - times_descent, ranges_descent, expected_times_s_descent, - expected_ranges_m_descent, abs_tolerance=atol, rel_tolerance=rtol) + times_descent, distances_descent, expected_times_s_descent, + expected_distances_m_descent, abs_tolerance=atol, rel_tolerance=rtol) warn_timeseries_near_equal( times_descent, velocities_descent, expected_times_s_descent, expected_velocities_ms_descent, abs_tolerance=atol, rel_tolerance=rtol) diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_landing.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_landing.py index 512b710d8..bc4b16846 100644 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_landing.py +++ b/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_landing.py @@ -17,15 +17,12 @@ landing_trajectory_builder as _landing_trajectory_builder, landing_fullstop_user_options as _landing_fullstop_user_options) -from aviary.variable_info.variables import Dynamic as _Dynamic +from aviary.variable_info.variables import Dynamic from aviary.interface.default_phase_info.height_energy import default_premission_subsystems from aviary.utils.preprocessors import preprocess_crewpayload from aviary.variable_info.variables_in import VariablesIn -Dynamic = _Dynamic.Mission - - @use_tempdirs class TestFLOPSDetailedLanding(unittest.TestCase): # @require_pyoptsparse(optimizer='IPOPT') @@ -87,10 +84,11 @@ def _do_run(self, driver: Driver, optimizer, *args): landing_trajectory_builder.build_trajectory( aviary_options=aviary_options, model=landing.model, traj=traj) - max_range, units = landing_fullstop_user_options.get_item('max_range') + distance_max, units = landing_fullstop_user_options.get_item('distance_max') fullstop = landing_trajectory_builder.get_phase('landing_fullstop') - fullstop.add_objective(Dynamic.RANGE, loc='final', ref=max_range, units=units) + fullstop.add_objective(Dynamic.Mission.DISTANCE, loc='final', + ref=distance_max, units=units) landing.model.add_subsystem( 'input_sink', @@ -123,7 +121,7 @@ def _do_run(self, driver: Driver, optimizer, *args): desired = 3409.47 # ft actual = landing.model.get_val( - 'traj.landing_fullstop.states:range', units='ft')[-1] + 'traj.landing_fullstop.states:distance', units='ft')[-1] assert_near_equal(actual, desired, 0.05) diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_takeoff.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_takeoff.py index bc403ccbf..e0752de37 100644 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_takeoff.py +++ b/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_takeoff.py @@ -17,15 +17,12 @@ takeoff_trajectory_builder as _takeoff_trajectory_builder, takeoff_liftoff_user_options as _takeoff_liftoff_user_options) -from aviary.variable_info.variables import Aircraft, Dynamic as _Dynamic +from aviary.variable_info.variables import Aircraft, Dynamic from aviary.interface.default_phase_info.height_energy import default_premission_subsystems from aviary.utils.preprocessors import preprocess_crewpayload from aviary.variable_info.variables_in import VariablesIn -Dynamic = _Dynamic.Mission - - @use_tempdirs class TestFLOPSDetailedTakeoff(unittest.TestCase): @require_pyoptsparse(optimizer='IPOPT') @@ -93,11 +90,11 @@ def _do_run(self, driver: Driver, optimizer, *args): takeoff_trajectory_builder.build_trajectory( aviary_options=aviary_options, model=takeoff.model, traj=traj) - max_range, units = takeoff_liftoff_user_options.get_item('max_range') + distance_max, units = takeoff_liftoff_user_options.get_item('distance_max') liftoff = takeoff_trajectory_builder.get_phase('takeoff_liftoff') liftoff.add_objective( - Dynamic.RANGE, loc='final', ref=max_range, units=units) + Dynamic.Mission.DISTANCE, loc='final', ref=distance_max, units=units) # Insert a constraint for a fake decision speed, until abort is added. takeoff.model.add_constraint( @@ -140,7 +137,7 @@ def _do_run(self, driver: Driver, optimizer, *args): desired = 5649.9 # ft actual = takeoff.model.get_val( - 'traj.takeoff_liftoff.states:range', units='ft')[-1] + 'traj.takeoff_liftoff.states:distance', units='ft')[-1] assert_near_equal(actual, desired, 2e-2) diff --git a/aviary/validation_cases/benchmark_tests/test_bench_FwFm.py b/aviary/validation_cases/benchmark_tests/test_bench_FwFm.py index ef9c25759..cbd74f4df 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_FwFm.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_FwFm.py @@ -1,11 +1,9 @@ -from copy import deepcopy -import os import unittest import numpy as np from openmdao.utils.testing_utils import use_tempdirs +from openmdao.utils.testing_utils import require_pyoptsparse -from aviary.interface.default_phase_info.height_energy import phase_info from aviary.interface.methods_for_level1 import run_aviary from aviary.validation_cases.benchmark_utils import \ compare_against_expected_values @@ -13,11 +11,7 @@ @use_tempdirs class ProblemPhaseTestCase(unittest.TestCase): - def bench_test_swap_4_FwFm(self): - local_phase_info = deepcopy(phase_info) - prob = run_aviary('models/test_aircraft/aircraft_for_bench_FwFm.csv', - local_phase_info) - + def setUp(self): expected_dict = {} expected_dict['times'] = np.array([[120.], @@ -265,9 +259,112 @@ def bench_test_swap_4_FwFm(self): [116.22447082], [102.07377559]]) - compare_against_expected_values(prob, expected_dict) + self.expected_dict = expected_dict + + phase_info = { + "pre_mission": {"include_takeoff": True, "optimize_mass": True}, + "climb": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + 'fix_initial': False, + 'input_initial': True, + "optimize_mach": True, + "optimize_altitude": True, + "use_polynomial_control": False, + "num_segments": 6, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.3, "unitless"), + "final_mach": (0.79, "unitless"), + "mach_bounds": ((0.1, 0.8), "unitless"), + "initial_altitude": (35., "ft"), + "final_altitude": (35000.0, "ft"), + "altitude_bounds": ((0.0, 35000.0), "ft"), + "throttle_enforcement": "path_constraint", + "constrain_final": False, + "fix_duration": False, + "initial_bounds": ((0.0, 2.0), "min"), + "duration_bounds": ((5.0, 50.0), "min"), + "no_descent": False, + "add_initial_mass_constraint": True, + }, + "initial_guesses": {"times": ([0, 40.0], "min")}, + }, + "cruise": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + "optimize_mach": True, + "optimize_altitude": True, + "polynomial_control_order": 1, + "use_polynomial_control": True, + "num_segments": 1, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.79, "unitless"), + "final_mach": (0.79, "unitless"), + "mach_bounds": ((0.79, 0.79), "unitless"), + "initial_altitude": (35000.0, "ft"), + "final_altitude": (35000.0, "ft"), + "altitude_bounds": ((35000.0, 35000.0), "ft"), + "throttle_enforcement": "boundary_constraint", + "fix_initial": False, + "constrain_final": False, + "fix_duration": False, + "initial_bounds": ((64.0, 192.0), "min"), + "duration_bounds": ((60.0, 720.0), "min"), + }, + "initial_guesses": {"times": ([128, 113], "min")}, + }, + "descent": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + "optimize_mach": True, + "optimize_altitude": True, + "use_polynomial_control": False, + "num_segments": 5, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.79, "unitless"), + "final_mach": (0.3, "unitless"), + "mach_bounds": ((0.2, 0.8), "unitless"), + "initial_altitude": (35000.0, "ft"), + "final_altitude": (35.0, "ft"), + "altitude_bounds": ((0.0, 35000.0), "ft"), + "throttle_enforcement": "path_constraint", + "fix_initial": False, + "constrain_final": True, + "fix_duration": False, + "initial_bounds": ((120., 800.), "min"), + "duration_bounds": ((5.0, 35.0), "min"), + "no_climb": True, + }, + "initial_guesses": {"times": ([241, 30], "min")}, + }, + "post_mission": { + "include_landing": True, + "constrain_range": True, + "target_range": (3375.0, "nmi"), + }, + } + + self.phase_info = phase_info + + @require_pyoptsparse(optimizer="IPOPT") + def test_bench_FwFm(self): + prob = run_aviary( + 'models/test_aircraft/aircraft_for_bench_FwFm.csv', self.phase_info, max_iter=50, optimizer='IPOPT') + + compare_against_expected_values(prob, self.expected_dict) + + @require_pyoptsparse(optimizer="SNOPT") + def test_bench_FwFm_SNOPT(self): + prob = run_aviary( + 'models/test_aircraft/aircraft_for_bench_FwFm.csv', self.phase_info, max_iter=50, optimizer='SNOPT') + + compare_against_expected_values(prob, self.expected_dict) if __name__ == '__main__': test = ProblemPhaseTestCase() - test.bench_test_swap_4_FwFm() + test.setUp() + test.test_bench_FwFm_SNOPT() diff --git a/aviary/validation_cases/benchmark_tests/test_bench_FwGm.py b/aviary/validation_cases/benchmark_tests/test_bench_FwGm.py index 29dacddbf..81cd4f4c2 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_FwGm.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_FwGm.py @@ -1,5 +1,4 @@ from copy import deepcopy -import os import unittest from openmdao.utils.assert_utils import assert_near_equal @@ -13,32 +12,56 @@ @use_tempdirs class ProblemPhaseTestCase(unittest.TestCase): - @require_pyoptsparse(optimizer="SNOPT") + @require_pyoptsparse(optimizer="IPOPT") def bench_test_swap_3_FwGm(self): local_phase_info = deepcopy(phase_info) prob = run_aviary('models/test_aircraft/aircraft_for_bench_FwGm.csv', - local_phase_info) + local_phase_info, optimizer='IPOPT') + + rtol = 1e-2 + + # There are no truth values for these. + assert_near_equal(prob.get_val(Mission.Design.GROSS_MASS), + 186418., tolerance=rtol) + + assert_near_equal(prob.get_val(Aircraft.Design.OPERATING_MASS), + 104530., tolerance=rtol) + + assert_near_equal(prob.get_val(Mission.Summary.TOTAL_FUEL_MASS), + 44032., tolerance=rtol) + + assert_near_equal(prob.get_val('landing.' + Mission.Landing.GROUND_DISTANCE), + 2528., tolerance=rtol) + + assert_near_equal(prob.get_val("traj.desc2.timeseries.distance")[-1], + 3675.0, tolerance=rtol) + + @require_pyoptsparse(optimizer="SNOPT") + def bench_test_swap_3_FwGm_SNOPT(self): + local_phase_info = deepcopy(phase_info) + prob = run_aviary('models/test_aircraft/aircraft_for_bench_FwGm.csv', + local_phase_info, optimizer='SNOPT') rtol = 1e-2 # There are no truth values for these. assert_near_equal(prob.get_val(Mission.Design.GROSS_MASS), - 184426., tolerance=rtol) + 186418., tolerance=rtol) assert_near_equal(prob.get_val(Aircraft.Design.OPERATING_MASS), - 104215., tolerance=rtol) + 104530., tolerance=rtol) assert_near_equal(prob.get_val(Mission.Summary.TOTAL_FUEL_MASS), - 42355., tolerance=rtol) + 44032., tolerance=rtol) assert_near_equal(prob.get_val('landing.' + Mission.Landing.GROUND_DISTANCE), - 2544.3, tolerance=rtol) + 2528., tolerance=rtol) - assert_near_equal(prob.get_val("traj.desc2.timeseries.states:distance")[-1], + assert_near_equal(prob.get_val("traj.desc2.timeseries.distance")[-1], 3675.0, tolerance=rtol) if __name__ == "__main__": # unittest.main() test = ProblemPhaseTestCase() - test.bench_test_swap_3_FwGm() + test.bench_test_swap_3_FwGm_SNOPT() diff --git a/aviary/validation_cases/benchmark_tests/test_bench_GwFm.py b/aviary/validation_cases/benchmark_tests/test_bench_GwFm.py index a6748628f..2103bf9cc 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_GwFm.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_GwFm.py @@ -5,14 +5,12 @@ Computed Aero Large Single Aisle 1 data ''' -from copy import deepcopy -import os import unittest import numpy as np from openmdao.utils.testing_utils import use_tempdirs +from openmdao.utils.testing_utils import require_pyoptsparse -from aviary.interface.default_phase_info.height_energy import phase_info from aviary.interface.methods_for_level1 import run_aviary from aviary.validation_cases.benchmark_utils import \ compare_against_expected_values @@ -20,11 +18,7 @@ @use_tempdirs class ProblemPhaseTestCase(unittest.TestCase): - def bench_test_swap_1_GwFm(self): - local_phase_info = deepcopy(phase_info) - prob = run_aviary('models/test_aircraft/aircraft_for_bench_GwFm.csv', - local_phase_info) - + def setUp(self): expected_dict = {} expected_dict['times'] = np.array([[120.], [163.76271231], @@ -271,9 +265,112 @@ def bench_test_swap_1_GwFm(self): [116.34759903], [102.07377559]]) - compare_against_expected_values(prob, expected_dict) + self.expected_dict = expected_dict + + phase_info = { + "pre_mission": {"include_takeoff": True, "optimize_mass": True}, + "climb": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + 'fix_initial': False, + 'input_initial': True, + "optimize_mach": True, + "optimize_altitude": True, + "use_polynomial_control": False, + "num_segments": 6, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.2, "unitless"), + "final_mach": (0.79, "unitless"), + "mach_bounds": ((0.1, 0.8), "unitless"), + "initial_altitude": (0.0, "ft"), + "final_altitude": (35000.0, "ft"), + "altitude_bounds": ((0.0, 36000.0), "ft"), + "throttle_enforcement": "path_constraint", + "constrain_final": False, + "fix_duration": False, + "initial_bounds": ((0.0, 0.0), "min"), + "duration_bounds": ((5.0, 50.0), "min"), + "no_descent": True, + "add_initial_mass_constraint": True, + }, + "initial_guesses": {"times": ([0, 40.0], "min")}, + }, + "cruise": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + "optimize_mach": True, + "optimize_altitude": True, + "polynomial_control_order": 1, + "use_polynomial_control": True, + "num_segments": 1, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.79, "unitless"), + "final_mach": (0.79, "unitless"), + "mach_bounds": ((0.78, 0.8), "unitless"), + "initial_altitude": (35000.0, "ft"), + "final_altitude": (35000.0, "ft"), + "altitude_bounds": ((35000.0, 35000.0), "ft"), + "throttle_enforcement": "boundary_constraint", + "fix_initial": False, + "constrain_final": False, + "fix_duration": False, + "initial_bounds": ((64.0, 192.0), "min"), + "duration_bounds": ((60.0, 7200.0), "min"), + }, + "initial_guesses": {"times": ([128, 113], "min")}, + }, + "descent": { + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, + "user_options": { + "optimize_mach": True, + "optimize_altitude": True, + "use_polynomial_control": False, + "num_segments": 5, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.79, "unitless"), + "final_mach": (0.3, "unitless"), + "mach_bounds": ((0.2, 0.8), "unitless"), + "initial_altitude": (35000.0, "ft"), + "final_altitude": (500.0, "ft"), + "altitude_bounds": ((0.0, 35000.0), "ft"), + "throttle_enforcement": "path_constraint", + "fix_initial": False, + "constrain_final": True, + "fix_duration": False, + "initial_bounds": ((120.5, 361.5), "min"), + "duration_bounds": ((5.0, 30.0), "min"), + "no_climb": True, + }, + "initial_guesses": {"times": ([241, 30], "min")}, + }, + "post_mission": { + "include_landing": True, + "constrain_range": True, + "target_range": (3360.0, "nmi"), + }, + } + + self.phase_info = phase_info + + @require_pyoptsparse(optimizer="IPOPT") + def bench_test_swap_1_GwFm(self): + prob = run_aviary('models/test_aircraft/aircraft_for_bench_GwFm.csv', self.phase_info, + max_iter=50, optimizer='IPOPT') + + compare_against_expected_values(prob, self.expected_dict) + + @require_pyoptsparse(optimizer="SNOPT") + def bench_test_swap_1_GwFm_SNOPT(self): + prob = run_aviary('models/test_aircraft/aircraft_for_bench_GwFm.csv', self.phase_info, + max_iter=50, optimizer='SNOPT') + + compare_against_expected_values(prob, self.expected_dict) if __name__ == '__main__': - z = ProblemPhaseTestCase() - z.bench_test_swap_1_GwFm() + test = ProblemPhaseTestCase() + test.setUp() + test.bench_test_swap_1_GwFm_SNOPT() diff --git a/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py b/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py index d87013d8d..db7bde97d 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py @@ -1,5 +1,4 @@ from copy import deepcopy -import os import unittest from openmdao.utils.assert_utils import assert_near_equal @@ -13,11 +12,35 @@ @use_tempdirs class ProblemPhaseTestCase(unittest.TestCase): + @require_pyoptsparse(optimizer="IPOPT") + def test_bench_GwGm(self): + local_phase_info = deepcopy(phase_info) + prob = run_aviary('models/test_aircraft/aircraft_for_bench_GwGm.csv', + local_phase_info, optimizer='IPOPT') + + rtol = 0.01 + + # There are no truth values for these. + assert_near_equal(prob.get_val(Mission.Design.GROSS_MASS), + 174039., tolerance=rtol) + + assert_near_equal(prob.get_val(Aircraft.Design.OPERATING_MASS), + 95509, tolerance=rtol) + + assert_near_equal(prob.get_val(Mission.Summary.TOTAL_FUEL_MASS), + 42529., tolerance=rtol) + + assert_near_equal(prob.get_val('landing.' + Mission.Landing.GROUND_DISTANCE), + 2634.8, tolerance=rtol) + + assert_near_equal(prob.get_val("traj.desc2.timeseries.distance")[-1], + 3675.0, tolerance=rtol) + @require_pyoptsparse(optimizer="SNOPT") - def bench_test_swap_2_GwGm(self): + def test_bench_GwGm_SNOPT(self): local_phase_info = deepcopy(phase_info) prob = run_aviary('models/test_aircraft/aircraft_for_bench_GwGm.csv', - local_phase_info) + local_phase_info, optimizer='SNOPT') rtol = 0.01 @@ -34,11 +57,11 @@ def bench_test_swap_2_GwGm(self): assert_near_equal(prob.get_val('landing.' + Mission.Landing.GROUND_DISTANCE), 2634.8, tolerance=rtol) - assert_near_equal(prob.get_val("traj.desc2.timeseries.states:distance")[-1], + assert_near_equal(prob.get_val("traj.desc2.timeseries.distance")[-1], 3675.0, tolerance=rtol) if __name__ == '__main__': # unittest.main() test = ProblemPhaseTestCase() - test.bench_test_swap_2_GwGm() + test.test_bench_GwGm_SNOPT() diff --git a/aviary/validation_cases/benchmark_tests/test_full_mission_solved_level3.py b/aviary/validation_cases/benchmark_tests/test_full_mission_solved_level3.py index a479d8077..d194d8c5c 100644 --- a/aviary/validation_cases/benchmark_tests/test_full_mission_solved_level3.py +++ b/aviary/validation_cases/benchmark_tests/test_full_mission_solved_level3.py @@ -26,9 +26,9 @@ def bench_test_solved_full_mission(self): prob.load_inputs(input_file, local_phase_info) prob.aviary_inputs.set_val(Mission.Design.RANGE, 2000.0, units="NM") - # Have checks for clashing user inputs - # Raise warnings or errors depending on how clashing the issues are - prob.check_inputs() + +# Preprocess inputs + prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() diff --git a/aviary/validation_cases/benchmark_tests/test_full_mission_solved_ode.py b/aviary/validation_cases/benchmark_tests/test_full_mission_solved_ode.py index f09ffd6ed..372a67974 100644 --- a/aviary/validation_cases/benchmark_tests/test_full_mission_solved_ode.py +++ b/aviary/validation_cases/benchmark_tests/test_full_mission_solved_ode.py @@ -5,7 +5,6 @@ import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs -from packaging import version from aviary.mission.gasp_based.ode.params import ParamPort from aviary.variable_info.enums import SpeedType @@ -293,7 +292,7 @@ def run_mission(optimizer): phase.set_state_options("mass", rate_source="dmass_dv", fix_initial=True, fix_final=False, lower=1, upper=195_000, ref=takeoff_mass, defect_ref=takeoff_mass) - phase.set_state_options(Dynamic.Mission.RANGE, rate_source="over_a", + phase.set_state_options(Dynamic.Mission.DISTANCE, rate_source="over_a", fix_initial=True, fix_final=False, lower=0, upper=2000., ref=1.e2, defect_ref=1.e2) phase.add_parameter("t_init_gear", units="s", @@ -330,7 +329,7 @@ def run_mission(optimizer): static_target=False) phase.set_time_options(fix_initial=False, fix_duration=False, - units="range_units", name=Dynamic.Mission.RANGE, + units="distance_units", name=Dynamic.Mission.DISTANCE, duration_bounds=duration_bounds, duration_ref=duration_ref, initial_bounds=initial_bounds, initial_ref=initial_ref) @@ -425,7 +424,7 @@ def run_mission(optimizer): pass elif phase_name == "descent": phase.add_boundary_constraint( - Dynamic.Mission.RANGE, + Dynamic.Mission.DISTANCE, loc="final", equals=target_range, units="NM", @@ -465,8 +464,6 @@ def run_mission(optimizer): traj.add_phase(phase_name, phase) - phase.timeseries_options['use_prefix'] = True - traj.add_linkage_constraint(phase_a='ascent_to_gear_retract', phase_b='ascent_to_flap_retract', var_a='time', @@ -510,7 +507,7 @@ def run_mission(optimizer): traj.link_phases(phases[6:], vars=[Dynamic.Mission.ALTITUDE], ref=10.e3) traj.link_phases(phases, vars=['time'], ref=100.) traj.link_phases(phases, vars=['mass'], ref=10.e3) - traj.link_phases(phases, vars=[Dynamic.Mission.RANGE], units='m', ref=10.e3) + traj.link_phases(phases, vars=[Dynamic.Mission.DISTANCE], units='m', ref=10.e3) traj.link_phases(phases[:7], vars=['TAS'], units='kn', ref=200.) # traj.link_phases(phases[7:], vars=['TAS'], units='kn', ref=200.) @@ -588,11 +585,11 @@ def run_mission(optimizer): for idx, (phase_name, phase) in enumerate(traj._phases.items()): if phase_name != "groundroll": - range_initial = range_guesses[idx] + distance_initial = range_guesses[idx] p.set_val(f"traj.{phase_name}.t_initial", - range_initial, units='range_units') + distance_initial, units='distance_units') p.set_val(f"traj.{phase_name}.t_duration", - range_guesses[idx+1] - range_initial, units='range_units') + range_guesses[idx+1] - distance_initial, units='distance_units') p.set_val( f"traj.{phase_name}.polynomial_controls:altitude", @@ -641,9 +638,9 @@ def run_mission(optimizer): if phase_name == "groundroll": ranges.extend( - p.get_val(f"traj.{phase_name}.timeseries.states:range", units="m")[0]) + p.get_val(f"traj.{phase_name}.timeseries.distance", units="m")[0]) ranges.extend( - p.get_val(f"traj.{phase_name}.timeseries.states:range", units="m")[-1]) + p.get_val(f"traj.{phase_name}.timeseries.distance", units="m")[-1]) masses.extend( p.get_val(f"traj.{phase_name}.timeseries.mass", units="lbm")[0]) @@ -657,13 +654,13 @@ def run_mission(optimizer): TASs.extend( p.get_val(f"traj.{phase_name}.timeseries.TAS", units="kn")[-1]) else: - range_initial = p.get_val( - f"traj.{phase_name}.t_initial", units='range_units') + distance_initial = p.get_val( + f"traj.{phase_name}.t_initial", units='distance_units') if idx > 1: - ranges.extend(range_initial) + ranges.extend(distance_initial) if idx == (len(traj._phases) - 1): ranges.extend( - p.get_val(f"traj.{phase_name}.t_duration", units='range_units') + range_initial) + p.get_val(f"traj.{phase_name}.t_duration", units='distance_units') + distance_initial) masses.extend( p.get_val(f"traj.{phase_name}.timeseries.mass", units="lbm")[-1]) alts.extend( diff --git a/aviary/validation_cases/benchmark_tests/test_subsystems_within_a_mission.py b/aviary/validation_cases/benchmark_tests/test_subsystems_within_a_mission.py index d6c841e19..00a4b8514 100644 --- a/aviary/validation_cases/benchmark_tests/test_subsystems_within_a_mission.py +++ b/aviary/validation_cases/benchmark_tests/test_subsystems_within_a_mission.py @@ -1,7 +1,6 @@ import numpy as np import unittest -import pkg_resources from numpy.testing import assert_almost_equal from openmdao.utils.testing_utils import use_tempdirs @@ -15,48 +14,36 @@ @use_tempdirs class TestSubsystemsMission(unittest.TestCase): def setUp(self): - polar_file = "subsystems/aerodynamics/gasp_based/data/large_single_aisle_1_aero_free.txt" - aero_data = pkg_resources.resource_filename("aviary", polar_file) - self.phase_info = { 'pre_mission': { 'include_takeoff': False, 'external_subsystems': [ArrayGuessSubsystemBuilder(), AdditionalArrayGuessSubsystemBuilder()], - 'optimize_mass': False, + 'optimize_mass': True, }, 'cruise': { - 'subsystem_options': { - 'core_aerodynamics': {'method': 'solved_alpha', 'aero_data': aero_data, 'training_data': False} - }, + "subsystem_options": {"core_aerodynamics": {"method": "computed"}}, 'external_subsystems': [ArrayGuessSubsystemBuilder(), AdditionalArrayGuessSubsystemBuilder()], - 'user_options': { - 'fix_initial': True, - 'fix_final': False, - 'fix_duration': False, - 'num_segments': 1, - 'order': 5, - 'initial_ref': (1., 's'), - 'initial_bounds': ((0., 0.), 's'), - 'duration_ref': (21.e3, 's'), - 'duration_bounds': ((10.e3, 20.e3), 's'), - 'min_altitude': (10.668e3, 'm'), - 'max_altitude': (10.668e3, 'm'), - 'min_mach': 0.8, - 'max_mach': 0.8, - 'required_available_climb_rate': (1.524, 'm/s'), - 'input_initial': False, - 'mass_f_cruise': (1.e4, 'lbm'), - 'range_f_cruise': (1.e6, 'm'), + "user_options": { + "optimize_mach": False, + "optimize_altitude": False, + "polynomial_control_order": 1, + "num_segments": 2, + "order": 3, + "solve_for_distance": False, + "initial_mach": (0.72, "unitless"), + "final_mach": (0.72, "unitless"), + "mach_bounds": ((0.7, 0.74), "unitless"), + "initial_altitude": (35000.0, "ft"), + "final_altitude": (35000.0, "ft"), + "altitude_bounds": ((23000.0, 38000.0), "ft"), + "throttle_enforcement": "boundary_constraint", + "fix_initial": True, + "constrain_final": False, + "fix_duration": False, + "initial_bounds": ((0.0, 0.0), "min"), + "duration_bounds": ((10., 30.), "min"), }, - 'initial_guesses': { - 'times': ([0., 15000.], 's'), - 'altitude': ([35.e3, 35.e3], 'ft'), - 'velocity': ([455.49, 455.49], 'kn'), - 'mass': ([130.e3, 120.e3], 'lbm'), - 'range': ([0., 3000.], 'NM'), - 'velocity_rate': ([0., 0.], 'm/s**2'), - 'throttle': ([0.6, 0.6], 'unitless'), - } + "initial_guesses": {"times": ([0, 30], "min")}, }, 'post_mission': { 'include_landing': False, @@ -69,14 +56,10 @@ def test_subsystems_in_a_mission(self): prob = AviaryProblem() - csv_path = pkg_resources.resource_filename( - "aviary", "models/test_aircraft/aircraft_for_bench_GwFm.csv") + prob.load_inputs("models/test_aircraft/aircraft_for_bench_GwFm.csv", phase_info) - prob.load_inputs(csv_path, phase_info) - - # Have checks for clashing user inputs - # Raise warnings or errors depending on how clashing the issues are - prob.check_inputs() + # Preprocess inputs + prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() @@ -101,17 +84,18 @@ def test_subsystems_in_a_mission(self): # add an assert to see if the initial guesses are correct for Mission.Dummy.VARIABLE assert_almost_equal(prob[f'traj.phases.cruise.states:{Mission.Dummy.VARIABLE}'], [[10.], - [22.57838779], - [47.47686109], - [75.08412877], - [94.86062235], - [100.],]) + [25.97729616], + [48.02270384], + [55.], + [70.97729616], + [93.02270384], + [100.]]) prob.run_aviary_problem() # add an assert to see if MoreMission.Dummy.TIMESERIES_VAR was correctly added to the dymos problem assert_almost_equal(prob[f'traj.phases.cruise.timeseries.{MoreMission.Dummy.TIMESERIES_VAR}'], np.array( - [[0.5, 0.5, 0.5, 0.5, 0.5, 0.5]]).T) + [[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]]).T) def test_bad_initial_guess_key(self): phase_info = self.phase_info.copy() @@ -119,25 +103,19 @@ def test_bad_initial_guess_key(self): prob = AviaryProblem(reports=False) - csv_path = pkg_resources.resource_filename( - "aviary", "models/test_aircraft/aircraft_for_bench_GwFm.csv") - - prob.load_inputs(csv_path, phase_info) + prob.load_inputs("models/test_aircraft/aircraft_for_bench_GwFm.csv", phase_info) - # Have checks for clashing user inputs - # Raise warnings or errors depending on how clashing the issues are - prob.check_inputs() + # Preprocess inputs + prob.check_and_preprocess_inputs() prob.add_pre_mission_systems() with self.assertRaises(TypeError) as context: prob.add_phases() + print(str(context.exception)) self.assertTrue( - 'Cruise: cruise: unsupported initial guess: bad_guess_name' in str(context.exception)) + 'EnergyPhase: cruise: unsupported initial guess: bad_guess_name' in str(context.exception)) if __name__ == "__main__": - # unittest.main() - test = TestSubsystemsMission() - test.setUp() - test.test_subsystems_in_a_mission() + unittest.main() diff --git a/aviary/validation_cases/benchmark_utils.py b/aviary/validation_cases/benchmark_utils.py index 8540817a2..2cdd81c98 100644 --- a/aviary/validation_cases/benchmark_utils.py +++ b/aviary/validation_cases/benchmark_utils.py @@ -22,14 +22,18 @@ def compare_against_expected_values(prob, expected_dict): for idx, phase in enumerate(['climb', 'cruise', 'descent']): times.extend(prob.get_val(f'traj.{phase}.timeseries.time', units='s')) - altitudes.extend(prob.get_val( - f'traj.{phase}.timeseries.states:altitude', units='m')) + try: + altitudes.extend(prob.get_val( + f'traj.{phase}.timeseries.altitude', units='m')) + except KeyError: + altitudes.extend(prob.get_val( + f'traj.{phase}.timeseries.altitude', units='m')) + velocities.extend(prob.get_val( + f'traj.{phase}.timeseries.velocity', units='m/s')) masses.extend( - prob.get_val(f'traj.{phase}.timeseries.states:mass', units='kg')) + prob.get_val(f'traj.{phase}.timeseries.mass', units='kg')) ranges.extend( - prob.get_val(f'traj.{phase}.timeseries.states:range', units='m')) - velocities.extend(prob.get_val( - f'traj.{phase}.timeseries.states:velocity', units='m/s')) + prob.get_val(f'traj.{phase}.timeseries.distance', units='m')) times = np.array(times) altitudes = np.array(altitudes) diff --git a/aviary/validation_cases/validation_data/flops_data/full_mission_test_data.py b/aviary/validation_cases/validation_data/flops_data/full_mission_test_data.py index 7e09b2d2b..02aca9e40 100644 --- a/aviary/validation_cases/validation_data/flops_data/full_mission_test_data.py +++ b/aviary/validation_cases/validation_data/flops_data/full_mission_test_data.py @@ -123,7 +123,7 @@ data.set_val( # state_rates:range - Dynamic.Mission.RANGE_RATE, + Dynamic.Mission.DISTANCE_RATE, val=[163.776550884386, 232.775306059091, 117.631414542995, ], units='m/s', ) diff --git a/aviary/variable_info/enums.py b/aviary/variable_info/enums.py index 949253944..7978e4da3 100644 --- a/aviary/variable_info/enums.py +++ b/aviary/variable_info/enums.py @@ -59,8 +59,6 @@ class EquationsOfMotion(Enum): """ HEIGHT_ENERGY = 'height_energy' TWO_DEGREES_OF_FREEDOM = '2DOF' - # TODO these are a little out of place atm - SIMPLE = 'simple' SOLVED = 'solved' diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index 94d66ccdc..508cc0f62 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -1060,7 +1060,7 @@ add_meta_data( Aircraft.Design.IJEFF, meta_data=_MetaData, - historical_name={"GASP": 'INGASP.ijeff', + historical_name={"GASP": 'INGASP.IJEFF', "FLOPS": None, "LEAPS1": None }, @@ -1195,16 +1195,31 @@ ) add_meta_data( - Aircraft.Design.RESERVES, + Aircraft.Design.RESERVE_FUEL_ADDITIONAL, meta_data=_MetaData, historical_name={"GASP": 'INGASP.FRESF', "FLOPS": None, "LEAPS1": None }, option=True, + units="lbm", + desc='required fuel reserves: directly in lbm', + default_value=0, +) + +add_meta_data( + Aircraft.Design.RESERVE_FUEL_FRACTION, + meta_data=_MetaData, + historical_name={"GASP": None, + "FLOPS": None, + "LEAPS1": None + }, + option=True, units="unitless", - desc='required fuel reserves: given either as a proportion of mission fuel' - '(<0) or directly in lbf (>10)', + desc='required fuel reserves: given as a proportion of mission fuel. This value must be nonnegative.\ + If it is 0.5, the reserve fuel is half of the mission fuel (one third of the total fuel). Note\ + it can be greater than 1. If it is 2, there would be twice as much reserve fuel as mission fuel\ + (the total fuel carried would be 1/3 for the mission and 2/3 for the reserve)', default_value=0, ) @@ -5853,7 +5868,7 @@ Dynamic.Mission.DISTANCE, meta_data=_MetaData, historical_name={"GASP": None, - "FLOPS": None, + "FLOPS": 'range', "LEAPS1": None }, units='NM', @@ -5864,7 +5879,7 @@ Dynamic.Mission.DISTANCE_RATE, meta_data=_MetaData, historical_name={"GASP": None, - "FLOPS": None, + "FLOPS": 'range_rate', "LEAPS1": None }, units='NM/s', @@ -6078,29 +6093,6 @@ desc='Current total rate of nitrous oxide (NOx) production by the vehicle' ) -add_meta_data( - Dynamic.Mission.RANGE, - meta_data=_MetaData, - historical_name={"GASP": None, - "FLOPS": None, - "LEAPS1": None - }, - units='nm', - desc='Current cumulative ground distance the vehicle has flown' -) - -add_meta_data( - Dynamic.Mission.RANGE_RATE, - meta_data=_MetaData, - historical_name={"GASP": None, - "FLOPS": None, - "LEAPS1": None - }, - units='nm/s', - desc='Current rate of change in cumulative ground distance (ground velocity) for ' - 'the vehicle' -) - add_meta_data( Dynamic.Mission.SPECIFIC_ENERGY_RATE, meta_data=_MetaData, @@ -7067,6 +7059,18 @@ default_value=35.0, ) +add_meta_data( + Mission.Takeoff.FINAL_MACH, + meta_data=_MetaData, + historical_name={"GASP": None, + "FLOPS": None, + "LEAPS1": None, + }, + units='unitless', + desc='Mach number of aircraft after taking off and ' + 'clearing a 35 foot obstacle' +) + add_meta_data( Mission.Takeoff.FINAL_MASS, meta_data=_MetaData, diff --git a/aviary/variable_info/variables.py b/aviary/variable_info/variables.py index a64d774f4..ad4c9f6f0 100644 --- a/aviary/variable_info/variables.py +++ b/aviary/variable_info/variables.py @@ -132,7 +132,7 @@ class Design: FINENESS = 'aircraft:design:fineness' FIXED_EQUIPMENT_MASS = 'aircraft:design:fixed_equipment_mass' FIXED_USEFUL_LOAD = 'aircraft:design:fixed_useful_load' - IJEFF = 'aircraft:design:ijeff' + IJEFF = 'ijeff' LAMINAR_FLOW_LOWER = 'aircraft:design:laminar_flow_lower' LAMINAR_FLOW_UPPER = 'aircraft:design:laminar_flow_upper' @@ -148,7 +148,8 @@ class Design: MAX_STRUCTURAL_SPEED = 'aircraft:design:max_structural_speed' OPERATING_MASS = 'aircraft:design:operating_mass' PART25_STRUCTURAL_CATEGORY = 'aircraft:design:part25_structural_category' - RESERVES = 'aircraft:design:reserves' + RESERVE_FUEL_ADDITIONAL = 'aircraft:design:reserve_fuel_additional' + RESERVE_FUEL_FRACTION = 'aircraft:design:reserve_fuel_fraction' SMOOTH_MASS_DISCONTINUITIES = 'aircraft:design:smooth_mass_discontinuities' STATIC_MARGIN = 'aircraft:design:static_margin' STRUCTURAL_MASS_INCREMENT = 'aircraft:design:structural_mass_increment' @@ -598,8 +599,6 @@ class Mission: MASS_RATE = 'mass_rate' NOX_RATE = 'nox_rate' NOX_RATE_TOTAL = 'nox_rate_total' - RANGE = 'range' - RANGE_RATE = 'range_rate' SPECIFIC_ENERGY = 'specific_energy' SPECIFIC_ENERGY_RATE = 'specific_energy_rate' SPECIFIC_ENERGY_RATE_EXCESS = 'specific_energy_rate_excess' @@ -709,6 +708,7 @@ class Takeoff: DRAG_COEFFICIENT_MIN = 'mission:takeoff:drag_coefficient_min' FIELD_LENGTH = 'mission:takeoff:field_length' FINAL_ALTITUDE = 'mission:takeoff:final_altitude' + FINAL_MACH = 'mission:takeoff:final_mach' FINAL_MASS = 'mission:takeoff:final_mass' FINAL_VELOCITY = 'mission:takeoff:final_velocity' FUEL_SIMPLE = 'mission:takeoff:fuel_simple' diff --git a/aviary/visualization/assets/aviary_vars/script.js b/aviary/visualization/assets/aviary_vars/script.js index 36d48f3ec..17ed2cbab 100755 --- a/aviary/visualization/assets/aviary_vars/script.js +++ b/aviary/visualization/assets/aviary_vars/script.js @@ -56,6 +56,12 @@ function createTabulator(tableData) width: 300, tooltip: (e, cell, onRendered) => displayValueInToolTip(e, cell, onRendered), }, + { + title: "Units", + field: "units", + width: 200, + tooltip: (e, cell, onRendered) => displayValueInToolTip(e, cell, onRendered), + }, ] }); diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 39699abd2..3d713903d 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -17,11 +17,10 @@ import openmdao.api as om from openmdao.utils.general_utils import env_truthy -pn.extension(sizing_mode='stretch_width') +pn.extension(sizing_mode="stretch_width") # Constants - # Can't get using CSS to work with frames and the raw_css for the template so going with # this for now -iframe_css = "width=100% height=4000vh overflow=hidden margin=0px padding=0px border=none" aviary_variables_json_file_name = 'aviary_vars.json' @@ -35,21 +34,41 @@ def _dashboard_setup_parser(parser): The parser we're adding options to. """ parser.add_argument( - 'script_name', + "script_name", type=str, - help='Name of aviary script that was run (not including .py).' + help="Name of aviary script that was run (not including .py).", ) - parser.add_argument('--problem_recorder', type=str, help="Problem case recorder file name", - dest='problem_recorder', default='aviary_history.db') - parser.add_argument('--driver_recorder', type=str, help="Driver case recorder file name", - dest='driver_recorder', default=None) - parser.add_argument('--port', dest='port', type=int, - default=5000, help="dashboard server port ID (default is 5000)") + parser.add_argument( + "--problem_recorder", + type=str, + help="Problem case recorder file name", + dest="problem_recorder", + default="aviary_history.db", + ) + parser.add_argument( + "--driver_recorder", + type=str, + help="Driver case recorder file name", + dest="driver_recorder", + default=None, + ) + parser.add_argument( + "--port", + dest="port", + type=int, + default=5000, + help="dashboard server port ID (default is 5000)", + ) # For future use - parser.add_argument('-d', '--debug', action='store_true', dest='debug_output', - help="show debugging output") + parser.add_argument( + "-d", + "--debug", + action="store_true", + dest="debug_output", + help="show debugging output", + ) def _dashboard_cmd(options, user_args): @@ -63,8 +82,12 @@ def _dashboard_cmd(options, user_args): user_args : list of str Args to be passed to the user script. """ - dashboard(options.script_name, options.problem_recorder, - options.driver_recorder, options.port) + dashboard( + options.script_name, + options.problem_recorder, + options.driver_recorder, + options.port, + ) def create_report_frame(format, text_filepath): @@ -81,21 +104,22 @@ def create_report_frame(format, text_filepath): Returns ------- pane : Panel.Pane or None - A Panel Pane object to be displayed in the dashboard. Or None if the file + A Panel Pane object to be displayed in the dashboard. Or None if the file does not exist. """ if os.path.exists(text_filepath): if format == 'html': + iframe_css = 'width=1200px height=800px overflow-x="scroll" overflow="scroll" margin=0px padding=0px border=20px frameBorder=20px scrolling="yes"' report_pane = pn.pane.HTML( f'') elif format in ['markdown', 'text']: with open(text_filepath, "rb") as f: file_text = f.read() # need to deal with some encoding errors - file_text = file_text.decode('latin-1') - if format == 'markdown': + file_text = file_text.decode("latin-1") + if format == "markdown": report_pane = pn.pane.Markdown(file_text) - elif format == 'text': + elif format == "text": report_pane = pn.pane.Markdown(f"```\n{file_text}\n```\n") else: raise RuntimeError(f"Report format of {format} is not supported.") @@ -109,8 +133,8 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): Create a JSON file with information about Aviary variables. The JSON file has one level of hierarchy of the variables. The file - is written to aviary_vars.json. That file is then read in by the - aviary/visualization/assets/aviary_vars/script.js script. That is inside the + is written to aviary_vars.json. That file is then read in by the + aviary/visualization/assets/aviary_vars/script.js script. That is inside the aviary/visualization/assets/aviary_vars/index.html file that is embedded in the dashboard. @@ -128,6 +152,9 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): """ cr = om.CaseReader(recorder_file) + if 'final' not in cr.list_cases(): + return None + case = cr.get_case('final') outputs = case.list_outputs(explicit=True, implicit=True, val=True, residuals=True, residuals_tol=None, @@ -139,7 +166,7 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): grouped = {} for s in sorted_abs_names: - prefix = s.split(':')[0] + prefix = s.split(":")[0] if prefix not in grouped: grouped[prefix] = [] grouped[prefix].append(s) @@ -153,8 +180,9 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): table_data_nested.append( { "abs_name": group_name, - "prom_name": outputs[var_info]['prom_name'], - "value": str(outputs[var_info]['val']), + "prom_name": outputs[var_info]["prom_name"], + "value": str(outputs[var_info]["val"]), + "units": str(outputs[var_info]["units"]), } ) else: @@ -165,8 +193,9 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): children_list.append( { "abs_name": children_name, - "prom_name": outputs[children_name]['prom_name'], - "value": str(outputs[children_name]['val']), + "prom_name": outputs[children_name]["prom_name"], + "value": str(outputs[children_name]["val"]), + "units": str(outputs[children_name]["units"]), } ) table_data_nested.append( # not a real var, just a group of vars so no values @@ -174,12 +203,15 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): "abs_name": group_name, "prom_name": "", "value": "", + "units": "", "_children": children_list, } ) - aviary_variables_file_path = f'reports/{script_name}/aviary_vars/{aviary_variables_json_file_name}' - with open(aviary_variables_file_path, 'w') as fp: + aviary_variables_file_path = ( + f"reports/{script_name}/aviary_vars/{aviary_variables_json_file_name}" + ) + with open(aviary_variables_file_path, "w") as fp: json.dump(table_data_nested, fp) return table_data_nested @@ -195,7 +227,7 @@ def convert_case_recorder_file_to_df(recorder_file_name): Name of the case recorder file. """ cr = om.CaseReader(recorder_file_name) - driver_cases = cr.list_cases('driver', out_stream=None) + driver_cases = cr.list_cases("driver", out_stream=None) df = None for i, case in enumerate(driver_cases): @@ -230,7 +262,9 @@ def convert_case_recorder_file_to_df(recorder_file_name): df = pd.DataFrame(columns=header) # Now fill up a row - row = [i,] + row = [ + i, + ] # important to do in this order since that is the order added to the header for varname in objectives_names: value = objectives[varname] @@ -267,36 +301,42 @@ def dashboard(script_name, problem_recorder, driver_recorder, port): driver_recorder : str Name of the recorder file containing the Driver cases. """ - reports_dir = f'reports/{script_name}/' + reports_dir = f"reports/{script_name}/" - # TODO - use lists and functions to do this with a lot less code + if not Path(reports_dir).is_dir(): + raise ValueError( + f"The script name, '{script_name}', does not have a reports folder associated with it. " + f"The directory '{reports_dir}' does not exist." + ) + # TODO - use lists and functions to do this with a lot less code ####### Model Tab ####### model_tabs_list = [] # Inputs - inputs_pane = create_report_frame('html', f'{reports_dir}/inputs.html') + inputs_pane = create_report_frame("html", f"{reports_dir}/inputs.html") if inputs_pane: model_tabs_list.append(('Inputs', inputs_pane)) # Debug Input List - input_list_pane = create_report_frame('text', 'input_list.txt') + input_list_pane = create_report_frame("text", "input_list.txt") if input_list_pane: model_tabs_list.append(('Debug Input List', input_list_pane)) # Debug Output List - output_list_pane = create_report_frame('text', 'output_list.txt') + output_list_pane = create_report_frame("text", "output_list.txt") if output_list_pane: model_tabs_list.append(('Debug Output List', output_list_pane)) # N2 - n2_pane = create_report_frame('html', f'{reports_dir}/n2.html') + n2_pane = create_report_frame("html", f"{reports_dir}/n2.html") if n2_pane: model_tabs_list.append(('N2', n2_pane)) # Trajectory Linkage - traj_linkage_report_pane = create_report_frame('html', - f'{reports_dir}/traj_linkage_report.html') + traj_linkage_report_pane = create_report_frame( + "html", f"{reports_dir}/traj_linkage_report.html" + ) if traj_linkage_report_pane: model_tabs_list.append(('Trajectory Linkage Report', traj_linkage_report_pane)) @@ -305,95 +345,110 @@ def dashboard(script_name, problem_recorder, driver_recorder, port): optimization_tabs_list = [] # Driver scaling - driver_scaling_report_pane = create_report_frame('html', - f'{reports_dir}/driver_scaling_report.html') + driver_scaling_report_pane = create_report_frame( + "html", f"{reports_dir}/driver_scaling_report.html" + ) if driver_scaling_report_pane: - optimization_tabs_list.append(('Driver Scaling Report', - driver_scaling_report_pane)) + optimization_tabs_list.append( + ("Driver Scaling Report", driver_scaling_report_pane) + ) # Coloring report - coloring_report_pane = create_report_frame('html', - f'{reports_dir}/total_coloring.html') + coloring_report_pane = create_report_frame( + "html", f"{reports_dir}/total_coloring.html" + ) if coloring_report_pane: - optimization_tabs_list.append(('Total Coloring Report', - coloring_report_pane)) + optimization_tabs_list.append(("Total Coloring Report", coloring_report_pane)) # Optimization report - opt_report_pane = create_report_frame('html', - f'{reports_dir}/opt_report.html') + opt_report_pane = create_report_frame("html", f"{reports_dir}/opt_report.html") if opt_report_pane: - optimization_tabs_list.append(('Optimization Report', - opt_report_pane)) + optimization_tabs_list.append(("Optimization Report", opt_report_pane)) # IPOPT report - ipopt_pane = create_report_frame('text', - f'{reports_dir}/IPOPT.out') + ipopt_pane = create_report_frame("text", f"{reports_dir}/IPOPT.out") if ipopt_pane: - optimization_tabs_list.append(('IPOPT Output', ipopt_pane)) + optimization_tabs_list.append(("IPOPT Output", ipopt_pane)) # SNOPT report - snopt_pane = create_report_frame('text', - f'{reports_dir}/SNOPT_print.out') + snopt_pane = create_report_frame("text", f"{reports_dir}/SNOPT_print.out") if snopt_pane: - optimization_tabs_list.append(('SNOPT Output', snopt_pane)) + optimization_tabs_list.append(("SNOPT Output", snopt_pane)) # SNOPT summary - snopt_summary_pane = create_report_frame('text', - f'{reports_dir}/SNOPT_summary.out') + snopt_summary_pane = create_report_frame("text", f"{reports_dir}/SNOPT_summary.out") if snopt_summary_pane: - optimization_tabs_list.append(('SNOPT Summary', snopt_summary_pane)) + optimization_tabs_list.append(("SNOPT Summary", snopt_summary_pane)) # PyOpt report - pyopt_solution_pane = create_report_frame('text', - f'{reports_dir}/pyopt_solution.txt') + pyopt_solution_pane = create_report_frame( + "text", f"{reports_dir}/pyopt_solution.txt" + ) if pyopt_solution_pane: - optimization_tabs_list.append(('PyOpt Solution', pyopt_solution_pane)) + optimization_tabs_list.append(("PyOpt Solution", pyopt_solution_pane)) # Desvars, cons, opt interactive plot if driver_recorder: if os.path.exists(driver_recorder): - df = convert_case_recorder_file_to_df(f'{driver_recorder}') + df = convert_case_recorder_file_to_df(f"{driver_recorder}") if df is not None: variables = pn.widgets.CheckBoxGroup( name="Variables", options=list(df.columns), # just so all of them aren't plotted from the beginning. Skip the iter count - value=list(df.columns)[1:2] + value=list(df.columns)[1:2], ) ipipeline = df.interactive() - ihvplot = ipipeline.hvplot(y=variables, responsive=True, min_height=400, - color=list(Category10[10]), - yformatter="%.0f", - title="Model Optimization using OpenMDAO") + ihvplot = ipipeline.hvplot( + y=variables, + responsive=True, + min_height=400, + color=list(Category10[10]), + yformatter="%.0f", + title="Model Optimization using OpenMDAO", + ) optimization_plot_pane = pn.Column( pn.Row( pn.Column( variables, pn.VSpacer(height=30), pn.VSpacer(height=30), - width=300 + width=300, ), ihvplot.panel(), ) ) optimization_tabs_list.append( - ('Desvars, cons, opt', optimization_plot_pane)) + ("Desvars, cons, opt", optimization_plot_pane) + ) else: - optimization_tabs_list.append(('Desvars, cons, opt', pn.pane.Markdown( - f"# Recorder file '{driver_recorder}' does not have Driver case recordings"))) + optimization_tabs_list.append( + ( + "Desvars, cons, opt", + pn.pane.Markdown( + f"# Recorder file '{driver_recorder}' does not have Driver case recordings" + ), + ) + ) else: - optimization_tabs_list.append(('Desvars, cons, opt', pn.pane.Markdown( - f"# Recorder file '{driver_recorder}' not found"))) + optimization_tabs_list.append( + ( + "Desvars, cons, opt", + pn.pane.Markdown(f"# Recorder file '{driver_recorder}' not found"), + ) + ) ####### Results Tab ####### results_tabs_list = [] # Trajectory results - traj_results_report_pane = create_report_frame('html', - f'{reports_dir}/traj_results_report.html') + traj_results_report_pane = create_report_frame( + "html", f"{reports_dir}/traj_results_report.html" + ) if traj_results_report_pane: - results_tabs_list.append(('Trajectory Results Report', - traj_results_report_pane)) + results_tabs_list.append( + ("Trajectory Results Report", traj_results_report_pane) + ) # Make the Aviary variables table pane if problem_recorder: @@ -405,26 +460,32 @@ def dashboard(script_name, problem_recorder, driver_recorder, port): # copy index.html file to reports/script_name/aviary_vars/index.html aviary_dir = pathlib.Path(importlib.util.find_spec("aviary").origin).parent - shutil.copy(aviary_dir.joinpath( - 'visualization/assets/aviary_vars/index.html'), aviary_vars_dir.joinpath('index.html')) - shutil.copy(aviary_dir.joinpath( - 'visualization/assets/aviary_vars/script.js'), aviary_vars_dir.joinpath('script.js')) + shutil.copy( + aviary_dir.joinpath("visualization/assets/aviary_vars/index.html"), + aviary_vars_dir.joinpath("index.html"), + ) + shutil.copy( + aviary_dir.joinpath("visualization/assets/aviary_vars/script.js"), + aviary_vars_dir.joinpath("script.js"), + ) # copy script.js file to reports/script_name/aviary_vars/index.html. # mod the script.js file to point at the json file # create the json file and put it in reports/script_name/aviary_vars/aviary_vars.json create_aviary_variables_table_data_nested( - script_name, problem_recorder) # create the json file - aviary_vars_pane = create_report_frame('html', - f'{reports_dir}/aviary_vars/index.html') + script_name, problem_recorder + ) # create the json file + aviary_vars_pane = create_report_frame( + "html", f"{reports_dir}/aviary_vars/index.html" + ) - results_tabs_list.append(('Aviary Variables', aviary_vars_pane)) + results_tabs_list.append(("Aviary Variables", aviary_vars_pane)) ####### Subsystems Tab ####### subsystem_tabs_list = [] # Look through subsystems directory for markdown files - for md_file in Path(f'{reports_dir}subsystems').glob('*.md'): - example_subsystems_pane = create_report_frame('markdown', str(md_file)) + for md_file in Path(f"{reports_dir}subsystems").glob("*.md"): + example_subsystems_pane = create_report_frame("markdown", str(md_file)) subsystem_tabs_list.append((md_file.stem, example_subsystems_pane)) model_tabs = pn.Tabs(*model_tabs_list, stylesheets=['assets/aviary_styles.css']) @@ -445,43 +506,47 @@ def dashboard(script_name, problem_recorder, driver_recorder, port): tabs = pn.Tabs(*high_level_tabs, stylesheets=['assets/aviary_styles.css']) template = pn.template.FastListTemplate( - title=f'Aviary Dashboard for {script_name}', + title=f"Aviary Dashboard for {script_name}", logo="assets/aviary_logo.png", favicon="assets/aviary_logo.png", main=[tabs], accent_base_color="black", header_background="rgb(0, 212, 169)", - background_color='white', + background_color="white", theme=DefaultTheme, theme_toggle=False, main_layout=None, css_files=['assets/aviary_styles.css'] ) - if env_truthy('TESTFLO_RUNNING'): + if env_truthy("TESTFLO_RUNNING"): show = False threaded = True else: show = True threaded = False - assets_dir = pathlib.Path(importlib.util.find_spec( - "aviary").origin).parent.joinpath('visualization/assets/') - home_dir = '.' - server = pn.serve(template, port=port, address='localhost', - websocket_origin=f'localhost:{port}', - show=show, - threaded=threaded, - static_dirs={ - 'reports': reports_dir, - 'home': home_dir, - 'assets': assets_dir, - }) + assets_dir = pathlib.Path( + importlib.util.find_spec("aviary").origin + ).parent.joinpath("visualization/assets/") + home_dir = "." + server = pn.serve( + template, + port=port, + address="localhost", + websocket_origin=f"localhost:{port}", + show=show, + threaded=threaded, + static_dirs={ + "reports": reports_dir, + "home": home_dir, + "assets": assets_dir, + }, + ) server.stop() -if __name__ == '__main__': - # so we can get the files written to the repo top directory +if __name__ == "__main__": parser = argparse.ArgumentParser() _dashboard_setup_parser(parser) args = parser.parse_args() diff --git a/aviary/xdsm/__init__.py b/aviary/xdsm/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/aviary/xdsm/accel_xdsm.py b/aviary/xdsm/accel_xdsm.py deleted file mode 100644 index e833e5263..000000000 --- a/aviary/xdsm/accel_xdsm.py +++ /dev/null @@ -1,59 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -simplified = True - -# Create subsystem components -x.add_system("atmos", FUNC, [r"AtmosphereModel"]) -x.add_system("fc", FUNC, [r"\textbf{FlightConditions}"]) -x.add_system("weight", FUNC, ["MassToWeight"]) -x.add_system("aero", GROUP, ["CruiseAero", "(alpha~in)"]) -x.add_system("prop", GROUP, [r"Propulsion"]) -x.add_system("eom", FUNC, [r"\textbf{AccelEOM}"]) - -# create inputs -if simplified: - x.add_input("aero", ["aircraft:*"]) -else: - x.add_input("aero", [ - # Aircraft.Wing.AREA, - "aircraft:wing:*", - "aircraft:horizontal_tail:*", - "aircraft:vertical_tail:*", - "aircraft:fuselage:*", - "aircraft:design:*", - "aircraft:nacelle:*", - "aircraft:strut:*", - Mission.Design.LIFT_COEFFICIENT_MAX_FLAPS_UP, - ]) - -# create outputs -x.add_output("fc", ["EAS"], side="right") -x.add_output("aero", [Dynamic.Mission.LIFT, Dynamic.Mission.DRAG, "alpha"], side="right") -# for accel_ode, CruiseAero(output_alpha=True). So alpha is not an input. -x.add_output("eom", ["TAS_rate", "distance_rate"], side="right") - -# make connections -x.add_input("atmos", [Dynamic.Mission.ALTITUDE]) -x.add_input("fc", ["TAS"]) -x.add_input("prop", [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.THROTTLE, - Aircraft.Engine.SCALE_FACTOR, -]) -x.add_input("weight", [Dynamic.Mission.MASS]) -x.add_input("eom", [Dynamic.Mission.MASS, "TAS"]) -x.connect("atmos", "fc", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -# x.connect("atmos", "prop", [Dynamic.Mission.TEMPERATURE, Dynamic.Mission.STATIC_PRESSURE]) -x.connect("fc", "aero", [Dynamic.Mission.MACH, Dynamic.Mission.DYNAMIC_PRESSURE]) -x.connect("fc", "prop", [Dynamic.Mission.MACH]) -x.connect("weight", "aero", ["weight/lift_req"]) -x.connect("aero", "eom", [Dynamic.Mission.DRAG]) -# x.connect("aero", "balance", [Dynamic.Mission.LIFT]) -x.connect("prop", "eom", [Dynamic.Mission.THRUST_TOTAL]) - -# Do not run spec test on aero. -x.write("accel_xdsm") -x.write_sys_specs("accel_specs") diff --git a/aviary/xdsm/ascent_xdsm.py b/aviary/xdsm/ascent_xdsm.py deleted file mode 100644 index 687870af3..000000000 --- a/aviary/xdsm/ascent_xdsm.py +++ /dev/null @@ -1,69 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -# Create subsystem components -x.add_system("atmos", GROUP, ["AtmosphereModel"]) -x.add_system("fc", FUNC, ["FlightConditions"]) -x.add_system("prop", GROUP, ["Propulsion"]) -x.add_system("aero", GROUP, ["LowSpeedAero", "(alpha~in)"]) -x.add_system("eom", GROUP, [r"\textbf{AscentEOM}"]) - -# create inputs -x.add_input("atmos", [Dynamic.Mission.ALTITUDE]) -x.add_input("fc", ["TAS"]) -x.add_input("prop", [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.THROTTLE, - Aircraft.Engine.SCALE_FACTOR,]) -x.add_input("aero", [ - "alpha", - "t_curr", - "dt_flaps", - "dt_gear", - "t_init_flaps", - "t_init_gear", - "aircraft:*", - # Aircraft.Wing.FLAP_DEFLECTION_TAKEOFF, - # Aircraft.Wing.HEIGHT, - # Aircraft.Wing.SPAN, - # Aircraft.Wing.AREA, - Dynamic.Mission.ALTITUDE, - Mission.Takeoff.AIRPORT_ALTITUDE, - Mission.Design.GROSS_MASS, - # 'aero_ramps:flap_factor:initial_val', - # 'aero_ramps:gear_factor:initial_val', - # 'aero_ramps:flap_factor:final_val', - # 'aero_ramps:gear_factor:final_val', -],) -x.add_input("eom", [ - "alpha", - "TAS", - Dynamic.Mission.MASS, - Dynamic.Mission.FLIGHT_PATH_ANGLE, - Aircraft.Wing.INCIDENCE, -],) - -# make connections -x.connect("atmos", "fc", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -# x.connect("atmos", "prop", [Dynamic.Mission.TEMPERATURE, Dynamic.Mission.STATIC_PRESSURE]) -x.connect("fc", "prop", [Dynamic.Mission.MACH]) -x.connect("fc", "aero", [Dynamic.Mission.DYNAMIC_PRESSURE, Dynamic.Mission.MACH]) -x.connect("prop", "eom", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("aero", "eom", [Dynamic.Mission.LIFT, Dynamic.Mission.DRAG]) - -# create outputs -x.add_output("eom", [ - "TAS_rate", - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.DISTANCE_RATE, - "alpha_rate", - "normal_force", - "fuselage_pitch", - "load_factor", -], side="right",) - -x.write("ascent_xdsm") -x.write_sys_specs("ascent_specs") diff --git a/aviary/xdsm/climb_xdsm.py b/aviary/xdsm/climb_xdsm.py deleted file mode 100644 index 1e9dddadf..000000000 --- a/aviary/xdsm/climb_xdsm.py +++ /dev/null @@ -1,90 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -simplified = True - -# input_speed_type = "EAS" for both high altitude and low altitude -# analysis_scheme = COLLOCATION - -# Create subsystem components -x.add_system("atmos", FUNC, ["AtmosphereModel"]) -x.add_system("speeds", FUNC, [r"\textbf{SpeedConstraints}"]) -x.add_system("ks", FUNC, ["KSComp"]) -x.add_system("balance_speed", IFUNC, ["BalanceSpeed"]) -x.add_system("fc", FUNC, [r"\textbf{FlightConditions}"]) -x.add_system("prop", GROUP, ["Propulsion"]) -x.add_system("aero", GROUP, ["CruiseAero", "(alpha~in)"]) -x.add_system("eom", GROUP, [r"\textbf{ClimbEOM}"]) -x.add_system("balance_lift", IFUNC, ["BalanceLift"]) -x.add_system("constraints", FUNC, [r"\textbf{Constraints}"]) - -# create inputs -x.add_input("speeds", [Dynamic.Mission.MACH]) -x.add_input("constraints", [ - Dynamic.Mission.MASS, - Aircraft.Wing.AREA, - Aircraft.Wing.INCIDENCE -]) -x.add_input("balance_speed", ["EAS"]) -x.add_input("atmos", [Dynamic.Mission.ALTITUDE]) -if simplified: - x.add_input("aero", ["alpha", "aircraft:*"]) -else: - x.add_input("aero", [ - # Aircraft.Wing.AREA, - "aircraft:wing:*", - "aircraft:horizontal_tail:*", - "aircraft:vertical_tail:*", - "aircraft:fuselage:*", - "aircraft:design:*", - "aircraft:nacelle:*", - "aircraft:strut:*", - Mission.Design.LIFT_COEFFICIENT_MAX_FLAPS_UP, - ]) -x.add_input("prop", [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.THROTTLE, - Aircraft.Engine.SCALE_FACTOR, -]) -x.add_input("eom", [Dynamic.Mission.MASS]) - -# make connections -x.connect("atmos", "fc", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -# x.connect("atmos", "prop", [Dynamic.Mission.TEMPERATURE, Dynamic.Mission.STATIC_PRESSURE]) -x.connect("atmos", "constraints", ["rho"]) -x.connect("fc", "prop", [Dynamic.Mission.MACH]) -x.connect("fc", "aero", [Dynamic.Mission.DYNAMIC_PRESSURE, Dynamic.Mission.MACH]) -x.connect("fc", "eom", ["TAS"]) -x.connect("fc", "constraints", ["TAS"]) -x.connect("fc", "speeds", [Dynamic.Mission.MACH]) -x.connect("speeds", "ks", ["speed_constraint"]) -x.connect("ks", "balance_speed", ["KS"]) -x.connect("balance_speed", "fc", ["EAS"]) -x.connect("balance_speed", "speeds", ["EAS"]) -x.connect("aero", "eom", [Dynamic.Mission.DRAG]) -x.connect("aero", "constraints", ["CL_max"]) -x.connect("prop", "eom", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("eom", "constraints", [Dynamic.Mission.FLIGHT_PATH_ANGLE]) -x.connect("aero", "balance_lift", [Dynamic.Mission.LIFT]) -x.connect("eom", "balance_lift", ["required_lift"]) -x.connect("balance_lift", "constraints", ["alpha"]) -x.connect("balance_lift", "aero", ["alpha"]) - -# create outputs -x.add_output("eom", [ - Dynamic.Mission.ALTITUDE_RATE, - "distance_rate", - "required_lift", -], side="right") -x.add_output("constraints", ["theta", "TAS_violation"], side="right") - -x.add_output("aero", [ - Dynamic.Mission.LIFT, - Dynamic.Mission.DRAG, - "CL_max", -], side="right") - -x.write("climb_xdsm") -x.write_sys_specs("climb_specs") diff --git a/aviary/xdsm/computed_aero_xdsm.py b/aviary/xdsm/computed_aero_xdsm.py deleted file mode 100644 index d660d8e9e..000000000 --- a/aviary/xdsm/computed_aero_xdsm.py +++ /dev/null @@ -1,172 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, IGROUP, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -# src = "subsystems/flops_based/computed_aero_group.py" -simplified = False -show_outputs = True - -# Create subsystem components -x.add_system("mux", FUNC, ["MUX"]) -x.add_system("dynPress", FUNC, ["DynamicPressure"]) -x.add_system("lt_eq_wt", FUNC, ["LiftEqualsWeight"]) -x.add_system("deptDrag", FUNC, ["LiftDependentDrag"]) -x.add_system("induDrag", FUNC, ["InducedDrag"]) -x.add_system("compDrag", FUNC, ["CompressibilityDrag"]) -x.add_system("skinFric", FUNC, ["SkinFriction"]) -x.add_system("skinDrag", FUNC, ["SkinFrictionDrag"]) -x.add_system("cmpdDrag", GROUP, ["ComputedDrag"]) -x.add_system("buffLift", FUNC, ["BuffetLift"]) - -if simplified: - x.add_input("mux", ["InputValues"]) - x.add_input("dynPress", ["InputValues"]) - x.add_input("lt_eq_wt", ["InputValues"]) - x.add_input("deptDrag", ["InputValues"]) - x.add_input("induDrag", ["InputValues"]) - x.add_input("compDrag", ["InputValues"]) - x.add_input("skinFric", ["InputValues"]) - x.add_input("skinDrag", ["InputValues"]) - x.add_input("cmpdDrag", ["InputValues"]) - x.add_input("buffLift", ["InputValues"]) -else: - # Wing - x.add_input("mux", [ - Aircraft.Wing.WETTED_AREA, - Aircraft.Wing.FINENESS, - Aircraft.Wing.CHARACTERISTIC_LENGTH, - Aircraft.Wing.LAMINAR_FLOW_UPPER, - Aircraft.Wing.LAMINAR_FLOW_LOWER, - # Tail - Aircraft.HorizontalTail.WETTED_AREA, - Aircraft.HorizontalTail.FINENESS, - Aircraft.HorizontalTail.CHARACTERISTIC_LENGTH, - Aircraft.HorizontalTail.LAMINAR_FLOW_UPPER, - Aircraft.HorizontalTail.LAMINAR_FLOW_LOWER, - # Vertical Tail - Aircraft.VerticalTail.WETTED_AREA, - Aircraft.VerticalTail.FINENESS, - Aircraft.VerticalTail.CHARACTERISTIC_LENGTH, - Aircraft.VerticalTail.LAMINAR_FLOW_UPPER, - Aircraft.VerticalTail.LAMINAR_FLOW_LOWER, - # Fuselage - Aircraft.Fuselage.WETTED_AREA, - Aircraft.Fuselage.FINENESS, - Aircraft.Fuselage.CHARACTERISTIC_LENGTH, - Aircraft.Fuselage.LAMINAR_FLOW_UPPER, - Aircraft.Fuselage.LAMINAR_FLOW_LOWER, - # Engine - Aircraft.Nacelle.WETTED_AREA, - Aircraft.Nacelle.FINENESS, - Aircraft.Nacelle.CHARACTERISTIC_LENGTH, - Aircraft.Nacelle.LAMINAR_FLOW_UPPER, - Aircraft.Nacelle.LAMINAR_FLOW_LOWER, - ]) - - x.add_input("dynPress", [Dynamic.Mission.MACH, Dynamic.Mission.STATIC_PRESSURE]) - x.add_input("lt_eq_wt", [ - Aircraft.Wing.AREA, - Dynamic.Mission.MASS, - # Dynamic.Mission.DYNAMIC_PRESSURE, - ]) - x.add_input("deptDrag", [ - Dynamic.Mission.MACH, - # Dynamic.Mission.LIFT, - Dynamic.Mission.STATIC_PRESSURE, - Mission.Design.MACH, - Mission.Design.LIFT_COEFFICIENT, - Aircraft.Wing.AREA, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.MAX_CAMBER_AT_70_SEMISPAN, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD, - ]) - x.add_input("induDrag", [ - Dynamic.Mission.MACH, - # Dynamic.Mission.LIFT, - Dynamic.Mission.STATIC_PRESSURE, - Aircraft.Wing.AREA, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SPAN_EFFICIENCY_FACTOR, - Aircraft.Wing.SWEEP, - Aircraft.Wing.TAPER_RATIO, - ]) - x.add_input("compDrag", [ - Dynamic.Mission.MACH, - Mission.Design.MACH, - Aircraft.Design.BASE_AREA, - Aircraft.Wing.AREA, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.MAX_CAMBER_AT_70_SEMISPAN, - Aircraft.Wing.SWEEP, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD, - Aircraft.Fuselage.CROSS_SECTION, - Aircraft.Fuselage.DIAMETER_TO_WING_SPAN, - Aircraft.Fuselage.LENGTH_TO_DIAMETER, - ]) - x.add_input("skinFric", [ - Dynamic.Mission.MACH, - Dynamic.Mission.STATIC_PRESSURE, - Dynamic.Mission.TEMPERATURE, - # "characteristic_lengths", - ]) - x.add_input("skinDrag", [ - # "skin_friction_coeff", - # "Re", - Aircraft.Wing.AREA, - ]) - x.add_input("cmpdDrag", [ - # Dynamic.Mission.DYNAMIC_PRESSURE, - Dynamic.Mission.MACH, - Aircraft.Wing.AREA, - Aircraft.Design.ZERO_LIFT_DRAG_COEFF_FACTOR, - Aircraft.Design.LIFT_DEPENDENT_DRAG_COEFF_FACTOR, - Aircraft.Design.SUBSONIC_DRAG_COEFF_FACTOR, - Aircraft.Design.SUPERSONIC_DRAG_COEFF_FACTOR, - ]) - x.add_input("buffLift", [ - Dynamic.Mission.MACH, - Mission.Design.MACH, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.MAX_CAMBER_AT_70_SEMISPAN, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD, - ]) - -# make connections -x.connect("mux", "skinFric", ["characteristic_lengths"]) - -x.connect("mux", "skinDrag", [ - "wetted_areas", - "fineness_ratios", - "laminar_fractions_upper", - "laminar_fractions_lower", -]) - -x.connect("dynPress", "lt_eq_wt", [Dynamic.Mission.DYNAMIC_PRESSURE]) -x.connect("dynPress", "cmpdDrag", [Dynamic.Mission.DYNAMIC_PRESSURE]) -x.connect("lt_eq_wt", "deptDrag", [Dynamic.Mission.LIFT]) -x.connect("lt_eq_wt", "induDrag", [Dynamic.Mission.LIFT]) - -x.connect("skinFric", "skinDrag", ["skin_friction_coeff", "Re"]) -x.connect("deptDrag", "cmpdDrag", ["CD/pressure_drag_coeff"]) -x.connect("induDrag", "cmpdDrag", ["induced_drag_coeff"]) -x.connect("compDrag", "cmpdDrag", ["compress_drag_coeff"]) -x.connect("skinDrag", "cmpdDrag", ["skin_friction_drag_coeff"]) - -# create outputs -if show_outputs: - x.add_output("lt_eq_wt", ["CL", Dynamic.Mission.LIFT], side="right") - # x.add_output("skinFric", ["cf_iter", "wall_temp"], side="right") # don't see why needed - x.add_output("cmpdDrag", [ - "CDI", - "CD0", - "CD/drag_coefficient", - Dynamic.Mission.DRAG, - ], side="right") - x.add_output("buffLift", ["delta_CL_Before"], side="right") # DELCLB - -x.write("computed_aero_xdsm") -x.write_sys_specs("computed_aero_specs") diff --git a/aviary/xdsm/cruise_xdsm.py b/aviary/xdsm/cruise_xdsm.py deleted file mode 100644 index 73ff201cc..000000000 --- a/aviary/xdsm/cruise_xdsm.py +++ /dev/null @@ -1,64 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -simplified = True - -# Create subsystem components -x.add_system("atmos", FUNC, ["AtmosphereModel"]) -x.add_system("fc", FUNC, [r"\textbf{FlightConditions}"]) -x.add_system("weight", FUNC, ["MassToWeight"]) -x.add_system("aero", GROUP, ["CruiseAero", "(alpha~in)"]) -x.add_system("prop", GROUP, ["Propulsion"]) -x.add_system("thrust_bal", IFUNC, ["BalanceThrust"]) -x.add_system("breguet_eom", FUNC, [r"\textbf{BreguetEOM}"]) - -# create inputs - -if simplified: - x.add_input("aero", [Dynamic.Mission.MACH, "aircraft:*"]) -else: - x.add_input("aero", [ - # Aircraft.Wing.AREA, - "aircraft:wing:*", - "aircraft:horizontal_tail:*", - "aircraft:vertical_tail:*", - "aircraft:fuselage:*", - "aircraft:design:*", - "aircraft:nacelle:*", - "aircraft:strut:*", - Mission.Design.LIFT_COEFFICIENT_MAX_FLAPS_UP, - ]) -x.add_input("atmos", [Dynamic.Mission.ALTITUDE]) # alt_cruise -x.add_input("fc", [Dynamic.Mission.MACH]) -x.add_input("prop", [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MACH, - Dynamic.Mission.THROTTLE, - Aircraft.Engine.SCALE_FACTOR, -]) -x.add_input("breguet_eom", [ - Dynamic.Mission.MASS, - "cruise_range_initial", - "cruise_time_initial", -]) -x.add_input("weight", ["mass"]) - -# make connections -x.connect("fc", "aero", [Dynamic.Mission.DYNAMIC_PRESSURE]) -x.connect("atmos", "fc", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -x.connect("weight", "aero", ["weight/lift_req"]) -x.connect("aero", "prop", ["drag/thrust_req"]) -x.connect("aero", "thrust_bal", ["thrust_req"]) -x.connect("fc", "breguet_eom", ["TAS"]) # TAS_cruise -x.connect("prop", "breguet_eom", [Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL]) -x.connect("prop", "thrust_bal", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("thrust_bal", "prop", ["thrust_req"]) - -# create outputs -x.add_output("breguet_eom", [Dynamic.Mission.DISTANCE, "cruise_time"], side="right") -# x.add_output("fc", ["EAS"], side="right") - -x.write("cruise_xdsm") -x.write_sys_specs("cruise_specs") diff --git a/aviary/xdsm/descent1_xdsm.py b/aviary/xdsm/descent1_xdsm.py deleted file mode 100644 index 277268f2c..000000000 --- a/aviary/xdsm/descent1_xdsm.py +++ /dev/null @@ -1,74 +0,0 @@ -"""XDSM for above 10 kft""" - -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, IGROUP, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -# input_speed_type = "MACH" -# analysis_scheme = "COLLOCATION" - -# Create subsystem components -x.add_system("atmos", FUNC, ["AtmosphereModel"]) -x.add_system("balance_speed", IFUNC, ["BalanceSpeed"]) -x.add_system("speeds", FUNC, [r"\textbf{SpeedConstraints}"]) -x.add_system("fc", FUNC, ["FlightConditions"]) -x.add_system("ks", FUNC, ["KSComp"]) -x.add_system("prop", GROUP, ["Propulsion"]) -x.add_system("eom", GROUP, [r"\textbf{EOM}"]) -x.add_system("balance_lift", FUNC, ["BalanceLift"]) -x.add_system("aero", IGROUP, ["CruiseAero", "(alpha~in)"]) -x.add_system("pitch", FUNC, ["Constraints"]) - -# create inputs -# x.add_input("fc", [Dynamic.Mission.MACH]) -x.add_input("atmos", [Dynamic.Mission.ALTITUDE]) -x.add_input("balance_speed", ["rhs_mass"]) -# x.add_input("aero", [Aircraft.Wing.AREA]) -x.add_input("aero", [r"aircraft:*", Dynamic.Mission.ALTITUDE]) -x.add_input("prop", [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.THROTTLE, - Aircraft.Engine.SCALE_FACTOR, -]) -x.add_input("eom", ["mass"]) -x.add_input("pitch", [ - Dynamic.Mission.MASS, - Aircraft.Wing.AREA, - Aircraft.Wing.INCIDENCE, -]) - -# make connections -x.connect("atmos", "fc", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -x.connect("atmos", "aero", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -x.connect("atmos", "pitch", ["rho"]) -x.connect("balance_speed", "fc", [Dynamic.Mission.MACH]) -x.connect("balance_speed", "speeds", [Dynamic.Mission.MACH]) -x.connect("balance_speed", "aero", [Dynamic.Mission.MACH]) -x.connect("balance_speed", "prop", [Dynamic.Mission.MACH]) -x.connect("speeds", "ks", ["speed_constraint"]) -x.connect("fc", "aero", [Dynamic.Mission.DYNAMIC_PRESSURE]) -x.connect("fc", "eom", ["TAS"]) -x.connect("fc", "speeds", ["EAS"]) -x.connect("fc", "pitch", ["TAS"]) -x.connect("ks", "balance_speed", ["KS"]) -x.connect("prop", "eom", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("prop", "aero", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("eom", "pitch", [Dynamic.Mission.FLIGHT_PATH_ANGLE]) -x.connect("eom", "balance_lift", ["required_lift"]) -x.connect("balance_lift", "aero", ["alpha"]) -x.connect("balance_lift", "eom", ["alpha"]) -x.connect("balance_lift", "pitch", ["alpha"]) -x.connect("aero", "eom", [Dynamic.Mission.DRAG]) -x.connect("aero", "pitch", ["CL_max"]) -x.connect("aero", "balance_lift", [Dynamic.Mission.LIFT]) - -# create outputs -x.add_output("eom", [ - Dynamic.Mission.ALTITUDE_RATE, - "distance_rate", - "required_lift" -], side="right") - -x.write("descent1_xdsm") -x.write_sys_specs("descent1_specs") diff --git a/aviary/xdsm/descent2_xdsm.py b/aviary/xdsm/descent2_xdsm.py deleted file mode 100644 index 99e0dfa6a..000000000 --- a/aviary/xdsm/descent2_xdsm.py +++ /dev/null @@ -1,62 +0,0 @@ -"""XDSM for below 10 kft""" - -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, IGROUP, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -# Create subsystem components -x.add_system("atmos", FUNC, ["AtmosphereModel"]) -x.add_system("fc", FUNC, ["FlightConditions"]) -x.add_system("prop", GROUP, ["Propulsion"]) -x.add_system("eom", GROUP, ["EOM"]) -x.add_system("balance_lift", FUNC, ["BalanceLift"]) -x.add_system("aero", IGROUP, ["CruiseAero", "(alpha~in)"]) -x.add_system("pitch", FUNC, ["Constraints"]) - -# create inputs -# x.add_input("fc", ["EAS"]) -# x.add_input("aero", [Aircraft.Wing.AREA]) -x.add_input("aero", [r"aircraft:*", Dynamic.Mission.ALTITUDE]) -x.add_input("prop", [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.THROTTLE, - Aircraft.Engine.SCALE_FACTOR, -]) -x.add_input("eom", ["mass"]) -x.add_input("pitch", [ - Dynamic.Mission.MASS, - Aircraft.Wing.AREA, - Aircraft.Wing.INCIDENCE, -]) - -# make connections -x.add_input("fc", ["EAS"]) -x.add_input("atmos", [Dynamic.Mission.ALTITUDE]) -x.connect("atmos", "fc", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -x.connect("atmos", "aero", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -x.connect("atmos", "pitch", ["rho"]) -x.connect("fc", "aero", [Dynamic.Mission.MACH, Dynamic.Mission.DYNAMIC_PRESSURE]) -x.connect("fc", "prop", [Dynamic.Mission.MACH]) -x.connect("fc", "eom", ["TAS"]) -x.connect("fc", "pitch", ["TAS"]) -x.connect("prop", "eom", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("prop", "aero", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("eom", "pitch", [Dynamic.Mission.FLIGHT_PATH_ANGLE]) -x.connect("eom", "balance_lift", ["required_lift"]) -x.connect("balance_lift", "aero", ["alpha"]) -x.connect("balance_lift", "eom", ["alpha"]) -x.connect("balance_lift", "pitch", ["alpha"]) -x.connect("aero", "eom", [Dynamic.Mission.DRAG]) -x.connect("aero", "pitch", ["CL_max"]) -x.connect("aero", "balance_lift", [Dynamic.Mission.LIFT]) - -# create outputs -x.add_output("eom", [ - Dynamic.Mission.ALTITUDE_RATE, - "distance_rate", - "required_lift" -], side="right") - -x.write("descent2_xdsm") -x.write_sys_specs("descent2_specs") diff --git a/aviary/xdsm/design_load_xdsm.py b/aviary/xdsm/design_load_xdsm.py deleted file mode 100644 index cf2e37ccb..000000000 --- a/aviary/xdsm/design_load_xdsm.py +++ /dev/null @@ -1,40 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -show_outputs = True - -# Create subsystem components -x.add_system("speeds", FUNC, ["LoadSpeeds"]) -x.add_system("params", FUNC, ["LoadParameters"]) -x.add_system("slope", FUNC, ["LiftCurveSlope", "(at Cruise)"]) -x.add_system("factors", FUNC, ["LoadFactors"]) - -# create inputs -x.add_input("speeds", [ - Aircraft.Design.MAX_STRUCTURAL_SPEED, - Aircraft.Wing.LOADING, -]) -x.add_input("slope", [ - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SWEEP, Mission.Design.MACH -]) -x.add_input("factors", [Aircraft.Wing.AVERAGE_CHORD]) - -# make connections -x.connect("speeds", "params", ["max_airspeed", "vel_c"]) -x.connect("speeds", "factors", ["max_maneuver_factor", "min_dive_vel"]) -x.connect("slope", "factors", [Aircraft.Design.LIFT_CURVE_SLOPE]) -x.connect("params", "factors", ["density_ratio", "V9"]) - -### add outputs ### -if show_outputs is True: - # params - x.add_output("params", ["max_mach"], side="right") - # factors - x.add_output("factors", [Aircraft.Wing.ULTIMATE_LOAD_FACTOR], side="right") - - -x.write("design_load_xdsm") -x.write_sys_specs("design_load_specs") diff --git a/aviary/xdsm/empennage_size_xdsm.py b/aviary/xdsm/empennage_size_xdsm.py deleted file mode 100644 index 9bca74515..000000000 --- a/aviary/xdsm/empennage_size_xdsm.py +++ /dev/null @@ -1,80 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft - -x = XDSM() - -compute_volume_coeff = True - -if compute_volume_coeff: - x.add_system("hvc", FUNC, [r"\textcolor{gray}{htail_vc\, (TailVolCoef)}"]) - x.add_system("vvc", FUNC, [r"\textcolor{gray}{vtail_vc\, (TailVolCoef)}"]) -x.add_system("htail", FUNC, [r"htail\, (TailSize)"]) -x.add_system("vtail", FUNC, [r"vtail\, (TailSize)"]) - -# move to EmpennageSize inputs -# x.add_input("htail", [r"chord_hor_arm", r"h_ar", r"h_tr", r"(htail_vol_coef)"]) -# x.add_input("vtail", [r"span_ver_arm", r"v_ar", r"v_tr", r"(vtail_vol_coef)"]) - -if compute_volume_coeff: - x.add_input("hvc", [ - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION+"}", - r"\textcolor{gray}{"+Aircraft.Fuselage.LENGTH+"}", - r"\textcolor{gray}{"+Aircraft.Fuselage.AVG_DIAMETER+"}", - r"\textcolor{gray}{"+Aircraft.Wing.AREA+"}", - r"\textcolor{gray}{"+Aircraft.Wing.AVERAGE_CHORD+"}", - ]) - x.add_input("vvc", [ - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION+"}", - r"\textcolor{gray}{"+Aircraft.Fuselage.LENGTH+"}", - r"\textcolor{gray}{"+Aircraft.Fuselage.AVG_DIAMETER+"}", - r"\textcolor{gray}{"+Aircraft.Wing.AREA+"}", - r"\textcolor{gray}{"+Aircraft.Wing.SPAN+"}", - ]) -in_htail_array = [ - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Wing.AREA, - Aircraft.Wing.AVERAGE_CHORD, -] -if not compute_volume_coeff: - in_htail_array.append( - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VOLUME_COEFFICIENT+"}") -x.add_input("htail", in_htail_array) -in_vtail_array = [ - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.VerticalTail.TAPER_RATIO, - Aircraft.Wing.AREA, - Aircraft.Wing.SPAN, -] -if not compute_volume_coeff: - in_vtail_array.append( - r"\textcolor{gray}{"+Aircraft.VerticalTail.VOLUME_COEFFICIENT+"}") -x.add_input("vtail", in_vtail_array) - -# make connections -if compute_volume_coeff: - x.connect("hvc", "htail", [ - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VOLUME_COEFFICIENT+"}"]) - x.connect("vvc", "vtail", [ - r"\textcolor{gray}{"+Aircraft.VerticalTail.VOLUME_COEFFICIENT+"}"]) - -# create outputs -x.add_output("htail", [ - Aircraft.HorizontalTail.AREA, - Aircraft.HorizontalTail.SPAN, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.HorizontalTail.AVERAGE_CHORD, - Aircraft.HorizontalTail.MOMENT_ARM, -], side="right") -x.add_output("vtail", [ - Aircraft.VerticalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.VerticalTail.AVERAGE_CHORD, - Aircraft.VerticalTail.MOMENT_ARM -], side="right") - -x.write("empennage_size_xdsm") -# x.write_sys_specs("empennage_size_specs") diff --git a/aviary/xdsm/engineDeck_xdsm.py b/aviary/xdsm/engineDeck_xdsm.py deleted file mode 100644 index 88b09dbdf..000000000 --- a/aviary/xdsm/engineDeck_xdsm.py +++ /dev/null @@ -1,46 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, METAMODEL, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic - -x = XDSM() - -use_hybrid_throttle = False -# use_thrust = False - -x.add_system('engine', METAMODEL, ['Engine~Interpolator'], stack=True) -x.add_system('scaling', FUNC, [r'Engine~Scaling'], stack=True) - -# create inputs -engine_inputs = [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MACH, - Dynamic.Mission.THROTTLE, -] -if use_hybrid_throttle: - engine_inputs.append(r"\textcolor{gray}{"+Dynamic.Mission.HYBRID_THROTTLE+"}") -x.add_input('engine', engine_inputs) - -x.add_input("scaling", [ - Dynamic.Mission.MACH, - Aircraft.Engine.SCALE_FACTOR, -]) - -# make connections -x.connect("engine", "scaling", [ - 'thrust_net_unscaled', - 'thrust_net_max_unscaled', - 'fuel_flow_rate_unscaled', - 'electric_power_unscaled', - 'nox_rate_unscaled', -], stack=True) - -# create outputs -x.add_output("scaling", [ - Dynamic.Mission.THRUST, - Dynamic.Mission.THRUST_MAX, - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE, - Dynamic.Mission.ELECTRIC_POWER, - Dynamic.Mission.NOX_RATE -], stack=True, side='right') - -x.write("engineDeck_xdsm") -# x.write_sys_specs("engineDeck_specs") diff --git a/aviary/xdsm/equipment_and_useful_load_mass_xdsm.py b/aviary/xdsm/equipment_and_useful_load_mass_xdsm.py deleted file mode 100644 index dbf30f2e9..000000000 --- a/aviary/xdsm/equipment_and_useful_load_mass_xdsm.py +++ /dev/null @@ -1,42 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -simplified = False -show_outputs = True - -# Create subsystem components -x.add_system("equip", FUNC, ["EquipAndUsefulMass"]) - -### make input connections ### -if simplified is True: - # FixedEquipMass - x.add_input("equip", ["InputValues"]) -else: - x.add_input("equip", [ - Mission.Design.GROSS_MASS, - Aircraft.Design.EQUIPMENT_MASS_COEFFICIENTS, - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.LandingGear.TOTAL_MASS, - Aircraft.Controls.TOTAL_MASS, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.Design.EXTERNAL_SUBSYSTEMS_MASS, - Aircraft.Wing.AREA, - ]) - -### add outputs ### -if show_outputs is True: - x.add_output("equip", [ - Aircraft.Design.FIXED_USEFUL_LOAD, - Aircraft.Design.FIXED_EQUIPMENT_MASS, - ], side="right") - -x.write("equipment_and_useful_load_mass_xdsm") -x.write_sys_specs("equipment_and_useful_load_mass_specs") diff --git a/aviary/xdsm/fixed_mass_xdsm.py b/aviary/xdsm/fixed_mass_xdsm.py deleted file mode 100644 index 56c318755..000000000 --- a/aviary/xdsm/fixed_mass_xdsm.py +++ /dev/null @@ -1,219 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -simplified = False -show_outputs = True -has_hybrid_system = False -has_propellers = False - -# Create subsystem components -x.add_system("params", FUNC, ["MassParameters"]) -x.add_system("payload", FUNC, ["PayloadMass"]) -x.add_system("tail", FUNC, ["TailMass"]) -x.add_system("HL", FUNC, ["HighLiftMass"]) -x.add_system("controls", FUNC, ["ControlMass"]) -x.add_system("gear", FUNC, ["GearMass"]) -if has_hybrid_system: - x.add_system("aug", FUNC, [r"\textcolor{gray}{ElectricAugmentationMass}"]) -x.add_system("engine", FUNC, ["EngineMass"]) - -### make input connections ### -if simplified is True: - x.add_input("params", ["InputValues"]) - x.add_input("payload", ["InputValues"]) - x.add_input("tail", ["InputValues"]) - x.add_input("HL", ["InputValues"]) - x.add_input("controls", ["InputValues"]) - x.add_input("gear", ["InputValues"]) - if has_hybrid_system: - x.add_input("aug", [r"\textcolor{gray}{InputValues}"]) - x.add_input("engine", ["InputValues"]) -else: - x.add_input("params", [ - Aircraft.Wing.SPAN, - Aircraft.Wing.SWEEP, # SweepC4 - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.ASPECT_RATIO, - "max_mach", - Aircraft.Strut.ATTACHMENT_LOCATION_DIMENSIONLESS, # StrutX - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - ]) - x.add_input("payload", [ - # Aircraft.CrewPayload.PASSENGER_MASS_WITH_BAGS, # PaxMass. This is an option - Aircraft.CrewPayload.CARGO_MASS, # CargoMass - ]) - x.add_input("tail", [ - Aircraft.Wing.SWEEP, - Mission.Design.GROSS_MASS, # GrossMassInitial - "min_dive_vel", - Aircraft.Wing.SPAN, - Aircraft.VerticalTail.TAPER_RATIO, # TaperRatioVtail - Aircraft.VerticalTail.ASPECT_RATIO, # ARVtail - Aircraft.VerticalTail.SWEEP, # QuarterSweepTail - Aircraft.VerticalTail.SPAN, # SpanVtail - Aircraft.HorizontalTail.MASS_COEFFICIENT, # CoefHtail - Aircraft.Fuselage.LENGTH, - Aircraft.HorizontalTail.SPAN, - Aircraft.LandingGear.TAIL_HOOK_MASS_SCALER, - Aircraft.HorizontalTail.TAPER_RATIO, # TaperRatioHtail - Aircraft.VerticalTail.MASS_COEFFICIENT, # CoefVtail - Aircraft.HorizontalTail.AREA, # HtailArea - Aircraft.HorizontalTail.MOMENT_ARM, # HtailMomArm - Aircraft.HorizontalTail.THICKNESS_TO_CHORD, # TcRatioRootHtail - Aircraft.HorizontalTail.ROOT_CHORD, # RootChordHtail - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, # TailLoc - Aircraft.VerticalTail.AREA, - Aircraft.VerticalTail.MOMENT_ARM, # VtailMomArm - Aircraft.VerticalTail.THICKNESS_TO_CHORD, # TcRatioRootVtail - Aircraft.VerticalTail.ROOT_CHORD, # RootChordVtail - # "CMassTrendHighLift", - ]) - x.add_input("HL", [ - "density", - Aircraft.Wing.SWEEP, - # "GrossMassInitial", - # "minDiveVel", - Aircraft.Wing.AREA, - Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, # CMassTrendHighLift - # "wing_area", - # "num_flaps", - Aircraft.Wing.SLAT_CHORD_RATIO, # slat_chord_ratio - Aircraft.Wing.FLAP_CHORD_RATIO, # flap_chord_ratio - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.FLAP_SPAN_RATIO, # flap_span_ratio - Aircraft.Wing.SLAT_SPAN_RATIO, # slat_span_ratio - Aircraft.Wing.LOADING, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, # tc_ratio_root - Aircraft.Wing.SPAN, - Aircraft.Fuselage.AVG_DIAMETER, # cabin_width - Aircraft.Wing.CENTER_CHORD, - Mission.Landing.LIFT_COEFFICIENT_MAX, # CL_max_flaps_landing - ]) - x.add_input("controls", [ - Aircraft.Wing.SURFACE_CONTROL_MASS_COEFFICIENT, - Mission.Design.GROSS_MASS, # GrossMassInitial - "min_dive_vel", - Aircraft.Wing.AREA, - # "CMassTrendWingControl", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, # ULF - Aircraft.Design.COCKPIT_CONTROL_MASS_COEFFICIENT, # CMassTrendCockpitControl - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS, # StabAugMass - Aircraft.Controls.COCKPIT_CONTROL_MASS_SCALER, # CK15 - Aircraft.Wing.SURFACE_CONTROL_MASS_SCALER, # CK18 - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS_SCALER, # CK19 - Aircraft.Controls.CONTROL_MASS_INCREMENT, # DeltaControlMass - ]) - x.add_input("gear", [ - Aircraft.Wing.MOUNTING_TYPE, - Mission.Design.GROSS_MASS, # GrossMassInitial - Aircraft.LandingGear.MASS_COEFFICIENT, # CGearMass - Aircraft.LandingGear.MAIN_GEAR_MASS_COEFFICIENT, # CMainGear - Aircraft.Nacelle.CLEARANCE_RATIO, # ClearanceRatio - Aircraft.Nacelle.AVG_DIAMETER, # NacelleDiam - ]) - - if has_hybrid_system: - x.add_input("aug", [ - r"\textcolor{gray}{motor_power}", # MotorPowerKw - r"\textcolor{gray}{motor_voltage}", # MotorVoltage - r"\textcolor{gray}{max_amp_per_wire}", # MaxAmpPerWire - r"\textcolor{gray}{safety_factor}", # SafetyFactor - r"\textcolor{gray}{" - + Aircraft.Electrical.HYBRID_CABLE_LENGTH + "}", # CableLen - r"\textcolor{gray}{wire_area}", - r"\textcolor{gray}{rho_wire}", - r"\textcolor{gray}{battery_energy}", - r"\textcolor{gray}{motor_eff}", - r"\textcolor{gray}{inverter_eff}", - r"\textcolor{gray}{transmission_eff}", - r"\textcolor{gray}{battery_eff}", - r"\textcolor{gray}{rho_battery}", - r"\textcolor{gray}{motor_spec_mass}", - r"\textcolor{gray}{inverter_spec_mass}", - r"\textcolor{gray}{TMS_spec_mass}", - ]) - # engine - engine_inputs = [ - Aircraft.Engine.MASS_SPECIFIC, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Nacelle.MASS_SPECIFIC, - Aircraft.Nacelle.SURFACE_AREA, - Aircraft.Engine.PYLON_FACTOR, - Aircraft.Engine.ADDITIONAL_MASS_FRACTION, - Aircraft.Engine.MASS_SCALER, - Aircraft.Propulsion.MISC_MASS_SCALER, - Aircraft.Engine.WING_LOCATIONS, - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - ] - if has_hybrid_system: - engine_inputs.append(r"\textcolor{gray}{aug_mass}") - if has_propellers: - engine_inputs.append(r"\textcolor{gray}{prop_mass}") - x.add_input("engine", engine_inputs) - -### make component connections ### -x.connect("gear", "engine", ["main_gear_mass"]) -if has_hybrid_system: - x.connect("aug", "engine", [r"\textcolor{gray}{aug_mass}"]) - -### add outputs ### -if show_outputs is True: - x.add_output("params", [ - Aircraft.Wing.MATERIAL_FACTOR, # CMaterial - "c_strut_braced", - "c_gear_loc", - Aircraft.Engine.POSITION_FACTOR, # CEngPos - "half_sweep", - ], side="right") - - x.add_output("payload", [ - Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, - "payload_mass_des", - "payload_mass_max", - ], side="right") - - x.add_output("tail", [ - "loc_MAC_vtail", - Aircraft.HorizontalTail.MASS, - Aircraft.VerticalTail.MASS - ], side="right") - - x.add_output("HL", [ - Aircraft.Wing.HIGH_LIFT_MASS, - "flap_mass", - "slat_mass", - ], side="right") - - x.add_output("controls", [ - Aircraft.Controls.TOTAL_MASS, - Aircraft.Wing.SURFACE_CONTROL_MASS, - ], side="right") - - x.add_output("gear", [ - Aircraft.LandingGear.TOTAL_MASS, - "main_gear_mass", - ], side="right") - - if has_hybrid_system: - x.add_output("aug", [ - r"\textcolor{gray}{aug_mass}", - ], side="right") - - # engine - engine_outputs = [ - "eng_comb_mass", - "wing_mounted_mass", - "pylon_mass", - Aircraft.Propulsion.TOTAL_ENGINE_MASS, - Aircraft.Nacelle.MASS, - Aircraft.Propulsion.TOTAL_ENGINE_POD_MASS, - Aircraft.Engine.ADDITIONAL_MASS, - ] - if has_propellers: - engine_outputs.append(r"\textcolor{gray}{prop_mass_all}") - x.add_output("engine", engine_outputs, side="right") - -x.write("fixed_mass_xdsm") -x.write_sys_specs("fixed_mass_specs") diff --git a/aviary/xdsm/flaps_xdsm.py b/aviary/xdsm/flaps_xdsm.py deleted file mode 100644 index 37b997f58..000000000 --- a/aviary/xdsm/flaps_xdsm.py +++ /dev/null @@ -1,107 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, IGROUP, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic - -x = XDSM() - -simplified = False # for testflo run, it must be False - -# Create subsystem components -x.add_system("basic", GROUP, [r"\textbf{BasicFlapsCalculations}"]) -x.add_system("CL_max", FUNC, [r"\textbf{CLmaxCalculation}"]) -x.add_system("tables", IGROUP, [r"\textbf{LookupTables}"]) -x.add_system("increments", FUNC, [r"\textbf{LiftAndDragIncrements}"]) - -# create inputs -if simplified is True: - x.add_input("basic", ["InputValues"]) - x.add_input("CL_max", ["InputValues"]) - x.add_input("tables", ["InputValues"]) - x.add_input("increments", ["InputValues"]) -else: - x.add_input("basic", [ - Aircraft.Wing.SWEEP, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.FLAP_CHORD_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.CENTER_CHORD, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.Wing.SPAN, - Aircraft.Wing.SLAT_CHORD_RATIO, - "slat_defl", - Aircraft.Wing.OPTIMUM_SLAT_DEFLECTION, - "flap_defl", - Aircraft.Wing.OPTIMUM_FLAP_DEFLECTION, - Aircraft.Wing.ROOT_CHORD, - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.LEADING_EDGE_SWEEP, - ]) - x.add_input("CL_max", [ - Aircraft.Wing.FLAP_LIFT_INCREMENT_OPTIMUM, - Aircraft.Wing.LOADING, - Dynamic.Mission.STATIC_PRESSURE, - Aircraft.Wing.AVERAGE_CHORD, - Aircraft.Wing.MAX_LIFT_REF, - Aircraft.Wing.SLAT_LIFT_INCREMENT_OPTIMUM, - "fus_lift", # fuselage lift increment - "kinematic_viscosity", - Dynamic.Mission.TEMPERATURE, - Dynamic.Mission.SPEED_OF_SOUND, - ]) - x.add_input("tables", [ - Aircraft.Wing.FLAP_CHORD_RATIO, - Aircraft.Wing.FLAP_SPAN_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, - "flap_defl", - ]) - x.add_input("increments", [ - Aircraft.Wing.FLAP_DRAG_INCREMENT_OPTIMUM, - Aircraft.Wing.FLAP_LIFT_INCREMENT_OPTIMUM, - ]) - -# make connections -x.connect("basic", "increments", ["VLAM8", "VDEL4", "VDEL5"]) -x.connect("basic", "CL_max", ["VLAM8", "VLAM9", "VLAM12"]) -x.connect("basic", "tables", [ - "slat_defl_ratio", - "flap_defl_ratio", - "body_to_span_ratio", - Aircraft.Wing.SLAT_SPAN_RATIO, - "chord_to_body_ratio", -]) -x.connect("CL_max", "tables", ["reynolds", Dynamic.Mission.MACH]) -x.connect("tables", "increments", [ - "VDEL1", - "VDEL2", - "VDEL3", - "VLAM3", - "VLAM4", - "VLAM5", - "VLAM6", - "VLAM7", - "VLAM13", - "VLAM14", -]) -x.connect("tables", "CL_max", [ - "VLAM1", - "VLAM2", - "VLAM3", - "VLAM4", - "VLAM5", - "VLAM6", - "VLAM7", - "VLAM10", - "VLAM11", - "VLAM13", - "VLAM14", - "fus_lift", -]) - -# create outputs -x.add_output("increments", ["delta_CL", "delta_CD"], side="right") -x.add_output("CL_max", ["CL_max"], side="right") - -x.write("flaps_xdsm") -x.write_sys_specs("flaps_specs") diff --git a/aviary/xdsm/fuel_mass_xdsm.py b/aviary/xdsm/fuel_mass_xdsm.py deleted file mode 100644 index cb82f5bdb..000000000 --- a/aviary/xdsm/fuel_mass_xdsm.py +++ /dev/null @@ -1,137 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -simplified = True -show_outputs = True - -# Create subsystem components -x.add_system("sys_and_fus", FUNC, ["FuelSysAndFullFuselageMass"]) -x.add_system("fus_and_struct", FUNC, ["FuselageAndStructMass"]) -x.add_system("fuel", FUNC, ["FuelMass"]) -x.add_system("fuel_and_oem", FUNC, ["FuelAndOEMOutputs"]) -x.add_system("body", IFUNC, ["BodyTankCalculations"]) - -### make input connections ### -if simplified is True: - x.add_input("sys_and_fus", ["InputValues"]) - x.add_input("fus_and_struct", ["InputValues"]) - x.add_input("fuel", ["InputValues"]) - x.add_input("fuel_and_oem", ["InputValues"]) -else: - x.add_input("sys_and_fus", [ - "wing_mounted_mass", - Mission.Design.GROSS_MASS, # GrossMassInitial - Aircraft.Wing.MASS, # TotalWingMass - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, # CK21 - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, # CMassTrendFuelSys - Aircraft.Fuel.DENSITY, # RhoFuel - Aircraft.Fuel.FUEL_MARGIN, # FuelMargin - ]) - - x.add_input("fus_and_struct", [ - Aircraft.Wing.MASS, # TotalWingMass - Aircraft.Fuselage.MASS_COEFFICIENT, # CFuselage - Aircraft.Fuselage.WETTED_AREA, # FusSA - Aircraft.Fuselage.AVG_DIAMETER, # CabinWidth - Aircraft.TailBoom.LENGTH, # CabinLenTailboom - "pylon_len", - "min_dive_vel", - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, # PDiffFus - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, # ULF - "MAT", - Aircraft.Wing.MASS_SCALER, # CK8 - Aircraft.HorizontalTail.MASS_SCALER, # CK9 - Aircraft.HorizontalTail.MASS, # HtailMass - Aircraft.VerticalTail.MASS_SCALER, # CK10 - Aircraft.VerticalTail.MASS, # VtailMass - Aircraft.Fuselage.MASS_SCALER, # CK11 - Aircraft.LandingGear.TOTAL_MASS_SCALER, # CK12 - Aircraft.LandingGear.TOTAL_MASS, # LandingGearMass - Aircraft.Engine.POD_MASS_SCALER, # CK14 - Aircraft.Propulsion.TOTAL_ENGINE_POD_MASS, # SecMass - Aircraft.Design.STRUCTURAL_MASS_INCREMENT, # DeltaStructMass - ]) - - x.add_input("fuel", [ - "eng_comb_mass", - "payload_mass_des", - "payload_mass_max", - Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, # PayloadMass - Mission.Design.GROSS_MASS, # GrossMassInitial - Aircraft.Controls.TOTAL_MASS, # ControlMass - Aircraft.Design.FIXED_EQUIPMENT_MASS, # FixedEquipMass - Aircraft.Design.FIXED_USEFUL_LOAD, # UsefulMass - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, # CK21 - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, # CMassTrendFuelSys - Aircraft.Fuel.DENSITY, # RhoFuel - Aircraft.Fuel.FUEL_MARGIN, # FuelMargin - ]) - - x.add_input("fuel_and_oem", [ - Aircraft.Fuel.DENSITY, # RhoFuel - Mission.Design.GROSS_MASS, # GrossMassInitial - # Aircraft.Propulsion.MASS, - Aircraft.Controls.TOTAL_MASS, # ControlMass - # Aircraft.Design.STRUCTURE_MASS, - Aircraft.Design.FIXED_EQUIPMENT_MASS, # FixedEquipMass - Aircraft.Design.FIXED_USEFUL_LOAD, # UsefulMass - Mission.Design.FUEL_MASS_REQUIRED, - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, # GeometricFuelVol - Aircraft.Fuel.FUEL_MARGIN, # FuelMargin - Aircraft.Fuel.TOTAL_CAPACITY, - ]) - - x.add_input("body", [ - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, # GeometricFuelVol - Aircraft.Fuel.DENSITY, # RhoFuel - Mission.Design.GROSS_MASS # GrossMassInitial - ]) - -### make component connections ### -x.connect("sys_and_fus", "fus_and_struct", ["fus_mass_full"]) # FusMassFull -x.connect("sys_and_fus", "fuel", [Aircraft.Fuel.FUEL_SYSTEM_MASS]) -x.connect("fus_and_struct", "fuel", [Aircraft.Design.STRUCTURE_MASS]) -x.connect("fus_and_struct", "fuel_and_oem", [Aircraft.Design.STRUCTURE_MASS]) -x.connect("fuel", "sys_and_fus", [Mission.Design.FUEL_MASS]) # FuelMassDes -x.connect("fuel", "fuel_and_oem", [ - Aircraft.Propulsion.MASS, - Mission.Design.FUEL_MASS_REQUIRED, # ReqFuelMass -]) -x.connect("fuel", "body", [ - "fuel_mass_min", # FuelMassMin - Mission.Design.FUEL_MASS_REQUIRED, # ReqFuelMass - Mission.Design.FUEL_MASS, # FuelMassDes -]) -x.connect("fuel_and_oem", "body", [ - Aircraft.Fuel.WING_VOLUME_DESIGN, # DesignFuelVol - Aircraft.Fuel.WING_VOLUME_STRUCTURAL_MAX, # MaxWingfuelVol - "max_wingfuel_mass", # MaxWingfuelMass - Aircraft.Design.OPERATING_MASS, -]) -x.connect("body", "sys_and_fus", ["wingfuel_mass_min"]) # WingfuelMassMin -x.connect("body", "fuel_and_oem", [Aircraft.Fuel.TOTAL_CAPACITY]) # MaxFuelAvail - -### add outputs ### -if show_outputs is True: - # fus_and_struct - x.add_output("fus_and_struct", [Aircraft.Fuselage.MASS], side="right") - - # fuel_and_oem - x.add_output("fuel_and_oem", [ - "OEM_wingfuel_mass", - "OEM_fuel_vol", - "payload_mass_max_fuel", # PayloadMassMaxFuel - "volume_wingfuel_mass", # VolumeWingfuelMass - ], side="right") - - # body - x.add_output("body", [ - Aircraft.Fuel.AUXILIARY_FUEL_CAPACITY, # ExtraFuelMass - "extra_fuel_volume", # ExtraFuelVolume - "max_extra_fuel_mass", # MaxExtraFuelMass - ], side="right") - -x.write("fuel_mass_xdsm") -x.write_sys_specs("fuel_mass_specs") diff --git a/aviary/xdsm/fuselage_size_xdsm.py b/aviary/xdsm/fuselage_size_xdsm.py deleted file mode 100644 index 41ba303ec..000000000 --- a/aviary/xdsm/fuselage_size_xdsm.py +++ /dev/null @@ -1,48 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft - -x = XDSM() - -# Create subsystem components -x.add_system("fus_parameters", FUNC, ["FuselageParameters"]) -x.add_system("fus_size", FUNC, ["FuselageSize"]) - -# create inputs -x.add_input("fus_parameters", [ - # Aircraft.Fuselage.NUM_SEATS_ABREAST, # option - # Aircraft.Fuselage.SEAT_WIDTH, # option - # Aircraft.Fuselage.NUM_AISLES, # option - # Aircraft.Fuselage.AISLE_WIDTH, # option - # Aircraft.Fuselage.SEAT_PITCH, # option - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, -]) -x.add_input("fus_size", [ - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, -]) - -# make connections -x.connect("fus_parameters", "fus_size", [ - "cabin_height", - "cabin_len", - "nose_height", -]) - -# create outputs -x.add_output("fus_parameters", [ - Aircraft.Fuselage.AVG_DIAMETER, - "cabin_height", - "cabin_len", - "nose_height", -], side="right") -x.add_output("fus_size", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.TailBoom.LENGTH, -], side="right") - -x.write("fuselage_size_xdsm") -# x.write_sys_specs("fuselage_size_specs") diff --git a/aviary/xdsm/groundroll_xdsm.py b/aviary/xdsm/groundroll_xdsm.py deleted file mode 100644 index b86d086d9..000000000 --- a/aviary/xdsm/groundroll_xdsm.py +++ /dev/null @@ -1,90 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -with_EXE_Comps = False - -# Create subsystem components -x.add_system("atmos", FUNC, ["USatm"]) -x.add_system("fc", FUNC, ["FlightConditions"]) -x.add_system("ialpha", FUNC, ["InitAlpha"]) -x.add_system("prop", GROUP, ["Propulsion"]) -x.add_system("aero", GROUP, ["LowspeedAero"]) -x.add_system("eom", GROUP, [r"\textbf{EOM}"]) -if with_EXE_Comps: - x.add_system("exec", FUNC, ["Exec"]) - x.add_system("exec2", FUNC, ["Exec2"]) - x.add_system("exec3", FUNC, ["Exec3"]) - -# create inputs -x.add_input("atmos", [Dynamic.Mission.ALTITUDE]) -x.add_input("fc", ["TAS"]) -x.add_input("ialpha", ["i_wing"]) -x.add_input("eom", [ - "mass", - "TAS", - Dynamic.Mission.FLIGHT_PATH_ANGLE, - Aircraft.Wing.INCIDENCE, -]) -x.add_input("aero", [ - "t_curr", - "dt_flaps", - "dt_gear", - "t_init_flaps", - "t_init_gear", - "aircraft:*", - # Aircraft.Wing.FLAP_DEFLECTION_TAKEOFF, # flap_defl - # Aircraft.Wing.HEIGHT, - # Aircraft.Wing.SPAN, - # Aircraft.Wing.AREA, - Dynamic.Mission.ALTITUDE, - Mission.Takeoff.AIRPORT_ALTITUDE, - Mission.Design.GROSS_MASS, - # 'aero_ramps.flap_factor:initial_val', - # 'aero_ramps.gear_factor:initial_val', - # 'aero_ramps.flap_factor:final_val', - # 'aero_ramps.gear_factor:final_val', -]) -x.add_input("prop", [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.THROTTLE, - Aircraft.Engine.SCALE_FACTOR, -]) - - -# make connections -x.connect("atmos", "fc", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -x.connect("fc", "prop", [Dynamic.Mission.MACH]) -x.connect("fc", "aero", [Dynamic.Mission.DYNAMIC_PRESSURE, Dynamic.Mission.MACH]) -x.connect("prop", "eom", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("aero", "eom", [Dynamic.Mission.LIFT, Dynamic.Mission.DRAG]) -x.connect("ialpha", "aero", ["alpha"]) -x.connect("ialpha", "eom", ["alpha"]) -if with_EXE_Comps: - x.add_input("exec", ["TAS"]) - x.connect("eom", "exec", ["TAS_rate"]) - x.connect("eom", "exec2", ["TAS_rate"]) - x.connect("prop", "exec2", ["fuel_flow_rate_negative_total"]) # mass_rate - x.connect("exec2", "exec3", ["dt_dv"]) - -# create outputs -eom_output_list = [ - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - Dynamic.Mission.ALTITUDE_RATE, - "distance_rate", - "alpha_rate", - "normal_force", - "fuselage_pitch", -] -if not with_EXE_Comps: - eom_output_list.append("TAS_rate") -x.add_output("eom", eom_output_list, side="right") - -if with_EXE_Comps: - x.add_output("exec", ["over_a",], side="right") - x.add_output("exec3", ["dmss_dv",], side="right") -x.add_output("fc", ["EAS",], side="right") - -x.write("groundroll_xdsm") -x.write_sys_specs("groundroll_specs") diff --git a/aviary/xdsm/landing_xdsm.py b/aviary/xdsm/landing_xdsm.py deleted file mode 100644 index bf0a456f9..000000000 --- a/aviary/xdsm/landing_xdsm.py +++ /dev/null @@ -1,123 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, METAMODEL, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -show_outputs = True - -# Create subsystem components -x.add_system("landing_alt", FUNC, [r"\textbf{LandingAlt}"]) -x.add_system("atmos_approach", FUNC, ["ApproachAtmosphere"]) -x.add_system("fc_approach", FUNC, ["ApproachFlightConditions"]) -x.add_system("prop_approach", GROUP, ["ApproachPropulsion"]) -x.add_system("aero_approach", GROUP, ["ApproachLowSpeedAero", "(alpha~in)"]) -x.add_system("glide", FUNC, [r"\textbf{GlideCondition}"]) -x.add_system("atmos_touchdown", FUNC, ["TouchdownAtmosphere"]) -x.add_system("fc_touchdown", FUNC, ["TouchdownFlightConditions"]) -x.add_system("aero_touchdown", GROUP, ["TouchdownLowSpeedAero"]) -x.add_system("groundroll", FUNC, [r"\textbf{GroundRoll}"]) - -# create inputs -x.add_input("landing_alt", [ - Mission.Landing.OBSTACLE_HEIGHT, - Mission.Landing.AIRPORT_ALTITUDE, -]) -x.add_input("fc_approach", [Mission.Landing.INITIAL_MACH]) -x.add_input("prop_approach", [ - Dynamic.Mission.THROTTLE, - Mission.Landing.INITIAL_MACH, -]) -x.add_input("aero_approach", [ - "aero_app.alpha", - "t_init_flaps", - "t_init_gear", - Mission.Landing.INITIAL_MACH, - Mission.Landing.AIRPORT_ALTITUDE, - Mission.Design.GROSS_MASS, - Mission.Landing.LIFT_COEFFICIENT_FLAP_INCREMENT, # dCL_flaps_model - Mission.Landing.DRAG_COEFFICIENT_FLAP_INCREMENT, # dCD_flaps_model - # "flap_defl", # Aircraft.Wing.FLAP_DEFLECTION_LANDING - "aircraft:*", -]) -x.add_input("glide", [ - Mission.Landing.MAXIMUM_SINK_RATE, - Mission.Landing.GLIDE_TO_STALL_RATIO, - Mission.Landing.MAXIMUM_FLARE_LOAD_FACTOR, - Mission.Landing.TOUCHDOWN_SINK_RATE, - Mission.Landing.BRAKING_DELAY, - Dynamic.Mission.MASS, - Aircraft.Wing.AREA, -]) -x.add_input("atmos_touchdown", [Mission.Landing.AIRPORT_ALTITUDE]) -x.add_input("aero_touchdown", [ - Aircraft.Wing.INCIDENCE, - Mission.Landing.AIRPORT_ALTITUDE, - Aircraft.Wing.AREA, - Aircraft.Wing.HEIGHT, - Aircraft.Wing.SPAN, - Mission.Design.GROSS_MASS, - Aircraft.Wing.FLAP_DEFLECTION_LANDING, # flap_defl -]) -x.add_input("groundroll", ["mass"]) - -# make connections -x.connect("landing_alt", "atmos_approach", [Mission.Landing.INITIAL_ALTITUDE]) -x.connect("landing_alt", "prop_approach", [Mission.Landing.INITIAL_ALTITUDE]) -x.connect("landing_alt", "aero_approach", [Mission.Landing.INITIAL_ALTITUDE]) -x.connect("landing_alt", "glide", [Mission.Landing.INITIAL_ALTITUDE]) - -x.connect("atmos_approach", "fc_approach", [ - "rho_app", - "speed_of_sound_app", # Dynamic.Mission.SPEED_OF_SOUND -]) -# x.connect("atmos_approach", "prop_approach", ["T_app", "P_app"]) -x.connect("atmos_approach", "glide", ["rho_app"]) - -x.connect("fc_approach", "aero_approach", [ - "dynamic_pressure_app", # Dynamic.Mission.DYNAMIC_PRESSURE -]) - -x.connect("prop_approach", "groundroll", ["thrust_idle"]) - -x.connect("aero_approach", "glide", ["CL_max"]) -x.connect("aero_approach", "groundroll", ["CL_max"]) - -x.connect("glide", "groundroll", [ - Mission.Landing.STALL_VELOCITY, - "TAS_touchdown", - "density_ratio", - "wing_loading_land", - "glide_distance", - "tr_distance", # transition distance - "delay_distance", -]) -x.connect("glide", "fc_touchdown", ["TAS_touchdown"]) - -x.connect("atmos_touchdown", "fc_touchdown", [ - "rho_touchdown", - "speed_of_sound_touchdown", -]) - -x.connect("fc_touchdown", "aero_touchdown", [ - "mach_touchdown", - "dynamic_pressure_touchdown", -]) - -x.connect("aero_touchdown", "groundroll", ["touchdown_CD", "touchdown_CL"]) - -# create outputs -if show_outputs: - x.add_output("glide", [ - Mission.Landing.INITIAL_VELOCITY, - "theta", - "flare_alt", - ], side="right") - x.add_output("groundroll", [ - # Mission.Landing.INITIAL_VELOCITY, - "ground_roll_distance", - Mission.Landing.GROUND_DISTANCE, - "average_acceleration", # ABAR - ], side="right") - -x.write("landing_xdsm") -x.write_sys_specs("landing_specs") diff --git a/aviary/xdsm/mass_and_sizing_basic_xdsm.py b/aviary/xdsm/mass_and_sizing_basic_xdsm.py deleted file mode 100644 index 3d93f5d1c..000000000 --- a/aviary/xdsm/mass_and_sizing_basic_xdsm.py +++ /dev/null @@ -1,282 +0,0 @@ -""" -This XDSM is for the case with no strut, no fold, volume coefficients input, no -augmented system -""" - -from pyxdsm.XDSM import FUNC, GROUP, IGROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -simplified = False -# HAS_FOLD = False -# HAS_STRUT = False -HAS_PROPELLERS = True -# has_hybrid_system = False # see fixed_mass_xdsm.py - -# Create subsystem components -x.add_system("size", GROUP, [r"\textbf{SizeGroup}"]) -x.add_system("design_load", GROUP, [r"\textbf{DesignLoadGroup}"]) -x.add_system("fixed_mass", GROUP, ["FixedMassGroup"]) -x.add_system("equip", FUNC, [r"\textbf{EquipAndUsefulMass}"]) -x.add_system("wing_mass", IGROUP, [r"\textbf{WingMassGroup}"]) -x.add_system("fuel_mass", IGROUP, [r"\textbf{FuelMassGroup}"]) - -### make input connections ### - -# size -size_inputs = [ - # connect inputs: - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - Aircraft.Wing.LOADING, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.VerticalTail.TAPER_RATIO, - # direct inputs: - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, - Aircraft.HorizontalTail.VOLUME_COEFFICIENT, - Aircraft.VerticalTail.VOLUME_COEFFICIENT, - # Aircraft.Engine.WING_LOCATIONS, -] -if simplified: - x.add_input("size", ["InputValues"]) -else: - x.add_input("size", size_inputs) - -# design_load -if simplified: - x.add_input("design_load", ["InputValues"]) -else: - x.add_input("design_load", [ - # connect input: - Aircraft.Wing.LOADING, - # direct input: - Aircraft.Design.MAX_STRUCTURAL_SPEED, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.MACH - ]) - -# fixed_mass -fixed_input = [ - # connect inputs: - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.GROSS_MASS, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Engine.WING_LOCATIONS, - Aircraft.VerticalTail.TAPER_RATIO, - # direct inputs: - Aircraft.CrewPayload.CARGO_MASS, - Aircraft.VerticalTail.SWEEP, - Aircraft.HorizontalTail.MASS_COEFFICIENT, - Aircraft.LandingGear.TAIL_HOOK_MASS_SCALER, - Aircraft.VerticalTail.MASS_COEFFICIENT, - Aircraft.HorizontalTail.THICKNESS_TO_CHORD, - Aircraft.VerticalTail.THICKNESS_TO_CHORD, - Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, - Aircraft.Wing.SURFACE_CONTROL_MASS_COEFFICIENT, - Aircraft.Design.COCKPIT_CONTROL_MASS_COEFFICIENT, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS, - Aircraft.Controls.COCKPIT_CONTROL_MASS_SCALER, - Aircraft.Wing.SURFACE_CONTROL_MASS_SCALER, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS_SCALER, - Aircraft.Controls.CONTROL_MASS_INCREMENT, - Aircraft.LandingGear.MASS_COEFFICIENT, - Aircraft.LandingGear.MAIN_GEAR_MASS_COEFFICIENT, - Aircraft.Engine.MASS_SPECIFIC, - Aircraft.Nacelle.MASS_SPECIFIC, - Aircraft.Engine.PYLON_FACTOR, - Aircraft.Engine.ADDITIONAL_MASS_FRACTION, - Aircraft.Engine.MASS_SCALER, - Aircraft.Propulsion.MISC_MASS_SCALER, - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - # Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Wing.MOUNTING_TYPE, - Aircraft.Nacelle.CLEARANCE_RATIO, -] -if HAS_PROPELLERS: - fixed_input.append( - r"\textcolor{gray}{engine.prop_mass}") # direct input: mass of one propeller. Note: should output "prop_mass_all" -if simplified: - x.add_input("fixed_mass", ["InputValues"]) -else: - x.add_input("fixed_mass", fixed_input) - -# equip -if simplified: - x.add_input("equip", ["InputValues"]) -else: - x.add_input("equip", [ - Mission.Design.GROSS_MASS, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.Design.EXTERNAL_SUBSYSTEMS_MASS, - # direct input: - Aircraft.Design.EQUIPMENT_MASS_COEFFICIENTS, - ]) - -# wing_mass -wing_inputs = [ - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - # direct input: - Aircraft.Wing.MASS_COEFFICIENT, -] -if simplified: - x.add_input("wing_mass", ["InputValues"]) -else: - x.add_input("wing_mass", wing_inputs) - -# fuel_mass -if simplified: - x.add_input("fuel_mass", ["InputValues"]) -else: - x.add_input("fuel_mass", [ - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Mission.Design.GROSS_MASS, - # direct input - Aircraft.Fuel.FUEL_MARGIN, - Aircraft.Fuselage.MASS_COEFFICIENT, - "fus_and_struct.pylon_len", - "fus_and_struct.MAT", - Aircraft.Wing.MASS_SCALER, - Aircraft.HorizontalTail.MASS_SCALER, - Aircraft.VerticalTail.MASS_SCALER, - Aircraft.Fuselage.MASS_SCALER, - Aircraft.LandingGear.TOTAL_MASS_SCALER, - Aircraft.Engine.POD_MASS_SCALER, - Aircraft.Design.STRUCTURAL_MASS_INCREMENT, - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, - Aircraft.Fuel.DENSITY, - # Mission.Design.FUEL_MASS_REQUIRED, - # Aircraft.Fuel.TOTAL_CAPACITY, - ]) - -# make connections -x.connect("size", "design_load", [Aircraft.Wing.AVERAGE_CHORD]) - -wing_inputs = [Aircraft.Wing.SPAN] -x.connect("size", "wing_mass", wing_inputs) - -if simplified: - x.connect("size", "fixed_mass", ["aircraft:*"]) -else: - x.connect("size", "fixed_mass", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.Wing.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.HorizontalTail.SPAN, - Aircraft.HorizontalTail.MOMENT_ARM, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.Nacelle.SURFACE_AREA, - Aircraft.Nacelle.AVG_DIAMETER, - ]) -x.connect("size", "equip", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.Wing.AREA, -]) -if simplified: - x.connect("size", "fuel_mass", ["aircraft:*"]) -else: - x.connect("size", "fuel_mass", [ - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.TailBoom.LENGTH, - ]) - -x.connect("design_load", "fixed_mass", [ - "max_mach", - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, -]) -x.connect("design_load", "wing_mass", [Aircraft.Wing.ULTIMATE_LOAD_FACTOR]) -x.connect("design_load", "fuel_mass", [ - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, -]) - -if simplified: - x.connect("fixed_mass", "wing_mass", [ - "aircraft:*", - "c_strut_braced", - "c_gear_loc", - "half_sweep", - ] - ) -else: - x.connect("fixed_mass", "wing_mass", [ - Aircraft.Wing.MATERIAL_FACTOR, - "c_strut_braced", - "c_gear_loc", - Aircraft.Engine.POSITION_FACTOR, - "half_sweep", - Aircraft.Wing.HIGH_LIFT_MASS, - ]) -if simplified: - x.connect("fixed_mass", "fuel_mass", [ - "aircraft:*", - "payload_mass_des", - "payload_mass_max", - "wing_mounted_mass", - "eng_comb_mass", - ]) -else: - x.connect("fixed_mass", "fuel_mass", [ - Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, - "payload_mass_des", - "payload_mass_max", - Aircraft.HorizontalTail.MASS, - Aircraft.VerticalTail.MASS, - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, - Aircraft.Propulsion.TOTAL_ENGINE_POD_MASS, - "wing_mounted_mass", - "eng_comb_mass", - ]) -x.connect("fixed_mass", "equip", [ - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS -]) -x.connect("equip", "fuel_mass", [ - Aircraft.Design.FIXED_EQUIPMENT_MASS, - Aircraft.Design.FIXED_USEFUL_LOAD, -]) -x.connect("wing_mass", "fuel_mass", [Aircraft.Wing.MASS]) - -x.write("mass_and_sizing_basic_xdsm") -x.write_sys_specs("mass_and_sizing_basic_specs") diff --git a/aviary/xdsm/mass_and_sizing_both_xdsm.py b/aviary/xdsm/mass_and_sizing_both_xdsm.py deleted file mode 100644 index 08f1b8d00..000000000 --- a/aviary/xdsm/mass_and_sizing_both_xdsm.py +++ /dev/null @@ -1,302 +0,0 @@ -""" -This XDSM is for the case with both a fold and a strut, volume coefficients input, no -augmented system, no propeller, and a low wing -""" - -from pyxdsm.XDSM import FUNC, GROUP, IGROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -simplified = False -HAS_FOLD = True -HAS_STRUT = True -# HAS_PROPELLERS = False -# has_hybrid_system = False # see fixed_mass_xdsm.py - -# Create subsystem components -x.add_system("size", GROUP, [r"\textbf{SizeGroup}"]) -x.add_system("design_load", GROUP, ["DesignLoadGroup"]) -x.add_system("fixed_mass", GROUP, [r"\textbf{FixedMassGroup}"]) -x.add_system("equip", FUNC, ["EquipAndUsefulMass"]) -x.add_system("wing_mass", IGROUP, [r"\textbf{WingMassGroup}"]) -x.add_system("fuel_mass", IGROUP, ["FuelMassGroup"]) - -### make input connections ### - -size_inputs = [ - # connect inputs: - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - Aircraft.Wing.LOADING, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.VerticalTail.TAPER_RATIO, - # direct inputs: - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, - Aircraft.HorizontalTail.VOLUME_COEFFICIENT, - Aircraft.VerticalTail.VOLUME_COEFFICIENT, - # Aircraft.Engine.WING_LOCATIONS, -] -if HAS_FOLD: - size_inputs.append( - r"\textcolor{gray}{"+Aircraft.Engine.SCALE_FACTOR+"}") # connect inputs - size_inputs.append( - r"\textcolor{gray}{"+Aircraft.Wing.FOLDED_SPAN+"}") # connect inputs -if HAS_STRUT: - size_inputs.append( - r"\textcolor{gray}{"+Aircraft.Strut.ATTACHMENT_LOCATION+"}") # connect inputs - size_inputs.append( - r"\textcolor{gray}{"+Aircraft.Strut.AREA_RATIO+"}") # direct inputs -if simplified: - x.add_input("size", ["InputValues"]) -else: - x.add_input("size", size_inputs) -if simplified: - x.add_input("design_load", ["InputValues"]) -else: - x.add_input("design_load", [ - # connect input: - Aircraft.Wing.LOADING, - # direct input: - Aircraft.Design.MAX_STRUCTURAL_SPEED, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.MACH - ]) -fixed_input = [ - # HL connect inputs: - "density", - Aircraft.Wing.LOADING, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.Wing.FLAP_CHORD_RATIO, - Aircraft.Wing.SLAT_CHORD_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.Wing.CENTER_CHORD, - Aircraft.Wing.FLAP_SPAN_RATIO, - Aircraft.Wing.SLAT_SPAN_RATIO, - Mission.Landing.LIFT_COEFFICIENT_MAX, - # connect inputs: - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.GROSS_MASS, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Engine.WING_LOCATIONS, - Aircraft.VerticalTail.TAPER_RATIO, - # direct inputs: - Aircraft.CrewPayload.CARGO_MASS, - Aircraft.VerticalTail.SWEEP, - Aircraft.HorizontalTail.MASS_COEFFICIENT, - Aircraft.LandingGear.TAIL_HOOK_MASS_SCALER, - Aircraft.VerticalTail.MASS_COEFFICIENT, - Aircraft.HorizontalTail.THICKNESS_TO_CHORD, - Aircraft.VerticalTail.THICKNESS_TO_CHORD, - Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, - Aircraft.Wing.SURFACE_CONTROL_MASS_COEFFICIENT, - Aircraft.Design.COCKPIT_CONTROL_MASS_COEFFICIENT, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS, - Aircraft.Controls.COCKPIT_CONTROL_MASS_SCALER, - Aircraft.Wing.SURFACE_CONTROL_MASS_SCALER, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS_SCALER, - Aircraft.Controls.CONTROL_MASS_INCREMENT, - Aircraft.LandingGear.MASS_COEFFICIENT, - Aircraft.LandingGear.MAIN_GEAR_MASS_COEFFICIENT, - Aircraft.Engine.MASS_SPECIFIC, - Aircraft.Nacelle.MASS_SPECIFIC, - Aircraft.Engine.PYLON_FACTOR, - Aircraft.Engine.ADDITIONAL_MASS_FRACTION, - Aircraft.Engine.MASS_SCALER, - Aircraft.Propulsion.MISC_MASS_SCALER, - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - # Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Wing.MOUNTING_TYPE, - Aircraft.Nacelle.CLEARANCE_RATIO, -] -if HAS_STRUT: - fixed_input.append( - r"\textcolor{gray}{"+Aircraft.Strut.ATTACHMENT_LOCATION_DIMENSIONLESS+"}") -if simplified: - x.add_input("fixed_mass", ["InputValues"]) -else: - x.add_input("fixed_mass", fixed_input) -if simplified: - x.add_input("equip", ["InputValues"]) -else: - x.add_input("equip", [ - Mission.Design.GROSS_MASS, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.Design.EXTERNAL_SUBSYSTEMS_MASS, - # direct input: - Aircraft.Design.EQUIPMENT_MASS_COEFFICIENTS, - ]) -wing_inputs = [ - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - # direct input: - Aircraft.Wing.MASS_COEFFICIENT, -] -if HAS_FOLD: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Wing.FOLD_MASS_COEFFICIENT+"}") -if HAS_STRUT: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Strut.MASS_COEFFICIENT+"}") -if simplified: - x.add_input("wing_mass", ["InputValues"]) -else: - x.add_input("wing_mass", wing_inputs) -if simplified: - x.add_input("fuel_mass", ["InputValues"]) -else: - x.add_input("fuel_mass", [ - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Mission.Design.GROSS_MASS, - # direct input - Aircraft.Fuel.FUEL_MARGIN, - Aircraft.Fuselage.MASS_COEFFICIENT, - "pylon_len", - "MAT", - Aircraft.Wing.MASS_SCALER, - Aircraft.HorizontalTail.MASS_SCALER, - Aircraft.VerticalTail.MASS_SCALER, - Aircraft.Fuselage.MASS_SCALER, - Aircraft.LandingGear.TOTAL_MASS_SCALER, - Aircraft.Engine.POD_MASS_SCALER, - Aircraft.Design.STRUCTURAL_MASS_INCREMENT, - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, - Aircraft.Fuel.DENSITY, - # Mission.Design.FUEL_MASS_REQUIRED, - # Aircraft.Fuel.TOTAL_CAPACITY, - ]) - -# make connections -x.connect("size", "design_load", [Aircraft.Wing.AVERAGE_CHORD]) - -wing_inputs = [Aircraft.Wing.SPAN] -if HAS_FOLD: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Wing.AREA+"}") - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Wing.FOLDING_AREA+"}") -if simplified: - x.connect("size", "wing_mass", ["aircraft:*"]) -else: - x.connect("size", "wing_mass", wing_inputs) - -if simplified: - x.connect("size", "fixed_mass", ["aircraft:*"]) -else: - x.connect("size", "fixed_mass", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.Wing.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.HorizontalTail.SPAN, - Aircraft.HorizontalTail.MOMENT_ARM, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.Nacelle.SURFACE_AREA, - Aircraft.Nacelle.AVG_DIAMETER, - ]) -x.connect("size", "equip", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.Wing.AREA, -]) -if simplified: - x.connect("size", "fuel_mass", ["aircraft:*"]) -else: - x.connect("size", "fuel_mass", [ - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.TailBoom.LENGTH, - ]) - -x.connect("design_load", "fixed_mass", [ - "max_mach", - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, -]) -x.connect("design_load", "wing_mass", [ - Aircraft.Wing.ULTIMATE_LOAD_FACTOR -]) -x.connect("design_load", "fuel_mass", [ - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, -]) - -if simplified: - x.connect("fixed_mass", "wing_mass", [ - "aircraft:*", - "c_strut_braced", - "c_gear_loc", - "half_sweep", - ]) -else: - x.connect("fixed_mass", "wing_mass", [ - Aircraft.Wing.MATERIAL_FACTOR, - "c_strut_braced", - "c_gear_loc", - Aircraft.Engine.POSITION_FACTOR, - "half_sweep", - Aircraft.Wing.HIGH_LIFT_MASS, - ]) -if simplified: - x.connect("fixed_mass", "fuel_mass", [ - "aircraft:*", - "payload_mass_des", - "payload_mass_max", - "wing_mounted_mass", - "eng_comb_mass", - ]) -else: - x.connect("fixed_mass", "fuel_mass", [ - Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, - "payload_mass_des", - "payload_mass_max", - Aircraft.HorizontalTail.MASS, - Aircraft.VerticalTail.MASS, - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, - Aircraft.Propulsion.TOTAL_ENGINE_POD_MASS, - "wing_mounted_mass", - "eng_comb_mass", - ]) -x.connect("fixed_mass", "equip", [ - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, -]) -x.connect("equip", "fuel_mass", [ - Aircraft.Design.FIXED_EQUIPMENT_MASS, - Aircraft.Design.FIXED_USEFUL_LOAD, -]) -x.connect("wing_mass", "fuel_mass", [Aircraft.Wing.MASS]) - -x.write("mass_and_sizing_both_xdsm") -x.write_sys_specs("mass_and_sizing_both_specs") diff --git a/aviary/xdsm/mass_and_sizing_converter_configuration_test_xdsm.py b/aviary/xdsm/mass_and_sizing_converter_configuration_test_xdsm.py deleted file mode 100644 index d932a2407..000000000 --- a/aviary/xdsm/mass_and_sizing_converter_configuration_test_xdsm.py +++ /dev/null @@ -1,215 +0,0 @@ -""" -This XDSM is for the case with a strut but no fold, volume coefficients calculated, -no augmented system -""" - -from pyxdsm.XDSM import FUNC, GROUP, IGROUP, XDSM - -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -simplified = False - -# Create subsystem components -x.add_system("size", GROUP, ["SizeGroup"]) -x.add_system("design_load", GROUP, ["DesignLoadGroup"]) -x.add_system("fixed_mass", GROUP, ["FixedMassGroup"]) -x.add_system("equip", FUNC, ["FixedEquipAndUsefulMass"]) -x.add_system("wing_mass", IGROUP, ["WingMassGroup"]) -x.add_system("fuel_mass", IGROUP, ["FuelMassGroup"]) - -### make input connections ### - -if simplified is True: - x.add_input("size", ["InputValues"]) - x.add_input("design_load", ["InputValues"]) - x.add_input("fixed_mass", ["InputValues"]) - x.add_input("equip", ["InputValues"]) - x.add_input("wing_mass", ["InputValues"]) - x.add_input("fuel_mass", ["InputValues"]) -else: - x.add_input("size", [ - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, - Aircraft.Strut.AREA_RATIO, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - Aircraft.Wing.LOADING, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.VerticalTail.TAPER_RATIO, - Aircraft.Strut.ATTACHMENT_LOCATION_DIMENSIONLESS, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - ]) - x.add_input("design_load", [ - Aircraft.Wing.LOADING, - Aircraft.Design.MAX_STRUCTURAL_SPEED, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.MACH, - ]) - x.add_input("fixed_mass", [ - Aircraft.CrewPayload.CARGO_MASS, - Aircraft.VerticalTail.SWEEP, - Aircraft.HorizontalTail.MASS_COEFFICIENT, - Aircraft.LandingGear.TAIL_HOOK_MASS_SCALER, - Aircraft.VerticalTail.MASS_COEFFICIENT, - Aircraft.HorizontalTail.THICKNESS_TO_CHORD, - Aircraft.VerticalTail.THICKNESS_TO_CHORD, - Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, - Aircraft.Wing.SURFACE_CONTROL_MASS_COEFFICIENT, - Aircraft.Design.COCKPIT_CONTROL_MASS_COEFFICIENT, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS, - Aircraft.Controls.COCKPIT_CONTROL_MASS_SCALER, - Aircraft.Wing.SURFACE_CONTROL_MASS_SCALER, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS_SCALER, - Aircraft.Controls.CONTROL_MASS_INCREMENT, - Aircraft.LandingGear.MASS_COEFFICIENT, - Aircraft.LandingGear.MAIN_GEAR_MASS_COEFFICIENT, - Aircraft.Engine.MASS_SPECIFIC, - Aircraft.Nacelle.MASS_SPECIFIC, - Aircraft.Engine.PYLON_FACTOR, - Aircraft.Engine.ADDITIONAL_MASS_FRACTION, - Aircraft.Engine.MASS_SCALER, - Aircraft.Propulsion.MISC_MASS_SCALER, - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - "(prop_mass)", - "("+Aircraft.Nacelle.CLEARANCE_RATIO+")", - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.GROSS_MASS, - Aircraft.Strut.ATTACHMENT_LOCATION, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Engine.WING_LOCATIONS, - Aircraft.VerticalTail.TAPER_RATIO, - ]) - x.add_input("equip", [ - Mission.Design.GROSS_MASS, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.Design.EXTERNAL_SUBSYSTEMS_MASS, - Aircraft.Design.EQUIPMENT_MASS_COEFFICIENTS, - ]) - x.add_input("wing_mass", [ - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - Aircraft.Wing.MASS_COEFFICIENT, - Aircraft.Strut.MASS_COEFFICIENT, - ]) - x.add_input("fuel_mass", [ - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Mission.Design.GROSS_MASS, - Aircraft.Fuel.FUEL_MARGIN, - Aircraft.Fuselage.MASS_COEFFICIENT, - "pylon_len", - "MAT", - Aircraft.Wing.MASS_SCALER, - Aircraft.HorizontalTail.MASS_SCALER, - Aircraft.VerticalTail.MASS_SCALER, - Aircraft.Fuselage.MASS_SCALER, - Aircraft.LandingGear.TOTAL_MASS_SCALER, - Aircraft.Engine.POD_MASS_SCALER, - Aircraft.Design.STRUCTURAL_MASS_INCREMENT, - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, - Aircraft.Fuel.DENSITY, - ]) - -# make connection -x.connect("size", "design_load", [Aircraft.Wing.AVERAGE_CHORD]) -x.connect("size", "wing_mass", [Aircraft.Wing.SPAN]) -x.connect("size", "fixed_mass", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.Wing.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.HorizontalTail.SPAN, - Aircraft.HorizontalTail.MOMENT_ARM, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.Nacelle.SURFACE_AREA, - Aircraft.Nacelle.AVG_DIAMETER, -]) -x.connect("size", "equip", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.Wing.AREA, -]) -x.connect("size", "fuel_mass", [ - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.TailBoom.LENGTH, -]) - -x.connect("design_load", "fixed_mass", [ - "max_mach", - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR -]) -x.connect("design_load", "wing_mass", [Aircraft.Wing.ULTIMATE_LOAD_FACTOR]) -x.connect("design_load", "fuel_mass", [ - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, -]) - -x.connect("fixed_mass", "wing_mass", [ - Aircraft.Wing.MATERIAL_FACTOR, - "c_strut_braced", - "c_gear_loc", - Aircraft.Engine.POSITION_FACTOR, - "half_sweep", - Aircraft.Wing.HIGH_LIFT_MASS, -]) -x.connect("fixed_mass", "fuel_mass", [ - Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, - "payload_mass_des", - "payload_mass_max", - Aircraft.HorizontalTail.MASS, - Aircraft.VerticalTail.MASS, - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, - Aircraft.Propulsion.TOTAL_ENGINE_POD_MASS, - "wing_mounted_mass", - "eng_comb_mass", -]) -x.connect("fixed_mass", "equip", [ - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, -]) -x.connect("equip", "fuel_mass", [ - Aircraft.Design.FIXED_EQUIPMENT_MASS, - Aircraft.Design.FIXED_USEFUL_LOAD, -]) -x.connect("wing_mass", "fuel_mass", [Aircraft.Wing.MASS]) - -x.write("mass_and_sizing_converter_configuration_test_xdsm") -x.write_sys_specs("mass_and_sizing_strut_specs") diff --git a/aviary/xdsm/mass_and_sizing_large_single_aisle_1_xdsm.py b/aviary/xdsm/mass_and_sizing_large_single_aisle_1_xdsm.py deleted file mode 100644 index edbcf8d1a..000000000 --- a/aviary/xdsm/mass_and_sizing_large_single_aisle_1_xdsm.py +++ /dev/null @@ -1,213 +0,0 @@ -""" -This XDSM is for the case with no strut, no fold, volume coefficients input, no -augmented system -""" - -from pyxdsm.XDSM import FUNC, GROUP, IGROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -simplified = False - -# Create subsystem components -x.add_system("size", GROUP, ["SizeGroup"]) -x.add_system("design_load", GROUP, ["DesignLoadGroup"]) -x.add_system("fixed_mass", GROUP, ["FixedMassGroup"]) -x.add_system("equip", FUNC, ["FixedEquipAndUsefulMass"]) -x.add_system("wing_mass", IGROUP, ["WingMassGroup"]) -x.add_system("fuel_mass", IGROUP, ["FuelMassGroup"]) - -### make input connections ### - -if simplified is True: - x.add_input("size", ["InputValues"]) - x.add_input("design_load", ["InputValues"]) - x.add_input("fixed_mass", ["InputValues"]) - x.add_input("equip", ["InputValues"]) - x.add_input("wing_mass", ["InputValues"]) - x.add_input("fuel_mass", ["InputValues"]) -else: - x.add_input("size", [ - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Engine.REFERENCE_SLS_THRUST, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, - Aircraft.HorizontalTail.VOLUME_COEFFICIENT, - Aircraft.VerticalTail.VOLUME_COEFFICIENT, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - Aircraft.Wing.LOADING, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.VerticalTail.TAPER_RATIO, - ]) - x.add_input("design_load", [ - Aircraft.Design.MAX_STRUCTURAL_SPEED, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.MACH, - Aircraft.Wing.LOADING, - ]) - x.add_input("fixed_mass", [ - Aircraft.CrewPayload.CARGO_MASS, - Aircraft.VerticalTail.SWEEP, - Aircraft.HorizontalTail.MASS_COEFFICIENT, - Aircraft.LandingGear.TAIL_HOOK_MASS_SCALER, - Aircraft.VerticalTail.MASS_COEFFICIENT, - Aircraft.HorizontalTail.THICKNESS_TO_CHORD, - Aircraft.VerticalTail.THICKNESS_TO_CHORD, - Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, - Aircraft.Wing.SURFACE_CONTROL_MASS_COEFFICIENT, - Aircraft.Design.COCKPIT_CONTROL_MASS_COEFFICIENT, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS, - Aircraft.Controls.COCKPIT_CONTROL_MASS_SCALER, - Aircraft.Wing.SURFACE_CONTROL_MASS_SCALER, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS_SCALER, - Aircraft.Controls.CONTROL_MASS_INCREMENT, - Aircraft.LandingGear.MASS_COEFFICIENT, - Aircraft.LandingGear.MAIN_GEAR_MASS_COEFFICIENT, - Aircraft.Engine.MASS_SPECIFIC, - Aircraft.Nacelle.MASS_SPECIFIC, - Aircraft.Engine.PYLON_FACTOR, - Aircraft.Engine.ADDITIONAL_MASS_FRACTION, - Aircraft.Engine.MASS_SCALER, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Propulsion.MISC_MASS_SCALER, - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - Aircraft.Nacelle.CLEARANCE_RATIO, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.GROSS_MASS, - Aircraft.Strut.ATTACHMENT_LOCATION, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Engine.WING_LOCATIONS, - Aircraft.VerticalTail.TAPER_RATIO, - ]) - x.add_input("equip", [ - Aircraft.Design.EQUIPMENT_MASS_COEFFICIENTS, - Mission.Design.GROSS_MASS, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Aircraft.Fuel.WING_FUEL_FRACTION, - ]) - x.add_input("wing_mass", [ - Aircraft.Wing.MASS_COEFFICIENT, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - ]) - x.add_input("fuel_mass", [ - Aircraft.Fuel.FUEL_MARGIN, - Aircraft.Fuselage.MASS_COEFFICIENT, - "fus_and_struct.pylon_len", - "fus_and_struct.MAT", - Aircraft.Wing.MASS_SCALER, - Aircraft.HorizontalTail.MASS_SCALER, - Aircraft.VerticalTail.MASS_SCALER, - Aircraft.Fuselage.MASS_SCALER, - Aircraft.LandingGear.TOTAL_MASS_SCALER, - Aircraft.Engine.POD_MASS_SCALER, - Aircraft.Design.STRUCTURAL_MASS_INCREMENT, - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, - Aircraft.Fuel.DENSITY, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Mission.Design.GROSS_MASS, - ]) - -# make connections -x.connect("size", "design_load", [Aircraft.Wing.AVERAGE_CHORD]) -x.connect("size", "wing_mass", [Aircraft.Wing.SPAN]) -x.connect("size", "fixed_mass", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.Wing.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.HorizontalTail.SPAN, - Aircraft.HorizontalTail.MOMENT_ARM, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.Nacelle.SURFACE_AREA, - Aircraft.Nacelle.AVG_DIAMETER, -]) -x.connect("size", "equip", [ - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.Wing.AREA, -]) -x.connect("size", "fuel_mass", [ - Aircraft.Fuel.WING_VOLUME_STRUCTURAL_MAX, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.TailBoom.LENGTH, -]) - -x.connect("design_load", "fixed_mass", [ - "max_mach", - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, -]) -x.connect("design_load", "wing_mass", [Aircraft.Wing.ULTIMATE_LOAD_FACTOR]) -x.connect("design_load", "fuel_mass", [ - "min_dive_vel", Aircraft.Wing.ULTIMATE_LOAD_FACTOR]) - -x.connect("fixed_mass", "wing_mass", [ - Aircraft.Wing.MATERIAL_FACTOR, - "c_strut_braced", - "c_gear_loc", - Aircraft.Engine.POSITION_FACTOR, - "half_sweep", - Aircraft.Wing.HIGH_LIFT_MASS, -]) -x.connect("fixed_mass", "fuel_mass", [ - Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, - "payload_mass_des", - "payload_mass_max", - Aircraft.HorizontalTail.MASS, - Aircraft.VerticalTail.MASS, - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, - Aircraft.Propulsion.TOTAL_ENGINE_POD_MASS, - "wing_mounted_mass", - "eng_comb_mass", -]) -x.connect("fixed_mass", "equip", [ - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, -]) -x.connect("equip", "fuel_mass", [ - Aircraft.Design.FIXED_EQUIPMENT_MASS, - Aircraft.Design.FIXED_USEFUL_LOAD, -]) -x.connect("wing_mass", "fuel_mass", [Aircraft.Wing.MASS]) - -x.write("mass_and_sizing_large_single_aisle_1_xdsm") -# x.write_sys_specs("mass_and_sizing_large_single_aisle_1_specs") diff --git a/aviary/xdsm/mass_basic_xdsm.py b/aviary/xdsm/mass_basic_xdsm.py deleted file mode 100644 index 969c522b6..000000000 --- a/aviary/xdsm/mass_basic_xdsm.py +++ /dev/null @@ -1,202 +0,0 @@ -""" -This XDSM is for the case with no strut, no fold, volume coefficients input, no -augmented system -""" - -from pyxdsm.XDSM import FUNC, GROUP, IGROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -simplified = True - -# Create subsystem components -x.add_system("design_load", GROUP, ["DesignLoadGroup"]) -x.add_system("fixed_mass", GROUP, ["FixedMassGroup"]) -x.add_system("equip", FUNC, ["FixedEquipAndUsefulMass"]) -x.add_system("wing_mass", GROUP, ["WingMassGroup"]) -x.add_system("fuel_mass", GROUP, ["FuelMassGroup"]) - -### make input connections ### - -# design_load -if simplified: - x.add_input("design_load", ["InputValues"]) -else: - x.add_input("design_load", [ - Aircraft.Wing.LOADING, - Aircraft.Design.MAX_STRUCTURAL_SPEED, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.MACH, - Aircraft.Wing.AVERAGE_CHORD, - ]) - -# fixed_mass -if simplified: - x.add_input("fixed_mass", ["InputValues"]) -else: - x.add_input("fixed_mass", [ - Aircraft.CrewPayload.CARGO_MASS, - Aircraft.VerticalTail.SWEEP, - Aircraft.HorizontalTail.MASS_COEFFICIENT, - Aircraft.LandingGear.TAIL_HOOK_MASS_SCALER, - Aircraft.VerticalTail.MASS_COEFFICIENT, - Aircraft.HorizontalTail.THICKNESS_TO_CHORD, - Aircraft.VerticalTail.THICKNESS_TO_CHORD, - Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, - Aircraft.Wing.SURFACE_CONTROL_MASS_COEFFICIENT, - Aircraft.Design.COCKPIT_CONTROL_MASS_COEFFICIENT, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS, - Aircraft.Controls.COCKPIT_CONTROL_MASS_SCALER, - Aircraft.Wing.SURFACE_CONTROL_MASS_SCALER, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS_SCALER, - Aircraft.Controls.CONTROL_MASS_INCREMENT, - Aircraft.LandingGear.MASS_COEFFICIENT, - Aircraft.LandingGear.MAIN_GEAR_MASS_COEFFICIENT, - Aircraft.Engine.MASS_SPECIFIC, - Aircraft.Nacelle.MASS_SPECIFIC, - Aircraft.Engine.PYLON_FACTOR, - Aircraft.Engine.ADDITIONAL_MASS_FRACTION, - Aircraft.Engine.MASS_SCALER, - Aircraft.Propulsion.MISC_MASS_SCALER, - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - "(prop_mass)", - "("+Aircraft.Nacelle.CLEARANCE_RATIO+")", - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.GROSS_MASS, - Aircraft.Strut.ATTACHMENT_LOCATION, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Engine.WING_LOCATIONS, - Aircraft.VerticalTail.TAPER_RATIO, - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.Wing.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.HorizontalTail.SPAN, - Aircraft.HorizontalTail.MOMENT_ARM, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.Nacelle.SURFACE_AREA, - Aircraft.Nacelle.AVG_DIAMETER, - ]) - -# equip -if simplified: - x.add_input("equip", ["InputValues"]) -else: - x.connect("equip", [ - Aircraft.Design.EQUIPMENT_MASS_COEFFICIENTS, - Mission.Design.GROSS_MASS, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.Design.EXTERNAL_SUBSYSTEMS_MASS, - Aircraft.Fuselage.LENGTH, - Aircraft.Wing.SPAN, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.Wing.AREA, - ]) - -# wing_mass -if simplified: - x.add_input("wing_mass", ["InputValues"]) -else: - x.add_input("wing_mass", [ - Aircraft.Wing.MASS_COEFFICIENT, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Mission.Design.GROSS_MASS, - Aircraft.Wing.SPAN, - ]) - -# fuel_mass -if simplified: - x.add_input("fuel_mass", ["InputValues"]) -else: - x.add_input("fuel_mass", [ - Aircraft.Fuel.FUEL_MARGIN, - Aircraft.Fuselage.MASS_COEFFICIENT, - "fus_and_struct.pylon_len", - "fus_and_struct.MAT", - Aircraft.Wing.MASS_SCALER, - Aircraft.HorizontalTail.MASS_SCALER, - Aircraft.VerticalTail.MASS_SCALER, - Aircraft.Fuselage.MASS_SCALER, - Aircraft.LandingGear.TOTAL_MASS_SCALER, - Aircraft.Engine.POD_MASS_SCALER, - Aircraft.Design.STRUCTURAL_MASS_INCREMENT, - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, - Aircraft.Fuel.DENSITY, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Mission.Design.GROSS_MASS, - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.TailBoom.LENGTH, - ]) - - -x.connect("design_load", "fixed_mass", [ - "max_mach", - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR -]) -x.connect("design_load", "wing_mass", [Aircraft.Wing.ULTIMATE_LOAD_FACTOR]) -x.connect("design_load", "fuel_mass", [ - "min_dive_vel", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, -]) - -x.connect("fixed_mass", "wing_mass", [ - Aircraft.Wing.MATERIAL_FACTOR, - "c_strut_braced", - "c_gear_loc", - Aircraft.Engine.POSITION_FACTOR, - "half_sweep", - Aircraft.Wing.HIGH_LIFT_MASS, -]) -if simplified: - x.connect("fixed_mass", "fuel_mass", [ - "aircraft:*", - "payload_mass_des", - "payload_mass_max", - "wing_mounted_mass", - "eng_comb_mass", - ]) -else: - x.connect("fixed_mass", "fuel_mass", [ - Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, - "payload_mass_des", - "payload_mass_max", - Aircraft.HorizontalTail.MASS, - Aircraft.VerticalTail.MASS, - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, - Aircraft.Propulsion.TOTAL_ENGINE_POD_MASS, - "wing_mounted_mass", - "eng_comb_mass", - ]) -x.connect("fixed_mass", "equip", [ - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS -]) -x.connect("equip", "fuel_mass", [ - Aircraft.Design.FIXED_EQUIPMENT_MASS, - Aircraft.Design.FIXED_USEFUL_LOAD -]) -x.connect("wing_mass", "fuel_mass", [Aircraft.Wing.MASS]) - -x.write("mass_basic_xdsm") -# x.write_sys_specs("mass_basic_specs") diff --git a/aviary/xdsm/mass_large_single_aisle_1_xdsm.py b/aviary/xdsm/mass_large_single_aisle_1_xdsm.py deleted file mode 100644 index f8e517ee4..000000000 --- a/aviary/xdsm/mass_large_single_aisle_1_xdsm.py +++ /dev/null @@ -1,178 +0,0 @@ -"""Mass for large single aisle""" - -from pyxdsm.XDSM import GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -x.add_system("mass", GROUP, [r"\textbf{MassSummation}"]) -x.add_input("mass", [ - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.VerticalTail.TAPER_RATIO, - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, - Aircraft.Wing.LOADING, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Aircraft.Design.MAX_STRUCTURAL_SPEED, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.MACH, - Aircraft.Design.EQUIPMENT_MASS_COEFFICIENTS, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.Wing.SWEEP, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Strut.ATTACHMENT_LOCATION_DIMENSIONLESS, - Aircraft.CrewPayload.CARGO_MASS, - Aircraft.Engine.MASS_SPECIFIC, - Aircraft.Nacelle.MASS_SPECIFIC, - Aircraft.Engine.PYLON_FACTOR, - Aircraft.Engine.ADDITIONAL_MASS_FRACTION, - Aircraft.Engine.MASS_SCALER, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Propulsion.MISC_MASS_SCALER, - Aircraft.Engine.WING_LOCATIONS, - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.VerticalTail.SWEEP, - Aircraft.HorizontalTail.MASS_COEFFICIENT, - Aircraft.LandingGear.TAIL_HOOK_MASS_SCALER, - Aircraft.VerticalTail.MASS_COEFFICIENT, - Aircraft.HorizontalTail.THICKNESS_TO_CHORD, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - Aircraft.VerticalTail.THICKNESS_TO_CHORD, - Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, - Aircraft.Wing.SURFACE_CONTROL_MASS_COEFFICIENT, - Aircraft.Design.COCKPIT_CONTROL_MASS_COEFFICIENT, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS, - Aircraft.Controls.COCKPIT_CONTROL_MASS_SCALER, - Aircraft.Wing.SURFACE_CONTROL_MASS_SCALER, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS_SCALER, - Aircraft.Controls.CONTROL_MASS_INCREMENT, - Aircraft.LandingGear.MASS_COEFFICIENT, - Aircraft.LandingGear.MAIN_GEAR_MASS_COEFFICIENT, - Aircraft.Wing.MOUNTING_TYPE, - Aircraft.Fuel.DENSITY, - Aircraft.Fuel.FUEL_MARGIN, - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, - Aircraft.Fuselage.MASS_COEFFICIENT, - "fuel_mass.fus_and_struct.pylon_len", - "fuel_mass.fus_and_struct.MAT", - Aircraft.Wing.MASS_SCALER, - Aircraft.HorizontalTail.MASS_SCALER, - Aircraft.VerticalTail.MASS_SCALER, - Aircraft.Fuselage.MASS_SCALER, - Aircraft.LandingGear.TOTAL_MASS_SCALER, - Aircraft.Engine.POD_MASS_SCALER, - Aircraft.Design.STRUCTURAL_MASS_INCREMENT, - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, - Mission.Design.GROSS_MASS, - Aircraft.Wing.MASS_COEFFICIENT, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.HorizontalTail.VOLUME_COEFFICIENT, - Aircraft.VerticalTail.VOLUME_COEFFICIENT, - Aircraft.Nacelle.CLEARANCE_RATIO, - Aircraft.Wing.FLAP_CHORD_RATIO, - Aircraft.Wing.FLAP_SPAN_RATIO, - Aircraft.Wing.SLAT_CHORD_RATIO, - Aircraft.Wing.SLAT_SPAN_RATIO, - Mission.Landing.LIFT_COEFFICIENT_MAX, - "density", - Aircraft.Design.EXTERNAL_SUBSYSTEMS_MASS, -]) - -x.add_output("mass", [ - Aircraft.HorizontalTail.AREA, - Aircraft.HorizontalTail.SPAN, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.HorizontalTail.MOMENT_ARM, - Aircraft.HorizontalTail.AVERAGE_CHORD, - Aircraft.VerticalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.VerticalTail.AVERAGE_CHORD, - Aircraft.Nacelle.AVG_LENGTH, - Aircraft.Nacelle.SURFACE_AREA, - Aircraft.Fuselage.AVG_DIAMETER, - "size.fuselage.cabin_height", - "size.fuselage.cabin_len", - "size.fuselage.nose_height", - Aircraft.Fuselage.LENGTH, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.TailBoom.LENGTH, - Aircraft.Wing.AREA, - Aircraft.Wing.SPAN, - Aircraft.Wing.CENTER_CHORD, - Aircraft.Wing.AVERAGE_CHORD, - Aircraft.Wing.ROOT_CHORD, - Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, - Aircraft.Fuel.WING_VOLUME_STRUCTURAL_MAX, - "max_airspeed", - "vel_c", - "max_maneuver_factor", - "min_dive_vel", - "max_mach", - "density_ratio", - "V9", - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, - Aircraft.Design.FIXED_USEFUL_LOAD, - Aircraft.Design.FIXED_EQUIPMENT_MASS, - Aircraft.Wing.MATERIAL_FACTOR, - "c_strut_braced", - "c_gear_loc", - Aircraft.Engine.POSITION_FACTOR, - "half_sweep", - Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, - "payload_mass_des", - "payload_mass_max", - Aircraft.Propulsion.TOTAL_ENGINE_MASS, - Aircraft.Propulsion.TOTAL_ENGINE_POD_MASS, - "eng_comb_mass", - "wing_mounted_mass", - "fixed_mass.tail.loc_MAC_vtail", - Aircraft.HorizontalTail.MASS, - Aircraft.VerticalTail.MASS, - Aircraft.Wing.HIGH_LIFT_MASS, - Aircraft.Controls.TOTAL_MASS, - Aircraft.LandingGear.TOTAL_MASS, - "fixed_mass.main_gear_mass", - Aircraft.Fuel.AUXILIARY_FUEL_CAPACITY, - "fuel_mass.body_tank.extra_fuel_volume", - "fuel_mass.body_tank.max_extra_fuel_mass", - "fuel_mass.wingfuel_mass_min", - Aircraft.Fuel.TOTAL_CAPACITY, - "fuel_mass.fuel_and_oem.OEM_wingfuel_mass", - "fuel_mass.fuel_and_oem.OEM_fuel_vol", - Aircraft.Fuel.WING_VOLUME_DESIGN, - Aircraft.Design.OPERATING_MASS, - "fuel_mass.fuel_and_oem.payload_mass_max_fuel", - "fuel_mass.fuel_and_oem.volume_wingfuel_mass", - "fuel_mass.max_wingfuel_mass", - Aircraft.Fuel.WING_VOLUME_STRUCTURAL_MAX, - "fuel_mass.fus_mass_full", - Aircraft.Fuel.FUEL_SYSTEM_MASS, - Aircraft.Design.STRUCTURE_MASS, - Aircraft.Fuselage.MASS, - Mission.Design.FUEL_MASS, - Aircraft.Propulsion.MASS, - Mission.Design.FUEL_MASS_REQUIRED, - "fuel_mass.fuel_mass_min", - "wing_mass.isolated_wing_mass", - Aircraft.Wing.MASS, - Aircraft.Nacelle.AVG_DIAMETER, -], side="right") - -x.write("mass_large_single_aisle_1_xdsm") -x.write_sys_specs("mass_large_single_aisle_1_specs") diff --git a/aviary/xdsm/propulsion_engine_xdsm.py b/aviary/xdsm/propulsion_engine_xdsm.py deleted file mode 100644 index 7b38e400a..000000000 --- a/aviary/xdsm/propulsion_engine_xdsm.py +++ /dev/null @@ -1,64 +0,0 @@ -from pyxdsm.XDSM import XDSM, GROUP, FUNC, METAMODEL -from aviary.variable_info.variables import Aircraft, Mission, Dynamic - -x = XDSM() - -use_hybrid_throttle = False - -x.add_system("engine", METAMODEL, ["Engine~Interpolator"], stack=True) -x.add_system("scaling", FUNC, ["Engine~Scaling"], stack=True) -x.add_system("mux", FUNC, ["Performance~Mux"]) -x.add_system("sum", FUNC, ["Propulsion~Sum"]) - -# create inputs -prop_eng_inputs = [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MACH, - Dynamic.Mission.THROTTLE, -] -if use_hybrid_throttle: - prop_eng_inputs.append(r"\textcolor{gray}{"+Dynamic.Mission.HYBRID_THROTTLE+"}") -x.add_input("engine", prop_eng_inputs) - -x.add_input("scaling", [ - Dynamic.Mission.MACH, - Aircraft.Engine.SCALE_FACTOR, -] -) - -# make connections -x.connect("engine", "scaling", [ - 'thrust_net_unscaled', - 'thrust_net_max_unscaled', - 'fuel_flow_rate_unscaled', - 'electric_power_unscaled', - 'nox_rate_unscaled', -], stack=True) - -x.connect("scaling", "mux", [ - Dynamic.Mission.THRUST, - Dynamic.Mission.THRUST_MAX, - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE, - Dynamic.Mission.ELECTRIC_POWER, - Dynamic.Mission.NOX_RATE, -], stack=True) - -x.connect("mux", "sum", [ - Dynamic.Mission.THRUST, - Dynamic.Mission.THRUST_MAX, - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE, - Dynamic.Mission.ELECTRIC_POWER, - Dynamic.Mission.NOX_RATE, -]) - -# create outputs -x.add_output("engine", [Dynamic.Mission.TEMPERATURE_ENGINE_T4], side="right") -x.add_output("sum", [ - Dynamic.Mission.THRUST_MAX_TOTAL, - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - Dynamic.Mission.ELECTRIC_POWER_TOTAL, - Dynamic.Mission.NOX_RATE_TOTAL, -], side="right") - -x.write("propulsion_engine_xdsm") -# x.write_sys_specs("propulsion_engine_specs") diff --git a/aviary/xdsm/rotation_xdsm.py b/aviary/xdsm/rotation_xdsm.py deleted file mode 100644 index e812b41ed..000000000 --- a/aviary/xdsm/rotation_xdsm.py +++ /dev/null @@ -1,77 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -simplified = False - -# Create subsystem components -x.add_system("atmos", FUNC, ["USatm"]) -x.add_system("fc", FUNC, ["FlightConditions"]) -x.add_system("prop", GROUP, ["Propulsion"]) -x.add_system("aero", GROUP, [r"\textbf{Aerodynamics}"]) -x.add_system("eom", GROUP, [r"\textbf{EOM}"]) - -# create inputs -x.add_input("atmos", [Dynamic.Mission.ALTITUDE]) -x.add_input("fc", ["TAS"]) - -if simplified: - x.add_input("prop", ["InputValues"]) -else: - x.add_input("prop", [ - Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MACH, - Dynamic.Mission.THROTTLE - ]) - -if simplified: - x.add_input("aero", ["InputValues"]) -else: - x.add_input("aero", [ - "airport_alt", - Dynamic.Mission.ALTITUDE, - Aircraft.Wing.HEIGHT, - Aircraft.Wing.SPAN, - "alpha", - "flap_defl", - Mission.Design.GROSS_MASS, - Aircraft.Wing.AREA, - "t_init_gear", - "t_curr", - "dt_gear", - "t_init_flaps", - "dt_flaps", - ]) - -if simplified: - x.add_input("eom", ["InputValues"]) -else: - x.add_input("eom", [ - Dynamic.Mission.MASS, - "TAS", - Dynamic.Mission.FLIGHT_PATH_ANGLE, - Aircraft.Wing.INCIDENCE, - "alpha", - ]) - -# make connections -x.connect("atmos", "fc", ["rho", Dynamic.Mission.SPEED_OF_SOUND]) -x.connect("fc", "prop", [Dynamic.Mission.MACH]) -x.connect("fc", "aero", [Dynamic.Mission.DYNAMIC_PRESSURE, Dynamic.Mission.MACH]) -x.connect("prop", "eom", [Dynamic.Mission.THRUST_TOTAL]) -x.connect("aero", "eom", [Dynamic.Mission.LIFT, Dynamic.Mission.DRAG]) - -# create outputs -x.add_output("eom", [ - "TAS_rate", - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.DISTANCE_RATE, - "alpha_rate", - "normal_force", - "fuselage_pitch", -], side="right") - -x.write("rotation_xdsm") -x.write_sys_specs("rotation_specs") diff --git a/aviary/xdsm/run_all.py b/aviary/xdsm/run_all.py deleted file mode 100644 index 2cc4249fc..000000000 --- a/aviary/xdsm/run_all.py +++ /dev/null @@ -1,58 +0,0 @@ -import glob -import os -import subprocess -from importlib import import_module -from pathlib import Path - - -os.chdir(Path(__file__).parents[0]) - -for file_name in glob.iglob("*xdsm.py"): - xdsm = import_module(file_name[:-3]) - # remove the grayed out text decoration in spec files - spec_name = file_name[:-7] + "specs" - for item in xdsm.x.systems: - spec = item.spec_name - try: - str_line = "" - with open(spec_name + "/" + spec + ".json", "r") as fr: - for line in fr: - if "textcolor{gray}" in line: - str_line += line.replace( - "\\\\textcolor{gray}{", "").replace("}", "") - else: - str_line += line - with open(spec_name + "/" + spec + ".json", "w") as fw: - fw.write(str_line) - except: - pass - -# go through all *xdsm.py files again and replace "_" by "\_" -print("post-processing") -for file_name in glob.iglob("*xdsm.py"): - file_name = file_name[:-3] - print(f"post-processing {file_name}.tikz and {file_name}.tex") - # backup .tikz files, assuming Python 3.3+ - os.replace(f"{file_name}.tikz", f"{file_name}_save.tikz") - str_line = "" - # replace "_" by "\_" in .tikz files - with open(file_name+"_save.tikz", "r") as fr: - for line in fr: - if "begin{array}" in line: - index_begin = line.index("begin{array}") - str_line += line[:index_begin] + line[index_begin:].replace("_", "\\_") - else: - str_line += line - # save updated .tikz files - with open(file_name+".tikz", "w") as fw: - fw.write(str_line) - - # generate .pdf file again - cmd = f"pdflatex {file_name}.tex" - try: - output = subprocess.check_output(cmd.split()) - except subprocess.CalledProcessError as err: - print("Command '{}' failed. Return code: {}".format(cmd, err.returncode)) - os.remove(file_name+".log") - os.remove(file_name+".aux") - os.remove(file_name+"_save.tikz") diff --git a/aviary/xdsm/size_basic_xdsm.py b/aviary/xdsm/size_basic_xdsm.py deleted file mode 100644 index 2ad60f3f6..000000000 --- a/aviary/xdsm/size_basic_xdsm.py +++ /dev/null @@ -1,128 +0,0 @@ -"""This XDSM is for the case with no fold and no strut""" - -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -HAS_FOLD = False -HAS_STRUT = False -compute_volume_coeff = True # see empennage_size_xdsm.py - -x.add_system("fuselage", GROUP, ["FuselageGroup"]) -x.add_input("fuselage", [ - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, -]) -x.add_output("fuselage", [ - Aircraft.Fuselage.WETTED_AREA, - Aircraft.TailBoom.LENGTH, - "cabin_height", - "cabin_len", - "nose_height", -], side="right") - -x.add_system("wing", GROUP, [r"\textbf{WingGroup}"]) -wing_inputs = [ - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Mission.Design.GROSS_MASS, - Aircraft.Wing.LOADING, -] -if HAS_FOLD: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Wing.FOLDED_SPAN+"}") -else: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Fuel.WING_FUEL_FRACTION+"}") -if HAS_STRUT: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Strut.ATTACHMENT_LOCATION+"}") - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Strut.AREA_RATIO+"}") -x.add_input("wing", wing_inputs) -wing_outputs = [ - Aircraft.Wing.CENTER_CHORD, - Aircraft.Wing.ROOT_CHORD, - Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, - Aircraft.Wing.LEADING_EDGE_SWEEP, - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, -] -if HAS_FOLD: - wing_outputs.append("fold.nonfolded_taper_ratio") - wing_outputs.append(Aircraft.Wing.FOLDING_AREA) - wing_outputs.append("fold.nonfolded_wing_area") - wing_outputs.append("fold.tc_ratio_mean_folded") - wing_outputs.append("fold.nonfolded_AR") -if HAS_STRUT: - wing_outputs.append(Aircraft.Strut.LENGTH) - wing_outputs.append(Aircraft.Strut.CHORD) - wing_outputs.append("strut.strut_y") -x.add_output("wing", wing_outputs, side="right") - -x.add_system("empennage", GROUP, ["EmpennageSize"]) - -emp_inputs = [ - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.VerticalTail.TAPER_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, -] -if compute_volume_coeff: - emp_inputs.append( - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION+"}") -else: - emp_inputs.append( - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VOLUME_COEFFICIENT+"}") - emp_inputs.append(r"\textcolor{gray}{"+Aircraft.VerticalTail.VOLUME_COEFFICIENT+"}") -x.add_input("empennage", emp_inputs) -x.add_output("empennage", [ - Aircraft.VerticalTail.AREA, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.HorizontalTail.SPAN, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.VerticalTail.AREA, - Aircraft.VerticalTail.AVERAGE_CHORD, - Aircraft.HorizontalTail.AVERAGE_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.HorizontalTail.MOMENT_ARM, -], side="right") -x.connect("wing", "empennage", [ - Aircraft.Wing.SPAN, - Aircraft.Wing.AREA, - Aircraft.Wing.AVERAGE_CHORD -]) - -if compute_volume_coeff: - x.connect("fuselage", "empennage", [ - Aircraft.Fuselage.LENGTH, Aircraft.Fuselage.AVG_DIAMETER]) - -x.add_system("engine", FUNC, ["EngineSize"]) -x.add_input("engine", [ - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, -]) -x.add_output("engine", [ - Aircraft.Nacelle.AVG_DIAMETER, - Aircraft.Nacelle.AVG_LENGTH, - Aircraft.Nacelle.SURFACE_AREA, -], side="right") - -x.add_system("cable_size", FUNC, ["CableSize"]) -x.add_input("cable_size", [Aircraft.Engine.WING_LOCATIONS]) -x.add_output("cable_size", [Aircraft.Electrical.HYBRID_CABLE_LENGTH], side="right") - -x.connect("fuselage", "wing", [Aircraft.Fuselage.AVG_DIAMETER]) -x.connect("fuselage", "cable_size", [Aircraft.Fuselage.AVG_DIAMETER]) -x.connect("wing", "cable_size", [Aircraft.Wing.SPAN]) - -x.write("size_basic_xdsm") -x.write_sys_specs("size_basic_specs") diff --git a/aviary/xdsm/size_both1_xdsm.py b/aviary/xdsm/size_both1_xdsm.py deleted file mode 100644 index f898ce74f..000000000 --- a/aviary/xdsm/size_both1_xdsm.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -This XDSM is for the case with a fold and a strut, fold location based on strut -location. It also is the case where tail volume coefficients are NOT computed. -""" - -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -# Do not change the following flags. Otherwise, unittest will fail. -HAS_FOLD = True -HAS_STRUT = True -compute_volume_coeff = False # see empennage_size_xdsm.py - -x.add_system("fuselage", GROUP, ["FuselageGroup"]) -x.add_input("fuselage", [ - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, -]) -x.add_output("fuselage", [ - Aircraft.Fuselage.WETTED_AREA, - Aircraft.TailBoom.LENGTH, - "cabin_height", - "cabin_len", - "nose_height", -], side="right") - -x.add_system("wing", GROUP, [r"\textbf{WingGroup}"]) -wing_inputs = [ - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Mission.Design.GROSS_MASS, - Aircraft.Wing.LOADING, - Aircraft.Fuel.WING_FUEL_FRACTION, -] -if HAS_FOLD: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Wing.FOLDED_SPAN+"}") -if HAS_STRUT: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Strut.ATTACHMENT_LOCATION+"}") - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Strut.AREA_RATIO+"}") -x.add_input("wing", wing_inputs) -wing_outputs = [ - Aircraft.Wing.CENTER_CHORD, - Aircraft.Wing.ROOT_CHORD, - Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, - Aircraft.Wing.LEADING_EDGE_SWEEP, - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, -] -if HAS_FOLD: - wing_outputs.append("fold.nonfolded_taper_ratio") - wing_outputs.append(Aircraft.Wing.FOLDING_AREA) - wing_outputs.append("fold.nonfolded_wing_area") - wing_outputs.append("fold.tc_ratio_mean_folded") - wing_outputs.append("fold.nonfolded_AR") -if HAS_STRUT: - wing_outputs.append(Aircraft.Strut.LENGTH) - wing_outputs.append(Aircraft.Strut.CHORD) -x.add_output("wing", wing_outputs, side="right") - -x.add_system("empennage", GROUP, [r"\textbf{EmpennageSize}"]) - -emp_inputs = [ - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.VerticalTail.TAPER_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, -] -if compute_volume_coeff: - emp_inputs.append( - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION+"}") -else: - emp_inputs.append( - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VOLUME_COEFFICIENT+"}") - emp_inputs.append( - r"\textcolor{gray}{"+Aircraft.VerticalTail.VOLUME_COEFFICIENT+"}") -x.add_input("empennage", emp_inputs) -x.add_output("empennage", [ - Aircraft.VerticalTail.AREA, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.HorizontalTail.SPAN, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.VerticalTail.AREA, - Aircraft.VerticalTail.AVERAGE_CHORD, - Aircraft.HorizontalTail.AVERAGE_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.HorizontalTail.MOMENT_ARM, -], side="right") -x.connect("wing", "empennage", [ - Aircraft.Wing.SPAN, - Aircraft.Wing.AREA, - Aircraft.Wing.AVERAGE_CHORD -]) -if compute_volume_coeff: - x.connect("fuselage", "empennage", [ - Aircraft.Fuselage.LENGTH, Aircraft.Fuselage.AVG_DIAMETER]) - -x.add_system("engine", FUNC, ["EngineSize"]) -x.add_input("engine", [ - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, -]) -x.add_output("engine", [ - Aircraft.Nacelle.AVG_DIAMETER, - Aircraft.Nacelle.AVG_LENGTH, - Aircraft.Nacelle.SURFACE_AREA, -], side="right") - -x.add_system("cable_size", FUNC, ["CableSize"]) -x.add_input("cable_size", [Aircraft.Engine.WING_LOCATIONS]) -x.add_output("cable_size", [Aircraft.Electrical.HYBRID_CABLE_LENGTH], side="right") - -x.connect("fuselage", "wing", [Aircraft.Fuselage.AVG_DIAMETER]) -x.connect("fuselage", "cable_size", [Aircraft.Fuselage.AVG_DIAMETER]) -x.connect("wing", "cable_size", [Aircraft.Wing.SPAN]) - -x.write("size_both1_xdsm") -x.write_sys_specs("size_both1_specs") diff --git a/aviary/xdsm/size_both2_xdsm.py b/aviary/xdsm/size_both2_xdsm.py deleted file mode 100644 index a0affe55d..000000000 --- a/aviary/xdsm/size_both2_xdsm.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -This XDSM is for the case with a fold and a strut, fold location not based on strut -location. It also is the case where tail volume coefficients ARE computed. -""" - -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -# Do not change the following flags. Otherwise, unittest will fail. -HAS_FOLD = True -HAS_STRUT = True -compute_volume_coeff = True # see empennage_size_xdsm.py - -x.add_system("fuselage", GROUP, ["FuselageGroup"]) -x.add_input("fuselage", [ - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, -]) -x.add_output("fuselage", [ - Aircraft.Fuselage.WETTED_AREA, - Aircraft.TailBoom.LENGTH, - "cabin_height", - "cabin_len", - "nose_height", -], side="right") - -x.add_system("wing", GROUP, [r"\textbf{WingGroup}"]) -wing_inputs = [ - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.SWEEP, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Mission.Design.GROSS_MASS, - Aircraft.Wing.LOADING, - Aircraft.Fuel.WING_FUEL_FRACTION, -] -if HAS_FOLD: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Wing.FOLDED_SPAN+"}") -if HAS_STRUT: - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Strut.ATTACHMENT_LOCATION+"}") - wing_inputs.append(r"\textcolor{gray}{"+Aircraft.Strut.AREA_RATIO+"}") -x.add_input("wing", wing_inputs) -wing_outputs = [ - Aircraft.Wing.CENTER_CHORD, - Aircraft.Wing.ROOT_CHORD, - Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, - Aircraft.Wing.LEADING_EDGE_SWEEP, - Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX, -] -if HAS_FOLD: - wing_outputs.append("fold.nonfolded_taper_ratio") - wing_outputs.append(Aircraft.Wing.FOLDING_AREA) - wing_outputs.append("fold.nonfolded_wing_area") - wing_outputs.append("fold.tc_ratio_mean_folded") - wing_outputs.append("fold.nonfolded_AR") -if HAS_STRUT: - wing_outputs.append(Aircraft.Strut.LENGTH) - wing_outputs.append(Aircraft.Strut.CHORD) - wing_outputs.append("strut.strut_y") -x.add_output("wing", wing_outputs, side="right") - -x.add_system("empennage", GROUP, [r"\textbf{EmpennageSize}"]) - -emp_inputs = [ - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.VerticalTail.TAPER_RATIO, - Aircraft.HorizontalTail.TAPER_RATIO, -] -if compute_volume_coeff: - emp_inputs.append( - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION+"}") -else: - emp_inputs.append( - r"\textcolor{gray}{"+Aircraft.HorizontalTail.VOLUME_COEFFICIENT+"}") - emp_inputs.append(r"\textcolor{gray}{"+Aircraft.VerticalTail.VOLUME_COEFFICIENT+"}") -x.add_input("empennage", emp_inputs) -x.add_output("empennage", [ - Aircraft.VerticalTail.AREA, - Aircraft.HorizontalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.HorizontalTail.SPAN, - Aircraft.VerticalTail.ROOT_CHORD, - Aircraft.HorizontalTail.ROOT_CHORD, - Aircraft.VerticalTail.AREA, - Aircraft.VerticalTail.AVERAGE_CHORD, - Aircraft.HorizontalTail.AVERAGE_CHORD, - Aircraft.VerticalTail.MOMENT_ARM, - Aircraft.HorizontalTail.MOMENT_ARM, -], side="right") -x.connect("wing", "empennage", [ - Aircraft.Wing.SPAN, - Aircraft.Wing.AREA, - Aircraft.Wing.AVERAGE_CHORD, -]) -if compute_volume_coeff: - x.connect("fuselage", "empennage", [ - Aircraft.Fuselage.LENGTH, Aircraft.Fuselage.AVG_DIAMETER]) - -x.add_system("engine", FUNC, ["EngineSize"]) -x.add_input("engine", [ - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, -]) -x.add_output("engine", [ - Aircraft.Nacelle.AVG_DIAMETER, - Aircraft.Nacelle.AVG_LENGTH, - Aircraft.Nacelle.SURFACE_AREA, -], side="right") - -x.add_system("cable_size", FUNC, ["CableSize"]) -x.add_input("cable_size", [Aircraft.Engine.WING_LOCATIONS]) -x.add_output("cable_size", [Aircraft.Electrical.HYBRID_CABLE_LENGTH], side="right") - -x.connect("fuselage", "wing", [Aircraft.Fuselage.AVG_DIAMETER]) -x.connect("fuselage", "cable_size", [Aircraft.Fuselage.AVG_DIAMETER]) -x.connect("wing", "cable_size", [Aircraft.Wing.SPAN]) - -x.write("size_both2_xdsm") -x.write_sys_specs("size_both2_specs") diff --git a/aviary/xdsm/statics_xdsm.py b/aviary/xdsm/statics_xdsm.py deleted file mode 100644 index 761845370..000000000 --- a/aviary/xdsm/statics_xdsm.py +++ /dev/null @@ -1,442 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, OPT, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -simplified = False - -# Create subsystem components -if simplified is False: - x.add_system("shared", GROUP, ["SharedInputs"]) -else: - x.add_system("inputs", GROUP, ["Inputs"]) -x.add_system("opt", OPT, ["Optimizer"]) -x.add_system(Dynamic.Mission.MASS, GROUP, [r"\textbf{MassSummation}"]) -x.add_system("xform", FUNC, ["EventXform"]) -x.add_system("dymos", GROUP, ["Dymos"]) -x.add_system("taxi", GROUP, ["Taxi"]) -x.add_system("groundroll", GROUP, [r"\textbf{GroundRoll}"]) -x.add_system("rotation", GROUP, [r"\textbf{Rotation}"]) -x.add_system("ascent", GROUP, [r"\textbf{InitialAscent}"]) -x.add_system("accelerate", GROUP, [r"\textbf{Accelerate}"]) -x.add_system("climb1", GROUP, [r"\textbf{ClimbTo10kFt}"]) -x.add_system("climb2", GROUP, [r"\textbf{ClimbToCruise}"]) -x.add_system("poly", IFUNC, ["PolynomialFit"]) -x.add_system("cruise", GROUP, [r"\textbf{BreguetRange}"]) -x.add_system("descent1", GROUP, [r"\textbf{DescentTo10kFt}"]) -x.add_system("descent2", GROUP, [r"\textbf{DescentTo1kFt}"]) -x.add_system("landing", GROUP, [r"\textbf{Landing}"]) -x.add_system("fuelburn", FUNC, ["FuelBurn"]) -x.add_system("mass_diff", FUNC, ["MassDifference"]) -x.add_system(Dynamic.Mission.RANGE, FUNC, ["RangeConstraint"]) - -if simplified is False: - # independent vars input to ParamPort, common to all phases - ivc_params = [ - Aircraft.Wing.INCIDENCE, - Aircraft.Wing.HEIGHT, - Mission.Summary.FUEL_FLOW_SCALER, - Mission.Takeoff.AIRPORT_ALTITUDE, - Mission.Landing.AIRPORT_ALTITUDE, - Aircraft.Wing.FLAP_DEFLECTION_TAKEOFF, - Aircraft.Wing.FLAP_DEFLECTION_LANDING, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - Aircraft.Wing.SWEEP, - Aircraft.HorizontalTail.SWEEP, - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.Wing.MOUNTING_TYPE, - Aircraft.Design.STATIC_MARGIN, - Aircraft.Design.CG_DELTA, - Aircraft.Wing.FORM_FACTOR, - Aircraft.Fuselage.FORM_FACTOR, - Aircraft.Nacelle.FORM_FACTOR, - Aircraft.VerticalTail.FORM_FACTOR, - Aircraft.HorizontalTail.FORM_FACTOR, - Aircraft.Wing.FUSELAGE_INTERFERENCE_FACTOR, - Aircraft.Strut.FUSELAGE_INTERFERENCE_FACTOR, - Aircraft.Design.DRAG_COEFFICIENT_INCREMENT, - Aircraft.Fuselage.FLAT_PLATE_AREA_INCREMENT, - Aircraft.Wing.CENTER_DISTANCE, - Aircraft.Wing.MIN_PRESSURE_LOCATION, - Aircraft.Wing.MAX_THICKNESS_LOCATION, - Aircraft.Strut.AREA_RATIO, - Aircraft.Wing.ZERO_LIFT_ANGLE, - Aircraft.Design.SUPERCRITICAL_DIVERGENCE_SHIFT, - Aircraft.Wing.FLAP_CHORD_RATIO, - Mission.Design.LIFT_COEFFICIENT_MAX_FLAPS_UP, - Mission.Takeoff.LIFT_COEFFICIENT_MAX, - Mission.Landing.LIFT_COEFFICIENT_MAX, - Mission.Takeoff.LIFT_COEFFICIENT_FLAP_INCREMENT, - Mission.Landing.LIFT_COEFFICIENT_FLAP_INCREMENT, - Mission.Takeoff.DRAG_COEFFICIENT_FLAP_INCREMENT, - Mission.Landing.DRAG_COEFFICIENT_FLAP_INCREMENT, - Aircraft.Strut.CHORD, # normally would be output by sizing - Mission.Summary.GROSS_MASS, - ] - - # ParamPort inputs from mass/sizing - sizing_params = [ - Aircraft.Wing.AREA, - Aircraft.Wing.SPAN, - Aircraft.Wing.AVERAGE_CHORD, - Aircraft.Fuselage.AVG_DIAMETER, - Aircraft.HorizontalTail.AVERAGE_CHORD, - Aircraft.HorizontalTail.AREA, - Aircraft.HorizontalTail.SPAN, - Aircraft.VerticalTail.AVERAGE_CHORD, - Aircraft.VerticalTail.AREA, - Aircraft.VerticalTail.SPAN, - Aircraft.Fuselage.LENGTH, - Aircraft.Nacelle.AVG_LENGTH, - Aircraft.Fuselage.WETTED_AREA, - Aircraft.Nacelle.SURFACE_AREA, - Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, - # Aircraft.Strut.CHORD, - ] - - # Connect shared inputs - x.connect("shared", "taxi", ivc_params) - x.connect("shared", "groundroll", ivc_params) - x.connect("shared", "rotation", ivc_params) - x.connect("shared", "ascent", ivc_params) - x.connect("shared", "accelerate", ivc_params) - x.connect("shared", "climb1", ivc_params) - x.connect("shared", "climb2", ivc_params) - x.connect("shared", "cruise", ivc_params) - x.connect("shared", "descent1", ivc_params) - x.connect("shared", "descent2", ivc_params) - x.connect("shared", "landing", ivc_params) - -else: - x.connect("inputs", Dynamic.Mission.MASS, ["InputValue"]) - x.connect("inputs", "taxi", ["InputValue"]) - x.connect("inputs", "groundroll", ["InputValue"]) - x.connect("inputs", "rotation", ["InputValue"]) - x.connect("inputs", "ascent", ["InputValue"]) - x.connect("inputs", "accelerate", ["InputValue"]) - x.connect("inputs", "climb1", ["InputValue"]) - x.connect("inputs", "climb2", ["InputValue"]) - x.connect("inputs", "cruise", ["InputValue"]) - x.connect("inputs", "descent1", ["InputValue"]) - x.connect("inputs", "descent2", ["InputValue"]) - x.connect("inputs", "landing", ["InputValue"]) - -# Connect mass -x.connect( - Dynamic.Mission.MASS, - "mass_diff", - [Aircraft.Design.OPERATING_MASS, Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS], -) - -x.connect("opt", Dynamic.Mission.MASS, [Mission.Design.GROSS_MASS]) -x.connect("opt", "taxi", [Mission.Design.GROSS_MASS]) -x.connect("opt", "landing", [Mission.Design.GROSS_MASS]) -x.connect("fuelburn", "mass_diff", [Mission.Design.FUEL_MASS_REQUIRED]) - -# Connect sizing calculated values -if simplified is False: - x.connect(Dynamic.Mission.MASS, "taxi", sizing_params) - x.connect(Dynamic.Mission.MASS, "groundroll", sizing_params) - x.connect(Dynamic.Mission.MASS, "rotation", sizing_params) - x.connect(Dynamic.Mission.MASS, "ascent", sizing_params) - x.connect(Dynamic.Mission.MASS, "accelerate", sizing_params) - x.connect(Dynamic.Mission.MASS, "climb1", sizing_params) - x.connect(Dynamic.Mission.MASS, "climb2", sizing_params) - x.connect(Dynamic.Mission.MASS, "cruise", sizing_params) - x.connect(Dynamic.Mission.MASS, "descent1", sizing_params) - x.connect(Dynamic.Mission.MASS, "descent2", sizing_params) - x.connect(Dynamic.Mission.MASS, "landing", sizing_params) - -# Connect miscellaneous -x.connect("xform", "dymos", ["t_init_gear", "t_init_flaps"]) -x.connect("xform", "ascent", ["t_init_gear", "t_init_flaps"]) -x.connect("xform", "poly", ["t_init_gear", "t_init_flaps"]) - -if simplified is False: - # Create inputs - x.add_input( - Dynamic.Mission.MASS, - [ - Aircraft.HorizontalTail.TAPER_RATIO, - Aircraft.HorizontalTail.MOMENT_RATIO, - Aircraft.HorizontalTail.ASPECT_RATIO, - Aircraft.VerticalTail.MOMENT_RATIO, - Aircraft.VerticalTail.TAPER_RATIO, - Aircraft.Engine.REFERENCE_DIAMETER, - Aircraft.Nacelle.CORE_DIAMETER_RATIO, - Aircraft.Nacelle.FINENESS, - Aircraft.Fuselage.DELTA_DIAMETER, - Aircraft.Fuselage.NOSE_FINENESS, - Aircraft.Fuselage.PILOT_COMPARTMENT_LENGTH, - Aircraft.Fuselage.TAIL_FINENESS, - Aircraft.Fuselage.WETTED_AREA_FACTOR, - Aircraft.Wing.LOADING, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Aircraft.Design.MAX_STRUCTURAL_SPEED, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.SWEEP, - Mission.Design.MACH, - Aircraft.Design.EQUIPMENT_MASS_COEFFICIENTS, - Aircraft.Fuselage.PRESSURE_DIFFERENTIAL, - Aircraft.Fuel.WING_FUEL_FRACTION, - Aircraft.Wing.SWEEP, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Strut.ATTACHMENT_LOCATION_DIMENSIONLESS, - Aircraft.CrewPayload.CARGO_MASS, - Aircraft.Engine.MASS_SPECIFIC, - Aircraft.Nacelle.MASS_SPECIFIC, - Aircraft.Engine.PYLON_FACTOR, - Aircraft.Engine.ADDITIONAL_MASS_FRACTION, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Engine.SCALED_SLS_THRUST, - Aircraft.Engine.MASS_SCALER, - Aircraft.Propulsion.MISC_MASS_SCALER, - Aircraft.Engine.WING_LOCATIONS, - Aircraft.LandingGear.MAIN_GEAR_LOCATION, - Aircraft.VerticalTail.ASPECT_RATIO, - Aircraft.VerticalTail.SWEEP, - Aircraft.HorizontalTail.MASS_COEFFICIENT, - Aircraft.LandingGear.TAIL_HOOK_MASS_SCALER, - Aircraft.VerticalTail.MASS_COEFFICIENT, - Aircraft.HorizontalTail.THICKNESS_TO_CHORD, - Aircraft.HorizontalTail.VERTICAL_TAIL_FRACTION, - Aircraft.VerticalTail.THICKNESS_TO_CHORD, - Aircraft.Wing.HIGH_LIFT_MASS_COEFFICIENT, - Aircraft.Wing.SURFACE_CONTROL_MASS_COEFFICIENT, - Aircraft.Design.COCKPIT_CONTROL_MASS_COEFFICIENT, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS, - Aircraft.Controls.COCKPIT_CONTROL_MASS_SCALER, - Aircraft.Wing.SURFACE_CONTROL_MASS_SCALER, - Aircraft.Controls.STABILITY_AUGMENTATION_SYSTEM_MASS_SCALER, - Aircraft.Controls.CONTROL_MASS_INCREMENT, - Aircraft.LandingGear.MASS_COEFFICIENT, - Aircraft.LandingGear.MAIN_GEAR_MASS_COEFFICIENT, - Aircraft.Wing.MOUNTING_TYPE, - Aircraft.Fuel.DENSITY, - Aircraft.Fuel.FUEL_MARGIN, - Aircraft.Fuel.FUEL_SYSTEM_MASS_COEFFICIENT, - Aircraft.Fuselage.MASS_COEFFICIENT, - "fuel_mass.fus_and_struct.pylon_len", - "fuel_mass.fus_and_struct.MAT", - Aircraft.Wing.MASS_SCALER, - Aircraft.HorizontalTail.MASS_SCALER, - Aircraft.VerticalTail.MASS_SCALER, - Aircraft.Fuselage.MASS_SCALER, - Aircraft.LandingGear.TOTAL_MASS_SCALER, - Aircraft.Engine.POD_MASS_SCALER, - Aircraft.Design.STRUCTURAL_MASS_INCREMENT, - Aircraft.Fuel.FUEL_SYSTEM_MASS_SCALER, - Mission.Design.GROSS_MASS, - Aircraft.Wing.MASS_COEFFICIENT, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.HorizontalTail.VOLUME_COEFFICIENT, - Aircraft.VerticalTail.VOLUME_COEFFICIENT, - Aircraft.Nacelle.CLEARANCE_RATIO, - Aircraft.Wing.FLAP_CHORD_RATIO, - Aircraft.Wing.FLAP_SPAN_RATIO, - Aircraft.Wing.SLAT_CHORD_RATIO, - Aircraft.Wing.SLAT_SPAN_RATIO, - Mission.Landing.LIFT_COEFFICIENT_MAX, - "density", - Aircraft.Design.EXTERNAL_SUBSYSTEMS_MASS, - ], - ) - - aero_in = [ - 'aero_ramps.flap_factor:final_val', - 'aero_ramps.gear_factor:final_val', - 'aero_ramps.flap_factor:initial_val', - 'aero_ramps.gear_factor:initial_val', - ] - - # common propulsion inputs that are set internally but propagate up to IVC - prop_in = [ - Aircraft.Engine.SCALE_FACTOR, - ] - - x.add_input("taxi", ['throttle', - 'propulsion.vectorize_performance.t4_0', - 'propulsion.aircraft:engine:scale_factor']) - x.add_input("groundroll", - ["dt_flaps", - "dt_gear", - "t_init_gear", - "t_init_flaps", - Dynamic.Mission.FLIGHT_PATH_ANGLE, - Dynamic.Mission.ALTITUDE] + aero_in + - [ - 'throttle', - 'vectorize_performance.t4_0', - 'aircraft:engine:scale_factor', - ], - ) - x.add_input("rotation", - ["dt_flaps", "dt_gear", "t_init_gear", "t_init_flaps"] + - aero_in + prop_in + - ['throttle', 'vectorize_performance.t4_0'] - ) - x.add_input("ascent", ["dt_flaps", - "dt_gear", - 'vectorize_performance.t4_0', - 'throttle'] + aero_in + prop_in) - x.add_input("accelerate", ['throttle', - 'vectorize_performance.t4_0'] + prop_in) - x.add_input("climb1", ["speed_bal.rhs:EAS", - 'vectorize_performance.t4_0', - 'throttle'] + prop_in) - x.add_input("climb2", ["speed_bal.rhs:EAS", - 'vectorize_performance.t4_0', - 'throttle'] + prop_in) - x.add_input( - "cruise", - [ - 'prop_initial.prop.vectorize_performance.t4_0', - 'prop_final.prop.vectorize_performance.t4_0', - 'prop_final.prop.aircraft:engine:scale_factor', - 'prop_initial.prop.aircraft:engine:scale_factor' - ] - ) - x.add_input("descent1", ["speed_bal.rhs:mach", - 'aircraft:engine:scale_factor', - 'throttle', - 'vectorize_performance.t4_0']) - x.add_input("descent2", ["EAS", - 'aircraft:engine:scale_factor', - 'vectorize_performance.t4_0', - 'throttle']) - x.add_input( - "landing", - [ - Mission.Landing.AIRPORT_ALTITUDE, - Mission.Landing.OBSTACLE_HEIGHT, - Mission.Landing.INITIAL_MACH, - "alpha", - Mission.Landing.MAXIMUM_SINK_RATE, - Mission.Landing.GLIDE_TO_STALL_RATIO, - Mission.Landing.MAXIMUM_FLARE_LOAD_FACTOR, - Mission.Landing.TOUCHDOWN_SINK_RATE, - Mission.Landing.BRAKING_DELAY, - "dt_flaps", - "dt_gear", - "t_init_flaps_app", - "t_init_gear_app", - "t_init_flaps_td", - "t_init_gear_td", - "t_curr", - 'aero_ramps.flap_factor:final_val', - 'aero_ramps.gear_factor:final_val', - 'aero_ramps.flap_factor:initial_val', - 'aero_ramps.gear_factor:initial_val', - Dynamic.Mission.THROTTLE, - 'vectorize_performance.t4_0', - 'aircraft:engine:scale_factor' - ] - - ) - x.add_input("fuelburn", [Aircraft.Fuel.FUEL_MARGIN, Mission.Summary.GROSS_MASS]) - x.add_input(Dynamic.Mission.RANGE, [Mission.Design.RANGE]) - -# Create outputs -x.add_output("landing", [Mission.Landing.GROUND_DISTANCE], side="right") - -# Create phase continuities -x.connect("dymos", "groundroll", [Dynamic.Mission.MASS, "TAS", "t_curr"]) -x.connect("dymos", "rotation", [Dynamic.Mission.MASS, "TAS", - "alpha", Dynamic.Mission.FLIGHT_PATH_ANGLE, "t_curr", Dynamic.Mission.ALTITUDE]) -x.connect("dymos", "ascent", [Dynamic.Mission.MASS, Dynamic.Mission.ALTITUDE, - "TAS", Dynamic.Mission.FLIGHT_PATH_ANGLE, "t_curr", "alpha"]) -x.connect("dymos", "accelerate", [Dynamic.Mission.ALTITUDE, "TAS", Dynamic.Mission.MASS]) -x.connect("dymos", "climb1", [Dynamic.Mission.ALTITUDE, Dynamic.Mission.MASS]) -x.connect("dymos", "climb2", [Dynamic.Mission.ALTITUDE, Dynamic.Mission.MASS]) -x.connect("dymos", "poly", ["time_cp", "h_cp"]) -x.connect("dymos", "descent2", [Dynamic.Mission.ALTITUDE, Dynamic.Mission.MASS]) -x.connect("dymos", "landing", [Dynamic.Mission.MASS]) -x.connect("taxi", "groundroll", [Dynamic.Mission.MASS]) -x.connect("dymos", - "cruise", - [Dynamic.Mission.ALTITUDE, - Dynamic.Mission.MACH, - "mass_initial", - "cruise_time_initial", - "cruise_range_initial"], - ) -x.connect("dymos", "descent1", [Dynamic.Mission.ALTITUDE, Dynamic.Mission.MASS]) -x.connect("dymos", "fuelburn", [Mission.Landing.TOUCHDOWN_MASS]) -x.connect("dymos", Dynamic.Mission.RANGE, [Mission.Summary.RANGE]) -x.connect("cruise", "dymos", ["cruise_time_final", "cruise_range_final"]) - -# Add Design Variables -x.connect( - "opt", - "dymos", - [ - "cruise_mass_final", - "ascent:t_initial", - Mission.Takeoff.ASCENT_DURATION, - Mission.Design.GROSS_MASS, - ], -) -x.connect( - "opt", - "xform", - [ - "tau_gear", - "tau_flaps", - "ascent:t_initial", - Mission.Takeoff.ASCENT_DURATION, - ], -) -x.connect("opt", "groundroll", [Mission.Design.GROSS_MASS]) -x.connect("opt", "rotation", [Mission.Design.GROSS_MASS]) -x.connect("opt", "ascent", [Mission.Design.GROSS_MASS]) -x.connect("opt", "accelerate", [Mission.Design.GROSS_MASS]) -x.connect("opt", "climb1", [Mission.Design.GROSS_MASS]) -x.connect("opt", "climb2", [Mission.Design.GROSS_MASS]) -x.connect("opt", "cruise", [Mission.Design.GROSS_MASS, "mass_final"]) -x.connect("opt", "descent1", [Mission.Design.GROSS_MASS]) -x.connect("opt", "descent2", [Mission.Design.GROSS_MASS]) -x.connect("opt", "mass_diff", [Mission.Design.GROSS_MASS]) - -# Add Constraints -x.connect("dymos", "opt", [r"\mathcal{R}"]) -x.connect("mass_diff", "opt", [Mission.Constraints.MASS_RESIDUAL]) -x.connect(Dynamic.Mission.RANGE, "opt", [Mission.Constraints.RANGE_RESIDUAL]) -x.connect("poly", "opt", ["h_init_gear", "h_init_flaps"]) - -# Connect State Rates -x.connect( - "groundroll", "dymos", [ - "TAS_rate", Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, "distance_rate"]) -x.connect("rotation", - "dymos", - ["TAS_rate", - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - "distance_rate", - "alpha_rate"]) -x.connect("ascent", - "dymos", - ["TAS_rate", - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, - "distance_rate", - Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE], - ) -x.connect( - "accelerate", "dymos", [ - "TAS_rate", Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, "distance_rate"]) -x.connect("climb1", "dymos", [Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, "distance_rate"]) -x.connect("climb2", "dymos", [Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, "distance_rate"]) -x.connect("descent1", "dymos", [Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, "distance_rate"]) -x.connect("descent2", "dymos", [Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, "distance_rate"]) - -x.write("statics_xdsm") -x.write_sys_specs("statics_specs") diff --git a/aviary/xdsm/taxi_xdsm.py b/aviary/xdsm/taxi_xdsm.py deleted file mode 100644 index 28799da36..000000000 --- a/aviary/xdsm/taxi_xdsm.py +++ /dev/null @@ -1,30 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - -x = XDSM() - -# Create subsystem components -x.add_system("atmos", FUNC, ["USatm"]) -x.add_system("prop", GROUP, ["prop"]) -x.add_system("fuel", GROUP, [r"\textbf{taxifuel}"]) - -# create inputs -x.add_input("atmos", ["airport_alt"]) -x.add_input("prop", [ - Dynamic.Mission.THROTTLE, - Dynamic.Mission.ALTITUDE, -]) -x.add_input("fuel", [Mission.Summary.GROSS_MASS]) - -# make connections -x.connect("atmos", "prop", [ - Dynamic.Mission.TEMPERATURE, - Dynamic.Mission.STATIC_PRESSURE -]) -x.connect("prop", "fuel", [Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL]) - -# create output -x.add_output("fuel", ["mass"], side="right") - -x.write("taxi_xdsm") -x.write_sys_specs("taxi_specs") diff --git a/aviary/xdsm/wing_mass_xdsm.py b/aviary/xdsm/wing_mass_xdsm.py deleted file mode 100644 index 548a4f499..000000000 --- a/aviary/xdsm/wing_mass_xdsm.py +++ /dev/null @@ -1,56 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, IFUNC, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -HAS_STRUT = True -HAS_FOLD = True - -# Create subsystem components -x.add_system("wing_mass_solve", IFUNC, ["WingMassSolve"]) -x.add_system("tot_wing_mass", FUNC, ["WingMassTotal"]) - -# add inputs -x.add_input("wing_mass_solve", [ - Mission.Design.GROSS_MASS, - Aircraft.Wing.HIGH_LIFT_MASS, - Aircraft.Wing.ULTIMATE_LOAD_FACTOR, - Aircraft.Wing.MASS_COEFFICIENT, - Aircraft.Wing.MATERIAL_FACTOR, - Aircraft.Engine.POSITION_FACTOR, - Aircraft.Wing.SPAN, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - r"\textcolor{gray}{c_strut_braced}", - "c_gear_loc", - "half_sweep", -]) -mass_inputs = [] -if HAS_STRUT: - mass_inputs.append( - r"\textcolor{gray}{aircraft:strut:mass_coefficient}", - ) -if HAS_FOLD: - mass_inputs.append(r"\textcolor{gray}{aircraft:wing:area}") - mass_inputs.append(r"\textcolor{gray}{aircraft:wing:folding_area}") - mass_inputs.append(r"\textcolor{gray}{aircraft:wing:fold_mass_coefficient}") - -if HAS_STRUT or HAS_FOLD: - x.add_input("tot_wing_mass", mass_inputs) - -# connect up tot_wing_mass -x.connect("wing_mass_solve", "tot_wing_mass", ["isolated_wing_mass"]) - -mass_output = [Aircraft.Wing.MASS] -if HAS_STRUT: - mass_output.append( - r"\textcolor{gray}{aircraft:strut:mass}", - ) -if HAS_FOLD: - mass_output.append( - r"\textcolor{gray}{aircraft:wing:fold_mass}", - ) -x.add_output("tot_wing_mass", mass_output, side="right") - -x.write("wing_mass_xdsm") -x.write_sys_specs("wing_mass_specs") diff --git a/aviary/xdsm/wing_size_xdsm.py b/aviary/xdsm/wing_size_xdsm.py deleted file mode 100644 index 00477eb4c..000000000 --- a/aviary/xdsm/wing_size_xdsm.py +++ /dev/null @@ -1,42 +0,0 @@ -from pyxdsm.XDSM import FUNC, GROUP, XDSM -from aviary.variable_info.variables import Aircraft, Mission - -x = XDSM() - -# DIMENSIONAL_LOCATION_SPECIFIED = False -# This file assume HAS_FOLD = False and HAS_STRUT = False -# Otherwise, see wing_size_with_strut_fold_xdsm.py - -# Create subsystem components -x.add_system("wing_size", FUNC, ["WingSize"]) -x.add_system("wing_parameters", FUNC, ["WingParameters"]) - -# x.add_input("wing_size", [r"gross_wt_initial", r"wing_loading"]) -x.add_input("wing_size", [ - Mission.Design.GROSS_MASS, - Aircraft.Wing.LOADING, - Aircraft.Wing.ASPECT_RATIO, -]) - -# x.add_input("wing_parameters", [r"sweep_c4", r"(fuel_vol_frac)"]) -x.add_input("wing_parameters", [ - Aircraft.Wing.SWEEP, - Aircraft.Wing.ASPECT_RATIO, - Aircraft.Wing.TAPER_RATIO, - Aircraft.Wing.THICKNESS_TO_CHORD_ROOT, - Aircraft.Wing.THICKNESS_TO_CHORD_TIP, - Aircraft.Fuselage.AVG_DIAMETER, -]) - -x.connect("wing_size", "wing_parameters", [Aircraft.Wing.AREA, Aircraft.Wing.SPAN]) - -x.add_output("wing_parameters", [ - Aircraft.Wing.CENTER_CHORD, - Aircraft.Wing.AVERAGE_CHORD, - Aircraft.Wing.ROOT_CHORD, - Aircraft.Wing.THICKNESS_TO_CHORD_UNWEIGHTED, - Aircraft.Wing.LEADING_EDGE_SWEEP, -], side="right") - -x.write("wing_size_xdsm") -# x.write_sys_specs("wing_size_specs") diff --git a/environment.yml b/environment.yml index 101843014..0193e042e 100644 --- a/environment.yml +++ b/environment.yml @@ -134,7 +134,6 @@ dependencies: - openmdao==3.28.1.dev0 - parameterized==0.9.0 - pyoptsparse==2.9.1 - - pyxdsm==2.3.0 - requests==2.31.0 - setuptools==65.7.0 - simupy==1.1.2 diff --git a/setup.py b/setup.py index bf4a38729..fed8c5c50 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,8 @@ pkgname = "aviary" extras_require = { - "test": ["testflo", "pyxdsm", "pre-commit"], - "examples": ["openaerostruct", "ambiance", "boring_battery @ git+https://github.com/jcchin/boring.git"], + "test": ["testflo", "pre-commit"], + "examples": ["openaerostruct", "ambiance", "itables"], } all_packages = [] @@ -59,12 +59,14 @@ "visualization/assets/*", "visualization/assets/aviary_vars/*" ], - 'aviary.docs': ['*.py', - 'tests/*.py', - '*/*.md', - '*/*.ipynb', - '*/*/*.md', - '*/*/*.ipynb',], + f"{pkgname}.docs": [ + "*.py", + "tests/*.py", + "*/*.md", + "*/*.ipynb", + "*/*/*.md", + "*/*/*.ipynb", + ], f"{pkgname}.subsystems.aero.test.data": ["*.csv"], f"{pkgname}.subsystems.prop.test": ["*.csv"], },