From 171196eb888391c45a8d35be88ff37a47eccceb9 Mon Sep 17 00:00:00 2001 From: Eytan Adler <63426601+eytanadler@users.noreply.github.com> Date: Tue, 8 Feb 2022 10:26:59 -0500 Subject: [PATCH] Add compatibility for OpenMDAO 3.16 (#37) * Updated docs for updated OAS * Fixed src_indices error in OAS aerostruct tests (in OM 3.16.0), not sure if this is OM bug or expected * Addressed src_indices deprecation warning in takeoff phases * Small changes for src_indices to make backward compatible with OpenMDAO 3.10.0 * Simplified setup.py and GHA yaml to install only necessary packages (no more openmdao[docs]) * Added call directly to aerostructural tests to figure out why the stall * Changed parallel pool initialization in OAS integration to properly clean up pool * Trying to change OMP_NUM_THREADS to 1 to improve OAS training time * Try something different with environment variable * I was being dumb, there's a GHA way of setting environment vars * Debugging pytest on OAS stuff * Adding coverage to test pytest call * Added cleanup for multiprocessing with pytest-cov * Removed pytest-cov parallel pool cleanup since it doesn't work since python 3.8 (and removed commented out old steady flight phase) * Removed dependencies to old OpenMDAO docs modules (embed-code -> literalinclude) * Removed redbaron dependency, created docs extras_require, and removed OAS debug from GHA yaml * Bumped RTD build to use Python 3.8 * Added plot extras_require and installed docs extras_require on GHA * Removed some stuff from conf.py that's already in the sphinx_mdolab_theme one * Fixed code blocks so the use the MDO Lab 's Sphinx theme * Dedented ODE integration code in docs * Fixed monospaced font in docs * Removed commented out code in conf.py * Removed monospaced fonts from RTD headers * Fixed underline problem in RST files * Removed italics to fix monospace in prop modeling docs * Fixed a typo in docs that said OpenMDAO instead of OpenConcept --- .github/workflows/openconcept.yaml | 6 +- readthedocs.yml => .readthedocs.yml | 4 +- docs/_static/css/override_theme.css | 5 - docs/conf.py | 42 +------ docs/features/odeint/ode_integration.rst | 22 ++-- docs/features/openmdao/openmdao_basics.rst | 61 +++++----- docs/features/propulsion/prop_modeling.rst | 16 +-- docs/index.rst | 5 +- openconcept/__init__.py | 2 +- .../analysis/openaerostruct/aerostructural.py | 25 ++-- .../analysis/openaerostruct/drag_polar.py | 12 +- .../tests/test_aerostructural.py | 2 +- .../analysis/performance/solver_phases.py | 108 ++---------------- openconcept/components/splitter.py | 8 +- openconcept/utilities/dict_indepvarcomp.py | 8 +- .../utilities/math/add_subtract_comp.py | 2 +- .../utilities/math/combine_split_comp.py | 4 +- openconcept/utilities/math/sum_comp.py | 2 +- readme.md | 1 - setup.py | 7 +- 20 files changed, 111 insertions(+), 231 deletions(-) rename readthedocs.yml => .readthedocs.yml (84%) delete mode 100644 docs/_static/css/override_theme.css diff --git a/.github/workflows/openconcept.yaml b/.github/workflows/openconcept.yaml index 1f286fbd..f0e951dc 100644 --- a/.github/workflows/openconcept.yaml +++ b/.github/workflows/openconcept.yaml @@ -13,6 +13,8 @@ jobs: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] fail-fast: false + env: + OMP_NUM_THREADS: 1 defaults: run: shell: bash -l {0} @@ -29,8 +31,8 @@ jobs: conda install numpy scipy - name: Install run: | - pip install coverage pytest-cov sphinx_rtd_theme pip install .[testing] + pip install .[docs] - name: Download engine deck surrogate model run: | curl -L -o engine_kriging_surrogate_model.tar.gz http://umich.edu/~mdolaboratory/repo_files/openconcept/engine_kriging_surrogate_model.tar.gz @@ -44,7 +46,7 @@ jobs: run: | python -m pytest --cov-config=.coveragerc --cov=openconcept --cov-report=xml - name: Upload coverage to Codecov - if: ${{ matrix.os == 'ubuntu-latest'}} + if: ${{ matrix.os == 'ubuntu-latest' }} uses: codecov/codecov-action@v2 with: fail_ci_if_error: true diff --git a/readthedocs.yml b/.readthedocs.yml similarity index 84% rename from readthedocs.yml rename to .readthedocs.yml index c57c9d36..cf645257 100644 --- a/readthedocs.yml +++ b/.readthedocs.yml @@ -8,10 +8,10 @@ formats: - epub python: - version: 3.6 + version: 3.8 install: - method: pip path: . extra_requirements: - - testing + - docs system_packages: true diff --git a/docs/_static/css/override_theme.css b/docs/_static/css/override_theme.css deleted file mode 100644 index 4929a467..00000000 --- a/docs/_static/css/override_theme.css +++ /dev/null @@ -1,5 +0,0 @@ -@import url("theme.css"); - -.wy-nav-content { - max-width: 90%; -} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 0dfbb974..a860da33 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,8 +14,6 @@ # import os import sys -import subprocess -import openmdao import openconcept from sphinx_mdolab_theme.config import * @@ -207,21 +205,12 @@ def patched_parse(self): # ones. extensions = [ - 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'embed_code', - 'embed_options', - 'embed_compare', - 'embed_shell_cmd', - 'embed_bibtex', - 'embed_n2', - 'tags' + "sphinx_copybutton", ] autodoc_inherit_docstrings = False autodoc_member_order = 'bysource' @@ -247,43 +236,14 @@ def patched_parse(self): # Usually you set "language" from the command line for these cases. language = None -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - # -- Options for HTML output ------------------------------------------------- -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' -html_style = 'css/override_theme.css' -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - # -- Options for HTMLHelp output --------------------------------------------- diff --git a/docs/features/odeint/ode_integration.rst b/docs/features/odeint/ode_integration.rst index 25e473d6..5df184d1 100644 --- a/docs/features/odeint/ode_integration.rst +++ b/docs/features/odeint/ode_integration.rst @@ -6,19 +6,21 @@ ODE Integration OpenConcept includes built-in utilities for integrating ODEs, a frequent task in airplane performance analysis. -`Component`, `Group`, and Connections -------------------------------------- +Component, Group, and Connections +--------------------------------- If users are running examples or instantiating OpenConcept native components (e.g. the battery), there's no need to know what's going on under the hood in the ODE integration. However, for writing custom analysis routines, developing new components, or troubleshooting new airplane models, some background can be helpful. -The `openconcept.Trajectory` class acts just like an OpenMDAO `Group` except that it adds the ability to automatically link integrated states from one phase of a trajectory to the next using the `link_phases` method. -The `openconcept.Phase` class acts just like an OpenMDAO `Group` except it finds all the integrated states and automatically links the time duration variable to them. -It also collects the names of all the integrated states so that the `Trajectory` can find them and link them. -The `openconcept.IntegratorGroup` class again acts just like OpenMDAO's `Group` except it adds an ODE Integration component (called ode_integ), locates output variables tagged with the "integrate" tag, and automatically connects the tagged rate source to the integrator. -Any state you wish to be automatically integrated needs to be held in an `IntegratorGroup`. -However, `IntegratorGroup` instances can be buried deep in a model made up of mainly plain `Group`. +The ``openconcept.Trajectory`` class acts just like an OpenMDAO ``Group`` except that it adds the ability to automatically link integrated states from one phase of a trajectory to the next using the ``link_phases`` method. +The ``openconcept.Phase`` class acts just like an OpenMDAO ``Group`` except it finds all the integrated states and automatically links the time duration variable to them. +It also collects the names of all the integrated states so that the ``Trajectory`` can find them and link them. +The ``openconcept.IntegratorGroup`` class again acts just like OpenMDAO's ``Group`` except it adds an ODE Integration component (called ode_integ), locates output variables tagged with the "integrate" tag, and automatically connects the tagged rate source to the integrator. +Any state you wish to be automatically integrated needs to be held in an ``IntegratorGroup``. +However, ``IntegratorGroup`` instances can be buried deep in a model made up of mainly plain ``Group``. The following example illustrates the usage of this feature. The tags following the "integrate" tag define the name, units, and default values of the integrated output. -.. embed-code:: - openconcept.analysis.tests.test_trajectories.TestForDocs.trajectory_example +.. literalinclude:: /../openconcept/analysis/tests/test_trajectories.py + :pyobject: TestForDocs.trajectory_example + :language: python + :dedent: 4 diff --git a/docs/features/openmdao/openmdao_basics.rst b/docs/features/openmdao/openmdao_basics.rst index 2246be56..0a0eefb2 100644 --- a/docs/features/openmdao/openmdao_basics.rst +++ b/docs/features/openmdao/openmdao_basics.rst @@ -9,25 +9,26 @@ It is a Python-based environment for modeling, simulation, and optimization whic This greatly reduces the computational cost of solving nonlinear equations and performing gradient-based optimization. Users unfamiliar with OpenMDAO are encouraged to visit the `getting started `_ page on their doc site. -I will cover several portions of the OpenMDAO package that are used extensively in OpenMDAO. +I will cover several portions of the OpenMDAO package that are used extensively in OpenConcept. -`Component`, `Group`, and Connections -------------------------------------- -OpenMDAO models are generally made up of numerous `Component` instances. -Components can be combined into a `Group` of components. +Component, Group, and Connections +--------------------------------- +OpenMDAO models are generally made up of numerous ``Component`` instances. +Components can be combined into a ``Group`` of components. Each component (with some exceptions) has *inputs* and *outputs*. When outputs of one component are connected to inputs of another, components can be chained together and more complex models are formed. The following simple model calculates the drag on an airplane using a simple drag polar formulation. Pay attention to these four class methods which are very common in OpenMDAO models: - - The `initialize` method allows the user to define options which are instantiated with the component in a larger model. - - The `setup` method declares inputs, outputs, and the nature of the derivatives of outputs with respect to inputs (this is important for reasons that will be addressed later). - - The `compute` method establishes how the outputs should be computed. - - The `compute_partials` method tells the component how to compute partial derivatives. + - The ``initialize`` method allows the user to define options which are instantiated with the component in a larger model. + - The ``setup`` method declares inputs, outputs, and the nature of the derivatives of outputs with respect to inputs (this is important for reasons that will be addressed later). + - The ``compute`` method establishes how the outputs should be computed. + - The ``compute_partials`` method tells the component how to compute partial derivatives. -.. embed-code:: - openconcept.analysis.aerodynamics.PolarDrag +.. literalinclude:: /../openconcept/analysis/aerodynamics.py + :pyobject: PolarDrag + :language: python Connections in OpenMDAO can be `defined explicitly `_, or `semi-implicitly through variable promotion `_. Refer to the OpenMDAO docs for more details. @@ -36,23 +37,23 @@ In general, I use variable promotion if I have some high-level design parameters I use structured, unambiguous variable names so I can find and replace them if I need to refactor the code. Explicit connections are useful down at lower levels in the model where only one or two connections need to be made and it's unlikely that end users will edit the model. -The `Problem` Class -------------------- -To perform analysis and/or optimization, OpenMDAO requires a `Problem` object to be instantiated. -Examples of problems representative of aircraft design can be found in the OpenConcept `examples/` folder. +The Problem Class +----------------- +To perform analysis and/or optimization, OpenMDAO requires a ``Problem`` object to be instantiated. +Examples of problems representative of aircraft design can be found in the OpenConcept ``examples/`` folder. -The `Problem` object needs some attributes to be set in order to work properly. - - `problem.model` is an OpenMDAO `Group` containing all the necessary `Component` or `Group` objects to model the problem. It is the problem physics. - - `problem.driver` is the optimization algorithm (required in order to do MDO). A common choice of driver is the `ScipyOptimizeDriver`. - - `problem.nonlinear_solver` is required for problems which have implicit state variables. OpenMDAO's `NewtonSolver` is amazingly efficient at solving problems with accurate derivatives. - - `problem.linear_solver` is required when Newton methods are used for nonlinear systems, since a linear solve is a necssary component in computing the Newton step. +The ``Problem`` object needs some attributes to be set in order to work properly. + - ``problem.model`` is an OpenMDAO ``Group`` containing all the necessary ``Component`` or ``Group`` objects to model the problem. It is the problem physics. + - ``problem.driver`` is the optimization algorithm (required in order to do MDO). A common choice of driver is the ``ScipyOptimizeDriver``. + - ``problem.nonlinear_solver`` is required for problems which have implicit state variables. OpenMDAO's ``NewtonSolver`` is amazingly efficient at solving problems with accurate derivatives. + - ``problem.linear_solver`` is required when Newton methods are used for nonlinear systems, since a linear solve is a necssary component in computing the Newton step. If optimization is being done, the following methods must be employed: - - `problem.model.add_design_var()` method tells the optimizer which variables can be altered during the optimization. - - `problem.model.add_objective()` method sets the objective function for the optimization (the variable to be minimized/maximized). - - `problem.model.add_constraint()` method adds constraints (since most MDO problems are constrained in some way) + - ``problem.model.add_design_var()`` method tells the optimizer which variables can be altered during the optimization. + - ``problem.model.add_objective()`` method sets the objective function for the optimization (the variable to be minimized/maximized). + - ``problem.model.add_constraint()`` method adds constraints (since most MDO problems are constrained in some way) -Finally, the `problem.setup()` method is run once the model, settings, and optimization are all defined. This is required for OpenMDAO to work properly. +Finally, the ``problem.setup()`` method is run once the model, settings, and optimization are all defined. This is required for OpenMDAO to work properly. Setting and Accessing Values ---------------------------- @@ -64,8 +65,8 @@ If variable values need to be set (for example, initial design variable values), Running the Model/Optimization ------------------------------ -To run an analysis-only problem, the `problem.run_model()` method will execute all the components. -If optimization is being conducted, the `problem.run_driver()` method must be used instead. +To run an analysis-only problem, the ``problem.run_model()`` method will execute all the components. +If optimization is being conducted, the ``problem.run_driver()`` method must be used instead. Partial Derivatives ------------------- @@ -77,15 +78,15 @@ OpenMDAO uses the *partial* derivatives of each component and assembles them tog If you use the pre-built OpenConcept components, you don't need to worry about this at all. Enjoy the efficient and accurate solutions. If you build your own, custom components, you need to make sure that you're supplying accurate partial derivatives. -This usually means either ensuring your model can handle complex input and output variables (to use the complex step method), or supplying analytic derivatives to each component using the `compute_partials` method. -If you do this, you should make sure to use OpenMDAO's `check_partials` method regularly. +This usually means either ensuring your model can handle complex input and output variables (to use the complex step method), or supplying analytic derivatives to each component using the ``compute_partials`` method. +If you do this, you should make sure to use OpenMDAO's ``check_partials`` method regularly. **Errors in partial derivatives can cause MAJOR Newton and optimizer convergence issues which can be difficult to debug.** Recording and Retrieving Results -------------------------------- OpenMDAO includes a SQLlite database interface for recording the results of model/optimization runs. -First, instantiate an `openmdao.api.SqliteRecorder` object. Then attach the object to the problem.model object, like this: +First, instantiate an ``openmdao.api.SqliteRecorder`` object. Then attach the object to the problem.model object, like this: .. code-block:: python @@ -99,7 +100,7 @@ While the utility has many uses (see the OpenMDAO docs), every user should make Putting it all Together ----------------------- -The `examples/TBM850.py` `script `_ models a single-engine turboprop aircraft and uses all of the elements mentioned on this page in an OpenConcept context. +The ``examples/TBM850.py`` `script `_ models a single-engine turboprop aircraft and uses all of the elements mentioned on this page in an OpenConcept context. diff --git a/docs/features/propulsion/prop_modeling.rst b/docs/features/propulsion/prop_modeling.rst index 8ce01e4e..06c486b5 100644 --- a/docs/features/propulsion/prop_modeling.rst +++ b/docs/features/propulsion/prop_modeling.rst @@ -11,22 +11,24 @@ Single Turboprop Example ------------------------ This example illustrates the simples possible case (turboprop engine connected to a propeller). -The propulsion system is instantiated as an OpenMDAO `Group`. +The propulsion system is instantiated as an OpenMDAO ``Group``. -*Source: `examples/propulsion_layouts/simple_turboprop.py`* +Source: ``examples/propulsion_layouts/simple_turboprop.py`` -.. embed-code:: - examples.propulsion_layouts.simple_turboprop.TurbopropPropulsionSystem +.. literalinclude:: /../examples/propulsion_layouts/simple_turboprop.py + :pyobject: TurbopropPropulsionSystem + :language: python Series Hybrid Example --------------------- This example illustrates the complexities which arise when electrical components are included. -*Source: `examples.propulsion_layouts.simple_series_hybrid.py`* +Source: ``examples/propulsion_layouts/simple_series_hybrid.py`` -.. embed-code:: - examples.propulsion_layouts.simple_series_hybrid.SingleSeriesHybridElectricPropulsionSystem +.. literalinclude:: /../examples/propulsion_layouts/simple_series_hybrid.py + :pyobject: SingleSeriesHybridElectricPropulsionSystem + :language: python Components ---------- diff --git a/docs/index.rst b/docs/index.rst index 270ca4c3..30c36ea9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,7 +34,6 @@ Getting Started Dependencies ------------ This toolkit requires the use of `OpenMDAO `__ 3.10.0 or later due to backward-incompatible changes. OpenMDAO requires a recent NumPy and SciPy. -If you'd like to use the OpenAeroStruct features of OpenConcept, OpenMDAO 3.10 is required because that is the newest version supported by OpenAeroStruct at the time of writing this. Python 3.8 is recommended since it is the version with which the code is tested, but newer Python versions will likely work as well. --------------- @@ -48,7 +47,7 @@ Benjamin J. Brelje and Joaquim R.R.A. Martins. 2018 AIAA/IEEE Electric Aircraft Technologies Symposium, AIAA Propulsion and Energy Forum, (AIAA 2018-4979) DOI: 10.2514/6.2018-4979 -:: +.. code-block:: bibtex @inproceedings{Brelje2018, address = {{C}incinnati,~{OH}}, @@ -64,7 +63,7 @@ If using the integrated OpenAeroStruct VLM or aerostructural aerodynamic models, Eytan J. Adler and Joaquim R.R.A. Martins, "Aerostructural wing design optimization considering full mission analysis", 2022 AIAA SciTech Forum, San Diego, CA, January 2022. DOI: 10.2514/6.2022-0382 -:: +.. code-block:: bibtex @inproceedings{Adler2022a, author = {Eytan J. Adler and Joaquim R. R. A. Martins}, diff --git a/openconcept/__init__.py b/openconcept/__init__.py index abeeedbf..f0ede3d3 100644 --- a/openconcept/__init__.py +++ b/openconcept/__init__.py @@ -1 +1 @@ -__version__ = '0.4.0' +__version__ = '0.4.1' diff --git a/openconcept/analysis/openaerostruct/aerostructural.py b/openconcept/analysis/openaerostruct/aerostructural.py index b5d66456..72457e92 100644 --- a/openconcept/analysis/openaerostruct/aerostructural.py +++ b/openconcept/analysis/openaerostruct/aerostructural.py @@ -4,7 +4,7 @@ import openmdao.api as om from time import time from copy import copy, deepcopy -import multiprocessing.pool as mp +import multiprocessing as mp import warnings # Progress bar @@ -622,11 +622,11 @@ def compute_training_data(inputs, surf_dict=None): warnings.filterwarnings("ignore", category=np.ComplexWarning) # Initialize the parallel pool and compute the OpenAeroStruct data - parallel_pool = mp.Pool() - if progress_bar: - out = list(tqdm.tqdm(parallel_pool.imap(compute_aerodynamic_data, test_points), total=len(test_points))) - else: - out = list(parallel_pool.map(compute_aerodynamic_data, test_points)) + with mp.Pool() as parallel_pool: + if progress_bar: + out = list(tqdm.tqdm(parallel_pool.imap(compute_aerodynamic_data, test_points), total=len(test_points))) + else: + out = list(parallel_pool.map(compute_aerodynamic_data, test_points)) # Initialize output arrays CL = np.zeros(inputs["Mach_number_grid"].shape) @@ -834,6 +834,12 @@ class Aerostruct(om.Group): the _cp options are not supported. The input ac|geom|wing|twist is the same as modifying the twist_cp option in the surface dictionary. The mesh geometry modification is limited to adjusting the input parameters to this component. + set_defaults : bool + If True, sets Mach number and alpha input defaults. This option should almost never need + to be set!! It is for the edge case where OASAerostructDragPolarExact vectorizes this + component directly. For some reason, setting the input defaults here (at least in + OpenMDAO 3.16.0) prevents the proper shape from being set in OASAerostructDragPolarExact. + (default, True) """ def __init__(self, **kwargs): @@ -848,6 +854,7 @@ def initialize(self): self.options.declare("num_skin", default=4, desc="Number of skin thickness spline control points") self.options.declare("num_spar", default=4, desc="Number of spar thickness spline control points") self.options.declare("surf_options", default=None, desc="Dictionary of OpenAeroStruct surface options") + self.options.declare("set_defaults", default=True, desc="Sets input defaults for Mach number and alpha") def setup(self): n_x = int(self.options["num_x"]) @@ -1277,8 +1284,9 @@ def setup(self): self.connect("sound_speed.fltcond|a", "aerostruct_point.speed_of_sound") # Set input defaults for inputs that go to multiple locations - self.set_input_defaults("fltcond|M", 0.1) - self.set_input_defaults("fltcond|alpha", 0.0) + if self.options["set_defaults"]: + self.set_input_defaults("fltcond|M", 0.1) + self.set_input_defaults("fltcond|alpha", 0.0) self.set_input_defaults("load_factor", 1.0) self.set_input_defaults("aerostruct_point.coupled.wing.nodes", np.zeros((n_y, 3)), units="m") self.set_input_defaults("W0", 1.0, units="kg") # unused variable but must be set since promoted @@ -1427,6 +1435,7 @@ def setup(self): num_skin=self.options["num_skin"], num_spar=self.options["num_spar"], surf_options=self.options["surf_options"], + set_defaults=False, ), promotes_inputs=[ "ac|geom|wing|S_ref", diff --git a/openconcept/analysis/openaerostruct/drag_polar.py b/openconcept/analysis/openaerostruct/drag_polar.py index 0a5e2990..ccfc6dcf 100644 --- a/openconcept/analysis/openaerostruct/drag_polar.py +++ b/openconcept/analysis/openaerostruct/drag_polar.py @@ -4,7 +4,7 @@ import openmdao.api as om from time import time from copy import copy, deepcopy -import multiprocessing.pool as mp +import multiprocessing as mp # Progress bar progress_bar = True @@ -479,11 +479,11 @@ def compute_training_data(inputs, surf_dict=None): row[-1] = inputs_to_send # Initialize the parallel pool and compute the OpenAeroStruct data - parallel_pool = mp.Pool() - if progress_bar: - out = list(tqdm.tqdm(parallel_pool.imap(compute_aerodynamic_data, test_points), total=len(test_points))) - else: - out = list(parallel_pool.map(compute_aerodynamic_data, test_points)) + with mp.Pool() as parallel_pool: + if progress_bar: + out = list(tqdm.tqdm(parallel_pool.imap(compute_aerodynamic_data, test_points), total=len(test_points))) + else: + out = list(parallel_pool.map(compute_aerodynamic_data, test_points)) # Initialize output arrays CL = np.zeros(inputs["Mach_number_grid"].shape) diff --git a/openconcept/analysis/openaerostruct/tests/test_aerostructural.py b/openconcept/analysis/openaerostruct/tests/test_aerostructural.py index ce6a1f6c..f5080d62 100644 --- a/openconcept/analysis/openaerostruct/tests/test_aerostructural.py +++ b/openconcept/analysis/openaerostruct/tests/test_aerostructural.py @@ -374,7 +374,7 @@ def test_defaults(self): p.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=True, atol=1e-8, rtol=1e-10) p.model.linear_solver = om.DirectSolver() p.model.set_input_defaults( - "fltcond|CL", np.array([0.094142402327027, 0.158902999486838, 0.223695460208479]), units="deg" + "fltcond|CL", np.array([0.094142402327027, 0.158902999486838, 0.223695460208479]), ) p.model.set_input_defaults("fltcond|M", np.full(nn, 0.85)) p.model.set_input_defaults("fltcond|h", np.full(nn, 7.5e3), units="m") diff --git a/openconcept/analysis/performance/solver_phases.py b/openconcept/analysis/performance/solver_phases.py index 624b482e..0c8bde9f 100644 --- a/openconcept/analysis/performance/solver_phases.py +++ b/openconcept/analysis/performance/solver_phases.py @@ -780,100 +780,6 @@ def setup(self): self.add_subsystem('steadyflt',BalanceComp(name='throttle',val=np.ones((nn,))*0.5,lower=0.01,upper=1.05,units=None,normalize=False,eq_units='m/s**2',rhs_name='accel_horiz',lhs_name='zero_accel',rhs_val=np.zeros((nn,))), promotes_inputs=['accel_horiz','zero_accel'],promotes_outputs=['throttle']) -# class OldSteadyFlightPhase(Group): -# """ -# This component group models steady flight conditions. -# Settable mission parameters include: -# Airspeed (fltcond|Ueas) -# Vertical speed (fltcond|vs) -# Duration of the segment (duration) - -# Throttle is set automatically to ensure steady flight - -# The BaseAircraftGroup object is passed in. -# The BaseAircraftGroup should be built to accept the following inputs -# and return the following outputs. -# The outputs should be promoted to the top level in the component. - -# Inputs -# ------ -# range : float -# Total distance travelled (vector, m) -# fltcond|h : float -# Altitude (vector, m) -# fltcond|vs : float -# Vertical speed (vector, m/s) -# fltcond|Ueas : float -# Equivalent airspeed (vector, m/s) -# fltcond|Utrue : float -# True airspeed (vector, m/s) -# fltcond|p : float -# Pressure (vector, Pa) -# fltcond|rho : float -# Density (vector, kg/m3) -# fltcond|T : float -# Temperature (vector, K) -# fltcond|q : float -# Dynamic pressure (vector, Pa) -# fltcond|CL : float -# Lift coefficient (vector, dimensionless) -# throttle : float -# Motor / propeller throttle setting scaled from 0 to 1 or slightly more (vector, dimensionless) -# propulsor_active : float -# If a multi-propulsor airplane, a failure condition should be modeled in the propulsion model by multiplying throttle by propulsor_active. -# It will generally be 1.0 unless a failure condition is being modeled, in which case it will be 0 (vector, dimensionless) -# braking : float -# Brake friction coefficient (default 0.4 for dry runway braking, 0.03 for resistance unbraked) -# Should not be applied in the air or nonphysical effects will result (vector, dimensionless) -# lift : float -# Lift force (vector, N) - -# Outputs -# ------- -# thrust : float -# Total thrust force produced by all propulsors (vector, N) -# drag : float -# Total drag force in the airplane axis produced by all sources of drag (vector, N) -# weight : float -# Weight (mass, really) of the airplane at each point in time. (vector, kg) -# ac|geom|wing|S_ref -# Wing reference area (scalar, m**2) -# ac|aero|CLmax_TO -# CLmax with flaps in max takeoff position (scalar, dimensionless) -# ac|weights|MTOW -# Maximum takeoff weight (scalar, kg) -# """ -# def initialize(self): -# self.options.declare('num_nodes',default=1) -# self.options.declare('flight_phase',default=None,desc='Phase of flight e.g. v0v1, cruise') -# self.options.declare('aircraft_model',default=None) - -# def setup(self): -# nn = self.options['num_nodes'] -# ivcomp = self.add_subsystem('const_settings', IndepVarComp(), promotes_outputs=["*"]) -# ivcomp.add_output('propulsor_active', val=np.ones(nn)) -# ivcomp.add_output('braking', val=np.zeros(nn)) -# ivcomp.add_output('fltcond|Ueas',val=np.ones((nn,))*90, units='m/s') -# ivcomp.add_output('fltcond|vs',val=np.ones((nn,))*1, units='m/s') -# ivcomp.add_output('zero_accel',val=np.zeros((nn,)),units='m/s**2') - -# self.add_subsystem('inth',Integrator(num_nodes=nn, method='simpson', quantity_units='m', diff_units='s', time_setup='duration'), -# promotes_inputs=[('dqdt','fltcond|vs'),'duration',('q_initial','fltcond|h_initial')],promotes_outputs=[('q','fltcond|h'),('q_final','fltcond|h_final')]) -# self.add_subsystem('atmos', ComputeAtmosphericProperties(num_nodes=nn, true_airspeed_in=False), promotes_inputs=['*'], promotes_outputs=['*']) -# self.add_subsystem('gs',Groundspeeds(num_nodes=nn),promotes_inputs=['*'],promotes_outputs=['*']) -# # add the user-defined aircraft model -# self.add_subsystem('acmodel',self.options['aircraft_model'](num_nodes=nn, flight_phase=self.options['flight_phase']),promotes_inputs=['*'],promotes_outputs=['*']) -# self.add_subsystem('clcomp',SteadyFlightCL(num_nodes=nn), promotes_inputs=['*'],promotes_outputs=['*']) -# self.add_subsystem('lift',Lift(num_nodes=nn), promotes_inputs=['*'],promotes_outputs=['*']) -# self.add_subsystem('haccel',HorizontalAcceleration(num_nodes=nn), promotes_inputs=['*'],promotes_outputs=['*']) - -# self.add_subsystem('intrange',Integrator(num_nodes=nn, method='simpson', quantity_units='m', diff_units='s', time_setup='duration'), -# promotes_inputs=[('dqdt','fltcond|groundspeed'),'duration',('q_initial','range_initial')],promotes_outputs=[('q','range'),('q_final','range_final')]) - - -# self.add_subsystem('steadyflt',BalanceComp(name='throttle',val=np.ones((nn,))*0.5,lower=0.01,upper=2.0,units=None,normalize=False,eq_units='m/s**2',rhs_name='accel_horiz',lhs_name='zero_accel',rhs_val=np.zeros((nn,))), -# promotes_inputs=['accel_horiz','zero_accel'],promotes_outputs=['throttle']) - class ClimbAnglePhase(Group): """ This component checks the climb angle for a @@ -995,8 +901,8 @@ def initialize(self): self.options.declare('h_obstacle',default=10.66,desc='Obstacle clearance height in m') self.options.declare('load_factor', default=1.2, desc='Load factor during circular arc transition') def setup(self): - self.add_input('fltcond|Utrue', units='m/s', src_indices=0, flat_src_indices=True) - self.add_input('gamma', units='rad', src_indices=0, flat_src_indices=True) + self.add_input('fltcond|Utrue', units='m/s') + self.add_input('gamma', units='rad') self.add_output('s_transition', units='m') self.add_output('h_transition', units='m') self.add_output('t_transition',units='s') @@ -1077,8 +983,8 @@ def initialize(self): self.options.declare('h_obstacle',default=10.66,desc='Obstacle clearance height in m') def setup(self): self.add_input('h_transition', units='m') - self.add_input('gamma', units='rad',src_indices=-1, flat_src_indices=True) - self.add_input('fltcond|Utrue', units='m/s',src_indices=-1, flat_src_indices=True) + self.add_input('gamma', units='rad') + self.add_input('fltcond|Utrue', units='m/s') self.add_output('s_climb', units='m') self.add_output('t_climb', units='s') @@ -1202,8 +1108,10 @@ def setup(self): # the aircraft model needs to provide thrust and drag self.add_subsystem('acmodel',self.options['aircraft_model'](num_nodes=nn,flight_phase=self.options['flight_phase']),promotes_inputs=['*'],promotes_outputs=['*']) self.add_subsystem('climbangle',ClimbAngleComp(num_nodes=nn),promotes_inputs=['drag','weight','thrust'],promotes_outputs=['gamma']) - self.add_subsystem('transition',TakeoffTransition(),promotes_inputs=['fltcond|Utrue','gamma'],promotes_outputs=['h_transition','s_transition','t_transition']) - self.add_subsystem('v2climb',TakeoffClimb(),promotes_inputs=['h_transition','gamma','fltcond|Utrue'],promotes_outputs=['s_climb','t_climb']) + self.add_subsystem('transition',TakeoffTransition(),promotes_outputs=['h_transition','s_transition','t_transition']) + self.promotes('transition', inputs=['fltcond|Utrue','gamma'], src_indices=[0], flat_src_indices=True) + self.add_subsystem('v2climb',TakeoffClimb(),promotes_inputs=['h_transition'],promotes_outputs=['s_climb','t_climb']) + self.promotes('v2climb', inputs=['fltcond|Utrue','gamma'], src_indices=[-1], flat_src_indices=True) self.add_subsystem('tod_final',AddSubtractComp(output_name='range_final',input_names=['range_initial','s_transition','s_climb'],units='m'),promotes_inputs=['*'],promotes_outputs=['*']) self.add_subsystem('duration',AddSubtractComp(output_name='duration',input_names=['t_transition','t_climb'],units='s'),promotes_inputs=['*'],promotes_outputs=['*']) self.add_subsystem('h_final',AddSubtractComp(output_name='fltcond|h_final',input_names=['h_obstacle'],units='m'),promotes_inputs=['*'],promotes_outputs=['*']) diff --git a/openconcept/components/splitter.py b/openconcept/components/splitter.py index 7e5677db..f72d4fd3 100644 --- a/openconcept/components/splitter.py +++ b/openconcept/components/splitter.py @@ -15,10 +15,10 @@ class PowerSplit(ExplicitComponent): power_rating : float Maximum rated power of the split mechanism. (scalar, W) power_split_fraction: - If `'rule'` is set to `'fraction'`, sets percentage of input power directed + If ``'rule'`` is set to ``'fraction'``, sets percentage of input power directed to Output A (minus losses). (vector, dimensionless) power_split_amount: - If `'rule'` is set to `'fixed'`, sets amount of input power to Output A (minus + If ``'rule'`` is set to ``'fixed'``, sets amount of input power to Output A (minus losses). (vector, W) Outputs @@ -41,8 +41,8 @@ class PowerSplit(ExplicitComponent): num_nodes : int Number of analysis points to run (sets vec length; default 1) rule : str - Power split control rule to use; either `'fixed'` where a set - amount of power is sent to Output A or `'fraction'` where a + Power split control rule to use; either ``'fixed'`` where a set + amount of power is sent to Output A or ``'fraction'`` where a fraction of the total power is sent to Output A efficiency : float Component efficiency (default 1) diff --git a/openconcept/utilities/dict_indepvarcomp.py b/openconcept/utilities/dict_indepvarcomp.py index 833ae59b..3be1b28d 100644 --- a/openconcept/utilities/dict_indepvarcomp.py +++ b/openconcept/utilities/dict_indepvarcomp.py @@ -12,12 +12,12 @@ class DictIndepVarComp(IndepVarComp): a name matching their location in the data tree. For example, let's assume we have stored some data about a vehicle in a dictionary - which can be accessed using the Python expression `vehicledata['wheels']['diameter']`. - The structured_name in this case is `'wheels|diameter'`. + which can be accessed using the Python expression ``vehicledata['wheels']['diameter']``. + The structured_name in this case is ``'wheels|diameter'``. - The user instantiates a component as `DictIndepVarComp(vehicledata)` + The user instantiates a component as ``DictIndepVarComp(vehicledata)`` and adds an output as follows: - `component_instance.add_output_from_dict('wheels|diameter')`. + ``component_instance.add_output_from_dict('wheels|diameter')``. Outputs are created after initialization and are user-defined. diff --git a/openconcept/utilities/math/add_subtract_comp.py b/openconcept/utilities/math/add_subtract_comp.py index 486f4fdb..585b262b 100644 --- a/openconcept/utilities/math/add_subtract_comp.py +++ b/openconcept/utilities/math/add_subtract_comp.py @@ -88,7 +88,7 @@ def __init__(self, output_name=None, input_names=None, vec_size=1, length=1, else: raise ValueError( "first argument to adder init must be either of type " - "`str' or 'None'") + "'str' or 'None'") def add_equation(self, output_name, input_names, vec_size=1, length=1, val=1.0, units=None, res_units=None, desc='', lower=None, upper=None, ref=1.0, diff --git a/openconcept/utilities/math/combine_split_comp.py b/openconcept/utilities/math/combine_split_comp.py index f9ea8bfa..a2c64d66 100644 --- a/openconcept/utilities/math/combine_split_comp.py +++ b/openconcept/utilities/math/combine_split_comp.py @@ -73,7 +73,7 @@ def __init__(self, output_name=None, input_names=None, vec_sizes=None, length=1, else: raise ValueError( "First argument to init must be either of type " - "`str' or 'None'") + "'str' or 'None'") def add_relation(self, output_name, input_names, vec_sizes, length=1, val=1.0, units=None, res_units=None, desc='', lower=None, upper=None, ref=1.0, @@ -281,7 +281,7 @@ def __init__(self, output_names=None, input_name=None, vec_sizes=None, length=1, else: raise ValueError( "input_name argument to init must be either of type " - "`str' or 'None'") + "'str' or 'None'") def add_relation(self, output_names, input_name, vec_sizes, length=1, val=1.0, units=None, res_units=None, desc='', lower=None, upper=None, ref=1.0, diff --git a/openconcept/utilities/math/sum_comp.py b/openconcept/utilities/math/sum_comp.py index 7159d408..2665d7ae 100644 --- a/openconcept/utilities/math/sum_comp.py +++ b/openconcept/utilities/math/sum_comp.py @@ -82,7 +82,7 @@ def __init__(self, output_name=None, input_name=None, vec_size=1, length=1, else: raise ValueError( "first argument to init must be either of type " - "`str' or 'None'") + "'str' or 'None'") def initialize(self): """ diff --git a/readme.md b/readme.md index 5d0f7443..e0622205 100644 --- a/readme.md +++ b/readme.md @@ -34,7 +34,6 @@ To build the docs locally, install the `sphinx_mdolab_theme` and `redbaron` via ### Requirements This toolkit requires the use of [OpenMDAO](https://openmdao.org/) 3.10.0 or later. OpenMDAO requires a late NumPy and SciPy. -If you'd like to use the OpenAeroStruct features of OpenConcept, OpenMDAO 3.10 is required because that is the newest version supported by OpenAeroStruct at the time of writing this. ## Citation diff --git a/setup.py b/setup.py index 9488234f..a9902903 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,10 @@ 'openmdao>=3.10.0', ], extras_require = { - 'testing': ["pytest", "openmdao[docs]", "redbaron", "sphinx_mdolab_theme", - "openaerostruct @ git+https://github.com/mdolab/OpenAeroStruct.git@master"] + 'testing': ["pytest", "pytest-cov", "coverage", + "openaerostruct @ git+https://github.com/mdolab/OpenAeroStruct.git@master"], + 'docs': ["sphinx_mdolab_theme", + "openaerostruct @ git+https://github.com/mdolab/OpenAeroStruct.git@master"], + 'plot': ["matplotlib"] }, )