peterdsharpe / AeroSandbox

Aircraft design optimization made fast through modern automatic differentiation. Composable analysis tools for aerodynamics, propulsion, structures, trajectory design, and much more.
https://peterdsharpe.github.io/AeroSandbox/
MIT License
687 stars 111 forks source link

Add new features to `AVL`; Add new syntax for analysis-specific options in geometry stack #70

Closed peterdsharpe closed 2 years ago

peterdsharpe commented 2 years ago

Despite what GitHub indicates, this has now been successfully merged into develop! Closing PR

adamwass commented 2 years ago

The way you simplified analysis_specific_options is very elegant! I didn't know about the dict.update() method. But there is no longer any check for incorrect user input keys. Not sure if this was intended, but I think it would still be a nice check to prevent the user from getting the name of an analysis-specific option key wrong and thinking the option has been applied when it has really been discarded.

The way you simplified the control surfaces to one deflection using individual control surface gains is also very clever. However, it does eliminate a couple functionalities:

  1. The user cannot manipulate the control surfaces individually at runtime within AVL, so the output geometry files cannot stand on their own. The user would have to manually change the gains of each control surface in the geometry file or change the deflections in Python.
  2. AVL's trimmed flight calculations will not work if multiple control surfaces are present because they cannot be individually manipulated to trim different moments.
  3. AVL will not give any information on the derivatives of aero coefficients w.r.t. individual control surface deflections.

I see why you simplified the control surface handling so that you don't have to keep track of control surface information across both the geometry and keystroke files, but do you think there's a reasonable way to retain these functionalities?

peterdsharpe commented 2 years ago

Hey @adamwass!

Validation of analysis_specific_options keys is now added and working with asb.AVL, as seen in the 4 latest commits on develop (validation code here: 16bb419fcd2dc24d96ce109338be3d5596bb362e, docs here: e20f76c07264459d853bbf3ad8d5621cb42590ab, 7732dd50f1845cebc21c2d94547e297aae098d00, edits to AVL.py here: 0f69ddb5beb4caa24682cda1ab9abb17bbaef887).

Basic logic is that if the analysis provides a "template" of default options via self.default_analysis_specific_options (example in AVL.py), then it will validate keys. (If invalid, a warning is raised and a list of valid keys is displayed.) Otherwise, it won't validate.

Re: control deflections in AVL:

That's a good point... it's a bit of a tricky issue, because AeroSandbox doesn't track groups of control surfaces together as AVL does - and as a conceptual aircraft design tool, that seems perhaps outside of scope?

As an alternative to the all_deflections implementation (where the "deflection" of all surfaces is 1, and the gain is their actual deflection), we could perhaps completely separate all control surfaces by procedurally generating unique names:

control_surface_name_in_avl = f"wing{wing_index}xsec{xsec_index}surf{control_surface_index}"
# Example: "wing0xsec2surf0"

Then, we could set gains in AVL to 1, set the deflections for each surface at runtime (although we'd need to track declaration order, as AVL will map them onto names D1, D2, ...), and go from there.

Does that seem like a more useful implementation perhaps?

adamwass commented 2 years ago

Thanks for feedback @peterdsharpe!

Perhaps the finer points of control surfaces are beyond the scope of a conceptual design tool. However, if you do want to go down this rabbit hole, I have a couple ideas :). And in fact, these ideas could be generalized to the handling, identifying, and grouping of wings and fuselages as well for surface or fuselage-wise force reports, grouping multiple surfaces into one virtual surface, etc.

Identification Schemes in AVL

AVL has 3 different identification schemes for aircraft components:

  1. Lists - wings (surfaces in AVL terminology) and fuselages (bodies in AVL terminology) are identified using lists, sequenced by the order in which the surfaces/bodies were defined in the geometry file. Although each wing/fuselage also has a user-defined name, this name does not have to be uniquely identifying. For example, if I make an AVL geometry file with two surfaces, both called "Wing", the surface force report would look like this:
 Surface Forces (referred to Sref,Cref,Bref about Xref,Yref,Zref)
 Standard axis orientation,  X fwd, Z down         

     Sref =   6.000       Cref =    1.0000   Bref =    6.0000
     Xref =      0.0000   Yref =    0.0000   Zref =    0.0000

 n      Area      CL      CD      Cm      CY      Cn      Cl     CDi     CDv
 1     3.000  0.1947  0.0040 -0.0421  0.0000 -0.0027 -0.0428  0.0040  0.0000   Wing
 2     3.000  0.1947  0.0040 -0.0421  0.0000  0.0027  0.0428  0.0040  0.0000   Wing (YDUP)
 3     0.500  0.0164  0.0008 -0.0672  0.0000 -0.0000 -0.0012  0.0008  0.0000   Wing
 4     0.500  0.0164  0.0008 -0.0672  0.0000  0.0000  0.0012  0.0008  0.0000   Wing (YDUP)

