├── .coveragerc
├── .flake8
├── .github
├── CODEOWNERS
├── azure-pipelines.yaml
├── build_real.sh
└── test_real.sh
├── .gitignore
├── .readthedocs.yaml
├── CITATION.cff
├── LICENSE.md
├── README.md
├── openaerostruct
├── __init__.py
├── aerodynamics
│ ├── __init__.py
│ ├── aero_groups.py
│ ├── coeffs.py
│ ├── collocation_points.py
│ ├── compressible_states.py
│ ├── convert_velocity.py
│ ├── eval_mtx.py
│ ├── eval_velocities.py
│ ├── functionals.py
│ ├── geometry.py
│ ├── get_vectors.py
│ ├── horseshoe_circulations.py
│ ├── lift_coeff_2D.py
│ ├── lift_drag.py
│ ├── mesh_point_forces.py
│ ├── mtx_rhs.py
│ ├── panel_forces.py
│ ├── panel_forces_surf.py
│ ├── pg_scale.py
│ ├── pg_transform.py
│ ├── pg_wind_rotation.py
│ ├── rotational_velocity.py
│ ├── solve_matrix.py
│ ├── states.py
│ ├── total_drag.py
│ ├── total_lift.py
│ ├── viscous_drag.py
│ ├── vortex_mesh.py
│ └── wave_drag.py
├── common
│ ├── __init__.py
│ ├── atmos_comp.py
│ ├── atmos_group.py
│ └── reynolds_comp.py
├── docs
│ ├── Makefile
│ ├── __init__.py
│ ├── _n2html
│ │ └── generate_n2_n2.html
│ ├── _static
│ │ └── copybutton.js
│ ├── _utils
│ │ ├── __init__.py
│ │ ├── generate_sourcedocs.py
│ │ └── patch.py
│ ├── advanced_features.rst
│ ├── advanced_features
│ │ ├── aerostruct_ffd.rst
│ │ ├── custom_mesh_example.rst
│ │ ├── customizing_prob_setup.rst
│ │ ├── figs
│ │ │ ├── CRM_esque.svg
│ │ │ ├── bspline.svg
│ │ │ ├── chord.svg
│ │ │ ├── dihedral.svg
│ │ │ ├── ground_effect_correction.png
│ │ │ ├── ground_effect_polars.png
│ │ │ ├── groundplane.svg
│ │ │ ├── inverted_gull-wing.svg
│ │ │ ├── mesh-diagram.svg
│ │ │ ├── multi_section_2_sym.png
│ │ │ ├── oas_vsp_mesh.png
│ │ │ ├── radius.svg
│ │ │ ├── sweep.svg
│ │ │ ├── tacs-oas_coupling.png
│ │ │ ├── taper.svg
│ │ │ ├── thickness.svg
│ │ │ ├── twist.svg
│ │ │ ├── two_part_mesh.png
│ │ │ ├── vsp777.png
│ │ │ ├── vsp_chordwise.png
│ │ │ ├── vsp_spanwise.png
│ │ │ ├── wing_and_tail.png
│ │ │ ├── wingbox_Q400.png
│ │ │ ├── xshear.svg
│ │ │ └── zshear.svg
│ │ ├── geometry_manipulation.rst
│ │ ├── ground_effect.rst
│ │ ├── mphys_coupling.rst
│ │ ├── multi_section_surfaces.rst
│ │ ├── multiple_surfaces.rst
│ │ ├── multipoint.rst
│ │ ├── multipoint_parallel.rst
│ │ ├── openconcept_coupling.rst
│ │ ├── openvsp_mesh_example.rst
│ │ ├── scripts
│ │ │ ├── Boeing_777-9x_ref.vsp3
│ │ │ ├── basic_2_sec.py
│ │ │ ├── basic_2_sec_AS.py
│ │ │ ├── basic_2_sec_construction.py
│ │ │ ├── basic_2_sec_visc.py
│ │ │ ├── mphys_opt_chord.py
│ │ │ ├── rect_wing.vsp3
│ │ │ ├── run_vsp_777.py
│ │ │ ├── two_part_wing_custom_mesh.py
│ │ │ └── wingbox_mpt_Q400_example.py
│ │ └── solver.rst
│ ├── aero_walkthrough.rst
│ ├── aero_walkthrough
│ │ ├── generate_n2.py
│ │ ├── part_1.py
│ │ ├── part_2.py
│ │ ├── part_3.py
│ │ ├── part_4.py
│ │ ├── part_5.py
│ │ ├── part_6.py
│ │ └── part_7.py
│ ├── aerostructural_index.rst
│ ├── aerostructural_tube_walkthrough.rst
│ ├── aerostructural_wingbox_walkthrough.rst
│ ├── conf.py
│ ├── figures
│ │ ├── OptView.png
│ │ ├── aero.png
│ │ ├── aero_sample.png
│ │ ├── aerostruct_sample.png
│ │ ├── aerostruct_xdsm.pdf
│ │ ├── aerostruct_xdsm.png
│ │ ├── collapsed_aerostruct_diagram.png
│ │ ├── example.png
│ │ ├── plotall.png
│ │ ├── problem_diagram.png
│ │ ├── wingbox_fine.png
│ │ └── wingbox_opt.png
│ ├── how_to_contribute.rst
│ ├── index.rst
│ ├── installation.rst
│ ├── quick_example.rst
│ ├── struct_example.rst
│ ├── user_reference.rst
│ ├── user_reference
│ │ ├── debugging_tips.rst
│ │ ├── mesh_surface_dict.rst
│ │ └── v1_v2_conversion.rst
│ └── wingbox_mpt_opt_example.py
├── examples
│ ├── black_box_example.py
│ ├── drag_polar.py
│ ├── drag_polar_ground_effect.py
│ ├── example_deriv_check.py
│ ├── rectangular_wing
│ │ ├── drag_polar.py
│ │ ├── mphys_example.py
│ │ ├── mphys_opt_chord.py
│ │ ├── mphys_opt_twist.py
│ │ ├── opt_chord.py
│ │ ├── opt_twist.py
│ │ ├── rect_wing.vsp3
│ │ ├── run_rect_wing.py
│ │ └── run_roll.py
│ ├── run_CRM.py
│ ├── run_aerostruct_uCRM_multipoint.py
│ └── run_scaneagle.py
├── functionals
│ ├── __init__.py
│ ├── breguet_range.py
│ ├── center_of_gravity.py
│ ├── equilibrium.py
│ ├── moment_coefficient.py
│ ├── sum_areas.py
│ ├── total_aero_performance.py
│ ├── total_lift_drag.py
│ └── total_performance.py
├── geometry
│ ├── __init__.py
│ ├── ffd_component.py
│ ├── geometry_group.py
│ ├── geometry_mesh.py
│ ├── geometry_mesh_transformations.py
│ ├── geometry_multi_join.py
│ ├── geometry_unification.py
│ ├── monotonic_constraint.py
│ ├── radius_comp.py
│ └── utils.py
├── integration
│ ├── __init__.py
│ ├── aerostruct_groups.py
│ └── multipoint_comps.py
├── meshing
│ ├── CRM_definitions.py
│ ├── mesh_generator.py
│ ├── section_mesh_generator.py
│ └── utils.py
├── mphys
│ ├── __init__.py
│ ├── aero_builder.py
│ ├── aero_funcs_group.py
│ ├── aero_mesh.py
│ ├── aero_solver_group.py
│ ├── demux_surface_mesh.py
│ ├── lift_distribution.py
│ ├── mux_surface_forces.py
│ ├── surface_contours.py
│ └── utils.py
├── structures
│ ├── __init__.py
│ ├── assemble_k_group.py
│ ├── compute_nodes.py
│ ├── compute_point_mass_loads.py
│ ├── compute_thrust_loads.py
│ ├── create_rhs.py
│ ├── disp.py
│ ├── energy.py
│ ├── failure_exact.py
│ ├── failure_ks.py
│ ├── fem.py
│ ├── fuel_loads.py
│ ├── fuel_vol.py
│ ├── length.py
│ ├── local_stiff.py
│ ├── local_stiff_permuted.py
│ ├── local_stiff_transformed.py
│ ├── non_intersecting_thickness.py
│ ├── section_properties_tube.py
│ ├── section_properties_wingbox.py
│ ├── spar_within_wing.py
│ ├── spatial_beam_functionals.py
│ ├── spatial_beam_setup.py
│ ├── spatial_beam_states.py
│ ├── struct_groups.py
│ ├── structural_cg.py
│ ├── total_loads.py
│ ├── transform.py
│ ├── tube_group.py
│ ├── utils.py
│ ├── vonmises_tube.py
│ ├── vonmises_wingbox.py
│ ├── weight.py
│ ├── wing_weight_loads.py
│ ├── wingbox_fuel_vol_delta.py
│ ├── wingbox_geometry.py
│ └── wingbox_group.py
├── transfer
│ ├── __init__.py
│ ├── compute_transformation_matrix.py
│ ├── displacement_transfer.py
│ ├── displacement_transfer_group.py
│ └── load_transfer.py
└── utils
│ ├── __init__.py
│ ├── check_surface_dict.py
│ ├── constants.py
│ ├── interpolation.py
│ ├── plot_wing.py
│ ├── plot_wingbox.py
│ ├── testing.py
│ └── vector_algebra.py
├── setup.py
└── tests
├── aerodynamics_tests
├── test_2d_lift.py
├── test_coeffs.py
├── test_collocation_points.py
├── test_convert_velocity.py
├── test_eval_mtx.py
├── test_eval_velocities.py
├── test_functionals.py
├── test_geometry.py
├── test_get_vectors.py
├── test_horseshoe_circulations.py
├── test_lift_drag.py
├── test_mesh_point_forces.py
├── test_mtx_rhs.py
├── test_panel_forces.py
├── test_panel_forces_surf.py
├── test_pg_transform.py
├── test_rotational_velocity.py
├── test_solve_matrix.py
├── test_total_drag.py
├── test_total_lift.py
├── test_viscous_drag.py
├── test_vortex_mesh.py
└── test_wave_drag.py
├── common_tests
├── test_atmos_comp.py
└── test_reynolds_comp.py
├── functionals_tests
├── test_breguet_range.py
├── test_center_of_gravity.py
├── test_equilibrium.py
├── test_moment_coefficient.py
├── test_sum_areas.py
└── test_total_lift_drag.py
├── geometry_tests
├── rect_wing.vsp3
├── test_geometry_mesh.py
├── test_geometry_mesh_transformations.py
├── test_joining_comp.py
├── test_monotonic_constraint.py
├── test_radius_comp.py
├── test_unification_comp.py
└── test_vsp_mesh.py
├── integration_tests
├── test_aero.py
├── test_aero_all_geom.py
├── test_aero_analysis.py
├── test_aero_analysis_Sref.py
├── test_aero_analysis_compressible.py
├── test_aero_analysis_no_symmetry.py
├── test_aero_analysis_no_symmetry_wavedrag.py
├── test_aero_atmos.py
├── test_aero_ffd.py
├── test_aero_ground_effect.py
├── test_aero_ground_effect_right.py
├── test_aero_morphing_multipoint.py
├── test_aero_opt_no_symmetry.py
├── test_aero_opt_wavedrag.py
├── test_aerostruct.py
├── test_aerostruct_analysis.py
├── test_aerostruct_analysis_Sref.py
├── test_aerostruct_analysis_compressible.py
├── test_aerostruct_engine_thrusts.py
├── test_aerostruct_ffd.py
├── test_aerostruct_groundeffect.py
├── test_aerostruct_point_loads.py
├── test_aerostruct_wingbox_+weight_analysis.py
├── test_aerostruct_wingbox_analysis.py
├── test_aerostruct_wingbox_fuel_vol_constraint_opt.py
├── test_aerostruct_wingbox_opt.py
├── test_aerostruct_wingbox_wave_fuel_vol_constraint_opt.py
├── test_morphing_aerostruct.py
├── test_multi_2_sec.py
├── test_multi_2_sec_AS.py
├── test_multi_3_sec.py
├── test_multi_single.py
├── test_multi_single_AS.py
├── test_multiple_aero_analysis.py
├── test_multiple_rect.py
├── test_multipoint_aero.py
├── test_multipoint_aero_ffd.py
├── test_multipoint_parallel.py
├── test_multipoint_wingbox_aerostruct.py
├── test_multipoint_wingbox_derivs.py
├── test_scaneagle.py
├── test_simple_rect_AS.py
├── test_simple_rect_aero.py
├── test_simple_rect_aero_roll.py
├── test_simple_rect_aero_sideslip.py
├── test_simple_rect_aero_wo_symmetry.py
├── test_simple_rect_mphys_aero.py
├── test_simple_rect_mphys_aero_compressible.py
├── test_simple_rect_right_aero.py
├── test_struct.py
├── test_struct_analysis.py
├── test_struct_analysis_2g.py
├── test_struct_engine_thrusts.py
├── test_struct_no_loads.py
├── test_struct_point_masses.py
├── test_v1_aero_analysis.py
├── test_v1_aero_opt.py
├── test_v1_aero_viscous.py
├── test_v1_aero_viscous_opt.py
├── test_v1_aerostruct_analysis.py
├── test_v1_aerostruct_opt.py
├── test_v1_struct_analysis.py
├── test_v1_struct_opt.py
├── test_vsp_aero_analysis.py
├── test_wingbox_analysis.py
├── test_wingbox_derivs.py
├── test_wingbox_distributed_fuel.py
└── vsp_model.vsp3
├── meshing_tests
├── test_mesh_generator.py
├── test_mesh_regen.py
└── test_meshing_imports.py
├── mphys_tests
├── test_aero_builder.py
├── test_aero_funcs_group.py
├── test_aero_solver_group.py
├── test_demux_mesh.py
└── test_mux_forces.py
├── structures_tests
├── test_add_point_masses.py
├── test_compute_nodes.py
├── test_create_rhs.py
├── test_disp.py
├── test_energy.py
├── test_failure_exact.py
├── test_failure_ks.py
├── test_fem.py
├── test_fuel_loads.py
├── test_materials_tube.py
├── test_materials_wingbox.py
├── test_non_intersecting_thickness.py
├── test_spar_within_wing.py
├── test_structural_cg.py
├── test_thrust_loads.py
├── test_total_load_derivs.py
├── test_vonmises_tube.py
├── test_vonmises_wingbox.py
├── test_weight.py
└── test_wingbox_geometry.py
└── transfer_tests
├── test_compute_transformation_matrix.py
├── test_displacement_transfer.py
└── test_load_transfer.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [report]
2 | omit =
3 | */openaerostruct/utils/plot_wing.py
4 | */openaerostruct/utils/plot_wingbox.py
5 | */tests/*
6 | */docs/*
7 | exclude_lines =
8 | pragma: no cover
9 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | # OpenAeroStruct-specific settings in addition to the MDO Lab standard config file.
3 | extend-exclude =
4 | openaerostruct/docs/conf.py,
5 | openaerostruct/docs/aero_walkthrough/part*.py,
6 | openaerostruct/docs/_utils/docutil.py,
7 | openaerostruct/docs/_utils/patch.py,
8 | openaerostruct/utils/plot_*.py,
9 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @mdolab/openaerostruct_maintainers
2 |
--------------------------------------------------------------------------------
/.github/azure-pipelines.yaml:
--------------------------------------------------------------------------------
1 | trigger:
2 | branches:
3 | include:
4 | - main
5 | tags:
6 | include:
7 | - v*.*.*
8 |
9 | pr:
10 | - main
11 |
12 | resources:
13 | repositories:
14 | - repository: azure_template
15 | type: github
16 | name: mdolab/.github
17 | endpoint: mdolab
18 |
19 | stages:
20 | - template: azure/azure_template.yaml@azure_template
21 | parameters:
22 | REPO_NAME: OpenAeroStruct
23 | COVERAGE: true
24 |
25 | - stage:
26 | dependsOn:
27 | - Test_Real
28 | - Style
29 | displayName: PyPI
30 | condition: and(succeeded(), contains(variables['build.sourceBranch'], 'tags'))
31 | jobs:
32 | - template: azure/azure_pypi.yaml@azure_template
33 |
--------------------------------------------------------------------------------
/.github/build_real.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | sed -i '/mphys/d' "$HOME/.config/pip/constraints.txt" # Remove the pip constraint on the mphys version
4 | sed -i '/numpy/d; /openmdao/d' "$HOME/.config/pip/constraints.txt" # Remove the pip constraint on the numpy and openmdao version
5 | pip install .[testing,mphys]
6 |
--------------------------------------------------------------------------------
/.github/test_real.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | testflo -n 2 -v . --coverage --coverpkg openaerostruct
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore OpenMDAO reports folders
2 | **/reports/
3 |
4 | *.pyc
5 | *.py~
6 | *.db
7 | *~
8 | *.aux
9 | *.log
10 | *.pdf
11 | *.synctex.gz
12 | *.f90~
13 | *.so
14 | *.out
15 | *.0
16 | *.o
17 | *.mod
18 | openaerostruct/docs/_build/
19 | openaerostruct/docs/*.rst
20 | openaerostruct/docs/_srcdocs/
21 | *.a
22 | *.msg
23 | src/adjoint/tempReverse/
24 | src/adjoint/tempForward/
25 | openaerostruct.egg-info/
26 | .vscode
27 | *.plt
28 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # File: .readthedocs.yaml
2 |
3 | version: 2
4 |
5 | sphinx:
6 | # Path to your Sphinx configuration file.
7 | configuration: openaerostruct/docs/conf.py
8 |
9 | build:
10 | os: ubuntu-22.04
11 | tools:
12 | python: "3.11"
13 |
14 | sphinx:
15 | configuration: openaerostruct/docs/conf.py
16 |
17 | python:
18 | install:
19 | - method: pip
20 | path: .
21 | extra_requirements:
22 | - docs
23 |
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: "1.2.0"
2 | authors:
3 | - family-names: Jasa
4 | given-names: John P.
5 | orcid: "https://orcid.org/0000-0001-5442-2792"
6 | - family-names: Hwang
7 | given-names: John T.
8 | orcid: "https://orcid.org/0009-0002-5789-6357"
9 | - family-names: Martins
10 | given-names: Joaquim R. R. A.
11 | orcid: "https://orcid.org/0000-0003-2143-1478"
12 | doi: 10.1007/s00158-018-1912-8
13 | message: If you use this software, please cite the following paper.
14 | preferred-citation:
15 | authors:
16 | - family-names: Jasa
17 | given-names: John P.
18 | orcid: "https://orcid.org/0000-0001-5442-2792"
19 | - family-names: Hwang
20 | given-names: John T.
21 | orcid: "https://orcid.org/0009-0002-5789-6357"
22 | - family-names: Martins
23 | given-names: Joaquim R. R. A.
24 | orcid: "https://orcid.org/0000-0003-2143-1478"
25 | title: "Open-source coupled aerostructural optimization using Python"
26 | journal: "Structural and Multidisciplinary Optimization"
27 | volume: "57"
28 | number: "4"
29 | pages: "1815--1827"
30 | date: "2018-04"
31 | doi: "10.1007/s00158-018-1912-8"
--------------------------------------------------------------------------------
/openaerostruct/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "2.10.1"
2 |
--------------------------------------------------------------------------------
/openaerostruct/aerodynamics/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/aerodynamics/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/aerodynamics/coeffs.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 |
4 | class Coeffs(om.ExplicitComponent):
5 | """Compute lift and drag coefficients for each individual lifting surface.
6 |
7 | Parameters
8 | ----------
9 | S_ref : float
10 | The reference areas of the lifting surface.
11 | L : float
12 | Total lift for the lifting surface.
13 | D : float
14 | Total drag for the lifting surface.
15 | v : float
16 | Freestream air velocity in m/s.
17 | rho : float
18 | Air density in kg/m^3.
19 |
20 | Returns
21 | -------
22 | CL1 : float
23 | Induced coefficient of lift (CL) for the lifting surface.
24 | CDi : float
25 | Induced coefficient of drag (CD) for the lifting surface.
26 | """
27 |
28 | def setup(self):
29 | self.add_input("S_ref", val=1.0, units="m**2", tags=["mphys_coupling"])
30 | self.add_input("L", val=1.0, units="N")
31 | self.add_input("D", val=1.0, units="N")
32 | self.add_input("v", val=1.0, units="m/s", tags=["mphys_input"])
33 | self.add_input("rho", val=1.0, units="kg/m**3", tags=["mphys_input"])
34 |
35 | self.add_output("CL1", val=0.0)
36 | self.add_output("CDi", val=0.0)
37 |
38 | self.declare_partials("CL1", "L")
39 | self.declare_partials("CDi", "D")
40 | self.declare_partials("CL1", "v")
41 | self.declare_partials("CDi", "v")
42 | self.declare_partials("CL1", "rho")
43 | self.declare_partials("CDi", "rho")
44 | self.declare_partials("CL1", "S_ref")
45 | self.declare_partials("CDi", "S_ref")
46 |
47 | def compute(self, inputs, outputs):
48 | S_ref = inputs["S_ref"]
49 | rho = inputs["rho"]
50 | v = inputs["v"]
51 | L = inputs["L"]
52 | D = inputs["D"]
53 |
54 | outputs["CL1"] = L / (0.5 * rho * v**2 * S_ref)
55 | outputs["CDi"] = D / (0.5 * rho * v**2 * S_ref)
56 |
57 | def compute_partials(self, inputs, partials):
58 | S_ref = inputs["S_ref"]
59 | rho = inputs["rho"]
60 | v = inputs["v"]
61 | L = inputs["L"]
62 | D = inputs["D"]
63 |
64 | partials["CL1", "L"] = 1.0 / (0.5 * rho * v**2 * S_ref)
65 | partials["CDi", "D"] = 1.0 / (0.5 * rho * v**2 * S_ref)
66 |
67 | partials["CL1", "v"] = -2.0 * L / (0.5 * rho * v**3 * S_ref)
68 | partials["CDi", "v"] = -2.0 * D / (0.5 * rho * v**3 * S_ref)
69 |
70 | partials["CL1", "rho"] = -L / (0.5 * rho**2 * v**2 * S_ref)
71 | partials["CDi", "rho"] = -D / (0.5 * rho**2 * v**2 * S_ref)
72 |
73 | partials["CL1", "S_ref"] = -L / (0.5 * rho * v**2 * S_ref**2)
74 | partials["CDi", "S_ref"] = -D / (0.5 * rho * v**2 * S_ref**2)
75 |
--------------------------------------------------------------------------------
/openaerostruct/aerodynamics/functionals.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 | from openaerostruct.aerodynamics.lift_drag import LiftDrag
3 | from openaerostruct.aerodynamics.coeffs import Coeffs
4 | from openaerostruct.aerodynamics.total_lift import TotalLift
5 | from openaerostruct.aerodynamics.total_drag import TotalDrag
6 | from openaerostruct.aerodynamics.viscous_drag import ViscousDrag
7 | from openaerostruct.aerodynamics.wave_drag import WaveDrag
8 | from openaerostruct.aerodynamics.lift_coeff_2D import LiftCoeff2D
9 |
10 |
11 | class VLMFunctionals(om.Group):
12 | """
13 | Group that contains the aerodynamic functionals used to evaluate
14 | performance. These are not included in the coupled aerostructural group,
15 | but are only used to compute aerodynamic performance. This includes
16 | computing lift, drag, CL, CD, viscous CD, and wave drag CD.
17 | """
18 |
19 | def initialize(self):
20 | self.options.declare("surface", types=dict)
21 |
22 | def setup(self):
23 | surface = self.options["surface"]
24 |
25 | self.add_subsystem(
26 | "liftcoeff",
27 | LiftCoeff2D(surface=surface),
28 | promotes_inputs=["v", "alpha", "rho", "widths", "chords", "sec_forces"],
29 | promotes_outputs=["Cl"],
30 | )
31 |
32 | self.add_subsystem(
33 | "liftdrag",
34 | LiftDrag(surface=surface),
35 | promotes_inputs=["alpha", "beta", "sec_forces"],
36 | promotes_outputs=["L", "D"],
37 | )
38 |
39 | self.add_subsystem(
40 | "coeffs", Coeffs(), promotes_inputs=["v", "rho", "S_ref", "L", "D"], promotes_outputs=["CL1", "CDi"]
41 | )
42 |
43 | self.add_subsystem("CL", TotalLift(surface=surface), promotes_inputs=["CL1"], promotes_outputs=["CL"])
44 |
45 | self.add_subsystem(
46 | "viscousdrag",
47 | ViscousDrag(surface=surface),
48 | promotes_inputs=["Mach_number", "re", "widths", "lengths_spanwise", "lengths", "S_ref", "t_over_c"],
49 | promotes_outputs=["CDv"],
50 | )
51 |
52 | self.add_subsystem(
53 | "wavedrag",
54 | WaveDrag(surface=surface),
55 | promotes_inputs=["Mach_number", "lengths_spanwise", "widths", "CL", "chords", "t_over_c"],
56 | promotes_outputs=["CDw"],
57 | )
58 |
59 | self.add_subsystem(
60 | "CD", TotalDrag(surface=surface), promotes_inputs=["CDv", "CDi", "CDw"], promotes_outputs=["CD"]
61 | )
62 |
--------------------------------------------------------------------------------
/openaerostruct/aerodynamics/panel_forces_surf.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class PanelForcesSurf(om.ExplicitComponent):
7 | """
8 | Take in the computed panel forces and convert them to sectional forces
9 | for each surface. Basically just takes the long array that has info
10 | for all surfaces and creates a new output for each surface with only
11 | that surface's panel forces.
12 |
13 | Parameters
14 | ----------
15 | panel_forces[system_size, 3] : numpy array
16 | All of the forces acting on all panels in the total system.
17 |
18 | Returns
19 | -------
20 | sec_forces[nx-1, ny-1, 3] : numpy array
21 | Only the panel forces for one individual lifting surface.
22 | There is one of these per surface.
23 | """
24 |
25 | def initialize(self):
26 | self.options.declare("surfaces", types=list)
27 |
28 | def setup(self):
29 | surfaces = self.options["surfaces"]
30 |
31 | system_size = 0
32 |
33 | # Loop through the surfaces to get the total system size
34 | for surface in surfaces:
35 | mesh = surface["mesh"]
36 | nx = mesh.shape[0]
37 | ny = mesh.shape[1]
38 |
39 | system_size += (nx - 1) * (ny - 1)
40 |
41 | arange = np.arange(3 * system_size)
42 |
43 | self.add_input("panel_forces", shape=(system_size, 3), units="N")
44 |
45 | # Loop through the surfaces and add the output of sec_forces based on
46 | # the size of each surface. Here we keep track of the total indices
47 | # from panel_forces to make sure the forces go to the correct output
48 | ind1, ind2 = 0, 0
49 | for surface in surfaces:
50 | mesh = surface["mesh"]
51 | nx = mesh.shape[0]
52 | ny = mesh.shape[1]
53 | name = surface["name"]
54 |
55 | sec_forces_name = "{}_sec_forces".format(name)
56 |
57 | ind2 += (nx - 1) * (ny - 1) * 3
58 |
59 | self.add_output(sec_forces_name, shape=(nx - 1, ny - 1, 3), units="N", tags=["mphys_coupling"])
60 |
61 | rows = np.arange((nx - 1) * (ny - 1) * 3)
62 | cols = arange[ind1:ind2]
63 | self.declare_partials(sec_forces_name, "panel_forces", val=1.0, rows=rows, cols=cols)
64 |
65 | ind1 += (nx - 1) * (ny - 1) * 3
66 |
67 | def compute(self, inputs, outputs):
68 | surfaces = self.options["surfaces"]
69 |
70 | ind1, ind2 = 0, 0
71 | for surface in surfaces:
72 | mesh = surface["mesh"]
73 | nx = mesh.shape[0]
74 | ny = mesh.shape[1]
75 | name = surface["name"]
76 |
77 | sec_forces_name = "{}_sec_forces".format(name)
78 |
79 | ind2 += (nx - 1) * (ny - 1)
80 |
81 | # Just pluck out the relevant forces and reshape them
82 | outputs[sec_forces_name] = inputs["panel_forces"][ind1:ind2].reshape((nx - 1, ny - 1, 3))
83 |
84 | ind1 += (nx - 1) * (ny - 1)
85 |
--------------------------------------------------------------------------------
/openaerostruct/aerodynamics/pg_transform.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 | from openaerostruct.aerodynamics.pg_wind_rotation import RotateToWindFrame, RotateFromWindFrame
4 | from openaerostruct.aerodynamics.pg_scale import ScaleToPrandtlGlauert, ScaleFromPrandtlGlauert
5 |
6 |
7 | class PGTransform(om.Group):
8 | """
9 | This group is responsible for transforming the VLM geometries from
10 | physical coordinates to Prandtl-Glauert coordinates. This allows the
11 | compressible aerodynamic problem to be solved as an equivelent
12 | incompressible problem.
13 |
14 | The transform can be broken down into two steps:
15 |
16 | 1. Rotate the geometry from the body frame to the wind frame so
17 | x-axis is parallel to freestream velocity.
18 |
19 | 2. Scale wind frame coordinates by Prandtl-Glauert factor to retrieve
20 | equivalent Prandtl-Glauert geometry.
21 | """
22 |
23 | def initialize(self):
24 | self.options.declare("surfaces", types=list)
25 | self.options.declare(
26 | "rotational", False, types=bool, desc="Set to True to turn on support for computing angular velocities"
27 | )
28 |
29 | def setup(self):
30 | surfaces = self.options["surfaces"]
31 | rotational = self.options["rotational"]
32 |
33 | # Create component to rotate mesh geometry to wind frame,
34 | # s.t. the freestream velocity is along the x-axis
35 | self.add_subsystem(
36 | "rotate",
37 | RotateToWindFrame(surfaces=surfaces, rotational=rotational),
38 | promotes_inputs=["*"],
39 | promotes_outputs=["*"],
40 | )
41 |
42 | # Scale y and z direction by Prandtl Glauert factor
43 | self.add_subsystem(
44 | "scale",
45 | ScaleToPrandtlGlauert(surfaces=surfaces, rotational=rotational),
46 | promotes_inputs=["*"],
47 | promotes_outputs=["*"],
48 | )
49 |
50 |
51 | class InversePGTransform(om.Group):
52 | """
53 | This group is responsible for transforming the solved incompressible
54 | forces in the Prandtl-Glauert domain to the compressible forces in the
55 | physical aerodynamic frame. This is the reverse procedure used in the
56 | PGTransform group.
57 |
58 | The inverse transform can be broken down into two steps:
59 |
60 | 1. Scale Prandtl-Glauert force vectors to physical force vectors in wind
61 | frame.
62 |
63 | 2. Rotate physical forces from wind frame to the aerodynamic frame.
64 | """
65 |
66 | def initialize(self):
67 | self.options.declare("surfaces", types=list)
68 |
69 | def setup(self):
70 | surfaces = self.options["surfaces"]
71 |
72 | # Scale force vectors back to compressible values
73 | scale_sys = ScaleFromPrandtlGlauert(surfaces=surfaces)
74 | self.add_subsystem("scale", scale_sys, promotes_inputs=["*"], promotes_outputs=["*"])
75 |
76 | # Rotate forces back to aerodynamic frame
77 | rot_sys = RotateFromWindFrame(surfaces=surfaces)
78 | self.add_subsystem("rotate", rot_sys, promotes_inputs=["*"], promotes_outputs=["*"])
79 |
--------------------------------------------------------------------------------
/openaerostruct/aerodynamics/solve_matrix.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from scipy.linalg import lu_factor, lu_solve
3 |
4 | import openmdao.api as om
5 |
6 |
7 | class SolveMatrix(om.ImplicitComponent):
8 | """
9 | Solve the AIC linear system to obtain the vortex ring circulations.
10 |
11 | Parameters
12 | ----------
13 | mtx[system_size, system_size] : numpy array
14 | Final fully assembled AIC matrix that is used to solve for the
15 | circulations.
16 | rhs[system_size] : numpy array
17 | Right-hand side of the AIC linear system, constructed from the
18 | freestream velocities and panel normals.
19 |
20 | Returns
21 | -------
22 | circulations[system_size] : numpy array
23 | The vortex ring circulations obtained by solving the AIC linear system.
24 |
25 | """
26 |
27 | def initialize(self):
28 | self.options.declare("surfaces", types=list)
29 |
30 | def setup(self):
31 | system_size = 0
32 |
33 | for surface in self.options["surfaces"]:
34 | mesh = surface["mesh"]
35 | nx = mesh.shape[0]
36 | ny = mesh.shape[1]
37 |
38 | system_size += (nx - 1) * (ny - 1)
39 |
40 | self.system_size = system_size
41 |
42 | self.add_input("mtx", shape=(system_size, system_size), units="1/m")
43 | self.add_input("rhs", shape=system_size, units="m/s")
44 | self.add_output("circulations", shape=system_size, units="m**2/s", tags=["mphys_coupling"])
45 |
46 | self.declare_partials(
47 | "circulations",
48 | "circulations",
49 | rows=np.outer(np.arange(system_size), np.ones(system_size, int)).flatten(),
50 | cols=np.outer(np.ones(system_size, int), np.arange(system_size)).flatten(),
51 | )
52 | self.declare_partials(
53 | "circulations",
54 | "mtx",
55 | rows=np.outer(np.arange(system_size), np.ones(system_size, int)).flatten(),
56 | cols=np.arange(system_size**2),
57 | )
58 | self.declare_partials(
59 | "circulations",
60 | "rhs",
61 | val=-1.0,
62 | rows=np.arange(system_size),
63 | cols=np.arange(system_size),
64 | )
65 |
66 | def apply_nonlinear(self, inputs, outputs, residuals):
67 | residuals["circulations"] = inputs["mtx"].dot(outputs["circulations"]) - inputs["rhs"]
68 |
69 | def solve_nonlinear(self, inputs, outputs):
70 | self.lu = lu_factor(inputs["mtx"])
71 |
72 | outputs["circulations"] = lu_solve(self.lu, inputs["rhs"])
73 |
74 | def linearize(self, inputs, outputs, partials):
75 | system_size = self.system_size
76 | self.lu = lu_factor(inputs["mtx"])
77 |
78 | partials["circulations", "circulations"] = inputs["mtx"].flatten()
79 | partials["circulations", "mtx"] = np.outer(np.ones(system_size), outputs["circulations"]).flatten()
80 |
81 | def solve_linear(self, d_outputs, d_residuals, mode):
82 | if mode == "fwd":
83 | d_outputs["circulations"] = lu_solve(self.lu, d_residuals["circulations"], trans=0)
84 | else:
85 | d_residuals["circulations"] = lu_solve(self.lu, d_outputs["circulations"], trans=1)
86 |
--------------------------------------------------------------------------------
/openaerostruct/aerodynamics/total_drag.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 |
4 | class TotalDrag(om.ExplicitComponent):
5 | """Calculate total drag in force units.
6 |
7 | parameters
8 | ----------
9 | CDi : float
10 | Induced coefficient of drag (CD) for the lifting surface.
11 | CDv : float
12 | Calculated coefficient of viscous drag for the lifting surface.
13 |
14 | Returns
15 | -------
16 | CD : float
17 | Total coefficient of drag (CD) for the lifting surface.
18 | """
19 |
20 | def initialize(self):
21 | self.options.declare("surface", types=dict)
22 |
23 | def setup(self):
24 | surface = self.options["surface"]
25 |
26 | self.add_input("CDi", val=1.0)
27 | self.add_input("CDv", val=1.0)
28 | self.add_input("CDw", val=1.0)
29 |
30 | self.add_output("CD", val=1.0, tags=["mphys_result"])
31 |
32 | self.CD0 = surface["CD0"]
33 |
34 | self.declare_partials("CD", "CDi", val=1.0)
35 | self.declare_partials("CD", "CDv", val=1.0)
36 | self.declare_partials("CD", "CDw", val=1.0)
37 |
38 | def compute(self, inputs, outputs):
39 | outputs["CD"] = inputs["CDi"] + inputs["CDv"] + inputs["CDw"] + self.CD0
40 |
--------------------------------------------------------------------------------
/openaerostruct/aerodynamics/total_lift.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 |
4 | class TotalLift(om.ExplicitComponent):
5 | """
6 | Calculate total lift in force units by summing the induced CL
7 | with the CL0.
8 |
9 | Parameters
10 | ----------
11 | CL1 : float
12 | Induced coefficient of lift (CL) for the lifting surface.
13 |
14 | Returns
15 | -------
16 | CL : float
17 | Total coefficient of lift (CL) for the lifting surface.
18 | """
19 |
20 | def initialize(self):
21 | self.options.declare("surface", types=dict)
22 |
23 | def setup(self):
24 | surface = self.options["surface"]
25 |
26 | self.add_input("CL1", val=1.0)
27 |
28 | self.add_output("CL", val=1.0, tags=["mphys_result"])
29 |
30 | self.CL0 = surface["CL0"]
31 |
32 | self.declare_partials("CL", "CL1", val=1.0)
33 |
34 | def compute(self, inputs, outputs):
35 | outputs["CL"] = inputs["CL1"] + self.CL0
36 |
--------------------------------------------------------------------------------
/openaerostruct/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/common/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/common/atmos_group.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 | from openaerostruct.common.reynolds_comp import ReynoldsComp
3 | from openaerostruct.common.atmos_comp import AtmosComp
4 |
5 |
6 | class AtmosGroup(om.Group):
7 | def setup(self):
8 | self.add_subsystem(
9 | "atmos",
10 | AtmosComp(),
11 | promotes_inputs=["altitude", "Mach_number"],
12 | promotes_outputs=["T", "P", "rho", "speed_of_sound", "mu", "v"],
13 | )
14 |
15 | self.add_subsystem("reynolds", ReynoldsComp(), promotes_inputs=["rho", "mu", "v"], promotes_outputs=["re"])
16 |
--------------------------------------------------------------------------------
/openaerostruct/common/reynolds_comp.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 |
4 | class ReynoldsComp(om.ExplicitComponent):
5 | def setup(self):
6 | self.add_input("rho", val=1.0, units="slug/ft**3")
7 | self.add_input("mu", val=1.0, units="lbf*s/ft**2")
8 | self.add_input("v", val=1.0, units="ft/s")
9 |
10 | self.add_output("re", val=1.0, units="1/ft")
11 |
12 | self.declare_partials("re", ["rho", "mu", "v"])
13 |
14 | def compute(self, inputs, outputs):
15 | outputs["re"] = inputs["rho"] * inputs["v"] / inputs["mu"]
16 |
17 | def compute_partials(self, inputs, partials):
18 | partials["re", "rho"] = inputs["v"] / inputs["mu"]
19 | partials["re", "v"] = inputs["rho"] / inputs["mu"]
20 | partials["re", "mu"] = -inputs["rho"] * inputs["v"] / inputs["mu"] ** 2
21 |
--------------------------------------------------------------------------------
/openaerostruct/docs/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/docs/_static/copybutton.js:
--------------------------------------------------------------------------------
1 | // originally taken from scikit-learn's Sphinx theme
2 | $(document).ready(function() {
3 | /* Add a [>>>] button on the top-right corner of code samples to hide
4 | * the >>> and ... prompts and the output and thus make the code
5 | * copyable.
6 | * Note: This JS snippet was taken from the official python.org
7 | * documentation site.*/
8 | var div = $('.highlight-python .highlight,' +
9 | '.highlight-python3 .highlight,' +
10 | '.highlight-pycon .highlight')
11 | var pre = div.find('pre');
12 |
13 | // get the styles from the current theme
14 | pre.parent().parent().css('position', 'relative');
15 | var hide_text = 'Hide the prompts and output';
16 | var show_text = 'Show the prompts and output';
17 | var border_width = pre.css('border-top-width');
18 | var border_style = pre.css('border-top-style');
19 | var border_color = pre.css('border-top-color');
20 | var button_styles = {
21 | 'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0',
22 | 'border-color': border_color, 'border-style': border_style,
23 | 'border-width': border_width, 'color': border_color, 'text-size': '75%',
24 | 'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em'
25 | }
26 |
27 | // create and add the button to all the code blocks that contain >>>
28 | div.each(function(index) {
29 | var jthis = $(this);
30 | if (jthis.find('.gp').length > 0) {
31 | var button = $('>>>');
32 | button.css(button_styles)
33 | button.attr('title', hide_text);
34 | jthis.prepend(button);
35 | }
36 | // tracebacks (.gt) contain bare text elements that need to be
37 | // wrapped in a span to work with .nextUntil() (see later)
38 | jthis.find('pre:has(.gt)').contents().filter(function() {
39 | return ((this.nodeType == 3) && (this.data.trim().length > 0));
40 | }).wrap('');
41 | });
42 |
43 | // define the behavior of the button when it's clicked
44 | $('.copybutton').toggle(
45 | function() {
46 | var button = $(this);
47 | button.parent().find('.go, .gp, .gt').hide();
48 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden');
49 | button.css('text-decoration', 'line-through');
50 | button.attr('title', show_text);
51 | },
52 | function() {
53 | var button = $(this);
54 | button.parent().find('.go, .gp, .gt').show();
55 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible');
56 | button.css('text-decoration', 'none');
57 | button.attr('title', hide_text);
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/openaerostruct/docs/_utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/_utils/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features.rst:
--------------------------------------------------------------------------------
1 | .. _Advanced_Features:
2 |
3 | Advanced Features
4 | =================
5 |
6 | These examples show some advanced features in OpenAeroStruct.
7 |
8 | .. toctree::
9 | :maxdepth: 1
10 |
11 | advanced_features/solver.rst
12 | advanced_features/geometry_manipulation.rst
13 | advanced_features/custom_mesh_example.rst
14 | advanced_features/openvsp_mesh_example.rst
15 | advanced_features/multi_section_surfaces.rst
16 | advanced_features/multiple_surfaces.rst
17 | advanced_features/multipoint.rst
18 | advanced_features/multipoint_parallel.rst
19 | advanced_features/aerostruct_ffd.rst
20 | advanced_features/ground_effect.rst
21 | advanced_features/openconcept_coupling.rst
22 | advanced_features/customizing_prob_setup.rst
23 | advanced_features/mphys_coupling.rst
24 |
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/aerostruct_ffd.rst:
--------------------------------------------------------------------------------
1 | .. _Aerostruct_ffd:
2 |
3 | Aerostructural FFD
4 | ==================
5 |
6 | In additional to OpenAeroStruct's internal geometry manipulation group, you can also use `pyGeo` to perform free-form deformation (FFD) manipulation on the mesh.
7 | This allows for more general design shape changes and helps sync up geometry changes between meshes from different levels of fidelity.
8 |
9 | .. warning::
10 | This example requires `pyGeo`, an external code developed by the MDO Lab. You can install it from `here `_.
11 |
12 | .. literalinclude:: /../../tests/integration_tests/test_aerostruct_ffd.py
13 | :start-after: docs checkpoint 0
14 | :end-before: docs checkpoint 1
15 | :dedent: 8
16 |
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/custom_mesh_example.rst:
--------------------------------------------------------------------------------
1 | .. _Custom_Mesh:
2 |
3 | User-Provided Mesh Example
4 | ==========================
5 |
6 | Here is an example script with a custom mesh provided as an array of coordinates.
7 | This should help you understand how meshes are defined in OpenAeroStruct and how to create them for your own custom planform shapes.
8 | This is an alternative to the helper-function approach described in :ref:`Aerodynamic_Optimization_Walkthrough`.
9 |
10 | The following shows the portion of the example script in which the user provides the coordinates for the mesh.
11 | This example is for a wing with a kink and two distinct trapezoidal segments.
12 |
13 | .. literalinclude:: /advanced_features/scripts/two_part_wing_custom_mesh.py
14 | :start-after: checkpoint 0
15 | :end-before: checkpoint 1
16 |
17 | The following shows a visualization of the mesh.
18 |
19 | .. image:: /advanced_features/figs/two_part_mesh.png
20 |
21 | The complete script for the optimization is as follows.
22 | Make sure you go through the :ref:`Aerostructural_with_Wingbox_Walkthrough` before trying to understand this setup.
23 |
24 | .. embed-code::
25 | advanced_features/scripts/two_part_wing_custom_mesh.py
26 | :layout: interleave
27 |
28 | There is plenty of room for improvement.
29 | A finer mesh and a tighter optimization tolerance should be used.
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/customizing_prob_setup.rst:
--------------------------------------------------------------------------------
1 | .. _Customizing_Prob_Setup:
2 |
3 | Customizing Problem Setup
4 | =========================
5 |
6 | Specifying a Reference Area
7 | ---------------------------
8 |
9 | In order to use a specific reference area for calculating coefficients, rather than the default approach of area-weighted averaging of each lifting surface, a few modifications must be done. First, an option need to be passed to either ``AeroPoint`` or ``AeroStructPoint`` when it's intialized:
10 |
11 | .. code-block:: python
12 |
13 | aero_group = AeroPoint(surfaces=surfaces, user_specified_Sref=True)
14 |
15 | or in the aerostructural case,
16 |
17 | .. code-block:: python
18 |
19 | AS_point = AerostructPoint(surfaces=surfaces,user_specified_Sref=True)
20 |
21 | Next, a new independent variable called ``S_ref_total`` needs to be created in the run script. Here it is set to the value of ``areaRef``. Then, for each analysis point, this variable needs to be connected to each analysis point for performance computation.
22 |
23 | .. code-block:: python
24 |
25 | indep_var_comp.add_output('S_ref_total', val=areaRef, units='m**2')
26 | prob.model.connect('S_ref_total', point_name + '.S_ref_total')
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/ground_effect_correction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/ground_effect_correction.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/ground_effect_polars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/ground_effect_polars.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/multi_section_2_sym.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/multi_section_2_sym.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/oas_vsp_mesh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/oas_vsp_mesh.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/tacs-oas_coupling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/tacs-oas_coupling.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/two_part_mesh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/two_part_mesh.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/vsp777.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/vsp777.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/vsp_chordwise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/vsp_chordwise.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/vsp_spanwise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/vsp_spanwise.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/wing_and_tail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/wing_and_tail.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/figs/wingbox_Q400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/advanced_features/figs/wingbox_Q400.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/ground_effect.rst:
--------------------------------------------------------------------------------
1 | .. _Ground Effect:
2 |
3 | Ground Effect
4 | =============
5 |
6 | For certain flight conditions or types of aircraft, incorporating ground effect may be important.
7 | Mathematically, the ground effect is simply an extra boundary condition imposed such that the velocities normal to the ground plane are zero.
8 | In vortex lattice methods, this is accomplished by mirroring a second copy of the mesh across the ground plane such that the vortex induced velocities cancel out at the ground.
9 | Some VLM solvers, such as AVL, model ground effect by mirroring across an x-y plane.
10 | This is simple to implement but is not strictly correct because of the influence of the angle of attack.
11 |
12 | In OpenAeroStruct, the ground effect mirroring plane is parallel to the freestream (influenced by angle of attack).
13 | This means that configurations deep in ground effect (small altitude compared to wingspan) or at higher angles of attack will obtain the correct ground effect correction.
14 |
15 | .. image:: /advanced_features/figs/groundplane.svg
16 | :width: 600
17 |
18 | To enable ground effect, add a :code:`groundplane: True` attribute to your aerosurfaces, like so:
19 |
20 | .. literalinclude:: /../../tests/integration_tests/test_aero_ground_effect.py
21 | :start-after: docs checkpoint 0
22 | :end-before: docs checkpoint 1
23 | :dedent: 8
24 |
25 | If groundplane is turned on for an AeroPoint or AeroStructPoint, a new input will be created (height_agl) which represents the distance from the origin (in airplane coordinates) to the ground plane.
26 | The default value, 8000 meters, produces essentially zero ground effect.
27 |
28 | Note that symmetry must be turned on for the ground effect correction to be used.
29 | Also, crosswind (beta) may not be used when ground effect is turned on.
30 | Finally, take care when defining geometry and run cases that your baseline mesh does not end up below the ground plane.
31 | This can occur for wings with long chord, anhedral, shear, tail surfaces located far behind the wing, high angles of attack, or some combination.
32 |
33 | The following plots (generated using the :code:`examples/drag_polar_ground_effect.py` file) illustrate the effect of the ground plane on a rectangular wing with aspect ratio 12.
34 | As the wing approaches the ground, induced drag is significantly reduced compared to the free-flight induced drag.
35 | These results are consistent with published values in the literature, for example "Lifting-Line Predictions for Induced Drag and Lift in Ground Effect" by Phillips and Hunsaker.
36 |
37 | .. image:: /advanced_features/figs/ground_effect_correction.png
38 | :width: 600
39 |
40 | .. image:: /advanced_features/figs/ground_effect_polars.png
41 | :width: 600
42 |
43 |
44 |
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/mphys_coupling.rst:
--------------------------------------------------------------------------------
1 | .. _MPhys Integration:
2 |
3 | MPhys Integration
4 | =================
5 |
6 | OpenAeroStruct provides an interface for coupling its internal solvers with `MPhys `_.
7 | MPhys is a package that standardizes multiphysics problems in OpenMDAO across multiple fidelity levels.
8 | MPhys eases the problem set up, provides straightforward extension to new disciplines, and has a library of OpenMDAO groups for multidisciplinary problems addressed by its standard.
9 | The MPhys library currently supports from the following libraries: `ADFlow `_, `DAFoam `_, `FunToFEM `_,
10 | `pyCycle `_, `pyGeo `_, and `TACS `_.
11 |
12 | .. note:: Currently, only OpenAeroStruct's aerodynamic solver is supported in the MPhys wrapper. The structural solver has yet to be added.
13 |
14 | This potentially gives OpenAeroStruct the capability to be coupled with any of the above mentioned libraries.
15 | One could, for example, couple a 3D high-fidelity wingbox structural model in TACS with a VLM aerodynamic wing model in OpenAerostruct using FunToFEM's load/displacement coupling scheme.
16 | The following shows an example of a result from a from such an analysis.
17 |
18 | .. image:: /advanced_features/figs/tacs-oas_coupling.png
19 |
20 | The script below shows an example of how to setup and run the MPhys interface.
21 | The example couples pyGeo's OpenVSP parameterization with OpenAeroStruct's aerodynamic solver to perform an aerodynamic optimization.
22 | The interface will also write out aerodynamic solutions in a ``.plt`` file format that can open and viewed in TecPlot.
23 |
24 | .. embed-code::
25 | advanced_features/scripts/mphys_opt_chord.py
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/multiple_surfaces.rst:
--------------------------------------------------------------------------------
1 | .. _Multiple_Lifting_Surfaces:
2 |
3 | Multiple Lifting Surfaces
4 | =========================
5 |
6 | It's easily possible to simulate multiple lifting surfaces simultaneously in OpenAeroStruct.
7 | The most straightforward example is a wing and a tail for a conventional airplane, as shown below, though OpenAeroStruct can handle any arbitrary collection of lifting surfaces.
8 | Note that the moment coefficient is always normalized with respect to the MAC of the first surface in the list; therefore, the main lifting surface (i.e., wing) should always be the first entry in the list.
9 |
10 | .. literalinclude:: /../../tests/integration_tests/test_multiple_aero_analysis.py
11 | :start-after: docs checkpoint 0
12 | :end-before: docs checkpoint 1
13 | :dedent: 8
14 |
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/multipoint.rst:
--------------------------------------------------------------------------------
1 | .. _Multipoint Optimization:
2 |
3 | Multipoint Optimization
4 | =======================
5 |
6 | To simulate multiple flight conditions in a single analysis or optimization, you can add multiple `AeroPoint` or `AerostructPoint` groups to the problem.
7 | This allows you to analyze the performance of the aircraft at multiple flight conditions simultaneously, such as at different cruise and maneuver conditions.
8 |
9 |
10 | Aerodynamic Optimization Example
11 | --------------------------------
12 | We optimize the aircraft at two cruise flight conditions below.
13 |
14 | .. literalinclude:: /../../tests/integration_tests/test_multipoint_aero.py
15 | :start-after: docs checkpoint 0
16 | :end-before: docs checkpoint 1
17 | :dedent: 8
18 |
19 |
20 | Aerostructural Optimization Example (Q400)
21 | ------------------------------------------
22 |
23 | This is an additional example of a multipoint aerostructural optimization with the wingbox model using a wing based on the Bombardier Q400.
24 | Here we also create a custom mesh instead of using one provided by OpenAeroStruct.
25 | Make sure you go through the :ref:`Aerostructural_with_Wingbox_Walkthrough` before trying to understand this example.
26 |
27 | .. embed-code::
28 | advanced_features/scripts/wingbox_mpt_Q400_example.py
29 |
30 | The following shows a visualization of the results.
31 | As can be seen, there is plenty of room for improvement.
32 | A finer mesh and a tighter optimization tolerance should be used.
33 |
34 | .. image:: /advanced_features/figs/wingbox_Q400.png
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/openconcept_coupling.rst:
--------------------------------------------------------------------------------
1 | .. _Mission Analysis using OpenConcept:
2 |
3 | Integrating Mission Analysis
4 | ============================
5 |
6 | You can perform aerodynamic or aerostructural wing design optimization considering full mission analysis by integrating OpenAeroStruct with `OpenConcept `_.
7 | OpenConcept is a toolkit for aircraft conceptual design, which is also built on top of the OpenMDAO framework.
8 |
9 | Examples of the OpenAeroStruct-OpenConcept coupled work can be found in OpenConcept `repository `_ and `documentation `_.
--------------------------------------------------------------------------------
/openaerostruct/docs/advanced_features/openvsp_mesh_example.rst:
--------------------------------------------------------------------------------
1 | .. _Openvsp_Mesh:
2 |
3 | OpenVSP-generated Mesh Example
4 | ==============================
5 |
6 | OpenAeroStruct also has the ability to generate VLM meshes from an OpenVSP ``.vsp3`` file.
7 | In order to use this feature, users must have the OpenVSP Python API installed in their Python environment.
8 | For instructions on how to install OpenVSP's Python API see `here `_.
9 |
10 | Here is an example script with an aero surface mesh generated for a Boeing 777 aircraft defined in an OpenVSP model.
11 | This should help you understand how meshes are defined in OpenAeroStruct and how to create them for your own custom planform shapes.
12 | This is yet another alternative to the mesh definition approaches described in :ref:`Aerodynamic_Optimization_Walkthrough` and :ref:`Custom_Mesh`.
13 |
14 | The OpenVSP model used in this example is shown below:
15 |
16 | .. image:: /advanced_features/figs/vsp777.png
17 |
18 | The following shows the portion of the example script in which the user provides the OpenVSP model for the mesh.
19 | In this example we'll only be importing the wing, horizontal, and vertical tail from the OpenVSP model shown above.
20 |
21 | .. literalinclude:: /advanced_features/scripts/run_vsp_777.py
22 | :start-after: checkpoint 0
23 | :end-before: checkpoint 1
24 |
25 | The following shows a visualization of the mesh.
26 |
27 | .. image:: /advanced_features/figs/oas_vsp_mesh.png
28 |
29 | The complete script for the aerodynamic analysis is as follows.
30 |
31 | .. embed-code::
32 | advanced_features/scripts/run_vsp_777.py
33 |
34 | The user may have noticed that the VLM mesh density is never explicitly defined in the script.
35 | The discretization of the VLM is based on the tessellation refinement define in OpenVSP model.
36 | To get an idea of what the discretization will look like, users can turn on the wireframe model for each surface in OpenVSP.
37 | The spanwise and chordwise lines will roughly correspond to panels in the exported VLM mesh.
38 | More or fewer VLM panels can be added in the chordwise and spanwise direction for each surface by toggling the following OpenVSP parameters.
39 |
40 | .. image:: /advanced_features/figs/vsp_chordwise.png
41 | .. image:: /advanced_features/figs/vsp_spanwise.png
42 |
--------------------------------------------------------------------------------
/openaerostruct/docs/aero_walkthrough/part_1.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 | from openaerostruct.meshing.mesh_generator import generate_mesh
6 | from openaerostruct.geometry.geometry_group import Geometry
7 | from openaerostruct.aerodynamics.aero_groups import AeroPoint
8 |
9 | # Create a dictionary to store options about the mesh
10 | mesh_dict = {"num_y": 7, "num_x": 2, "wing_type": "CRM", "symmetry": True, "num_twist_cp": 5}
11 |
12 | # Generate the aerodynamic mesh based on the previous dictionary
13 | mesh, twist_cp = generate_mesh(mesh_dict)
14 |
--------------------------------------------------------------------------------
/openaerostruct/docs/aero_walkthrough/part_2.py:
--------------------------------------------------------------------------------
1 | # Create a dictionary with info and options about the aerodynamic
2 | # lifting surface
3 | surface = {
4 | # Wing definition
5 | "name": "wing", # name of the surface
6 | "symmetry": True, # if true, model one half of wing
7 | # reflected across the plane y = 0
8 | "S_ref_type": "wetted", # how we compute the wing area,
9 | # can be 'wetted' or 'projected'
10 | "fem_model_type": "tube",
11 | "twist_cp": twist_cp,
12 | "mesh": mesh,
13 | # Aerodynamic performance of the lifting surface at
14 | # an angle of attack of 0 (alpha=0).
15 | # These CL0 and CD0 values are added to the CL and CD
16 | # obtained from aerodynamic analysis of the surface to get
17 | # the total CL and CD.
18 | # These CL0 and CD0 values do not vary wrt alpha.
19 | "CL0": 0.0, # CL of the surface at alpha=0
20 | "CD0": 0.015, # CD of the surface at alpha=0
21 | # Airfoil properties for viscous drag calculation
22 | "k_lam": 0.05, # percentage of chord with laminar
23 | # flow, used for viscous drag
24 | "t_over_c_cp": np.array([0.15]), # thickness over chord ratio (NACA0015)
25 | "c_max_t": 0.303, # chordwise location of maximum (NACA0015)
26 | # thickness
27 | "with_viscous": True, # if true, compute viscous drag
28 | "with_wave": False, # if true, compute wave drag
29 | }
30 |
--------------------------------------------------------------------------------
/openaerostruct/docs/aero_walkthrough/part_3.py:
--------------------------------------------------------------------------------
1 | # Create the OpenMDAO problem
2 | prob = om.Problem()
3 |
4 | # Create an independent variable component that will supply the flow
5 | # conditions to the problem.
6 | indep_var_comp = om.IndepVarComp()
7 | indep_var_comp.add_output("v", val=248.136, units="m/s")
8 | indep_var_comp.add_output("alpha", val=5.0, units="deg")
9 | indep_var_comp.add_output("Mach_number", val=0.84)
10 | indep_var_comp.add_output("re", val=1.0e6, units="1/m")
11 | indep_var_comp.add_output("rho", val=0.38, units="kg/m**3")
12 | indep_var_comp.add_output("cg", val=np.zeros((3)), units="m")
13 |
14 | # Add this IndepVarComp to the problem model
15 | prob.model.add_subsystem("prob_vars", indep_var_comp, promotes=["*"])
16 |
--------------------------------------------------------------------------------
/openaerostruct/docs/aero_walkthrough/part_4.py:
--------------------------------------------------------------------------------
1 | # Create and add a group that handles the geometry for the
2 | # aerodynamic lifting surface
3 | geom_group = Geometry(surface=surface)
4 | prob.model.add_subsystem(surface["name"], geom_group)
5 |
6 | # Create the aero point group, which contains the actual aerodynamic
7 | # analyses
8 | aero_group = AeroPoint(surfaces=[surface])
9 | point_name = "aero_point_0"
10 | prob.model.add_subsystem(point_name, aero_group, promotes_inputs=["v", "alpha", "Mach_number", "re", "rho", "cg"])
11 |
12 | name = surface["name"]
13 |
14 | # Connect the mesh from the geometry component to the analysis point
15 | prob.model.connect(name + ".mesh", point_name + "." + name + ".def_mesh")
16 |
17 | # Perform the connections with the modified names within the
18 | # 'aero_states' group.
19 | prob.model.connect(name + ".mesh", point_name + ".aero_states." + name + "_def_mesh")
20 |
21 | prob.model.connect(name + ".t_over_c", point_name + "." + name + "_perf." + "t_over_c")
22 |
--------------------------------------------------------------------------------
/openaerostruct/docs/aero_walkthrough/part_5.py:
--------------------------------------------------------------------------------
1 | # Import the Scipy Optimizer and set the driver of the problem to use
2 | # it, which defaults to an SLSQP optimization method
3 | import openmdao.api as om
4 |
5 | prob.driver = om.ScipyOptimizeDriver()
6 | prob.driver.options["tol"] = 1e-9
7 |
8 | recorder = om.SqliteRecorder("aero_analysis_test.db")
9 | prob.driver.add_recorder(recorder)
10 | prob.driver.recording_options["record_derivatives"] = True
11 | prob.driver.recording_options["includes"] = ["*"]
12 |
13 | # Setup problem and add design variables, constraint, and objective
14 | prob.model.add_design_var("wing.twist_cp", lower=-10.0, upper=15.0)
15 | prob.model.add_constraint(point_name + ".wing_perf.CL", equals=0.5)
16 | prob.model.add_objective(point_name + ".wing_perf.CD", scaler=1e4)
17 |
--------------------------------------------------------------------------------
/openaerostruct/docs/aero_walkthrough/part_6.py:
--------------------------------------------------------------------------------
1 | # Set up and run the optimization problem
2 | prob.setup()
3 | prob.run_driver()
4 |
--------------------------------------------------------------------------------
/openaerostruct/docs/aero_walkthrough/part_7.py:
--------------------------------------------------------------------------------
1 | assert_near_equal(prob["aero_point_0.wing_perf.CD"][0], 0.033389699871650073, 1e-6)
2 | assert_near_equal(prob["aero_point_0.wing_perf.CL"][0], 0.5, 1e-6)
3 | assert_near_equal(prob["aero_point_0.CM"][1], -1.7885550372372376, 1e-6)
4 |
--------------------------------------------------------------------------------
/openaerostruct/docs/aerostructural_index.rst:
--------------------------------------------------------------------------------
1 | .. _Aerostructural Optimization:
2 |
3 | Aerostructural Optimization
4 | ===========================
5 |
6 | OpenAeroStruct implements two wing structural models: tubular spar and wingbox.
7 | We recommend going through the basic tubular spar case first.
8 |
9 | .. toctree::
10 | :maxdepth: 1
11 |
12 | aerostructural_tube_walkthrough.rst
13 |
14 | Then, you can review the wingbox walkthrough to understand how OAS models more detailed wing structures.
15 |
16 | .. toctree::
17 | :maxdepth: 1
18 |
19 | aerostructural_wingbox_walkthrough.rst
--------------------------------------------------------------------------------
/openaerostruct/docs/aerostructural_tube_walkthrough.rst:
--------------------------------------------------------------------------------
1 | .. _Aerostructural_Walkthrough:
2 |
3 | Aerostructural Optimization with Tubular Spar
4 | =============================================
5 |
6 | With aerodynamic- and structural-only analyses done, we now examine an aerostructural design problem.
7 | The construction of the problem follows the same logic as outlined in :ref:`Aerodynamic_Optimization_Walkthrough`, though with some added details.
8 | For example, we use an `AerostructPoint` group instead of an `AeroGroup` because it contains the additional components needed for aerostructural optimization.
9 | Additionally, we have more variable connections due to the more complex problem formulation.
10 |
11 | For more details about ``mesh_dict`` and ``surface`` in the following script, see :ref:`Mesh and Surface Dict`.
12 |
13 | .. literalinclude:: /../../tests/integration_tests/test_aerostruct.py
14 | :start-after: docs checkpoint 0
15 | :end-before: docs checkpoint 1
16 | :dedent: 8
17 |
--------------------------------------------------------------------------------
/openaerostruct/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is execfile()d with the current directory set to its
3 | # containing dir.
4 | import os
5 | import sys
6 | import importlib
7 | from unittest.mock import Mock
8 | from openaerostruct.docs._utils.generate_sourcedocs import generate_docs
9 | from sphinx_mdolab_theme.config import *
10 |
11 | sys.path.insert(0, os.path.abspath(".."))
12 | sys.path.insert(0, os.path.abspath("."))
13 | sys.path.insert(0, os.path.join("./_exts"))
14 |
15 | # Only mock the ones that don't import.
16 | MOCK_MODULES = ["h5py", "petsc4py", "pyoptsparse", "pyDOE2"]
17 | for mod_name in MOCK_MODULES:
18 | try:
19 | importlib.import_module(mod_name)
20 | except ImportError:
21 | sys.modules[mod_name] = Mock()
22 |
23 | # --- General configuration ---
24 |
25 | # Add any Sphinx extension module names here, as strings. They can be
26 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
27 | # ones.
28 | extensions = [
29 | "sphinx.ext.autodoc",
30 | "sphinx.ext.doctest",
31 | "sphinx.ext.coverage",
32 | "sphinx.ext.mathjax",
33 | "sphinx.ext.viewcode",
34 | "sphinx.ext.githubpages",
35 | "numpydoc",
36 | "sphinx_copybutton",
37 | "sphinx_mdolab_theme.ext.embed_code",
38 | "sphinx_mdolab_theme.ext.embed_compare",
39 | "sphinx_mdolab_theme.ext.embed_n2",
40 | ]
41 |
42 | # directories for which to generate sourcedocs
43 | packages = [
44 | "aerodynamics",
45 | "functionals",
46 | "geometry",
47 | "integration",
48 | "structures",
49 | "transfer",
50 | ]
51 |
52 | generate_docs("..", "../..", packages, project_name="openaerostruct")
53 |
54 | # If your documentation needs a minimal Sphinx version, state it here.
55 | needs_sphinx = "1.6.2"
56 |
57 | numpydoc_show_class_members = False
58 |
59 | # General information about the project.
60 | project = "OpenAeroStruct"
61 | copyright = "2018, John Jasa, Dr. John Hwang, Justin S. Gray"
62 | author = "John Jasa, Dr. John Hwang, Justin S. Gray"
63 |
64 | # The version info for the project you're documenting, acts as replacement for
65 | # |version| and |release|, also used in various other places throughout the
66 | # built documents.
67 |
68 | import re
69 |
70 | __version__ = re.findall(
71 | r"""__version__ = ["']+([0-9\.]*)["']+""",
72 | open("../__init__.py").read(),
73 | )[0]
74 |
75 | # The short X.Y version.
76 | version = __version__
77 |
78 | # The full version, including alpha/beta/rc tags.
79 | release = __version__
80 |
81 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
82 | # using the given strftime format.
83 | html_last_updated_fmt = "%b %d, %Y"
84 |
85 | # Output file base name for HTML help builder.
86 | htmlhelp_basename = "OpenAeroStructdoc"
87 |
88 | html_extra_path = ["_n2html"]
89 |
90 | # The master toctree document.
91 | master_doc = "index"
92 |
93 | # One entry per manual page. List of tuples
94 | # (source start file, name, description, authors, manual section).
95 | man_pages = [(master_doc, "openaerostruct", "OpenAeroStruct Documentation", [author], 1)]
96 |
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/OptView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/OptView.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/aero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/aero.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/aero_sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/aero_sample.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/aerostruct_sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/aerostruct_sample.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/aerostruct_xdsm.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/aerostruct_xdsm.pdf
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/aerostruct_xdsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/aerostruct_xdsm.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/collapsed_aerostruct_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/collapsed_aerostruct_diagram.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/example.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/plotall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/plotall.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/problem_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/problem_diagram.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/wingbox_fine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/wingbox_fine.png
--------------------------------------------------------------------------------
/openaerostruct/docs/figures/wingbox_opt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/docs/figures/wingbox_opt.png
--------------------------------------------------------------------------------
/openaerostruct/docs/installation.rst:
--------------------------------------------------------------------------------
1 | .. _Installation:
2 |
3 | Installation
4 | ============
5 |
6 | To use OpenAeroStruct, you must first install Python 3.
7 |
8 | The easiest way to get started is to install OpenAeroStruct from PyPI :
9 |
10 | .. code-block:: bash
11 |
12 | pip install openaerostruct
13 |
14 | If you'd like easier access to the examples and source code, you can also install OpenAeroStruct by cloning the OpenAeroStruct repository:
15 |
16 | .. code-block:: bash
17 |
18 | git clone https://github.com/mdolab/OpenAeroStruct.git
19 |
20 | Then from within the OpenAeroStruct folder, pip install the package:
21 |
22 | .. code-block:: bash
23 |
24 | cd openaerostruct
25 | pip install -e .
26 |
27 | Both methods will automatically install the dependencies: numpy, scipy, matplotlib, and OpenMDAO.
28 |
29 | The oldest and latest versions of the dependencies that we test regularly are the following (other versions may work, but no guarantees):
30 |
31 | .. list-table::
32 | :header-rows: 1
33 |
34 | * - Dependency
35 | - oldest
36 | - latest
37 | * - Python
38 | - 3.9
39 | - 3.11
40 | * - NumPy
41 | - 1.21
42 | - 1.26
43 | * - SciPy
44 | - 1.7
45 | - 1.15
46 | * - OpenMDAO
47 | - 3.35
48 | - latest
49 | * - pyGeo (optional)
50 | - 1.15.0
51 | - latest
52 | * - OpenVSP (optional)
53 | - 3.33
54 | - 3.33
55 | * - MPhys (optional)
56 | - 2.0
57 | - latest
58 |
59 | Numpy 2.0 or later should also work, but we currently do not run tests with Numpy 2.
60 |
61 | If you are unfamiliar with OpenMDAO and wish to modify the internals of OpenAeroStruct, you should examine the OpenMDAO documentation at https://openmdao.org/newdocs/versions/latest/main.html. The tutorials provided with OpenMDAO are helpful to understand the basics of using OpenMDAO to solve an optimization problem.
62 |
63 | Advanced Options
64 | ~~~~~~~~~~~~~~~~
65 |
66 | To run the tests on your machine, use the [test] option. This will install the pytest package.
67 |
68 | .. code-block:: bash
69 |
70 | pip install -e .[test]
71 |
72 | Then run the tests from the OpenAeroStruct root directory by calling:
73 |
74 | .. code-block:: bash
75 |
76 | testflo -v .
77 |
78 | To install the dependencies to build the documentation locally, run:
79 |
80 | .. code-block:: bash
81 |
82 | pip install -e .[docs]
83 |
--------------------------------------------------------------------------------
/openaerostruct/docs/quick_example.rst:
--------------------------------------------------------------------------------
1 | .. _Quick_Example:
2 |
3 | Quick Example
4 | =============
5 |
6 | Here is an example run script to perform aerodynamic optimization.
7 | We'll go into more detail later about how to set up a model, define the optimization problem, and postprocess results.
8 |
9 | .. literalinclude:: /../../tests/integration_tests/test_aero.py
10 | :start-after: docs checkpoint 0
11 | :end-before: docs checkpoint 1
12 | :dedent: 8
13 |
--------------------------------------------------------------------------------
/openaerostruct/docs/struct_example.rst:
--------------------------------------------------------------------------------
1 | .. _Structural_Optimization_Example:
2 |
3 | Structural Optimization
4 | =======================
5 |
6 | OpenAeroStruct can also handle structural-only optimization problems.
7 | Here we prescribe a load on the spar and allow the optimizer to vary the structural thickness to minimize weight subject to failure constraints.
8 | Although doing structural-only optimizations is relatively rare, this is a building block towards aerostructural optimization.
9 |
10 | For more details about ``mesh_dict`` and ``surf_dict`` in the following script, see :ref:`Mesh and Surface Dict`.
11 |
12 | .. literalinclude:: /../../tests/integration_tests/test_struct.py
13 | :start-after: docs checkpoint 0
14 | :end-before: docs checkpoint 1
15 | :dedent: 8
16 |
--------------------------------------------------------------------------------
/openaerostruct/docs/user_reference.rst:
--------------------------------------------------------------------------------
1 | .. _User Reference:
2 |
3 | User Reference
4 | ==============
5 |
6 | .. toctree::
7 | :maxdepth: 2
8 |
9 |
10 | user_reference/mesh_surface_dict.rst
11 | user_reference/debugging_tips.rst
12 | user_reference/v1_v2_conversion.rst
--------------------------------------------------------------------------------
/openaerostruct/docs/user_reference/debugging_tips.rst:
--------------------------------------------------------------------------------
1 | .. _Debugging Tips:
2 |
3 | Debugging Tips
4 | ==============
5 |
6 | Here are some common issues and possible solutions for them.
7 |
8 | - When debugging, you should use the smallest mesh size possible to minimize time invested.
9 |
10 | - Start with a simple optimization case and gradually add design variables and constraints while examining the change in optimized result. The design trade-offs in a simpler optimization problem should be easier to understand.
11 |
12 | - Use `plot_wing` often to visualize your results. Make sure your wing geometry and structure are set up like you expect.
13 |
14 | - After running an optimization case, always check to see if your constraints are satisfied. pyOptSparse prints out this information automatically, but if you're using Scipy's optimizer, you may need to manually print the constraints.
15 |
16 | - Check out https://openmdao.org/newdocs/versions/latest/features/recording/case_reader_data.html for more info about how to access the saved data in the outputted `.db` file.
17 |
18 | - If you are unsure of where a parameter or unknown is within the problem, view the N2 diagram. You can generate an html file by writing `openmdao.n2(prob)` in the runscript. You can use the search function to look for specific variables.
19 |
20 | - Use the `thickness_intersects` constraint when varying thickness to maintain a physically possible solution.
--------------------------------------------------------------------------------
/openaerostruct/examples/example_deriv_check.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import openmdao.api as om
4 |
5 | from numpy.testing import assert_almost_equal
6 |
7 | from openaerostruct.structures.section_properties_tube import SectionPropertiesTube
8 | from openaerostruct.utils.testing import get_default_surfaces, run_test, view_mat
9 |
10 |
11 | class Test(unittest.TestCase):
12 | """
13 | This class contains two tests on a single component.
14 | Each test verifies the derivatives found within the component by using
15 | OpenMDAO's derivative approximation functions to compare against
16 | finite-difference or complex-step.
17 | """
18 |
19 | def test_quick(self):
20 | """Short pre-setup test to compare component derivs."""
21 | surfaces = get_default_surfaces()
22 |
23 | comp = SectionPropertiesTube(surface=surfaces[0])
24 |
25 | run_test(self, comp, complex_flag=True)
26 |
27 | def test_detailed(self):
28 | """This is a longer version of the previous method, with plotting."""
29 |
30 | # Load in default lifting surfaces to setup the comparison
31 | surfaces = get_default_surfaces()
32 |
33 | # Instantiate an OpenMDAO problem and add the component we want to test
34 | # as asubsystem, giving that component a default lifting surface
35 | prob = om.Problem()
36 | prob.model.add_subsystem("tube", SectionPropertiesTube(surface=surfaces[0]))
37 |
38 | # Set up the problem and ensure it uses complex arrays so we can check
39 | # the derivatives using complex step
40 | prob.setup(force_alloc_complex=True)
41 |
42 | # Actually run the model, which is just a component in this case, then
43 | # check the derivatives and store the results in the `check` dict
44 | prob.run_model()
45 | check = prob.check_partials(compact_print=False)
46 |
47 | # Loop through this `check` dictionary and visualize the approximated
48 | # and computed derivatives
49 | for key, subjac in check[list(check.keys())[0]].items():
50 | print()
51 | print(key)
52 | view_mat(subjac["J_fd"], subjac["J_fwd"], key)
53 |
54 | # Loop through the `check` dictionary and perform assert that the
55 | # approximated deriv must be very close to the computed deriv
56 | for key, subjac in check[list(check.keys())[0]].items():
57 | if subjac["magnitude"].fd > 1e-6:
58 | assert_almost_equal(subjac["rel error"].forward, 0.0, err_msg="deriv of %s wrt %s" % key)
59 | assert_almost_equal(subjac["rel error"].reverse, 0.0, err_msg="deriv of %s wrt %s" % key)
60 |
61 |
62 | # Run the tests included in this script
63 | if __name__ == "__main__":
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/openaerostruct/functionals/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/functionals/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/functionals/sum_areas.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 |
4 | class SumAreas(om.ExplicitComponent):
5 | """
6 | Compute the total surface area of the entire aircraft as a sum of its
7 | individual surfaces' surface areas.
8 |
9 | Parameters
10 | ----------
11 | S_ref : float
12 | Surface area for one lifting surface.
13 |
14 | Returns
15 | -------
16 | S_ref_total : float
17 | Total surface area of the aircraft based on the sum of individual
18 | surface areas.
19 |
20 | """
21 |
22 | def initialize(self):
23 | self.options.declare("surfaces", types=list)
24 |
25 | def setup(self):
26 | for surface in self.options["surfaces"]:
27 | name = surface["name"]
28 | self.add_input(name + "_S_ref", val=1.0, units="m**2")
29 |
30 | self.add_output("S_ref_total", val=0.0, units="m**2", tags=["mphys_result"])
31 |
32 | self.declare_partials("*", "*", val=1.0)
33 |
34 | def compute(self, inputs, outputs):
35 | outputs["S_ref_total"] = 0.0
36 | for surface in self.options["surfaces"]:
37 | name = surface["name"]
38 | S_ref = inputs[name + "_S_ref"]
39 | outputs["S_ref_total"] += S_ref
40 |
--------------------------------------------------------------------------------
/openaerostruct/functionals/total_aero_performance.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 | from openaerostruct.functionals.moment_coefficient import MomentCoefficient
4 | from openaerostruct.functionals.total_lift_drag import TotalLiftDrag
5 | from openaerostruct.functionals.sum_areas import SumAreas
6 |
7 |
8 | class TotalAeroPerformance(om.Group):
9 | """
10 | Group to contain the total aerodynamic performance components.
11 | """
12 |
13 | def initialize(self):
14 | self.options.declare("surfaces", types=list)
15 | self.options.declare("user_specified_Sref", types=bool)
16 |
17 | def setup(self):
18 | surfaces = self.options["surfaces"]
19 |
20 | if not self.options["user_specified_Sref"]:
21 | self.add_subsystem(
22 | "sum_areas", SumAreas(surfaces=surfaces), promotes_inputs=["*S_ref"], promotes_outputs=["S_ref_total"]
23 | )
24 |
25 | self.add_subsystem(
26 | "CL_CD",
27 | TotalLiftDrag(surfaces=surfaces),
28 | promotes_inputs=["*CL", "*CD", "*S_ref", "S_ref_total", "rho", "v"],
29 | promotes_outputs=["CL", "CD", "L", "D"],
30 | )
31 |
32 | self.add_subsystem(
33 | "moment",
34 | MomentCoefficient(surfaces=surfaces),
35 | promotes_inputs=["v", "cg", "rho", "*S_ref", "*b_pts", "*widths", "*chords", "*sec_forces", "S_ref_total"],
36 | promotes_outputs=["CM"],
37 | )
38 |
--------------------------------------------------------------------------------
/openaerostruct/functionals/total_performance.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 | from openaerostruct.functionals.breguet_range import BreguetRange
4 | from openaerostruct.functionals.equilibrium import Equilibrium
5 | from openaerostruct.functionals.center_of_gravity import CenterOfGravity
6 | from openaerostruct.functionals.moment_coefficient import MomentCoefficient
7 | from openaerostruct.functionals.total_lift_drag import TotalLiftDrag
8 | from openaerostruct.functionals.sum_areas import SumAreas
9 |
10 |
11 | class TotalPerformance(om.Group):
12 | """
13 | Group to contain the total aerostructural performance components.
14 | """
15 |
16 | def initialize(self):
17 | self.options.declare("surfaces", types=list)
18 | self.options.declare("user_specified_Sref", types=bool)
19 | self.options.declare("internally_connect_fuelburn", types=bool, default=True)
20 |
21 | def setup(self):
22 | surfaces = self.options["surfaces"]
23 |
24 | if not self.options["user_specified_Sref"]:
25 | self.add_subsystem(
26 | "sum_areas", SumAreas(surfaces=surfaces), promotes_inputs=["*S_ref"], promotes_outputs=["S_ref_total"]
27 | )
28 |
29 | if self.options["internally_connect_fuelburn"]:
30 | promote_fuelburn = ["fuelburn"]
31 | else:
32 | promote_fuelburn = []
33 |
34 | self.add_subsystem(
35 | "CL_CD",
36 | TotalLiftDrag(surfaces=surfaces),
37 | promotes_inputs=["*CL", "*CD", "*S_ref", "S_ref_total", "rho", "v"],
38 | promotes_outputs=["CL", "CD", "L", "D"],
39 | )
40 |
41 | self.add_subsystem(
42 | "fuelburn",
43 | BreguetRange(surfaces=surfaces),
44 | promotes_inputs=["*structural_mass", "CL", "CD", "CT", "speed_of_sound", "R", "Mach_number", "W0"],
45 | promotes_outputs=["fuelburn"],
46 | )
47 |
48 | self.add_subsystem(
49 | "L_equals_W",
50 | Equilibrium(surfaces=surfaces),
51 | promotes_inputs=["CL", "*structural_mass", "S_ref_total", "W0", "load_factor", "rho", "v"]
52 | + promote_fuelburn,
53 | promotes_outputs=["L_equals_W", "total_weight"],
54 | )
55 |
56 | self.add_subsystem(
57 | "CG",
58 | CenterOfGravity(surfaces=surfaces),
59 | promotes_inputs=["*structural_mass", "*cg_location", "total_weight", "W0", "empty_cg", "load_factor"]
60 | + promote_fuelburn,
61 | promotes_outputs=["cg"],
62 | )
63 |
64 | self.add_subsystem(
65 | "moment",
66 | MomentCoefficient(surfaces=surfaces),
67 | promotes_inputs=["v", "rho", "cg", "S_ref_total", "*b_pts", "*widths", "*chords", "*sec_forces", "*S_ref"],
68 | promotes_outputs=["CM"],
69 | )
70 |
--------------------------------------------------------------------------------
/openaerostruct/geometry/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/geometry/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/geometry/ffd_component.py:
--------------------------------------------------------------------------------
1 | """ Manipulate geometry mesh based on high-level design parameters. """
2 |
3 | import numpy as np
4 |
5 | import openmdao.api as om
6 |
7 | try:
8 | from pygeo import DVGeometry
9 | except ImportError:
10 | pass
11 |
12 |
13 | class GeometryMesh(om.ExplicitComponent):
14 | """
15 | OpenMDAO component that performs mesh manipulation functions. It reads in
16 | the initial mesh from the surface dictionary and outputs the altered
17 | mesh based on the geometric design variables.
18 |
19 | Depending on the design variables selected or the supplied geometry information,
20 | only some of the follow parameters will actually be given to this component.
21 | If parameters are not active (they do not deform the mesh), then
22 | they will not be given to this component.
23 |
24 | Parameters
25 | ----------
26 |
27 | Returns
28 | -------
29 | mesh[nx, ny, 3] : numpy array
30 | Modified mesh based on the initial mesh in the surface dictionary and
31 | the geometric design variables.
32 | """
33 |
34 | def initialize(self):
35 | self.options.declare("surface", types=dict)
36 | self.options.declare("DVGeo", types=DVGeometry)
37 |
38 | def setup(self):
39 | self.surface = surface = self.options["surface"]
40 | self.mx, self.my = self.surface["mx"], self.surface["my"]
41 |
42 | self.DVGeo = self.options["DVGeo"]
43 |
44 | # Associate a 'reference axis' for large-scale manipulation
45 | self.DVGeo.addRefAxis("wing_axis", xFraction=0.25, alignIndex="i")
46 |
47 | # Now add local (shape) variables
48 | self.DVGeo.addLocalDV("shape", lower=-0.5, upper=0.5, axis="z")
49 |
50 | pts = surface["mesh"].reshape(-1, 3)
51 |
52 | self.DVGeo.addPointSet(pts, "surface")
53 |
54 | coords = self.DVGeo.getLocalIndex(0)
55 | self.inds = coords[:, 0, :]
56 | self.inds2 = coords[:, 1, :]
57 |
58 | self.add_input("shape", val=np.zeros((self.mx, self.my)), units="m")
59 |
60 | self.add_output("mesh", val=surface["mesh"], units="m")
61 |
62 | self.declare_partials("*", "*")
63 |
64 | def compute(self, inputs, outputs):
65 | surface = self.surface
66 |
67 | dvs = self.DVGeo.getValues()
68 |
69 | for i, row in enumerate(self.inds):
70 | for j, ind in enumerate(row):
71 | ind2 = self.inds2[i, j]
72 | dvs["shape"][ind] = inputs["shape"][i, j]
73 | dvs["shape"][ind2] = inputs["shape"][i, j]
74 |
75 | self.DVGeo.setDesignVars(dvs)
76 | coords = self.DVGeo.update("surface")
77 |
78 | mesh = coords.copy()
79 | mesh = mesh.reshape(surface["mesh"].shape)
80 | outputs["mesh"] = mesh
81 |
82 | def compute_partials(self, inputs, partials):
83 | self.DVGeo.computeTotalJacobian("surface")
84 | jac = self.DVGeo.JT["surface"].toarray().T
85 | my_jac = partials["mesh", "shape"]
86 | my_jac[:, :] = 0.0
87 |
88 | for i, ind in enumerate(self.inds.flatten()):
89 | ind2 = self.inds2.flatten()[i]
90 | my_jac[:, i] += jac[:, ind]
91 | my_jac[:, i] += jac[:, ind2]
92 |
93 | partials["mesh", "shape"] = my_jac
94 |
--------------------------------------------------------------------------------
/openaerostruct/geometry/monotonic_constraint.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class MonotonicConstraint(om.ExplicitComponent):
7 | """
8 | Produce a constraint that is violated if a user-chosen measure on the
9 | wing does not decrease monotonically from the root to the tip.
10 |
11 | Parameters
12 | ----------
13 | var_name : string
14 | The variable to which the user would like to apply the monotonic constraint.
15 |
16 | Returns
17 | -------
18 | monotonic[ny-1] : numpy array
19 | Values are greater than 0 if the constraint is violated.
20 |
21 | """
22 |
23 | def initialize(self):
24 | self.options.declare("var_name", types=str)
25 | self.options.declare("surface", types=dict)
26 |
27 | def setup(self):
28 | self.surface = surface = self.options["surface"]
29 | self.var_name = self.options["var_name"]
30 | self.con_name = "monotonic_" + self.var_name
31 |
32 | self.symmetry = surface["symmetry"]
33 | self.ny = surface["mesh"].shape[1]
34 |
35 | self.add_input(self.var_name, val=np.zeros(self.ny))
36 | self.add_output(self.con_name, val=np.zeros(self.ny - 1))
37 |
38 | rows = np.arange(0, self.ny - 1)
39 | rows = np.vstack((rows, rows)).flatten(order="F")
40 |
41 | cols = np.arange(1, self.ny - 1)
42 | cols = np.vstack((cols, cols)).flatten(order="F")
43 | cols = np.insert(cols, 0, 0)
44 | cols = np.append(cols, self.ny - 1)
45 |
46 | sparse_val = np.ones_like(rows)
47 | sparse_val[1::2] = -1
48 | if not self.symmetry:
49 | if self.ny % 2 == 0:
50 | sparse_val[self.ny - 2 :] *= -1
51 | else:
52 | sparse_val[self.ny - 1 :] *= -1
53 |
54 | self.declare_partials(self.con_name, self.var_name, rows=rows, cols=cols, val=sparse_val)
55 |
56 | def compute(self, inputs, outputs):
57 | # Compute the difference between adjacent variable values
58 | diff = inputs[self.var_name][:-1] - inputs[self.var_name][1:]
59 | if self.symmetry:
60 | outputs[self.con_name] = diff
61 | else:
62 | ny2 = (self.ny - 1) // 2
63 | outputs[self.con_name][:ny2] = diff[:ny2]
64 | outputs[self.con_name][ny2:] = -diff[ny2:]
65 |
--------------------------------------------------------------------------------
/openaerostruct/geometry/radius_comp.py:
--------------------------------------------------------------------------------
1 | """ Manipulate geometry mesh based on high-level design parameters. """
2 |
3 | import numpy as np
4 |
5 | import openmdao.api as om
6 | from openaerostruct.structures.utils import radii
7 |
8 |
9 | class RadiusComp(om.ExplicitComponent):
10 | """
11 | Compute the radius of a structural spar based on the mesh and thickness over
12 | chord ratio.
13 |
14 | Parameters
15 | ----------
16 | mesh[nx, ny, 3] : numpy array
17 | Nodal mesh defining the initial aerodynamic surface..
18 | t_over_c[ny-1] : numpy array
19 | The streamwise thickness-to-chord ratio of each VLM panel.
20 |
21 | Returns
22 | -------
23 | radius[ny-1] : numpy array
24 | Radius of each element of the FEM spar.
25 |
26 | """
27 |
28 | def initialize(self):
29 | self.options.declare("surface", types=dict)
30 |
31 | def setup(self):
32 | surface = self.options["surface"]
33 |
34 | nx, ny = self.nx, self.ny = surface["mesh"].shape[:2]
35 |
36 | self.add_input("mesh", val=np.zeros((nx, ny, 3)), units="m")
37 | self.add_input("t_over_c", val=np.ones((ny - 1)))
38 | self.add_output("radius", val=np.ones((ny - 1)), units="m")
39 |
40 | arange = np.arange(ny - 1)
41 | self.declare_partials("radius", "t_over_c", rows=arange, cols=arange)
42 |
43 | row = np.tile(np.zeros(6), ny - 1) + np.repeat(arange, 6)
44 | rows = np.concatenate([row, row])
45 | col = np.tile(np.arange(6), ny - 1) + np.repeat(3 * arange, 6)
46 | cols = np.concatenate([col, col + (nx - 1) * 3 * ny])
47 |
48 | self.declare_partials("radius", "mesh", rows=rows, cols=cols)
49 |
50 | def compute(self, inputs, outputs):
51 | outputs["radius"] = radii(inputs["mesh"], inputs["t_over_c"])
52 |
53 | def compute_partials(self, inputs, partials):
54 | """
55 | Obtain the radii of the FEM element based on local chord.
56 | """
57 | mesh = inputs["mesh"]
58 | vectors = mesh[-1, :, :] - mesh[0, :, :]
59 | chords = np.sqrt(np.sum(vectors**2, axis=1))
60 | t_c = inputs["t_over_c"]
61 |
62 | dr_dtoc = 0.25 * (chords[:-1] + chords[1:])
63 | partials["radius", "t_over_c"] = dr_dtoc
64 |
65 | dr_dchords = 0.25 * t_c
66 | dr = mesh[0, :] - mesh[-1, :]
67 |
68 | length = np.sqrt(np.sum(dr**2, axis=1))
69 | dr = dr / length[:, np.newaxis]
70 |
71 | drad = np.empty((self.ny - 1, 6))
72 | drad[:, :3] = np.einsum("i, ij -> ij", dr_dchords, dr[:-1, :])
73 | drad[:, 3:] = np.einsum("i, ij -> ij", dr_dchords, dr[1:, :])
74 | drad = drad.flatten()
75 |
76 | nn = 6 * (self.ny - 1)
77 | partials["radius", "mesh"][:nn] = drad
78 | partials["radius", "mesh"][nn:] = -drad
79 |
--------------------------------------------------------------------------------
/openaerostruct/integration/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/integration/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/integration/multipoint_comps.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 |
4 | class MultiCD(om.ExplicitComponent):
5 | def initialize(self):
6 | self.options.declare("n_points", types=int)
7 |
8 | def setup(self):
9 | self.n_points = self.options["n_points"]
10 | for i in range(self.n_points):
11 | self.add_input(str(i) + "_CD", val=0.0)
12 |
13 | self.add_output("CD", val=0.0)
14 | self.declare_partials("*", "*", val=1.0)
15 |
16 | def compute(self, inputs, outputs):
17 | outputs["CD"] = 0.0
18 | for i in range(self.n_points):
19 | outputs["CD"] += inputs[str(i) + "_CD"]
20 |
--------------------------------------------------------------------------------
/openaerostruct/mphys/__init__.py:
--------------------------------------------------------------------------------
1 | from .aero_builder import AeroBuilder
2 |
3 | __all__ = ["AeroBuilder"]
4 |
--------------------------------------------------------------------------------
/openaerostruct/mphys/aero_mesh.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import openmdao.api as om
3 | from mphys.core import MPhysVariables
4 |
5 | from openaerostruct.mphys.utils import get_number_of_nodes, get_src_indices
6 |
7 |
8 | class AeroMesh(om.IndepVarComp):
9 | """
10 | Component to read the initial mesh coordinates with OAS.
11 | Only the root will be responsible for this information.
12 | The mesh will be broadcasted to all other processors in a following step.
13 | """
14 |
15 | def initialize(self):
16 | self.options.declare("surfaces", default=None, desc="oas surface dicts", recordable=False)
17 |
18 | def setup(self):
19 | if self.comm.rank == 0:
20 | self.surfaces = self.options["surfaces"]
21 | nnodes = get_number_of_nodes(self.surfaces)
22 | src_indices = get_src_indices(self.surfaces)
23 | xpts = np.zeros(nnodes * 3)
24 | for surface in self.surfaces:
25 | surf_name = surface["name"]
26 | xpts[src_indices[surf_name]] = surface["mesh"]
27 | else:
28 | xpts = np.zeros(0)
29 | self.add_output(
30 | MPhysVariables.Aerodynamics.Surface.Mesh.COORDINATES,
31 | distributed=True,
32 | val=xpts,
33 | shape=xpts.size,
34 | units="m",
35 | desc="aero node coordinates",
36 | tags=["mphys_coordinates"],
37 | )
38 |
--------------------------------------------------------------------------------
/openaerostruct/mphys/aero_solver_group.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 | from mphys.core import MPhysVariables
3 |
4 | from openaerostruct.aerodynamics.compressible_states import CompressibleVLMStates
5 | from openaerostruct.aerodynamics.geometry import VLMGeometry
6 | from openaerostruct.aerodynamics.states import VLMStates
7 |
8 |
9 | class AeroSolverGroup(om.Group):
10 | """
11 | Group that contains the states for a incompresible/compressible aerodynamic analysis.
12 | """
13 |
14 | def initialize(self):
15 | self.options.declare("surfaces", default=None, desc="oas surface dicts", recordable=False)
16 | self.options.declare("compressible", default=True, desc="prandtl glauert compressibiity flag", recordable=True)
17 |
18 | def setup(self):
19 | self.surfaces = self.options["surfaces"]
20 |
21 | # Loop through each surface and promote relevant parameters
22 | proms_in = [
23 | ("alpha", MPhysVariables.Aerodynamics.FlowConditions.ANGLE_OF_ATTACK),
24 | ("beta", MPhysVariables.Aerodynamics.FlowConditions.YAW_ANGLE),
25 | ]
26 | proms_out = []
27 | for surface in self.surfaces:
28 | name = surface["name"]
29 |
30 | proms_in.append((name + "_normals", name + ".normals"))
31 | proms_out.append((name + "_sec_forces", name + ".sec_forces"))
32 |
33 | self.add_subsystem(name, VLMGeometry(surface=surface), promotes_inputs=[("def_mesh", name + "_def_mesh")])
34 |
35 | if self.options["compressible"]:
36 | proms_in.append(("Mach_number", MPhysVariables.Aerodynamics.FlowConditions.MACH_NUMBER))
37 | aero_states = CompressibleVLMStates(surfaces=self.surfaces)
38 | else:
39 | aero_states = VLMStates(surfaces=self.surfaces)
40 |
41 | self.add_subsystem(
42 | "solver",
43 | aero_states,
44 | promotes_inputs=proms_in + ["*"],
45 | promotes_outputs=proms_out + ["circulations", "*_mesh_point_forces"],
46 | )
47 |
--------------------------------------------------------------------------------
/openaerostruct/mphys/utils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 |
4 | def get_number_of_nodes(surfaces):
5 | """
6 | Get the total number of nodes over all surfaces.
7 |
8 | Parameters
9 | ----------
10 | surfaces : list[dict]
11 | List of all surfaces.
12 |
13 | Returns
14 | -------
15 | nnodes : int
16 | Total number of nodes across all surfaces.
17 |
18 | """
19 | nnodes = 0
20 | for surface in surfaces:
21 | nnodes += surface["mesh"].size // 3
22 | return nnodes
23 |
24 |
25 | def get_src_indices(surfaces):
26 | """
27 | Get src indices for each surface that will project each mesh into a single flattened array.
28 |
29 | Parameters
30 | ----------
31 | surfaces : list[dict]
32 | List of all surfaces.
33 |
34 | Returns
35 | -------
36 | src_indices : dict
37 | Dictionary holding source indices sorted by surface name.
38 |
39 | """
40 | src_indices = {}
41 | nindices = 0
42 | for surface in surfaces:
43 | surf_name = surface["name"]
44 | mesh = surface["mesh"]
45 | nx, ny, _ = mesh.shape
46 | surf_indices = np.arange(mesh.size) + nindices
47 | src_indices[surf_name] = surf_indices.reshape(nx, ny, 3)
48 | nindices += mesh.size
49 | return src_indices
50 |
51 |
52 | def get_node_indices(surfaces):
53 | """
54 | Get node indices for each surface that define the ordering of each node for all surfaces.
55 |
56 | Parameters
57 | ----------
58 | surfaces : list[dict]
59 | List of all surfaces.
60 |
61 | Returns
62 | -------
63 | node_indices : dict
64 | Dictionary holding node indices sorted by surface name.
65 |
66 | """
67 | node_indices = {}
68 | nnodes = 0
69 | for surface in surfaces:
70 | surf_name = surface["name"]
71 | mesh = surface["mesh"]
72 | nx, ny, _ = mesh.shape
73 | surf_indices = np.arange(nx * ny) + nnodes
74 | node_indices[surf_name] = surf_indices.reshape(nx, ny)
75 | nnodes += nx * ny
76 | return node_indices
77 |
--------------------------------------------------------------------------------
/openaerostruct/structures/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/structures/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/structures/assemble_k_group.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 | from openaerostruct.structures.transform import Transform
3 | from openaerostruct.structures.length import Length
4 | from openaerostruct.structures.local_stiff import LocalStiff
5 | from openaerostruct.structures.local_stiff_permuted import LocalStiffPermuted
6 | from openaerostruct.structures.local_stiff_transformed import LocalStiffTransformed
7 |
8 |
9 | class AssembleKGroup(om.Group):
10 | """Assemble that there compact local stiffness matrix."""
11 |
12 | def initialize(self):
13 | self.options.declare("surface", types=dict)
14 |
15 | def setup(self):
16 | surface = self.options["surface"]
17 |
18 | comp = Transform(surface=surface)
19 | self.add_subsystem("transform", comp, promotes=["*"])
20 |
21 | comp = Length(surface=surface)
22 | self.add_subsystem("length", comp, promotes=["*"])
23 |
24 | comp = LocalStiff(surface=surface)
25 | self.add_subsystem("local_stiff", comp, promotes=["*"])
26 |
27 | comp = LocalStiffPermuted(surface=surface)
28 | self.add_subsystem("local_stiff_permuted", comp, promotes=["*"])
29 |
30 | comp = LocalStiffTransformed(surface=surface)
31 | self.add_subsystem("local_stiff_transformed", comp, promotes=["*"])
32 |
--------------------------------------------------------------------------------
/openaerostruct/structures/compute_nodes.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class ComputeNodes(om.ExplicitComponent):
7 | """
8 | Compute FEM nodes based on aerodynamic mesh.
9 |
10 | The FEM nodes are placed at fem_origin * chord,
11 | with the default fem_origin = 0.35.
12 |
13 | Parameters
14 | ----------
15 | mesh[nx, ny, 3] : numpy array
16 | Array defining the nodal points of the lifting surface.
17 |
18 | Returns
19 | -------
20 | nodes[ny, 3] : numpy array
21 | Flattened array with coordinates for each FEM node.
22 |
23 | """
24 |
25 | def initialize(self):
26 | self.options.declare("surface", types=dict)
27 |
28 | def setup(self):
29 | surface = self.options["surface"]
30 | mesh = surface["mesh"]
31 | nx = mesh.shape[0]
32 | ny = mesh.shape[1]
33 |
34 | if surface["fem_model_type"] == "tube":
35 | self.fem_origin = surface["fem_origin"]
36 | else:
37 | y_upper = surface["data_y_upper"]
38 | x_upper = surface["data_x_upper"]
39 | y_lower = surface["data_y_lower"]
40 |
41 | self.fem_origin = (x_upper[0] * (y_upper[0] - y_lower[0]) + x_upper[-1] * (y_upper[-1] - y_lower[-1])) / (
42 | (y_upper[0] - y_lower[0]) + (y_upper[-1] - y_lower[-1])
43 | )
44 |
45 | self.add_input("mesh", val=np.zeros((nx, ny, 3)), units="m")
46 | self.add_output("nodes", val=np.zeros((ny, 3)), units="m")
47 |
48 | w = self.fem_origin
49 | n = ny * 3
50 |
51 | data = np.zeros((2 * n))
52 | data[:n] = 1 - w
53 | data[n:] = w
54 |
55 | arange = np.arange(n)
56 |
57 | rows = np.hstack((arange, arange))
58 | cols = np.hstack((arange, arange + (nx - 1) * n))
59 |
60 | self.declare_partials("nodes", "mesh", rows=rows, cols=cols, val=data)
61 |
62 | def compute(self, inputs, outputs):
63 | w = self.fem_origin
64 | mesh = inputs["mesh"]
65 | outputs["nodes"] = (1 - w) * mesh[0, :, :] + w * mesh[-1, :, :]
66 |
--------------------------------------------------------------------------------
/openaerostruct/structures/create_rhs.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class CreateRHS(om.ExplicitComponent):
7 | """
8 | Compute the right-hand-side of the K * u = f linear system to solve for the displacements.
9 | The RHS is based on the loads. For the aerostructural case, these are
10 | recomputed at each design point based on the aerodynamic loads.
11 |
12 | Parameters
13 | ----------
14 | loads[ny, 6] : numpy array
15 | Flattened array containing the loads applied on the FEM component,
16 | computed from the sectional forces.
17 |
18 | Returns
19 | -------
20 | forces[6*(ny+1)] : numpy array
21 | Right-hand-side of the linear system. The loads from the aerodynamic
22 | analysis or the user-defined loads.
23 | """
24 |
25 | def initialize(self):
26 | self.options.declare("surface", types=dict)
27 |
28 | def setup(self):
29 | surface = self.options["surface"]
30 |
31 | self.ny = surface["mesh"].shape[1]
32 |
33 | self.add_input("total_loads", val=np.zeros((self.ny, 6)), units="N")
34 | self.add_output("forces", val=np.ones(((self.ny + 1) * 6)), units="N")
35 |
36 | n = self.ny * 6
37 | arange = np.arange((n))
38 | self.declare_partials("forces", "total_loads", val=1.0, rows=arange, cols=arange)
39 |
40 | def compute(self, inputs, outputs):
41 | outputs["forces"][:] = 0.0
42 |
43 | # Populate the right-hand side of the linear system using the
44 | # prescribed or computed loads
45 | outputs["forces"][: 6 * self.ny] += inputs["total_loads"].reshape(self.ny * 6)
46 |
47 | # Remove extremely small values from the RHS so the linear system
48 | # can more easily be solved
49 | outputs["forces"][np.abs(outputs["forces"]) < 1e-6] = 0.0
50 |
--------------------------------------------------------------------------------
/openaerostruct/structures/disp.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class Disp(om.ExplicitComponent):
7 | """
8 | Reshape the flattened displacements from the linear system solution into
9 | a 2D array so we can more easily use the results.
10 |
11 | The solution to the linear system has meaingless entires due to the
12 | constraints on the FEM model. The displacements from this portion of
13 | the linear system are not needed, so we select only the relevant
14 | portion of the displacements for further calculations.
15 |
16 | Parameters
17 | ----------
18 | disp_aug[6*(ny+1)] : numpy array
19 | Augmented displacement array. Obtained by solving the system
20 | K * disp_aug = forces, where forces is a flattened version of loads.
21 |
22 | Returns
23 | -------
24 | disp[6*ny] : numpy array
25 | Actual displacement array formed by truncating disp_aug.
26 |
27 | """
28 |
29 | def initialize(self):
30 | self.options.declare("surface", types=dict)
31 |
32 | def setup(self):
33 | surface = self.options["surface"]
34 |
35 | self.ny = surface["mesh"].shape[1]
36 |
37 | self.add_input("disp_aug", val=np.zeros(((self.ny + 1) * 6)), units="m")
38 | self.add_output("disp", val=np.zeros((self.ny, 6)), units="m")
39 |
40 | n = self.ny * 6
41 | arange = np.arange((n))
42 | self.declare_partials("disp", "disp_aug", val=1.0, rows=arange, cols=arange)
43 |
44 | def compute(self, inputs, outputs):
45 | # Obtain the relevant portions of disp_aug and store the reshaped
46 | # displacements in disp
47 | outputs["disp"] = inputs["disp_aug"][:-6].reshape((-1, 6))
48 |
--------------------------------------------------------------------------------
/openaerostruct/structures/energy.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class Energy(om.ExplicitComponent):
7 | """Compute strain energy.
8 |
9 | Parameters
10 | ----------
11 | disp[ny, 6] : numpy array
12 | Actual displacement array formed by truncating disp_aug.
13 | loads[ny, 6] : numpy array
14 | Array containing the loads applied on the FEM component,
15 | computed from the sectional forces.
16 |
17 | Returns
18 | -------
19 | energy : float
20 | Total strain energy of the structural component.
21 |
22 | """
23 |
24 | def initialize(self):
25 | self.options.declare("surface", types=dict)
26 |
27 | def setup(self):
28 | surface = self.options["surface"]
29 |
30 | ny = surface["mesh"].shape[1]
31 |
32 | self.add_input("disp", val=np.zeros((ny, 6)), units="m")
33 | self.add_input("loads", val=np.zeros((ny, 6)), units="N")
34 | self.add_output("energy", val=0.0, units="N*m")
35 |
36 | self.declare_partials("*", "*")
37 |
38 | def compute(self, inputs, outputs):
39 | outputs["energy"] = np.sum(inputs["disp"] * inputs["loads"])
40 |
41 | def compute_partials(self, inputs, partials):
42 | partials["energy", "disp"][0, :] = inputs["loads"].real.flatten()
43 | partials["energy", "loads"][0, :] = inputs["disp"].real.flatten()
44 |
--------------------------------------------------------------------------------
/openaerostruct/structures/failure_exact.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class FailureExact(om.ExplicitComponent):
7 | """
8 | Output individual failure constraints on each FEM element.
9 |
10 | Parameters
11 | ----------
12 | vonmises[ny-1, 2] : numpy array
13 | von Mises stress magnitudes for each FEM element.
14 |
15 | Returns
16 | -------
17 | failure[ny-1, 2] : numpy array
18 | Array of failure conditions. Positive if element has failed.
19 |
20 | """
21 |
22 | def initialize(self):
23 | self.options.declare("surface", types=dict)
24 |
25 | def setup(self):
26 | surface = self.options["surface"]
27 |
28 | if surface["fem_model_type"] == "tube":
29 | num_failure_criteria = 2
30 | elif surface["fem_model_type"] == "wingbox":
31 | num_failure_criteria = 4
32 |
33 | self.ny = surface["mesh"].shape[1]
34 | self.sigma = surface["yield"]
35 |
36 | self.add_input("vonmises", val=np.zeros((self.ny - 1, num_failure_criteria)), units="N/m**2")
37 | self.add_output("failure", val=np.zeros((self.ny - 1, num_failure_criteria)))
38 |
39 | self.declare_partials("failure", "vonmises", val=np.eye(((self.ny - 1) * num_failure_criteria)) / self.sigma)
40 |
41 | def compute(self, inputs, outputs):
42 | outputs["failure"] = inputs["vonmises"] / self.sigma - 1
43 |
--------------------------------------------------------------------------------
/openaerostruct/structures/fuel_loads.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 | from openaerostruct.utils.constants import grav_constant
5 |
6 |
7 | def norm(vec):
8 | return np.sqrt(np.sum(vec**2))
9 |
10 |
11 | class FuelLoads(om.ExplicitComponent):
12 | """
13 | Compute the nodal loads from the distributed fuel within the wing
14 | to be applied to the wing structure.
15 |
16 | Parameters
17 | ----------
18 |
19 | Returns
20 | -------
21 |
22 | """
23 |
24 | def initialize(self):
25 | self.options.declare("surface", types=dict)
26 |
27 | def setup(self):
28 | self.surface = surface = self.options["surface"]
29 | self.ny = surface["mesh"].shape[1]
30 |
31 | self.add_input("fuel_vols", val=np.ones((self.ny - 1)), units="m**3")
32 | self.add_input("nodes", val=np.zeros((self.ny, 3)), units="m")
33 | self.add_input("fuel_mass", val=1.0, units="kg")
34 | self.add_input("load_factor", val=1.0)
35 | self.add_output("fuel_weight_loads", val=np.zeros((self.ny, 6)), units="N")
36 |
37 | self.declare_partials("*", "*", method="cs")
38 |
39 | def compute(self, inputs, outputs):
40 | nodes = inputs["nodes"]
41 |
42 | element_lengths = np.ones(self.ny - 1, dtype=complex)
43 | for i in range(self.ny - 1):
44 | element_lengths[i] = norm(nodes[i + 1] - nodes[i])
45 |
46 | # And we also need the deltas between consecutive nodes
47 | deltas = nodes[1:, :] - nodes[:-1, :]
48 |
49 | # Fuel weight
50 | fuel_weight = (inputs["fuel_mass"] + self.surface["Wf_reserve"]) * grav_constant * inputs["load_factor"]
51 |
52 | if self.surface["symmetry"]:
53 | fuel_weight /= 2.0
54 |
55 | vols = inputs["fuel_vols"]
56 | sum_vols = np.sum(vols)
57 |
58 | # Now we need the fuel weight per segment
59 | # Assume it's divided evenly based on vols
60 | z_weights = vols * fuel_weight / sum_vols
61 |
62 | # Assume weight coincides with the elastic axis
63 | z_forces_for_each = z_weights / 2.0
64 | z_moments_for_each = (
65 | z_weights * element_lengths / 12.0 * (deltas[:, 0] ** 2 + deltas[:, 1] ** 2) ** 0.5 / element_lengths
66 | )
67 |
68 | loads = np.zeros((self.ny, 6), dtype=complex)
69 |
70 | # Loads in z-direction
71 | loads[:-1, 2] = loads[:-1, 2] - z_forces_for_each
72 | loads[1:, 2] = loads[1:, 2] - z_forces_for_each
73 |
74 | # Bending moments for consistency
75 | loads[:-1, 3] = loads[:-1, 3] - z_moments_for_each * deltas[:, 1] / element_lengths
76 | loads[1:, 3] = loads[1:, 3] + z_moments_for_each * deltas[:, 1] / element_lengths
77 |
78 | loads[:-1, 4] = loads[:-1, 4] - z_moments_for_each * deltas[:, 0] / element_lengths
79 | loads[1:, 4] = loads[1:, 4] + z_moments_for_each * deltas[:, 0] / element_lengths
80 |
81 | outputs["fuel_weight_loads"] = loads
82 |
--------------------------------------------------------------------------------
/openaerostruct/structures/fuel_vol.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | def norm(vec):
7 | return np.sqrt(np.sum(vec**2))
8 |
9 |
10 | class WingboxFuelVol(om.ExplicitComponent):
11 | """
12 | Computes the internal volumes of the wingbox segments.
13 |
14 | parameters
15 | ----------
16 | nodes[ny, 3] : numpy array
17 | Coordinates of FEM nodes.
18 | A_int[ny-1] : numpy array
19 | Internal cross-sectional area of each wingbox segment.
20 |
21 | Returns
22 | -------
23 | fuel_vols[ny-1] : numpy array
24 | Internal volume of each wingbox segment.
25 | """
26 |
27 | def initialize(self):
28 | self.options.declare("surface", types=dict)
29 |
30 | def setup(self):
31 | self.surface = surface = self.options["surface"]
32 |
33 | self.ny = surface["mesh"].shape[1]
34 |
35 | self.add_input("nodes", val=np.zeros((self.ny, 3)), units="m")
36 | self.add_input("A_int", val=np.zeros((self.ny - 1)), units="m**2")
37 | self.add_output("fuel_vols", val=np.zeros((self.ny - 1)), units="m**3")
38 |
39 | self.declare_partials("*", "*", method="cs")
40 |
41 | def compute(self, inputs, outputs):
42 | nodes = inputs["nodes"]
43 |
44 | element_lengths = np.zeros(self.ny - 1, dtype=type(nodes[0, 0]))
45 |
46 | for i in range(self.ny - 1):
47 | element_lengths[i] = norm(nodes[i + 1] - nodes[i])
48 |
49 | # Next we multiply the element lengths with the A_int for the internal volumes of the wingobox segments
50 | vols = element_lengths * inputs["A_int"]
51 |
52 | outputs["fuel_vols"] = vols
53 |
--------------------------------------------------------------------------------
/openaerostruct/structures/length.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 | from openaerostruct.utils.vector_algebra import compute_norm
6 |
7 |
8 | class Length(om.ExplicitComponent):
9 | def initialize(self):
10 | self.options.declare("surface", types=dict)
11 |
12 | def setup(self):
13 | surface = self.options["surface"]
14 |
15 | self.ny = ny = surface["mesh"].shape[1]
16 |
17 | self.add_input("nodes", shape=(ny, 3), units="m")
18 | self.add_output("element_lengths", shape=ny - 1, units="m")
19 |
20 | mesh_indices = np.arange(3 * ny).reshape((ny, 3))
21 | length_indices = np.arange(ny - 1)
22 |
23 | rows = np.tile(np.outer(length_indices, np.ones(3, int)).flatten(), 2)
24 | cols = np.concatenate(
25 | [
26 | mesh_indices[:-1, :].flatten(),
27 | mesh_indices[1:, :].flatten(),
28 | ]
29 | )
30 | self.declare_partials("element_lengths", "nodes", rows=rows, cols=cols)
31 | self.set_check_partial_options(wrt="nodes", method="fd", step=1e-6)
32 |
33 | def compute(self, inputs, outputs):
34 | vec = inputs["nodes"][1:, :] - inputs["nodes"][:-1, :]
35 |
36 | outputs["element_lengths"] = np.linalg.norm(vec, axis=-1)
37 |
38 | def compute_partials(self, inputs, partials):
39 | ny = self.ny
40 |
41 | vec = inputs["nodes"][1:, :] - inputs["nodes"][:-1, :]
42 |
43 | derivs = partials["element_lengths", "nodes"].reshape((2, ny - 1, 3))
44 | derivs[0, :, :] = -vec / compute_norm(vec)
45 | derivs[1, :, :] = vec / compute_norm(vec)
46 |
--------------------------------------------------------------------------------
/openaerostruct/structures/local_stiff_permuted.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | row_indices = np.arange(12)
7 | col_indices = np.array(
8 | [
9 | 0,
10 | 6,
11 | 3,
12 | 9,
13 | 2,
14 | 4,
15 | 8,
16 | 10,
17 | 1,
18 | 5,
19 | 7,
20 | 11,
21 | ]
22 | )
23 | mtx = np.zeros((12, 12))
24 | mtx[row_indices, col_indices] = 1.0
25 |
26 |
27 | class LocalStiffPermuted(om.ExplicitComponent):
28 | def initialize(self):
29 | self.options.declare("surface", types=dict)
30 |
31 | def setup(self):
32 | surface = self.options["surface"]
33 |
34 | self.ny = ny = surface["mesh"].shape[1]
35 |
36 | self.add_input("local_stiff", shape=(ny - 1, 12, 12))
37 | self.add_output("local_stiff_permuted", shape=(ny - 1, 12, 12))
38 |
39 | indices = np.arange(144 * (ny - 1)).reshape((ny - 1, 12, 12))
40 | ones = np.ones((12, 12), int)
41 |
42 | # data = np.empty((ny - 1, 12, 12, 12, 12))
43 | # for row1 in range(12):
44 | # for col1 in range(12):
45 | # for row2 in range(12):
46 | # for col2 in range(12):
47 | # data[:, row1, col1, row2, col2] = mtx[row1, row2] * mtx[col2, col1]
48 | data = np.einsum("i,jl,mk->ijklm", np.ones(ny - 1), mtx.T, mtx)
49 |
50 | data = data.flatten()
51 | rows = np.einsum("ijk,lm->ijklm", indices, ones).flatten()
52 | cols = np.einsum("ilm,jk->ijklm", indices, ones).flatten()
53 | self.declare_partials("local_stiff_permuted", "local_stiff", val=data, rows=rows, cols=cols)
54 |
55 | def compute(self, inputs, outputs):
56 | outputs["local_stiff_permuted"] = np.einsum("jl,ilm,mk->ijk", mtx.T, inputs["local_stiff"], mtx)
57 |
--------------------------------------------------------------------------------
/openaerostruct/structures/local_stiff_transformed.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class LocalStiffTransformed(om.ExplicitComponent):
7 | def initialize(self):
8 | self.options.declare("surface", types=dict)
9 |
10 | def setup(self):
11 | surface = self.options["surface"]
12 |
13 | self.ny = ny = surface["mesh"].shape[1]
14 |
15 | self.add_input("transform", shape=(ny - 1, 12, 12))
16 | self.add_input("local_stiff_permuted", shape=(ny - 1, 12, 12))
17 | self.add_output("local_stiff_transformed", shape=(ny - 1, 12, 12))
18 |
19 | indices = np.arange(144 * (ny - 1)).reshape((ny - 1, 12, 12))
20 | ones = np.ones((12, 12), int)
21 |
22 | rows = np.einsum("ijk,lm->ijklm", indices, ones).flatten()
23 | cols = np.einsum("ilm,jk->ijklm", indices, ones).flatten()
24 |
25 | self.declare_partials("local_stiff_transformed", "transform", rows=rows, cols=cols)
26 | self.declare_partials("local_stiff_transformed", "local_stiff_permuted", rows=rows, cols=cols)
27 |
28 | def compute(self, inputs, outputs):
29 | outputs["local_stiff_transformed"] = np.einsum(
30 | "ilj,ilm,imk->ijk", inputs["transform"], inputs["local_stiff_permuted"], inputs["transform"]
31 | )
32 |
33 | def compute_partials(self, inputs, partials):
34 | partials["local_stiff_transformed", "local_stiff_permuted"] = np.einsum(
35 | "ilj,imk->ijklm", inputs["transform"], inputs["transform"]
36 | ).flatten()
37 |
38 | partials["local_stiff_transformed", "transform"] = (
39 | np.einsum("ilj,ilp,kq->ijkpq", inputs["transform"], inputs["local_stiff_permuted"], np.eye(12))
40 | + np.einsum("jq,ipm,imk->ijkpq", np.eye(12), inputs["local_stiff_permuted"], inputs["transform"])
41 | ).flatten()
42 |
--------------------------------------------------------------------------------
/openaerostruct/structures/non_intersecting_thickness.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class NonIntersectingThickness(om.ExplicitComponent):
7 | """
8 | Create a constraint so the thickness of the spar does not intersect
9 | itself in the center of the spar. Basically, the thickness must be less
10 | than or equal to the radius.
11 |
12 | parameters
13 | ----------
14 | thickness[ny-1] : numpy array
15 | Thickness of each element of the FEM spar.
16 | radius[ny-1] : numpy array
17 | Radius of each element of the FEM spar.
18 |
19 | Returns
20 | -------
21 | thickness_intersects[ny-1] : numpy array
22 | If all the values are negative, each element does not intersect itself.
23 | If a value is positive, then the thickness within the hollow spar
24 | intersects itself and presents an impossible design.
25 | Add a constraint as
26 | `OASProblem.add_constraint('thickness_intersects', upper=0.)`
27 | """
28 |
29 | def initialize(self):
30 | self.options.declare("surface", types=dict)
31 |
32 | def setup(self):
33 | self.surface = surface = self.options["surface"]
34 |
35 | self.ny = surface["mesh"].shape[1]
36 |
37 | self.add_input("thickness", val=np.zeros((self.ny - 1)), units="m")
38 | self.add_input("radius", val=np.zeros((self.ny - 1)), units="m")
39 | self.add_output("thickness_intersects", val=np.zeros((self.ny - 1)), units="m")
40 |
41 | arange = np.arange(self.ny - 1)
42 | self.declare_partials("thickness_intersects", "thickness", rows=arange, cols=arange, val=1.0)
43 | self.declare_partials("thickness_intersects", "radius", rows=arange, cols=arange, val=-1.0)
44 |
45 | def compute(self, inputs, outputs):
46 | outputs["thickness_intersects"] = inputs["thickness"] - inputs["radius"]
47 |
--------------------------------------------------------------------------------
/openaerostruct/structures/spar_within_wing.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 | from openaerostruct.structures.utils import radii
5 |
6 |
7 | class SparWithinWing(om.ExplicitComponent):
8 | """
9 | Create a constraint to see if the spar is within the wing.
10 | This is based on the wing's t/c and the spar radius.
11 |
12 | .. warning::
13 | This component has not been extensively tested.
14 | It may require additional coding to work as intended.
15 |
16 | parameters
17 | ----------
18 | mesh[nx, ny, 3] : numpy array
19 | Array defining the nodal points of the lifting surface.
20 | radius[ny-1] : numpy array
21 | Radius of each element of the FEM spar.
22 | t_over_c[ny-1] : numpy array
23 | The streamwise thickness-to-chord ratio of each VLM panel.
24 |
25 | Returns
26 | -------
27 | spar_within_wing[ny-1] : numpy array
28 | If all the values are negative, each element is within the wing,
29 | based on the surface's t_over_c value and the current chord.
30 | Set a constraint with
31 | `OASProblem.add_constraint('spar_within_wing', upper=0.)`
32 | """
33 |
34 | def initialize(self):
35 | self.options.declare("surface", types=dict)
36 |
37 | def setup(self):
38 | self.surface = surface = self.options["surface"]
39 |
40 | self.ny = surface["mesh"].shape[1]
41 | nx = surface["mesh"].shape[0]
42 |
43 | self.add_input("mesh", val=np.zeros((nx, self.ny, 3)), units="m")
44 | self.add_input("radius", val=np.zeros((self.ny - 1)), units="m")
45 | self.add_input("t_over_c", val=np.zeros((self.ny - 1)))
46 | self.add_output("spar_within_wing", val=np.zeros((self.ny - 1)), units="m")
47 |
48 | self.declare_partials("spar_within_wing", "mesh", method="cs")
49 |
50 | arange = np.arange(self.ny - 1)
51 | self.declare_partials("spar_within_wing", "radius", rows=arange, cols=arange, val=1.0)
52 |
53 | def compute(self, inputs, outputs):
54 | mesh = inputs["mesh"]
55 | t_over_c = inputs["t_over_c"]
56 | max_radius = radii(mesh, t_over_c)
57 | outputs["spar_within_wing"] = inputs["radius"] - max_radius
58 |
--------------------------------------------------------------------------------
/openaerostruct/structures/spatial_beam_functionals.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 |
3 | # from openaerostruct.structures.energy import Energy
4 | # from openaerostruct.structures.weight import Weight
5 | # from openaerostruct.structures.spar_within_wing import SparWithinWing
6 | from openaerostruct.structures.vonmises_tube import VonMisesTube
7 | from openaerostruct.structures.vonmises_wingbox import VonMisesWingbox
8 | from openaerostruct.structures.non_intersecting_thickness import NonIntersectingThickness
9 | from openaerostruct.structures.failure_exact import FailureExact
10 | from openaerostruct.structures.failure_ks import FailureKS
11 |
12 |
13 | class SpatialBeamFunctionals(om.Group):
14 | """Group that contains the spatial beam functionals used to evaluate
15 | performance."""
16 |
17 | def initialize(self):
18 | self.options.declare("surface", types=dict)
19 |
20 | def setup(self):
21 | surface = self.options["surface"]
22 |
23 | # Commented out energy for now since we haven't ever used its output
24 | # self.add_subsystem('energy',
25 | # Energy(surface=surface),
26 | # promotes=['*'])
27 |
28 | if surface["fem_model_type"] == "tube":
29 | self.add_subsystem(
30 | "thicknessconstraint",
31 | NonIntersectingThickness(surface=surface),
32 | promotes_inputs=["thickness", "radius"],
33 | promotes_outputs=["thickness_intersects"],
34 | )
35 |
36 | self.add_subsystem(
37 | "vonmises",
38 | VonMisesTube(surface=surface),
39 | promotes_inputs=["radius", "nodes", "disp"],
40 | promotes_outputs=["vonmises"],
41 | )
42 | elif surface["fem_model_type"] == "wingbox":
43 | self.add_subsystem(
44 | "vonmises",
45 | VonMisesWingbox(surface=surface),
46 | promotes_inputs=[
47 | "Qz",
48 | "J",
49 | "A_enc",
50 | "spar_thickness",
51 | "htop",
52 | "hbottom",
53 | "hfront",
54 | "hrear",
55 | "nodes",
56 | "disp",
57 | ],
58 | promotes_outputs=["vonmises"],
59 | )
60 | else:
61 | raise NameError("Please select a valid `fem_model_type` from either `tube` or `wingbox`.")
62 |
63 | # The following component has not been fully tested so we leave it
64 | # commented out for now. Use at your own risk.
65 | # self.add_subsystem('sparconstraint',
66 | # SparWithinWing(surface=surface),
67 | # promotes=['*'])
68 |
69 | if surface["exact_failure_constraint"]:
70 | self.add_subsystem(
71 | "failure", FailureExact(surface=surface), promotes_inputs=["vonmises"], promotes_outputs=["failure"]
72 | )
73 | else:
74 | self.add_subsystem(
75 | "failure", FailureKS(surface=surface), promotes_inputs=["vonmises"], promotes_outputs=["failure"]
76 | )
77 |
--------------------------------------------------------------------------------
/openaerostruct/structures/spatial_beam_setup.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 | from openaerostruct.structures.compute_nodes import ComputeNodes
3 | from openaerostruct.structures.assemble_k_group import AssembleKGroup
4 | from openaerostruct.structures.weight import Weight
5 | from openaerostruct.structures.structural_cg import StructuralCG
6 | from openaerostruct.structures.fuel_vol import WingboxFuelVol
7 |
8 |
9 | class SpatialBeamSetup(om.Group):
10 | """Group that sets up the spatial beam components and assembles the
11 | stiffness matrix."""
12 |
13 | def initialize(self):
14 | self.options.declare("surface", types=dict)
15 |
16 | def setup(self):
17 | surface = self.options["surface"]
18 |
19 | self.add_subsystem("nodes", ComputeNodes(surface=surface), promotes_inputs=["mesh"], promotes_outputs=["nodes"])
20 |
21 | self.add_subsystem(
22 | "assembly",
23 | AssembleKGroup(surface=surface),
24 | promotes_inputs=["A", "Iy", "Iz", "J", "nodes"],
25 | promotes_outputs=["local_stiff_transformed"],
26 | )
27 |
28 | self.add_subsystem(
29 | "structural_mass",
30 | Weight(surface=surface),
31 | promotes_inputs=["A", "nodes"],
32 | promotes_outputs=["structural_mass", "element_mass"],
33 | )
34 |
35 | self.add_subsystem(
36 | "structural_cg",
37 | StructuralCG(surface=surface),
38 | promotes_inputs=["nodes", "structural_mass", "element_mass"],
39 | promotes_outputs=["cg_location"],
40 | )
41 |
42 | if surface["fem_model_type"] == "wingbox":
43 | self.add_subsystem(
44 | "fuel_vol",
45 | WingboxFuelVol(surface=surface),
46 | promotes_inputs=["nodes", "A_int"],
47 | promotes_outputs=["fuel_vols"],
48 | )
49 |
--------------------------------------------------------------------------------
/openaerostruct/structures/spatial_beam_states.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 | from openaerostruct.structures.create_rhs import CreateRHS
3 | from openaerostruct.structures.fem import FEM
4 | from openaerostruct.structures.disp import Disp
5 | from openaerostruct.structures.wing_weight_loads import StructureWeightLoads
6 | from openaerostruct.structures.fuel_loads import FuelLoads
7 | from openaerostruct.structures.total_loads import TotalLoads
8 | from openaerostruct.structures.compute_point_mass_loads import ComputePointMassLoads
9 | from openaerostruct.structures.compute_thrust_loads import ComputeThrustLoads
10 |
11 |
12 | class SpatialBeamStates(om.Group):
13 | """Group that contains the spatial beam states."""
14 |
15 | def initialize(self):
16 | self.options.declare("surface", types=dict)
17 |
18 | def setup(self):
19 | surface = self.options["surface"]
20 |
21 | promotes = []
22 | if surface["struct_weight_relief"]:
23 | self.add_subsystem(
24 | "struct_weight_loads",
25 | StructureWeightLoads(surface=surface),
26 | promotes_inputs=["element_mass", "nodes", "load_factor"],
27 | promotes_outputs=["struct_weight_loads"],
28 | )
29 | promotes.append("struct_weight_loads")
30 |
31 | if surface["distributed_fuel_weight"]:
32 | self.add_subsystem(
33 | "fuel_loads",
34 | FuelLoads(surface=surface),
35 | promotes_inputs=["nodes", "load_factor", "fuel_vols", "fuel_mass"],
36 | promotes_outputs=["fuel_weight_loads"],
37 | )
38 | promotes.append("fuel_weight_loads")
39 |
40 | if "n_point_masses" in surface.keys():
41 | self.add_subsystem(
42 | "point_masses",
43 | ComputePointMassLoads(surface=surface),
44 | promotes_inputs=["point_mass_locations", "point_masses", "nodes", "load_factor"],
45 | promotes_outputs=["loads_from_point_masses"],
46 | )
47 | promotes.append("loads_from_point_masses")
48 |
49 | self.add_subsystem(
50 | "thrust_loads",
51 | ComputeThrustLoads(surface=surface),
52 | promotes_inputs=["point_mass_locations", "engine_thrusts", "nodes"],
53 | promotes_outputs=["loads_from_thrusts"],
54 | )
55 | promotes.append("loads_from_thrusts")
56 |
57 | self.add_subsystem(
58 | "total_loads",
59 | TotalLoads(surface=surface),
60 | promotes_inputs=["loads"] + promotes,
61 | promotes_outputs=["total_loads"],
62 | )
63 |
64 | self.add_subsystem(
65 | "create_rhs", CreateRHS(surface=surface), promotes_inputs=["total_loads"], promotes_outputs=["forces"]
66 | )
67 |
68 | self.add_subsystem("fem", FEM(surface=surface), promotes_inputs=["*"], promotes_outputs=["*"])
69 |
70 | self.add_subsystem("disp", Disp(surface=surface), promotes_inputs=["*"], promotes_outputs=["*"])
71 |
--------------------------------------------------------------------------------
/openaerostruct/structures/total_loads.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | class TotalLoads(om.ExplicitComponent):
7 | """
8 | Add the loads from the aerodynamics, structural weight, and fuel weight.
9 |
10 | Parameters
11 | ----------
12 | loads[ny, 6] : numpy array
13 | Array containing the loads applied on the FEM component,
14 | computed from the sectional forces.
15 | struct_weight_loads[ny, 6] : numpy array
16 | Array containing the loads applied on the FEM component,
17 | computed from the weight of the wing-structure segments.
18 | fuel_weight_loads[ny, 6] : numpy array
19 | Array containing the loads applied on the FEM component,
20 | computed from the weight of the fuel.
21 | loads_from_point_masses[ny, 6] : numpy array
22 | The cumulative loads from all point masses.
23 |
24 | Returns
25 | -------
26 | total_loads[ny, 6] : numpy array
27 | Flattened array containing the total loads applied on the FEM.
28 | """
29 |
30 | def initialize(self):
31 | self.options.declare("surface", types=dict)
32 |
33 | def setup(self):
34 | self.surface = surface = self.options["surface"]
35 | self.ny = surface["mesh"].shape[1]
36 |
37 | self.add_input("loads", val=np.ones((self.ny, 6)), units="N")
38 | if surface["struct_weight_relief"]:
39 | self.add_input("struct_weight_loads", val=np.zeros((self.ny, 6)), units="N")
40 | if surface["distributed_fuel_weight"]:
41 | self.add_input("fuel_weight_loads", val=np.zeros((self.ny, 6)), units="N")
42 | if "n_point_masses" in surface.keys():
43 | self.add_input("loads_from_point_masses", val=np.zeros((self.ny, 6)), units="N")
44 | self.add_input("loads_from_thrusts", val=np.zeros((self.ny, 6)), units="N")
45 |
46 | self.add_output("total_loads", val=np.ones((self.ny, 6)), units="N")
47 |
48 | arange = np.arange(self.ny * 6)
49 |
50 | self.declare_partials("total_loads", "loads", rows=arange, cols=arange, val=1.0)
51 |
52 | if self.surface["struct_weight_relief"]:
53 | self.declare_partials("total_loads", "struct_weight_loads", rows=arange, cols=arange, val=1.0)
54 |
55 | if self.surface["distributed_fuel_weight"]:
56 | self.declare_partials("total_loads", "fuel_weight_loads", rows=arange, cols=arange, val=1.0)
57 |
58 | if "n_point_masses" in surface.keys():
59 | self.declare_partials("total_loads", "loads_from_point_masses", rows=arange, cols=arange, val=1.0)
60 | self.declare_partials("total_loads", "loads_from_thrusts", rows=arange, cols=arange, val=1.0)
61 |
62 | def compute(self, inputs, outputs):
63 | outputs["total_loads"] = inputs["loads"]
64 |
65 | if self.surface["struct_weight_relief"]:
66 | outputs["total_loads"] += inputs["struct_weight_loads"]
67 |
68 | if self.surface["distributed_fuel_weight"]:
69 | outputs["total_loads"] += inputs["fuel_weight_loads"]
70 |
71 | if "n_point_masses" in self.surface.keys():
72 | outputs["total_loads"] += inputs["loads_from_point_masses"]
73 | outputs["total_loads"] += inputs["loads_from_thrusts"]
74 |
--------------------------------------------------------------------------------
/openaerostruct/structures/tube_group.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 | from openaerostruct.structures.section_properties_tube import SectionPropertiesTube
3 | from openaerostruct.geometry.radius_comp import RadiusComp
4 | from openaerostruct.utils.interpolation import get_normalized_span_coords
5 |
6 |
7 | class TubeGroup(om.Group):
8 | """Group that contains everything needed for a structural-only problem."""
9 |
10 | def initialize(self):
11 | self.options.declare("surface", types=dict)
12 | self.options.declare("connect_geom_DVs", default=True)
13 | # The option "connect_geom_DVs" is no longer necessary, but we still keep it to be backward compatible.
14 |
15 | def setup(self):
16 | surface = self.options["surface"]
17 |
18 | if "thickness_cp" in surface.keys():
19 | n_cp = len(surface["thickness_cp"])
20 | # Add bspline components for active bspline geometric variables.
21 | x_interp = get_normalized_span_coords(surface, mid_panel=True)
22 | comp = self.add_subsystem(
23 | "thickness_bsp",
24 | om.SplineComp(
25 | method="bsplines",
26 | x_interp_val=x_interp,
27 | num_cp=n_cp,
28 | interp_options={"order": min(n_cp, 4), "x_cp_start": 0, "x_cp_end": 1},
29 | ),
30 | promotes_inputs=["thickness_cp"],
31 | promotes_outputs=["thickness"],
32 | )
33 | comp.add_spline(y_cp_name="thickness_cp", y_interp_name="thickness", y_units="m")
34 | self.set_input_defaults("thickness_cp", val=surface["thickness_cp"], units="m")
35 |
36 | if "radius_cp" in surface.keys():
37 | n_cp = len(surface["radius_cp"])
38 | # Add bspline components for active bspline geometric variables.
39 | x_interp = get_normalized_span_coords(surface, mid_panel=True)
40 | comp = self.add_subsystem(
41 | "radius_bsp",
42 | om.SplineComp(
43 | method="bsplines",
44 | x_interp_val=x_interp,
45 | num_cp=n_cp,
46 | interp_options={"order": min(n_cp, 4), "x_cp_start": 0, "x_cp_end": 1},
47 | ),
48 | promotes_inputs=["radius_cp"],
49 | promotes_outputs=["radius"],
50 | )
51 | comp.add_spline(y_cp_name="radius_cp", y_interp_name="radius", y_units="m")
52 | self.set_input_defaults("radius_cp", val=surface["radius_cp"], units="m")
53 |
54 | else:
55 | self.add_subsystem(
56 | "radius_comp",
57 | RadiusComp(surface=surface),
58 | promotes_inputs=["mesh", "t_over_c"],
59 | promotes_outputs=["radius"],
60 | )
61 |
62 | self.add_subsystem(
63 | "tube",
64 | SectionPropertiesTube(surface=surface),
65 | promotes_inputs=["thickness", "radius"],
66 | promotes_outputs=["A", "Iy", "Iz", "J"],
67 | )
68 |
--------------------------------------------------------------------------------
/openaerostruct/structures/utils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 |
4 | def norm(vec, axis=None):
5 | return np.sqrt(np.sum(vec**2, axis=axis))
6 |
7 |
8 | def unit(vec):
9 | return vec / norm(vec)
10 |
11 |
12 | def norm_d(vec):
13 | vec_d = vec / norm(vec)
14 | return vec_d
15 |
16 |
17 | def unit_d(vec):
18 | n_d = norm_d(vec)
19 | normvec = norm(vec)
20 | vec_d = np.outer((-vec / (normvec * normvec)), n_d) + 1 / normvec * np.eye(len(vec))
21 |
22 | return vec_d
23 |
24 |
25 | # This is a limited cross product definition for 3 vectors
26 | def cross_d(a, b):
27 | if not isinstance(a, np.ndarray):
28 | a = np.array(a)
29 | if a.shape != (3,):
30 | raise ValueError("a must be a (3,) nd array")
31 | if not isinstance(b, np.ndarray):
32 | b = np.array(b)
33 | if b.shape != (3,):
34 | raise ValueError("b must be a (3,) nd array")
35 |
36 | dcda = np.zeros([3, 3])
37 | dcdb = np.zeros([3, 3])
38 |
39 | dcda[0, 1] = b[2]
40 | dcda[0, 2] = -b[1]
41 | dcda[1, 0] = -b[2]
42 | dcda[1, 2] = b[0]
43 | dcda[2, 0] = b[1]
44 | dcda[2, 1] = -b[0]
45 |
46 | dcdb[0, 1] = -a[2]
47 | dcdb[0, 2] = a[1]
48 | dcdb[1, 0] = a[2]
49 | dcdb[1, 2] = -a[0]
50 | dcdb[2, 0] = -a[1]
51 | dcdb[2, 1] = a[0]
52 |
53 | return dcda, dcdb
54 |
55 |
56 | def radii(mesh, t_c=0.15):
57 | """
58 | Obtain the radii of the FEM element based on local chord.
59 | """
60 | vectors = mesh[-1, :, :] - mesh[0, :, :]
61 | chords = np.sqrt(np.sum(vectors**2, axis=1))
62 | mean_chords = 0.5 * chords[:-1] + 0.5 * chords[1:]
63 | return t_c * mean_chords * 0.5
64 |
--------------------------------------------------------------------------------
/openaerostruct/structures/wingbox_fuel_vol_delta.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import openmdao.api as om
4 |
5 |
6 | def norm(vec):
7 | return np.sqrt(np.sum(vec**2))
8 |
9 |
10 | class WingboxFuelVolDelta(om.ExplicitComponent):
11 | """
12 | Create a constraint to ensure the wingbox has enough internal volume to
13 | store the required fuel.
14 |
15 | Parameters
16 | ----------
17 | fuelburn : float
18 | Fuel weight
19 | fuel_vols[ny-1] : numpy array
20 | The magnitude of each individual panel's fuel-carrying volumes.
21 |
22 | Returns
23 | -------
24 | fuel_vol_delta : float
25 | If the value is negative, then there isn't enough volume for the fuel.
26 | """
27 |
28 | def initialize(self):
29 | self.options.declare("surface", types=dict)
30 |
31 | def setup(self):
32 | self.surface = surface = self.options["surface"]
33 |
34 | self.ny = surface["mesh"].shape[1]
35 |
36 | self.add_input("fuelburn", val=0.0, units="kg")
37 | self.add_input("fuel_vols", val=np.zeros((self.ny - 1)), units="m**3")
38 | self.add_output("fuel_vol_delta", val=0.0, units="m**3")
39 |
40 | self.declare_partials("*", "*", method="cs")
41 |
42 | def compute(self, inputs, outputs):
43 | fuel_weight = inputs["fuelburn"]
44 | reserves = self.surface["Wf_reserve"]
45 | fuel_density = self.surface["fuel_density"]
46 | vols = inputs["fuel_vols"]
47 |
48 | if self.surface["symmetry"] is True:
49 | fuel_weight /= 2.0
50 | reserves /= 2.0
51 |
52 | sum_vols = np.sum(vols)
53 |
54 | # This is used for the fuel-volume constraint. It should be positive for fuel to fit.
55 | outputs["fuel_vol_delta"] = sum_vols - (fuel_weight + reserves) / fuel_density
56 |
--------------------------------------------------------------------------------
/openaerostruct/transfer/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/transfer/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/transfer/displacement_transfer_group.py:
--------------------------------------------------------------------------------
1 | import openmdao.api as om
2 | from openaerostruct.transfer.displacement_transfer import DisplacementTransfer
3 | from openaerostruct.transfer.compute_transformation_matrix import ComputeTransformationMatrix
4 |
5 |
6 | class DisplacementTransferGroup(om.Group):
7 | """
8 | These components take the displacements and rotations obtained by
9 | solving the FEM problem and applies them to the aerodynamic mesh
10 | to produce a deformed aerodynamic mesh.
11 | """
12 |
13 | def initialize(self):
14 | self.options.declare("surface", types=dict)
15 |
16 | def setup(self):
17 | surface = self.options["surface"]
18 |
19 | self.add_subsystem(
20 | "compute_transformation_matrix", ComputeTransformationMatrix(surface=surface), promotes=["*"]
21 | )
22 |
23 | self.add_subsystem("displacement_transfer", DisplacementTransfer(surface=surface), promotes=["*"])
24 |
--------------------------------------------------------------------------------
/openaerostruct/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdolab/OpenAeroStruct/7ae28a82779ad0e3bd027e9bd0afb5ce26f9a672/openaerostruct/utils/__init__.py
--------------------------------------------------------------------------------
/openaerostruct/utils/check_surface_dict.py:
--------------------------------------------------------------------------------
1 | import warnings
2 |
3 |
4 | def check_surface_dict_keys(surface):
5 | """
6 | Key valication function for the OAS surface dict.
7 | Shows a warning if a user provided a key that is (likely) not implemented in OAS.
8 |
9 | Parameters
10 | ----------
11 | surface : dict
12 | User-defined surface dict
13 | """
14 |
15 | # NOTE: make sure this is consistent to the documentation's surface dict page
16 | keys_implemented = [
17 | # wing definition
18 | "name",
19 | "symmetry",
20 | "S_ref_type",
21 | "mesh",
22 | "span",
23 | "taper",
24 | "sweep",
25 | "dihedral",
26 | "twist_cp",
27 | "chord_cp",
28 | "xshear_cp",
29 | "yshear_cp",
30 | "zshear_cp",
31 | "ref_axis_pos",
32 | # aerodynamics
33 | "CL0",
34 | "CD0",
35 | "with_viscous",
36 | "with_wave",
37 | "groundplane",
38 | "k_lam",
39 | "t_over_c_cp",
40 | "c_max_t",
41 | # structure
42 | "fem_model_type",
43 | "E",
44 | "G",
45 | "yield",
46 | "mrho",
47 | "fem_origin",
48 | "wing_weight_ratio",
49 | "exact_failure_constraint",
50 | "struct_weight_relief",
51 | "distributed_fuel_weight",
52 | "fuel_density",
53 | "Wf_reserve",
54 | "n_point_masses",
55 | # tube structure
56 | "thickness_cp",
57 | "radius_cp",
58 | # wingbox structure
59 | "spar_thickness_cp",
60 | "skin_thickness_cp",
61 | "original_wingbox_airfoil_t_over_c",
62 | "strength_factor_for_upper_skin",
63 | "data_x_upper",
64 | "data_y_upper",
65 | "data_x_lower",
66 | "data_y_lower",
67 | # FFD
68 | "mx",
69 | "my",
70 | # Multisection
71 | "is_multi_section",
72 | "num_sections",
73 | "sec_name",
74 | "meshes",
75 | "root_chord",
76 | "span",
77 | "ny",
78 | "nx",
79 | "bpanels",
80 | "cpanels",
81 | "root_section",
82 | ]
83 |
84 | for key in surface.keys():
85 | if key not in keys_implemented:
86 | warnings.warn(
87 | "Key `{}` in surface dict is (likely) not supported in OAS and will be ignored".format(key),
88 | category=RuntimeWarning,
89 | stacklevel=2,
90 | )
91 |
--------------------------------------------------------------------------------
/openaerostruct/utils/constants.py:
--------------------------------------------------------------------------------
1 | grav_constant = 9.80665
2 |
--------------------------------------------------------------------------------
/openaerostruct/utils/interpolation.py:
--------------------------------------------------------------------------------
1 | def get_normalized_span_coords(surface, mid_panel=False):
2 | """Get the normalised coordinates used for interpolating values along the wingspan
3 |
4 | These normalized coordinates range from 0 at the tip of the wing to 1 at the root.
5 |
6 | Parameters
7 | ----------
8 | surface : OpenAeroStruct surface dictionary
9 | Surface to generate coordinates for
10 | mid_panel : bool, optional
11 | Whether the normalized coordinate should be of the panel midpoints rather than the mesh nodes, by default False
12 |
13 | Returns
14 | -------
15 | np.array
16 | Normalized coordinate values
17 | """
18 | spanwise_coord = surface["mesh"][0, :, 1]
19 | span_range = spanwise_coord[-1] - spanwise_coord[0]
20 | span_offset = spanwise_coord[0]
21 | if mid_panel:
22 | x_real = (spanwise_coord[:-1] + spanwise_coord[1:]) / 2
23 | else:
24 | x_real = spanwise_coord
25 | x_norm = (x_real - span_offset) / span_range
26 | return x_norm
27 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | import re
4 | import os
5 |
6 | __version__ = re.findall(
7 | r"""__version__ = ["']+([0-9\.]*)["']+""",
8 | open("openaerostruct/__init__.py").read(),
9 | )[0]
10 |
11 | optional_dependencies = {
12 | "docs": ["sphinx_mdolab_theme"],
13 | "test": ["testflo"],
14 | "ffd": ["pygeo>=1.6.0"],
15 | "mphys": ["mphys>=2.0.0", "pygeo>=1.15.0"],
16 | }
17 |
18 | # Add an optional dependency that concatenates all others
19 | optional_dependencies["all"] = sorted(
20 | [dependency for dependencies in optional_dependencies.values() for dependency in dependencies]
21 | )
22 |
23 | this_directory = os.path.abspath(os.path.dirname(__file__))
24 | with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f:
25 | long_description = f.read()
26 |
27 | setup(
28 | name="openaerostruct",
29 | version=__version__,
30 | description="OpenAeroStruct",
31 | long_description=long_description,
32 | long_description_content_type="text/markdown",
33 | url="https://github.com/mdolab/OpenAeroStruct",
34 | keywords="",
35 | author="",
36 | author_email="",
37 | license="BSD-3",
38 | packages=[
39 | "openaerostruct",
40 | "openaerostruct/docs",
41 | "openaerostruct/docs/_utils",
42 | "openaerostruct/geometry",
43 | "openaerostruct/structures",
44 | "openaerostruct/aerodynamics",
45 | "openaerostruct/transfer",
46 | "openaerostruct/functionals",
47 | "openaerostruct/integration",
48 | "openaerostruct/meshing",
49 | "openaerostruct/common",
50 | "openaerostruct/utils",
51 | "openaerostruct/mphys",
52 | ],
53 | # Test files
54 | package_data={"openaerostruct": ["tests/*.py", "*/tests/*.py", "*/*/tests/*.py"]},
55 | install_requires=[
56 | # Remember to update the oldest versions in docs/installation.rst
57 | "openmdao>=3.35",
58 | "numpy>=1.21",
59 | "scipy>=1.7",
60 | "matplotlib",
61 | ],
62 | extras_require=optional_dependencies,
63 | zip_safe=False,
64 | # ext_modules=ext,
65 | entry_points="""
66 | [console_scripts]
67 | plot_wing=openaerostruct.utils.plot_wing:disp_plot
68 | plot_wingbox=openaerostruct.utils.plot_wingbox:disp_plot
69 | """,
70 | )
71 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_2d_lift.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.lift_coeff_2D import LiftCoeff2D
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = LiftCoeff2D(surface=surfaces[0])
12 |
13 | run_test(self, comp, method="cs", complex_flag=True)
14 |
15 | def test2(self):
16 | surfaces = get_default_surfaces()
17 |
18 | comp = LiftCoeff2D(surface=surfaces[1])
19 |
20 | run_test(self, comp, method="cs", complex_flag=True)
21 |
22 |
23 | if __name__ == "__main__":
24 | unittest.main()
25 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_coeffs.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.coeffs import Coeffs
4 | from openaerostruct.utils.testing import run_test
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | comp = Coeffs()
10 |
11 | run_test(self, comp)
12 |
13 |
14 | if __name__ == "__main__":
15 | unittest.main()
16 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_collocation_points.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.collocation_points import CollocationPoints
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = CollocationPoints(surfaces=surfaces)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_convert_velocity.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import numpy as np
4 |
5 | import openmdao.api as om
6 | from openmdao.utils.assert_utils import assert_check_partials
7 |
8 | from openaerostruct.aerodynamics.convert_velocity import ConvertVelocity
9 | from openaerostruct.utils.testing import run_test, get_default_surfaces
10 |
11 |
12 | class Test(unittest.TestCase):
13 | def test(self):
14 | surfaces = get_default_surfaces()
15 |
16 | comp = ConvertVelocity(surfaces=surfaces)
17 |
18 | run_test(self, comp)
19 |
20 | def test_rotation_option_derivatives(self):
21 | surfaces = get_default_surfaces()
22 |
23 | comp = ConvertVelocity(surfaces=surfaces, rotational=True)
24 |
25 | prob = om.Problem()
26 | prob.model.add_subsystem("comp", comp)
27 | prob.setup(force_alloc_complex=True)
28 |
29 | rng = np.random.default_rng(0)
30 | prob["comp.rotational_velocities"] = rng.random(prob["comp.rotational_velocities"].shape)
31 | prob["comp.beta"] = 15.0
32 | prob.run_model()
33 |
34 | check = prob.check_partials(compact_print=True, method="cs", step=1e-40)
35 |
36 | assert_check_partials(check)
37 |
38 |
39 | if __name__ == "__main__":
40 | unittest.main()
41 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_eval_velocities.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.eval_velocities import EvalVelocities
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = EvalVelocities(surfaces=surfaces, eval_name="TestEval", num_eval_points=11)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_functionals.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.functionals import VLMFunctionals
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = VLMFunctionals(surface=surfaces[0])
12 |
13 | run_test(self, comp, complex_flag=True, step=1e-8)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_get_vectors.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.get_vectors import GetVectors
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces, get_ground_effect_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = GetVectors(surfaces=surfaces, num_eval_points=10, eval_name="test_name")
12 |
13 | run_test(self, comp)
14 |
15 | def test_groundplane(self):
16 | surfaces = get_ground_effect_surfaces()
17 |
18 | comp = GetVectors(surfaces=surfaces, num_eval_points=10, eval_name="test_name")
19 |
20 | run_test(self, comp)
21 |
22 |
23 | if __name__ == "__main__":
24 | unittest.main()
25 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_horseshoe_circulations.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.horseshoe_circulations import HorseshoeCirculations
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = HorseshoeCirculations(surfaces=surfaces)
12 | run_test(self, comp)
13 |
14 |
15 | if __name__ == "__main__":
16 | unittest.main()
17 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_lift_drag.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import numpy as np
4 |
5 | import openmdao.api as om
6 | from openmdao.utils.assert_utils import assert_check_partials
7 |
8 | from openaerostruct.aerodynamics.lift_drag import LiftDrag
9 | from openaerostruct.utils.testing import run_test, get_default_surfaces
10 |
11 |
12 | class Test(unittest.TestCase):
13 | def test(self):
14 | surfaces = get_default_surfaces()
15 |
16 | comp = LiftDrag(surface=surfaces[0])
17 |
18 | run_test(self, comp)
19 |
20 | def test_derivs_with_sideslip(self):
21 | surfaces = get_default_surfaces()
22 |
23 | # Use Tail since it is not symmetric.
24 | comp = LiftDrag(surface=surfaces[1])
25 |
26 | prob = om.Problem()
27 | prob.model.add_subsystem("comp", comp)
28 | prob.setup(force_alloc_complex=True)
29 |
30 | prob["comp.alpha"] = 3.0
31 | prob["comp.beta"] = 15.0
32 | rng = np.random.default_rng(0)
33 | prob["comp.sec_forces"] = 10.0 * rng.random(prob["comp.sec_forces"].shape)
34 |
35 | prob.run_model()
36 |
37 | check = prob.check_partials(compact_print=True, method="cs", step=1e-40)
38 |
39 | assert_check_partials(check)
40 |
41 |
42 | if __name__ == "__main__":
43 | unittest.main()
44 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_mesh_point_forces.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import numpy as np
4 |
5 | import openmdao.api as om
6 | from openmdao.utils.assert_utils import assert_check_partials
7 |
8 | from openaerostruct.aerodynamics.mesh_point_forces import MeshPointForces
9 | from openaerostruct.meshing.mesh_generator import generate_mesh
10 | from openaerostruct.utils.testing import run_test, get_default_surfaces
11 |
12 |
13 | class Test(unittest.TestCase):
14 | def test(self):
15 | surfaces = get_default_surfaces()
16 |
17 | comp = MeshPointForces(surfaces=surfaces)
18 |
19 | run_test(self, comp)
20 |
21 | def test_derivatives(self):
22 | # For this test, we bump up the number of x and y points so that we are sure the
23 | # derivatives are correct.
24 | mesh_dict = {"num_y": 7, "num_x": 5, "wing_type": "CRM", "symmetry": True, "num_twist_cp": 5}
25 |
26 | # Generate the aerodynamic mesh based on the previous dictionary
27 | mesh, twist_cp = generate_mesh(mesh_dict)
28 |
29 | rng = np.random.default_rng(0)
30 | mesh[:, :, 2] = rng.random(mesh[:, :, 2].shape)
31 |
32 | # Create a dictionary with info and options about the aerodynamic
33 | # lifting surface
34 | surface = {
35 | # Wing definition
36 | "name": "wing", # name of the surface
37 | "twist_cp": twist_cp,
38 | "mesh": mesh,
39 | }
40 |
41 | # surfaces = get_default_surfaces()
42 | surfaces = [surface]
43 |
44 | prob = om.Problem()
45 | group = prob.model
46 |
47 | comp = MeshPointForces(surfaces=surfaces)
48 | group.add_subsystem("comp", comp)
49 |
50 | prob.setup()
51 |
52 | prob["comp.wing_sec_forces"] = rng.random(prob["comp.wing_sec_forces"].shape)
53 |
54 | prob.run_model()
55 |
56 | check = prob.check_partials(compact_print=True)
57 | assert_check_partials(check, atol=3e-5, rtol=1e-5)
58 |
59 |
60 | if __name__ == "__main__":
61 | unittest.main()
62 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_mtx_rhs.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.mtx_rhs import VLMMtxRHSComp
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = VLMMtxRHSComp(surfaces=surfaces)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_panel_forces.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.panel_forces import PanelForces
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = PanelForces(surfaces=surfaces)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_panel_forces_surf.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.panel_forces_surf import PanelForcesSurf
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = PanelForcesSurf(surfaces=surfaces)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_rotational_velocity.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import numpy as np
4 |
5 | import openmdao.api as om
6 | from openmdao.utils.assert_utils import assert_check_partials
7 |
8 | from openaerostruct.aerodynamics.rotational_velocity import RotationalVelocity
9 | from openaerostruct.utils.testing import run_test, get_default_surfaces
10 |
11 |
12 | class Test(unittest.TestCase):
13 | def test(self):
14 | surfaces = get_default_surfaces()
15 |
16 | comp = RotationalVelocity(surfaces=surfaces)
17 |
18 | run_test(self, comp)
19 |
20 | def test_rotation_option_derivatives(self):
21 | surfaces = get_default_surfaces()
22 |
23 | comp = RotationalVelocity(surfaces=surfaces)
24 |
25 | prob = om.Problem()
26 | prob.model.add_subsystem("comp", comp)
27 | prob.setup(force_alloc_complex=True)
28 |
29 | prob["comp.omega"] = np.array([0.3, 0.4, -0.1])
30 | prob["comp.cg"] = np.array([0.1, 0.6, 0.4])
31 | rng = np.random.default_rng(0)
32 | prob["comp.coll_pts"] = rng.random(prob["comp.coll_pts"].shape)
33 | prob.run_model()
34 |
35 | check = prob.check_partials(compact_print=True, method="cs", step=1e-40)
36 |
37 | assert_check_partials(check)
38 |
39 |
40 | if __name__ == "__main__":
41 | unittest.main()
42 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_solve_matrix.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 |
4 | import openmdao.api as om
5 |
6 | from openaerostruct.aerodynamics.solve_matrix import SolveMatrix
7 | from openaerostruct.utils.testing import run_test, get_default_surfaces
8 |
9 |
10 | class Test(unittest.TestCase):
11 | def test(self):
12 | surfaces = get_default_surfaces()
13 |
14 | group = om.Group()
15 | comp = SolveMatrix(surfaces=surfaces)
16 |
17 | indep_var_comp = om.IndepVarComp()
18 |
19 | system_size = 0
20 | for surface in surfaces:
21 | nx = surface["mesh"].shape[0]
22 | ny = surface["mesh"].shape[1]
23 | system_size += (nx - 1) * (ny - 1)
24 |
25 | indep_var_comp.add_output("rhs", val=np.ones((system_size)), units="m/s")
26 | indep_var_comp.add_output("mtx", val=np.identity((system_size)), units="1/m")
27 |
28 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
29 | group.add_subsystem("solve_matrix", comp, promotes=["*"])
30 |
31 | run_test(self, group)
32 |
33 |
34 | if __name__ == "__main__":
35 | unittest.main()
36 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_total_drag.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.total_drag import TotalDrag
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = TotalDrag(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_total_lift.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.total_lift import TotalLift
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = TotalLift(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_viscous_drag.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.viscous_drag import ViscousDrag
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 | import openmdao.api as om
6 | import numpy as np
7 |
8 |
9 | class Test(unittest.TestCase):
10 | def test(self):
11 | surface = get_default_surfaces()[0]
12 | surface["t_over_c_cp"] = np.array([0.1, 0.15, 0.2])
13 |
14 | ny = surface["mesh"].shape[1]
15 | n_cp = len(surface["t_over_c_cp"])
16 |
17 | group = om.Group()
18 |
19 | indep_var_comp = om.IndepVarComp()
20 | indep_var_comp.add_output("t_over_c_cp", val=surface["t_over_c_cp"])
21 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
22 |
23 | x_interp = np.linspace(0.0, 1.0, int(ny - 1))
24 | comp = group.add_subsystem(
25 | "t_over_c_bsp",
26 | om.SplineComp(
27 | method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)}
28 | ),
29 | promotes_inputs=["t_over_c_cp"],
30 | promotes_outputs=["t_over_c"],
31 | )
32 | comp.add_spline(y_cp_name="t_over_c_cp", y_interp_name="t_over_c", y_cp_val=np.zeros(n_cp))
33 |
34 | comp = ViscousDrag(surface=surface, with_viscous=True)
35 | group.add_subsystem("viscousdrag", comp, promotes=["*"])
36 |
37 | run_test(self, group, complex_flag=True)
38 |
39 | def test_2(self):
40 | surface = get_default_surfaces()[0]
41 | surface["k_lam"] = 0.5
42 |
43 | ny = surface["mesh"].shape[1]
44 | n_cp = len(surface["t_over_c_cp"])
45 |
46 | group = om.Group()
47 |
48 | indep_var_comp = om.IndepVarComp()
49 | indep_var_comp.add_output("t_over_c_cp", val=surface["t_over_c_cp"])
50 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
51 |
52 | x_interp = np.linspace(0.0, 1.0, int(ny - 1))
53 | comp = group.add_subsystem(
54 | "t_over_c_bsp",
55 | om.SplineComp(
56 | method="bsplines", x_interp_val=x_interp, num_cp=n_cp, interp_options={"order": min(n_cp, 4)}
57 | ),
58 | promotes_inputs=["t_over_c_cp"],
59 | promotes_outputs=["t_over_c"],
60 | )
61 | comp.add_spline(y_cp_name="t_over_c_cp", y_interp_name="t_over_c")
62 |
63 | comp = ViscousDrag(surface=surface, with_viscous=True)
64 | group.add_subsystem("viscousdrag", comp, promotes=["*"])
65 |
66 | run_test(self, group, complex_flag=True)
67 |
68 |
69 | if __name__ == "__main__":
70 | unittest.main()
71 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_vortex_mesh.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.aerodynamics.vortex_mesh import VortexMesh
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces, get_ground_effect_surfaces
5 |
6 | import numpy as np
7 |
8 | np.set_printoptions(linewidth=200)
9 |
10 |
11 | class Test(unittest.TestCase):
12 | def test(self):
13 | surfaces = get_default_surfaces()
14 |
15 | comp = VortexMesh(surfaces=surfaces)
16 |
17 | run_test(self, comp)
18 |
19 | def test_groundplane(self):
20 | surfaces = get_ground_effect_surfaces()
21 |
22 | comp = VortexMesh(surfaces=surfaces)
23 |
24 | run_test(self, comp, atol=1e6)
25 |
26 | def test_right_wing(self):
27 | surfaces = get_default_surfaces()
28 |
29 | # flip each surface to lie on right
30 | for surface in surfaces:
31 | surface["mesh"] = surface["mesh"][:, ::-1, :]
32 | surface["mesh"][:, :, 1] *= -1.0
33 |
34 | comp = VortexMesh(surfaces=surfaces)
35 |
36 | run_test(self, comp, atol=1e6)
37 |
38 |
39 | if __name__ == "__main__":
40 | unittest.main()
41 |
--------------------------------------------------------------------------------
/tests/aerodynamics_tests/test_wave_drag.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 | from openaerostruct.aerodynamics.wave_drag import WaveDrag
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 | import openmdao.api as om
6 |
7 |
8 | class Test(unittest.TestCase):
9 | def test(self):
10 | surface = get_default_surfaces()[0]
11 | surface["with_wave"] = True
12 | surface["t_over_c_cp"] = np.array([0.15, 0.21, 0.03, 0.05])
13 |
14 | ny = surface["mesh"].shape[1]
15 | n_cp = len(surface["t_over_c_cp"])
16 |
17 | group = om.Group()
18 |
19 | indep_var_comp = om.IndepVarComp()
20 | indep_var_comp.add_output("t_over_c_cp", val=surface["t_over_c_cp"])
21 | indep_var_comp.add_output("Mach_number", val=0.95)
22 | indep_var_comp.add_output("CL", val=0.7)
23 | indep_var_comp.add_output("lengths_spanwise", val=np.array([12.14757848, 11.91832712, 11.43730892]), units="m")
24 | indep_var_comp.add_output("widths", val=np.array([10.01555924, 9.80832351, 9.79003729]), units="m")
25 | indep_var_comp.add_output("chords", val=np.array([2.72835132, 5.12528179, 7.88916016, 13.6189974]), units="m")
26 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
27 |
28 | x_interp = np.linspace(0.0, 1.0, int(ny - 1))
29 | comp = group.add_subsystem(
30 | "t_over_c_bsp",
31 | om.SplineComp(
32 | method="bsplines",
33 | x_interp_val=x_interp,
34 | num_cp=n_cp,
35 | interp_options={"order": min(n_cp, 4)},
36 | ),
37 | promotes_inputs=["t_over_c_cp"],
38 | promotes_outputs=["t_over_c"],
39 | )
40 | comp.add_spline(y_cp_name="t_over_c_cp", y_interp_name="t_over_c")
41 |
42 | comp = WaveDrag(surface=surface)
43 | group.add_subsystem("wavedrag", comp, promotes=["*"])
44 |
45 | run_test(self, group, complex_flag=True)
46 |
47 |
48 | if __name__ == "__main__":
49 | unittest.main()
50 |
--------------------------------------------------------------------------------
/tests/common_tests/test_atmos_comp.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.common.atmos_comp import AtmosComp
4 | from openaerostruct.utils.testing import run_test
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | comp = AtmosComp()
10 |
11 | run_test(self, comp, method="fd", atol=1e20, rtol=1e-4)
12 |
13 |
14 | if __name__ == "__main__":
15 | unittest.main()
16 |
--------------------------------------------------------------------------------
/tests/common_tests/test_reynolds_comp.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 | import openmdao.api as om
4 | from openmdao.utils.assert_utils import assert_check_partials
5 | from openaerostruct.common.reynolds_comp import ReynoldsComp
6 |
7 |
8 | class Test(unittest.TestCase):
9 | def test_reynolds_derivs(self):
10 | comp = ReynoldsComp()
11 |
12 | prob = om.Problem()
13 | prob.model.add_subsystem("comp", comp, promotes=["*"])
14 | prob.setup(force_alloc_complex=True)
15 |
16 | rng = np.random.default_rng(0)
17 | prob["rho"] = rng.random()
18 | prob["mu"] = rng.random()
19 | prob["v"] = rng.random()
20 | prob.run_model()
21 |
22 | check = prob.check_partials(compact_print=True, method="cs", step=1e-40)
23 |
24 | assert_check_partials(check)
25 |
26 |
27 | if __name__ == "__main__":
28 | unittest.main()
29 |
--------------------------------------------------------------------------------
/tests/functionals_tests/test_breguet_range.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.functionals.breguet_range import BreguetRange
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = BreguetRange(surfaces=surfaces)
12 |
13 | run_test(self, comp, complex_flag=True)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/functionals_tests/test_center_of_gravity.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.functionals.center_of_gravity import CenterOfGravity
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = CenterOfGravity(surfaces=surfaces)
12 |
13 | run_test(self, comp, compact_print=True)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/functionals_tests/test_equilibrium.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.functionals.equilibrium import Equilibrium
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = Equilibrium(surfaces=surfaces)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/functionals_tests/test_moment_coefficient.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 |
4 | import openmdao.api as om
5 |
6 | from openaerostruct.functionals.moment_coefficient import MomentCoefficient
7 | from openaerostruct.utils.testing import run_test, get_default_surfaces
8 |
9 |
10 | class Test(unittest.TestCase):
11 | def test(self):
12 | wing_dict = {"name": "wing", "mesh": np.zeros((2, 7)), "symmetry": True}
13 | tail_dict = {"name": "tail", "mesh": np.zeros((3, 5)), "symmetry": False}
14 |
15 | surfaces = [wing_dict, tail_dict]
16 |
17 | comp = MomentCoefficient(surfaces=surfaces)
18 |
19 | run_test(self, comp, complex_flag=True, method="cs")
20 |
21 | def test2(self):
22 | surfaces = get_default_surfaces()
23 |
24 | group = om.Group()
25 |
26 | comp = MomentCoefficient(surfaces=surfaces)
27 |
28 | indep_var_comp = om.IndepVarComp()
29 |
30 | indep_var_comp.add_output("S_ref_total", val=1e4, units="m**2")
31 | indep_var_comp.add_output("cg", val=np.array([-10.0, 10.0, -10.0]), units="m")
32 |
33 | group.add_subsystem("moment_calc", comp)
34 | group.add_subsystem("indep_var_comp", indep_var_comp)
35 |
36 | group.connect("indep_var_comp.S_ref_total", "moment_calc.S_ref_total")
37 | group.connect("indep_var_comp.cg", "moment_calc.cg")
38 |
39 | run_test(self, group, complex_flag=True, method="cs")
40 |
41 |
42 | if __name__ == "__main__":
43 | unittest.main()
44 |
--------------------------------------------------------------------------------
/tests/functionals_tests/test_sum_areas.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.functionals.sum_areas import SumAreas
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = SumAreas(surfaces=surfaces)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/functionals_tests/test_total_lift_drag.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import openmdao.api as om
4 |
5 | from openaerostruct.functionals.total_lift_drag import TotalLiftDrag
6 | from openaerostruct.utils.testing import run_test, get_default_surfaces
7 |
8 |
9 | class Test(unittest.TestCase):
10 | def test(self):
11 | wing_dict = {"name": "wing", "num_y": 7, "num_x": 2, "symmetry": True}
12 | tail_dict = {"name": "tail", "num_y": 5, "num_x": 3, "symmetry": False}
13 |
14 | surfaces = [wing_dict, tail_dict]
15 |
16 | comp = TotalLiftDrag(surfaces=surfaces)
17 |
18 | run_test(self, comp)
19 |
20 | # This is known to have some issues for sufficiently small values of S_ref_total
21 | # There is probably a derivative bug somewhere in the moment_coefficient.py calcs
22 | def test2(self):
23 | surfaces = get_default_surfaces()
24 |
25 | group = om.Group()
26 |
27 | comp = TotalLiftDrag(surfaces=surfaces)
28 |
29 | indep_var_comp = om.IndepVarComp()
30 |
31 | indep_var_comp.add_output("S_ref_total", val=10.0, units="m**2")
32 |
33 | group.add_subsystem("moment_calc", comp)
34 | group.add_subsystem("indep_var_comp", indep_var_comp)
35 |
36 | group.connect("indep_var_comp.S_ref_total", "moment_calc.S_ref_total")
37 |
38 | run_test(self, group)
39 |
40 |
41 | if __name__ == "__main__":
42 | unittest.main()
43 |
--------------------------------------------------------------------------------
/tests/geometry_tests/test_geometry_mesh.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import unittest
3 |
4 | from openaerostruct.geometry.geometry_mesh import GeometryMesh
5 | from openaerostruct.utils.testing import run_test
6 |
7 |
8 | class Test(unittest.TestCase):
9 | def test(self):
10 | surface = {}
11 | surface["symmetry"] = False
12 | surface["type"] = "aero"
13 | span = 1
14 | ny = 5
15 | mesh = np.zeros((2, ny, 3))
16 | mesh[0, :, 0] = -1.0
17 | mesh[:, :, 1] = np.linspace(0, span, ny)
18 | surface["mesh"] = mesh
19 | surface["name"] = "wing"
20 |
21 | # The way this is currently set up, we don't actually use the values here
22 | surface["twist_cp"] = np.zeros((5))
23 | surface["chord_cp"] = np.zeros((5))
24 | surface["chord_scaling_pos"] = 0.25
25 | surface["xshear_cp"] = np.zeros((5))
26 | surface["yshear_cp"] = np.zeros((5))
27 | surface["zshear_cp"] = np.zeros((5))
28 |
29 | surface["span"] = span
30 | surface["t_over_c_cp"] = np.array([0.12])
31 |
32 | comp = GeometryMesh(surface=surface)
33 |
34 | run_test(self, comp, complex_flag=True, method="cs")
35 |
36 |
37 | if __name__ == "__main__":
38 | unittest.main()
39 |
--------------------------------------------------------------------------------
/tests/geometry_tests/test_joining_comp.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import unittest
3 |
4 | from openaerostruct.geometry.geometry_multi_join import GeomMultiJoin
5 | from openaerostruct.geometry.utils import build_section_dicts
6 | from openaerostruct.utils.testing import run_test, get_three_section_surface
7 |
8 |
9 | class Test(unittest.TestCase):
10 | def test(self):
11 | (surface, chord_bspline) = get_three_section_surface()
12 | sec_dicts = build_section_dicts(surface)
13 |
14 | comp = GeomMultiJoin(sections=sec_dicts, dim_constr=[np.ones(3), np.ones(3), np.ones(3)])
15 |
16 | run_test(self, comp, complex_flag=True, method="cs")
17 |
18 |
19 | if __name__ == "__main__":
20 | unittest.main()
21 |
--------------------------------------------------------------------------------
/tests/geometry_tests/test_monotonic_constraint.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import numpy as np
4 |
5 | from openaerostruct.geometry.monotonic_constraint import MonotonicConstraint
6 |
7 | from openaerostruct.utils.testing import run_test
8 |
9 |
10 | class Test(unittest.TestCase):
11 | def test_sym1(self):
12 | surface = {"symmetry": True, "mesh": np.zeros((1, 5, 3))}
13 |
14 | comp = MonotonicConstraint(var_name="x", surface=surface)
15 | run_test(self, comp)
16 |
17 | def test_sym2(self):
18 | surface = {"symmetry": True, "mesh": np.zeros((1, 16, 3))}
19 | comp = MonotonicConstraint(var_name="x", surface=surface)
20 |
21 | run_test(self, comp)
22 |
23 | def test_assym1(self):
24 | surface = {"symmetry": False, "mesh": np.zeros((1, 5, 3))}
25 | comp = MonotonicConstraint(var_name="x", surface=surface)
26 | run_test(self, comp)
27 |
28 | def test_assym2(self):
29 | surface = {"symmetry": False, "mesh": np.zeros((1, 16, 3))}
30 | comp = MonotonicConstraint(var_name="x", surface=surface)
31 | run_test(self, comp)
32 |
33 |
34 | if __name__ == "__main__":
35 | unittest.main()
36 |
--------------------------------------------------------------------------------
/tests/geometry_tests/test_radius_comp.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import openmdao.api as om
4 |
5 | from openaerostruct.geometry.radius_comp import RadiusComp
6 | from openaerostruct.utils.testing import run_test, get_default_surfaces
7 |
8 | import numpy as np
9 |
10 |
11 | class Test(unittest.TestCase):
12 | def test(self):
13 | surfaces = get_default_surfaces()
14 |
15 | group = om.Group()
16 |
17 | comp = RadiusComp(surface=surfaces[0])
18 | ny = surfaces[0]["mesh"].shape[1]
19 |
20 | indep_var_comp = om.IndepVarComp()
21 | indep_var_comp.add_output("mesh", val=surfaces[0]["mesh"], units="m")
22 | indep_var_comp.add_output("t_over_c", val=np.linspace(0.1, 0.5, num=ny - 1))
23 |
24 | group.add_subsystem("radius", comp)
25 | group.add_subsystem("indep_var_comp", indep_var_comp)
26 |
27 | group.connect("indep_var_comp.mesh", "radius.mesh")
28 | group.connect("indep_var_comp.t_over_c", "radius.t_over_c")
29 |
30 | run_test(self, group)
31 |
32 |
33 | if __name__ == "__main__":
34 | unittest.main()
35 |
--------------------------------------------------------------------------------
/tests/geometry_tests/test_unification_comp.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import openmdao.api as om
4 | from openaerostruct.geometry.geometry_unification import GeomMultiUnification
5 | from openaerostruct.geometry.utils import build_section_dicts
6 | from openaerostruct.geometry.utils import stretch, sweep, dihedral
7 | from openaerostruct.utils.testing import run_test, get_three_section_surface
8 |
9 |
10 | class Test(unittest.TestCase):
11 | def test_no_shift(self):
12 | (surface, chord_bspline) = get_three_section_surface()
13 | sec_dicts = build_section_dicts(surface)
14 |
15 | comp = GeomMultiUnification(sections=sec_dicts, surface_name=surface["name"], shift_uni_mesh=False)
16 |
17 | group = om.Group()
18 |
19 | group.add_subsystem("comp", comp, promotes=["*"])
20 |
21 | group.set_input_defaults("sec0_def_mesh", sec_dicts[0]["mesh"])
22 | group.set_input_defaults("sec1_def_mesh", sec_dicts[1]["mesh"])
23 | group.set_input_defaults("sec2_def_mesh", sec_dicts[2]["mesh"])
24 |
25 | run_test(self, group, complex_flag=True, method="cs")
26 |
27 | def test_shift(self):
28 | (surface, chord_bspline) = get_three_section_surface()
29 |
30 | # Apply some scalar mesh transformations
31 | surface["span"] = [5.0, 5.0, 3.0]
32 | surface["sweep"] = [-10.0, 10.0, -20.0]
33 | surface["dihedral"] = [-10.0, 10.0, -20.0]
34 |
35 | sec_dicts = build_section_dicts(surface)
36 |
37 | for i in range(surface["num_sections"]):
38 | sweep(sec_dicts[i]["mesh"], surface["sweep"][i], True)
39 | stretch(sec_dicts[i]["mesh"], surface["span"][i], True)
40 | dihedral(sec_dicts[i]["mesh"], surface["dihedral"][i], True)
41 |
42 | comp = GeomMultiUnification(sections=sec_dicts, surface_name=surface["name"], shift_uni_mesh=True)
43 |
44 | group = om.Group()
45 |
46 | group.add_subsystem("comp", comp, promotes=["*"])
47 |
48 | group.set_input_defaults("sec0_def_mesh", sec_dicts[0]["mesh"])
49 | group.set_input_defaults("sec1_def_mesh", sec_dicts[1]["mesh"])
50 | group.set_input_defaults("sec2_def_mesh", sec_dicts[2]["mesh"])
51 |
52 | run_test(self, group, complex_flag=True, method="cs")
53 |
54 |
55 | if __name__ == "__main__":
56 | unittest.main()
57 |
--------------------------------------------------------------------------------
/tests/geometry_tests/test_vsp_mesh.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 | from openmdao.utils.assert_utils import assert_near_equal
4 |
5 | from openaerostruct.meshing.mesh_generator import generate_mesh
6 | from openaerostruct.geometry.utils import generate_vsp_surfaces
7 |
8 | vsp_file = os.path.join(os.path.dirname(__file__), "rect_wing.vsp3")
9 |
10 | # check if openvsp is available
11 | try:
12 | generate_vsp_surfaces(vsp_file)
13 |
14 | openvsp_flag = True
15 | except ImportError:
16 | openvsp_flag = False
17 |
18 |
19 | @unittest.skipUnless(openvsp_flag, "OpenVSP is required.")
20 | class Test(unittest.TestCase):
21 | def test_full(self):
22 | # Create a rectangular wing with uniform spacing
23 | mesh_dict = {
24 | "num_y": 9,
25 | "num_x": 3,
26 | "wing_type": "rect",
27 | "symmetry": False,
28 | "span": 10.0,
29 | "root_chord": 1,
30 | "span_cos_spacing": 0.0,
31 | "offset": [-0.5, 0, 0], # chordwise coordinate goes from -0.5 to 0.5
32 | }
33 |
34 | oas_mesh = generate_mesh(mesh_dict)
35 |
36 | # Read in equivilent wing from vsp
37 | vsp_surf_list = generate_vsp_surfaces(vsp_file, symmetry=False)
38 |
39 | assert_near_equal(vsp_surf_list[0]["mesh"], oas_mesh)
40 |
41 | def test_symm(self):
42 | # Create a rectangular wing with uniform spacing
43 | mesh_dict = {
44 | "num_y": 9,
45 | "num_x": 3,
46 | "wing_type": "rect",
47 | "symmetry": True,
48 | "span": 10.0,
49 | "root_chord": 1,
50 | "span_cos_spacing": 0.0,
51 | "offset": [-0.5, 0, 0], # chordwise coordinate goes from -0.5 to 0.5
52 | }
53 |
54 | oas_mesh = generate_mesh(mesh_dict)
55 | # Flip mesh to be right-handed
56 | oas_mesh = oas_mesh[:, ::-1]
57 | oas_mesh[:, :, 1] *= -1.0
58 |
59 | # Read in equivilent wing from vsp
60 | vsp_surf_list = generate_vsp_surfaces(vsp_file, symmetry=True)
61 |
62 | assert_near_equal(vsp_surf_list[0]["mesh"], oas_mesh)
63 |
64 |
65 | if __name__ == "__main__":
66 | unittest.main()
67 |
--------------------------------------------------------------------------------
/tests/integration_tests/test_struct_analysis.py:
--------------------------------------------------------------------------------
1 | from openmdao.utils.assert_utils import assert_near_equal
2 | import unittest
3 | import numpy as np
4 |
5 | from openaerostruct.meshing.mesh_generator import generate_mesh
6 | from openaerostruct.structures.struct_groups import SpatialBeamAlone
7 |
8 | import openmdao.api as om
9 |
10 |
11 | class Test(unittest.TestCase):
12 | def test(self):
13 | # Create a dictionary to store options about the surface
14 | mesh_dict = {"num_y": 7, "wing_type": "CRM", "symmetry": True, "num_twist_cp": 5}
15 |
16 | mesh, twist_cp = generate_mesh(mesh_dict)
17 |
18 | surf_dict = {
19 | # Wing definition
20 | "name": "wing", # name of the surface
21 | "symmetry": True, # if true, model one half of wing
22 | # reflected across the plane y = 0
23 | "fem_model_type": "tube",
24 | "mesh": mesh,
25 | "radius_cp": np.ones((5)) * 0.5,
26 | # Structural values are based on aluminum 7075
27 | "E": 70.0e9, # [Pa] Young's modulus of the spar
28 | "G": 30.0e9, # [Pa] shear modulus of the spar
29 | "yield": 500.0e6 / 2.5, # [Pa] yield stress divided by 2.5 for limiting case
30 | "mrho": 3.0e3, # [kg/m^3] material density
31 | "fem_origin": 0.35, # normalized chordwise location of the spar
32 | "t_over_c_cp": np.array([0.15]), # maximum airfoil thickness
33 | "thickness_cp": np.ones((3)) * 0.1,
34 | "wing_weight_ratio": 2.0,
35 | "struct_weight_relief": False, # True to add the weight of the structure to the loads on the structure
36 | "distributed_fuel_weight": False,
37 | "exact_failure_constraint": False,
38 | }
39 |
40 | # Create the problem and assign the model group
41 | prob = om.Problem()
42 |
43 | ny = surf_dict["mesh"].shape[1]
44 |
45 | indep_var_comp = om.IndepVarComp()
46 | indep_var_comp.add_output("loads", val=np.ones((ny, 6)) * 2e5, units="N")
47 | indep_var_comp.add_output("load_factor", val=1.0)
48 |
49 | struct_group = SpatialBeamAlone(surface=surf_dict)
50 |
51 | # Add indep_vars to the structural group
52 | struct_group.add_subsystem("indep_vars", indep_var_comp, promotes=["*"])
53 |
54 | prob.model.add_subsystem(surf_dict["name"], struct_group)
55 |
56 | # Set up the problem
57 | prob.setup()
58 |
59 | prob.run_model()
60 |
61 | assert_near_equal(prob["wing.structural_mass"][0], 117819.798089, 1e-4)
62 |
63 |
64 | if __name__ == "__main__":
65 | unittest.main()
66 |
--------------------------------------------------------------------------------
/tests/integration_tests/test_struct_analysis_2g.py:
--------------------------------------------------------------------------------
1 | from openmdao.utils.assert_utils import assert_near_equal
2 | import unittest
3 | import numpy as np
4 |
5 | from openaerostruct.meshing.mesh_generator import generate_mesh
6 | from openaerostruct.structures.struct_groups import SpatialBeamAlone
7 |
8 | import openmdao.api as om
9 |
10 |
11 | class Test(unittest.TestCase):
12 | def test(self):
13 | # Create a dictionary to store options about the surface
14 | mesh_dict = {"num_y": 7, "wing_type": "CRM", "symmetry": True, "num_twist_cp": 5}
15 |
16 | mesh, twist_cp = generate_mesh(mesh_dict)
17 |
18 | surf_dict = {
19 | # Wing definition
20 | "name": "wing", # name of the surface
21 | "symmetry": True, # if true, model one half of wing
22 | # reflected across the plane y = 0
23 | "fem_model_type": "tube",
24 | "mesh": mesh,
25 | # Structural values are based on aluminum 7075
26 | "E": 70.0e9, # [Pa] Young's modulus of the spar
27 | "G": 30.0e9, # [Pa] shear modulus of the spar
28 | "yield": 500.0e6 / 2.5, # [Pa] yield stress divided by 2.5 for limiting case
29 | "mrho": 3.0e3, # [kg/m^3] material density
30 | "fem_origin": 0.35, # normalized chordwise location of the spar
31 | "t_over_c_cp": np.array([0.15]), # maximum airfoil thickness
32 | "thickness_cp": np.ones((3)) * 0.1,
33 | "wing_weight_ratio": 2.0,
34 | "struct_weight_relief": False, # True to add the weight of the structure to the loads on the structure
35 | "distributed_fuel_weight": False,
36 | "exact_failure_constraint": False,
37 | }
38 |
39 | # Create the problem and assign the model group
40 | prob = om.Problem()
41 |
42 | ny = surf_dict["mesh"].shape[1]
43 |
44 | indep_var_comp = om.IndepVarComp()
45 | indep_var_comp.add_output("loads", val=np.ones((ny, 6)) * 2e5, units="N")
46 | indep_var_comp.add_output("load_factor", val=2.0)
47 |
48 | struct_group = SpatialBeamAlone(surface=surf_dict)
49 |
50 | # Add indep_vars to the structural group
51 | struct_group.add_subsystem("indep_vars", indep_var_comp, promotes=["*"])
52 |
53 | prob.model.add_subsystem(surf_dict["name"], struct_group)
54 |
55 | # Set up the problem
56 | prob.setup()
57 |
58 | prob.run_model()
59 |
60 | assert_near_equal(prob["wing.structural_mass"][0], 124229.646011, 1e-4)
61 |
62 |
63 | if __name__ == "__main__":
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/tests/integration_tests/test_struct_no_loads.py:
--------------------------------------------------------------------------------
1 | from openmdao.utils.assert_utils import assert_near_equal
2 | import unittest
3 | import numpy as np
4 |
5 | from openaerostruct.meshing.mesh_generator import generate_mesh
6 | from openaerostruct.structures.struct_groups import SpatialBeamAlone
7 |
8 | import openmdao.api as om
9 |
10 |
11 | class Test(unittest.TestCase):
12 | def test(self):
13 | # Create a dictionary to store options about the surface
14 | mesh_dict = {"num_y": 7, "wing_type": "CRM", "symmetry": True, "num_twist_cp": 5}
15 |
16 | mesh, twist_cp = generate_mesh(mesh_dict)
17 |
18 | surf_dict = {
19 | # Wing definition
20 | "name": "wing", # name of the surface
21 | "symmetry": True, # if true, model one half of wing
22 | # reflected across the plane y = 0
23 | "fem_model_type": "tube",
24 | "mesh": mesh,
25 | # Structural values are based on aluminum 7075
26 | "E": 70.0e9, # [Pa] Young's modulus of the spar
27 | "G": 30.0e9, # [Pa] shear modulus of the spar
28 | "yield": 500.0e6 / 2.5, # [Pa] yield stress divided by 2.5 for limiting case
29 | "mrho": 3.0e3, # [kg/m^3] material density
30 | "fem_origin": 0.35, # normalized chordwise location of the spar
31 | "t_over_c_cp": np.array([0.15]), # maximum airfoil thickness
32 | "thickness_cp": np.ones((3)) * 0.1,
33 | "wing_weight_ratio": 2.0,
34 | "struct_weight_relief": False, # True to add the weight of the structure to the loads on the structure
35 | "distributed_fuel_weight": False,
36 | "exact_failure_constraint": False,
37 | }
38 |
39 | # Create the problem and assign the model group
40 | prob = om.Problem()
41 |
42 | ny = surf_dict["mesh"].shape[1]
43 |
44 | indep_var_comp = om.IndepVarComp()
45 | indep_var_comp.add_output("loads", val=np.zeros((ny, 6)), units="N")
46 | indep_var_comp.add_output("load_factor", val=1.0)
47 |
48 | struct_group = SpatialBeamAlone(surface=surf_dict)
49 |
50 | # Add indep_vars to the structural group
51 | struct_group.add_subsystem("indep_vars", indep_var_comp, promotes=["*"])
52 |
53 | prob.model.add_subsystem(surf_dict["name"], struct_group, promotes=["*"])
54 |
55 | prob.driver = om.ScipyOptimizeDriver()
56 |
57 | # Setup problem and add design variables, constraint, and objective
58 | prob.model.add_design_var("thickness_cp", lower=0.01, upper=0.5, scaler=1e2)
59 | prob.model.add_constraint("failure", upper=0.0)
60 | prob.model.add_constraint("thickness_intersects", upper=0.0)
61 |
62 | # Add design variables, constraisnt, and objective on the problem
63 | prob.model.add_objective("structural_mass", scaler=1e-5)
64 |
65 | # Set up the problem
66 | prob.setup()
67 |
68 | prob.run_driver()
69 |
70 | assert_near_equal(prob["structural_mass"][0], 13601.162582, 1e-4)
71 | assert_near_equal(prob["disp"][1, 2], 0.0, 1e-4)
72 |
73 |
74 | if __name__ == "__main__":
75 | unittest.main()
76 |
--------------------------------------------------------------------------------
/tests/meshing_tests/test_mesh_generator.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 |
4 | class Test(unittest.TestCase):
5 | def test_span_cos_spacing_rect(self):
6 | # Checks if a valid mesh is returned for various spanwise spacing factors
7 | import numpy as np
8 | from openaerostruct.meshing.mesh_generator import gen_rect_mesh
9 |
10 | num_x = 2
11 | num_y = 11
12 | span = 10
13 | span_cos_spacing = [0.0, 0.25, 0.5, 0.75, 1.0, 2.0, 2.25, 2.5, 2.75, 3.0]
14 |
15 | for csp in span_cos_spacing:
16 | mesh = gen_rect_mesh(num_x, num_y, span, chord=2, span_cos_spacing=csp, chord_cos_spacing=0.0)
17 |
18 | self.assertTrue(np.all(np.diff(mesh[0, :, 1]) > 0.0))
19 | self.assertTrue(np.all(np.diff(mesh[:, 0, 0]) > 0.0))
20 |
21 | def test_chord_cos_spacing_rect(self):
22 | # Checks if a valid mesh is returned for various spanwise spacing factors
23 | import numpy as np
24 | from openaerostruct.meshing.mesh_generator import gen_rect_mesh
25 |
26 | num_x = 11
27 | num_y = 11
28 | span = 10
29 | chord_cos_spacing = [0.0, 0.25, 0.5, 0.75, 1.0]
30 |
31 | for csp in chord_cos_spacing:
32 | mesh = gen_rect_mesh(num_x, num_y, span, chord=2, span_cos_spacing=0.0, chord_cos_spacing=csp)
33 |
34 | self.assertTrue(np.all(np.diff(mesh[0, :, 1]) > 0.0))
35 | self.assertTrue(np.all(np.diff(mesh[:, 0, 0]) > 0.0))
36 |
37 | def test_span_cos_spacing_crm(self):
38 | # Checks if a valid mesh is returned for various spanwise spacing factors
39 | import numpy as np
40 | from openaerostruct.meshing.mesh_generator import gen_crm_mesh
41 |
42 | num_x = 2
43 | num_y = 6
44 | span_cos_spacing = [0.0, 0.25, 0.5, 0.75, 1.0, 2.0, 2.25, 2.5, 2.75, 3.0]
45 |
46 | for csp in span_cos_spacing:
47 | mesh, _, _ = gen_crm_mesh(num_x, num_y, span_cos_spacing=csp, chord_cos_spacing=0.0)
48 |
49 | self.assertTrue(np.all(np.diff(mesh[0, :, 1]) > 0.0))
50 | self.assertTrue(np.all(np.diff(mesh[:, 0, 0]) > 0.0))
51 |
52 | def test_chord_cos_spacing_crm(self):
53 | # Checks if a valid mesh is returned for various spanwise spacing factors
54 | import numpy as np
55 | from openaerostruct.meshing.mesh_generator import gen_crm_mesh
56 |
57 | num_x = 6
58 | num_y = 6
59 | chord_cos_spacing = [0.0, 0.25, 0.5, 0.75, 1.0]
60 |
61 | for csp in chord_cos_spacing:
62 | mesh, _, _ = gen_crm_mesh(num_x, num_y, span_cos_spacing=0.0, chord_cos_spacing=csp)
63 |
64 | self.assertTrue(np.all(np.diff(mesh[0, :, 1]) > 0.0))
65 | self.assertTrue(np.all(np.diff(mesh[:, 0, 0]) > 0.0))
66 |
67 |
68 | if __name__ == "__main__":
69 | unittest.main()
70 |
--------------------------------------------------------------------------------
/tests/meshing_tests/test_mesh_regen.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from openmdao.utils.assert_utils import assert_near_equal
3 |
4 |
5 | class Test(unittest.TestCase):
6 | def test_span_regen_rect(self):
7 | # Checks if a valid mesh is returned for various spanwise spacing factors and numbers of points
8 | from openaerostruct.meshing.mesh_generator import gen_rect_mesh
9 | from openaerostruct.meshing.utils import regen_spanwise_panels
10 |
11 | num_x = 2
12 | num_y = 11
13 | span = 10
14 | span_cos_spacing = [0.0, 0.25, 0.5, 0.75, 1.0, 2.0, 2.25, 2.5, 2.75, 3.0]
15 |
16 | mesh = gen_rect_mesh(num_x, num_y, span, chord=2, span_cos_spacing=0.0, chord_cos_spacing=0.0)
17 | rc = mesh[0, -1, 0] - mesh[-1, -1, 0]
18 | tc = mesh[0, 0, 0] - mesh[-1, 0, 0]
19 | b = mesh[0, 0, 1] - mesh[0, -1, 1]
20 |
21 | new_num_y = [7, 21, 31]
22 |
23 | for nny in new_num_y:
24 | for csp in span_cos_spacing:
25 | newMesh = regen_spanwise_panels(mesh, nny, span_cos_spacing=csp)
26 |
27 | new_rc = newMesh[0, -1, 0] - newMesh[-1, -1, 0]
28 | new_tc = newMesh[0, 0, 0] - newMesh[-1, 0, 0]
29 | new_b = newMesh[0, 0, 1] - newMesh[0, -1, 1]
30 |
31 | assert_near_equal(new_rc, rc, 1e-10)
32 | assert_near_equal(new_tc, tc, 1e-10)
33 | assert_near_equal(new_b, b, 1e-10)
34 |
35 | def test_span_regen_crm(self):
36 | # Checks if a valid mesh is returned for various spanwise spacing factors and numbers of points
37 | from openaerostruct.meshing.mesh_generator import gen_crm_mesh
38 | from openaerostruct.meshing.utils import regen_spanwise_panels
39 |
40 | num_x = 2
41 | num_y = 11
42 |
43 | span_cos_spacing = [0.0, 0.25, 0.5, 0.75, 1.0, 2.0, 2.25, 2.5, 2.75, 3.0]
44 |
45 | mesh, _, _ = gen_crm_mesh(num_x, num_y, span_cos_spacing=0.0, chord_cos_spacing=0.0)
46 | rc = mesh[0, -1, 0] - mesh[-1, -1, 0]
47 | tc = mesh[0, 0, 0] - mesh[-1, 0, 0]
48 | b = mesh[0, 0, 1] - mesh[0, -1, 1]
49 |
50 | new_num_y = [7, 21, 31]
51 |
52 | for nny in new_num_y:
53 | for csp in span_cos_spacing:
54 | newMesh = regen_spanwise_panels(mesh, nny, span_cos_spacing=csp)
55 |
56 | new_rc = newMesh[0, -1, 0] - newMesh[-1, -1, 0]
57 | new_tc = newMesh[0, 0, 0] - newMesh[-1, 0, 0]
58 | new_b = newMesh[0, 0, 1] - newMesh[0, -1, 1]
59 |
60 | assert_near_equal(new_rc, rc, 1e-10)
61 | assert_near_equal(new_tc, tc, 1e-10)
62 | assert_near_equal(new_b, b, 1e-10)
63 |
64 |
65 | if __name__ == "__main__":
66 | unittest.main()
67 |
--------------------------------------------------------------------------------
/tests/mphys_tests/test_aero_builder.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import importlib
3 | import numpy as np
4 |
5 | from openaerostruct.mphys import AeroBuilder
6 | from openaerostruct.utils.testing import get_default_surfaces
7 |
8 | # check if mphys/mpi4py is available
9 | try:
10 | from mpi4py import MPI
11 |
12 | mpi_flag = True
13 | except ImportError:
14 | mpi_flag = False
15 |
16 | mphys_flag = importlib.util.find_spec("mphys") is not None
17 |
18 |
19 | @unittest.skipUnless(mphys_flag and mpi_flag, "mphys/mpi4py is required.")
20 | class Test(unittest.TestCase):
21 | def setUp(self):
22 | self.surfaces = get_default_surfaces()
23 | comm = MPI.COMM_WORLD
24 | # Create mphys builder for aero solver
25 | self.aero_builder = AeroBuilder(self.surfaces)
26 | self.aero_builder.initialize(comm)
27 |
28 | def test_tagged_indices(self):
29 | wing_nnodes = self.surfaces[0]["mesh"].size // 3
30 | tail_nnodes = self.surfaces[1]["mesh"].size // 3
31 | with self.subTest(case="wing"):
32 | wing_inds = self.aero_builder.get_tagged_indices(["wing"])
33 | np.testing.assert_equal(wing_inds, np.arange(0, wing_nnodes))
34 |
35 | with self.subTest(case="tail"):
36 | tail_inds = self.aero_builder.get_tagged_indices(["tail"])
37 | np.testing.assert_equal(tail_inds, np.arange(wing_nnodes, wing_nnodes + tail_nnodes))
38 |
39 | with self.subTest(case="wing+tail"):
40 | wt_inds = self.aero_builder.get_tagged_indices(["wing", "tail"])
41 | np.testing.assert_equal(wt_inds, np.arange(0, wing_nnodes + tail_nnodes))
42 |
43 | with self.subTest(case="all"):
44 | wt_inds = self.aero_builder.get_tagged_indices(-1)
45 | np.testing.assert_equal(wt_inds, np.arange(0, wing_nnodes + tail_nnodes))
46 |
47 |
48 | if __name__ == "__main__":
49 | unittest.main()
50 |
--------------------------------------------------------------------------------
/tests/mphys_tests/test_aero_funcs_group.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import openmdao.api as om
4 | from mphys.core import MPhysVariables
5 |
6 | from openaerostruct.mphys.aero_funcs_group import AeroFuncsGroup
7 | from openaerostruct.utils.testing import run_test, get_default_surfaces
8 |
9 | FlowVars = MPhysVariables.Aerodynamics.FlowConditions
10 |
11 |
12 | class Test(unittest.TestCase):
13 | def test(self):
14 | surfaces = get_default_surfaces()
15 |
16 | group = om.Group()
17 |
18 | ivc = group.add_subsystem("ivc", om.IndepVarComp())
19 | ivc.add_output(FlowVars.ANGLE_OF_ATTACK, val=1.0)
20 | ivc.add_output(FlowVars.MACH_NUMBER, val=0.6)
21 | ivc.add_output("wing_widths", val=[1.0, 1.0, 1.0])
22 | ivc.add_output("v", val=1.0)
23 | ivc.add_output("rho", val=1.0)
24 |
25 | group.add_subsystem("funcs", AeroFuncsGroup(surfaces=[surfaces[0]], write_solution=False))
26 | group.promotes("funcs", [(f"{surfaces[0]['name']}.widths", f"{surfaces[0]['name']}_widths")])
27 |
28 | group.connect(f"ivc.{FlowVars.ANGLE_OF_ATTACK}", f"funcs.{FlowVars.ANGLE_OF_ATTACK}")
29 | group.connect(f"ivc.{FlowVars.MACH_NUMBER}", f"funcs.{FlowVars.MACH_NUMBER}")
30 | group.connect("ivc.rho", "funcs.rho")
31 | group.connect("ivc.v", "funcs.v")
32 | group.connect("ivc.wing_widths", f"{surfaces[0]['name']}_widths")
33 |
34 | run_test(self, group, complex_flag=True, method="cs")
35 |
36 |
37 | if __name__ == "__main__":
38 | unittest.main()
39 |
--------------------------------------------------------------------------------
/tests/mphys_tests/test_aero_solver_group.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import openmdao.api as om
4 | from mphys.core import MPhysVariables
5 |
6 | from openaerostruct.mphys.aero_solver_group import AeroSolverGroup
7 | from openaerostruct.utils.testing import run_test, get_default_surfaces
8 |
9 | FlowVars = MPhysVariables.Aerodynamics.FlowConditions
10 |
11 |
12 | class Test(unittest.TestCase):
13 | def test_incompressible(self):
14 | group = self.setup_solver(compressible=False)
15 | run_test(self, group, complex_flag=True, method="cs", atol=1e-4, rtol=1e-5)
16 |
17 | def test_compressible(self):
18 | group = self.setup_solver(compressible=True)
19 | run_test(self, group, complex_flag=True, method="cs", atol=1e-4, rtol=1e-5)
20 |
21 | def setup_solver(self, compressible=True):
22 | surfaces = get_default_surfaces()
23 |
24 | group = om.Group()
25 |
26 | ivc = group.add_subsystem("ivc", om.IndepVarComp())
27 | ivc.add_output(f"{surfaces[0]['name']}_def_mesh", val=surfaces[0]["mesh"])
28 | ivc.add_output(FlowVars.ANGLE_OF_ATTACK, val=1.0)
29 | ivc.add_output(FlowVars.MACH_NUMBER, val=0.6)
30 |
31 | group.add_subsystem("solver", AeroSolverGroup(surfaces=[surfaces[0]], compressible=compressible))
32 | group.promotes("solver", [(f"{surfaces[0]['name']}.normals", f"{surfaces[0]['name']}_normals")])
33 |
34 | group.connect(f"ivc.{surfaces[0]['name']}_def_mesh", f"solver.{surfaces[0]['name']}_def_mesh")
35 | group.connect(f"ivc.{FlowVars.ANGLE_OF_ATTACK}", f"solver.{FlowVars.ANGLE_OF_ATTACK}")
36 | if compressible:
37 | group.connect(f"ivc.{FlowVars.MACH_NUMBER}", f"solver.{FlowVars.MACH_NUMBER}")
38 |
39 | return group
40 |
41 |
42 | if __name__ == "__main__":
43 | unittest.main()
44 |
--------------------------------------------------------------------------------
/tests/mphys_tests/test_demux_mesh.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.mphys.demux_surface_mesh import DemuxSurfaceMesh
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = DemuxSurfaceMesh(surfaces=surfaces)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/mphys_tests/test_mux_forces.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.mphys.mux_surface_forces import MuxSurfaceForces
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surfaces = get_default_surfaces()
10 |
11 | comp = MuxSurfaceForces(surfaces=surfaces)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_add_point_masses.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 |
4 | from openmdao.utils.assert_utils import assert_near_equal
5 | import openmdao.api as om
6 | from openaerostruct.structures.compute_point_mass_loads import ComputePointMassLoads
7 | from openaerostruct.utils.testing import run_test, get_default_surfaces
8 |
9 |
10 | derivs_added = False
11 |
12 |
13 | class Test(unittest.TestCase):
14 | @unittest.skipUnless(derivs_added, "Analytic derivs not added yet")
15 | def test_derivs(self):
16 | surface = get_default_surfaces()[0]
17 |
18 | surface["n_point_masses"] = 2
19 |
20 | comp = ComputePointMassLoads(surface=surface)
21 |
22 | group = om.Group()
23 |
24 | indep_var_comp = om.IndepVarComp()
25 |
26 | nodesval = np.array([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 2.0, 0.0], [0.0, 3.0, 0.0]])
27 |
28 | point_masses = np.array([[2.0, 1.0]])
29 |
30 | point_mass_locations = np.array([[2.1, 0.1, 0.2], [3.2, 1.2, 0.3]])
31 |
32 | indep_var_comp.add_output("nodes", val=nodesval, units="m")
33 | indep_var_comp.add_output("point_masses", val=point_masses, units="kg")
34 | indep_var_comp.add_output("point_mass_locations", val=point_mass_locations, units="m")
35 |
36 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
37 | group.add_subsystem("compute_point_mass_loads", comp, promotes=["*"])
38 |
39 | run_test(self, group, complex_flag=True, step=1e-8, atol=1e-5, compact_print=True)
40 |
41 | @unittest.skipUnless(derivs_added, "Analytic derivs not added yet")
42 | def test_simple_values(self):
43 | surface = get_default_surfaces()[0]
44 |
45 | surface["n_point_masses"] = 1
46 |
47 | comp = ComputePointMassLoads(surface=surface)
48 |
49 | group = om.Group()
50 |
51 | indep_var_comp = om.IndepVarComp()
52 |
53 | nodesval = np.array([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 2.0, 0.0], [0.0, 3.0, 0.0]])
54 |
55 | point_masses = np.array([[1 / 9.8]])
56 |
57 | point_mass_locations = np.array([[0.55012, 0.1, 0.0]])
58 |
59 | indep_var_comp.add_output("nodes", val=nodesval, units="m")
60 | indep_var_comp.add_output("point_masses", val=point_masses, units="kg")
61 | indep_var_comp.add_output("point_mass_locations", val=point_mass_locations, units="m")
62 |
63 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
64 | group.add_subsystem("compute_point_mass_loads", comp, promotes=["*"])
65 |
66 | prob = run_test(self, group, complex_flag=True, step=1e-8, atol=1e-5, compact_print=True)
67 |
68 | truth_array = np.array([0, 0, -1.0, 0.0, 0.55012, 0.0])
69 |
70 | assert_near_equal(prob["comp.loads_from_point_masses"][0, :], truth_array, 1e-6)
71 |
72 |
73 | if __name__ == "__main__":
74 | unittest.main()
75 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_compute_nodes.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.compute_nodes import ComputeNodes
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = ComputeNodes(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_create_rhs.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.create_rhs import CreateRHS
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = CreateRHS(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_disp.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.disp import Disp
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = Disp(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_energy.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.energy import Energy
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = Energy(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_failure_exact.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.failure_exact import FailureExact
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | # turn down some of these properties, so the absolute deriv error isn't magnified
12 | surface["E"] = 7
13 | surface["G"] = 3
14 | surface["yield"] = 0.02
15 |
16 | comp = FailureExact(surface=surface)
17 |
18 | run_test(self, comp)
19 |
20 |
21 | if __name__ == "__main__":
22 | unittest.main()
23 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_failure_ks.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.failure_ks import FailureKS
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | # turn down some of these properties, so the absolute deriv error isn't magnified
12 | surface["E"] = 7
13 | surface["G"] = 3
14 | surface["yield"] = 0.02
15 |
16 | comp = FailureKS(surface=surface)
17 |
18 | run_test(self, comp, complex_flag=True, step=1e-40, method="cs", compact_print=False)
19 |
20 |
21 | if __name__ == "__main__":
22 | unittest.main()
23 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_fem.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.fem import FEM
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = FEM(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_fuel_loads.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import openmdao.api as om
4 | from openaerostruct.structures.fuel_loads import FuelLoads
5 | from openaerostruct.utils.testing import run_test, get_default_surfaces
6 | import numpy as np
7 |
8 |
9 | class Test(unittest.TestCase):
10 | def test_0(self):
11 | surface = get_default_surfaces()[0]
12 |
13 | comp = FuelLoads(surface=surface)
14 |
15 | group = om.Group()
16 |
17 | indep_var_comp = om.IndepVarComp()
18 |
19 | nodesval = np.array([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 2.0, 0.0], [0.0, 3.0, 0.0]], dtype=complex)
20 |
21 | indep_var_comp.add_output("nodes", val=nodesval, units="m")
22 |
23 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
24 | group.add_subsystem("load", comp, promotes=["*"])
25 |
26 | run_test(self, group, complex_flag=True, atol=1e-2, rtol=1e-6)
27 |
28 |
29 | if __name__ == "__main__":
30 | unittest.main()
31 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_materials_tube.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.section_properties_tube import SectionPropertiesTube
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = SectionPropertiesTube(surface=surface)
12 |
13 | run_test(self, comp, complex_flag=True)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_non_intersecting_thickness.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.non_intersecting_thickness import NonIntersectingThickness
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = NonIntersectingThickness(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_spar_within_wing.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.spar_within_wing import SparWithinWing
4 | from openaerostruct.utils.testing import run_test, get_default_surfaces
5 |
6 |
7 | class Test(unittest.TestCase):
8 | def test(self):
9 | surface = get_default_surfaces()[0]
10 |
11 | comp = SparWithinWing(surface=surface)
12 |
13 | run_test(self, comp)
14 |
15 |
16 | if __name__ == "__main__":
17 | unittest.main()
18 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_structural_cg.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 |
4 | import openmdao.api as om
5 | from openaerostruct.structures.structural_cg import StructuralCG
6 | from openaerostruct.utils.testing import run_test, get_default_surfaces
7 |
8 | np.random.seed(1)
9 |
10 |
11 | class Test(unittest.TestCase):
12 | def test(self):
13 | surface = get_default_surfaces()[0]
14 |
15 | group = om.Group()
16 |
17 | comp = StructuralCG(surface=surface)
18 |
19 | indep_var_comp = om.IndepVarComp()
20 |
21 | ny = surface["mesh"].shape[1]
22 |
23 | rng = np.random.default_rng(0)
24 | indep_var_comp.add_output("nodes", val=rng.random((ny, 3)), units="m")
25 | indep_var_comp.add_output("structural_mass", val=1.0, units="kg")
26 | indep_var_comp.add_output("element_mass", val=np.ones((ny - 1)), units="kg")
27 |
28 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
29 | group.add_subsystem("structural_cg", comp, promotes=["*"])
30 |
31 | run_test(self, group, complex_flag=True, compact_print=False)
32 |
33 |
34 | if __name__ == "__main__":
35 | unittest.main()
36 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_total_load_derivs.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from openaerostruct.structures.wing_weight_loads import StructureWeightLoads
4 | from openaerostruct.structures.total_loads import TotalLoads
5 | from openaerostruct.utils.testing import run_test, get_default_surfaces
6 | import openmdao.api as om
7 | import numpy as np
8 |
9 |
10 | class Test(unittest.TestCase):
11 | def test_0(self):
12 | surface = get_default_surfaces()[0]
13 |
14 | comp = TotalLoads(surface=surface)
15 |
16 | run_test(self, comp, complex_flag=True)
17 |
18 | def test_1(self):
19 | surface = get_default_surfaces()[0]
20 | surface["struct_weight_relief"] = True
21 |
22 | comp = TotalLoads(surface=surface)
23 |
24 | run_test(self, comp, complex_flag=True)
25 |
26 | def test_2(self):
27 | surface = get_default_surfaces()[0]
28 | surface["distributed_fuel_weight"] = True
29 |
30 | comp = TotalLoads(surface=surface)
31 |
32 | run_test(self, comp, complex_flag=True)
33 |
34 | def test_structural_mass_loads(self):
35 | surface = get_default_surfaces()[0]
36 |
37 | comp = StructureWeightLoads(surface=surface)
38 |
39 | group = om.Group()
40 |
41 | indep_var_comp = om.IndepVarComp()
42 |
43 | ny = surface["mesh"].shape[1]
44 |
45 | # carefully chosen "random" values that give non-uniform derivatives outputs that are good for testing
46 | nodesval = np.array([[1.0, 2.0, 4.0], [20.0, 22.0, 7.0], [8.0, 17.0, 14.0], [13.0, 14.0, 16.0]], dtype=complex)
47 | element_mass_val = np.arange(ny - 1) + 1
48 |
49 | indep_var_comp.add_output("nodes", val=nodesval, units="m")
50 | indep_var_comp.add_output("element_mass", val=element_mass_val, units="kg")
51 |
52 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
53 | group.add_subsystem("load", comp, promotes=["*"])
54 |
55 | run_test(self, group, complex_flag=True, compact_print=True)
56 |
57 |
58 | if __name__ == "__main__":
59 | unittest.main()
60 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_vonmises_tube.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 |
4 | import openmdao.api as om
5 | from openaerostruct.structures.vonmises_tube import VonMisesTube
6 | from openaerostruct.utils.testing import run_test, get_default_surfaces
7 |
8 |
9 | class Test(unittest.TestCase):
10 | def test(self):
11 | surface = get_default_surfaces()[0]
12 |
13 | group = om.Group()
14 |
15 | comp = VonMisesTube(surface=surface)
16 |
17 | indep_var_comp = om.IndepVarComp()
18 |
19 | ny = surface["mesh"].shape[1]
20 |
21 | # define the spar with y out the wing
22 | nodes = np.zeros((ny, 3))
23 | nodes[:, 0] = np.linspace(0, 0.01, ny)
24 | nodes[:, 1] = np.linspace(0, 1, ny)
25 |
26 | radius = 0.01 * np.ones((ny - 1))
27 |
28 | disp = np.zeros((ny, 6))
29 | for i in range(6):
30 | disp[:, i] = np.linspace(0, 0.001, ny)
31 |
32 | indep_var_comp.add_output("nodes", val=nodes, units="m")
33 | indep_var_comp.add_output("radius", val=radius, units="m")
34 | indep_var_comp.add_output("disp", val=disp, units="m")
35 |
36 | group.add_subsystem("vm_comp", comp)
37 | group.add_subsystem("indep_var_comp", indep_var_comp)
38 |
39 | group.connect("indep_var_comp.nodes", "vm_comp.nodes")
40 | group.connect("indep_var_comp.radius", "vm_comp.radius")
41 | group.connect("indep_var_comp.disp", "vm_comp.disp")
42 |
43 | run_test(self, group, complex_flag=True, compact_print=True, method="cs", step=1e-40, atol=2e-4, rtol=1e-8)
44 |
45 |
46 | if __name__ == "__main__":
47 | unittest.main()
48 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_vonmises_wingbox.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 |
4 | import openmdao.api as om
5 | from openaerostruct.structures.vonmises_wingbox import VonMisesWingbox
6 | from openaerostruct.utils.testing import run_test, get_default_surfaces
7 |
8 |
9 | class Test(unittest.TestCase):
10 | def test(self):
11 | surface = get_default_surfaces()[0]
12 |
13 | # turn down some of these properties, so the absolute deriv error isn't magnified
14 | surface["E"] = 7
15 | surface["G"] = 3
16 | surface["yield"] = 0.02
17 |
18 | surface["strength_factor_for_upper_skin"] = 1.0
19 |
20 | comp = VonMisesWingbox(surface=surface)
21 |
22 | group = om.Group()
23 |
24 | indep_var_comp = om.IndepVarComp()
25 |
26 | ny = surface["mesh"].shape[1]
27 |
28 | nodesval = np.array([[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 2.0, 0.0], [0.0, 3.0, 0.0]])
29 |
30 | indep_var_comp.add_output("nodes", val=nodesval, units="m")
31 | indep_var_comp.add_output("disp", val=np.ones((ny, 6)), units="m")
32 | indep_var_comp.add_output("Qz", val=np.ones((ny - 1)), units="m**3")
33 | indep_var_comp.add_output("Iz", val=np.ones((ny - 1)))
34 | indep_var_comp.add_output("J", val=np.ones((ny - 1)), units="m**4")
35 | indep_var_comp.add_output("A_enc", val=np.ones((ny - 1)), units="m**2")
36 | indep_var_comp.add_output("spar_thickness", val=np.ones((ny - 1)), units="m")
37 | indep_var_comp.add_output("skin_thickness", val=np.ones((ny - 1)), units="m")
38 | indep_var_comp.add_output("htop", val=np.ones((ny - 1)), units="m")
39 | indep_var_comp.add_output("hbottom", val=np.ones((ny - 1)), units="m")
40 | indep_var_comp.add_output("hfront", val=np.ones((ny - 1)), units="m")
41 | indep_var_comp.add_output("hrear", val=np.ones((ny - 1)), units="m")
42 |
43 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
44 | group.add_subsystem("vonmises_wingbox", comp, promotes=["*"])
45 |
46 | run_test(self, group, complex_flag=True, step=1e-8, atol=2e-5, compact_print=True)
47 |
48 |
49 | if __name__ == "__main__":
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/tests/structures_tests/test_weight.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 |
4 | import openmdao.api as om
5 | from openaerostruct.structures.weight import Weight
6 | from openaerostruct.utils.testing import run_test, get_default_surfaces
7 |
8 |
9 | np.random.seed(314)
10 |
11 |
12 | class Test(unittest.TestCase):
13 | def test(self):
14 | surface = get_default_surfaces()[0]
15 | ny = surface["mesh"].shape[1]
16 |
17 | group = om.Group()
18 |
19 | ivc = om.IndepVarComp()
20 | rng = np.random.default_rng(0)
21 | ivc.add_output("nodes", val=rng.random((ny, 3)), units="m")
22 |
23 | comp = Weight(surface=surface)
24 |
25 | group.add_subsystem("ivc", ivc, promotes=["*"])
26 | group.add_subsystem("comp", comp, promotes=["*"])
27 |
28 | run_test(self, group, compact_print=False, complex_flag=True)
29 |
30 |
31 | if __name__ == "__main__":
32 | unittest.main()
33 |
--------------------------------------------------------------------------------
/tests/transfer_tests/test_compute_transformation_matrix.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 | import openmdao.api as om
4 |
5 | from openaerostruct.transfer.compute_transformation_matrix import ComputeTransformationMatrix
6 | from openaerostruct.utils.testing import run_test, get_default_surfaces
7 |
8 |
9 | np.random.seed(314)
10 |
11 |
12 | class Test(unittest.TestCase):
13 | def test(self):
14 | surface = get_default_surfaces()[0]
15 |
16 | comp = ComputeTransformationMatrix(surface=surface)
17 |
18 | group = om.Group()
19 |
20 | indep_var_comp = om.IndepVarComp()
21 |
22 | ny = surface["mesh"].shape[1]
23 | rng = np.random.default_rng(0)
24 | disp = rng.random((ny, 6)) * 100.0
25 |
26 | indep_var_comp.add_output("disp", val=disp, units="m")
27 |
28 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
29 | group.add_subsystem("trans_mtx", comp, promotes=["*"])
30 |
31 | run_test(self, group, complex_flag=True, method="cs")
32 |
33 |
34 | if __name__ == "__main__":
35 | unittest.main()
36 |
--------------------------------------------------------------------------------
/tests/transfer_tests/test_displacement_transfer.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import openmdao.api as om
3 |
4 | from openaerostruct.transfer.displacement_transfer import DisplacementTransfer
5 | from openaerostruct.utils.testing import run_test, get_default_surfaces
6 |
7 |
8 | class Test(unittest.TestCase):
9 | def test(self):
10 | surface = get_default_surfaces()[0]
11 |
12 | comp = DisplacementTransfer(surface=surface)
13 |
14 | group = om.Group()
15 |
16 | indep_var_comp = om.IndepVarComp()
17 |
18 | mesh = surface["mesh"]
19 |
20 | indep_var_comp.add_output("mesh", val=mesh, units="m")
21 |
22 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
23 | group.add_subsystem("load", comp, promotes=["*"])
24 |
25 | run_test(self, group, complex_flag=True)
26 |
27 |
28 | if __name__ == "__main__":
29 | unittest.main()
30 |
--------------------------------------------------------------------------------
/tests/transfer_tests/test_load_transfer.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import numpy as np
3 |
4 | import openmdao.api as om
5 | from openaerostruct.transfer.load_transfer import LoadTransfer
6 | from openaerostruct.utils.testing import run_test, get_default_surfaces
7 |
8 |
9 | class Test(unittest.TestCase):
10 | def test(self):
11 | surface = get_default_surfaces()[0]
12 | group = om.Group()
13 |
14 | comp = LoadTransfer(surface=surface)
15 |
16 | indep_var_comp = om.IndepVarComp()
17 |
18 | nx = surface["mesh"].shape[0]
19 | ny = surface["mesh"].shape[1]
20 |
21 | rng = np.random.default_rng(0)
22 | indep_var_comp.add_output("def_mesh", val=rng.random((nx, ny, 3)), units="m")
23 | indep_var_comp.add_output("sec_forces", val=rng.random((nx - 1, ny - 1, 3)), units="N")
24 |
25 | group.add_subsystem("indep_var_comp", indep_var_comp, promotes=["*"])
26 | group.add_subsystem("load_transfer", comp, promotes=["*"])
27 |
28 | run_test(self, group, complex_flag=True, compact_print=False)
29 |
30 |
31 | if __name__ == "__main__":
32 | unittest.main()
33 |
--------------------------------------------------------------------------------