OpenMDAO / Aviary

NASA's aircraft analysis, design, and optimization tool
https://openmdao.github.io/Aviary/
Other
129 stars 53 forks source link

How to specify an external aero deck #490

Open iiwolf opened 4 weeks ago

iiwolf commented 4 weeks ago

Desired documentation content.

Hello,

Apologies if I've missed it somewhere, but is there an example on how to use an existing aero deck? Some examples come close, but never actually detail how to use the files. I believe I have formatted the aero deck as it should be, e.g.

Mach,Altitude (ft),CL,CD,Angle of Attack (deg),
0.1,0,0,0.1,-3
0.1,0,0.1,0.1,-2
...

But then how do I specify that in an input file or even level 2 script? e.g. I figured you could do something like this

aircraft:design:aero_data_file,aero.csv,unitless

My existing research / what I tried

Thanks!

Is there any existing documentation on your requested topic? Please describe.

https://openmdao.github.io/Aviary/user_guide/aerodynamics.html#user-specified-tabular-drag-polars https://openmdao.github.io/Aviary/user_guide/external_aero.html https://openmdao.github.io/Aviary/getting_started/onboarding_level2.html#summary

iiwolf commented 3 weeks ago

After some ripgreping I found the Jupyter notebook that Externally Computed Aero is based off of. This was great because apparently page was hiding the code I needed to see. Namely the definition of ExternalAero and the definition of the compute method (look at that, I guessed right!).

I'm understanding better now, but this example doesn't actually run/solve the Aviary problem. When I added a prob.run_aviary_problem('history.db') to the end of the Jupyter notebook, it fails with the error shown below.