where each "Wing" surface is listed in the order defined in the geometry file. Note that this scheme does not allow grouping.

  1. Unique ID number - although surfaces are identified using lists, they can be grouped together using the COMPONENT keyword and associated component ID number. All surfaces with the same component ID number will be treated as one composite virtual surface for flow solving.

  2. Unique ID string - control surfaces each have a user-defined ID string, and all control surfaces with the same ID string are treated as one control surface (y-duplicated control surfaces are also considered to be part of the same control surface), allowing control surfaces to span multiple sections or even wings. Like the unique ID number, this scheme allows for grouping.

Identification Schemes in AeroSandbox

Any of these methods could be used to identify and track control surfaces, wings, or fuselages in AeroSandbox.

The current implementation of the ControlSurface class is probably closest to a list, as each instance is identified uniquely only by its order within the Airplane>Wing>XSec hierarchy and its memory address, and they cannot be grouped. You translated this into AVL using the unique ID string scheme (all_deflections) to create one virtual control surface. However, any of these ID schemes could be used to modify the desired behavior of and user interactions with control surfaces.

For example, here is a simple example for a unique ID number scheme implemented using a class attribute of the ControlSurface class:

class ControlSurface(AeroSandboxObject):
    id = 0 # class attribute
    def __init__(self):
        self.id = __class__.id
        __class__.id += 1

...

from aerosandbox.geometry.wing import ControlSurface

class Airplane(AeroSandboxObject):
    def __init__(self):
        ControlSurface.id = 0 # reset ControlSurface id class attribute every time a new Airplane is initialized.

Note that this scheme would not allow for grouping multiple control surfaces into one.

Anyway, I'm just brainstorming and throwing out ideas. Certainly, any identification scheme would add additional complexity to the code. Let me know if you think any of these would be worth pursuing.

peterdsharpe commented 2 years ago

These are some interesting things to think about - thank you for writing this up!

Re: the proposed scheme:

I'm a bit sketched out by the idea of tracking id as a class attribute of ControlSurface in the manner shown - it seems easily broken, and difficult to debug when it does break. For example, here's a illustrative failing example:

import aerosandbox as asb
import aerosandbox.numpy as np

aileron = asb.ControlSurface(
    ...
)  # aileron.id == 0

wing_root = asb.WingXSec(
    ...,
    control_surfaces=[
        aileron
    ]
)

wing_tip = asb.WingXSec(...)

wing = asb.Wing(
    ...,
    name="Main Wing",
    xsecs=[wing_root, wing_tip]
)

airplane = asb.Airplane(
    ...,
    wings=[wing]
)

### "Oh wait, I forgot to add the tail, let me do that"
airplane.wings.append(
    asb.Wing(
        name="Horizontal Stabilizer",
        xsecs=[
            asb.WingXSec(
                control_surfaces=[
                    asb.ControlSurface(
                        ...,
                        name="Elevator",
                    )  # elevator.id == 0
                ]
            ),
            asb.WingXSec(...)
        ]
    )
)

So, now you have instances of a class (ControlSurface) whose attributes depend on when they were defined with respect to initialization of the separate Airplane class.

And, in this example, both the aileron and elevator would have id == 0, unintentionally losing uniqueness.


Perhaps a different way to group surfaces would be to check for equality between surfaces (either using a is b, i.e. same object in memory, or by overloading a __eq__() method in ControlSurface that checks each attribute), and then group surfaces in AVL iff they are "equal" in some sense. So, you could define a grouped surface (here, two aileron sections that move together) like so:

import aerosandbox as asb
import aerosandbox.numpy as np

aileron = asb.ControlSurface(
    name="Aileron",
    ...,
)

airplane = asb.Airplane(
    wings=[
        asb.Wing(
            xsecs=[
                asb.WingXSec( # Root
                    ...,
                    control_surfaces=[aileron]
                ),
                asb.WingXSec( # Mid
                    ...,
                    control_surfaces=[aileron]
                ),
                asb.WingXSec( # Tip
                    ...,
                )
            ]
        )
    ]
)

Thoughts?