├── README.md ├── problems ├── cma_es │ ├── CMAESDriver.ipynb │ ├── README.md │ ├── cmaes.ipynb │ ├── cmaes_driver.py │ ├── generic_driver.py │ └── test │ │ ├── test_cmaes.py │ │ ├── test_cmaes_driver.py │ │ ├── test_generic_cmaes_driver.py │ │ └── test_generic_devol_driver.py ├── evtol_trajectory │ ├── README.md │ ├── evtol_dymos │ │ ├── README.md │ │ ├── SNOPT_print_vectorized.out │ │ ├── evtol_dymos.ipynb │ │ ├── evtol_dymos.py │ │ ├── evtol_dymos_shooting.py │ │ ├── evtol_dymos_vectorized.py │ │ ├── evtol_dymos_vectorized_shooting.py │ │ ├── plot_results.py │ │ ├── restart.sql │ │ └── results.png │ ├── evtol_explicit_time_integraiton │ │ └── time_step_rk4.py │ ├── ode │ │ ├── evtol_dynamics_comp.py │ │ ├── evtol_dynamics_comp2.py │ │ ├── evtol_dynamics_comp_vectorized.py │ │ ├── readme.md │ │ ├── test_cl_func.py │ │ ├── test_dynamics_comp.py │ │ └── verify_data.py │ ├── original │ │ ├── CD.npy │ │ ├── CL.npy │ │ ├── D_fuse.npy │ │ ├── D_wings.npy │ │ ├── L_wings.npy │ │ ├── N.npy │ │ ├── a_x.npy │ │ ├── a_y.npy │ │ ├── acc.npy │ │ ├── aoa.npy │ │ ├── aoa_prop.npy │ │ ├── atov.npy │ │ ├── bsplines_comp.py │ │ ├── complex_transition_components.py │ │ ├── energy.npy │ │ ├── evtol_openmdao.py │ │ ├── ft.npy │ │ ├── make_ns_plots.py │ │ ├── min_u_prop.npy │ │ ├── powers.npy │ │ ├── takeoff_cs_run_script.py │ │ ├── thetas.npy │ │ ├── thrusts.npy │ │ ├── x.npy │ │ ├── x_dots.npy │ │ ├── y.npy │ │ └── y_dots.npy │ └── reference_solution.sql ├── nested_optimization │ ├── components │ │ ├── compute_modified_power.py │ │ ├── compute_pitch_angles.py │ │ ├── compute_pitch_angles_solver.py │ │ ├── compute_pitch_angles_using_subproblem.py │ │ └── design_airfoil.py │ ├── readme.md │ ├── run_MDF_opt.py │ ├── run_MDF_opt_using_subproblem.py │ ├── run_pitch_angle_opt.py │ ├── run_pitch_angle_opt_using_subproblem.py │ ├── run_sequential_opt.py │ ├── run_sequential_opt_using_subproblem.py │ └── xdsm_scripts │ │ ├── MDF_xdsm_nested_sub_opt.png │ │ ├── MDF_xdsm_nested_sub_opt.py │ │ ├── MDF_xdsm_top_level.png │ │ ├── MDF_xdsm_top_level.py │ │ ├── comp_pitch_angles_xdsm_nested.png │ │ ├── comp_pitch_angles_xdsm_nested.py │ │ ├── comp_pitch_angles_xdsm_top_level.png │ │ ├── comp_pitch_angles_xdsm_top_level.py │ │ ├── sequential_xdsm.png │ │ └── sequential_xdsm.py ├── oas_stability_derivs │ ├── aerostruct_vsp_groups.py │ ├── baseline_meshes.pkl │ ├── baseline_meshes_reduced.pkl │ ├── eCRM-001.1_wing_tail.vsp3 │ ├── ecrm_analysis.py │ ├── ecrm_comp_with_stability_derivs.py │ ├── notes.txt │ ├── opt_crm_stability_derivs.py │ ├── parameter_sweep.py │ ├── post_proc_param_sweep.py │ ├── readme.md │ ├── stab_derivs.md │ └── vsp_eCRM.py ├── oas_trajectory │ └── readme.md └── unsteady_vlm │ ├── __init__.py │ ├── aero_time_vars.png │ ├── aerostruct.png │ ├── aerostruct_for_loop.png │ ├── aerostruct_inputs.png │ ├── bspline.py │ ├── crm_data.py │ ├── geometry.py │ ├── materials.py │ ├── post_block_inputs.png │ ├── pyNBSolver.py │ ├── readme.md │ ├── run.py │ ├── run_for_loop.py │ ├── spacialbeam.py │ ├── thesis_Giovanni_Pesare.pdf │ ├── time_loop_comp.py │ ├── timeloop.py │ ├── transfer.py │ └── uvlm.py ├── solution_approaches ├── OpenVSP_OpenMDAO │ ├── OpenVSPwithOpenMDAO.ipynb │ ├── diagram_derivs_as_outputs.png │ ├── install_vsp_mac.sh │ ├── openmdao_with_vsp.md │ └── problem_dicussion.md ├── how_big.md ├── no_driver.md ├── sub_problem_cartoon.png ├── sub_problems.md ├── unsteady_analysis.md ├── unsteady_analysis │ └── sample_time_stepping.py └── unsteady_vlm_n2.png └── usingColab.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OpenMDAO RevHack 2020 3 | 4 | RevHack is a reverse hackathon where the OM users propose problems to be solved and then the dev team tries to solve them! 5 | The primary dev team goal is to gain a better understanding of the use cases that users are struggling with, and to learn to see our framework through your eyes. 6 | 7 | What you get out of it is two-fold: 8 | 1) We solves some problems for you 9 | 2) We go into detail about our thought process and justifications for how we solved them, so you can learn something about how we see things. 10 | 11 | ## Problem Solutions 12 | 13 | ### The ones we finished 14 | * OpenAeroStruct+VSP to optimize an aircraft subject stability constraints 15 | * [eVTOL takeoff optimization with Dymos][prob-evtol] 16 | * [Nested optimizations][prob-nested] 17 | * [Optimize an OpenMDAO model with CMA-ES optimizer][prob-cma_es] 18 | * [Unsteady VLM simulation][prob-unsteady-vlm] 19 | 20 | ### The ones we didn't get to 21 | * OpenAeroStruct+Dymos for aircraft trajectory optimization 22 | * Integrate an OpenAeroStruct analysis into a Dymos trajectory analysis model (@shamsheersc19) 23 | 24 | ## General solution approaches: 25 | 26 | Over the course of solving the problems we noticed a few general themes that were worth discussing in more detail. 27 | These topics involve general model building practices and concepts, and don't deal with specific solutions. 28 | 29 | * [Building an OpenVSP Component][openvsp] -- Google Colab workbook that builds an OpenVSP component 30 | * [You don't have to use a driver!][no-driver] -- Exploit the parts you like, leave the parts you don't! 31 | * [Sub-problems][subproblem] -- They are pretty handy in some situations! 32 | * [How big should I make my components?][how-big] --- How many lines of code should I aim for? 33 | * [Unsteady/transient analysis in OpenMDAO][unsteady] --- There is a right way, and some other right ways! 34 | 35 | 36 | [no-driver]: ./solution_approaches/no_driver.md 37 | [subproblem]: ./solution_approaches/sub_problems.md 38 | [unsteady]: ./solution_approaches/unsteady_analysis.md 39 | [how-big]: ./solution_approaches/how_big.md 40 | [openvsp]: ./solution_approaches/OpenVSP_OpenMDAO/OpenVSPwithOpenMDAO.ipynb 41 | 42 | [prob-evtol]: ./problems/evtol_trajectory 43 | [prob-nested]: ./problems/nested_optimization 44 | [prob-unsteady-vlm]: ./problems/unsteady_vlm 45 | [prob-cma_es]: ./problems/cma_es 46 | -------------------------------------------------------------------------------- /problems/cma_es/README.md: -------------------------------------------------------------------------------- 1 | # OpenMDAO Driver for CMA-ES optimizer 2 | 3 | The CMA-ES (Covariance Matrix Adaptation Evolution Strategy) is an evolutionary algorithm for difficult non-linear non-convex black-box optimization problems in continuous domain... 4 | 5 | * [INRIA reference site](http://cma.gforge.inria.fr) 6 | 7 | * [Python source code](https://github.com/CMA-ES/pycma) 8 | 9 | * [API entry point](http://cma.gforge.inria.fr/apidocs-pycma/cma.html) 10 | 11 | ## Request: 12 | Initiate an OpenMDAO driver for the CMA-ES optimizer. 13 | 14 | As an evolutionary algorithm, the driver should be something along the lines of the SimpleGADriver. 15 | We want to be able to run in parallel as well. 16 | 17 | Would it be interesting to subclass or identify some common base class for evolutionary optimizers yet? 18 | 19 | ## Solutions: 20 | 21 | 1. Custom run script using the native CMA-ES optimizer 22 | 23 | This approach involves wrapping an OpenMDAO Component or model with a function, so that it the CMAES library can be used as intended via it's functional interface. See [this Jupyter Notebook](cmaes.ipynb) for an example of how this is accomplished. 24 | 25 | 2. OpenMDAO Driver using CMA-ES optimizer 26 | 27 | As an initial step, an OpenMDAO [CMAESDriver](cmaes_driver.py) class was implemented based heavily on the existing [DifferentialEvolutionDriver](https://github.com/OpenMDAO/OpenMDAO/blob/master/openmdao/drivers/differential_evolution_driver.py). See [this Jupyter Notebook](CMAESDriver.ipynb) to see the driver in action. 28 | 29 | This implementation uses the simple [functional](http://cma.gforge.inria.fr/apidocs-pycma/cma.evolution_strategy.html#fmin) `cmaes` API when running in serial, and the [object-oriented](http://cma.gforge.inria.fr/apidocs-pycma/cma.interfaces.OOOptimizer.html) API for parallel execution. The object-oriented API is useful because it allows for custom logic after case generation. For parallel execution, we are able to use OpenMDAO's infrastructure for concurrent execution to evaluate the generated cases using all available processors. 30 | 31 | 3. Common base class for evolutionary optimizers 32 | 33 | In adapting the DifferentialEvolutionDriver to use the `cmaes` library, it was clear that there was a significant amount of common driver code that could be factored out such that different algorithms could more easily be implemented as an OpenMDAO driver. A [GenericDriver](generic_driver.py) base class was implemented with the common code that was factored out of the existing drivers. This driver uses a `DriverAlgorithm` object that contains the algorithm-specific logic. A rough re-implementation of both the Differential Evolution and CMAES drivers was done based on this generic driver base class (see [test_generic_devol](test/test_generic_devol_driver.py) and [test_generic_cmaes](test/test_generic_cmaes_driver.py)). 34 | 35 | More work is necessary to shake out bugs and corner cases, but the supposition in the proposal is shown to be correct. OpenMDAO's [SimpleGADriver](https://github.com/OpenMDAO/OpenMDAO/blob/master/openmdao/drivers/genetic_algorithm_driver.py) is also a candidate for refactoring to use the base class, and may clarify additional requirements. 36 | -------------------------------------------------------------------------------- /problems/cma_es/test/test_cmaes.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import cma 4 | 5 | import numpy as np 6 | import openmdao.api as om 7 | 8 | from openmdao.utils.assert_utils import assert_near_equal 9 | 10 | class CMAESTestCase(unittest.TestCase): 11 | 12 | def test_rosenbrock_om(self): 13 | # 14 | # test case from test_differential_evolution_driver 15 | # 16 | 17 | ORDER = 6 # dimension of problem 18 | 19 | span = 2 # upper and lower limits 20 | lower_bound = -span*np.ones(ORDER) 21 | upper_bound = span*np.ones(ORDER) 22 | 23 | class RosenbrockComp(om.ExplicitComponent): 24 | """ 25 | nth dimensional Rosenbrock function, array input and scalar output 26 | global minimum at f(1,1,1...) = 0 27 | """ 28 | def initialize(self): 29 | self.options.declare('order', types=int, default=2, desc='dimension of input.') 30 | 31 | def setup(self): 32 | self.add_input('x', np.zeros(self.options['order'])) 33 | self.add_output('y', 0.0) 34 | 35 | def compute(self, inputs, outputs): 36 | x = inputs['x'] 37 | 38 | n = len(x) 39 | assert (n > 1) 40 | s = 0 41 | for i in range(n - 1): 42 | s += 100 * (x[i + 1] - x[i] * x[i]) ** 2 + (1 - x[i]) ** 2 43 | 44 | outputs['y'] = s 45 | 46 | rosenbrock_model = om.Group() 47 | rosenbrock_model.add_subsystem('rosenbrock', RosenbrockComp(order=ORDER)) 48 | rosenbrock_model.add_design_var('rosenbrock.x', lower=lower_bound, upper=upper_bound) 49 | rosenbrock_model.add_objective('rosenbrock.y') 50 | 51 | p = om.Problem(model=rosenbrock_model, driver=om.DifferentialEvolutionDriver(max_gen=800)) 52 | p.setup() 53 | p.run_driver() 54 | 55 | assert_near_equal(p['rosenbrock.y'], 0.0, 1e-3) 56 | assert_near_equal(p['rosenbrock.x'], np.ones(ORDER), 1e-3) 57 | 58 | def test_rosenbrock_cma(self): 59 | # 60 | # test case from cma.test 61 | # 62 | rosenbrock = cma.ff.rosen 63 | 64 | ORDER = 6 # dimension of problem 65 | 66 | span = 2 # upper and lower limits 67 | lower_bound = -span*np.ones(ORDER) 68 | upper_bound = span*np.ones(ORDER) 69 | 70 | res = cma.fmin(rosenbrock, [-1]*ORDER, 0.01, 71 | options={'ftarget':1e-6, 'bounds':[lower_bound, upper_bound], 72 | 'verb_time':0, 'verb_disp':0, 'seed':3}) 73 | 74 | # - res[0] (xopt) -- best evaluated solution 75 | # - res[1] (fopt) -- respective function value 76 | # - res[2] (evalsopt) -- respective number of function evaluations 77 | # - res[3] (evals) -- number of overall conducted objective function evaluations 78 | # - res[4] (iterations) -- number of overall conducted iterations 79 | # - res[5] (xmean) -- mean of the final sample distribution 80 | # - res[6] (stds) -- effective stds of the final sample distribution 81 | # - res[-3] (stop) -- termination condition(s) in a dictionary 82 | # - res[-2] (cmaes) -- class `CMAEvolutionStrategy` instance 83 | # - res[-1] (logger) -- class `CMADataLogger` instance 84 | 85 | xopt = res[0] 86 | fopt = res[1] 87 | 88 | assert_near_equal(fopt, 0.0, 1e-3) 89 | assert_near_equal(xopt, np.ones(ORDER), 1e-3) 90 | 91 | es = cma.CMAEvolutionStrategy([1]*ORDER, 1).optimize(rosenbrock) 92 | 93 | assert_near_equal(es.result.fbest, 0.0, 1e-3) 94 | assert_near_equal(es.result.xbest, np.ones(ORDER), 1e-3) 95 | -------------------------------------------------------------------------------- /problems/evtol_trajectory/evtol_dymos/evtol_dymos.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | import dymos as dm 3 | import numpy as np 4 | 5 | import sys 6 | 7 | sys.path.insert(0, "../ode") 8 | 9 | from evtol_dynamics_comp import Dynamics 10 | 11 | import verify_data 12 | 13 | if __name__ == '__main__': 14 | input_arg_1 = 0.0 15 | 16 | input_arg_2 = 'ns' 17 | 18 | # Some specifications 19 | prop_rad = 0.75 20 | wing_S = 9. 21 | wing_span = 6. 22 | num_blades = 3. 23 | blade_chord = 0.1 24 | num_props = 8 25 | 26 | # User-specified input dictionary 27 | input_dict = {'T_guess': 9.8 * 725 * 1.2, # initial thrust guess 28 | 'x_dot_initial': 0., # initial horizontal speed 29 | 'y_dot_initial': 0.01, # initial vertical speed 30 | 'y_initial': 0.01, # initial vertical displacement 31 | 'A_disk': np.pi * prop_rad ** 2 * num_props, # total propeller disk area 32 | 'AR': wing_span ** 2 / (0.5 * wing_S), # aspect ratio of each wing 33 | 'e': 0.68, # span efficiency factor of each wing 34 | 't_over_c': 0.12, # airfoil thickness-to-chord ratio 35 | 'S': wing_S, # total wing reference area 36 | 'CD0': 0.35 / wing_S, # coefficient of drag of the fuselage, gear, etc. 37 | 'm': 725., # mass of aircraft 38 | 'a0': 5.9, # airfoil lift-curve slope 39 | 'alpha_stall': 15. / 180. * np.pi, # wing stall angle 40 | 'rho': 1.225, # air density 41 | 'induced_velocity_factor': int(input_arg_1) / 100., # induced-velocity factor 42 | 'stall_option': input_arg_2, # stall option: 's' allows stall, 'ns' does not 43 | 'R': prop_rad, # propeller radius 44 | 'solidity': num_blades * blade_chord / np.pi / prop_rad, # solidity 45 | 'omega': 136. / prop_rad, # angular rotation rate 46 | 'prop_CD0': 0.012, # CD0 for prop profile power 47 | 'k_elec': 0.9, # electrical and mechanical losses factor 48 | 'k_ind': 1.2, # induced-losses factor 49 | 'nB': num_blades, # number of blades per propeller 50 | 'bc': blade_chord, # representative blade chord 51 | 'n_props': num_props # number of propellers 52 | } 53 | 54 | p = om.Problem() 55 | 56 | traj = dm.Trajectory() 57 | p.model.add_subsystem('traj', traj) 58 | 59 | phase = dm.Phase(transcription=dm.GaussLobatto(num_segments=10, order=3, solve_segments=False, 60 | compressed=False), 61 | ode_class=Dynamics, 62 | ode_init_kwargs={'input_dict': input_dict}) 63 | 64 | traj.add_phase('phase0', phase) 65 | 66 | phase.set_time_options(fix_initial=True, duration_bounds=(5, 60), duration_ref=30) 67 | phase.add_state('x', fix_initial=True, rate_source='x_dot', ref0=0, ref=900, defect_ref=100) 68 | phase.add_state('y', fix_initial=True, rate_source='y_dot', ref0=0, ref=300, defect_ref=300) 69 | phase.add_state('vx', fix_initial=True, rate_source='a_x', ref0=0, ref=10) 70 | phase.add_state('vy', fix_initial=True, rate_source='a_y', ref0=0, ref=10) 71 | phase.add_state('energy', fix_initial=True, rate_source='energy_dot', ref0=0, ref=1E7, defect_ref=1E5) 72 | 73 | phase.add_control('power', lower=1e3, upper=311000, ref0=1e3, ref=311000, rate_continuity=False) 74 | phase.add_control('theta', lower=0., upper=3 * np.pi / 4, ref0=0, ref=3 * np.pi / 4, 75 | rate_continuity=False) 76 | 77 | phase.add_timeseries_output(['CL', 'CD']) 78 | 79 | # Objective 80 | phase.add_objective('energy', loc='final', ref0=0, ref=1E7) 81 | 82 | # Boundary Constraints 83 | phase.add_boundary_constraint('y', loc='final', lower=305, 84 | ref=100) # Constraint for the final vertical displacement 85 | phase.add_boundary_constraint('x', loc='final', equals=900, 86 | ref=100) # Constraint for the final horizontal displacement 87 | phase.add_boundary_constraint('x_dot', loc='final', equals=67., 88 | ref=100) # Constraint for the final horizontal speed 89 | 90 | # Path Constraints 91 | phase.add_path_constraint('y', lower=0., upper=305, 92 | ref=300) # Constraint for the minimum vertical displacement 93 | phase.add_path_constraint('acc', upper=0.3, 94 | ref=1.0) # Constraint for the acceleration magnitude 95 | phase.add_path_constraint('aoa', lower=-np.radians(15), upper=np.radians(15), ref0=-np.radians(15), 96 | ref=np.radians(15)) # Constraint for the angle of attack 97 | phase.add_path_constraint('thrust', lower=10, ref0=10, 98 | ref=100) # Constraint for the thrust magnitude 99 | 100 | # # Setup the driver 101 | p.driver = om.pyOptSparseDriver() 102 | 103 | # p.driver.options['optimizer'] = 'SNOPT' 104 | # p.driver.opt_settings['Major optimality tolerance'] = 1e-4 105 | # p.driver.opt_settings['Major feasibility tolerance'] = 1e-6 106 | # p.driver.opt_settings['Major iterations limit'] = 1000 107 | # p.driver.opt_settings['Minor iterations limit'] = 100_000_000 108 | # p.driver.opt_settings['iSumm'] = 6 109 | 110 | p.driver.options['optimizer'] = 'IPOPT' 111 | p.driver.opt_settings['max_iter'] = 1000 112 | p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas' 113 | p.driver.opt_settings['print_level'] = 5 114 | p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based' 115 | p.driver.opt_settings['tol'] = 5.0E-5 116 | 117 | p.driver.declare_coloring(tol=1.0E-8) 118 | 119 | p.setup() 120 | 121 | p.set_val('traj.phase0.t_initial', 0.0) 122 | p.set_val('traj.phase0.t_duration', 30) 123 | p.set_val('traj.phase0.states:x', phase.interpolate(ys=[0, 900], nodes='state_input')) 124 | p.set_val('traj.phase0.states:y', phase.interpolate(ys=[0.01, 300], nodes='state_input')) 125 | p.set_val('traj.phase0.states:vx', phase.interpolate(ys=[0, 60], nodes='state_input')) 126 | p.set_val('traj.phase0.states:vy', phase.interpolate(ys=[0.01, 10], nodes='state_input')) 127 | p.set_val('traj.phase0.states:energy', phase.interpolate(ys=[0, 1E7], nodes='state_input')) 128 | 129 | p.set_val('traj.phase0.controls:power', phase.interpolate(xs=np.linspace(0, 28.368, 500), 130 | ys=verify_data.powers.ravel(), 131 | nodes='control_input')) 132 | p.set_val('traj.phase0.controls:theta', phase.interpolate(xs=np.linspace(0, 28.368, 500), 133 | ys=verify_data.thetas.ravel(), 134 | nodes='control_input')) 135 | 136 | p.set_val('traj.phase0.controls:power', 200000.0) 137 | p.set_val('traj.phase0.controls:theta', phase.interpolate(ys=[0.001, np.radians(85)], nodes='control_input')) 138 | 139 | dm.run_problem(p, run_driver=True, simulate=True) 140 | -------------------------------------------------------------------------------- /problems/evtol_trajectory/evtol_dymos/evtol_dymos_shooting.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | import dymos as dm 3 | import numpy as np 4 | 5 | import sys 6 | 7 | sys.path.insert(0, "../ode") 8 | 9 | from evtol_dynamics_comp_vectorized import Dynamics as DynamicsVectorized 10 | 11 | import verify_data 12 | 13 | if __name__ == '__main__': 14 | input_arg_1 = 0.0 15 | 16 | input_arg_2 = 'ns' 17 | 18 | # Some specifications 19 | prop_rad = 0.75 20 | wing_S = 9. 21 | wing_span = 6. 22 | num_blades = 3. 23 | blade_chord = 0.1 24 | num_props = 8 25 | 26 | # User-specified input dictionary 27 | input_dict = {'T_guess': 9.8 * 725 * 1.2, # initial thrust guess 28 | 'x_dot_initial': 0., # initial horizontal speed 29 | 'y_dot_initial': 0.01, # initial vertical speed 30 | 'y_initial': 0.01, # initial vertical displacement 31 | 'A_disk': np.pi * prop_rad ** 2 * num_props, # total propeller disk area 32 | 'AR': wing_span ** 2 / (0.5 * wing_S), # aspect ratio of each wing 33 | 'e': 0.68, # span efficiency factor of each wing 34 | 't_over_c': 0.12, # airfoil thickness-to-chord ratio 35 | 'S': wing_S, # total wing reference area 36 | 'CD0': 0.35 / wing_S, # coefficient of drag of the fuselage, gear, etc. 37 | 'm': 725., # mass of aircraft 38 | 'a0': 5.9, # airfoil lift-curve slope 39 | 'alpha_stall': 15. / 180. * np.pi, # wing stall angle 40 | 'rho': 1.225, # air density 41 | 'induced_velocity_factor': int(input_arg_1) / 100., # induced-velocity factor 42 | 'stall_option': input_arg_2, # stall option: 's' allows stall, 'ns' does not 43 | 'R': prop_rad, # propeller radius 44 | 'solidity': num_blades * blade_chord / np.pi / prop_rad, # solidity 45 | 'omega': 136. / prop_rad, # angular rotation rate 46 | 'prop_CD0': 0.012, # CD0 for prop profile power 47 | 'k_elec': 0.9, # electrical and mechanical losses factor 48 | 'k_ind': 1.2, # induced-losses factor 49 | 'nB': num_blades, # number of blades per propeller 50 | 'bc': blade_chord, # representative blade chord 51 | 'n_props': num_props # number of propellers 52 | } 53 | 54 | p = om.Problem() 55 | 56 | traj = dm.Trajectory() 57 | p.model.add_subsystem('traj', traj) 58 | 59 | phase = dm.Phase(transcription=dm.GaussLobatto(num_segments=10, order=3, solve_segments=True, 60 | compressed=True), 61 | ode_class=DynamicsVectorized, 62 | ode_init_kwargs={'input_dict': input_dict}) 63 | 64 | traj.add_phase('phase0', phase) 65 | 66 | phase.set_time_options(fix_initial=True, duration_bounds=(5, 60), duration_ref=30) 67 | phase.add_state('x', fix_initial=True, rate_source='x_dot', ref0=0, ref=900, defect_ref=100) 68 | phase.add_state('y', fix_initial=True, rate_source='y_dot', ref0=0, ref=300, defect_ref=300) 69 | phase.add_state('vx', fix_initial=True, rate_source='a_x', ref0=0, ref=10) 70 | phase.add_state('vy', fix_initial=True, rate_source='a_y', ref0=0, ref=10) 71 | phase.add_state('energy', fix_initial=True, rate_source='energy_dot', ref0=0, ref=1E7, defect_ref=1E5) 72 | 73 | phase.add_control('power', lower=1e3, upper=311000, ref0=1e3, ref=311000, rate_continuity=False) 74 | phase.add_control('theta', lower=0., upper=3 * np.pi / 4, ref0=0, ref=3 * np.pi / 4, 75 | rate_continuity=False) 76 | 77 | phase.add_timeseries_output(['CL', 'CD']) 78 | 79 | # Objective 80 | phase.add_objective('energy', loc='final', ref0=0, ref=1E7) 81 | 82 | # Boundary Constraints 83 | phase.add_boundary_constraint('y', loc='final', lower=305, 84 | ref=100) # Constraint for the final vertical displacement 85 | phase.add_boundary_constraint('x', loc='final', equals=900, 86 | ref=100) # Constraint for the final horizontal displacement 87 | phase.add_boundary_constraint('x_dot', loc='final', equals=67., 88 | ref=100) # Constraint for the final horizontal speed 89 | 90 | # Path Constraints 91 | phase.add_path_constraint('y', lower=0., upper=305, 92 | ref=300) # Constraint for the minimum vertical displacement 93 | phase.add_path_constraint('acc', upper=0.3, 94 | ref=1.0) # Constraint for the acceleration magnitude 95 | phase.add_path_constraint('aoa', lower=-np.radians(15), upper=np.radians(15), ref0=-np.radians(15), 96 | ref=np.radians(15)) # Constraint for the angle of attack 97 | phase.add_path_constraint('thrust', lower=10, ref0=10, 98 | ref=100) # Constraint for the thrust magnitude 99 | 100 | # # Setup the driver 101 | p.driver = om.pyOptSparseDriver() 102 | 103 | # p.driver.options['optimizer'] = 'SNOPT' 104 | # p.driver.opt_settings['Major optimality tolerance'] = 1e-4 105 | # p.driver.opt_settings['Major feasibility tolerance'] = 1e-6 106 | # p.driver.opt_settings['Major iterations limit'] = 1000 107 | # p.driver.opt_settings['Minor iterations limit'] = 100_000_000 108 | # p.driver.opt_settings['iSumm'] = 6 109 | 110 | p.driver.options['optimizer'] = 'IPOPT' 111 | p.driver.opt_settings['max_iter'] = 1000 112 | p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas' 113 | p.driver.opt_settings['print_level'] = 5 114 | p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based' 115 | p.driver.opt_settings['tol'] = 5.0E-5 116 | 117 | p.driver.declare_coloring(tol=1.0E-8) 118 | 119 | p.setup() 120 | 121 | p.set_val('traj.phase0.t_initial', 0.0) 122 | p.set_val('traj.phase0.t_duration', 30) 123 | p.set_val('traj.phase0.states:x', phase.interpolate(ys=[0, 900], nodes='state_input')) 124 | p.set_val('traj.phase0.states:y', phase.interpolate(ys=[0.01, 300], nodes='state_input')) 125 | p.set_val('traj.phase0.states:vx', phase.interpolate(ys=[0, 60], nodes='state_input')) 126 | p.set_val('traj.phase0.states:vy', phase.interpolate(ys=[0.01, 10], nodes='state_input')) 127 | p.set_val('traj.phase0.states:energy', phase.interpolate(ys=[0, 1E7], nodes='state_input')) 128 | 129 | p.set_val('traj.phase0.controls:power', phase.interpolate(xs=np.linspace(0, 28.368, 500), 130 | ys=verify_data.powers.ravel(), 131 | nodes='control_input')) 132 | p.set_val('traj.phase0.controls:theta', phase.interpolate(xs=np.linspace(0, 28.368, 500), 133 | ys=verify_data.thetas.ravel(), 134 | nodes='control_input')) 135 | 136 | p.set_val('traj.phase0.controls:power', 200000.0) 137 | p.set_val('traj.phase0.controls:theta', phase.interpolate(ys=[0.001, np.radians(85)], nodes='control_input')) 138 | 139 | dm.run_problem(p, run_driver=True, simulate=True) 140 | -------------------------------------------------------------------------------- /problems/evtol_trajectory/evtol_dymos/evtol_dymos_vectorized.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | import dymos as dm 3 | import numpy as np 4 | 5 | import sys 6 | 7 | sys.path.insert(0, "../ode") 8 | 9 | from evtol_dynamics_comp_vectorized import Dynamics as DynamicsVectorized 10 | 11 | import verify_data 12 | 13 | if __name__ == '__main__': 14 | input_arg_1 = 0.0 15 | 16 | input_arg_2 = 'ns' 17 | 18 | # Some specifications 19 | prop_rad = 0.75 20 | wing_S = 9. 21 | wing_span = 6. 22 | num_blades = 3. 23 | blade_chord = 0.1 24 | num_props = 8 25 | 26 | # User-specified input dictionary 27 | input_dict = {'T_guess': 9.8 * 725 * 1.2, # initial thrust guess 28 | 'x_dot_initial': 0., # initial horizontal speed 29 | 'y_dot_initial': 0.01, # initial vertical speed 30 | 'y_initial': 0.01, # initial vertical displacement 31 | 'A_disk': np.pi * prop_rad ** 2 * num_props, # total propeller disk area 32 | 'AR': wing_span ** 2 / (0.5 * wing_S), # aspect ratio of each wing 33 | 'e': 0.68, # span efficiency factor of each wing 34 | 't_over_c': 0.12, # airfoil thickness-to-chord ratio 35 | 'S': wing_S, # total wing reference area 36 | 'CD0': 0.35 / wing_S, # coefficient of drag of the fuselage, gear, etc. 37 | 'm': 725., # mass of aircraft 38 | 'a0': 5.9, # airfoil lift-curve slope 39 | 'alpha_stall': 15. / 180. * np.pi, # wing stall angle 40 | 'rho': 1.225, # air density 41 | 'induced_velocity_factor': int(input_arg_1) / 100., # induced-velocity factor 42 | 'stall_option': input_arg_2, # stall option: 's' allows stall, 'ns' does not 43 | 'R': prop_rad, # propeller radius 44 | 'solidity': num_blades * blade_chord / np.pi / prop_rad, # solidity 45 | 'omega': 136. / prop_rad, # angular rotation rate 46 | 'prop_CD0': 0.012, # CD0 for prop profile power 47 | 'k_elec': 0.9, # electrical and mechanical losses factor 48 | 'k_ind': 1.2, # induced-losses factor 49 | 'nB': num_blades, # number of blades per propeller 50 | 'bc': blade_chord, # representative blade chord 51 | 'n_props': num_props # number of propellers 52 | } 53 | 54 | p = om.Problem() 55 | 56 | traj = dm.Trajectory() 57 | p.model.add_subsystem('traj', traj) 58 | 59 | phase = dm.Phase(transcription=dm.GaussLobatto(num_segments=10, order=3, solve_segments=False, 60 | compressed=False), 61 | ode_class=DynamicsVectorized, 62 | ode_init_kwargs={'input_dict': input_dict}) 63 | 64 | traj.add_phase('phase0', phase) 65 | 66 | phase.set_time_options(fix_initial=True, duration_bounds=(5, 60), duration_ref=30) 67 | phase.add_state('x', fix_initial=True, rate_source='x_dot', ref0=0, ref=900, defect_ref=100) 68 | phase.add_state('y', fix_initial=True, rate_source='y_dot', ref0=0, ref=300, defect_ref=300) 69 | phase.add_state('vx', fix_initial=True, rate_source='a_x', ref0=0, ref=10) 70 | phase.add_state('vy', fix_initial=True, rate_source='a_y', ref0=0, ref=10) 71 | phase.add_state('energy', fix_initial=True, rate_source='energy_dot', ref0=0, ref=1E7, defect_ref=1E5) 72 | 73 | phase.add_control('power', lower=1e3, upper=311000, ref0=1e3, ref=311000, rate_continuity=False) 74 | phase.add_control('theta', lower=0., upper=3 * np.pi / 4, ref0=0, ref=3 * np.pi / 4, 75 | rate_continuity=False) 76 | 77 | phase.add_timeseries_output(['CL', 'CD']) 78 | 79 | # Objective 80 | phase.add_objective('energy', loc='final', ref0=0, ref=1E7) 81 | 82 | # Boundary Constraints 83 | phase.add_boundary_constraint('y', loc='final', lower=305, 84 | ref=100) # Constraint for the final vertical displacement 85 | phase.add_boundary_constraint('x', loc='final', equals=900, 86 | ref=100) # Constraint for the final horizontal displacement 87 | phase.add_boundary_constraint('x_dot', loc='final', equals=67., 88 | ref=100) # Constraint for the final horizontal speed 89 | 90 | # Path Constraints 91 | phase.add_path_constraint('y', lower=0., upper=305, 92 | ref=300) # Constraint for the minimum vertical displacement 93 | phase.add_path_constraint('acc', upper=0.3, 94 | ref=1.0) # Constraint for the acceleration magnitude 95 | phase.add_path_constraint('aoa', lower=-np.radians(15), upper=np.radians(15), ref0=-np.radians(15), 96 | ref=np.radians(15)) # Constraint for the angle of attack 97 | phase.add_path_constraint('thrust', lower=10, ref0=10, 98 | ref=100) # Constraint for the thrust magnitude 99 | 100 | # # Setup the driver 101 | p.driver = om.pyOptSparseDriver() 102 | 103 | # p.driver.options['optimizer'] = 'SNOPT' 104 | # p.driver.opt_settings['Major optimality tolerance'] = 1e-4 105 | # p.driver.opt_settings['Major feasibility tolerance'] = 1e-6 106 | # p.driver.opt_settings['Major iterations limit'] = 1000 107 | # p.driver.opt_settings['Minor iterations limit'] = 100_000_000 108 | # p.driver.opt_settings['iSumm'] = 6 109 | 110 | p.driver.options['optimizer'] = 'IPOPT' 111 | p.driver.opt_settings['max_iter'] = 1000 112 | p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas' 113 | p.driver.opt_settings['print_level'] = 5 114 | p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based' 115 | p.driver.opt_settings['tol'] = 5.0E-5 116 | 117 | p.driver.declare_coloring(tol=1.0E-8) 118 | 119 | p.setup() 120 | 121 | p.set_val('traj.phase0.t_initial', 0.0) 122 | p.set_val('traj.phase0.t_duration', 30) 123 | p.set_val('traj.phase0.states:x', phase.interpolate(ys=[0, 900], nodes='state_input')) 124 | p.set_val('traj.phase0.states:y', phase.interpolate(ys=[0.01, 300], nodes='state_input')) 125 | p.set_val('traj.phase0.states:vx', phase.interpolate(ys=[0, 60], nodes='state_input')) 126 | p.set_val('traj.phase0.states:vy', phase.interpolate(ys=[0.01, 10], nodes='state_input')) 127 | p.set_val('traj.phase0.states:energy', phase.interpolate(ys=[0, 1E7], nodes='state_input')) 128 | 129 | p.set_val('traj.phase0.controls:power', phase.interpolate(xs=np.linspace(0, 28.368, 500), 130 | ys=verify_data.powers.ravel(), 131 | nodes='control_input')) 132 | p.set_val('traj.phase0.controls:theta', phase.interpolate(xs=np.linspace(0, 28.368, 500), 133 | ys=verify_data.thetas.ravel(), 134 | nodes='control_input')) 135 | 136 | p.set_val('traj.phase0.controls:power', 200000.0) 137 | p.set_val('traj.phase0.controls:theta', phase.interpolate(ys=[0.001, np.radians(85)], nodes='control_input')) 138 | 139 | dm.run_problem(p, run_driver=True, simulate=True) 140 | -------------------------------------------------------------------------------- /problems/evtol_trajectory/evtol_dymos/evtol_dymos_vectorized_shooting.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | import dymos as dm 3 | import numpy as np 4 | 5 | import sys 6 | 7 | sys.path.insert(0, "../ode") 8 | 9 | from evtol_dynamics_comp_vectorized import Dynamics as DynamicsVectorized 10 | 11 | import verify_data 12 | 13 | if __name__ == '__main__': 14 | input_arg_1 = 0.0 15 | 16 | input_arg_2 = 'ns' 17 | 18 | # Some specifications 19 | prop_rad = 0.75 20 | wing_S = 9. 21 | wing_span = 6. 22 | num_blades = 3. 23 | blade_chord = 0.1 24 | num_props = 8 25 | 26 | # User-specified input dictionary 27 | input_dict = {'T_guess': 9.8 * 725 * 1.2, # initial thrust guess 28 | 'x_dot_initial': 0., # initial horizontal speed 29 | 'y_dot_initial': 0.01, # initial vertical speed 30 | 'y_initial': 0.01, # initial vertical displacement 31 | 'A_disk': np.pi * prop_rad ** 2 * num_props, # total propeller disk area 32 | 'AR': wing_span ** 2 / (0.5 * wing_S), # aspect ratio of each wing 33 | 'e': 0.68, # span efficiency factor of each wing 34 | 't_over_c': 0.12, # airfoil thickness-to-chord ratio 35 | 'S': wing_S, # total wing reference area 36 | 'CD0': 0.35 / wing_S, # coefficient of drag of the fuselage, gear, etc. 37 | 'm': 725., # mass of aircraft 38 | 'a0': 5.9, # airfoil lift-curve slope 39 | 'alpha_stall': 15. / 180. * np.pi, # wing stall angle 40 | 'rho': 1.225, # air density 41 | 'induced_velocity_factor': int(input_arg_1) / 100., # induced-velocity factor 42 | 'stall_option': input_arg_2, # stall option: 's' allows stall, 'ns' does not 43 | 'R': prop_rad, # propeller radius 44 | 'solidity': num_blades * blade_chord / np.pi / prop_rad, # solidity 45 | 'omega': 136. / prop_rad, # angular rotation rate 46 | 'prop_CD0': 0.012, # CD0 for prop profile power 47 | 'k_elec': 0.9, # electrical and mechanical losses factor 48 | 'k_ind': 1.2, # induced-losses factor 49 | 'nB': num_blades, # number of blades per propeller 50 | 'bc': blade_chord, # representative blade chord 51 | 'n_props': num_props # number of propellers 52 | } 53 | 54 | p = om.Problem() 55 | 56 | traj = dm.Trajectory() 57 | p.model.add_subsystem('traj', traj) 58 | 59 | phase = dm.Phase(transcription=dm.GaussLobatto(num_segments=10, order=3, solve_segments=True, 60 | compressed=True), 61 | ode_class=DynamicsVectorized, 62 | ode_init_kwargs={'input_dict': input_dict}) 63 | 64 | traj.add_phase('phase0', phase) 65 | 66 | phase.set_time_options(fix_initial=True, duration_bounds=(5, 60), duration_ref=30) 67 | phase.add_state('x', fix_initial=True, rate_source='x_dot', ref0=0, ref=900, defect_ref=100) 68 | phase.add_state('y', fix_initial=True, rate_source='y_dot', ref0=0, ref=300, defect_ref=300) 69 | phase.add_state('vx', fix_initial=True, rate_source='a_x', ref0=0, ref=10) 70 | phase.add_state('vy', fix_initial=True, rate_source='a_y', ref0=0, ref=10) 71 | phase.add_state('energy', fix_initial=True, rate_source='energy_dot', ref0=0, ref=1E7, defect_ref=1E5) 72 | 73 | phase.add_control('power', lower=1e3, upper=311000, ref0=1e3, ref=311000, rate_continuity=False) 74 | phase.add_control('theta', lower=0., upper=3 * np.pi / 4, ref0=0, ref=3 * np.pi / 4, 75 | rate_continuity=False) 76 | 77 | phase.add_timeseries_output(['CL', 'CD']) 78 | 79 | # Objective 80 | phase.add_objective('energy', loc='final', ref0=0, ref=1E7) 81 | 82 | # Boundary Constraints 83 | phase.add_boundary_constraint('y', loc='final', lower=305, 84 | ref=100) # Constraint for the final vertical displacement 85 | phase.add_boundary_constraint('x', loc='final', equals=900, 86 | ref=100) # Constraint for the final horizontal displacement 87 | phase.add_boundary_constraint('x_dot', loc='final', equals=67., 88 | ref=100) # Constraint for the final horizontal speed 89 | 90 | # Path Constraints 91 | phase.add_path_constraint('y', lower=0., upper=305, 92 | ref=300) # Constraint for the minimum vertical displacement 93 | phase.add_path_constraint('acc', upper=0.3, 94 | ref=1.0) # Constraint for the acceleration magnitude 95 | phase.add_path_constraint('aoa', lower=-np.radians(15), upper=np.radians(15), ref0=-np.radians(15), 96 | ref=np.radians(15)) # Constraint for the angle of attack 97 | phase.add_path_constraint('thrust', lower=10, ref0=10, 98 | ref=100) # Constraint for the thrust magnitude 99 | 100 | # # Setup the driver 101 | p.driver = om.pyOptSparseDriver() 102 | 103 | # p.driver.options['optimizer'] = 'SNOPT' 104 | # p.driver.opt_settings['Major optimality tolerance'] = 1e-4 105 | # p.driver.opt_settings['Major feasibility tolerance'] = 1e-6 106 | # p.driver.opt_settings['Major iterations limit'] = 1000 107 | # p.driver.opt_settings['Minor iterations limit'] = 100_000_000 108 | # p.driver.opt_settings['iSumm'] = 6 109 | 110 | p.driver.options['optimizer'] = 'IPOPT' 111 | p.driver.opt_settings['max_iter'] = 1000 112 | p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas' 113 | p.driver.opt_settings['print_level'] = 5 114 | p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based' 115 | p.driver.opt_settings['tol'] = 5.0E-5 116 | 117 | p.driver.declare_coloring(tol=1.0E-8) 118 | 119 | p.setup() 120 | 121 | p.set_val('traj.phase0.t_initial', 0.0) 122 | p.set_val('traj.phase0.t_duration', 30) 123 | p.set_val('traj.phase0.states:x', phase.interpolate(ys=[0, 900], nodes='state_input')) 124 | p.set_val('traj.phase0.states:y', phase.interpolate(ys=[0.01, 300], nodes='state_input')) 125 | p.set_val('traj.phase0.states:vx', phase.interpolate(ys=[0, 60], nodes='state_input')) 126 | p.set_val('traj.phase0.states:vy', phase.interpolate(ys=[0.01, 10], nodes='state_input')) 127 | p.set_val('traj.phase0.states:energy', phase.interpolate(ys=[0, 1E7], nodes='state_input')) 128 | 129 | p.set_val('traj.phase0.controls:power', phase.interpolate(xs=np.linspace(0, 28.368, 500), 130 | ys=verify_data.powers.ravel(), 131 | nodes='control_input')) 132 | p.set_val('traj.phase0.controls:theta', phase.interpolate(xs=np.linspace(0, 28.368, 500), 133 | ys=verify_data.thetas.ravel(), 134 | nodes='control_input')) 135 | 136 | p.set_val('traj.phase0.controls:power', 200000.0) 137 | p.set_val('traj.phase0.controls:theta', phase.interpolate(ys=[0.001, np.radians(85)], nodes='control_input')) 138 | 139 | dm.run_problem(p, run_driver=True, simulate=True) 140 | -------------------------------------------------------------------------------- /problems/evtol_trajectory/evtol_dymos/plot_results.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | import matplotlib as mpl 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | 6 | import sys 7 | sys.path.insert(0, "../ode") 8 | 9 | import verify_data 10 | 11 | mpl.rcParams['lines.markersize'] = 4 12 | 13 | sol_case = om.CaseReader('dymos_solution.db').get_case('final') 14 | sim_case = om.CaseReader('dymos_simulation.db').get_case(-1) 15 | 16 | sol = {} 17 | sol['time'] = sol_case.get_val('traj.phase0.timeseries.time') 18 | sol['power'] = sol_case.get_val('traj.phase0.timeseries.controls:power') 19 | sol['theta'] = sol_case.get_val('traj.phase0.timeseries.controls:theta') 20 | sol['x'] = sol_case.get_val('traj.phase0.timeseries.states:x') 21 | sol['y'] = sol_case.get_val('traj.phase0.timeseries.states:y') 22 | sol['vx'] = sol_case.get_val('traj.phase0.timeseries.states:vx') 23 | sol['vy'] = sol_case.get_val('traj.phase0.timeseries.states:vy') 24 | sol['energy'] = sol_case.get_val('traj.phase0.timeseries.states:energy') 25 | sol['acc'] = sol_case.get_val('traj.phase0.timeseries.acc') 26 | sol['aoa'] = sol_case.get_val('traj.phase0.timeseries.aoa') 27 | sol['thrust'] = sol_case.get_val('traj.phase0.timeseries.thrust') 28 | sol['cl'] = sol_case.get_val('traj.phase0.timeseries.CL') 29 | sol['cd'] = sol_case.get_val('traj.phase0.timeseries.CD') 30 | 31 | sim = {} 32 | sim['time'] = sim_case.get_val('traj.phase0.timeseries.time') 33 | sim['power'] = sim_case.get_val('traj.phase0.timeseries.controls:power') 34 | sim['theta'] = sim_case.get_val('traj.phase0.timeseries.controls:theta') 35 | sim['x'] = sim_case.get_val('traj.phase0.timeseries.states:x') 36 | sim['y'] = sim_case.get_val('traj.phase0.timeseries.states:y') 37 | sim['vx'] = sim_case.get_val('traj.phase0.timeseries.states:vx') 38 | sim['vy'] = sim_case.get_val('traj.phase0.timeseries.states:vy') 39 | sim['energy'] = sim_case.get_val('traj.phase0.timeseries.states:energy') 40 | sim['acc'] = sim_case.get_val('traj.phase0.timeseries.acc') 41 | sim['aoa'] = sim_case.get_val('traj.phase0.timeseries.aoa') 42 | sim['thrust'] = sim_case.get_val('traj.phase0.timeseries.thrust') 43 | sim['cl'] = sim_case.get_val('traj.phase0.timeseries.CL') 44 | sim['cd'] = sim_case.get_val('traj.phase0.timeseries.CD') 45 | 46 | verif = {} 47 | verif['theta'] = np.load('../original/thetas.npy') 48 | verif['power'] = np.load('../original/powers.npy') 49 | steps = verify_data.thetas.size 50 | verif['time'] = np.linspace(0, verify_data.flight_time, steps) 51 | verif['x'] = np.load('../original/x.npy') 52 | verif['y'] = np.load('../original/y.npy') 53 | verif['vx'] = np.load('../original/x_dots.npy') 54 | verif['vy'] = np.load('../original/y_dots.npy') 55 | verif['energy'] = np.load('../original/energy.npy')[:-1] 56 | verif['ax'] = np.load('../original/a_x.npy') 57 | verif['ay'] = np.load('../original/a_y.npy') 58 | verif['acc'] = np.load('../original/acc.npy') 59 | verif['aoa'] = np.load('../original/aoa.npy') 60 | verif['thrust'] = verify_data.thrusts[:-1] 61 | verif['cl'] = verify_data.CL[:-1] 62 | verif['cd'] = verify_data.CD[:-1] 63 | 64 | print(sol['energy']) 65 | print(sim['energy']) 66 | print(verif['energy']) 67 | 68 | def plot_on_axes(x, y, axes, upper=None, lower=None): 69 | axes.plot(sol[x], sol[y].ravel(), 'o') 70 | axes.plot(sim[x], sim[y].ravel(), '-') 71 | axes.plot(verif[x], verif[y].ravel(), 'k:') 72 | if upper is not None: 73 | axes.plot([0, 30], [upper, upper], 'r--') 74 | if lower is not None: 75 | axes.plot([0, 30], [lower, lower], 'r--') 76 | 77 | axes.set_xlabel(x) 78 | axes.set_ylabel(y) 79 | 80 | plt.style.use('seaborn-whitegrid') 81 | fig, axes = plt.subplots(3, 3, figsize=(15,6)) 82 | 83 | axes = np.reshape(axes, (9,)) 84 | 85 | plot_on_axes('time', 'power', axes[0]) 86 | plot_on_axes('time', 'theta', axes[1]) 87 | plot_on_axes('x', 'y', axes[2]) 88 | plot_on_axes('time', 'energy', axes[3]) 89 | plot_on_axes('time', 'aoa', axes[4], upper=np.radians(15)) 90 | plot_on_axes('time', 'thrust', axes[5]) 91 | plot_on_axes('time', 'acc', axes[6], upper=0.3) 92 | plot_on_axes('time', 'cl', axes[7]) 93 | plot_on_axes('time', 'cd', axes[8]) 94 | 95 | plt.tight_layout() 96 | 97 | plt.savefig('results.png') 98 | 99 | plt.show() -------------------------------------------------------------------------------- /problems/evtol_trajectory/evtol_dymos/restart.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/evtol_dymos/restart.sql -------------------------------------------------------------------------------- /problems/evtol_trajectory/evtol_dymos/results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/evtol_dymos/results.png -------------------------------------------------------------------------------- /problems/evtol_trajectory/evtol_explicit_time_integraiton/time_step_rk4.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../ode") 4 | 5 | import numpy as np 6 | from scipy.interpolate import interp1d 7 | from scipy.integrate import solve_ivp 8 | 9 | import openmdao.api as om 10 | 11 | from evtol_dynamics_comp import Dynamics 12 | 13 | from bsplines_comp import BsplinesComp 14 | 15 | # The first argument is the induced-velocity factor in percentage (e.g., 0, 50, 100). 16 | input_arg_1 = 0 17 | # The second is the stall option: 's' allows stall or 'ns' does not allow stall. 18 | input_arg_2 = 'ns' 19 | 20 | # User-specified number of B-spline control points 21 | num_cp = 20 22 | # User-specified numer of time steps 23 | num_steps = 500 24 | 25 | # Some specifications 26 | prop_rad = 0.75 27 | wing_S = 9. 28 | wing_span = 6. 29 | num_blades = 3. 30 | blade_chord = 0.1 31 | num_props = 8 32 | 33 | ####################################################### 34 | # Settings from Shamsheer's original code 35 | ####################################################### 36 | 37 | # User-specified input dictionary 38 | input_dict = {'T_guess': 9.8 * 725 * 1.2, # initial thrust guess 39 | 'x_dot_initial': 0., # initial horizontal speed 40 | 'y_dot_initial': 0.01, # initial vertical speed 41 | 'y_initial': 0.01, # initial vertical displacement 42 | 'A_disk': np.pi * prop_rad ** 2 * num_props, # total propeller disk area 43 | 'AR': wing_span ** 2 / (0.5 * wing_S), # aspect ratio of each wing 44 | 'e': 0.68, # span efficiency factor of each wing 45 | 't_over_c': 0.12, # airfoil thickness-to-chord ratio 46 | 'S': wing_S, # total wing reference area 47 | 'CD0': 0.35 / wing_S, # coefficient of drag of the fuselage, gear, etc. 48 | 'm': 725., # mass of aircraft 49 | 'a0': 5.9, # airfoil lift-curve slope 50 | 'alpha_stall': 15. / 180. * np.pi, # wing stall angle 51 | 'rho': 1.225, # air density 52 | 'induced_velocity_factor': int(input_arg_1) / 100., # induced-velocity factor 53 | 'stall_option': input_arg_2, # stall option: 's' allows stall, 'ns' does not 54 | 'num_steps': num_steps, # number of time steps 55 | 'R': prop_rad, # propeller radius 56 | 'solidity': num_blades * blade_chord / np.pi / prop_rad, # solidity 57 | 'omega': 136. / prop_rad, # angular rotation rate 58 | 'prop_CD0': 0.012, # CD0 for prop profile power 59 | 'k_elec': 0.9, # electrical and mechanical losses factor 60 | 'k_ind': 1.2, # induced-losses factor 61 | 'nB': num_blades, # number of blades per propeller 62 | 'bc': blade_chord, # representative blade chord 63 | 'n_props': num_props # number of propellers 64 | } 65 | 66 | # make some splines to define the time history 67 | 68 | t_final = 28.36866175 69 | # using the sine_distribution helper from the SplineComp docs 70 | # http://openmdao.org/twodocs/versions/3.4.0/features/building_blocks/components/spline_comp.html#splinecomp-interpolation-distribution 71 | time_cp = om.sine_distribution(num_cp, start=0, end=1, phase=np.pi) 72 | 73 | theta_cp = [0.07188392, 0.17391331, 0.34028059, 0.5101345, 0.66561472, 74 | 0.76287459, 0.84858573, 0.91466015, 0.96113079, 0.99573339, 75 | 1.00574242, 1.01472168, 1.03595189, 1.10451423, 1.16461664, 76 | 1.22062094, 1.28553096, 1.3307819, 1.40092757, 1.43952015] 77 | power_cp = [207161.23632379, 239090.09259429, 228846.07476655, 228171.35928472, 78 | 203168.64876755, 214967.45622033, 215557.60195517, 224144.75074625, 79 | 234546.06852611, 248761.85655837, 264579.96329677, 238568.31766929, 80 | 238816.66768314, 236739.41494728, 244041.61634308, 242472.86320043, 81 | 239606.77670727, 277307.47563171, 225369.8825676, 293871.23097611] 82 | 83 | 84 | class RK4Integration(om.ExplicitComponent): 85 | 86 | def setup(self): 87 | sub_prob = om.Problem() 88 | sub_prob.model.add_subsystem('time_calc', om.ExecComp('norm_time=time/t_final', 89 | time={'units': 's'}, 90 | t_final={'units': 's'})) 91 | controls = sub_prob.model.add_subsystem('controls', om.MetaModelStructuredComp(method='scipy_cubic', 92 | training_data_gradients=True)) 93 | controls.add_input('norm_time', 0.0, training_data=time_cp) 94 | controls.add_output('theta', 0.0, training_data=theta_cp) 95 | controls.add_output('power', 0.0, training_data=power_cp) 96 | sub_prob.model.connect('time_calc.norm_time', 'controls.norm_time') 97 | 98 | sub_prob.model.add_subsystem('ode', Dynamics(input_dict=input_dict, num_nodes=1)) 99 | sub_prob.model.connect('controls.theta', 'ode.theta') 100 | sub_prob.model.connect('controls.power', 'ode.power') 101 | 102 | sub_prob.setup() 103 | 104 | self.sub_prob = sub_prob 105 | 106 | self.add_input('t_final', val=30., units='s') 107 | self.add_input('time', val=0., units='s') 108 | self.add_input('theta_cp', val=np.ones(num_cp), units='rad') 109 | self.add_input('power_cp', val=np.ones(num_cp), units='W') 110 | 111 | self.add_output('x', shape=num_steps, units='m') 112 | self.add_output('y', shape=num_steps, units='m') 113 | self.add_output('vx', shape=num_steps, units='m/s') 114 | self.add_output('vy', shape=num_steps, units='m/s') 115 | self.add_output('energy', shape=num_steps, units='J') 116 | self.add_output('times', shape=num_steps, units='s') 117 | 118 | def rk4_weight(self, t, vx, vy): 119 | self.sub_prob['time_calc.time'] = t 120 | self.sub_prob['ode.vx'] = vx 121 | self.sub_prob['ode.vy'] = vy 122 | self.sub_prob.run_model() 123 | dx_dt = self.sub_prob['ode.x_dot'][0] 124 | dy_dt = self.sub_prob['ode.y_dot'][0] 125 | dvx_dt = self.sub_prob['ode.a_x'][0] 126 | dvy_dt = self.sub_prob['ode.a_y'][0] 127 | denergy_dt = self.sub_prob['ode.energy_dot'][0] 128 | f = np.array([dx_dt, dy_dt, dvx_dt, dvy_dt, denergy_dt]) 129 | return f 130 | 131 | def compute(self, inputs, outputs): 132 | sub_prob = self.sub_prob 133 | 134 | outputs['x'][0] = 0 135 | outputs['y'][0] = 0.01 136 | outputs['vx'][0] = 0 137 | outputs['vy'][0] = .01 138 | outputs['energy'][0] = 0 139 | outputs['times'][0] = 0 140 | 141 | dt = t_final / num_steps 142 | 143 | sub_prob['time_calc.t_final'] = inputs['t_final'] 144 | for i in range(num_steps - 1): 145 | sub_prob['time_calc.time'] = outputs['times'][i].copy() 146 | 147 | k1 = dt * self.rk4_weight(sub_prob['time_calc.time'], outputs['vx'][i], outputs['vy'][i]) 148 | k2 = dt * self.rk4_weight(sub_prob['time_calc.time'] + 0.5 * dt, outputs['vx'][i] + 0.5 * k1[2], 149 | outputs['vy'][i] + 0.5 * k1[3]) 150 | 151 | k3 = dt * self.rk4_weight(sub_prob['time_calc.time'] + 0.5 * dt, outputs['vx'][i] + 0.5 * k2[2], 152 | outputs['vy'][i] + 0.5 * k2[3]) 153 | 154 | k4 = dt * self.rk4_weight(sub_prob['time_calc.time'] + dt, outputs['vx'][i] + k3[2], 155 | outputs['vy'][i] + k3[3]) 156 | 157 | outputs['x'][i + 1] = outputs['x'][i] + k1[0]/6 + k2[0]/3 + k3[0]/3 + k4[0]/6 158 | outputs['y'][i + 1] = outputs['y'][i] + k1[1]/6 + k2[1]/3 + k3[1]/3 + k4[1]/6 159 | outputs['vx'][i + 1] = outputs['vx'][i] + k1[2]/6 + k2[2]/3 + k3[2]/3 + k4[2]/6 160 | outputs['vy'][i + 1] = outputs['vy'][i] + k1[3]/6 + k2[3]/3 + k3[3]/3 + k4[3]/6 161 | outputs['energy'][i + 1] = outputs['energy'][i] + k1[4]/6 + k2[4]/3 + k3[4]/3 + k4[4]/6 162 | 163 | outputs['times'][i + 1] = outputs['times'][i] + dt 164 | 165 | 166 | if __name__ == "__main__": 167 | import matplotlib.pylab as plt 168 | 169 | p = om.Problem() 170 | p.model = RK4Integration() 171 | 172 | p.setup() 173 | 174 | p['theta_cp'] = theta_cp 175 | p['power_cp'] = power_cp 176 | 177 | p.run_model() 178 | 179 | fig, ax = plt.subplots() 180 | ax.plot(p['x'], p['y']) 181 | plt.show() 182 | -------------------------------------------------------------------------------- /problems/evtol_trajectory/ode/test_cl_func.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_near_equal 7 | 8 | from evtol_dynamics_comp import CLfunc 9 | from evtol_dynamics_comp_vectorized import CLfunc as cl_func_vectorized 10 | 11 | import verify_data 12 | 13 | class TestCL(unittest.TestCase): 14 | 15 | def test_cl_func_vectorized(self): 16 | 17 | alpha_stall = 0.26179939 18 | AR = 8 19 | e = 0.68 20 | a0 = 5.9 21 | t_over_c = 0.12 22 | 23 | aoa_blown = np.array([-0.07188392, 0.2270590416478, 0.2364583821384856, 0.2401759902150005, 0.2423804104464628, 0.243943990853836]) 24 | 25 | CL = cl_func_vectorized(aoa_blown, alpha_stall, AR, e, a0, t_over_c) 26 | 27 | expected = np.array([-0.3152743051485073, 0.9958538389888826, 1.0370761798060673, 28 | 1.0533747112777043, 1.063032129491213, 1.069874574395663]) 29 | 30 | with np.printoptions(precision=16): 31 | print(CL) 32 | 33 | assert_near_equal(CL, expected, tolerance=1.0E-8) 34 | 35 | def test_cl_func(self): 36 | CL = [] 37 | 38 | for aoa_blown in [-0.07188392, 0.2270590416478, 0.2364583821384856, 0.2401759902150005, 0.2423804104464628, 0.243943990853836]: 39 | alpha_stall = 0.26179939 40 | AR = 8 41 | e = 0.68 42 | a0 = 5.9 43 | t_over_c = 0.12 44 | 45 | CL.append(CLfunc(aoa_blown, alpha_stall, AR, e, a0, t_over_c)) 46 | 47 | 48 | expected = np.array([-0.3152743051485073, 0.9958538389888826, 1.0370761798060673, 49 | 1.0533747112777043, 1.063032129491213, 1.069874574395663]) 50 | 51 | with np.printoptions(precision=16): 52 | print(CL) 53 | 54 | assert_near_equal(CL, expected, tolerance=1.0E-8) 55 | 56 | if __name__ == "__main__": 57 | unittest.main() -------------------------------------------------------------------------------- /problems/evtol_trajectory/ode/test_dynamics_comp.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_near_equal 7 | 8 | from evtol_dynamics_comp import Dynamics 9 | from evtol_dynamics_comp_vectorized import Dynamics as DynamicsVectorized 10 | 11 | import verify_data 12 | 13 | class VerifyTest(unittest.TestCase): 14 | 15 | def make_problem(self, comp_class=Dynamics): 16 | 17 | # Input arguments are required when calling this script. 18 | # The first argument is the induced-velocity factor in percentage (e.g., 0, 50, 100). 19 | input_arg_1 = 0 20 | # The second is the stall option: 's' allows stall or 'ns' does not allow stall. 21 | input_arg_2 = 'ns' 22 | 23 | 24 | prob = om.Problem() 25 | 26 | # User-specified number of B-spline control points 27 | num_cp = 20 28 | # User-specified numer of time steps 29 | num_steps = 500 30 | 31 | # Some specifications 32 | prop_rad = 0.75 33 | wing_S = 9. 34 | wing_span = 6. 35 | num_blades = 3. 36 | blade_chord = 0.1 37 | num_props = 8 38 | 39 | # User-specified input dictionary 40 | input_dict = { 'T_guess' : 9.8*725*1.2, # initial thrust guess 41 | 'x_dot_initial' : 0., # initial horizontal speed 42 | 'y_dot_initial' : 0.01, # initial vertical speed 43 | 'y_initial' : 0.01, # initial vertical displacement 44 | 'A_disk' : np.pi * prop_rad**2 * num_props, # total propeller disk area 45 | 'AR' : wing_span**2 / (0.5 * wing_S), # aspect ratio of each wing 46 | 'e' : 0.68, # span efficiency factor of each wing 47 | 't_over_c' : 0.12, # airfoil thickness-to-chord ratio 48 | 'S' : wing_S, # total wing reference area 49 | 'CD0' : 0.35 / wing_S, # coefficient of drag of the fuselage, gear, etc. 50 | 'm' : 725., # mass of aircraft 51 | 'a0' : 5.9, # airfoil lift-curve slope 52 | 'alpha_stall' : 15. / 180. * np.pi, # wing stall angle 53 | 'rho' : 1.225, # air density 54 | 'induced_velocity_factor': int(input_arg_1)/100., # induced-velocity factor 55 | 'stall_option' : input_arg_2, # stall option: 's' allows stall, 'ns' does not 56 | 'num_steps' : num_steps, # number of time steps 57 | 'R' : prop_rad, # propeller radius 58 | 'solidity' : num_blades * blade_chord / np.pi / prop_rad, # solidity 59 | 'omega' : 136. / prop_rad, # angular rotation rate 60 | 'prop_CD0' : 0.012, # CD0 for prop profile power 61 | 'k_elec' : 0.9, # electrical and mechanical losses factor 62 | 'k_ind' : 1.2, # induced-losses factor 63 | 'nB' : num_blades, # number of blades per propeller 64 | 'bc' : blade_chord, # representative blade chord 65 | 'n_props' : num_props # number of propellers 66 | } 67 | 68 | # ADD THE MAIN PHYSICS COMPONENT TO THE SYSTEM 69 | prob.model.add_subsystem('dynamics', comp_class(input_dict=input_dict, num_nodes=num_steps), promotes=['*']) 70 | 71 | prob.setup(check=True) 72 | 73 | return prob 74 | 75 | def assert_results(self, prob): 76 | prob['power'] = verify_data.powers 77 | prob['theta'] = verify_data.thetas 78 | 79 | prob['vx'] = verify_data.x_dot[:-1] 80 | prob['vy'] = verify_data.y_dot[:-1] 81 | 82 | prob.run_model() 83 | 84 | tol = 1e-6 85 | 86 | assert_near_equal(prob['x_dot'], verify_data.x_dot[:-1], tol) 87 | assert_near_equal(prob['y_dot'], verify_data.y_dot[:-1], tol) 88 | 89 | assert_near_equal(prob['thrust'], verify_data.thrusts[:-1], tol) 90 | assert_near_equal(prob['atov'], verify_data.atov[1:], tol) 91 | 92 | assert_near_equal(prob['CL'], verify_data.CL[1:], tol) 93 | assert_near_equal(prob['CD'], verify_data.CD[1:], tol) 94 | 95 | assert_near_equal(prob['L_wings'], verify_data.L_wings, tol) 96 | assert_near_equal(prob['D_wings'], verify_data.D_wings, tol) 97 | 98 | assert_near_equal(prob['a_y'], verify_data.a_y, tol) 99 | assert_near_equal(prob['a_x'], verify_data.a_x, tol) 100 | 101 | def test_dynamics(self): 102 | 103 | prob = self.make_problem(comp_class=Dynamics) 104 | 105 | self.assert_results(prob) 106 | 107 | def test_dynamics_vectorized(self): 108 | 109 | prob = self.make_problem(comp_class=DynamicsVectorized) 110 | 111 | self.assert_results(prob) 112 | 113 | 114 | 115 | 116 | 117 | if __name__ == "__main__": 118 | unittest.main() -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/CD.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/CD.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/CL.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/CL.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/D_fuse.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/D_fuse.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/D_wings.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/D_wings.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/L_wings.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/L_wings.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/N.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/N.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/a_x.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/a_x.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/a_y.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/a_y.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/acc.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/acc.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/aoa.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/aoa.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/aoa_prop.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/aoa_prop.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/atov.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/atov.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/bsplines_comp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple B-spline component for interpolation. 3 | """ 4 | from six import string_types 5 | 6 | import numpy as np 7 | from scipy.sparse import csc_matrix, csr_matrix 8 | 9 | from openmdao.core.explicitcomponent import ExplicitComponent 10 | from openmdao.utils.array_utils import tile_sparse_jac 11 | from openmdao.utils.general_utils import warn_deprecation 12 | 13 | CITATIONS = """ 14 | @conference {Hwang2012c, 15 | title = {GeoMACH: Geometry-Centric MDAO of Aircraft Configurations with High Fidelity}, 16 | booktitle = {Proceedings of the 14th AIAA/ISSMO Multidisciplinary Analysis Optimization 17 | Conference}, 18 | year = {2012}, 19 | note = {
AIAA 2012-5605
}, 20 | month = {September}, 21 | address = {Indianapolis, IN}, 22 | author = {John T. Hwang and Joaquim R. R. A. Martins} 23 | } 24 | """ 25 | 26 | 27 | def get_bspline_mtx(num_cp, num_pt, order=4, distribution='sine'): 28 | """ 29 | Compute matrix of B-spline coefficients. 30 | Parameters 31 | ---------- 32 | num_cp : int 33 | Number of control points. 34 | num_pt : int 35 | Number of interpolated points. 36 | order : int(4) 37 | B-spline order. 38 | distribution : str 39 | Choice of spatial distribution to use for placing the control points. It can be 'sine' or 40 | 'uniform'. 41 | Returns 42 | ------- 43 | csr_matrix 44 | Sparse matrix of B-spline coefficients. 45 | """ 46 | knots = np.zeros(num_cp + order) 47 | knots[order - 1:num_cp + 1] = np.linspace(0, 1, num_cp - order + 2) 48 | knots[num_cp + 1:] = 1.0 49 | 50 | t_vec = np.linspace(0, 1, num_pt) 51 | if distribution == 'uniform': 52 | pass 53 | elif distribution == 'sine': 54 | t_vec = 0.5 * (1.0 + np.sin(-0.5 * np.pi + t_vec * np.pi)) 55 | 56 | basis = np.zeros(order) 57 | arange = np.arange(order) 58 | data = np.zeros((num_pt, order)) 59 | rows = np.zeros((num_pt, order), int) 60 | cols = np.zeros((num_pt, order), int) 61 | 62 | for ipt in range(num_pt): 63 | t = t_vec[ipt] 64 | 65 | i0 = -1 66 | for ind in range(order, num_cp + 1): 67 | if (knots[ind - 1] <= t) and (t < knots[ind]): 68 | i0 = ind - order 69 | if t == knots[-1]: 70 | i0 = num_cp - order 71 | 72 | basis[:] = 0. 73 | basis[-1] = 1. 74 | 75 | for i in range(2, order + 1): 76 | ll = i - 1 77 | j1 = order - ll 78 | j2 = order 79 | n = i0 + j1 80 | if knots[n + ll] != knots[n]: 81 | basis[j1 - 1] = (knots[n + ll] - t) / (knots[n + ll] - knots[n]) * basis[j1] 82 | else: 83 | basis[j1 - 1] = 0. 84 | for j in range(j1 + 1, j2): 85 | n = i0 + j 86 | if knots[n + ll - 1] != knots[n - 1]: 87 | basis[j - 1] = (t - knots[n - 1]) / \ 88 | (knots[n + ll - 1] - knots[n - 1]) * basis[j - 1] 89 | else: 90 | basis[j - 1] = 0. 91 | if knots[n + ll] != knots[n]: 92 | basis[j - 1] += (knots[n + ll] - t) / (knots[n + ll] - knots[n]) * basis[j] 93 | n = i0 + j2 94 | if knots[n + ll - 1] != knots[n - 1]: 95 | basis[j2 - 1] = (t - knots[n - 1]) / \ 96 | (knots[n + ll - 1] - knots[n - 1]) * basis[j2 - 1] 97 | else: 98 | basis[j2 - 1] = 0. 99 | 100 | data[ipt, :] = basis 101 | rows[ipt, :] = ipt 102 | cols[ipt, :] = i0 + arange 103 | 104 | data, rows, cols = data.flatten(), rows.flatten(), cols.flatten() 105 | 106 | return csr_matrix((data, (rows, cols)), shape=(num_pt, num_cp)) 107 | 108 | 109 | class BsplinesComp(ExplicitComponent): 110 | """ 111 | Simple B-spline component for interpolation. 112 | Attributes 113 | ---------- 114 | cite : str 115 | Listing of relevant citations that should be referenced when publishing 116 | work that uses this class. 117 | """ 118 | 119 | def __init__(self, **kwargs): 120 | """ 121 | Initialize the BsplinesComp. 122 | Parameters 123 | ---------- 124 | **kwargs : dict of keyword arguments 125 | Keyword arguments that will be mapped into the Component options. 126 | """ 127 | super(BsplinesComp, self).__init__(**kwargs) 128 | 129 | self.cite = CITATIONS 130 | 131 | warn_deprecation("'BsplinesComp' has been deprecated. Use 'SplineComp' instead.") 132 | 133 | def initialize(self): 134 | """ 135 | Declare options. 136 | """ 137 | self.options.declare('num_control_points', types=int, default=10, 138 | desc="Number of control points.") 139 | self.options.declare('num_points', types=int, default=20, 140 | desc="Number of interpolated points.") 141 | self.options.declare('vec_size', types=int, default=1, 142 | desc='The number of independent rows to interpolate.') 143 | self.options.declare('bspline_order', 4, types=int, desc="B-spline order.") 144 | self.options.declare('in_name', types=str, default='h_cp', 145 | desc="Name to use for the input variable (control points).") 146 | self.options.declare('out_name', types=str, default='h', 147 | desc="Name to use for the output variable (interpolated points).") 148 | self.options.declare('units', types=string_types, default=None, allow_none=True, 149 | desc="Units to use for the input and output variables.") 150 | self.options.declare('distribution', default='sine', values=['uniform', 'sine'], 151 | desc="Choice of spatial distribution to use for placing the control " 152 | "points. It can be 'sine' or 'uniform'.") 153 | 154 | def setup(self): 155 | """ 156 | Set up the B-spline component. 157 | """ 158 | opts = self.options 159 | num_control_points = opts['num_control_points'] 160 | num_points = opts['num_points'] 161 | vec_size = opts['vec_size'] 162 | in_name = opts['in_name'] 163 | out_name = opts['out_name'] 164 | units = opts['units'] 165 | 166 | self.add_input(in_name, val=np.random.rand(vec_size, num_control_points), units=units) 167 | self.add_output(out_name, val=np.random.rand(vec_size, num_points), units=units) 168 | 169 | jac = get_bspline_mtx(num_control_points, num_points, 170 | order=opts['bspline_order'], 171 | distribution=opts['distribution']).tocoo() 172 | 173 | data, rows, cols = tile_sparse_jac(jac.data, jac.row, jac.col, 174 | num_points, num_control_points, vec_size) 175 | 176 | self.jac = csc_matrix((data, (rows, cols)), 177 | shape=(vec_size * num_points, vec_size * num_control_points)) 178 | 179 | self.declare_partials(of=out_name, wrt=in_name, val=data, rows=rows, cols=cols) 180 | 181 | self.set_check_partial_options('*', method='cs') 182 | 183 | def compute(self, inputs, outputs): 184 | """ 185 | Compute values at the B-spline interpolation points. 186 | Parameters 187 | ---------- 188 | inputs : `Vector` 189 | `Vector` containing inputs. 190 | outputs : `Vector` 191 | `Vector` containing outputs. 192 | """ 193 | opts = self.options 194 | num_control_points = opts['num_control_points'] 195 | num_points = opts['num_points'] 196 | vec_size = opts['vec_size'] 197 | 198 | out_shape = (vec_size, num_points) 199 | in_shape = (vec_size, num_control_points) 200 | 201 | out = self.jac * inputs[opts['in_name']].reshape(np.prod(in_shape)) 202 | outputs[opts['out_name']] = out.reshape(out_shape) -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/energy.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/energy.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/evtol_openmdao.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/evtol_openmdao.py -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/ft.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/ft.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/min_u_prop.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/min_u_prop.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/powers.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/powers.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/takeoff_cs_run_script.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------------------------- 2 | # This contains the OpenMDAO run script (this works with OpenMDAO version 2.6.0) 3 | # This requires the SNOPT optimizer, but options for the free SciPy optimizer are also included. 4 | # SI units used for everthing unless otherwise specified. 5 | # Author: Shamsheer Chauhan 6 | # -------------------------------------------------------------------------------------------------- 7 | 8 | from __future__ import division, print_function 9 | import sys 10 | import numpy as np 11 | from openmdao.api import Problem, ScipyOptimizeDriver, IndepVarComp, ExplicitComponent, n2 12 | from openmdao.api import pyOptSparseDriver, SqliteRecorder 13 | from bsplines_comp import BsplinesComp 14 | from complex_transition_components import Dynamics 15 | 16 | # Input arguments are required when calling this script. 17 | # The first argument is the induced-velocity factor in percentage (e.g., 0, 50, 100). 18 | input_arg_1 = sys.argv[1] 19 | # The second is the stall option: 's' allows stall or 'ns' does not allow stall. 20 | input_arg_2 = sys.argv[2] 21 | 22 | if input_arg_2 == 'ns': 23 | print("STALL CONSTRAINT IS ON") 24 | elif input_arg_2 != 's': 25 | print("Incorrect stall option, should be ns or s"); exit() 26 | 27 | prob = Problem() 28 | 29 | # User-specified number of B-spline control points 30 | num_cp = 20 31 | # User-specified numer of time steps 32 | num_steps = 500 33 | 34 | # Some specifications 35 | prop_rad = 0.75 36 | wing_S = 9. 37 | wing_span = 6. 38 | num_blades = 3. 39 | blade_chord = 0.1 40 | num_props = 8 41 | 42 | # User-specified input dictionary 43 | input_dict = { 'T_guess' : 9.8*725*1.2, # initial thrust guess 44 | 'x_dot_initial' : 0., # initial horizontal speed 45 | 'y_dot_initial' : 0.01, # initial vertical speed 46 | 'y_initial' : 0.01, # initial vertical displacement 47 | 'A_disk' : np.pi * prop_rad**2 * num_props, # total propeller disk area 48 | 'AR' : wing_span**2 / (0.5 * wing_S), # aspect ratio of each wing 49 | 'e' : 0.68, # span efficiency factor of each wing 50 | 't_over_c' : 0.12, # airfoil thickness-to-chord ratio 51 | 'S' : wing_S, # total wing reference area 52 | 'CD0' : 0.35 / wing_S, # coefficient of drag of the fuselage, gear, etc. 53 | 'm' : 725., # mass of aircraft 54 | 'a0' : 5.9, # airfoil lift-curve slope 55 | 'alpha_stall' : 15. / 180. * np.pi, # wing stall angle 56 | 'rho' : 1.225, # air density 57 | 'induced_velocity_factor': int(input_arg_1)/100., # induced-velocity factor 58 | 'stall_option' : input_arg_2, # stall option: 's' allows stall, 'ns' does not 59 | 'num_steps' : num_steps, # number of time steps 60 | 'R' : prop_rad, # propeller radius 61 | 'solidity' : num_blades * blade_chord / np.pi / prop_rad, # solidity 62 | 'omega' : 136. / prop_rad, # angular rotation rate 63 | 'prop_CD0' : 0.012, # CD0 for prop profile power 64 | 'k_elec' : 0.9, # electrical and mechanical losses factor 65 | 'k_ind' : 1.2, # induced-losses factor 66 | 'nB' : num_blades, # number of blades per propeller 67 | 'bc' : blade_chord, # representative blade chord 68 | 'n_props' : num_props # number of propellers 69 | } 70 | 71 | indeps = prob.model.add_subsystem('indeps', IndepVarComp(), promotes=['*']) 72 | 73 | # INITIAL GUESSES FOR DESIGN VARIABLES 74 | indeps.add_output('flight_time', val=20.) 75 | indeps.add_output('powers_cp', val=np.ones(num_cp)*2e5) 76 | indeps.add_output('thetas_cp', val=np.ones(num_cp)*np.pi/5.) 77 | 78 | # ADD B-SPLINE COMPONENTS TO THE SYSTEM 79 | prob.model.add_subsystem('b_spline_power', BsplinesComp(num_control_points = num_cp, num_points = num_steps, in_name = 'powers_cp', out_name = 'powers'), promotes=['*']) 80 | prob.model.add_subsystem('b_spline_thetas', BsplinesComp(num_control_points = num_cp, num_points = num_steps, in_name = 'thetas_cp', out_name = 'thetas'), promotes=['*']) 81 | 82 | # ADD THE MAIN PHYSICS COMPONENT TO THE SYSTEM 83 | prob.model.add_subsystem('dynamics', Dynamics(input_dict=input_dict), promotes=['*']) 84 | 85 | # OBJECTIVE 86 | prob.model.add_objective('energy', scaler = 2e-7) 87 | 88 | # DESIGN VARIABLES 89 | prob.model.add_design_var('powers_cp', lower = 1e3, upper = 311000, scaler=5e-6) 90 | prob.model.add_design_var('thetas_cp', lower = 0., upper = 3*np.pi/4, scaler=1.2) 91 | prob.model.add_design_var('flight_time', lower = 5., upper = 60., scaler = 3e-2) 92 | 93 | # CONSTRAINTS 94 | prob.model.add_constraint('y', lower=305, scaler = 3e-3) # Constraint for the final vertical displacement 95 | prob.model.add_constraint('x', equals=900, scaler = 3e-3) # Constraint for the final horizontal displacement 96 | prob.model.add_constraint('y_min', lower=0.) # Constraint for the minimum vertical displacement 97 | # prob.model.add_constraint('u_prop_min', lower=1e-6) # Constraint for the inflow velocity for the propeller 98 | prob.model.add_constraint('x_dot', equals=67., scaler = 2e-2) # Constraint for the final horizontal speed 99 | if input_arg_2 == 'ns': # stall constraints 100 | prob.model.add_constraint('aoa_max', upper=15. / 180 * np.pi, scaler = 4.) 101 | prob.model.add_constraint('aoa_min', lower=-15. / 180 * np.pi, scaler = 4.) 102 | prob.model.add_constraint('acc_max', upper = 0.3, scaler = 4.) # Constraint for the acceleration magnitude 103 | 104 | prob.model.approx_totals(method='cs', step=1e-30) # Use the complex step method for total derivatives 105 | # prob.model.approx_totals(method='fd', form='central') 106 | 107 | # Options for the SNOPT optimizer 108 | prob.driver = pyOptSparseDriver() 109 | prob.driver.add_recorder(SqliteRecorder("cases.sql")) 110 | prob.driver.options['optimizer'] = "SNOPT" 111 | prob.driver.opt_settings['Major optimality tolerance'] = 1e-8 112 | prob.driver.opt_settings['Major feasibility tolerance'] = 1e-8 113 | prob.driver.opt_settings['Major iterations limit'] = 400 114 | 115 | ## Uncomment following lines for ScipyOptimizeDriver. Don't forget to comment out above driver options. 116 | ## Tuning the above scalers and initial guesses may be necessary. 117 | # from openmdao.api import ScipyOptimizeDriver 118 | # prob.driver = ScipyOptimizeDriver() 119 | # prob.driver.add_recorder(SqliteRecorder("cases.sql")) 120 | # prob.driver.options['tol'] = 1e-8 121 | # prob.driver.options['maxiter'] = 200 122 | # prob.driver.options['disp'] = True 123 | 124 | prob.setup(check=True) 125 | 126 | prob.run_driver() 127 | 128 | # Save the design variables and states 129 | np.save("y", prob.model.dynamics.y) 130 | np.save("x", prob.model.dynamics.x) 131 | np.save("y_dots", prob.model.dynamics.y_dots) 132 | np.save("x_dots", prob.model.dynamics.x_dots) 133 | np.save("thetas", prob.model.dynamics.thetas) 134 | np.save("powers", prob.model.dynamics.powers) 135 | np.save("atov", prob.model.dynamics.atov) 136 | np.save("thrusts", prob.model.dynamics.thrusts) 137 | np.save("energy", prob.model.dynamics.energy) 138 | np.save("ft", prob.model.dynamics.flight_time) 139 | np.save("CD", prob.model.dynamics.CD) 140 | np.save("CL", prob.model.dynamics.CL) 141 | np.save("aoa", prob.model.dynamics.aoa) 142 | np.save("min_u_prop", prob.model.dynamics.min_u_prop) 143 | np.save("acc", prob.model.dynamics.acc) 144 | np.save("L_wings", prob.model.dynamics.L_wings) 145 | np.save("D_wings", prob.model.dynamics.D_wings) 146 | np.save("D_fuse", prob.model.dynamics.D_fuse) 147 | np.save("N", prob.model.dynamics.N) 148 | np.save("aoa_prop", prob.model.dynamics.aoa_prop) 149 | np.save("a_x", prob.model.dynamics.a_x) 150 | np.save("a_y", prob.model.dynamics.a_y) 151 | -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/thetas.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/thetas.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/thrusts.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/thrusts.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/x.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/x.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/x_dots.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/x_dots.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/y.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/y.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/original/y_dots.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/original/y_dots.npy -------------------------------------------------------------------------------- /problems/evtol_trajectory/reference_solution.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/evtol_trajectory/reference_solution.sql -------------------------------------------------------------------------------- /problems/nested_optimization/components/compute_modified_power.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize 3 | import openmdao.api as om 4 | 5 | 6 | class ComputeModifiedPower(om.ExplicitComponent): 7 | def initialize(self): 8 | self.options.declare("size") 9 | 10 | def setup(self): 11 | size = self.options["size"] 12 | 13 | self.add_input("aerodynamic_efficiency", np.zeros(size)) 14 | self.add_input("powers", np.zeros(size)) 15 | 16 | self.add_output("modified_power") 17 | 18 | def compute(self, inputs, outputs): 19 | outputs["modified_power"] = np.sum((1. + inputs["aerodynamic_efficiency"])**3 * inputs["powers"]) -------------------------------------------------------------------------------- /problems/nested_optimization/components/compute_pitch_angles.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize 3 | import openmdao.api as om 4 | 5 | 6 | def compute_power(pitch_angle, wind_speed, drag_modifier): 7 | CD = np.pi * drag_modifier * np.deg2rad(pitch_angle) ** 2 8 | airfoil_power_boost = (drag_modifier - wind_speed * 2.0) ** 2.0 / 10.0 9 | return -((wind_speed - CD) ** 3) - airfoil_power_boost 10 | 11 | 12 | def compute_power_constraint(pitch_angle, wind_speed, drag_modifier, P_rated): 13 | neg_power = compute_power(pitch_angle, wind_speed, drag_modifier) 14 | return neg_power + P_rated 15 | 16 | 17 | class ComputePitchAngles(om.ExplicitComponent): 18 | def initialize(self): 19 | self.options.declare("size") 20 | self.options.declare("P_rated") 21 | 22 | def setup(self): 23 | size = self.options["size"] 24 | 25 | self.add_input("wind_speeds", np.zeros(size)) 26 | self.add_input("drag_modifier", 11.0) 27 | 28 | self.add_output("pitch_angles", np.zeros(size)) 29 | self.add_output("powers", np.zeros(size)) 30 | self.add_output("total_power") 31 | 32 | def compute(self, inputs, outputs): 33 | P_rated = self.options["P_rated"] 34 | drag_modifier = inputs["drag_modifier"] 35 | 36 | for i, wind_speed in enumerate(inputs["wind_speeds"]): 37 | constraints = [ 38 | { 39 | "type": "ineq", 40 | "fun": compute_power_constraint, 41 | "args": [wind_speed, drag_modifier, P_rated], 42 | } 43 | ] 44 | result = minimize( 45 | compute_power, 46 | 1.0, 47 | args=(wind_speed, drag_modifier), 48 | method="SLSQP", 49 | bounds=[(-15.0, 15.0)], 50 | options={"disp": False}, 51 | constraints=constraints, 52 | ) 53 | outputs["pitch_angles"][i] = result.x 54 | outputs["powers"][i] = result.fun 55 | 56 | outputs["total_power"] = np.sum(outputs["powers"]) 57 | 58 | -------------------------------------------------------------------------------- /problems/nested_optimization/components/compute_pitch_angles_solver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import brentq 3 | import openmdao.api as om 4 | 5 | 6 | def compute_power(pitch_angle, wind_speed, drag_modifier): 7 | CD = np.pi * drag_modifier * np.deg2rad(pitch_angle) ** 2 8 | airfoil_power_boost = (drag_modifier - wind_speed * 2.0) ** 2.0 / 10.0 9 | return -((wind_speed - CD) ** 3) - airfoil_power_boost 10 | 11 | 12 | def fd_dpower__dpitch_angle(pitch_angle, wind_speed, drag_modifier): 13 | '''central difference approximation of dpower__dpitch_angle''' 14 | 15 | step = 1e-4 16 | p = compute_power(pitch_angle, wind_speed, drag_modifier) 17 | p_minus = compute_power(pitch_angle-step, wind_speed, drag_modifier) 18 | p_plus = compute_power(pitch_angle+step, wind_speed, drag_modifier) 19 | 20 | return p, (p_plus - p_minus)/(2*step) 21 | 22 | def composite_residual(pitch_angle, wind_speed, drag_modifier, P_rated, return_power=False): 23 | ''' a "trick" to apply a constraint is to conditionally evaluate different residuals''' 24 | 25 | # NOTES: This trick works fine as long as one of two conditions is met: 26 | # 1) Your residuals are c1 continuous across the conditional 27 | # 2) Your residuals are c0 continuous and you don't end up oscillating 28 | # back and forth across the breakpoint in the conditional 29 | 30 | power, d_power = fd_dpower__dpitch_angle(pitch_angle, wind_speed, drag_modifier) 31 | 32 | 33 | if power < -P_rated: 34 | # NOTE: its usually beneficial to normalize your residuals by a reference quantity 35 | R = (power-P_rated)/P_rated 36 | else: 37 | R = d_power 38 | 39 | if return_power: 40 | return R, power 41 | else: 42 | return R 43 | 44 | 45 | # If you are going to globally finite difference over an entire model, which this is a part of 46 | # then the explicit component works just as well as the implicit one 47 | class ComputePitchAnglesSolverExplicit(om.ExplicitComponent): 48 | def initialize(self): 49 | self.options.declare("size") 50 | self.options.declare("P_rated") 51 | 52 | def setup(self): 53 | size = self.options["size"] 54 | 55 | self.add_input("wind_speeds", np.zeros(size)) 56 | self.add_input("drag_modifier", 11.0) 57 | 58 | self.add_output("pitch_angles", np.zeros(size)) 59 | self.add_output("powers", np.zeros(size)) 60 | self.add_output("total_power") 61 | 62 | def compute(self, inputs, outputs): 63 | P_rated = self.options["P_rated"] 64 | drag_modifier = inputs["drag_modifier"] 65 | 66 | RETURN_POWER = False 67 | 68 | for i, wind_speed in enumerate(inputs["wind_speeds"]): 69 | root = brentq(composite_residual, -15, 15, 70 | args=(wind_speed, drag_modifier, P_rated, RETURN_POWER)) 71 | outputs["pitch_angles"][i] = root 72 | outputs["powers"][i] = compute_power(root, wind_speed, drag_modifier) 73 | 74 | outputs["total_power"] = np.sum(outputs["powers"]) 75 | 76 | 77 | # If you are going to be finite differencing the partial derivatives of the component 78 | # then an implicit component will be more efficient and numerically stable 79 | class ComputePitchAnglesSolverImplicit(om.ImplicitComponent): 80 | def initialize(self): 81 | self.options.declare("size") 82 | self.options.declare("P_rated") 83 | 84 | def setup(self): 85 | size = self.options["size"] 86 | 87 | self.add_input("wind_speeds", np.zeros(size)) 88 | self.add_input("drag_modifier", 11.0) 89 | 90 | self.add_output("pitch_angles", np.zeros(size)) 91 | self.add_output("powers", np.zeros(size)) 92 | self.add_output("total_power") 93 | 94 | def apply_nonlinear(self, inputs, outputs, residuals): 95 | P_rated = self.options["P_rated"] 96 | drag_modifier = inputs["drag_modifier"] 97 | 98 | RETURN_POWER = True 99 | 100 | for i, wind_speed in enumerate(inputs["wind_speeds"]): 101 | pitch_angle = outputs['pitch_angles'][i] 102 | R, power = composite_residual(pitch_angle, wind_speed, drag_modifier, RETURN_POWER) 103 | residuals['pitch_angles'][i] = R 104 | residauls['powers'][i] = outputs['powers'][i] - power 105 | 106 | def solve_nonlinear(self, inputs, outputs): 107 | P_rated = self.options["P_rated"] 108 | drag_modifier = inputs["drag_modifier"] 109 | 110 | RETURN_POWER = False 111 | 112 | for i, wind_speed in enumerate(inputs["wind_speeds"]): 113 | root = brentq(composite_residual, -15, 15, 114 | args=(wind_speed, drag_modifier, P_rated, RETURN_POWER)) 115 | outputs["pitch_angles"][i] = root 116 | outputs["powers"][i] = compute_power(root, wind_speed, drag_modifier) 117 | 118 | outputs["total_power"] = np.sum(outputs["powers"]) 119 | 120 | if __name__ == "__main__": 121 | 122 | from compute_pitch_angles import ComputePitchAngles 123 | wind_speeds = [4.0, 6.0, 8.0, 10.0] 124 | P_rated = 500.0 125 | 126 | p = om.Problem() 127 | p.model = ComputePitchAngles(P_rated=P_rated, size=4) 128 | p.setup() 129 | p['wind_speeds'] = wind_speeds 130 | p.run_model() 131 | expected = p['powers'].copy() 132 | 133 | p = om.Problem() 134 | p.model = ComputePitchAnglesSolverExplicit(P_rated=P_rated, size=4) 135 | p.setup() 136 | p['wind_speeds'] = wind_speeds 137 | p.run_model() 138 | computed_explicit = p['powers'].copy() 139 | 140 | p = om.Problem() 141 | p.model = ComputePitchAnglesSolverImplicit(P_rated=P_rated, size=4) 142 | p.setup() 143 | p['wind_speeds'] = wind_speeds 144 | p.run_model() 145 | computed_implicit = p['powers'].copy() 146 | 147 | print('expected', expected) 148 | print('computed explicit', computed_explicit) 149 | print('computed implicit', computed_implicit) 150 | 151 | -------------------------------------------------------------------------------- /problems/nested_optimization/components/compute_pitch_angles_using_subproblem.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | 4 | 5 | def compute_power(pitch_angle, wind_speed, drag_modifier): 6 | CD = np.pi * drag_modifier * np.deg2rad(pitch_angle) ** 2 7 | airfoil_power_boost = (drag_modifier - wind_speed * 2.0) ** 2.0 / 10.0 8 | return -((wind_speed - CD) ** 3) - airfoil_power_boost 9 | 10 | def compute_power_constraint(pitch_angle, wind_speed, drag_modifier, P_rated): 11 | neg_power = compute_power(pitch_angle, wind_speed, drag_modifier) 12 | return neg_power + P_rated 13 | 14 | class ComputePower(om.ExplicitComponent): 15 | def setup(self): 16 | self.add_input("pitch_angle", 0.0) 17 | self.add_input("wind_speed", 0.0) 18 | self.add_input("drag_modifier", 0.0) 19 | self.add_input("P_rated", 0.0) 20 | 21 | self.add_output("power") 22 | self.add_output("power_constraint") 23 | 24 | def compute(self, inputs, outputs): 25 | outputs["power"] = compute_power( 26 | inputs["pitch_angle"], 27 | inputs["wind_speed"], 28 | inputs["drag_modifier"]) 29 | 30 | outputs["power_constraint"] = compute_power_constraint( 31 | inputs["pitch_angle"], 32 | inputs["wind_speed"], 33 | inputs["drag_modifier"], 34 | inputs["P_rated"]) 35 | 36 | class ComputePitchAnglesUsingSubProblem(om.ExplicitComponent): 37 | 38 | def initialize(self): 39 | self.options.declare("size") 40 | self.options.declare("P_rated") 41 | self._problem = None 42 | 43 | def setup(self): 44 | size = self.options["size"] 45 | 46 | self.add_input("wind_speeds", np.zeros(size)) 47 | self.add_input("drag_modifier", 11.0) 48 | 49 | self.add_output("pitch_angles", np.zeros(size)) 50 | self.add_output("powers", np.zeros(size)) 51 | self.add_output("total_power") 52 | 53 | self._problem = prob = om.Problem() 54 | prob.model.add_subsystem( 55 | "compute_power", 56 | ComputePower(), 57 | promotes=["*"], 58 | ) 59 | 60 | prob.driver = om.ScipyOptimizeDriver() 61 | prob.driver.options["optimizer"] = "COBYLA" 62 | prob.model.approx_totals(method="fd") 63 | 64 | prob.model.add_design_var("pitch_angle", lower=-15.0, upper=15.0) 65 | prob.model.add_constraint("power_constraint", lower=0.0) 66 | prob.model.add_objective("power") 67 | 68 | prob.setup() 69 | 70 | def compute(self, inputs, outputs): 71 | P_rated = self.options["P_rated"] 72 | drag_modifier = inputs["drag_modifier"] 73 | 74 | prob = self._problem 75 | prob.set_val("drag_modifier", drag_modifier) 76 | prob.set_val("P_rated", P_rated) 77 | 78 | for i, wind_speed in enumerate(inputs["wind_speeds"]): 79 | prob.set_val("wind_speed", wind_speed) 80 | print("inputs before run", prob["pitch_angle"], prob["wind_speed"], prob["drag_modifier"], prob["P_rated"]) 81 | prob.run_driver() 82 | outputs["pitch_angles"][i] = prob["pitch_angle"] 83 | outputs["powers"][i] = prob["power"] 84 | 85 | prob.model.list_inputs() 86 | prob.model.list_outputs() 87 | 88 | outputs["total_power"] = np.sum(outputs["powers"]) 89 | 90 | -------------------------------------------------------------------------------- /problems/nested_optimization/components/design_airfoil.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.optimize import minimize 3 | import openmdao.api as om 4 | 5 | 6 | class DesignAirfoil(om.ExplicitComponent): 7 | def initialize(self): 8 | self.options.declare("size") 9 | 10 | def setup(self): 11 | size = self.options["size"] 12 | 13 | self.add_input("wind_speeds", np.zeros(size)) 14 | self.add_input("airfoil_design", 11.0) 15 | 16 | self.add_output("aerodynamic_efficiency", np.zeros(size)) 17 | self.add_output("summed_efficiency") 18 | 19 | def compute(self, inputs, outputs): 20 | airfoil_design = inputs["airfoil_design"] 21 | 22 | for i, wind_speed in enumerate(inputs["wind_speeds"]): 23 | lift = (airfoil_design - 7) * wind_speed 24 | drag = (airfoil_design * wind_speed) ** 2 25 | outputs["aerodynamic_efficiency"][i] = lift / drag 26 | 27 | outputs["summed_efficiency"] = -np.sum(outputs["aerodynamic_efficiency"]) 28 | 29 | 30 | if __name__ == "__main__": 31 | wind_speeds = [4.0, 6.0, 8.0, 10.0] 32 | 33 | prob = om.Problem() 34 | prob.model.add_subsystem( 35 | "design_airfoil", 36 | DesignAirfoil(size=len(wind_speeds)), 37 | promotes=["*"], 38 | ) 39 | 40 | prob.driver = om.ScipyOptimizeDriver() 41 | prob.driver.options["optimizer"] = "SLSQP" 42 | prob.model.approx_totals(method="fd") 43 | 44 | prob.model.add_design_var("airfoil_design", lower=6.0, upper=15.0) 45 | prob.model.add_objective("summed_efficiency") 46 | 47 | prob.setup() 48 | 49 | prob.set_val("wind_speeds", wind_speeds) 50 | prob.run_driver() 51 | 52 | prob.model.list_inputs() 53 | prob.model.list_outputs(print_arrays=True) 54 | -------------------------------------------------------------------------------- /problems/nested_optimization/run_MDF_opt.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | from components.compute_pitch_angles import ComputePitchAngles 4 | from components.design_airfoil import DesignAirfoil 5 | from components.compute_modified_power import ComputeModifiedPower 6 | 7 | 8 | wind_speeds = [4.0, 6.0, 8.0, 10.0] 9 | P_rated = 500.0 10 | 11 | prob = om.Problem() 12 | prob.model.add_subsystem( 13 | "design_airfoil", 14 | DesignAirfoil(size=len(wind_speeds)), 15 | promotes=["*"], 16 | ) 17 | prob.model.add_subsystem( 18 | "compute_pitch_angles", 19 | ComputePitchAngles(size=len(wind_speeds), P_rated=P_rated), 20 | promotes=["*"], 21 | ) 22 | prob.model.add_subsystem( 23 | "compute_modified_power", 24 | ComputeModifiedPower(size=len(wind_speeds)), 25 | promotes=["*"], 26 | ) 27 | 28 | prob.driver = om.ScipyOptimizeDriver() 29 | prob.driver.options["optimizer"] = "SLSQP" 30 | prob.model.approx_totals(method="fd") 31 | 32 | prob.model.add_design_var("airfoil_design", lower=6.0, upper=15.0) 33 | prob.model.add_design_var("drag_modifier", lower=6.0, upper=15.0) 34 | prob.model.add_objective("modified_power") 35 | 36 | prob.setup() 37 | 38 | prob.set_val("wind_speeds", wind_speeds) 39 | prob.run_driver() 40 | 41 | prob.model.list_inputs() 42 | prob.model.list_outputs(print_arrays=True) -------------------------------------------------------------------------------- /problems/nested_optimization/run_MDF_opt_using_subproblem.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | from components.compute_pitch_angles_using_subproblem import ComputePitchAnglesUsingSubProblem 3 | from components.design_airfoil import DesignAirfoil 4 | from components.compute_modified_power import ComputeModifiedPower 5 | 6 | 7 | wind_speeds = [4.0, 6.0, 8.0, 10.0] 8 | P_rated = 500.0 9 | 10 | prob = om.Problem() 11 | prob.model.add_subsystem( 12 | "design_airfoil", 13 | DesignAirfoil(size=len(wind_speeds)), 14 | promotes=["*"], 15 | ) 16 | prob.model.add_subsystem( 17 | "compute_pitch_angles_using_problem", 18 | ComputePitchAnglesUsingSubProblem(size=len(wind_speeds), P_rated=P_rated), 19 | promotes=["*"], 20 | ) 21 | prob.model.add_subsystem( 22 | "compute_modified_power", 23 | ComputeModifiedPower(size=len(wind_speeds)), 24 | promotes=["*"], 25 | ) 26 | 27 | prob.driver = om.ScipyOptimizeDriver() 28 | prob.driver.options["optimizer"] = "SLSQP" 29 | prob.model.approx_totals(method="fd") 30 | 31 | prob.model.add_design_var("airfoil_design", lower=6.0, upper=15.0) 32 | prob.model.add_design_var("drag_modifier", lower=6.0, upper=15.0) 33 | prob.model.add_objective("modified_power") 34 | 35 | prob.setup() 36 | 37 | prob.set_val("wind_speeds", wind_speeds) 38 | prob.run_driver() 39 | -------------------------------------------------------------------------------- /problems/nested_optimization/run_pitch_angle_opt.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | from components.compute_pitch_angles import ComputePitchAngles 4 | 5 | wind_speeds = [4.0, 6.0, 8.0, 10.0] 6 | P_rated = 500.0 7 | 8 | prob = om.Problem() 9 | prob.model.add_subsystem( 10 | "compute_pitch_angles", 11 | ComputePitchAngles(size=len(wind_speeds), P_rated=P_rated), 12 | promotes=["*"], 13 | ) 14 | 15 | prob.driver = om.ScipyOptimizeDriver() 16 | prob.driver.options["optimizer"] = "SLSQP" 17 | prob.model.approx_totals(method="fd") 18 | 19 | prob.model.add_design_var("drag_modifier", lower=6.0, upper=15.0) 20 | prob.model.add_objective("total_power") 21 | 22 | prob.setup() 23 | 24 | prob.set_val("wind_speeds", wind_speeds) 25 | prob.run_driver() 26 | 27 | prob.model.list_outputs(print_arrays=True) -------------------------------------------------------------------------------- /problems/nested_optimization/run_pitch_angle_opt_using_subproblem.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | from components.compute_pitch_angles_using_subproblem import ComputePitchAnglesUsingSubProblem 3 | 4 | wind_speeds = [4.0, 6.0, 8.0, 10.0] 5 | P_rated = 500.0 6 | 7 | prob = om.Problem() 8 | prob.model.add_subsystem( 9 | "compute_pitch_angles", 10 | ComputePitchAnglesUsingSubProblem(size=len(wind_speeds), P_rated=P_rated), 11 | promotes=["*"], 12 | ) 13 | 14 | prob.driver = om.ScipyOptimizeDriver() 15 | prob.driver.options["optimizer"] = "SLSQP" 16 | prob.model.approx_totals(method="fd") 17 | 18 | prob.model.add_design_var("drag_modifier", lower=6.0, upper=15.0) 19 | prob.model.add_objective("total_power") 20 | 21 | prob.setup() 22 | 23 | prob.set_val("wind_speeds", wind_speeds) 24 | prob.run_driver() 25 | 26 | prob.model.list_outputs(print_arrays=True) -------------------------------------------------------------------------------- /problems/nested_optimization/run_sequential_opt.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openmdao.api as om 3 | from components.compute_pitch_angles import ComputePitchAngles 4 | from components.design_airfoil import DesignAirfoil 5 | from components.compute_modified_power import ComputeModifiedPower 6 | 7 | 8 | wind_speeds = [4.0, 6.0, 8.0, 10.0] 9 | P_rated = 500.0 10 | 11 | prob1 = om.Problem() 12 | prob1.model.add_subsystem( 13 | "design_airfoil", 14 | DesignAirfoil(size=len(wind_speeds)), 15 | promotes=["*"], 16 | ) 17 | prob1.model.add_subsystem( 18 | "compute_modified_power", 19 | ComputeModifiedPower(size=len(wind_speeds)), 20 | promotes=["*"], 21 | ) 22 | 23 | prob1.driver = om.ScipyOptimizeDriver() 24 | prob1.driver.options["optimizer"] = "SLSQP" 25 | prob1.model.approx_totals(method="fd") 26 | 27 | prob1.model.add_design_var("airfoil_design", lower=6.0, upper=15.0) 28 | prob1.model.add_objective("modified_power") 29 | 30 | prob1.setup() 31 | 32 | prob1.set_val("wind_speeds", wind_speeds) 33 | 34 | 35 | prob2 = om.Problem() 36 | prob2.model.add_subsystem( 37 | "compute_pitch_angles", 38 | ComputePitchAngles(size=len(wind_speeds), P_rated=P_rated), 39 | promotes=["*"], 40 | ) 41 | prob2.model.add_subsystem( 42 | "compute_modified_power", 43 | ComputeModifiedPower(size=len(wind_speeds)), 44 | promotes=["*"], 45 | ) 46 | 47 | prob2.driver = om.ScipyOptimizeDriver() 48 | prob2.driver.options["optimizer"] = "SLSQP" 49 | prob2.model.approx_totals(method="fd") 50 | 51 | prob2.model.add_design_var("drag_modifier", lower=6.0, upper=15.0) 52 | prob2.model.add_constraint("powers", lower=-P_rated) 53 | prob2.model.add_objective("modified_power") 54 | 55 | prob2.setup() 56 | 57 | prob2.set_val("wind_speeds", wind_speeds) 58 | 59 | 60 | 61 | for i in range(10): 62 | prob1.set_val("powers", prob2["powers"]) 63 | prob1.run_driver() 64 | prob1.model.list_inputs() 65 | prob1.model.list_outputs(print_arrays=True) 66 | 67 | prob2.set_val("aerodynamic_efficiency", prob1["aerodynamic_efficiency"]) 68 | prob2.run_driver() 69 | prob2.model.list_inputs() 70 | prob2.model.list_outputs(print_arrays=True) -------------------------------------------------------------------------------- /problems/nested_optimization/run_sequential_opt_using_subproblem.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | from components.compute_pitch_angles_using_subproblem import ComputePitchAnglesUsingSubProblem 3 | from components.design_airfoil import DesignAirfoil 4 | from components.compute_modified_power import ComputeModifiedPower 5 | 6 | 7 | wind_speeds = [4.0, 6.0, 8.0, 10.0] 8 | P_rated = 500.0 9 | 10 | prob1 = om.Problem() 11 | prob1.model.add_subsystem( 12 | "design_airfoil", 13 | DesignAirfoil(size=len(wind_speeds)), 14 | promotes=["*"], 15 | ) 16 | prob1.model.add_subsystem( 17 | "compute_modified_power", 18 | ComputeModifiedPower(size=len(wind_speeds)), 19 | promotes=["*"], 20 | ) 21 | 22 | prob1.driver = om.ScipyOptimizeDriver() 23 | prob1.driver.options["optimizer"] = "SLSQP" 24 | prob1.model.approx_totals(method="fd") 25 | 26 | prob1.model.add_design_var("airfoil_design", lower=6.0, upper=15.0) 27 | prob1.model.add_objective("modified_power") 28 | 29 | prob1.setup(check=True) 30 | prob1.set_val("wind_speeds", wind_speeds) 31 | 32 | 33 | prob2 = om.Problem() 34 | prob2.model.add_subsystem( 35 | "compute_pitch_angles", 36 | ComputePitchAnglesUsingSubProblem(size=len(wind_speeds), P_rated=P_rated), 37 | promotes=["*"], 38 | ) 39 | prob2.model.add_subsystem( 40 | "compute_modified_power", 41 | ComputeModifiedPower(size=len(wind_speeds)), 42 | promotes=["*"], 43 | ) 44 | 45 | prob2.driver = om.ScipyOptimizeDriver() 46 | prob2.driver.options["optimizer"] = "SLSQP" 47 | prob2.model.approx_totals(method="fd") 48 | 49 | prob2.model.add_design_var("drag_modifier", lower=6.0, upper=15.0) 50 | prob2.model.add_constraint("powers", lower=-P_rated) 51 | prob2.model.add_objective("modified_power") 52 | 53 | prob2.setup(check=True) 54 | prob2.set_val("wind_speeds", wind_speeds) 55 | 56 | for i in range(10): 57 | prob1.set_val("powers", prob2["powers"]) 58 | prob1.run_driver() 59 | prob1.model.list_inputs() 60 | prob1.model.list_outputs(print_arrays=True) 61 | 62 | prob2.set_val("aerodynamic_efficiency", prob1["aerodynamic_efficiency"]) 63 | prob2.run_driver() 64 | prob2.model.list_inputs() 65 | prob2.model.list_outputs(print_arrays=True) -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/MDF_xdsm_nested_sub_opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/nested_optimization/xdsm_scripts/MDF_xdsm_nested_sub_opt.png -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/MDF_xdsm_nested_sub_opt.py: -------------------------------------------------------------------------------- 1 | from pyxdsm.XDSM import XDSM, OPT, SOLVER, FUNC, GROUP 2 | 3 | x = XDSM() 4 | 5 | # Systems 6 | x.add_system("top_opt", OPT, "Optimizer") 7 | x.add_system("sub_opt", OPT, "Sub\_Optimizer", stack=True) 8 | x.add_system("comp_pitch_angles", FUNC, "ComputePitchAngle", stack=True) 9 | x.add_system("d_airfoil", FUNC, "DesignAirfoil", stack=True) 10 | x.add_system("comp_mod_power", FUNC, "ComputeModifiedPower") 11 | 12 | x.connect("d_airfoil", "comp_mod_power", "aerodynamic\_eff", stack=True) 13 | x.connect("comp_pitch_angles", "comp_mod_power", "powers*", stack=True) 14 | x.connect("top_opt", "d_airfoil", "airfoil\_design") 15 | x.connect("top_opt", "comp_pitch_angles", "drag\_modifier") 16 | x.connect("comp_mod_power", "top_opt", "modified\_power") 17 | x.connect("sub_opt", "comp_pitch_angles", "pitch\_angle", stack=True) 18 | x.connect("comp_pitch_angles", "sub_opt", "powers", stack=True) 19 | 20 | x.add_input("d_airfoil", "wind\_speeds", stack=True) 21 | x.add_input("comp_pitch_angles", "wind\_speeds", stack=True) 22 | 23 | x.write("run_MDF_sub_opt") 24 | 25 | -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/MDF_xdsm_top_level.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/nested_optimization/xdsm_scripts/MDF_xdsm_top_level.png -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/MDF_xdsm_top_level.py: -------------------------------------------------------------------------------- 1 | from pyxdsm.XDSM import XDSM, OPT, SOLVER, FUNC, GROUP 2 | 3 | x = XDSM() 4 | 5 | # Systems 6 | x.add_system("top_opt", OPT, "Optimizer") 7 | x.add_system("comp_pitch_angles", GROUP, "ComputeOptPitchAngle", stack=True) 8 | x.add_system("d_airfoil", FUNC, "DesignAirfoil", stack=True) 9 | x.add_system("comp_mod_power", FUNC, "ComputeModifiedPower") 10 | 11 | x.connect("d_airfoil", "comp_mod_power", "aerodynamic\_eff", stack=True) 12 | x.connect("comp_pitch_angles", "comp_mod_power", "powers*", stack=True) 13 | x.connect("top_opt", "d_airfoil", "airfoil\_design") 14 | x.connect("top_opt", "comp_pitch_angles", "drag\_modifier") 15 | x.connect("comp_mod_power", "top_opt", "modified\_power") 16 | 17 | x.add_input("d_airfoil", "wind\_speeds", stack=True) 18 | x.add_input("comp_pitch_angles", "wind\_speeds", stack=True) 19 | 20 | x.write("run_MDF_top_level") -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/comp_pitch_angles_xdsm_nested.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/nested_optimization/xdsm_scripts/comp_pitch_angles_xdsm_nested.png -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/comp_pitch_angles_xdsm_nested.py: -------------------------------------------------------------------------------- 1 | from pyxdsm.XDSM import XDSM, OPT, SOLVER, FUNC, GROUP 2 | 3 | x = XDSM() 4 | 5 | x.add_system("top_opt", OPT, "Optimizer") 6 | x.add_system("sub_opt", OPT, "Sub\_Optimizer") 7 | x.add_system("comp_pitch_angles", FUNC, "ComputePitchAngle", stack=True) 8 | 9 | x.connect('top_opt', 'comp_pitch_angles', 'drag\_modifier') 10 | x.connect('comp_pitch_angles', 'top_opt', 'total\_power') 11 | x.connect("sub_opt", "comp_pitch_angles", "pitch\_angle", stack=True) 12 | x.connect("comp_pitch_angles", "sub_opt", "powers", stack=True) 13 | 14 | x.add_input("comp_pitch_angles", "wind\_speeds", stack=True) 15 | 16 | x.write("run_comp_pitch_angles_sub_opt") -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/comp_pitch_angles_xdsm_top_level.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/nested_optimization/xdsm_scripts/comp_pitch_angles_xdsm_top_level.png -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/comp_pitch_angles_xdsm_top_level.py: -------------------------------------------------------------------------------- 1 | from pyxdsm.XDSM import XDSM, OPT, SOLVER, FUNC, GROUP 2 | 3 | x = XDSM() 4 | 5 | x.add_system("top_opt", OPT, "Optimizer") 6 | x.add_system("comp_pitch_angles", GROUP, "ComputeOptPitchAngle", stack=True) 7 | 8 | x.connect('top_opt', 'comp_pitch_angles', 'drag\_modifier') 9 | x.connect('comp_pitch_angles', 'top_opt', 'total\_power') 10 | 11 | x.add_input("comp_pitch_angles", "wind\_speeds", stack=True) 12 | 13 | x.write("run_comp_pitch_angles") -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/sequential_xdsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/nested_optimization/xdsm_scripts/sequential_xdsm.png -------------------------------------------------------------------------------- /problems/nested_optimization/xdsm_scripts/sequential_xdsm.py: -------------------------------------------------------------------------------- 1 | from pyxdsm.XDSM import XDSM, OPT, SOLVER, FUNC, GROUP 2 | 3 | x = XDSM() 4 | 5 | x.add_system("prob1_opt", OPT, "Optimize\_Prob1") 6 | x.add_system("d_airfoil", FUNC, "DesignAirfoil", stack=True) 7 | x.add_system("comp_mod_power", FUNC, "ComputeModifiedPower") 8 | 9 | x.connect("prob1_opt", "d_airfoil", "airfoil\_design") 10 | x.connect("comp_mod_power","prob1_opt", "modified\_power") 11 | x.connect("d_airfoil","comp_mod_power", "aerodynamic\_eff", stack=True) 12 | x.connect("d_airfoil","comp_mod_power2", "aerodynamic\_eff*", stack=True) 13 | 14 | x.add_input("d_airfoil", "wind\_speeds", stack=True) 15 | 16 | x.add_system("prob2_opt", OPT, "Optimize\_Prob2") 17 | x.add_system("comp_pitch_angles", GROUP, "ComputeOptPitchAngle", stack=True) 18 | x.add_system("comp_mod_power2", FUNC, "ComputeModifiedPower") 19 | 20 | x.connect("prob2_opt", "comp_pitch_angles", "drag\_modifier") 21 | x.connect("comp_mod_power2","prob2_opt", "powers, modified\_power") 22 | x.connect("comp_pitch_angles", "comp_mod_power2", "powers*", stack=True) 23 | x.connect("comp_pitch_angles", "comp_mod_power", "powers**", stack=True) 24 | 25 | x.add_input("comp_pitch_angles", "wind\_speeds", stack=True) 26 | 27 | x.write("run_sequential_top_level") -------------------------------------------------------------------------------- /problems/oas_stability_derivs/aerostruct_vsp_groups.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | """ 4 | import numpy as np 5 | 6 | import openmdao.api as om 7 | 8 | from openaerostruct.structures.spatial_beam_setup import SpatialBeamSetup 9 | from openaerostruct.structures.tube_group import TubeGroup 10 | from openaerostruct.structures.wingbox_group import WingboxGroup 11 | 12 | from vsp_eCRM import VSPeCRM 13 | 14 | 15 | class AerostructGeometries(om.Group): 16 | """ 17 | Modification of AerostructGeometry to use VSP. 18 | 19 | Structural analysis only happens on the wing. 20 | """ 21 | 22 | def initialize(self): 23 | self.options.declare('surfaces', types=list) 24 | 25 | def setup(self): 26 | surfaces = self.options['surfaces'] 27 | 28 | # Splinecomp for the thickness control points. 29 | for surface in surfaces: 30 | if 't_over_c_cp' in surface.keys(): 31 | name = surface['name'] 32 | n_cp = len(surface['t_over_c_cp']) 33 | ny = surface['mesh'].shape[1] 34 | x_interp = np.linspace(0., 1., int(ny-1)) 35 | 36 | spline_comp = om.SplineComp(method='bsplines', x_interp_val=x_interp, num_cp=n_cp, 37 | interp_options={'order' : min(n_cp, 4)}) 38 | 39 | self.add_subsystem(f'{name}_t_over_c_bsp', spline_comp, 40 | promotes_inputs=[('t_over_c_cp', f'{name}:t_over_c_cp')], 41 | promotes_outputs=[('t_over_c', f'{name}:t_over_c')]) 42 | spline_comp.add_spline(y_cp_name='t_over_c_cp', y_interp_name='t_over_c', 43 | y_cp_val=surface['t_over_c_cp']) 44 | 45 | # VSP Geometry. 46 | self.add_subsystem('vsp', VSPeCRM(horiz_tail_name="Tail", 47 | vert_tail_name="VerticalTail", 48 | wing_name="Wing", 49 | reduced=True), 50 | promotes_inputs=['wing_cord', 'vert_tail_area', 'horiz_tail_area'], 51 | promotes_outputs=['wing_mesh', 'vert_tail_mesh', 'horiz_tail_mesh']) 52 | 53 | # Setting up the structural solve. 54 | for surface in surfaces: 55 | name = surface['name'] 56 | sub = self.add_subsystem(name, om.Group()) 57 | 58 | if surface['fem_model_type'] == 'tube': 59 | tube_promotes = [] 60 | tube_inputs = [] 61 | if 'thickness_cp' in surface.keys(): 62 | tube_promotes.append('thickness_cp') 63 | if 'radius_cp' not in surface.keys(): 64 | tube_inputs = ['mesh', 't_over_c'] 65 | sub.add_subsystem('tube_group', 66 | TubeGroup(surface=surface, connect_geom_DVs=True), 67 | promotes_inputs=tube_inputs, 68 | promotes_outputs=['A', 'Iy', 'Iz', 'J', 'radius', 'thickness'] + tube_promotes) 69 | 70 | elif surface['fem_model_type'] == 'wingbox': 71 | wingbox_promotes = [] 72 | if 'skin_thickness_cp' in surface.keys() and 'spar_thickness_cp' in surface.keys(): 73 | wingbox_promotes.append('skin_thickness_cp') 74 | wingbox_promotes.append('spar_thickness_cp') 75 | wingbox_promotes.append('skin_thickness') 76 | wingbox_promotes.append('spar_thickness') 77 | elif 'skin_thickness_cp' in surface.keys() or 'spar_thickness_cp' in surface.keys(): 78 | raise NameError('Please have both skin and spar thickness as design variables, not one or the other.') 79 | 80 | sub.add_subsystem('wingbox_group', 81 | WingboxGroup(surface=surface), 82 | promotes_inputs=['mesh', 't_over_c'], 83 | promotes_outputs=['A', 'Iy', 'Iz', 'J', 'Qz', 'A_enc', 'A_int', 'htop', 'hbottom', 'hfront', 'hrear'] + wingbox_promotes) 84 | else: 85 | raise NameError('Please select a valid `fem_model_type` from either `tube` or `wingbox`.') 86 | 87 | if surface['fem_model_type'] == 'wingbox': 88 | promotes = ['A_int'] 89 | else: 90 | promotes = [] 91 | 92 | sub.add_subsystem('struct_setup', 93 | SpatialBeamSetup(surface=surface), 94 | promotes_inputs=['mesh', 'A', 'Iy', 'Iz', 'J'] + promotes, 95 | promotes_outputs=['nodes', 'local_stiff_transformed', 'structural_mass', 'cg_location', 'element_mass']) 96 | 97 | self.connect(f'{name}_mesh', [f'{name}.mesh']) 98 | self.connect(f'{name}:t_over_c', [f'{name}.t_over_c']) 99 | -------------------------------------------------------------------------------- /problems/oas_stability_derivs/baseline_meshes.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/oas_stability_derivs/baseline_meshes.pkl -------------------------------------------------------------------------------- /problems/oas_stability_derivs/baseline_meshes_reduced.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/oas_stability_derivs/baseline_meshes_reduced.pkl -------------------------------------------------------------------------------- /problems/oas_stability_derivs/notes.txt: -------------------------------------------------------------------------------- 1 | Quarter-sized problem 2 | --------------------- 3 | analytic derivs:86.3 sec 4 | fd: 3.6 sec 5 | linear problem: A.shape (904929, 904929) 6 | wing: 12x9 7 | tails: 9x9 8 | 9 | config_summary(prob) 10 | ============== Problem Summary ============ 11 | Groups: 36 12 | Components: 116 13 | Max tree depth: 5 14 | 15 | Design variables: 0 Total size: 0 16 | 17 | Nonlinear Constraints: 0 Total size: 0 18 | equality: 0 0 19 | inequality: 0 0 20 | 21 | Linear Constraints: 0 Total size: 0 22 | equality: 0 0 23 | inequality: 0 0 24 | 25 | Objectives: 0 Total size: 0 26 | 27 | Input variables: 330 Total size: 929077 28 | Output variables: 189 Total size: 920033 29 | 30 | Total connections: 330 Total transfer data size: 929077 31 | 32 | Driver type: Driver 33 | Linear Solvers: [LinearRunOnce x 35, DirectSolver] 34 | Nonlinear Solvers: [NonlinearRunOnce x 35, NonlinearBlockGS] 35 | 36 | 37 | htail: 5x5 mesh = 10499 data size 38 | vtail: 5x5 mesh = 8459 data size 39 | 12x9 mesh = 175025 data size 40 | alltogether: 319765 data size 41 | 42 | 43 | Final-sized problem 44 | --------------------- 45 | wing: 6 x 9 46 | tails: 5 x 5 47 | analytic derivs:7.45 sec 48 | fd: 2.93 sec 49 | linear problem: data size: 128305 50 | wing: 6x9 51 | tails: 5x9 52 | 53 | 54 | Major source of the vector size: 55 | 56 | get_vectors_force + mtx_assy_forces + eval_velocities (GetMatrix + EvalVelMtx + EvalVelocities) 57 | force_pts + mesh --> force_pts_velocities. 58 | - Don't need intermediate calcs for anything else. 59 | - They are huge (216 x 12 x 17 x 3) 60 | 61 | Barring that, should the surfaces be solved by independent VL calculations instead of all together 62 | 63 | Normal Way 64 | OAS stability deriv time: 8.344069242477417 65 | stability derivs 66 | CM_alpha [-0.2959168] 67 | CL_alpha [0.07197423] 68 | CN_beta [0.27372895] 69 | 70 | Setup time: 0.0021522045135498047 71 | Run time: 11.343416690826416 72 | done 73 | 74 | Coloring - numbers are wrong 75 | OAS stability deriv time: 24.654993295669556 76 | stability derivs 77 | CM_alpha [-0.29591672] 78 | CL_alpha [0.07197422] 79 | CN_beta [0.27372769] 80 | 81 | Setup time: 0.0022094249725341797 82 | Run time: 34.72225594520569 83 | done 84 | 85 | 86 | 87 | 88 | ---------------- 89 | Design Variables 90 | ---------------- 91 | name value size 92 | -------------- ----- ---- 93 | wing_cord [75.] 1 94 | ecrm_150.alpha [14.] 1 95 | 96 | ----------- 97 | Constraints 98 | ----------- 99 | name value size 100 | ----------------------- ------------ ---- 101 | con_alpha_150.val [0.05609402] 1 102 | par.ecrm_150.CN_beta [0.01088005] 1 103 | par.ecrm_150.CL [1.06849623] 1 104 | par.ecrm_150.L_equals_W [-2.6467711] 1 105 | 106 | ---------- 107 | Objectives 108 | ---------- 109 | name value size 110 | ------------ ------------- ---- 111 | l_over_d.val [-11.5061441] 1 112 | 113 | 114 | 150 success 115 | 116 | ---------------- 117 | Design Variables 118 | ---------------- 119 | name value size 120 | --------------- ------------ ---- 121 | wing_cord [1.21862888] 1 122 | vert_tail_area [3.10537502] 1 123 | horiz_tail_area [0.60370382] 1 124 | ecrm.alpha [2.7799519] 1 125 | 126 | ----------- 127 | Constraints 128 | ----------- 129 | name value size 130 | --------------- ----------------- ---- 131 | con_alpha.val [1.32696023e-10] 1 132 | ecrm.CN_beta [-1.76933121e-12] 1 133 | ecrm.CL [1.3] 1 134 | ecrm.L_equals_W [1.36890499e-13] 1 135 | 136 | ---------- 137 | Objectives 138 | ---------- 139 | name value size 140 | ------------ -------------- ---- 141 | l_over_d.val [-16.59593442] 1 142 | 143 | ecrm.alpha [13.89975949] 144 | wing_cord [71.8991041] 145 | vert_tail_area [7126.83567871] 146 | horiz_tail_area [3825.06741167] 147 | done 148 | 149 | 150 | Latest: 151 | 152 | ------------------------------------------------------------- 153 | Design Vars 154 | {'ecrm.alpha': array([15.70811467, 14.62857619, 13.50798867]), 155 | 'horiz_tail_area': array([10634.97745041]), 156 | 'vert_tail_area': array([7970.37699252]), 157 | 'wing_cord': array([79.89429342])} 158 | 159 | 160 | Nonlinear constraints 161 | {'con_alpha.val': array([1.30776822, 1.05382635, 0.08375099]), 162 | 'ecrm.CL': array([1.30000001, 1.2163612 , 1.14054028]), 163 | 'ecrm.CN_beta': array([4.71807971e-07, 9.99560179e-02, 4.76336253e-02]), 164 | 'ecrm.L_equals_W': array([-6.87998214e-09, 2.27426051e-08, 4.41431052e-08])} 165 | 166 | Objectives 167 | {'l_over_d.val': array([-12.69082787])} 168 | 169 | First success after fixing in->cm 170 | 171 | ---------------- 172 | Design Variables 173 | ---------------- 174 | name value size 175 | --------------- ------------ ---- 176 | wing_cord [42.8044836] 1 177 | vert_tail_area [4439.56031] 1 178 | horiz_tail_area [9886.93021] 1 179 | ecrm.alpha [10.529341] 1 180 | 181 | ----------- 182 | Constraints 183 | ----------- 184 | name value size 185 | --------------- ---------------- ---- 186 | con_alpha.val [3.93089981e-13] 1 187 | ecrm.CN_beta [4.79397771e-13] 1 188 | ecrm.CL [0.77304843] 1 189 | ecrm.L_equals_W [4.00790512e-14] 1 190 | 191 | ---------- 192 | Objectives 193 | ---------- 194 | name value size 195 | ------------ -------------- ---- 196 | l_over_d.val [-12.91291605] 1 197 | 198 | ecrm.alpha [10.529341] 199 | wing_cord [42.8044836] 200 | vert_tail_area [4439.56031413] 201 | horiz_tail_area [9886.93021249] 202 | done 203 | -------------------------------------------------------------------------------- /problems/oas_stability_derivs/parameter_sweep.py: -------------------------------------------------------------------------------- 1 | """ 2 | OpenMDAO optimization of eCRM design subject to stability derivative constraints. 3 | 4 | This model is best run in MPI with 3 processors. 5 | """ 6 | import pickle 7 | 8 | import numpy as np 9 | 10 | import openmdao.api as om 11 | from openaerostruct.utils.constants import grav_constant 12 | 13 | from ecrm_comp_with_stability_derivs import ECRM 14 | 15 | 16 | #Read baseline mesh 17 | with open('baseline_meshes_reduced.pkl', "rb") as f: 18 | meshes = pickle.load(f) 19 | 20 | wing_mesh = meshes['wing_mesh'] 21 | horiz_tail_mesh = meshes['horiz_tail_mesh'] 22 | vert_tail_mesh = meshes['vert_tail_mesh'] 23 | 24 | # Define data for surfaces 25 | wing_surface = { 26 | # Wing definition 27 | 'name' : 'wing', # name of the surface 28 | 'symmetry' : True, # if true, model one half of wing 29 | # reflected across the plane y = 0 30 | 'S_ref_type' : 'wetted', # how we compute the wing area, 31 | # can be 'wetted' or 'projected' 32 | 'fem_model_type': 'tube', 33 | 'twist_cp': np.zeros((1)), 34 | 'mesh': wing_mesh, 35 | 36 | 'thickness_cp' : np.array([.01, .05]), 37 | 38 | # Aerodynamic performance of the lifting surface at 39 | # an angle of attack of 0 (alpha=0). 40 | # These CL0 and CD0 values are added to the CL and CD 41 | # obtained from aerodynamic analysis of the surface to get 42 | # the total CL and CD. 43 | # These CL0 and CD0 values do not vary wrt alpha. 44 | 'CL0' : 0.5, # CL of the surface at alpha=0 45 | 'CD0' : 0.015, # CD of the surface at alpha=0 46 | 47 | # Airfoil properties for viscous drag calculation 48 | 'k_lam' : 0.05, # percentage of chord with laminar 49 | # flow, used for viscous drag 50 | 't_over_c_cp': np.array([0.15]), # thickness over chord ratio (NACA0015) 51 | 'c_max_t': 0.303, # chordwise location of maximum (NACA0015) 52 | # thickness 53 | 'with_viscous' : True, 54 | 'with_wave' : False, # if true, compute wave drag 55 | 56 | # Structural values are based on aluminum 7075 57 | 'E' : 70.e9, # [Pa] Young's modulus of the spar 58 | 'G' : 30.e9, # [Pa] shear modulus of the spar 59 | 'yield' : 500.e6 / 2.5, # [Pa] yield stress divided by 2.5 for limiting case 60 | 'mrho' : 2.78e3, # [kg/m^3] material density 61 | 'fem_origin' : 0.35, # normalized chordwise location of the spar 62 | 'wing_weight_ratio' : 1.25, 63 | 'struct_weight_relief' : False, # True to add the weight of the structure to the loads on the structure 64 | 'distributed_fuel_weight' : False, 65 | # Constraints 66 | 'exact_failure_constraint' : False, # if false, use KS function 67 | } 68 | 69 | # TODO - need real data for horiz tail. 70 | horiz_tail_surface = { 71 | # Wing definition 72 | 'name': 'horiz_tail', # name of the surface 73 | 'symmetry': True, # if true, model one half of wing 74 | # reflected across the plane y = 0 75 | 'S_ref_type': 'wetted', # how we compute the wing area, 76 | # can be 'wetted' or 'projected' 77 | 'fem_model_type': 'tube', 78 | 'twist_cp': np.zeros((1)), 79 | 'twist_cp_dv': False, 80 | 81 | 'mesh': horiz_tail_mesh, 82 | 83 | 'thickness_cp' : np.array([.01, .02]), 84 | 85 | # Aerodynamic performance of the lifting surface at 86 | # an angle of attack of 0 (alpha=0). 87 | # These CL0 and CD0 values are added to the CL and CD 88 | # obtained from aerodynamic analysis of the surface to get 89 | # the total CL and CD. 90 | # These CL0 and CD0 values do not vary wrt alpha. 91 | 'CL0': 0.0, # CL of the surface at alpha=0 92 | 'CD0': 0.0, # CD of the surface at alpha=0 93 | # Airfoil properties for viscous drag calculation 94 | 'k_lam': 0.05, # percentage of chord with laminar 95 | # flow, used for viscous drag 96 | 't_over_c_cp': np.array([0.15]), # thickness over chord ratio (NACA0015) 97 | 'c_max_t': 0.303, # chordwise location of maximum (NACA0015) 98 | # thickness 99 | 'with_viscous': True, # if true, compute viscous drag 100 | 'with_wave': False, 101 | 102 | # Structural values are based on aluminum 7075 103 | 'E' : 70.e9, # [Pa] Young's modulus of the spar 104 | 'G' : 30.e9, # [Pa] shear modulus of the spar 105 | 'yield' : 500.e6 / 2.5, # [Pa] yield stress divided by 2.5 for limiting case 106 | 'mrho' : 2.78e3, # [kg/m^3] material density 107 | 'fem_origin' : 0.35, # normalized chordwise location of the spar 108 | 'wing_weight_ratio' : 1.25, 109 | 'struct_weight_relief' : False, # True to add the weight of the structure to the loads on the structure 110 | 'distributed_fuel_weight' : False, 111 | # Constraints 112 | 'exact_failure_constraint' : False, # if false, use KS function 113 | } 114 | 115 | vert_tail_surface = { 116 | # Wing definition 117 | 'name': 'vert_tail', # name of the surface 118 | 'symmetry': False, # if true, model one half of wing 119 | # reflected across the plane y = 0 120 | 'S_ref_type': 'wetted', # how we compute the wing area, 121 | # can be 'wetted' or 'projected' 122 | 'fem_model_type': 'tube', 123 | 'twist_cp': np.zeros((1)), 124 | 'twist_cp_dv': False, 125 | 126 | 'mesh': vert_tail_mesh, 127 | 128 | 'thickness_cp' : np.array([.01, .02]), 129 | 130 | # Aerodynamic performance of the lifting surface at 131 | # an angle of attack of 0 (alpha=0). 132 | # These CL0 and CD0 values are added to the CL and CD 133 | # obtained from aerodynamic analysis of the surface to get 134 | # the total CL and CD. 135 | # These CL0 and CD0 values do not vary wrt alpha. 136 | 'CL0': 0.0, # CL of the surface at alpha=0 137 | 'CD0': 0.0, # CD of the surface at alpha=0 138 | # Airfoil properties for viscous drag calculation 139 | 'k_lam': 0.05, # percentage of chord with laminar 140 | # flow, used for viscous drag 141 | 't_over_c_cp': np.array([0.15]), # thickness over chord ratio (NACA0015) 142 | 'c_max_t': 0.303, # chordwise location of maximum (NACA0015) 143 | # thickness 144 | 'with_viscous': True, # if true, compute viscous drag 145 | 'with_wave': False, 146 | 147 | 'E' : 70.e9, # [Pa] Young's modulus of the spar 148 | 'G' : 30.e9, # [Pa] shear modulus of the spar 149 | 'yield' : 500.e6 / 2.5, # [Pa] yield stress divided by 2.5 for limiting case 150 | 'mrho' : 2.78e3, # [kg/m^3] material density 151 | 'fem_origin' : 0.35, # normalized chordwise location of the spar 152 | 'wing_weight_ratio' : 1.25, 153 | 'struct_weight_relief' : False, # True to add the weight of the structure to the loads on the structure 154 | 'distributed_fuel_weight' : False, 155 | # Constraints 156 | 'exact_failure_constraint' : False, # if false, use KS function 157 | } 158 | 159 | 160 | prob = om.Problem() 161 | model = prob.model 162 | 163 | par = model.add_subsystem('par', om.ParallelGroup(), promotes=['*']) 164 | 165 | design_inputs = ['wing_cord', 'vert_tail_area', 'horiz_tail_area'] 166 | common_settings = ['beta', 're', 'rho', 'CT', 'R', 'W0', 'load_factor', 'speed_of_sound', 'empty_cg'] 167 | 168 | par.add_subsystem('ecrm_150', ECRM(wing_surface=wing_surface, 169 | horiz_tail_surface=horiz_tail_surface, 170 | vert_tail_surface=vert_tail_surface), 171 | promotes_inputs=design_inputs + common_settings) 172 | 173 | # Objective: Maximize L/D @ 150 mph 174 | model.add_subsystem('l_over_d', om.ExecComp('val = -CL / CD')) 175 | model.connect('ecrm_150.CL', 'l_over_d.CL') 176 | model.connect('ecrm_150.CD', 'l_over_d.CD') 177 | model.add_objective('l_over_d.val') 178 | 179 | # Constraint: -CM_α/CL_α > 0.0 180 | model.add_subsystem('con_alpha_150', om.ExecComp('val = -CMa / CLa')) 181 | model.connect('ecrm_150.CM_alpha', 'con_alpha_150.CMa') 182 | model.connect('ecrm_150.CL_alpha', 'con_alpha_150.CLa') 183 | model.add_constraint('con_alpha_150.val', lower=0.0) 184 | 185 | # Constraint: CN_β > 0.0 186 | model.add_constraint('ecrm_150.CN_beta', lower=0.0) 187 | 188 | # Constraint: CL < 1.3 189 | model.add_constraint('ecrm_150.CL', upper=1.3) 190 | 191 | # Constraint: CL = W/qS 192 | model.add_constraint('ecrm_150.L_equals_W', equals=0.0) 193 | 194 | # Design Variables 195 | model.add_design_var('wing_cord', lower=45.0, upper=75.0) 196 | #model.add_design_var('vert_tail_area', lower=1500.0, upper=3000.0) 197 | #model.add_design_var('horiz_tail_area', lower=4500, upper=7500) 198 | model.add_design_var('ecrm_150.alpha', lower=2.0, upper=14.0) 199 | 200 | prob.driver = om.DOEDriver(om.FullFactorialGenerator(levels=4)) 201 | prob.driver.options['debug_print'] = ['desvars', 'nl_cons', 'objs'] 202 | prob.driver.add_recorder(om.SqliteRecorder("cases.sql")) 203 | prob.driver.recording_options['record_desvars'] = True 204 | prob.driver.recording_options['record_objectives'] = True 205 | prob.driver.recording_options['record_constraints'] = True 206 | prob.driver.recording_options['record_derivatives'] = False 207 | 208 | prob.setup() 209 | 210 | # Set Initial Conditions 211 | prob.set_val('beta', 0.0, units='deg') 212 | prob.set_val('re', 1.0e6, units='1/m') 213 | prob.set_val('rho', 1.225, units='kg/m**3') 214 | #prob.set_val('rho', 0.625, units='kg/m**3') 215 | prob.set_val('CT', grav_constant * 17.e-6, units='1/s') 216 | prob.set_val('R', 50, units='km') 217 | prob.set_val('W0', 1000.0, units='kg') 218 | prob.set_val('load_factor', 1.) 219 | prob.set_val('speed_of_sound', 767.0, units='mi/h') 220 | prob.set_val('empty_cg', np.array([262.614, 0.0, 115.861]), units='cm') 221 | 222 | # Set Initial Conditions 150 mph model 223 | prob.set_val('ecrm_150.v', 150.0, units='mi/h') 224 | prob.set_val('ecrm_150.Mach_number', 150.0/767) 225 | prob.set_val('ecrm_150.alpha', 3.0) 226 | 227 | # Initial Design 228 | prob.set_val('wing_cord', 59.05128) 229 | prob.set_val('vert_tail_area', 2295.0) 230 | prob.set_val('horiz_tail_area', 6336.0) 231 | 232 | #{'ecrm.alpha': array([1.3266714]), 233 | #'horiz_tail_area': array([15852.40061693]), 234 | #'vert_tail_area': array([5549.2437398]), 235 | #'wing_cord': array([50.39181606])} 236 | 237 | 238 | #prob.run_model() 239 | #z=prob.check_totals() 240 | 241 | prob.run_driver() 242 | 243 | prob.list_problem_vars() 244 | print('done') -------------------------------------------------------------------------------- /problems/oas_stability_derivs/post_proc_param_sweep.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | 4 | import openmdao.api as om 5 | 6 | cr = om.CaseReader('cases.sql') 7 | cases = cr.get_cases() 8 | n = len(cases) 9 | 10 | alpha = np.zeros(n) 11 | horiz_tail_area = np.zeros(n) 12 | vert_tail_area = np.zeros(n) 13 | wing_cord = np.zeros(n) 14 | 15 | CL = np.zeros(n) 16 | con_alpha = np.zeros(n) 17 | l_over_d = np.zeros(n) 18 | CN_beta = np.zeros(n) 19 | L_equals_W = np.zeros(n) 20 | 21 | for n, case in enumerate(cases): 22 | alpha[n] = case['ecrm_150.alpha'] 23 | #horiz_tail_area[n] = case['horiz_tail_area'] 24 | #vert_tail_area[n] = case['vert_tail_area'] 25 | wing_cord[n] = case['wing_cord'] 26 | 27 | CL[n] = case['ecrm_150.CL'] 28 | con_alpha[n] = case['con_alpha_150.val'] 29 | l_over_d[n] = case['l_over_d.val'] 30 | CN_beta[n] = case['ecrm_150.CN_beta'] 31 | L_equals_W[n] = case['ecrm_150.L_equals_W'] 32 | 33 | plt.figure(0) 34 | plt.plot(alpha, CL, '+') 35 | plt.figure(1) 36 | plt.plot(alpha, L_equals_W, '+') 37 | plt.figure(2) 38 | plt.plot(alpha, l_over_d, '+') 39 | plt.figure(3) 40 | plt.plot(alpha, con_alpha, '+') 41 | plt.figure(4) 42 | plt.plot(alpha, CN_beta, '+') 43 | plt.show() 44 | print('done') 45 | 46 | # Constraint: -CM_α/CL_α > 0.0 47 | # Constraint: CN_β > 0.0 48 | # Constraint: CL < 1.3 49 | # Constraint: CL = W/qS -------------------------------------------------------------------------------- /problems/oas_stability_derivs/readme.md: -------------------------------------------------------------------------------- 1 | two solutions: 2 | 3 | 1) multiple-instance finite difference 4 | 2) custom component with a sub-problem that does the finite differencing -------------------------------------------------------------------------------- /problems/oas_stability_derivs/stab_derivs.md: -------------------------------------------------------------------------------- 1 | We're interested in optimizing a vehicle subject to aerodynamic stability requirements. This is a novel OpenMDAO problem because it requires constraining derivatives of components, not just values themselves. 2 | 3 | We envision using OpenAeroStruct (since it has analytical derivatives) to drive parameters such as dihedral and taper ratio to some objective (weight, range, etc.,) subject to stability derivatives (Cnbeta, Cmalpha, etc.) being greater than some value. 4 | 5 | The aero stability equations are straightforward - they're the derivatives of each 6 forces and moments with respect to alpha, beta, rotation rates, velocity, and control surface deflections. 6 | 7 | We don't think OpenAeroStruct has roll rates or control surface deflections, so this example could just be alpha, beta, and freestream velocity. 8 | 9 | We view the test problem as having a dihedral wing and a V-tail. The objective would be to minimize wing weight, subject to Cn-beta and Cm-alpha constraints. We can provide baseline geometry. 10 | -------------------------------------------------------------------------------- /problems/oas_stability_derivs/vsp_eCRM.py: -------------------------------------------------------------------------------- 1 | """ 2 | OpenMDAO component wrapper for a VSP model of a modified CRM (eCRM-001) that will be used to demonstrate the 3 | computation and use of stability derivatives in a design problem. 4 | """ 5 | import itertools 6 | import pickle 7 | 8 | import numpy as np 9 | 10 | import openmdao.api as om 11 | 12 | import openvsp as vsp 13 | import degen_geom 14 | 15 | 16 | class VSPeCRM(om.ExplicitComponent): 17 | 18 | def initialize(self): 19 | self.options.declare('horiz_tail_name', default='Tail', 20 | desc="Name of the horizontal tail in the vsp model.") 21 | self.options.declare('vert_tail_name', default='VerticalTail', 22 | desc="Name of the vertical tail in the vsp model.") 23 | self.options.declare('wing_name', default='Wing', 24 | desc="Name of the wing in the vsp model.") 25 | self.options.declare('reduced', default=False, 26 | desc="When True, output reduced meshes instead of full-size ones. " 27 | "Running with a smaller mesh is of value when debugging.") 28 | 29 | def setup(self): 30 | options = self.options 31 | horiz_tail_name = options['horiz_tail_name'] 32 | vert_tail_name = options['vert_tail_name'] 33 | wing_name = options['wing_name'] 34 | reduced = options['reduced'] 35 | 36 | # Read the geometry. 37 | vsp_file = 'eCRM-001.1_wing_tail.vsp3' 38 | vsp.ReadVSPFile(vsp_file) 39 | 40 | self.wing_id = vsp.FindGeomsWithName(wing_name)[0] 41 | self.horiz_tail_id = vsp.FindGeomsWithName(horiz_tail_name)[0] 42 | self.vert_tail_id = vsp.FindGeomsWithName(vert_tail_name)[0] 43 | 44 | self.add_input('wing_cord', units='cm', val=59.05128,) 45 | self.add_input('vert_tail_area', units='cm**2', val=2295.) 46 | self.add_input('horiz_tail_area', units='cm**2', val=6336.) 47 | 48 | # Shapes are pre-determined. 49 | if reduced: 50 | self.add_output('wing_mesh', shape=(6, 9, 3), units='cm') 51 | self.add_output('vert_tail_mesh', shape=(5, 5, 3), units='cm') 52 | self.add_output('horiz_tail_mesh', shape=(5, 5, 3), units='cm') 53 | else: 54 | # Note: at present, OAS can't handle this size. 55 | self.add_output('wing_mesh', shape=(23, 33, 3), units='cm') 56 | self.add_output('vert_tail_mesh', shape=(33, 9, 3), units='cm') 57 | self.add_output('horiz_tail_mesh', shape=(33, 9, 3), units='cm') 58 | 59 | self.declare_partials(of='*', wrt='*', method='fd') 60 | 61 | def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): 62 | # Set values. 63 | vsp.SetParmVal(self.vert_tail_id, "TotalArea", "WingGeom", inputs['vert_tail_area'][0]) 64 | vsp.SetParmVal(self.horiz_tail_id, "TotalArea", "WingGeom", inputs['horiz_tail_area'][0]) 65 | vsp.SetParmVal(self.wing_id, "TotalChord", "WingGeom", inputs['wing_cord'][0]) 66 | 67 | vsp.Update() 68 | #vsp.Update() # just in case.. 69 | 70 | # run degen geom to get measurements 71 | dg:degen_geom.DegenGeomMgr = vsp.run_degen_geom(set_index=vsp.SET_ALL) 72 | obj_dict = {p.name:p for p in dg.get_all_objs()} 73 | 74 | # pull measurements out of degen_geom api 75 | degen_obj: degen_geom.DegenGeom = obj_dict[self.options['wing_name']] 76 | wing_cloud = self.vsp_to_point_cloud(degen_obj) 77 | 78 | degen_obj: degen_geom.DegenGeom = obj_dict[self.options['horiz_tail_name']] 79 | horiz_cloud = self.vsp_to_point_cloud(degen_obj) 80 | 81 | degen_obj: degen_geom.DegenGeom = obj_dict[self.options['vert_tail_name']] 82 | vert_cloud = self.vsp_to_point_cloud(degen_obj) 83 | 84 | # VSP outputs wing outer mold lines at points along the span. 85 | # Reshape to (chord, span, dimension) 86 | wing_cloud = wing_cloud.reshape((45, 33, 3), order='F') 87 | horiz_cloud = horiz_cloud.reshape((33, 9, 3), order='F') 88 | vert_cloud = vert_cloud.reshape((33, 9, 3), order='F') 89 | 90 | # Meshes have upper and lower surfaces, so we average the z (or y for vertical). 91 | wing_pts = wing_cloud[:23, :, :] 92 | wing_pts[1:-1, :, 2] = 0.5 * (wing_cloud[-2:-23:-1, :, 2] + wing_pts[1:-1, :, 2]) 93 | horiz_tail_pts = horiz_cloud[:17, :, :] 94 | horiz_tail_pts[1:-1, :, 2] = 0.5 * (horiz_cloud[-2:-17:-1, :, 2] + horiz_tail_pts[1:-1, :, 2]) 95 | vert_tail_pts = vert_cloud[:17, :, :] 96 | vert_tail_pts[1:-1, :, 1] = 0.5 * (vert_cloud[-2:-17:-1, :, 1] + vert_tail_pts[1:-1, :, 1]) 97 | 98 | # Reduce the mesh size for testing. (See John Jasa's recommendations in the docs.) 99 | if self.options['reduced']: 100 | wing_pts = wing_pts[:, ::4, :] 101 | wing_pts = wing_pts[[0, 4, 8, 12, 16, 22], ...] 102 | horiz_tail_pts = horiz_tail_pts[::4, ::2, :] 103 | vert_tail_pts = vert_tail_pts[::4, ::2, :] 104 | 105 | # Flip around so that FEM normals yield positive areas. 106 | wing_pts = wing_pts[::-1, ::-1, :] 107 | horiz_tail_pts = horiz_tail_pts[::-1, ::-1, :] 108 | vert_tail_pts = vert_tail_pts[:, ::-1, :] 109 | 110 | # outputs go here 111 | outputs['wing_mesh'] = wing_pts 112 | outputs['vert_tail_mesh'] = vert_tail_pts 113 | outputs['horiz_tail_mesh'] = horiz_tail_pts 114 | 115 | def vsp_to_point_cloud(self, degen_obj: degen_geom.DegenGeom)->np.ndarray: 116 | npts = degen_obj.surf.num_pnts 117 | n_xsecs = degen_obj.surf.num_secs 118 | 119 | points = np.empty((npts * n_xsecs, 3)) 120 | points[:, 0] = list(itertools.chain.from_iterable(degen_obj.surf.x)) 121 | points[:, 1] = list(itertools.chain.from_iterable(degen_obj.surf.y)) 122 | points[:, 2] = list(itertools.chain.from_iterable(degen_obj.surf.z)) 123 | 124 | return points 125 | 126 | 127 | if __name__ == "__main__": 128 | 129 | vsp_comp = VSPeCRM(horiz_tail_name="Tail", 130 | vert_tail_name="VerticalTail", 131 | wing_name="Wing", 132 | reduced=True) 133 | 134 | p = om.Problem() 135 | 136 | model = p.model 137 | 138 | p.model.add_subsystem("vsp_comp", vsp_comp) 139 | 140 | p.setup() 141 | 142 | p.run_model() 143 | 144 | data = {} 145 | for item in ['wing_mesh', 'vert_tail_mesh', 'horiz_tail_mesh']: 146 | data[item] = p.get_val(f"vsp_comp.{item}", units='m') 147 | 148 | # Save the meshes in a pickle. These will become the undeformed baseline meshes in 149 | # OpenAeroStruct. 150 | with open('baseline_meshes_reduced.pkl', 'wb') as f: 151 | pickle.dump(data, f) 152 | 153 | #om.n2(p) 154 | print('done') -------------------------------------------------------------------------------- /problems/oas_trajectory/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/oas_trajectory/readme.md -------------------------------------------------------------------------------- /problems/unsteady_vlm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/unsteady_vlm/__init__.py -------------------------------------------------------------------------------- /problems/unsteady_vlm/aero_time_vars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/unsteady_vlm/aero_time_vars.png -------------------------------------------------------------------------------- /problems/unsteady_vlm/aerostruct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/unsteady_vlm/aerostruct.png -------------------------------------------------------------------------------- /problems/unsteady_vlm/aerostruct_for_loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/unsteady_vlm/aerostruct_for_loop.png -------------------------------------------------------------------------------- /problems/unsteady_vlm/aerostruct_inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/unsteady_vlm/aerostruct_inputs.png -------------------------------------------------------------------------------- /problems/unsteady_vlm/bspline.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import scipy.sparse 3 | 4 | 5 | def get_bspline_mtx(num_cp, num_pt, mesh=None, order=4): 6 | knots = numpy.zeros(num_cp + order) 7 | knots[order-1:num_cp+1] = numpy.linspace(0, 1, num_cp - order + 2) 8 | knots[num_cp+1:] = 1.0 9 | 10 | if numpy.any(mesh): 11 | lins = mesh[1, :, 1] 12 | t_vec = (lins - numpy.min(lins)) / (numpy.max(lins) - numpy.min(lins)) 13 | else: 14 | t_vec = numpy.linspace(0, 1, num_pt) 15 | 16 | basis = numpy.zeros(order) 17 | arange = numpy.arange(order) 18 | data = numpy.zeros((num_pt, order)) 19 | rows = numpy.zeros((num_pt, order), int) 20 | cols = numpy.zeros((num_pt, order), int) 21 | 22 | for ipt in range(num_pt): 23 | t = t_vec[ipt] 24 | 25 | i0 = -1 26 | for ind in range(order, num_cp+1): 27 | if (knots[ind-1] <= t) and (t < knots[ind]): 28 | i0 = ind - order 29 | if t == knots[-1]: 30 | i0 = num_cp - order 31 | 32 | basis[:] = 0. 33 | basis[-1] = 1. 34 | 35 | for i in range(2, order+1): 36 | l = i - 1 37 | j1 = order - l 38 | j2 = order 39 | n = i0 + j1 40 | if knots[n+l] != knots[n]: 41 | basis[j1-1] = (knots[n+l] - t) / \ 42 | (knots[n+l] - knots[n]) * basis[j1] 43 | else: 44 | basis[j1-1] = 0. 45 | for j in range(j1+1, j2): 46 | n = i0 + j 47 | if knots[n+l-1] != knots[n-1]: 48 | basis[j-1] = (t - knots[n-1]) / \ 49 | (knots[n+l-1] - knots[n-1]) * basis[j-1] 50 | else: 51 | basis[j-1] = 0. 52 | if knots[n+l] != knots[n]: 53 | basis[j-1] += (knots[n+l] - t) / \ 54 | (knots[n+l] - knots[n]) * basis[j] 55 | n = i0 + j2 56 | if knots[n+l-1] != knots[n-1]: 57 | basis[j2-1] = (t - knots[n-1]) / \ 58 | (knots[n+l-1] - knots[n-1]) * basis[j2-1] 59 | else: 60 | basis[j2-1] = 0. 61 | 62 | data[ipt, :] = basis 63 | rows[ipt, :] = ipt 64 | cols[ipt, :] = i0 + arange 65 | 66 | data, rows, cols = data.flatten(), rows.flatten(), cols.flatten() 67 | 68 | return scipy.sparse.csr_matrix((data, (rows, cols)), 69 | shape=(num_pt, num_cp)) 70 | 71 | if __name__ == "__main__": 72 | 73 | 74 | num_cp = 5 75 | num_pt = 100 76 | rng = 5 # 700 * 1.852 / 1e3 77 | alt = 11 78 | 79 | lins = numpy.linspace(0, 1, num_cp) 80 | cos_dist = 0.5 * (1 - numpy.cos(lins * numpy.pi)) 81 | x_cp = rng * cos_dist 82 | h_cp = alt * numpy.sin(numpy.pi * cos_dist) 83 | 84 | import time 85 | t0 = time.time() 86 | jac = get_bspline_mtx(num_cp, num_pt) 87 | print(time.time() - t0) 88 | 89 | 90 | h_cp[3] += 2 91 | 92 | 93 | x = jac.dot(x_cp) 94 | h = jac.dot(h_cp) 95 | 96 | 97 | 98 | import pylab 99 | pylab.plot(x_cp, h_cp, 'o') 100 | pylab.plot(x, h) 101 | pylab.show() 102 | -------------------------------------------------------------------------------- /problems/unsteady_vlm/crm_data.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # eta, xle, yle, zle, twist, chord 4 | raw_crm_points = np.array([ 5 | [0., 904.294, 0.0, 174.126, 6.7166, 536.181], # 0 6 | [.1, 989.505, 115.675, 175.722, 4.4402, 468.511], 7 | [.15, 1032.133, 173.513, 176.834, 3.6063, 434.764], 8 | [.2, 1076.030, 231.351, 177.912, 2.2419, 400.835], 9 | [.25, 1120.128, 289.188, 177.912, 2.2419, 366.996], 10 | [.3, 1164.153, 347.026, 178.886, 1.5252, 333.157], 11 | [.35, 1208.203, 404.864, 180.359, .9379, 299.317], # 6 yehudi break 12 | [.4, 1252.246, 462.701, 182.289, .4285, 277.288], 13 | [.45, 1296.289, 520.539, 184.904, -.2621, 263], 14 | [.5, 1340.329, 578.377, 188.389, -.6782, 248.973], 15 | [.55, 1384.375, 636.214, 192.736, -.9436, 234.816], 16 | [.60, 1428.416, 694.052, 197.689, -1.2067, 220.658], 17 | [.65, 1472.458, 751.890, 203.294, -1.4526, 206.501], 18 | [.7, 1516.504, 809.727, 209.794, -1.6350, 192.344], 19 | [.75, 1560.544, 867.565, 217.084, -1.8158, 178.186], 20 | [.8, 1604.576, 925.402, 225.188, -2.0301, 164.029], 21 | [.85, 1648.616, 983.240, 234.082, -2.2772, 149.872], 22 | [.9, 1692.659, 1041.078, 243.625, -2.5773, 135.714], 23 | [.95, 1736.710, 1098.915, 253.691, -3.1248, 121.557], 24 | [1., 1780.737, 1156.753, 263.827, -3.75, 107.4] # 19 25 | ]) 26 | 27 | 28 | le = np.vstack((raw_crm_points[:,1], 29 | raw_crm_points[:,2], 30 | raw_crm_points[:,3])) 31 | 32 | te = np.vstack((raw_crm_points[:,1]+raw_crm_points[:,5], 33 | raw_crm_points[:,2], 34 | raw_crm_points[:,3])) 35 | 36 | mesh = np.empty((2,20,3)) 37 | mesh[0,:,:] = le.T 38 | mesh[1,:,:] = te.T 39 | 40 | mesh *= 0.0254 # convert to meters 41 | 42 | 43 | 44 | 45 | # pull out the 3 key y-locations to define the two linear regions of the wing 46 | crm_base_points = raw_crm_points[(0,6,19),:] 47 | 48 | le_base = np.vstack((crm_base_points[:,1], 49 | crm_base_points[:,2], 50 | crm_base_points[:,3])) 51 | 52 | te_base = np.vstack((crm_base_points[:,1]+crm_base_points[:,5], 53 | crm_base_points[:,2], 54 | crm_base_points[:,3])) 55 | 56 | crm_base_mesh = np.empty((2,3,3)) 57 | crm_base_mesh[0,:,:] = le_base.T 58 | crm_base_mesh[1,:,:] = te_base.T 59 | crm_base_mesh[:,:,2] = 0 # get rid of the z deflection 60 | crm_base_mesh *= 0.0254 # convert to meters 61 | 62 | -------------------------------------------------------------------------------- /problems/unsteady_vlm/geometry.py: -------------------------------------------------------------------------------- 1 | """ Manipulate geometry mesh based on high-level design parameters. """ 2 | 3 | import numpy 4 | from numpy import cos, sin, tan 5 | 6 | import openmdao.api as om 7 | 8 | from bspline import get_bspline_mtx 9 | from crm_data import crm_base_mesh 10 | 11 | 12 | def rotate(mesh, thetas): 13 | """ Computes rotation matricies given mesh and rotation angles in degress. """ 14 | te = mesh[-1] 15 | le = mesh[ 0] 16 | quarter_chord = 0.25*te + 0.75*le 17 | 18 | ny = mesh.shape[1] 19 | nx = mesh.shape[0] 20 | 21 | rad_thetas = thetas * numpy.pi / 180. 22 | 23 | mats = numpy.zeros((ny, 3, 3), dtype="complex") 24 | mats[:, 0, 0] = cos(rad_thetas) 25 | mats[:, 0, 2] = sin(rad_thetas) 26 | mats[:, 1, 1] = 1 27 | mats[:, 2, 0] = -sin(rad_thetas) 28 | mats[:, 2, 2] = cos(rad_thetas) 29 | 30 | for ix in range(nx): 31 | row = mesh[ix] 32 | row[:] = numpy.einsum("ikj, ij -> ik", mats, row - quarter_chord) 33 | row += quarter_chord 34 | return mesh 35 | 36 | 37 | def sweep(mesh, angle): 38 | """ Shearing sweep angle. Positive sweeps back. """ 39 | 40 | num_x, num_y, _ = mesh.shape 41 | ny2 = int((num_y-1)/2) 42 | 43 | le = mesh[0] 44 | te = mesh[-1] 45 | 46 | y0 = le[ny2, 1] 47 | 48 | tan_theta = tan(numpy.radians(angle)) 49 | dx_right = (le[ny2:, 1] - y0) * tan_theta 50 | dx_left = -(le[:ny2, 1] - y0) * tan_theta 51 | dx = numpy.hstack((dx_left, dx_right)) 52 | 53 | for i in range(num_x): 54 | mesh[i, :, 0] += dx 55 | 56 | return mesh 57 | 58 | 59 | def dihedral(mesh, angle): 60 | """ Dihedral angle. Positive bends up. """ 61 | 62 | num_x, num_y, _ = mesh.shape 63 | ny2 = int((num_y+1)/2) 64 | 65 | le = mesh[0] 66 | te = mesh[-1] 67 | 68 | y0 = le[ny2, 1] 69 | 70 | tan_theta = tan(numpy.radians(angle)) 71 | dx_right = (le[ny2:, 1] - y0) * tan_theta 72 | dx_left = -(le[:ny2, 1] - y0) * tan_theta 73 | dx = numpy.hstack((dx_left, dx_right)) 74 | 75 | for i in range(num_x): 76 | mesh[i, :, 2] += dx 77 | 78 | return mesh 79 | 80 | 81 | def stretch(mesh, length): 82 | """ Strech mesh in span-wise direction to reach specified length. """ 83 | 84 | le = mesh[0] 85 | te = mesh[-1] 86 | 87 | num_x, num_y, _ = mesh.shape 88 | 89 | span = le[-1, 1] - le[0, 1] 90 | dy = (length - span) / (num_y - 1) * numpy.arange(1, num_y) 91 | 92 | for i in range(num_x): 93 | mesh[i, 1:, 1] += dy 94 | 95 | return mesh 96 | 97 | 98 | def taper(mesh, taper_ratio): 99 | """ Change the spanwise chord to produce a tapered wing. """ 100 | 101 | le = mesh[0] 102 | te = mesh[-1] 103 | num_x, num_y, _ = mesh.shape 104 | ny2 = int((num_y+1)/2) 105 | 106 | center_chord = .5 * te + .5 * le 107 | span = le[-1, 1] - le[0, 1] 108 | taper = numpy.linspace(1, taper_ratio, ny2)[::-1] 109 | 110 | jac = get_bspline_mtx(ny2, ny2, mesh, order=2) 111 | taper = jac.dot(taper).flatten() 112 | 113 | dx = numpy.hstack((taper, taper[::-1][1:])) 114 | 115 | for i in range(num_x): 116 | for ind in range(3): 117 | mesh[i, :, ind] = (mesh[i, :, ind] - center_chord[:, ind]) * \ 118 | dx + center_chord[:, ind] 119 | 120 | return mesh 121 | 122 | 123 | def mirror(mesh, right_side=True): 124 | """ Takes a half geometry and mirrors it across the symmetry plane. 125 | If right_side==True, it mirrors from right to left, assuming that 126 | the first point is on the symmetry plane. Else it mirrors from left 127 | to right, assuming the last point is on the symmetry plane. """ 128 | 129 | num_x, num_y, _ = mesh.shape 130 | new_mesh = numpy.empty((num_x, 2 * num_y - 1, 3)) 131 | mirror_y = numpy.ones(mesh.shape) 132 | mirror_y[:, :, 1] *= -1.0 133 | 134 | if right_side: 135 | new_mesh[:, :num_y, :] = mesh[:, ::-1, :] * mirror_y 136 | new_mesh[:, num_y:, :] = mesh[:, 1:, :] 137 | else: 138 | new_mesh[:, :num_y, :] = mesh[:, ::-1, :] 139 | new_mesh[:, num_y:, :] = mesh[:, 1:, :] * mirror_y[:, 1:, :] 140 | 141 | return new_mesh 142 | 143 | 144 | def gen_crm_mesh(n_points_inboard=2, n_points_outboard=2, num_x=2, mesh=crm_base_mesh): 145 | """ Builds the right hand side of the CRM wing with specified number 146 | of inboard and outboard panels. """ 147 | 148 | # LE pre-yehudi 149 | s1 = (mesh[0, 1, 0] - mesh[0, 0, 0]) / (mesh[0, 1, 1] - mesh[0, 0, 1]) 150 | o1 = mesh[0, 0, 0] 151 | 152 | # TE pre-yehudi 153 | s2 = (mesh[1, 1, 0] - mesh[1, 0, 0]) / (mesh[1, 1, 1] - mesh[1, 0, 1]) 154 | o2 = mesh[1, 0, 0] 155 | 156 | # LE post-yehudi 157 | s3 = (mesh[0, 2, 0] - mesh[0, 1, 0]) / (mesh[0, 2, 1] - mesh[0, 1, 1]) 158 | o3 = mesh[0, 2, 0] - s3 * mesh[0, 2, 1] 159 | 160 | # TE post-yehudi 161 | s4 = (mesh[1, 2, 0] - mesh[1, 1, 0]) / (mesh[1, 2, 1] - mesh[1, 1, 1]) 162 | o4 = mesh[1, 2, 0] - s4 * mesh[1, 2, 1] 163 | 164 | n_points_total = n_points_inboard + n_points_outboard - 1 165 | half_mesh = numpy.zeros((2, n_points_total, 3)) 166 | 167 | # generate inboard points 168 | dy = (mesh[0, 1, 1] - mesh[0, 0, 1]) / (n_points_inboard - 1) 169 | for i in range(n_points_inboard): 170 | y = half_mesh[0, i, 1] = i * dy 171 | half_mesh[0, i, 0] = s1 * y + o1 # le point 172 | half_mesh[1, i, 1] = y 173 | half_mesh[1, i, 0] = s2 * y + o2 # te point 174 | 175 | yehudi_break = mesh[0, 1, 1] 176 | # generate outboard points 177 | dy = (mesh[0, 2, 1] - mesh[0, 1, 1]) / (n_points_outboard - 1) 178 | for j in range(n_points_outboard): 179 | i = j + n_points_inboard - 1 180 | y = half_mesh[0, i, 1] = j * dy + yehudi_break 181 | half_mesh[0, i, 0] = s3 * y + o3 # le point 182 | half_mesh[1, i, 1] = y 183 | half_mesh[1, i, 0] = s4 * y + o4 # te point 184 | 185 | full_mesh = mirror(half_mesh) 186 | full_mesh = add_chordwise_panels(full_mesh, num_x) 187 | return full_mesh 188 | 189 | 190 | def add_chordwise_panels(mesh, num_x): 191 | """ Divides the wing into multiple chordwise panels. """ 192 | le = mesh[ 0, :, :] 193 | te = mesh[-1, :, :] 194 | 195 | new_mesh = numpy.zeros((num_x, mesh.shape[1], 3)) 196 | new_mesh[ 0, :, :] = le 197 | new_mesh[-1, :, :] = te 198 | 199 | for i in range(1, num_x-1): 200 | w = float(i) / (num_x - 1) 201 | new_mesh[i, :, :] = (1 - w) * le + w * te 202 | 203 | return new_mesh 204 | 205 | 206 | def gen_mesh(num_x, num_y, span, chord, cosine_spacing=0.): 207 | """ Builds the right hand side of the rectangular wing. """ 208 | mesh = numpy.zeros((num_x, num_y, 3)) 209 | ny2 = (num_y + 1) // 2 210 | beta = numpy.linspace(0, numpy.pi/2, ny2) 211 | 212 | # mixed spacing with w as a weighting factor 213 | cosine = .5 * numpy.cos(beta) # cosine spacing 214 | uniform = numpy.linspace(0, .5, ny2)[::-1] # uniform spacing 215 | half_wing = cosine * cosine_spacing + (1 - cosine_spacing) * uniform 216 | full_wing = numpy.hstack((-half_wing[:-1], half_wing[::-1])) * span 217 | 218 | for ind_x in range(num_x): 219 | for ind_y in range(num_y): 220 | mesh[ind_x, ind_y, :] = [ind_x / (num_x-1) * chord - chord/2., full_wing[ind_y], 0] 221 | 222 | return mesh 223 | 224 | 225 | class GeometryMesh(om.ExplicitComponent): 226 | """ Changes a given mesh with span, swee3p, and twist des-vars. 227 | Takes in a half mesh with symmetry plane about the middle and 228 | outputs a full symmetric mesh. """ 229 | 230 | def initialize(self): 231 | self.options.declare("mesh") 232 | self.options.declare("num_twist") 233 | 234 | def setup(self): 235 | self.mesh = mesh = self.options["mesh"] 236 | self.num_twist = num_twist = self.options["num_twist"] 237 | 238 | self.new_mesh = numpy.empty(mesh.shape, dtype=complex) 239 | self.new_mesh[:] = mesh 240 | self.n = self.mesh.shape[1] 241 | self.half = half = int((self.n-1)/2) 242 | self.add_input('span', val=58.7630524) 243 | self.add_input('sweep', val=0.) 244 | self.add_input('dihedral', val=0.) 245 | self.add_input('twist', val=numpy.zeros(num_twist)) 246 | self.add_input('taper', val=1.) 247 | self.add_output('mesh', val=self.mesh[:, :half+1, :]) 248 | 249 | def setup_partials(self): 250 | self.declare_partials(of='*', wrt='*', method='cs') 251 | 252 | def compute(self, inputs, outputs): 253 | jac = get_bspline_mtx(self.num_twist, self.n, self.mesh) 254 | h_cp = inputs['twist'] 255 | h = jac.dot(h_cp) 256 | 257 | self.new_mesh[:] = self.mesh 258 | stretch(self.new_mesh, inputs['span']) 259 | sweep(self.new_mesh, inputs['sweep']) 260 | rotate(self.new_mesh, h) 261 | dihedral(self.new_mesh, inputs['dihedral']) 262 | taper(self.new_mesh, inputs['taper']) 263 | outputs['mesh'] = self.new_mesh[:, :self.half+1, :] 264 | -------------------------------------------------------------------------------- /problems/unsteady_vlm/materials.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | 3 | import openmdao.api as om 4 | 5 | class MaterialsTube(om.ExplicitComponent): 6 | """ Computes geometric properties for a tube element """ 7 | 8 | def initialize(self): 9 | self.options.declare('n') 10 | 11 | def setup(self): 12 | n = self.options['n'] 13 | 14 | self.add_input('r', val=numpy.zeros((n - 1))) 15 | self.add_input('thick', val=numpy.zeros((n - 1))) 16 | self.add_output('A', val=numpy.zeros((n - 1))) 17 | self.add_output('Iy', val=numpy.zeros((n - 1))) 18 | self.add_output('Iz', val=numpy.zeros((n - 1))) 19 | self.add_output('J', val=numpy.zeros((n - 1))) 20 | 21 | self.arange = numpy.arange(n-1) 22 | 23 | def setup_partials(self): 24 | self.declare_partials(of='*', wrt='*', method='fd', form='central') 25 | 26 | def compute(self, inputs, outputs): 27 | 28 | pi = numpy.pi 29 | r1 = inputs['r'] - 0.5 * inputs['thick'] 30 | r2 = inputs['r'] + 0.5 * inputs['thick'] 31 | 32 | outputs['A'] = pi * (r2**2 - r1**2) 33 | outputs['Iy'] = pi * (r2**4 - r1**4) / 4. 34 | outputs['Iz'] = pi * (r2**4 - r1**4) / 4. 35 | outputs['J'] = pi * (r2**4 - r1**4) / 2. 36 | 37 | def compute_partials(self, inputs, outputs): 38 | pi = numpy.pi 39 | r = inputs['r'].real 40 | t = inputs['thick'].real 41 | r1 = r - 0.5 * t 42 | r2 = r + 0.5 * t 43 | 44 | dr1_dr = 1. 45 | dr2_dr = 1. 46 | dr1_dt = -0.5 47 | dr2_dt = 0.5 48 | 49 | r1_3 = r1**3 50 | r2_3 = r2**3 51 | 52 | a = self.arange 53 | outputs['A', 'r'][a, a] = 2 * pi * (r2 * dr2_dr - r1 * dr1_dr) 54 | outputs['A', 'thick'][a, a] = 2 * pi * (r2 * dr2_dt - r1 * dr1_dt) 55 | outputs['Iy', 'r'][a, a] = pi * (r2_3 * dr2_dr - r1_3 * dr1_dr) 56 | outputs['Iy', 'thick'][a, a] = pi * (r2_3 * dr2_dt - r1_3 * dr1_dt) 57 | outputs['Iz', 'r'][a, a] = pi * (r2_3 * dr2_dr - r1_3 * dr1_dr) 58 | outputs['Iz', 'thick'][a, a] = pi * (r2_3 * dr2_dt - r1_3 * dr1_dt) 59 | outputs['J', 'r'][a, a] = 2 * pi * (r2_3 * dr2_dr - r1_3 * dr1_dr) 60 | outputs['J', 'thick'][a, a] = 2 * pi * (r2_3 * dr2_dt - r1_3 * dr1_dt) 61 | 62 | return outputs 63 | 64 | 65 | -------------------------------------------------------------------------------- /problems/unsteady_vlm/post_block_inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/unsteady_vlm/post_block_inputs.png -------------------------------------------------------------------------------- /problems/unsteady_vlm/readme.md: -------------------------------------------------------------------------------- 1 | # Original Request From @shamsheersc19 2 | 3 | ## Background: 4 | * [OpenAeroStruct (OAS)][0] is gaining popularity and is often advertised as a good example of a practical application that takes advantage of the strengths of OpenMDAO. 5 | * A few years ago, Giovanni Pesare implemented an unsteady VLM solver with OAS with a little direction from @shamsheersc19 (see attached thesis). 6 | * In order to take advantage of OpenMDAO's analytic derivatives, the only clear path was to stamp out copies 7 | of the time-varying states for each time instance inside an OpenMDAO model. 8 | * This seems like a less than ideal way to set it up, are there better ways? 9 | 10 | # Structure of an unsteady problem 11 | 12 | As Giovanni Pesare points out in his thesis, an unsteady problem is made up of three parts: 13 | 14 | 1) A block of computations before the unsteady part 15 | 2) The unsteady part 16 | 3) A block of computations after the unsteady part 17 | 18 |  19 | 20 | In this code, we can see that the before block includes the groups named `tube`, `mesh`, `matricies`, and `eig`. 21 | The entire unsteady block is contained inside the `coupled` group (that's a weird name for that group since nothing is coupled, but we wanted to change the code as little as possible so we left it.) 22 | The after block is contained in the `vlm_funcs` group. 23 | 24 | The waterfall nature of this time loop clearly shows up in the N2 diagram above where each time step feeds directly into the next. 25 | This style of unsteady model is not the most efficient way to set it up. 26 | You have to keep all times in memory at once, but you're also constrained to evaluating all times in sequence. 27 | 28 | A better approach would be to wrap the time-loop into a component that does the time-stepping for you with a sub-problem to hold the guts of the time-varying calculation. 29 | 30 | ## Converting the analysis into a time-loop 31 | 32 | In order to break the model down we needed to map the I/O from the *before block* into the time loop, 33 | the i/o between each time step, and the i/o to the *after block* from the time loop. 34 | 35 | ### I/O: Before block -> time loop 36 | Using the N2, we can see what the various inputs variables are: `dt`, `K_matrix`, `mesh`, `loads`, `rho`, `alpha`, `v` 37 | 38 |  39 | 40 | ### I/O: time_i -> time_i+1 41 | 42 | Again, using the N2 diagram we can find the necessary data passing: `circ_{i}`, `circ_wake_{i}`, `wake_mesh_{i}`, `sigma_x_{i}`, `disp_{i}` 43 |  44 | 45 | ### I/O: time loop -> after block 46 | 47 | Lastly we have the inputs from the time-loop to the final calculation block: 48 | `S_ref` (which is a bit weird, because it comes from time step 0) 49 | `sec_L_19`, an `sec_D_19`. 50 | 51 |  52 | 53 | 54 | ### The model is much simpler with a for loop 55 | 56 | At the run-script level, the changes are extremely minor. 57 | Commented out parts are from the non-for loop part. 58 | 59 | ```python 60 | 61 | # Time loop 62 | # coupled = om.Group() 63 | # for t in range(num_dt): 64 | # name_step = 'step_%d'%t 65 | # coupled.add_subsystem(name_step, 66 | # SingleStep(num_x, num_y_sym, num_w, E, G, mrho, fem_origin, SBEIG, t), 67 | # promotes=['*']) 68 | 69 | # root.add_subsystem('coupled', 70 | # coupled, 71 | # promotes=['*']) 72 | 73 | root.add_subsystem('time_loop', TimeLoopComp(num_x, num_y_sym, num_w, E, G, mrho, fem_origin, SBEIG, num_dt), 74 | promotes=['*']) 75 | ``` 76 | The change in the N2 is much more visible. 77 | The waterfall of time steps is now gone. 78 |  79 | 80 | You can see the full code to make this work [here](./timeloop.py) in the `TimeLoop` class. 81 | The key bit is here, where you should notice that the for-loop that was commented out of the run-script has been transferred into the run and there are now a set of sub-problems. 82 | 83 | ```python 84 | def compute(self, inputs, outputs): 85 | 86 | num_dt = self.num_times 87 | p0 = self._prob0 88 | 89 | p0['dt'] = inputs['dt'] 90 | p0['K_matrix'] = inputs['K_matrix'] 91 | p0['mesh'] = inputs['mesh'] 92 | p0['loads'] = inputs['loads'] 93 | p0['rho'] = inputs['rho'] 94 | p0['alpha'] = inputs['alpha'] 95 | p0['v'] = inputs['v'] 96 | 97 | p0.run_model() 98 | 99 | circ = p0['circ_0'].copy() 100 | # circ_wake = p0['circ_wake_0'].copy() 101 | wake_mesh = p0['wake_mesh_1'].copy() 102 | sigma_x = p0['sigma_x_0'].copy() 103 | disp = p0['disp_0'] 104 | 105 | for t in range(1,num_dt): 106 | 107 | pi = om.Problem() 108 | 109 | # NOTE: The size of the wake mesh grows with each iteration, so we need to re-do setup each time 110 | pi.model.add_subsystem('step', SingleStep(self.num_x, self.num_y, self.num_w, self.E, 111 | self.G, self.mrho, self.fem_origin, self.SBEIG, t=t), 112 | promotes=['*']) 113 | 114 | pi.setup() 115 | pi.final_setup() 116 | 117 | pi['dt'] = inputs['dt'] 118 | pi['K_matrix'] = inputs['K_matrix'] 119 | pi['mesh'] = inputs['mesh'] 120 | pi['loads'] = inputs['loads'] 121 | pi['rho'] = inputs['rho'] 122 | pi['alpha'] = inputs['alpha'] 123 | pi['v'] = inputs['v'] 124 | 125 | pi[f'circ_{t-1}'] = circ 126 | if t > 1: 127 | pi[f'circ_wake_{t-1}'] = circ_wake 128 | pi[f'wake_mesh_{t}'] = wake_mesh 129 | pi[f'sigma_x_{t-1}'] = sigma_x 130 | pi[f'disp_{t-1}'] = disp 131 | 132 | pi.run_model() 133 | 134 | # save the data to pass to the next time instance 135 | circ = pi[f'circ_{t}'].copy() 136 | circ_wake = pi[f'circ_wake_{t}'].copy() 137 | wake_mesh = pi[f'wake_mesh_{t+1}'].copy() 138 | sigma_x = pi[f'sigma_x_{t}'].copy() 139 | disp = pi[f'disp_{t}'].copy() 140 | 141 | outputs['S_ref'] = p0['S_ref'] 142 | outputs['sec_L_19'] = pi['sec_L_19'] 143 | outputs['sec_D_19'] = pi['sec_D_19'] 144 | ``` 145 | 146 | ### What about derivatives? 147 | This is one area where the waterfall is a bit easier to work with. 148 | By chaining everything the way that the original run script did, OpenMDAO handled the derivatives for you. 149 | When you move the for loop into the component, you're now responsible for the derivatives yourself. 150 | 151 | We didn't do the derivatives here, mostly for lack of time. 152 | We admit that they are a bit tricky, and you need to be fairly experienced with derivatives to handle them efficiently and in a way that won't negate all the memory savings from pushing the for loop down into 153 | the component in the first place. 154 | 155 | If you interested in this topic, check out the [docs on the matrix-free total derivatives](http://openmdao.org/twodocs/versions/3.4.0/features/core_features/working_with_derivatives/total_compute_jacvec_product.html). 156 | 157 | In future updates to Dymos, we are looking at building in this kind of time-stepping approach. 158 | 159 | 160 | ### Notes on the use of sub-problems in this application 161 | 162 | This for-loop uses a lot of sub-problems. 163 | There is one for time 0, then one more for every time step after that. 164 | The one for time 0 is persistent, but the other times get discarded as you move through the loop. 165 | 166 | This multi-problem structure did not have to be here in general, but due to specifics of how the original code was implemented (the `SingleStep` group changed a bit depending on the time-step) it was necessary to preserve multiple instances in the for loop as well. 167 | 168 | 169 | -------------------------------------------------------------------------------- /problems/unsteady_vlm/run.py: -------------------------------------------------------------------------------- 1 | """ Code to run aerostructural analysis and evaluate flutter velocity. 2 | Call as `python run_aerostruct.py` to run a single analysis. """ 3 | 4 | import numpy 5 | from time import time 6 | 7 | import openmdao.api as om 8 | from geometry import GeometryMesh, gen_crm_mesh, gen_mesh 9 | from materials import MaterialsTube 10 | from spacialbeam import SpatialBeamMatrices, SpatialBeamEIG, radii 11 | from timeloop import SingleStep 12 | from uvlm import UVLMFunctionals 13 | import warnings 14 | warnings.filterwarnings("ignore") 15 | 16 | ####################################### 17 | # Define loop for parametric analysis # 18 | ####################################### 19 | num_of_points = 1 20 | num_of_angles = 1 21 | velocities_vect = numpy.linspace(10.0, 50.0, num=num_of_points) 22 | #num_dt_vect = numpy.linspace(100, 400, num=num_of_points) 23 | #zeta_vect = numpy.linspace(0., 1., num=num_of_points) 24 | alpha_vect = numpy.linspace(2.0, 2.0, num=num_of_angles) 25 | 26 | for p in range(num_of_angles): 27 | for i in range(num_of_points): 28 | 29 | v = velocities_vect[i] 30 | #v = 200. # flow speed [m/s] 31 | v = float(v) 32 | 33 | ############################################ 34 | # Define parameters for simulation in time # 35 | ############################################ 36 | #num_dt = int(num_dt_vect[i]) 37 | num_dt = 20 # number of time steps 38 | final_t = 1. # time-simulation duration [s] 39 | num_w = 20 # number of (timewise) deforming wake elements 40 | 41 | ##################################### 42 | # Define the aerodynamic parameters # 43 | ##################################### 44 | rho = 0.0889 # air density [kg/m^3] 45 | alpha = float(alpha_vect[p]) 46 | #alpha = 0.5 # angle of attack [deg.] 47 | CL0 = 0. 48 | CD0 = 0. 49 | 50 | ############################################# 51 | # Define wing geometry and aerodynamic mesh # 52 | ############################################# 53 | CRM = 0 54 | 55 | if CRM: # Use the CRM wing model 56 | wing = 'CRM' 57 | npi = 2 # number of points inboard 58 | npo = 3 # number of points outboard 59 | full_wing_mesh = gen_crm_mesh(npi, npo, num_x=5) 60 | num_x, num_y = full_wing_mesh.shape[:2] 61 | num_y_sym = numpy.int((num_y + 1) / 2) 62 | span = 58.7630524 # [m] 63 | num_twist = 5 64 | 65 | else: # Use the rectangular wing model 66 | wing = 'RECT' 67 | num_x = 3 # number of spanwise nodes 68 | num_y = 11 # number of chordwise nodes 69 | num_y_sym = numpy.int((num_y + 1) / 2) 70 | span = 32. # [m] 71 | chord = 1. # [m] 72 | cosine_spacing = 0. 73 | full_wing_mesh = gen_mesh(num_x, num_y, span, chord, cosine_spacing) 74 | num_twist = numpy.max([int((num_y - 1) / 5), 5]) 75 | 76 | half_wing_mesh = full_wing_mesh[:, (num_y_sym-1):, :] 77 | 78 | ########################## 79 | # Define beam properties # 80 | ########################## 81 | r = radii(half_wing_mesh)/5 # beam radius 82 | thick = r / 5 # beam thickness 83 | fem_origin = 0.5 # elastic axis position along the chord 84 | #zeta = zeta_vect[i] 85 | zeta = 0.0 # damping percentual coeff. 86 | zeta = float(zeta) 87 | 88 | E = 70.e9 # [Pa] 89 | poisson = 0.3 90 | G = E / (2 * (1 + poisson)) 91 | mrho = 2800. # [kg/m^3] 92 | 93 | 94 | #################################### 95 | # Define the independent variables # 96 | #################################### 97 | indep_vars = [ 98 | ('span', span), 99 | ('twist', numpy.zeros(num_twist)), 100 | ('dihedral', 0.), 101 | ('sweep', 0.), 102 | ('taper', 1.0), 103 | ('v', v), 104 | ('alpha', alpha), 105 | ('rho', rho), 106 | ('r', r), 107 | ('thick', thick), 108 | ('zeta', zeta)] 109 | 110 | ####################### 111 | # Calls of components # 112 | ####################### 113 | root = om.Group() 114 | 115 | # Components before the time loop 116 | 117 | for name, val in indep_vars: 118 | root.set_input_defaults(name, val) 119 | root.add_subsystem('tube', 120 | MaterialsTube(n=num_y_sym), 121 | promotes=['*']) 122 | root.add_subsystem('mesh', 123 | GeometryMesh(mesh=full_wing_mesh, num_twist=num_twist), 124 | promotes=['*']) 125 | root.add_subsystem('matrices', 126 | SpatialBeamMatrices(nx=num_x, n=num_y_sym, E=E, G=G, mrho=mrho, 127 | fem_origin=fem_origin), 128 | promotes=['*']) 129 | SBEIG = SpatialBeamEIG(n=num_y_sym, num_dt=num_dt, final_t=final_t) 130 | root.add_subsystem('eig', 131 | SBEIG, 132 | promotes=['*']) 133 | 134 | # Time loop 135 | coupled = om.Group() 136 | for t in range(num_dt): 137 | name_step = 'step_%d'%t 138 | coupled.add_subsystem(name_step, 139 | SingleStep(num_x, num_y_sym, num_w, E, G, mrho, fem_origin, SBEIG, t), 140 | promotes=['*']) 141 | 142 | root.add_subsystem('coupled', 143 | coupled, 144 | promotes=['*']) 145 | 146 | # Components after the time loop 147 | root.add_subsystem('vlm_funcs', 148 | UVLMFunctionals(num_x, num_y_sym, CL0, CD0, num_dt), 149 | promotes=['*']) 150 | 151 | ################### 152 | # Run the program # 153 | ################### 154 | 155 | prob = om.Problem() 156 | prob.model = root 157 | prob.set_solver_print() 158 | 159 | # Setup data recording 160 | # name_data = '%s_v%.2f_ndt%.0f_damp%.2f_alpha%.1f_w%.0f'%(wing, v, num_dt, zeta, alpha, num_w) 161 | # db_name = 'results/flutter/db/%s'%(name_data) 162 | # prob.driver.add_recorder(om.SqliteRecorder(db_name)) 163 | 164 | prob.setup() 165 | om.n2(prob, outfile="aerostruct.html", show_browser=False) 166 | st = time() 167 | prob.run_model() 168 | 169 | prob.model.coupled.step_0.list_inputs(print_arrays=True, prom_name=True) 170 | 171 | print("run time", time() - st) 172 | print("number of steps =", num_dt) 173 | print("dt =", prob['dt']) 174 | print("CL =", prob['CL'], "; CD =", prob['CD']) 175 | -------------------------------------------------------------------------------- /problems/unsteady_vlm/run_for_loop.py: -------------------------------------------------------------------------------- 1 | """ Code to run aerostructural analysis and evaluate flutter velocity. 2 | Call as `python run_aerostruct.py` to run a single analysis. """ 3 | 4 | import numpy 5 | from time import time 6 | 7 | import openmdao.api as om 8 | from geometry import GeometryMesh, gen_crm_mesh, gen_mesh 9 | from materials import MaterialsTube 10 | from spacialbeam import SpatialBeamMatrices, SpatialBeamEIG, radii 11 | from timeloop import SingleStep, TimeLoopComp 12 | from uvlm import UVLMFunctionals 13 | import warnings 14 | warnings.filterwarnings("ignore") 15 | 16 | ####################################### 17 | # Define loop for parametric analysis # 18 | ####################################### 19 | num_of_points = 1 20 | num_of_angles = 1 21 | velocities_vect = numpy.linspace(10.0, 50.0, num=num_of_points) 22 | #num_dt_vect = numpy.linspace(100, 400, num=num_of_points) 23 | #zeta_vect = numpy.linspace(0., 1., num=num_of_points) 24 | alpha_vect = numpy.linspace(2.0, 2.0, num=num_of_angles) 25 | 26 | for p in range(num_of_angles): 27 | for i in range(num_of_points): 28 | 29 | v = velocities_vect[i] 30 | #v = 200. # flow speed [m/s] 31 | v = float(v) 32 | 33 | ############################################ 34 | # Define parameters for simulation in time # 35 | ############################################ 36 | #num_dt = int(num_dt_vect[i]) 37 | num_dt = 20 # number of time steps 38 | final_t = 1. # time-simulation duration [s] 39 | num_w = 20 # number of (timewise) deforming wake elements 40 | 41 | ##################################### 42 | # Define the aerodynamic parameters # 43 | ##################################### 44 | rho = 0.0889 # air density [kg/m^3] 45 | alpha = float(alpha_vect[p]) 46 | #alpha = 0.5 # angle of attack [deg.] 47 | CL0 = 0. 48 | CD0 = 0. 49 | 50 | ############################################# 51 | # Define wing geometry and aerodynamic mesh # 52 | ############################################# 53 | CRM = 0 54 | 55 | if CRM: # Use the CRM wing model 56 | wing = 'CRM' 57 | npi = 2 # number of points inboard 58 | npo = 3 # number of points outboard 59 | full_wing_mesh = gen_crm_mesh(npi, npo, num_x=5) 60 | num_x, num_y = full_wing_mesh.shape[:2] 61 | num_y_sym = numpy.int((num_y + 1) / 2) 62 | span = 58.7630524 # [m] 63 | num_twist = 5 64 | 65 | else: # Use the rectangular wing model 66 | wing = 'RECT' 67 | num_x = 3 # number of spanwise nodes 68 | num_y = 11 # number of chordwise nodes 69 | num_y_sym = numpy.int((num_y + 1) / 2) 70 | span = 32. # [m] 71 | chord = 1. # [m] 72 | cosine_spacing = 0. 73 | full_wing_mesh = gen_mesh(num_x, num_y, span, chord, cosine_spacing) 74 | num_twist = numpy.max([int((num_y - 1) / 5), 5]) 75 | 76 | half_wing_mesh = full_wing_mesh[:, (num_y_sym-1):, :] 77 | 78 | ########################## 79 | # Define beam properties # 80 | ########################## 81 | r = radii(half_wing_mesh)/5 # beam radius 82 | thick = r / 5 # beam thickness 83 | fem_origin = 0.5 # elastic axis position along the chord 84 | #zeta = zeta_vect[i] 85 | zeta = 0.0 # damping percentual coeff. 86 | zeta = float(zeta) 87 | 88 | E = 70.e9 # [Pa] 89 | poisson = 0.3 90 | G = E / (2 * (1 + poisson)) 91 | mrho = 2800. # [kg/m^3] 92 | 93 | 94 | #################################### 95 | # Define the independent variables # 96 | #################################### 97 | indep_vars = [ 98 | ('span', span), 99 | ('twist', numpy.zeros(num_twist)), 100 | ('dihedral', 0.), 101 | ('sweep', 0.), 102 | ('taper', 1.0), 103 | ('v', v), 104 | ('alpha', alpha), 105 | ('rho', rho), 106 | ('r', r), 107 | ('thick', thick), 108 | ('zeta', zeta)] 109 | 110 | ####################### 111 | # Calls of components # 112 | ####################### 113 | root = om.Group() 114 | 115 | # Components before the time loop 116 | 117 | for name, val in indep_vars: 118 | root.set_input_defaults(name, val) 119 | root.add_subsystem('tube', 120 | MaterialsTube(n=num_y_sym), 121 | promotes=['*']) 122 | root.add_subsystem('mesh', 123 | GeometryMesh(mesh=full_wing_mesh, num_twist=num_twist), 124 | promotes=['*']) 125 | root.add_subsystem('matrices', 126 | SpatialBeamMatrices(nx=num_x, n=num_y_sym, E=E, G=G, mrho=mrho, 127 | fem_origin=fem_origin), 128 | promotes=['*']) 129 | SBEIG = SpatialBeamEIG(n=num_y_sym, num_dt=num_dt, final_t=final_t) 130 | root.add_subsystem('eig', 131 | SBEIG, 132 | promotes=['*']) 133 | 134 | # Time loop 135 | # coupled = om.Group() 136 | # for t in range(num_dt): 137 | # name_step = 'step_%d'%t 138 | # coupled.add_subsystem(name_step, 139 | # SingleStep(num_x, num_y_sym, num_w, E, G, mrho, fem_origin, SBEIG, t), 140 | # promotes=['*']) 141 | 142 | # root.add_subsystem('coupled', 143 | # coupled, 144 | # promotes=['*']) 145 | 146 | root.add_subsystem('time_loop', TimeLoopComp(num_x, num_y_sym, num_w, E, G, mrho, fem_origin, SBEIG, num_dt), 147 | promotes=['*']) 148 | 149 | # Components after the time loop 150 | root.add_subsystem('vlm_funcs', 151 | UVLMFunctionals(num_x, num_y_sym, CL0, CD0, num_dt), 152 | promotes=['*']) 153 | 154 | ################### 155 | # Run the program # 156 | ################### 157 | 158 | prob = om.Problem() 159 | prob.model = root 160 | prob.set_solver_print() 161 | 162 | # Setup data recording 163 | # name_data = '%s_v%.2f_ndt%.0f_damp%.2f_alpha%.1f_w%.0f'%(wing, v, num_dt, zeta, alpha, num_w) 164 | # db_name = 'results/flutter/db/%s'%(name_data) 165 | # prob.driver.add_recorder(om.SqliteRecorder(db_name)) 166 | 167 | prob.setup() 168 | om.n2(prob, outfile="aerostruct_for_loop.html", show_browser=False) 169 | st = time() 170 | prob.run_model() 171 | 172 | print("run time", time() - st) 173 | print("number of steps =", num_dt) 174 | print("dt =", prob['dt']) 175 | print("CL =", prob['CL'], "; CD =", prob['CD']) 176 | -------------------------------------------------------------------------------- /problems/unsteady_vlm/thesis_Giovanni_Pesare.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/unsteady_vlm/thesis_Giovanni_Pesare.pdf -------------------------------------------------------------------------------- /problems/unsteady_vlm/time_loop_comp.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/problems/unsteady_vlm/time_loop_comp.py -------------------------------------------------------------------------------- /problems/unsteady_vlm/timeloop.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | 3 | from transfer import TransferDisplacements, TransferLoads 4 | from uvlm import UVLMStates 5 | from spacialbeam import SpatialBeamStates 6 | 7 | 8 | class SingleStep(om.Group): 9 | """ Group that contains components that have to be run for each time step """ 10 | 11 | def __init__(self, num_x, num_y, num_w, E, G, mrho, fem_origin, SBEIG, t): 12 | super(SingleStep, self).__init__() 13 | 14 | name_def_mesh = 'def_mesh_%d'%t 15 | name_vlmstates = 'vlmstates_%d'%t 16 | name_loads = 'loads_%d'%t 17 | name_spatialbeamstates = 'spatialbeamstates_%d'%t 18 | 19 | self.add_subsystem(name_def_mesh, 20 | TransferDisplacements(nx=num_x, n=num_y, t=t, fem_origin=fem_origin), 21 | promotes=['*']) 22 | self.add_subsystem(name_vlmstates, 23 | UVLMStates(num_x, num_y, num_w, t), 24 | promotes=['*']) 25 | self.add_subsystem(name_loads, 26 | TransferLoads(nx=num_x, n=num_y, t=t, fem_origin=fem_origin), 27 | promotes=['*']) 28 | self.add_subsystem(name_spatialbeamstates, 29 | SpatialBeamStates(num_x, num_y, E, G, mrho, SBEIG, t), 30 | promotes=['*']) 31 | 32 | 33 | 34 | class TimeLoopComp(om.ExplicitComponent): 35 | 36 | def __init__(self, num_x, num_y, num_w, E, G, mrho, fem_origin, SBEIG, num_times): 37 | 38 | self.num_x = num_x 39 | self.num_y = num_y 40 | self.num_w = num_w 41 | self.E = E 42 | self.G = G 43 | self.mrho = mrho 44 | self.fem_origin = fem_origin 45 | self.SBEIG = SBEIG 46 | self.num_times = num_times 47 | 48 | super().__init__() 49 | 50 | 51 | 52 | def setup(self): 53 | 54 | self.add_input('dt') 55 | self.add_input('K_matrix', shape=(self.num_y*6,self.num_y*6)) 56 | self.add_input('mesh', shape=(self.num_x, self.num_y, 3)) 57 | self.add_input('loads', val=0.) 58 | self.add_input('rho') 59 | self.add_input('alpha') 60 | self.add_input('v') 61 | 62 | self.add_output('S_ref') 63 | self.add_output('sec_L_19', shape=(self.num_x-1, self.num_y-1)) 64 | self.add_output('sec_D_19', shape=(self.num_x-1, self.num_y-1)) 65 | 66 | 67 | p0 = self._prob0 = om.Problem() 68 | 69 | # NOTE: In the original code `t` was used in the variable naming scheme 70 | # and there were some slight differences between I/O from t=0 and t>0 71 | # so we need to have both 72 | p0.model = SingleStep(self.num_x, self.num_y, self.num_w, self.E, 73 | self.G, self.mrho, self.fem_origin, self.SBEIG, t=0) 74 | 75 | p0.setup() 76 | p0.final_setup() 77 | 78 | 79 | 80 | def compute(self, inputs, outputs): 81 | 82 | num_dt = self.num_times 83 | p0 = self._prob0 84 | 85 | p0['dt'] = inputs['dt'] 86 | p0['K_matrix'] = inputs['K_matrix'] 87 | p0['mesh'] = inputs['mesh'] 88 | p0['loads'] = inputs['loads'] 89 | p0['rho'] = inputs['rho'] 90 | p0['alpha'] = inputs['alpha'] 91 | p0['v'] = inputs['v'] 92 | 93 | p0.run_model() 94 | 95 | circ = p0['circ_0'].copy() 96 | # circ_wake = p0['circ_wake_0'].copy() 97 | wake_mesh = p0['wake_mesh_1'].copy() 98 | sigma_x = p0['sigma_x_0'].copy() 99 | disp = p0['disp_0'] 100 | 101 | for t in range(1,num_dt): 102 | 103 | pi = om.Problem() 104 | 105 | # NOTE: The size of the wake mesh grows with each iteration, so we need to re-do setup each time 106 | pi.model.add_subsystem('step', SingleStep(self.num_x, self.num_y, self.num_w, self.E, 107 | self.G, self.mrho, self.fem_origin, self.SBEIG, t=t), 108 | promotes=['*']) 109 | 110 | pi.setup() 111 | pi.final_setup() 112 | 113 | pi['dt'] = inputs['dt'] 114 | pi['K_matrix'] = inputs['K_matrix'] 115 | pi['mesh'] = inputs['mesh'] 116 | pi['loads'] = inputs['loads'] 117 | pi['rho'] = inputs['rho'] 118 | pi['alpha'] = inputs['alpha'] 119 | pi['v'] = inputs['v'] 120 | 121 | pi[f'circ_{t-1}'] = circ 122 | if t > 1: 123 | pi[f'circ_wake_{t-1}'] = circ_wake 124 | pi[f'wake_mesh_{t}'] = wake_mesh 125 | pi[f'sigma_x_{t-1}'] = sigma_x 126 | pi[f'disp_{t-1}'] = disp 127 | 128 | pi.run_model() 129 | 130 | # save the data to pass to the next time instance 131 | circ = pi[f'circ_{t}'].copy() 132 | circ_wake = pi[f'circ_wake_{t}'].copy() 133 | wake_mesh = pi[f'wake_mesh_{t+1}'].copy() 134 | sigma_x = pi[f'sigma_x_{t}'].copy() 135 | disp = pi[f'disp_{t}'].copy() 136 | 137 | outputs['S_ref'] = p0['S_ref'] 138 | outputs['sec_L_19'] = pi['sec_L_19'] 139 | outputs['sec_D_19'] = pi['sec_D_19'] 140 | -------------------------------------------------------------------------------- /problems/unsteady_vlm/transfer.py: -------------------------------------------------------------------------------- 1 | """ Defines the transfer component to couple aero and struct analyses. """ 2 | 3 | import numpy 4 | 5 | import openmdao.api as om 6 | 7 | 8 | class TransferDisplacements(om.ExplicitComponent): 9 | """ Performs displacement transfer """ 10 | 11 | def initialize(self): 12 | self.options.declare("nx") 13 | self.options.declare("n") 14 | self.options.declare("t") 15 | self.options.declare("fem_origin") 16 | 17 | def setup(self): 18 | nx = self.options["nx"] 19 | n = self.options["n"] 20 | t = self.options["t"] 21 | if self.options["fem_origin"] is None: 22 | fem_origin = 0.35 23 | else: 24 | fem_origin = self.options["fem_origin"] 25 | 26 | lt = t - 1 # previous time step (last dt) 27 | disp_lt = 'disp_%d'%lt 28 | def_mesh_t = 'def_mesh_%d'%t 29 | 30 | self.fem_origin = fem_origin 31 | 32 | self.add_output(def_mesh_t, val=numpy.zeros((nx, n, 3))) 33 | self.add_input('mesh', val=numpy.zeros((nx, n, 3))) 34 | 35 | if t > 0: 36 | self.add_input(disp_lt, val=numpy.zeros((n, 6))) 37 | 38 | self.num_x = nx 39 | self.num_y = n 40 | self.t = t 41 | 42 | self.disp = numpy.zeros((n, 6), dtype="complex") 43 | self.Smesh = numpy.zeros((nx, n, 3), dtype="complex") 44 | self.def_mesh = numpy.zeros((nx, n, 3), dtype="complex") 45 | 46 | self.disp_lt = disp_lt 47 | self.def_mesh_t = def_mesh_t 48 | 49 | def setup_partials(self): 50 | self.declare_partials('*', '*', method='cs') 51 | 52 | def compute(self, inputs, outputs): 53 | 54 | nx = self.num_x 55 | n = self.num_y 56 | t = self.t 57 | disp_lt = self.disp_lt 58 | def_mesh_t = self.def_mesh_t 59 | 60 | print("time step ", t) 61 | 62 | if t > 0: 63 | self.disp = inputs[disp_lt] 64 | 65 | w = self.fem_origin 66 | ref_curve = (1-w) * inputs['mesh'][0, :, :] + w * inputs['mesh'][-1, :, :] 67 | 68 | for ind in range(nx): 69 | self.Smesh[ind, :, :] = inputs['mesh'][ind, :, :] - ref_curve 70 | 71 | cos, sin = numpy.cos, numpy.sin 72 | for ind in range(n): 73 | dx, dy, dz, rx, ry, rz = self.disp[ind, :] 74 | 75 | # 1 eye from the axis rotation matrices 76 | # -3 eye from subtracting Smesh three times 77 | T = -2 * numpy.eye(3, dtype="complex") 78 | T[ 1:, 1:] += [[cos(rx), sin(rx)], [-sin(rx), cos(rx)]] 79 | T[::2, ::2] += [[cos(ry),-sin(ry)], [ sin(ry), cos(ry)]] 80 | T[ :2, :2] += [[cos(rz), sin(rz)], [-sin(rz), cos(rz)]] 81 | 82 | self.def_mesh[:, ind, :] += self.Smesh[:, ind, :].dot(T) 83 | self.def_mesh[:, ind, 0] += dx 84 | self.def_mesh[:, ind, 1] += dy 85 | self.def_mesh[:, ind, 2] += dz 86 | 87 | outputs[def_mesh_t] = self.def_mesh + inputs['mesh'] 88 | 89 | 90 | class TransferLoads(om.ExplicitComponent): 91 | """ Performs load transfer """ 92 | 93 | def initialize(self): 94 | self.options.declare("nx") 95 | self.options.declare("n") 96 | self.options.declare("t") 97 | self.options.declare("fem_origin") 98 | 99 | def setup(self): 100 | nx = self.options["nx"] 101 | n = self.options["n"] 102 | t = self.options["t"] 103 | if self.options["fem_origin"] is None: 104 | fem_origin = 0.35 105 | else: 106 | fem_origin = self.options["fem_origin"] 107 | 108 | # t is the actual time step (actual dt) 109 | def_mesh_t = 'def_mesh_%d'%t 110 | sec_forces_t = 'sec_forces_%d'%t 111 | loads_t = 'loads_%d'%t 112 | 113 | self.add_input(def_mesh_t, val=numpy.zeros((nx, n, 3))) 114 | self.add_input(sec_forces_t, val=numpy.zeros((n-1, 3), dtype="complex")) 115 | self.add_output(loads_t, val=numpy.zeros((n, 6), dtype="complex")) 116 | 117 | self.n = n 118 | self.t = t 119 | self.fem_origin = fem_origin 120 | 121 | self.moment = numpy.zeros((n-1, 3), dtype="complex") 122 | self.loads = numpy.zeros((n, 6), dtype="complex") 123 | 124 | self.def_mesh_t = def_mesh_t 125 | self.sec_forces_t = sec_forces_t 126 | self.loads_t = loads_t 127 | 128 | def setup_partials(self): 129 | self.declare_partials(of='*', wrt='*') 130 | 131 | def compute(self, inputs, outputs): 132 | 133 | def_mesh_t = self.def_mesh_t 134 | sec_forces_t = self.sec_forces_t 135 | loads_t = self.loads_t 136 | 137 | mesh = inputs[def_mesh_t] 138 | sec_forces = inputs[sec_forces_t] 139 | 140 | w = 0.25 141 | a_pts = 0.5 * (1-w) * mesh[:-1, :-1, :] + \ 142 | 0.5 * w * mesh[1:, :-1, :] + \ 143 | 0.5 * (1-w) * mesh[:-1, 1:, :] + \ 144 | 0.5 * w * mesh[1:, 1:, :] 145 | 146 | w = self.fem_origin 147 | s_pts = 0.5 * (1-w) * mesh[:-1, :-1, :] + \ 148 | 0.5 * w * mesh[1:, :-1, :] + \ 149 | 0.5 * (1-w) * mesh[:-1, 1:, :] + \ 150 | 0.5 * w * mesh[1:, 1:, :] 151 | 152 | for ind in range(numpy.int(self.n-1)): 153 | r = a_pts[0, ind, :] - s_pts[0, ind, :] 154 | F = sec_forces[ind, :] 155 | self.moment[ind, :] = numpy.cross(r, F) 156 | 157 | self.loads[:-1, :3] += 0.5 * sec_forces[:, :] 158 | self.loads[ 1:, :3] += 0.5 * sec_forces[:, :] 159 | self.loads[:-1, 3:] += 0.5 * self.moment[:, :] 160 | self.loads[ 1:, 3:] += 0.5 * self.moment[:, :] 161 | 162 | outputs[loads_t] = self.loads 163 | -------------------------------------------------------------------------------- /solution_approaches/OpenVSP_OpenMDAO/diagram_derivs_as_outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/RevHack2020/3a1e8af137955f9f93671126fe007d02c6fbd0a3/solution_approaches/OpenVSP_OpenMDAO/diagram_derivs_as_outputs.png -------------------------------------------------------------------------------- /solution_approaches/OpenVSP_OpenMDAO/install_vsp_mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION=3.21.2 3 | # MANUAL PREREQUISITES: 4 | # Homebrew 5 | which brew > /dev/null 2>&1 || { 6 | Homebrew not found, obtain from https://brew.sh/ 7 | exit 1 8 | } 9 | 10 | # Activate the Python environment you want to work from; pip should be available 11 | which pip > /dev/null 2>&1 || { 12 | Python pip not found, please install/activate an environment first. 13 | exit 1 14 | } 15 | 16 | if [ -n "$CONDA_PREFIX" ]; then 17 | INST_PREFIX=$CONDA_PREFIX 18 | elif 19 | [ -n "$VIRTUAL_ENV" ]; then 20 | INST_PREFIX=$VIRTUAL_ENV 21 | else 22 | INST_PREFIX=$HOME/opt 23 | fi 24 | 25 | set -e 26 | trap 'cmd_failed $? $LINENO' EXIT 27 | 28 | cmd_failed() { 29 | if [ "$1" != "0" ]; then 30 | echo "FATAL ERROR: The command failed with error $1 at line $2." 31 | exit 1 32 | fi 33 | } 34 | 35 | # Install dependencies: 36 | brew install graphviz doxygen libjpeg git-gui cmake gfortran 37 | brew cask install basictex 38 | which swig > /dev/null 2>&1 || pip install swig 39 | 40 | # Make main directory and clone OpenVSP source 41 | mkdir -p OpenVSP; cd OpenVSP 42 | mkdir -p repo build buildlibs 43 | [ ! -d repo/.git ] && git clone -b OpenVSP_${VERSION} --depth=1 https://github.com/OpenVSP/OpenVSP.git repo 44 | 45 | # Prepare build files for the libraries and build them: 46 | cd buildlibs 47 | cmake -DPYTHON_INCLUDE_DIR=$(python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())") \ 48 | -DPYTHON_LIBRARY=$(python -c "import distutils.sysconfig as sysconfig; print(sysconfig.get_config_var('LIBDIR'))") \ 49 | ../repo/Libraries -DCMAKE_BUILD_TYPE=Release 50 | make -j8 51 | 52 | # Set up and build OpenVSP: 53 | cd .. 54 | BUILD_LIBS_PATH=`pwd` 55 | cd build 56 | cmake ../repo/src/ \ 57 | -DVSP_LIBRARY_PATH=${BUILD_LIBS_PATH}/buildlibs \ 58 | -DPYTHON_INCLUDE_DIR=$(python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())") \ 59 | -DPYTHON_LIBRARY=$(python -c "import distutils.sysconfig as sysconfig; print(sysconfig.get_config_var('LIBDIR'))") \ 60 | -DCMAKE_BUILD_TYPE=Release 61 | make -j8 62 | 63 | # Make a zip file: 64 | make package 65 | 66 | # Install OpenVSP Python Packages 67 | CHARM_BUILD=CHARM/charm/charm_fortran_utilities/build 68 | pushd _CPack_Packages/MacOS/ZIP/OpenVSP-${VERSION}-MacOS/python 69 | mkdir -p $CHARM_BUILD 70 | pushd $CHARM_BUILD 71 | gfortran -o bg2charm_thick ../src/bg2charm_thick.f 72 | popd 73 | 74 | # If you are not going to modify the packages: 75 | pip install -r requirements.txt 76 | # If you want to modify the python packages 77 | # pip install -r requirements-dev.txt 78 | pushd .. 79 | cp vspaero vspscript vspslicer vspviewer $INST_PREFIX/bin 80 | popd 81 | popd 82 | python -c 'import openvsp' || { 83 | echo "OpenVSP Python Packages did not install correctly, cannot import openvsp" 84 | exit 1 85 | } 86 | -------------------------------------------------------------------------------- /solution_approaches/no_driver.md: -------------------------------------------------------------------------------- 1 | # Using OpenMDAO's Driver interface is not a requirement 2 | 3 | Believe it or not, the Driver interface and associated driver classes are not critical infrastructure for OpenMDAO. 4 | That sounds weird since drivers provide the "O" in MDAO, but weird or not it is true. 5 | The Driver interface is nice to have, because it handles a bunch of subtle details for you and provides a single generic interface that you can count on. 6 | "Nice to have" is not the same thing as "critical infrastructure" though. 7 | Using just the Problem level APIs, you can absolutely write your own interface to you optimization library of choice. 8 | 9 | ## Why have Drivers at all? 10 | 11 | We never even considered this question until RevHack2020, 12 | but thinking about the proposed CMA-ES problem it caused us to re-evaluate the very existence of drivers. 13 | 14 | ### Spoiler: OpenMDAO is not going to remove the drivers 15 | 16 | If for no other reason than the backwards compatibility hit would be too large, we are not going to get rid of drivers. 17 | Id like to share the details we considered about the issue anyway, because they might help you make your own decision about whether you use them or not going forward. 18 | 19 | ### Basic data on drivers: 20 | * There are about 8000 lines of code in the [OpenMDAO drivers module](https://github.com/OpenMDAO/OpenMDAO/tree/2186adb1ba66e0babaad8d2e6c7da071e1c6e973/openmdao/drivers) 21 | * The code itself is about 1200 lines, and 6800 lines of that is for tests (are you surprised by that split? it takes a lot of effort to build a reliable test suite!) 22 | * The [Driver base class](https://github.com/OpenMDAO/OpenMDAO/blob/2186adb1ba66e0babaad8d2e6c7da071e1c6e973/openmdao/core/driver.py) adds another 1200 lines of code (its located in the `core` module) and 1000 lines of test code. 23 | * As of V3.4, there are 5 drivers: [ScipyOptimizeDriver][scipy-driver], [pyOptSparseDriver][pyopt-driver], [SimpleGADriver][simple-ga], [DifferentialEvolutionDriver][di-ga], [DOEDriver][doe-driver] 24 | * OpenMDAO devs developed an plugin (i.e. not in the main repo) experimental mixed integer optimizer called [AMIEGO](https://github.com/Kenneth-T-Moore/AMIEGO) 25 | * National Renewable Energy Lab (NREL) built a plugin with a [driver for NL-Opt](https://github.com/johnjasa/nrel_openmdao_extensions) 26 | * [Onera users built a plugin](https://github.com/OneraHub/openmdao_extensions) with several additional drivers for design of experiments and surrogate based optimization 27 | 28 | ### What does using a Driver give you that rolling your own run-script doesn't? 29 | 30 | At first glance, this is a somewhat surprisingly tough question to answer. 31 | For "simple" optimization problems --- simple from the perspective that they have a relatively small number of design variables and a small number of constraints --- the answer is not a whole lot! 32 | The one significant feature they offer is the handling of all the scaling (i.e. ref/ref0) and unit conversion between the model and the driver. 33 | This is pretty important, and surprisingly hard to deal with in the most general case. 34 | Another thing that they offer is integration with OpenMDAO's case recorder system, but depending on how you feel about our case recorders that could be either a positive or a negative. 35 | 36 | There are some useful features that come into play for more complex optimization problems formulations though: 37 | * separate handling of linear and nonlinear constraints (OpenMDAO drivers compute derivatives for linear constraints only once and cache the results) 38 | * Efficient support for double-sided constraints (i.e. `0 < x < 10`). Here, we can compute one derivative that applies to both sides of the constraint with only a sign difference 39 | * Tight integration with [total derivative coloring](http://openmdao.org/twodocs/versions/3.4.0/features/core_features/working_with_derivatives/simul_derivs.html). 40 | * [Driver debug printing](http://openmdao.org/twodocs/versions/3.4.0/features/debugging/debugging_drivers.html) 41 | 42 | Every feature, except the debug printing, is related to derivatives. 43 | If you're not using analytic derivatives then it seems like the value of the driver is pretty small, 44 | and it could be totally outweighed by some of the complexity it brings. 45 | 46 | ### Why do drivers require so much code? 47 | 48 | The sample run-script for `