Traceback (most recent call last):
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\linear\direct.py", line 320, in _linearize
    self._lu = scipy.sparse.linalg.splu(matrix)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\scipy\sparse\linalg\_dsolve\linsolve.py", line 438, in splu
    return _superlu.gstrf(N, A.nnz, A.data, indices, indptr,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Factor is exactly singular

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\UserA\projects\aviary\working_dir\plane\run_level2_external_aero.py", line 149, in <module>
    prob.run_aviary_problem(record_filename)
  File "C:\Users\UserA\projects\aviary\aviary\interface\methods_for_level2.py", line 2314, in run_aviary_problem
    failed = dm.run_problem(self, run_driver=run_driver, simulate=simulate, make_plots=make_plots,
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\dymos\run_problem.py", line 96, in run_problem
    failed = _refine_iter(problem, refine_iteration_limit, refine_method, case_prefix=case_prefix,
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\dymos\grid_refinement\refinement.py", line 40, in _refine_iter
    failed = problem.run_driver(case_prefix=case_prefix if refine_iteration_limit > 0 else _case_prefix,
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\utils\hooks.py", line 372, in __call__
    ret = self.func(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\problem.py", line 745, in run_driver
    return driver._run()
           ^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\driver.py", line 791, in _run
    self.result.success = not self.run()
                              ^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\drivers\scipy_optimizer.py", line 268, in run
    self._run_solve_nonlinear()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\driver.py", line 177, in wrapper
    ret = func(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\driver.py", line 1237, in _run_solve_nonlinear
    return self._problem().model.run_solve_nonlinear()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\system.py", line 4676, in run_solve_nonlinear
    self._solve_nonlinear()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\group.py", line 3484, in _solve_nonlinear
    self._nonlinear_solver._solve_with_cache_check()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\nonlinear_runonce.py", line 26, in _solve_with_cache_check
    self.solve()  # don't use caching
    ^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\nonlinear_runonce.py", line 45, in solve      
    self._gs_iter()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 865, in _gs_iter
    subsys._solve_nonlinear()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\group.py", line 3484, in _solve_nonlinear
    self._nonlinear_solver._solve_with_cache_check()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\nonlinear_runonce.py", line 26, in _solve_with_cache_check
    self.solve()  # don't use caching
    ^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\nonlinear_runonce.py", line 45, in solve      
    self._gs_iter()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 865, in _gs_iter
    subsys._solve_nonlinear()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\group.py", line 3484, in _solve_nonlinear
    self._nonlinear_solver._solve_with_cache_check()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\nonlinear_runonce.py", line 26, in _solve_with_cache_check
    self.solve()  # don't use caching
    ^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\nonlinear_runonce.py", line 45, in solve      
    self._gs_iter()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 865, in _gs_iter
    subsys._solve_nonlinear()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\group.py", line 3484, in _solve_nonlinear
    self._nonlinear_solver._solve_with_cache_check()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\nonlinear_runonce.py", line 26, in _solve_with_cache_check
    self.solve()  # don't use caching
    ^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\nonlinear_runonce.py", line 45, in solve      
    self._gs_iter()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 865, in _gs_iter
    subsys._solve_nonlinear()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\group.py", line 3484, in _solve_nonlinear
    self._nonlinear_solver._solve_with_cache_check()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 907, in _solve_with_cache_check        
    self.solve()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 669, in solve
    raise err
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 665, in solve
    self._solve()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 712, in _solve
    norm0, norm = self._iter_initialize()
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\newton.py", line 196, in _iter_initialize     
    self._gs_iter()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 865, in _gs_iter
    subsys._solve_nonlinear()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\core\group.py", line 3484, in _solve_nonlinear
    self._nonlinear_solver._solve_with_cache_check()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 907, in _solve_with_cache_check        
    self.solve()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 669, in solve
    raise err
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 665, in solve
    self._solve()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\solver.py", line 752, in _solve
    self._single_iteration()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\newton.py", line 233, in _single_iteration    
    self._linearize()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\nonlinear\newton.py", line 161, in _linearize
    self.linear_solver._linearize()
  File "C:\Users\UserA\Anaconda3\envs\aviary\Lib\site-packages\openmdao\solvers\linear\direct.py", line 322, in _linearize
    raise RuntimeError(format_singular_error(system, matrix))
RuntimeError: Singular entry found in 'traj.phases.climb.rhs_all.core_aerodynamics' <class SolvedAlphaGroup> for column associated with 
state/residual 'balance.alpha' ('traj.phases.climb.rhs_all.core_aerodynamics.balance.alpha') index 0.

I saw the note about the compute function doing nothing in this case, just returning the table values. If I understand correctly, that should look up CL/CD based on in inputs to the function correct? I'm a little confused on the different between inputs and options. At the risk of seeming dumb, it seems to me they are switched? In the majority of cases area and span are constant, and you want to lookup on mach, alpha, and altitude. I may be misunderstanding the purpose of this block though.

    def setup(self):
        altitude = self.options['altitude']
        mach = self.options['mach']
        angle_of_attack = self.options['angle_of_attack']

        self.add_input(Aircraft.Wing.AREA, 1.0, units='ft**2')
        self.add_input(Aircraft.Wing.SPAN, 1.0, units='ft')

        shape = (len(altitude), len(mach), len(angle_of_attack))

        self.add_output('drag_table', shape=shape, units='unitless')
        self.add_output('lift_table', shape=shape, units='unitless')

    def compute(self, inputs, outputs):
        """
        This component doesn't do anything, except set the drag and lift
        polars from the file we read in.
        """
        outputs['drag_table'] = CD
        outputs['lift_table'] = CL

Thanks for your time!

Kenneth-T-Moore commented 3 weeks ago

Hey, iiwolf. Yeah, that drag polar component is purely notional. A real one would actually calculate lift and drag using inputs like area and span, but this was meant to be a simple test and example, so it basically "cheats".

As for the NANs -- I think there must be something else wrong in this model. We will have to take a look. We have done some recent refactoring, so this might be an example we missed.

jkirk5 commented 2 weeks ago

Yeah, looks like there's a hole in our docs on available options for aero. Let me try and fill that gap here.

Aerodynamics has many methodologies you can use during a given mission phase. For example, for FLOPS-based aero you can select computed, low_speed, or tabular in your phase_info file.

What's missing is that there are additional options that can be selected for each of these methods. For tabular areo, I can point you to the part of the code that reads in that table. We have some options for that component related to passing data. You want CD0_data (that's a zero, not an "o"), and CDI_data, which can be either a filepath or a NamedValues object with the data in memory.

So your phase info might have something like this for a given phase:

{'core_aerodynamics': {'method': 'tabular',
                     'CD0_table': 'path/to/data/table'
                     'CDI_table': 'path/to/other/table'}

Sorry I'm just seeing these now - in the future if you post general questions in discussions I will see them much faster

iiwolf commented 2 weeks ago

Ok great this is helping clear things up. I have a few follow-ups but I will post them in discussions as suggested.

Thank you!