├── README.md ├── beam_comps.py ├── beam_xdsm └── fem_xdsm.py ├── getting_derivatives_in_openmdao ├── explicit_examples │ ├── compute_lift_analytic_dense.py │ ├── compute_lift_analytic_sparse.py │ ├── compute_lift_approximated.py │ ├── compute_lift_approximated_colored.py │ ├── debug_deriv_visually.py │ ├── plot_speed_comparison.py │ ├── run_speed_comparison.py │ └── speed_comparison.pdf ├── getting_derivatives_presentation.pptx └── implicit_examples │ ├── aircraft_group.py │ ├── balanced_eom.py │ ├── drag_polar.pdf │ ├── plot_drag_polar.py │ └── simple_wing.py ├── lab_0_solution.py ├── lab_0_template.py ├── lab_1_solution.py ├── lab_1_template.py ├── lab_2_solution.py ├── lab_2_template.py ├── lab_3 ├── lab_3_explicit_wrapper.py ├── lab_3_implicit_wrapper.py └── standalone_beam.py ├── lecture ├── Formatting │ ├── grid_overlay.sty │ └── python_formatting.sty ├── SourceCodes │ ├── Partial_Derivatives_in_OpenMDAO.py │ ├── another_way_to_connect.py │ ├── another_way_to_connect_2.py │ ├── connecting_multiple_components.py │ ├── connecting_multiple_components_2.py │ └── explicity_component_example.py ├── images │ ├── arch1.png │ ├── blade1.png │ ├── combined.png │ ├── documentation_53.png │ ├── explicit_box.png │ ├── frong2.png │ ├── front1.png │ ├── jet1.png │ ├── lab_0_N2.png │ ├── mesh1.png │ ├── michiganlogo.png │ ├── n2_48.PNG │ ├── n2_legend.png │ ├── omdao.png │ ├── plot1.png │ ├── rangeplot.png │ ├── slide73.png │ ├── slide75.png │ ├── slide76.png │ ├── slide77.png │ ├── slide78.png │ ├── slide79.png │ ├── slide_65_image.PNG │ ├── slide_67_image.PNG │ ├── slide_68_image.PNG │ ├── slide_70_image.PNG │ ├── slide_81.png │ └── span.png └── main.tex ├── motivation_to_use_openmdao.pdf ├── openmdao_beam.py ├── openmdao_workshop.pdf ├── openmdao_workshop.pptx └── paraboloid.py /README.md: -------------------------------------------------------------------------------- 1 | # openmdao_training 2 | 3 | This repo contains slides and tutorial scripts to help users get started with NASA's [OpenMDAO](https://github.com/OpenMDAO/openmdao). 4 | When given in a workshop, this material should take about 6 hours to complete. 5 | With this repo, you will be able to go through the slides and scripts at your own pace. 6 | 7 | To get started: 8 | 9 | 0. If you don't already have Python installed, download and install [Anaconda Python 3.7](https://www.anaconda.com/distribution/). 10 | 1. After that, follow [OpenMDAO's installation instructions](https://github.com/OpenMDAO/openmdao). 11 | 2. If you're looking for motivation to use OpenMDAO, check out the [`motivation_to_use_openmdao.pdf`](https://github.com/johnjasa/openmdao_training/blob/master/motivation_to_use_openmdao.pdf) file in the repo. 12 | 3. Then you can go through the `openmdao_workshop` powerpoint or pdf file in the repo, which will guide you through concepts within OpenMDAO and walk you through the tutorial scripts. 13 | 14 | If you have any questions or suggestions, please [open an issue](https://github.com/johnjasa/openmdao_training/issues) so we can discuss them. 15 | -------------------------------------------------------------------------------- /beam_comps.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from six.moves import range 3 | 4 | import numpy as np 5 | from scipy.sparse import coo_matrix 6 | from scipy.sparse.linalg import splu 7 | 8 | import openmdao.api as om 9 | 10 | 11 | class MomentOfInertiaComp(om.ExplicitComponent): 12 | 13 | def initialize(self): 14 | self.options.declare('num_elements', types=int) 15 | self.options.declare('b') 16 | 17 | def setup(self): 18 | num_elements = self.options['num_elements'] 19 | 20 | self.add_input('h', shape=num_elements) 21 | self.add_output('I', shape=num_elements) 22 | 23 | rows = np.arange(num_elements) 24 | cols = np.arange(num_elements) 25 | self.declare_partials('I', 'h', rows=rows, cols=cols) 26 | 27 | def compute(self, inputs, outputs): 28 | b = self.options['b'] 29 | 30 | outputs['I'] = 1./12. * b * inputs['h'] ** 3 31 | 32 | def compute_partials(self, inputs, partials): 33 | b = self.options['b'] 34 | 35 | partials['I', 'h'] = 1./4. * b * inputs['h'] ** 2 36 | 37 | class LocalStiffnessMatrixComp(om.ExplicitComponent): 38 | 39 | def initialize(self): 40 | self.options.declare('num_elements', types=int) 41 | self.options.declare('E') 42 | self.options.declare('L') 43 | 44 | def setup(self): 45 | num_elements = self.options['num_elements'] 46 | E = self.options['E'] 47 | L = self.options['L'] 48 | 49 | self.add_input('I', shape=num_elements) 50 | self.add_output('K_local', shape=(num_elements, 4, 4)) 51 | 52 | L0 = L / num_elements 53 | coeffs = np.empty((4, 4)) 54 | coeffs[0, :] = [12, 6 * L0, -12, 6 * L0] 55 | coeffs[1, :] = [6 * L0, 4 * L0 ** 2, -6 * L0, 2 * L0 ** 2] 56 | coeffs[2, :] = [-12, -6 * L0, 12, -6 * L0] 57 | coeffs[3, :] = [6 * L0, 2 * L0 ** 2, -6 * L0, 4 * L0 ** 2] 58 | coeffs *= E / L0 ** 3 59 | 60 | self.mtx = mtx = np.zeros((num_elements, 4, 4, num_elements)) 61 | for ind in range(num_elements): 62 | self.mtx[ind, :, :, ind] = coeffs 63 | 64 | self.declare_partials('K_local', 'I', 65 | val=self.mtx.reshape(16 * num_elements, num_elements)) 66 | 67 | def compute(self, inputs, outputs): 68 | num_elements = self.options['num_elements'] 69 | 70 | outputs['K_local'] = 0 71 | for ind in range(num_elements): 72 | outputs['K_local'][ind, :, :] = self.mtx[ind, :, :, ind] * inputs['I'][ind] 73 | 74 | 75 | ########################################## 76 | # NOTE: This component is Implicit!!!!!!!! 77 | ########################################## 78 | 79 | class FEM(om.ImplicitComponent): 80 | 81 | def initialize(self): 82 | self.options.declare('num_elements', types=int) 83 | self.options.declare('force_vector', types=np.ndarray) 84 | 85 | def setup(self): 86 | num_elements = self.options['num_elements'] 87 | num_nodes = num_elements + 1 88 | size = 2 * num_nodes + 2 89 | 90 | self.add_input('K_local', shape=(num_elements, 4, 4)) 91 | self.add_output('u', shape=size) 92 | 93 | cols = np.arange(16*num_elements) 94 | rows = np.repeat(np.arange(4), 4) 95 | rows = np.tile(rows, num_elements) + np.repeat(np.arange(num_elements), 16) * 2 96 | 97 | self.declare_partials('u', 'K_local', rows=rows, cols=cols) 98 | self.declare_partials('u', 'u') 99 | 100 | 101 | def apply_nonlinear(self, inputs, outputs, residuals): 102 | force_vector = np.concatenate([self.options['force_vector'], np.zeros(2)]) 103 | 104 | self.K = self.assemble_CSC_K(inputs) 105 | residuals['u'] = self.K.dot(outputs['u']) - force_vector 106 | 107 | def solve_nonlinear(self, inputs, outputs): 108 | 109 | # NOTE: Although this FEM is linear, you still solve it in the `solve_nonlinear` method! 110 | # This method is optional, but useful when you have codes that have their own 111 | # customized nonlinear solvers 112 | force_vector = np.concatenate([self.options['force_vector'], np.zeros(2)]) 113 | 114 | self.K = self.assemble_CSC_K(inputs) 115 | self.lu = splu(self.K) 116 | 117 | outputs['u'] = self.lu.solve(force_vector) 118 | 119 | def linearize(self, inputs, outputs, jacobian): 120 | 121 | num_elements = self.options['num_elements'] 122 | 123 | self.K = self.assemble_CSC_K(inputs) 124 | self.lu = splu(self.K) 125 | 126 | i_elem = np.tile(np.arange(4), 4) 127 | i_d = np.tile(i_elem, num_elements) + np.repeat(np.arange(num_elements), 16) * 2 128 | 129 | jacobian['u', 'K_local'] = outputs['u'][i_d] 130 | 131 | jacobian['u', 'u'] = self.K.toarray() 132 | 133 | # NOTE: this is an advanced OpenMDAO API method, that lets a component handle its own 134 | # linear solve, if it can. Its optional, but very useful if your code has highly 135 | # specialized linear solvers (like CFD and real FEA codes) 136 | def solve_linear(self, d_outputs, d_residuals, mode): 137 | if mode == 'fwd': 138 | d_outputs['u'] = self.lu.solve(d_residuals['u']) 139 | else: 140 | d_residuals['u'] = self.lu.solve(d_outputs['u']) 141 | 142 | def assemble_CSC_K(self, inputs): 143 | """ 144 | Assemble the stiffness matrix in sparse CSC format. 145 | 146 | Returns 147 | ------- 148 | ndarray 149 | Stiffness matrix as dense ndarray. 150 | """ 151 | num_elements = self.options['num_elements'] 152 | num_nodes = num_elements + 1 153 | num_entry = num_elements * 12 + 4 154 | ndim = num_entry + 4 155 | 156 | data = np.zeros((ndim, ), dtype=inputs._data.dtype) 157 | cols = np.empty((ndim, )) 158 | rows = np.empty((ndim, )) 159 | 160 | # First element. 161 | data[:16] = inputs['K_local'][0, :, :].flat 162 | cols[:16] = np.tile(np.arange(4), 4) 163 | rows[:16] = np.repeat(np.arange(4), 4) 164 | 165 | j = 16 166 | for ind in range(1, num_elements): 167 | ind1 = 2 * ind 168 | K = inputs['K_local'][ind, :, :] 169 | 170 | # NW quadrant gets summed with previous connected element. 171 | data[j-6:j-4] += K[0, :2] 172 | data[j-2:j] += K[1, :2] 173 | 174 | # NE quadrant 175 | data[j:j+4] = K[:2, 2:].flat 176 | rows[j:j+4] = np.array([ind1, ind1, ind1 + 1, ind1 + 1]) 177 | cols[j:j+4] = np.array([ind1 + 2, ind1 + 3, ind1 + 2, ind1 + 3]) 178 | 179 | # SE and SW quadrants together 180 | data[j+4:j+12] = K[2:, :].flat 181 | rows[j+4:j+12] = np.repeat(np.arange(ind1 + 2, ind1 + 4), 4) 182 | cols[j+4:j+12] = np.tile(np.arange(ind1, ind1 + 4), 2) 183 | 184 | j += 12 185 | 186 | # this implements the clamped boundary condition on the left side of the beam 187 | # using a weak formulation for the BC 188 | data[-4:] = 1.0 189 | rows[-4] = 2 * num_nodes 190 | rows[-3] = 2 * num_nodes + 1 191 | rows[-2] = 0.0 192 | rows[-1] = 1.0 193 | cols[-4] = 0.0 194 | cols[-3] = 1.0 195 | cols[-2] = 2 * num_nodes 196 | cols[-1] = 2 * num_nodes + 1 197 | 198 | n_K = 2 * num_nodes + 2 199 | return coo_matrix((data, (rows, cols)), shape=(n_K, n_K)).tocsc() 200 | 201 | class ComplianceComp(om.ExplicitComponent): 202 | 203 | def initialize(self): 204 | self.options.declare('num_elements', types=int) 205 | self.options.declare('force_vector', types=np.ndarray) 206 | 207 | def setup(self): 208 | num_elements = self.options['num_elements'] 209 | num_nodes = num_elements + 1 210 | force_vector = self.options['force_vector'] 211 | 212 | self.add_input('displacements', shape=2 * num_nodes) 213 | self.add_output('compliance') 214 | 215 | self.declare_partials('compliance', 'displacements', 216 | val=force_vector.reshape((1, 2 * num_nodes))) 217 | 218 | def compute(self, inputs, outputs): 219 | force_vector = self.options['force_vector'] 220 | 221 | outputs['compliance'] = np.dot(force_vector, inputs['displacements']) 222 | 223 | 224 | class VolumeComp(om.ExplicitComponent): 225 | 226 | def initialize(self): 227 | self.options.declare('num_elements', types=int) 228 | self.options.declare('b', default=1.) 229 | self.options.declare('L') 230 | 231 | def setup(self): 232 | num_elements = self.options['num_elements'] 233 | b = self.options['b'] 234 | L = self.options['L'] 235 | L0 = L / num_elements 236 | 237 | self.add_input('h', shape=num_elements) 238 | self.add_output('volume') 239 | 240 | self.declare_partials('volume', 'h', val=b * L0) 241 | 242 | def compute(self, inputs, outputs): 243 | num_elements = self.options['num_elements'] 244 | b = self.options['b'] 245 | L = self.options['L'] 246 | L0 = L / num_elements 247 | 248 | outputs['volume'] = np.sum(inputs['h'] * b * L0) -------------------------------------------------------------------------------- /beam_xdsm/fem_xdsm.py: -------------------------------------------------------------------------------- 1 | from pyxdsm.XDSM import XDSM 2 | 3 | # 4 | opt = 'Optimization' 5 | solver = 'MDA' 6 | ecomp = 'Analysis' 7 | icomp = 'ImplicitAnalysis' 8 | 9 | 10 | x = XDSM() 11 | 12 | x.add_system('moi', ecomp, [r'\text{1) moment of inertia}', 13 | r'I_i = \frac{1}{4} b * h_i^3']) 14 | 15 | x.add_system('local_K', ecomp, [r'\text{2) local stiffness:}', 16 | r'\left[K_\text{local}\right]_i']) 17 | 18 | x.add_system('global_K', icomp, [r'\text{3) FEM:}', 19 | r'\left[K_\text{global}\right] u = f', 20 | r'u = [d, \theta]']) 21 | 22 | x.add_system('compliance', ecomp, [r'\text{4) compliance:}', 23 | r'c = f \cdot d']) 24 | 25 | x.add_system('volume', ecomp, [r'\text{5) volume:}', 26 | r'\sum h_i b L_i']) 27 | 28 | x.connect('moi', 'local_K', 'I') 29 | x.connect('local_K', 'global_K', r'K_\text{local}') 30 | x.connect('global_K', 'compliance', r'd') 31 | 32 | x.add_input('volume', 'h') 33 | x.add_input('moi', 'h') 34 | 35 | x.write('fem_xdsm') 36 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/explicit_examples/compute_lift_analytic_dense.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import numpy as np 4 | import openmdao.api as om 5 | 6 | 7 | class ComputeLift(om.ExplicitComponent): 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int, default=1, desc='number of analysis points') 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | self.add_input('CL', val=0.5, shape=nn, desc='coefficient of lift', units=None) 16 | self.add_input('rho', val=1.2, shape=nn, desc='air density', units='kg/m**3') 17 | self.add_input('velocity', val=100., shape=nn, desc='aircraft velocity', units='m/s') 18 | self.add_input('S_ref', val=8., shape=nn, desc='wing reference area', units='m**2') 19 | 20 | self.add_output('lift', val=np.zeros(nn), desc='aircraft lift', units='N') 21 | 22 | # Dense partials 23 | self.declare_partials('*', '*') 24 | 25 | def compute(self, inputs, outputs): 26 | CL = inputs['CL'] 27 | rho = inputs['rho'] 28 | velocity = inputs['velocity'] 29 | S_ref = inputs['S_ref'] 30 | 31 | outputs['lift'] = 0.5 * CL * rho * velocity**2 * S_ref 32 | 33 | def compute_partials(self, inputs, partials): 34 | nn = self.options['num_nodes'] 35 | 36 | CL = inputs['CL'] 37 | rho = inputs['rho'] 38 | velocity = inputs['velocity'] 39 | S_ref = inputs['S_ref'] 40 | 41 | # Dense partials 42 | partials['lift', 'CL'] = 0.5 * rho * velocity**2 * S_ref * np.eye(nn) 43 | partials['lift', 'rho'] = 0.5 * CL * velocity**2 * S_ref * np.eye(nn) 44 | partials['lift', 'velocity'] = CL * rho * velocity * S_ref * np.eye(nn) 45 | partials['lift', 'S_ref'] = 0.5 * CL * rho * velocity**2 * np.eye(nn) 46 | 47 | 48 | if __name__ == "__main__": 49 | 50 | prob = om.Problem(model=om.Group()) 51 | 52 | nn = 11 53 | 54 | ivc = prob.model.add_subsystem('indep_var_comp', om.IndepVarComp(), promotes=['*']) 55 | ivc.add_output('CL', val=0.5, shape=nn, units=None) 56 | ivc.add_output('rho', val=1.2, shape=nn, units='kg/m**3') 57 | ivc.add_output('velocity', val=100., shape=nn, units='m/s') 58 | ivc.add_output('S_ref', val=8., shape=nn, units='m**2') 59 | 60 | prob.model.add_subsystem('compute_lift', ComputeLift(num_nodes=nn), promotes=['*']) 61 | 62 | prob.setup() 63 | prob.run_model() 64 | 65 | prob.compute_totals(['lift'], ['CL', 'rho', 'velocity', 'S_ref']) 66 | 67 | print('Computed lift: {} Newtons'.format(prob['lift'][0])) 68 | 69 | prob.check_partials(compact_print=True) 70 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/explicit_examples/compute_lift_analytic_sparse.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import numpy as np 4 | import openmdao.api as om 5 | 6 | 7 | class ComputeLift(om.ExplicitComponent): 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int, default=1, desc='number of analysis points') 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | self.add_input('CL', val=0.5, shape=nn, desc='coefficient of lift', units=None) 16 | self.add_input('rho', val=1.2, shape=nn, desc='air density', units='kg/m**3') 17 | self.add_input('velocity', val=100., shape=nn, desc='aircraft velocity', units='m/s') 18 | self.add_input('S_ref', val=8., shape=nn, desc='wing reference area', units='m**2') 19 | 20 | self.add_output('lift', val=np.zeros(nn), desc='aircraft lift', units='N') 21 | 22 | # Sparse partials 23 | arange = np.arange(nn) 24 | self.declare_partials('*', '*', rows=arange, cols=arange) 25 | 26 | def compute(self, inputs, outputs): 27 | CL = inputs['CL'] 28 | rho = inputs['rho'] 29 | velocity = inputs['velocity'] 30 | S_ref = inputs['S_ref'] 31 | 32 | outputs['lift'] = 0.5 * CL * rho * velocity**2 * S_ref 33 | 34 | def compute_partials(self, inputs, partials): 35 | CL = inputs['CL'] 36 | rho = inputs['rho'] 37 | velocity = inputs['velocity'] 38 | S_ref = inputs['S_ref'] 39 | 40 | # Sparse partials 41 | partials['lift', 'CL'] = 0.5 * rho * velocity**2 * S_ref 42 | partials['lift', 'rho'] = 0.5 * CL * velocity**2 * S_ref 43 | partials['lift', 'velocity'] = CL * rho * velocity * S_ref 44 | partials['lift', 'S_ref'] = 0.5 * CL * rho * velocity**2 45 | 46 | 47 | if __name__ == "__main__": 48 | 49 | prob = om.Problem(model=om.Group()) 50 | 51 | nn = 11 52 | 53 | ivc = prob.model.add_subsystem('indep_var_comp', om.IndepVarComp(), promotes=['*']) 54 | ivc.add_output('CL', val=0.5, shape=nn, units=None) 55 | ivc.add_output('rho', val=1.2, shape=nn, units='kg/m**3') 56 | ivc.add_output('velocity', val=100., shape=nn, units='m/s') 57 | ivc.add_output('S_ref', val=8., shape=nn, units='m**2') 58 | 59 | prob.model.add_subsystem('compute_lift', ComputeLift(num_nodes=nn), promotes=['*']) 60 | 61 | prob.setup() 62 | prob.run_model() 63 | 64 | prob.compute_totals(['lift'], ['CL', 'rho', 'velocity', 'S_ref']) 65 | 66 | print('Computed lift: {} Newtons'.format(prob['lift'][0])) 67 | 68 | prob.check_partials(compact_print=True) 69 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/explicit_examples/compute_lift_approximated.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import numpy as np 4 | import openmdao.api as om 5 | 6 | 7 | class ComputeLift(om.ExplicitComponent): 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int, default=1, desc='number of analysis points') 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | self.add_input('CL', val=0.5, shape=nn, desc='coefficient of lift', units=None) 16 | self.add_input('rho', val=1.2, shape=nn, desc='air density', units='kg/m**3') 17 | self.add_input('velocity', val=100., shape=nn, desc='aircraft velocity', units='m/s') 18 | self.add_input('S_ref', val=8., shape=nn, desc='wing reference area', units='m**2') 19 | 20 | self.add_output('lift', val=np.zeros(nn), desc='aircraft lift', units='N') 21 | 22 | # Finite-difference or complex-step approximation 23 | self.declare_partials('*', '*', method='cs') 24 | 25 | def compute(self, inputs, outputs): 26 | CL = inputs['CL'] 27 | rho = inputs['rho'] 28 | velocity = inputs['velocity'] 29 | S_ref = inputs['S_ref'] 30 | 31 | outputs['lift'] = 0.5 * CL * rho * velocity**2 * S_ref 32 | 33 | 34 | if __name__ == "__main__": 35 | 36 | import time 37 | 38 | prob = om.Problem(model=om.Group()) 39 | 40 | nn = 11000 41 | 42 | ivc = prob.model.add_subsystem('indep_var_comp', om.IndepVarComp(), promotes=['*']) 43 | ivc.add_output('CL', val=0.5, shape=nn, units=None) 44 | ivc.add_output('rho', val=1.2, shape=nn, units='kg/m**3') 45 | ivc.add_output('velocity', val=100., shape=nn, units='m/s') 46 | ivc.add_output('S_ref', val=8., shape=nn, units='m**2') 47 | 48 | prob.model.add_subsystem('compute_lift', ComputeLift(num_nodes=nn), promotes=['*']) 49 | 50 | prob.setup() 51 | prob.run_model() 52 | 53 | start_time = time.time() 54 | prob.compute_totals(['lift'], ['CL', 'rho', 'velocity', 'S_ref']) 55 | print('time to compute total derivatives: ', time.time()-start_time) 56 | 57 | print('Computed lift: {} Newtons'.format(prob['lift'][0])) 58 | 59 | # prob.check_partials(compact_print=True) 60 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/explicit_examples/compute_lift_approximated_colored.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import numpy as np 4 | import openmdao.api as om 5 | 6 | 7 | class ComputeLift(om.ExplicitComponent): 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int, default=1, desc='number of analysis points') 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | self.add_input('CL', val=0.5, shape=nn, desc='coefficient of lift', units=None) 16 | self.add_input('rho', val=1.2, shape=nn, desc='air density', units='kg/m**3') 17 | self.add_input('velocity', val=100., shape=nn, desc='aircraft velocity', units='m/s') 18 | self.add_input('S_ref', val=8., shape=nn, desc='wing reference area', units='m**2') 19 | 20 | self.add_output('lift', val=np.zeros(nn), desc='aircraft lift', units='N') 21 | 22 | # Sparse and colored approximated partials 23 | arange = np.arange(nn) 24 | self.declare_partials('*', '*', rows=arange, cols=arange) 25 | self.declare_coloring('*', method='cs', show_summary=True) 26 | 27 | def compute(self, inputs, outputs): 28 | CL = inputs['CL'] 29 | rho = inputs['rho'] 30 | velocity = inputs['velocity'] 31 | S_ref = inputs['S_ref'] 32 | 33 | outputs['lift'] = 0.5 * CL * rho * velocity**2 * S_ref 34 | 35 | 36 | if __name__ == "__main__": 37 | 38 | import time 39 | 40 | prob = om.Problem(model=om.Group()) 41 | 42 | nn = 11000 43 | 44 | ivc = prob.model.add_subsystem('indep_var_comp', om.IndepVarComp(), promotes=['*']) 45 | ivc.add_output('CL', val=0.5, shape=nn, units=None) 46 | ivc.add_output('rho', val=1.2, shape=nn, units='kg/m**3') 47 | ivc.add_output('velocity', val=100., shape=nn, units='m/s') 48 | ivc.add_output('S_ref', val=8., shape=nn, units='m**2') 49 | 50 | prob.model.add_subsystem('compute_lift', ComputeLift(num_nodes=nn), promotes=['*']) 51 | 52 | prob.setup() 53 | prob.run_model() 54 | 55 | start_time = time.time() 56 | prob.compute_totals(['lift'], ['CL', 'rho', 'velocity', 'S_ref']) 57 | print('time to compute total derivatives: ', time.time()-start_time) 58 | 59 | print('Computed lift: {} Newtons'.format(prob['lift'][0])) 60 | 61 | prob.check_partials(compact_print=True) 62 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/explicit_examples/debug_deriv_visually.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import numpy as np 4 | import openmdao.api as om 5 | 6 | 7 | class ComputeLift(om.ExplicitComponent): 8 | 9 | def initialize(self): 10 | self.options.declare('num_nodes', types=int, default=1) 11 | 12 | def setup(self): 13 | nn = self.options['num_nodes'] 14 | 15 | self.add_input('CL', val=0.5, shape=nn, desc='coefficient of lift', units=None) 16 | self.add_input('rho', val=1.2, shape=nn, desc='air density', units='kg/m**3') 17 | self.add_input('velocity', val=100., shape=nn, desc='aircraft velocity', units='m/s') 18 | self.add_input('S_ref', val=8., shape=nn, desc='wing reference area', units='m**2') 19 | 20 | self.add_output('lift', val=np.zeros(nn), desc='aircraft lift', units='N') 21 | 22 | # Sparse partials 23 | arange = np.arange(nn) 24 | self.declare_partials('*', '*', rows=arange, cols=arange) 25 | 26 | def compute(self, inputs, outputs): 27 | CL = inputs['CL'] 28 | rho = inputs['rho'] 29 | velocity = inputs['velocity'] 30 | S_ref = inputs['S_ref'] 31 | 32 | outputs['lift'] = 0.5 * CL * rho * velocity**2 * S_ref 33 | 34 | def compute_partials(self, inputs, partials): 35 | nn = self.options['num_nodes'] 36 | 37 | CL = inputs['CL'] 38 | rho = inputs['rho'] 39 | velocity = inputs['velocity'] 40 | S_ref = inputs['S_ref'] 41 | 42 | # Sparse partials 43 | partials['lift', 'CL'] = rho * velocity**2 * S_ref 44 | partials['lift', 'rho'] = 0.5 * CL * velocity**2 * S_ref 45 | partials['lift', 'velocity'] = CL * rho * velocity * S_ref 46 | partials['lift', 'S_ref'] = 0.5 * CL * rho * velocity**2 47 | 48 | 49 | if __name__ == "__main__": 50 | 51 | prob = om.Problem(model=om.Group()) 52 | 53 | nn = 11 54 | 55 | ivc = prob.model.add_subsystem('indep_var_comp', om.IndepVarComp(), promotes=['*']) 56 | ivc.add_output('CL', val=0.5, shape=nn, units=None) 57 | ivc.add_output('rho', val=0.4, shape=nn, units='kg/m**3') 58 | ivc.add_output('velocity', val=np.linspace(100., 200., nn), units='m/s') 59 | ivc.add_output('S_ref', val=8., shape=nn, units='m**2') 60 | 61 | prob.model.add_subsystem('compute_lift', ComputeLift(num_nodes=nn), promotes=['*']) 62 | 63 | prob.setup() 64 | prob.run_model() 65 | 66 | prob.compute_totals(['lift'], ['CL', 'rho', 'velocity', 'S_ref']) 67 | 68 | print('Computed lift: {} Newtons'.format(prob['lift'][0])) 69 | 70 | check_partials_data = prob.check_partials(compact_print=True) 71 | 72 | om.partial_deriv_plot('lift', 'CL', check_partials_data) 73 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/explicit_examples/plot_speed_comparison.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | from time import time 4 | import numpy as np 5 | from collections import OrderedDict 6 | import pickle 7 | 8 | import openmdao.api as om 9 | 10 | 11 | data = OrderedDict() 12 | data['Analytic Dense'] = None 13 | data['Analytic Sparse'] = None 14 | data['Approximated'] = None 15 | data['Approximated Colored'] = None 16 | 17 | with open('timing_data.pkl', 'rb') as f: 18 | output_data = pickle.load(f) 19 | 20 | timing_data = output_data['timing_data'] 21 | nns = output_data['nns'] 22 | 23 | import matplotlib.pyplot as plt 24 | 25 | plt.figure() 26 | 27 | for i_method, key in enumerate(data): 28 | plt.loglog(nns, timing_data[:, i_method], label=key) 29 | 30 | plt.legend() 31 | plt.xlabel('Num nodes') 32 | plt.ylabel('Time to compute total derivs, secs') 33 | plt.savefig('speed_comparison.pdf') 34 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/explicit_examples/run_speed_comparison.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | from time import time 4 | import numpy as np 5 | from collections import OrderedDict 6 | import pickle 7 | 8 | import openmdao.api as om 9 | 10 | 11 | nns = [2**i for i in range(13)] 12 | num_nns = len(nns) 13 | num_repeats = 20 14 | 15 | data = OrderedDict() 16 | data['Analytic Dense'] = np.zeros((num_nns, num_repeats)) 17 | data['Analytic Sparse'] = np.zeros((num_nns, num_repeats)) 18 | data['Approximated'] = np.zeros((num_nns, num_repeats)) 19 | data['Approximated Colored'] = np.zeros((num_nns, num_repeats)) 20 | 21 | timing_data = np.zeros((num_nns, len(data))) 22 | 23 | for i_method, key in enumerate(data): 24 | 25 | print() 26 | print(i_method, key) 27 | 28 | if key == 'Analytic Dense': 29 | from compute_lift_analytic_dense import ComputeLift 30 | if key == 'Analytic Sparse': 31 | from compute_lift_analytic_sparse import ComputeLift 32 | if key == 'Approximated': 33 | from compute_lift_approximated import ComputeLift 34 | if key == 'Approximated Colored': 35 | from compute_lift_approximated_colored import ComputeLift 36 | 37 | for i_nn, nn in enumerate(nns): 38 | 39 | print('Running {} cases of {} num_nodes'.format(num_repeats, nn)) 40 | 41 | prob = om.Problem(model=om.Group()) 42 | 43 | ivc = prob.model.add_subsystem('indep_var_comp', om.IndepVarComp(), promotes=['*']) 44 | ivc.add_output('CL', val=0.5, shape=nn, units=None) 45 | ivc.add_output('rho', val=1.2, shape=nn, units='kg/m**3') 46 | ivc.add_output('velocity', val=100., shape=nn, units='m/s') 47 | ivc.add_output('S_ref', val=8., shape=nn, units='m**2') 48 | 49 | prob.model.add_subsystem('compute_lift', ComputeLift(num_nodes=nn), promotes=['*']) 50 | 51 | prob.setup() 52 | prob.run_model() 53 | 54 | for i_repeat in range(num_repeats): 55 | 56 | pre_time = time() 57 | 58 | prob.compute_totals(['lift'], ['CL', 'rho', 'velocity', 'S_ref']) 59 | 60 | post_time = time() 61 | duration = post_time - pre_time 62 | 63 | data[key][i_nn, i_repeat] = duration 64 | 65 | timing_data[i_nn, i_method] = np.mean(data[key][i_nn, :]) 66 | 67 | 68 | output_data = { 69 | 'timing_data' : timing_data, 70 | 'nns' : nns, 71 | } 72 | 73 | with open('timing_data.pkl', 'wb') as f: 74 | pickle.dump(output_data, f) 75 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/explicit_examples/speed_comparison.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/getting_derivatives_in_openmdao/explicit_examples/speed_comparison.pdf -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/getting_derivatives_presentation.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/getting_derivatives_in_openmdao/getting_derivatives_presentation.pptx -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/implicit_examples/aircraft_group.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import numpy as np 3 | import openmdao.api as om 4 | 5 | # Import components from their files 6 | from balanced_eom import BalancedEOM 7 | from simple_wing import SimpleWing 8 | 9 | 10 | # Set the number of analysis points 11 | nn = 2 12 | 13 | # Instantiate an OpenMDAO problem with an empty group as the model 14 | prob = om.Problem(model=om.Group()) 15 | 16 | # Add an IndepVarComp to the model and promotes all variables 17 | design_parameters = prob.model.add_subsystem('design_parameters', 18 | om.IndepVarComp(), 19 | promotes=['*']) 20 | 21 | # Provide flight conditions, in this case for two analysis points 22 | design_parameters.add_output('mass', [250.e3, 200.e3], units='kg') 23 | design_parameters.add_output('velocity', [200., 250.], units='m/s') 24 | design_parameters.add_output('gamma', [0., 0.], units='rad') 25 | design_parameters.add_output('S_ref', 383.7 * np.ones(nn), units='m**s') 26 | design_parameters.add_output('rho', [1.2, 0.4], units='kg/m**3') 27 | 28 | # Add the drag polar and equations of motion components 29 | prob.model.add_subsystem('simple_wing', SimpleWing(num_nodes=nn), promotes=['*']) 30 | prob.model.add_subsystem('EOM', BalancedEOM(num_nodes=nn), promotes=['*']) 31 | 32 | # Set the nonlinear and linear solvers on the top level; print solver convergence 33 | prob.model.nonlinear_solver = om.NewtonSolver() 34 | prob.model.linear_solver = om.DirectSolver() 35 | prob.set_solver_print(level=2) 36 | 37 | # Setup and run the model 38 | prob.setup() 39 | prob.run_model() 40 | 41 | # List the outputs from the converged model 42 | prob.model.list_outputs(print_arrays=True, units=True) 43 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/implicit_examples/balanced_eom.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | 6 | # Acceleration due to gravity in m/s**2 7 | g = 9.80665 8 | 9 | class BalancedEOM(om.ImplicitComponent): 10 | """ 11 | An implicit component to solve for the angle of attack and thrust values 12 | needed to balance the forces that an aircraft experiences in steady flight. 13 | """ 14 | 15 | def initialize(self): 16 | self.options.declare('num_nodes', types=int) 17 | 18 | def setup(self): 19 | nn = self.options['num_nodes'] 20 | 21 | self.add_input(name='mass', 22 | val=np.ones(nn), 23 | desc='aircraft mass', 24 | units='kg') 25 | 26 | self.add_input(name='velocity', 27 | val=np.ones(nn), 28 | desc='aircraft velocity magnitude', 29 | units='m/s') 30 | 31 | self.add_input(name='lift', 32 | val=np.zeros(nn), 33 | desc='lift', 34 | units='N') 35 | 36 | self.add_input(name='drag', 37 | val=np.zeros(nn), 38 | desc='drag', 39 | units='N') 40 | 41 | self.add_input(name='gamma', 42 | val=np.ones(nn)*0.05, 43 | desc='flight path angle', 44 | units='rad') 45 | 46 | self.add_output(name='alpha', 47 | val=np.ones(nn)*0.1, 48 | desc='angle of attack', 49 | units='rad', 50 | lower=-1.0, upper=1.0) 51 | 52 | self.add_output(name='thrust', 53 | val=np.ones(nn) * 1.e4, 54 | desc='thrust', 55 | units='N', 56 | lower=10., upper=1.e6) 57 | 58 | arange = np.arange(nn) 59 | self.declare_partials('*', '*', rows=arange, cols=arange) 60 | 61 | # Compute the residual values of alpha and thrust 62 | def apply_nonlinear(self, inputs, outputs, residuals): 63 | mass = inputs['mass'] 64 | lift = inputs['lift'] 65 | drag = inputs['drag'] 66 | velocity = inputs['velocity'] 67 | gamma = inputs['gamma'] 68 | thrust = outputs['thrust'] 69 | alpha = outputs['alpha'] 70 | 71 | residuals['alpha'] = thrust * np.cos(alpha) - drag - mass * g * np.sin(gamma) 72 | residuals['thrust'] = thrust * np.sin(alpha) + lift - mass * g * np.cos(gamma) 73 | 74 | # Compute the partial derivatives of the residual equations wrt each of 75 | # the inputs 76 | def linearize(self, inputs, outputs, partials): 77 | mass = inputs['mass'] 78 | lift = inputs['lift'] 79 | drag = inputs['drag'] 80 | velocity = inputs['velocity'] 81 | gamma = inputs['gamma'] 82 | thrust = outputs['thrust'] 83 | alpha = outputs['alpha'] 84 | 85 | partials['alpha', 'thrust'] = np.cos(alpha) 86 | partials['alpha', 'alpha'] = thrust * -np.sin(alpha) 87 | partials['alpha', 'drag'] = -1. 88 | partials['alpha', 'mass'] = -g * np.sin(gamma) 89 | partials['alpha', 'gamma'] = -mass * g * np.cos(gamma) 90 | 91 | partials['thrust', 'thrust'] = np.sin(alpha) 92 | partials['thrust', 'alpha'] = thrust * np.cos(alpha) 93 | partials['thrust', 'lift'] = 1. 94 | partials['thrust', 'mass'] = -g * np.cos(gamma) 95 | partials['thrust', 'gamma'] = mass * g * np.sin(gamma) 96 | 97 | 98 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/implicit_examples/drag_polar.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/getting_derivatives_in_openmdao/implicit_examples/drag_polar.pdf -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/implicit_examples/plot_drag_polar.py: -------------------------------------------------------------------------------- 1 | import openmdao.api as om 2 | import numpy as np 3 | 4 | from simple_wing import SimpleWing 5 | 6 | 7 | prob = om.Problem() 8 | prob.model = om.Group() 9 | des_vars = prob.model.add_subsystem('des_vars', om.IndepVarComp(), promotes=['*']) 10 | 11 | nn = 101 12 | 13 | des_vars.add_output('velocity', 200. * np.ones(nn), units='m/s') 14 | des_vars.add_output('rho', 1.2 * np.ones(nn), units='kg/m**3') 15 | des_vars.add_output('S_ref', 383.7 * np.ones(nn), units='m**2') 16 | des_vars.add_output('alpha', np.linspace(-5., 12., nn), units='deg') 17 | 18 | prob.model.add_subsystem('SimpleWing', SimpleWing(num_nodes=nn), promotes=['*']) 19 | 20 | prob.setup(check=False, force_alloc_complex=True) 21 | 22 | prob.run_model() 23 | 24 | 25 | import matplotlib.pyplot as plt 26 | 27 | drag = prob['drag'] / 1000. 28 | lift = prob['lift'] / 1000. 29 | 30 | plt.figure(figsize=(7, 5)) 31 | 32 | plt.plot(drag, lift) 33 | 34 | indices = [0, nn//2, -1] 35 | 36 | x_scatter = drag[indices] 37 | y_scatter = lift[indices] 38 | 39 | plt.scatter(x_scatter, y_scatter, color='k', zorder=10) 40 | 41 | plt.annotate('Alpha = {:.1f} deg'.format(prob['alpha'][indices[0]]), xy=(x_scatter[0]+10., y_scatter[0])) 42 | plt.annotate('Alpha = {:.1f} deg'.format(prob['alpha'][indices[1]]), xy=(x_scatter[1]+15., y_scatter[1]-200.)) 43 | plt.annotate('Alpha = {:.1f} deg'.format(prob['alpha'][indices[2]]), xy=(x_scatter[2]-200., y_scatter[2]-200.)) 44 | 45 | plt.xlabel('Drag, kN') 46 | plt.ylabel('Lift, kN') 47 | 48 | plt.tight_layout() 49 | # plt.show() 50 | plt.savefig('drag_polar.pdf') 51 | -------------------------------------------------------------------------------- /getting_derivatives_in_openmdao/implicit_examples/simple_wing.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division, absolute_import 2 | 3 | import numpy as np 4 | import openmdao.api as om 5 | 6 | 7 | class SimpleWing(om.ExplicitComponent): 8 | """ 9 | A simple drag polar component that takes in the angle of attack and flight 10 | conditions and returns the lift and drag that the aircraft produces. 11 | """ 12 | 13 | def initialize(self): 14 | self.options.declare('num_nodes', types=int) 15 | 16 | def setup(self): 17 | nn = self.options['num_nodes'] 18 | 19 | self.add_input('alpha', shape=nn, desc='angle of attack', units='rad') 20 | self.add_input('rho', shape=nn, desc='air density', units='kg/m**3') 21 | self.add_input('velocity', shape=nn, desc='aircraft velocity', units='m/s') 22 | self.add_input('S_ref', shape=nn, desc='wing area', units='m**2') 23 | 24 | self.add_output('lift', val=np.zeros(nn), desc='aircraft lift', units='N') 25 | self.add_output('drag', val=np.zeros(nn), desc='aircraft drag', units='N') 26 | 27 | # Compute approximated partial derivatives using the complex-step method 28 | self.declare_partials('*', '*', method='cs') 29 | 30 | def compute(self, inputs, outputs): 31 | alpha = inputs['alpha'] 32 | rho = inputs['rho'] 33 | velocity = inputs['velocity'] 34 | S_ref = inputs['S_ref'] 35 | 36 | # Simple analytic equations for CL and CD 37 | CL = 2 * np.pi * alpha + 0.10 38 | CD = alpha ** 2 + 0.020 39 | 40 | # Compute the dimensioned lift and drag values acting on the aircraft 41 | outputs['lift'] = CL * 0.5 * rho * velocity**2 * S_ref 42 | outputs['drag'] = CD * 0.5 * rho * velocity**2 * S_ref 43 | 44 | -------------------------------------------------------------------------------- /lab_0_solution.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import openmdao.api as om 3 | 4 | # the __future__ import forces float division by default in Python2 5 | # the openmdao.api import loads baseclasses from OpenMDAO 6 | 7 | 8 | class BatteryWeight(om.ExplicitComponent): 9 | """Compute the weight left over for batteries at TOW 10 | You will need to complete this component""" 11 | 12 | def setup(self): 13 | # define the following inputs: 14 | # W_payload 800 lbm 15 | # W_empty 5800 lbm 16 | # TOW 12000 lbm 17 | # replace the placeholders including the brackets 18 | # do this for each input (3 calls) 19 | self.add_input('W_payload', 800, units='lbm') 20 | self.add_input('W_empty', 5800, units='lbm') 21 | self.add_input('MTOW', 12000, units='lbm') 22 | 23 | # define the following outputs: W_battery 24 | self.add_output('W_battery', val=1000, units='lbm') 25 | 26 | # declare generic finite difference partials 27 | self.declare_partials('W_battery',['*'], method='fd') 28 | 29 | def compute(self, inputs, outputs): 30 | # implement the calculation W_battery = TOW - W_payload - W_empty 31 | outputs['W_battery'] = inputs['MTOW'] - inputs['W_payload'] - inputs['W_empty'] 32 | 33 | class BreguetRange(om.ExplicitComponent): 34 | """Compute the Breguet range for an electric aircraft 35 | This example class is pre-filled with the correct code""" 36 | 37 | def initialize(self): 38 | self.options.declare('g', default=9.81) # options do not have units - careful! 39 | 40 | def setup(self): 41 | # Inputs 42 | self.add_input('LoverD', 15.0, units=None, desc="Lift to drag ratio") 43 | self.add_input('W_battery', 1000, units='lbm', desc="Battery weight") 44 | self.add_input('TOW', 12000, units='lbm', desc="Takeoff weight") 45 | self.add_input('eta_electric', 0.92, units=None, desc="Electric propulsion system efficiency") 46 | self.add_input('eta_prop', 0.8, units=None, desc="Propulsive efficiency") 47 | self.add_input('spec_energy', 300, units='W * h / kg', desc="Battery specific energy") 48 | 49 | # Outputs 50 | self.add_output('range', 100, units="NM", desc="Breguet range") # case sensitive - nm = nanometers 51 | 52 | # Partial derivatives 53 | self.declare_partials('range', ['*'], method='fd') 54 | 55 | def compute(self, inputs, outputs): 56 | g = self.options['g'] 57 | # outputs['range'] = # implement computation here 58 | outputs['range'] = (inputs['LoverD'] * 59 | inputs['eta_electric'] * inputs['eta_prop'] * 60 | inputs['spec_energy'] / g * 61 | inputs['W_battery'] / inputs['TOW']) 62 | 63 | 64 | 65 | class ElecRangeGroup(om.Group): 66 | """A model to compute the max range of an electric aircraft 67 | You will need to make connections between the components""" 68 | 69 | def setup(self): 70 | # set some input values - optimizers act on independent variables 71 | indeps = self.add_subsystem('indeps', om.IndepVarComp(), promotes_outputs=['*']) 72 | indeps.add_output('W_payload', 800, units="lbm") 73 | indeps.add_output('W_empty', 5800, units="lbm") 74 | indeps.add_output('MTOW', 12000, units="lbm") 75 | indeps.add_output('LoverD', 20) 76 | indeps.add_output('eta_electric', 0.92) 77 | indeps.add_output('eta_prop', 0.83) 78 | indeps.add_output('spec_energy', 300, units='W * h / kg') 79 | 80 | # add your disciplinary models to the group 81 | 82 | self.add_subsystem('batterywt', BatteryWeight(), 83 | promotes_inputs=['W_payload', 'W_empty', 'MTOW']) 84 | self.add_subsystem('breguet', BreguetRange(), 85 | promotes_inputs=['LoverD', 'eta_electric', 'spec_energy', 86 | 'eta_prop']) 87 | 88 | self.connect('batterywt.W_battery', 'breguet.W_battery') 89 | # for this exercise we assume TOW = MTOW (max range at current payload) 90 | self.connect('MTOW', 'breguet.TOW') 91 | 92 | 93 | 94 | 95 | if __name__ == "__main__": 96 | prob = om.Problem() 97 | prob.model = ElecRangeGroup() 98 | prob.setup() 99 | prob.run_model() 100 | print('Computed max range: ') 101 | print(str(prob['breguet.range']) + ' nautical miles') 102 | -------------------------------------------------------------------------------- /lab_0_template.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import openmdao.api as om 3 | 4 | # the __future__ import forces float division by default in Python2 5 | # the openmdao.api import loads baseclasses from OpenMDAO 6 | 7 | 8 | class BatteryWeight(om.ExplicitComponent): 9 | """Compute the weight left over for batteries at TOW. 10 | You will need to complete this component""" 11 | 12 | def setup(self): 13 | # TODO: define the following inputs: 14 | # W_payload 800 lbm 15 | # W_empty 5800 lbm 16 | # TOW 12000 lbm 17 | # replace the placeholders including the brackets 18 | # do this for each input (3 calls) 19 | self.add_input(, , units=) 20 | 21 | # TODO: define the following outputs: 22 | # W_battery, 1000 lbm 23 | # 24 | self.add_output(, , units=) 25 | 26 | # TODO: declare generic finite difference partials (wildcards) 27 | self.declare_partials(, , method='fd') 28 | 29 | def compute(self, inputs, outputs): 30 | # TODO: implement the calculation W_battery = TOW - W_payload - W_empty 31 | outputs[''] = inputs[''] ..... 32 | 33 | class BreguetRange(om.ExplicitComponent): 34 | """Compute the Breguet range for an electric aircraft. 35 | This example component is pre-filled with the correct code.""" 36 | 37 | def initialize(self): 38 | self.options.declare('g', default=9.81) # options do not have units - careful! 39 | 40 | def setup(self): 41 | # Inputs 42 | self.add_input('LoverD', 15.0, units=None, desc="Lift to drag ratio") 43 | self.add_input('W_battery', 1000, units='lbm', desc="Battery weight") 44 | self.add_input('TOW', 12000, units='lbm', desc="Takeoff weight") 45 | self.add_input('eta_electric', 0.92, units=None, desc="Electric propulsion system efficiency") 46 | self.add_input('eta_prop', 0.8, units=None, desc="Propulsive efficiency") 47 | self.add_input('spec_energy', 300, units='W * h / kg', desc="Battery specific energy") 48 | 49 | # Outputs 50 | self.add_output('range', 100, units="NM", desc="Breguet range") # case sensitive - nm = nanometers 51 | 52 | # Partial derivatives 53 | self.declare_partials('range', ['*'], method='fd') 54 | 55 | def compute(self, inputs, outputs): 56 | g = self.options['g'] 57 | # outputs['range'] = # implement computation here 58 | outputs['range'] = (inputs['LoverD'] * 59 | inputs['eta_electric'] * inputs['eta_prop'] * 60 | inputs['spec_energy'] / g * 61 | inputs['W_battery'] / inputs['TOW']) 62 | 63 | 64 | 65 | class ElecRangeGroup(om.Group): 66 | """A model to compute the max range of an electric aircraft 67 | You will need to make connections between the components""" 68 | 69 | def setup(self): 70 | # set some input values - optimizers act on independent variables 71 | indeps = self.add_subsystem('indeps', om.IndepVarComp(), promotes_outputs=['*']) 72 | indeps.add_output('W_payload', 800, units="lbm") 73 | indeps.add_output('W_empty', 5800, units="lbm") 74 | indeps.add_output('MTOW', 12000, units="lbm") 75 | indeps.add_output('LoverD', 20) 76 | indeps.add_output('eta_electric', 0.92) 77 | indeps.add_output('eta_prop', 0.83) 78 | indeps.add_output('spec_energy', 300, units='W * h / kg') 79 | 80 | # add your disciplinary models to the group 81 | 82 | self.add_subsystem('batterywt', BatteryWeight()) 83 | self.add_subsystem('breguet', BreguetRange()) 84 | # TODO: finish these connections yourself with self.connect() or promotions 85 | # self.connect(, ) 86 | # self.add_subsystems(..., promotes_inputs=['*'], promotes_outputs=['*']) 87 | # for this exercise we assume TOW = MTOW (max range at current payload) 88 | 89 | 90 | 91 | if __name__ == "__main__": 92 | prob = om.Problem() 93 | prob.model = ElecRangeGroup() 94 | prob.setup() 95 | prob.run_model() 96 | print('Computed max range: ') 97 | print(str(prob['breguet.range']) + ' nautical miles') 98 | -------------------------------------------------------------------------------- /lab_1_solution.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import openmdao.api as om 3 | # the __future__ import forces float division by default in Python2 4 | # the openmdao.api import loads baseclasses from OpenMDAO 5 | 6 | class BatteryWeight(om.ExplicitComponent): 7 | """Computes battery weight explicitly from desired range""" 8 | 9 | def initialize(self): 10 | self.options.declare('g', default=9.81) # options do not have units - careful! 11 | 12 | def setup(self): 13 | # Inputs 14 | self.add_input('LoverD', 20.0, units=None, desc="Lift to drag ratio") 15 | self.add_input('TOW', 6000, units='lbm', desc="Battery weight") 16 | self.add_input('eta_electric', 0.92, units=None, desc="Electric propulsion system efficiency") 17 | self.add_input('eta_prop', 0.8, units=None, desc="Propulsive efficiency") 18 | self.add_input('spec_energy', 300, units='W * h / kg', desc="Battery specific energy") 19 | self.add_input('range_desired', 150, units="NM", desc="Breguet range") # case sensitive - nm = nanometers 20 | 21 | # Outputs 22 | self.add_output('W_battery', 1500, units='lbm', desc="Takeoff weight") 23 | 24 | # TODO define finite difference derivatives for this component by adding method='fd' to the following declaration 25 | self.declare_partials('W_battery',['*'], method='fd') 26 | 27 | def compute(self, inputs, outputs): 28 | g = self.options['g'] 29 | outputs['W_battery'] = inputs['TOW'] / (inputs['LoverD'] * 30 | inputs['eta_electric'] * inputs['eta_prop'] * 31 | inputs['spec_energy'] / g / inputs['range_desired']) 32 | 33 | class WeightBuild(om.ExplicitComponent): 34 | """Compute TOW from component weights""" 35 | 36 | def setup(self): 37 | # define the following inputs: W_payload, W_empty, TOW 38 | self.add_input('W_payload', 800, units='lbm') 39 | self.add_input('W_empty', 5800, units='lbm') 40 | self.add_input('W_battery', 1500, units='lbm') 41 | 42 | # define the following outputs: W_battery 43 | self.add_output('TOW', val=6000, units='lbm') 44 | 45 | # declare generic finite difference partials 46 | self.declare_partials('TOW',['*'],method='fd') 47 | 48 | def compute(self, inputs, outputs): 49 | # implement the calculation W_battery = TOW - W_payload - W_empty 50 | outputs['TOW'] = inputs['W_battery'] + inputs['W_payload'] + inputs['W_empty'] 51 | 52 | def compute_partials(self, inputs, partials): 53 | # TODO define partial derivatives here 54 | partials['TOW','W_battery'] = 1 55 | partials['TOW','W_payload'] = 1 56 | partials['TOW','W_empty'] = 1 57 | 58 | 59 | class WeightBuildImplicit(om.ImplicitComponent): 60 | """Compute TOW from component weights""" 61 | 62 | def setup(self): 63 | # define the following inputs: W_payload, W_empty, TOW 64 | self.add_input('W_payload', 800, units='lbm') 65 | self.add_input('W_empty', 5800, units='lbm') 66 | self.add_input('W_battery', 1500, units='lbm') 67 | 68 | # define the following outputs: W_battery 69 | self.add_output('TOW', val=6000, units='lbm') 70 | 71 | # declare generic finite difference partials 72 | self.declare_partials('TOW',['*'],method='fd') 73 | 74 | def apply_nonlinear(self, inputs, outputs, residuals): 75 | # implement the calculation W_battery = TOW - W_payload - W_empty 76 | residuals['TOW'] = (inputs['W_battery'] + inputs['W_payload'] + inputs['W_empty']) - outputs['TOW'] 77 | 78 | def linearize(self, inputs, outputs, partials): 79 | # TODO define partial derivatives here 80 | partials['TOW','W_battery'] = 1 81 | partials['TOW','W_payload'] = 1 82 | partials['TOW','W_empty'] = 1 83 | partials['TOW','TOW'] = -1 84 | 85 | 86 | class ElecRangeGroup(om.Group): 87 | """A model to compute the max range of an electric aircraft 88 | Uses only ExplicitComponents""" 89 | 90 | def setup(self): 91 | # set some input values - optimizers act on independent variables 92 | indeps = self.add_subsystem('indeps', om.IndepVarComp(), promotes_outputs=['*']) 93 | indeps.add_output('W_payload', 800, units="lbm") 94 | indeps.add_output('range_desired', 150, units="NM") 95 | indeps.add_output('LoverD', 20) 96 | indeps.add_output('eta_electric', 0.92) 97 | indeps.add_output('eta_prop', 0.83) 98 | indeps.add_output('spec_energy', 300, units='W * h / kg') 99 | 100 | # add your disciplinary models to the group 101 | 102 | # The ExecComp lets you define an ad-hoc component without having to make a class 103 | self.add_subsystem('oew', om.ExecComp('W_empty=0.6*TOW', 104 | W_empty={'value':3500, 'units':'lbm'}, 105 | TOW={'value':6000,'units':'lbm'}), 106 | promotes_outputs=['W_empty']) 107 | 108 | self.add_subsystem('batterywt', BatteryWeight(), 109 | promotes_inputs=['LoverD','eta*','spec_energy'], 110 | promotes_outputs=['*']) 111 | self.connect('range_desired','batterywt.range_desired') 112 | # self.add_subsystem('tow' ,WeightBuild(), promotes_inputs=['W_*']) 113 | self.add_subsystem('tow' ,WeightBuildImplicit(), promotes_inputs=['W_*']) 114 | self.connect('tow.TOW', ['batterywt.TOW','oew.TOW']) 115 | 116 | 117 | 118 | if __name__ == "__main__": 119 | prob = om.Problem() 120 | 121 | prob.model = ElecRangeGroup() 122 | 123 | # pick a solver: 'newton', 'broyden', 'nlbgs', or 'nlbjac' 124 | # must define a nonlinear solver since this system has a cycle 125 | 126 | solver_flag = 'newton' 127 | 128 | if solver_flag == 'newton': 129 | prob.model.nonlinear_solver=om.NewtonSolver(iprint=2) 130 | # solve_subsystems should almost always be turned on 131 | # it improves solver robustness 132 | prob.model.nonlinear_solver.options['solve_subsystems'] = True 133 | prob.model.nonlinear_solver.options['maxiter'] = 100 134 | # these options control how tightly the solver converges the system 135 | prob.model.nonlinear_solver.options['atol'] = 1e-8 136 | prob.model.nonlinear_solver.options['rtol'] = 1e-8 137 | # the Newton solver requires a linear solver 138 | prob.model.linear_solver = om.DirectSolver() 139 | 140 | elif solver_flag == 'broyden': 141 | prob.model.nonlinear_solver=om.BroydenSolver(iprint=2) 142 | # TODO: Try using broyden with and without a computed jacobian. What happens? 143 | prob.model.nonlinear_solver.options['compute_jacobian'] = True 144 | prob.model.nonlinear_solver.options['maxiter'] = 100 145 | # these options control how tightly the solver converges the system 146 | prob.model.nonlinear_solver.options['atol'] = 1e-8 147 | prob.model.nonlinear_solver.options['rtol'] = 1e-8 148 | # the Broyden solver requires a linear solver *if* options['compute_jacobian'] = True 149 | prob.model.linear_solver = om.DirectSolver() 150 | 151 | elif solver_flag == 'nlbgs': 152 | # The nonlinear block Gauss-Seidel solver is an iterative solvver 153 | # Requires no linear solver and works even without derivatives 154 | prob.model.nonlinear_solver=om.NonlinearBlockGS(iprint=2) 155 | prob.model.nonlinear_solver.options['maxiter'] = 400 156 | prob.model.nonlinear_solver.options['atol'] = 1e-8 157 | prob.model.nonlinear_solver.options['rtol'] = 1e-8 158 | # The Aitken relaxation method improves robustness at cost of some speed 159 | prob.model.nonlinear_solver.options['use_aitken'] = False 160 | prob.model.nonlinear_solver.options['use_apply_nonlinear'] = True 161 | 162 | elif solver_flag == 'nlbjac': 163 | # We don't usually recommend using nonlinear block Jacobi as it converges slower 164 | prob.model.nonlinear_solver=om.NonlinearBlockJac(iprint=2) 165 | prob.model.nonlinear_solver.options['maxiter'] = 400 166 | prob.model.nonlinear_solver.options['atol'] = 1e-8 167 | prob.model.nonlinear_solver.options['rtol'] = 1e-8 168 | 169 | else: 170 | raise ValueError("bad solver selection!") 171 | 172 | prob.setup() 173 | ### If using the Newton solver you should generally check your partial derivatives 174 | ### before you run the model. It won't converge if you made a mistake. 175 | # prob.check_partials(compact_print=True) 176 | 177 | prob.run_model() 178 | 179 | ### If you want to list all inputs and outputs, uncomment the following 180 | # prob.model.list_inputs(units=True) 181 | # prob.model.list_outputs(units=True, residuals=True) 182 | print('Takeoff weight: ') 183 | print(str(prob['tow.TOW']) + ' lbs') 184 | -------------------------------------------------------------------------------- /lab_1_template.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import openmdao.api as om 3 | # the __future__ import forces float division by default in Python2 4 | # the openmdao.api import loads baseclasses from OpenMDAO 5 | 6 | class BatteryWeight(om.ExplicitComponent): 7 | """Computes battery weight explicitly from desired range""" 8 | 9 | def initialize(self): 10 | self.options.declare('g', default=9.81) # options do not have units - careful! 11 | 12 | def setup(self): 13 | # Inputs 14 | self.add_input('LoverD', 20.0, units=None, desc="Lift to drag ratio") 15 | self.add_input('TOW', 6000, units='lbm', desc="Battery weight") 16 | self.add_input('eta_electric', 0.92, units=None, desc="Electric propulsion system efficiency") 17 | self.add_input('eta_prop', 0.8, units=None, desc="Propulsive efficiency") 18 | self.add_input('spec_energy', 300, units='W * h / kg', desc="Battery specific energy") 19 | self.add_input('range_desired', 150, units="NM", desc="Breguet range") # case sensitive - nm = nanometers 20 | 21 | # Outputs 22 | self.add_output('W_battery', 1500, units='lbm', desc="Takeoff weight") 23 | 24 | # TODO define finite difference derivatives for this component by adding method='fd' to the following declaration 25 | self.declare_partials('W_battery',['*']) 26 | 27 | def compute(self, inputs, outputs): 28 | g = self.options['g'] 29 | outputs['W_battery'] = inputs['TOW'] / (inputs['LoverD'] * 30 | inputs['eta_electric'] * inputs['eta_prop'] * 31 | inputs['spec_energy'] / g / inputs['range_desired']) 32 | 33 | class WeightBuild(om.ExplicitComponent): 34 | """Compute TOW from component weights""" 35 | 36 | def setup(self): 37 | # define the following inputs: W_payload, W_empty, TOW 38 | self.add_input('W_payload', 800, units='lbm') 39 | self.add_input('W_empty', 5800, units='lbm') 40 | self.add_input('W_battery', 1500, units='lbm') 41 | 42 | # define the following outputs: W_battery 43 | self.add_output('TOW', val=6000, units='lbm') 44 | 45 | # declare generic finite difference partials 46 | self.declare_partials('TOW',['*']) 47 | 48 | def compute(self, inputs, outputs): 49 | # implement the calculation W_battery = TOW - W_payload - W_empty 50 | outputs['TOW'] = inputs['W_battery'] + inputs['W_payload'] + inputs['W_empty'] 51 | 52 | def compute_partials(self, inputs, partials): 53 | # TODO define partial derivatives here 54 | partials['TOW','W_battery'] = -75# TODO fill in with the correct value 55 | partials['TOW','W_payload'] = -75 # TODO fill in with the correct value 56 | partials['TOW','W_empty'] = -75# TODO fill in with the correct value 57 | 58 | 59 | class WeightBuildImplicit(om.ImplicitComponent): 60 | """Compute TOW from component weights""" 61 | 62 | def setup(self): 63 | # define the following inputs: W_payload, W_empty, TOW 64 | self.add_input('W_payload', 800, units='lbm') 65 | self.add_input('W_empty', 5800, units='lbm') 66 | self.add_input('W_battery', 1500, units='lbm') 67 | 68 | # define the following outputs: W_battery 69 | self.add_output('TOW', val=6000, units='lbm') 70 | 71 | # declare generic finite difference partials 72 | self.declare_partials('TOW',['*'],method='fd') 73 | 74 | def apply_nonlinear(self, inputs, outputs, residuals): 75 | # implement the calculation W_battery = TOW - W_payload - W_empty 76 | residuals['TOW'] = (inputs['W_battery'] + inputs['W_payload'] + inputs['W_empty']) - outputs['TOW'] 77 | 78 | def linearize(self, inputs, outputs, partials): 79 | # TODO define partial derivatives here 80 | partials['TOW','W_battery'] = 1 81 | partials['TOW','W_payload'] = 1 82 | partials['TOW','W_empty'] = 1 83 | partials['TOW','TOW'] = -1 84 | 85 | 86 | class ElecRangeGroup(om.Group): 87 | """A model to compute the max range of an electric aircraft 88 | Uses only ExplicitComponents""" 89 | 90 | def setup(self): 91 | # set some input values - optimizers act on independent variables 92 | indeps = self.add_subsystem('indeps', om.IndepVarComp(), promotes_outputs=['*']) 93 | indeps.add_output('W_payload', 800, units="lbm") 94 | indeps.add_output('range_desired', 150, units="NM") 95 | indeps.add_output('LoverD', 20) 96 | indeps.add_output('eta_electric', 0.92) 97 | indeps.add_output('eta_prop', 0.83) 98 | indeps.add_output('spec_energy', 300, units='W * h / kg') 99 | 100 | 101 | # TODO note that the ExecComp lets you define an ad-hoc component without having to make a class 102 | # TODO change the empty weight fraction from 0.6 to 0.55 103 | self.add_subsystem('oew', om.ExecComp('W_empty=0.6*TOW', 104 | W_empty={'value':3500, 'units':'lbm'}, 105 | TOW={'value':6000,'units':'lbm'}), 106 | promotes_outputs=['W_empty']) 107 | self.add_subsystem('batterywt', BatteryWeight(), promotes_inputs=['LoverD','eta*','spec_energy'],promotes_outputs=['W_battery']) 108 | self.connect('range_desired','batterywt.range_desired') 109 | self.add_subsystem('tow' ,WeightBuild(), promotes_inputs=['W_*']) 110 | # self.add_subsystem('tow' ,WeightBuildImplicit(), promotes_inputs=['W_*']) 111 | self.connect('tow.TOW', ['batterywt.TOW','oew.TOW']) 112 | 113 | 114 | if __name__ == "__main__": 115 | prob = om.Problem() 116 | 117 | # TODO eventually try running the ElecRangeGroup instead 118 | prob.model = ElecRangeGroup() 119 | 120 | # pick a solver: 'newton', 'broyden', 'nlbgs', or 'nlbjac' 121 | # must define a nonlinear solver since this system has a cycle 122 | # TODO experiment with newton and nlbgs for both systems 123 | solver_flag = 'nlbgs' 124 | 125 | if solver_flag == 'newton': 126 | prob.model.nonlinear_solver=om.NewtonSolver(iprint=2) 127 | # solve_subsystems should almost always be turned on 128 | # it improves solver robustness 129 | prob.model.nonlinear_solver.options['solve_subsystems'] = True 130 | prob.model.nonlinear_solver.options['maxiter'] = 20 131 | # these options control how tightly the solver converges the system 132 | prob.model.nonlinear_solver.options['atol'] = 1e-8 133 | prob.model.nonlinear_solver.options['rtol'] = 1e-8 134 | # the Newton solver requires a linear solver 135 | # AssembledJacobian requires memory but greatly increases speed 136 | # Should almost always be turned on 137 | prob.model.linear_solver = om.DirectSolver() 138 | 139 | elif solver_flag == 'broyden': 140 | prob.model.nonlinear_solver=om.BroydenSolver(iprint=2) 141 | # TODO: Try using broyden with and without a computed jacobian. What happens? 142 | prob.model.nonlinear_solver.options['compute_jacobian'] = True 143 | prob.model.nonlinear_solver.options['maxiter'] = 100 144 | # these options control how tightly the solver converges the system 145 | prob.model.nonlinear_solver.options['atol'] = 1e-8 146 | prob.model.nonlinear_solver.options['rtol'] = 1e-8 147 | # the Broyden solver requires a linear solver *if* options['compute_jacobian'] = True 148 | prob.model.linear_solver = om.DirectSolver() 149 | 150 | elif solver_flag == 'nlbgs': 151 | # The nonlinear block Gauss-Seidel solver is an iterative solvver 152 | # Requires no linear solver and works even without derivatives 153 | prob.model.nonlinear_solver=om.NonlinearBlockGS(iprint=2) 154 | prob.model.nonlinear_solver.options['maxiter'] = 400 155 | prob.model.nonlinear_solver.options['atol'] = 1e-8 156 | prob.model.nonlinear_solver.options['rtol'] = 1e-8 157 | # The Aitken relaxation method improves robustness at cost of some speed 158 | prob.model.nonlinear_solver.options['use_aitken'] = False 159 | 160 | elif solver_flag == 'nlbjac': 161 | # We don't usually recommend using nonlinear block Jacobi as it converges slower 162 | prob.model.nonlinear_solver=om.NonlinearBlockJac(iprint=2) 163 | prob.model.nonlinear_solver.options['maxiter'] = 400 164 | prob.model.nonlinear_solver.options['atol'] = 1e-8 165 | prob.model.nonlinear_solver.options['rtol'] = 1e-8 166 | 167 | prob.setup() 168 | ### If using the Newton solver you should generally check your partial derivatives 169 | ### before you run the model. It won't converge if you made a mistake. 170 | 171 | # TODO if you are using the Newton solver, uncomment to check your partial derivatives 172 | # prob.check_partials(compact_print=True) 173 | 174 | prob.run_model() 175 | 176 | ### TODO If you want to list all inputs and outputs, uncomment the following 177 | # prob.model.list_inputs(units=True) 178 | # prob.model.list_outputs(units=True) 179 | 180 | print('Takeoff weight: ') 181 | print(str(prob['tow.TOW']) + ' lbs') 182 | -------------------------------------------------------------------------------- /lab_2_solution.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import numpy as np 3 | 4 | import openmdao.api as om 5 | 6 | # all of these components have already been created for you, 7 | # but look in beam_comp.py if you're curious to see how 8 | from beam_comps import (MomentOfInertiaComp, LocalStiffnessMatrixComp, FEM, 9 | ComplianceComp, VolumeComp) 10 | 11 | 12 | class BeamGroup(om.Group): 13 | 14 | def initialize(self): 15 | self.options.declare('E') 16 | self.options.declare('L') 17 | self.options.declare('b') 18 | self.options.declare('volume') 19 | self.options.declare('num_elements', int) 20 | 21 | def setup(self): 22 | E = self.options['E'] 23 | L = self.options['L'] 24 | b = self.options['b'] 25 | volume = self.options['volume'] 26 | num_elements = self.options['num_elements'] 27 | num_nodes = num_elements + 1 28 | 29 | force_vector = np.zeros(2 * num_nodes) 30 | force_vector[-2] = -1. 31 | 32 | inputs_comp = om.IndepVarComp() 33 | inputs_comp.add_output('h', shape=num_elements) 34 | self.add_subsystem('inputs_comp', inputs_comp) 35 | 36 | I_comp = MomentOfInertiaComp(num_elements=num_elements, b=b) 37 | self.add_subsystem('I_comp', I_comp) 38 | 39 | # TODO: Add the rest of the components, following the XDSM 40 | comp = LocalStiffnessMatrixComp(num_elements=num_elements, E=E, L=L) 41 | self.add_subsystem('local_stiffness_matrix_comp', comp) 42 | 43 | comp = FEM(num_elements=num_elements, 44 | force_vector=force_vector) 45 | self.add_subsystem('FEM', comp) 46 | 47 | comp = ComplianceComp(num_elements=num_elements, force_vector=force_vector) 48 | self.add_subsystem('compliance_comp', comp) 49 | 50 | comp = VolumeComp(num_elements=num_elements, b=b, L=L) 51 | self.add_subsystem('volume_comp', comp) 52 | 53 | ############################################ 54 | # Connections between components 55 | ############################################ 56 | self.connect('inputs_comp.h', 'I_comp.h') 57 | # TODO: connect I_comp -> local_stiffness 58 | # local_stiffness -> FEM 59 | # inputs_comp -> volume 60 | self.connect('I_comp.I', 'local_stiffness_matrix_comp.I') 61 | self.connect( 62 | 'local_stiffness_matrix_comp.K_local', 63 | 'FEM.K_local') 64 | 65 | self.connect( 66 | 'inputs_comp.h', 67 | 'volume_comp.h') 68 | 69 | # connection for: FEM -> compliance 70 | # this one is tricky, because you just want the states from the nodes, 71 | # but not the last 2 which relate to the clamped boundary condition on the left 72 | 73 | self.connect( 74 | 'FEM.u', 75 | 'compliance_comp.displacements', src_indices=np.arange(2*num_nodes)) 76 | 77 | self.add_design_var('inputs_comp.h', lower=1e-2, upper=10.) 78 | self.add_objective('compliance_comp.compliance') 79 | self.add_constraint('volume_comp.volume', equals=volume) 80 | 81 | 82 | if __name__ == "__main__": 83 | 84 | import time 85 | 86 | E = 1. 87 | L = 1. 88 | b = 0.1 89 | volume = 0.01 90 | 91 | num_elements = 5 92 | 93 | prob = om.Problem(model=BeamGroup(E=E, L=L, b=b, volume=volume, num_elements=num_elements)) 94 | 95 | prob.driver = om.ScipyOptimizeDriver() 96 | prob.driver.options['optimizer'] = 'SLSQP' 97 | prob.driver.options['tol'] = 1e-9 98 | prob.driver.options['disp'] = True 99 | 100 | ###################################################### 101 | # Use top level FD or CS to approximate derivatives 102 | ###################################################### 103 | # prob.model.approx_totals(method='fd', step_calc="rel", step=1e-3) 104 | # prob.model.approx_totals(method='cs') 105 | 106 | prob.setup() 107 | 108 | start_time = time.time() 109 | prob.run_driver() 110 | print('opt time', time.time()-start_time) 111 | 112 | print(prob['inputs_comp.h']) 113 | -------------------------------------------------------------------------------- /lab_2_template.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import numpy as np 3 | 4 | import openmdao.api as om 5 | 6 | # all of these components have already been created for you, 7 | # but look in beam_comp.py if you're curious to see how 8 | from beam_comps import (MomentOfInertiaComp, LocalStiffnessMatrixComp, FEM, 9 | ComplianceComp, VolumeComp) 10 | 11 | 12 | class BeamGroup(om.Group): 13 | 14 | def initialize(self): 15 | self.options.declare('E', desc="Young's modulus") 16 | self.options.declare('L', desc="beam overall length") 17 | self.options.declare('b', desc='beam thickness') 18 | self.options.declare('num_elements', types=int, # this will force some type checking for you 19 | desc="number of segments to break beam into") 20 | 21 | # TODO: Declare volume as an option to fix the error msg when instantiating this component 22 | 23 | def setup(self): 24 | E = self.options['E'] 25 | L = self.options['L'] 26 | b = self.options['b'] 27 | 28 | volume = self.options['volume'] 29 | num_elements = self.options['num_elements'] 30 | num_nodes = num_elements + 1 31 | 32 | force_vector = np.zeros(2 * num_nodes) 33 | force_vector[-2] = -1. 34 | 35 | inputs_comp = om.IndepVarComp() 36 | inputs_comp.add_output('h', shape=num_elements) 37 | self.add_subsystem('inputs_comp', inputs_comp) 38 | 39 | I_comp = MomentOfInertiaComp(num_elements=num_elements, b=b) 40 | self.add_subsystem('I_comp', I_comp) 41 | 42 | # TODO: Add the rest of the components, following the XDSM 43 | # self.add_subsystem(...) 44 | 45 | ############################################ 46 | # Connections between components 47 | ############################################ 48 | self.connect('inputs_comp.h', 'I_comp.h') 49 | 50 | # TODO: connect I_comp -> local_stiffness 51 | # local_stiffness -> FEM 52 | # inputs_comp -> volume 53 | 54 | # connection for: FEM -> compliance 55 | # this one is tricky, because you just want the states from the nodes, 56 | # but not the last 2 which relate to the clamped boundary condition on the left 57 | self.connect( 58 | 'FEM.u', 59 | 'compliance_comp.displacements', src_indices=np.arange(2*num_nodes)) 60 | 61 | 62 | self.add_design_var('inputs_comp.h', lower=1e-2, upper=10.) 63 | self.add_objective('compliance_comp.compliance') 64 | self.add_constraint('volume_comp.volume', equals=volume) 65 | 66 | 67 | if __name__ == "__main__": 68 | 69 | import time 70 | 71 | E = 1. 72 | L = 1. 73 | b = 0.1 74 | volume = 0.01 75 | 76 | num_elements = 50 77 | 78 | prob = om.Problem(model=BeamGroup(E=E, L=L, b=b, volume=volume, num_elements=num_elements)) 79 | 80 | prob.driver = om.ScipyOptimizeDriver() 81 | prob.driver.options['optimizer'] = 'SLSQP' 82 | prob.driver.options['tol'] = 1e-9 83 | prob.driver.options['disp'] = True 84 | 85 | ###################################################### 86 | # Use top level FD or CS to approximate derivatives 87 | ###################################################### 88 | # prob.model.approx_totals(method='fd', step_calc="rel", step=1e-3) 89 | # prob.model.approx_totals(method='cs') 90 | 91 | prob.setup() 92 | 93 | start_time = time.time() 94 | prob.run_driver() 95 | print('opt time', time.time()-start_time) 96 | 97 | print(prob['inputs_comp.h']) 98 | -------------------------------------------------------------------------------- /lab_3/lab_3_explicit_wrapper.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | class FEMBeam(om.ExternalCodeComp): 6 | 7 | def initialize(self): 8 | self.options.declare('E') 9 | self.options.declare('L') 10 | self.options.declare('b') 11 | self.options.declare('num_elements', int) 12 | 13 | def setup(self): 14 | E = self.options['E'] 15 | L = self.options['L'] 16 | b = self.options['b'] 17 | num_elements = self.options['num_elements'] 18 | num_nodes = num_elements + 1 19 | 20 | force_vector = np.zeros(2 * num_nodes) 21 | force_vector[-2] = -1. 22 | 23 | self.add_input('h', shape=num_elements) 24 | self.add_output('compliance', shape=1) 25 | self.add_output('volume', shape=1) 26 | 27 | # providing these is optional; the component will verify that any input 28 | # files exist before execution and that the output files exist after. 29 | self.options['external_input_files'] = ['input.txt'] 30 | self.options['external_output_files'] = ['output.txt'] 31 | 32 | self.options['command'] = ['python', 'standalone_beam.py', 'solve'] 33 | 34 | def compute(self, inputs, outputs): 35 | E = self.options['E'] 36 | L = self.options['L'] 37 | b = self.options['b'] 38 | num_elements = self.options['num_elements'] 39 | 40 | h = inputs['h'] 41 | 42 | with open('input.txt', 'w') as f: 43 | data = [ 44 | 'num_elements = {}'.format(num_elements), 45 | 'E = {}'.format(E), 46 | 'L = {}'.format(L), 47 | 'b = {}'.format(b), 48 | 'h = np.array({})'.format(h.tolist()) 49 | ] 50 | 51 | f.write("\n".join(data)) 52 | 53 | # method from base class to execute the code with the given command 54 | super(FEMBeam, self).compute(inputs, outputs) 55 | 56 | with open('output.txt', 'r') as f: 57 | data = {} 58 | # parses the output and puts the variables into the data dictionary 59 | exec(f.read(), {}, data) 60 | 61 | outputs['compliance'] = data['compliance'] 62 | outputs['volume'] = data['volume'] 63 | 64 | 65 | 66 | 67 | if __name__ == "__main__": 68 | 69 | NUM_ELEMENTS = 5 70 | 71 | p = om.Problem() 72 | 73 | dvs = p.model.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) 74 | dvs.add_output('h', val=np.ones(NUM_ELEMENTS)*1.0) 75 | p.model.add_subsystem('FEM', FEMBeam(E=1, L=1, b=0.1, 76 | num_elements=NUM_ELEMENTS), 77 | promotes_inputs=['h'], 78 | promotes_outputs=['compliance', 'volume']) 79 | 80 | 81 | p.driver = om.ScipyOptimizeDriver() 82 | p.driver.options['tol'] = 1e-4 83 | p.driver.options['disp'] = True 84 | p.model.add_design_var('h', lower=0.01, upper=10.0) 85 | p.model.add_objective('compliance') 86 | p.model.add_constraint('volume', equals=0.01) 87 | 88 | p.model.approx_totals(method='fd', step=1e-4, step_calc='abs') 89 | 90 | p.setup() 91 | 92 | # p['h'] = [0.14007896, 0.12362061, 0.1046475 , 0.08152954, 0.05012339] 93 | 94 | p.run_driver() 95 | 96 | p.model.list_outputs(print_arrays=True) 97 | 98 | -------------------------------------------------------------------------------- /lab_3/lab_3_implicit_wrapper.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openmdao.api as om 4 | 5 | def fmt_data(data): 6 | """helper to format array data with lots of sig figs""" 7 | to_str = ['{:10.16f}'.format(n) for n in data] 8 | return '[{}]'.format(','.join(to_str)) 9 | 10 | class FEMBeam(om.ExternalCodeImplicitComp): 11 | 12 | def initialize(self): 13 | self.options.declare('E') 14 | self.options.declare('L') 15 | self.options.declare('b') 16 | self.options.declare('num_elements', int) 17 | 18 | def setup(self): 19 | E = self.options['E'] 20 | L = self.options['L'] 21 | b = self.options['b'] 22 | num_elements = self.options['num_elements'] 23 | num_nodes = num_elements + 1 24 | 25 | force_vector = np.zeros(2 * num_nodes) 26 | force_vector[-2] = -1. 27 | 28 | self.add_input('h', shape=num_elements) 29 | self.add_output('u', shape=2*num_elements+4) 30 | self.add_output('compliance', shape=1) 31 | self.add_output('volume', shape=1) 32 | 33 | # providing these is optional; the component will verify that any input 34 | # files exist before execution and that the output files exist after. 35 | self.options['external_input_files'] = ['input.txt'] 36 | self.options['external_output_files'] = ['output.txt'] 37 | 38 | self.options['command_solve'] = ['python', 'standalone_beam.py', 'solve'] 39 | 40 | self.options['command_apply'] = ['python', 'standalone_beam.py', 'apply'] 41 | 42 | 43 | self.declare_partials('u', '*', method='fd', step=1e-4, step_calc='abs') 44 | 45 | 46 | self.declare_partials('compliance', ['h', 'u'], method='fd', step=1e-4, step_calc='abs') 47 | # if we look in the wrapper, we can see that this deriv is analytically 1 48 | self.declare_partials('compliance', 'compliance', val=1) 49 | 50 | # note: we know there is no derivative with respect to displacements, so we don't declare it 51 | self.declare_partials('volume', 'h', method='fd', step=1e-4, step_calc='abs') 52 | # if we look in the wrapper, we can see that this deriv is analytically 1 53 | self.declare_partials('volume', 'volume', val=1) 54 | 55 | 56 | def solve_nonlinear(self, inputs, outputs): 57 | E = self.options['E'] 58 | L = self.options['L'] 59 | b = self.options['b'] 60 | num_elements = self.options['num_elements'] 61 | 62 | h = inputs['h'] 63 | 64 | with open('input.txt', 'w') as f: 65 | data = [ 66 | 'num_elements = {}'.format(num_elements), 67 | 'E = {}'.format(E), 68 | 'L = {}'.format(L), 69 | 'b = {}'.format(b), 70 | 'h = np.array({})'.format(fmt_data(h)) 71 | ] 72 | 73 | f.write("\n".join(data)) 74 | 75 | # method from base class to execute the code with the given command_apply 76 | super(FEMBeam, self).solve_nonlinear(inputs, outputs) 77 | 78 | with open('output.txt', 'r') as f: 79 | data = {} 80 | # parses the output and puts the variables into the data dictionary 81 | exec(f.read(), {}, data) 82 | 83 | outputs['u'] = data['u'] 84 | outputs['compliance'] = data['compliance'] 85 | outputs['volume'] = data['volume'] 86 | 87 | 88 | def apply_nonlinear(self, inputs, outputs, residuals): 89 | E = self.options['E'] 90 | L = self.options['L'] 91 | b = self.options['b'] 92 | num_elements = self.options['num_elements'] 93 | 94 | h = inputs['h'] 95 | 96 | u = outputs['u'] 97 | compliance = outputs['compliance'][0] 98 | volume = outputs['volume'][0] 99 | 100 | with open('input.txt', 'w') as f: 101 | data = [ 102 | 'num_elements = {}'.format(num_elements), 103 | 'E = {}'.format(E), 104 | 'L = {}'.format(L), 105 | 'b = {}'.format(b), 106 | 'h = np.array({})'.format(fmt_data(h)), 107 | 'u = np.array({})'.format(fmt_data(u)), 108 | 'compliance = {}'.format(compliance), 109 | 'volume = {}'.format(volume), 110 | ] 111 | 112 | f.write("\n".join(data)) 113 | 114 | # method from base class to execute the code with the given command_apply 115 | 116 | super(FEMBeam, self).apply_nonlinear(inputs, outputs, residuals) 117 | 118 | with open('output.txt', 'r') as f: 119 | data = {} 120 | # parses the output and puts the variables into the data dictionary 121 | exec(f.read(), {}, data) 122 | 123 | residuals['u'] = data['u_residuals'] 124 | residuals['compliance'] = data['c_residual'] 125 | residuals['volume'] = data['v_residual'] 126 | 127 | 128 | 129 | if __name__ == "__main__": 130 | 131 | NUM_ELEMENTS = 5 132 | 133 | p = om.Problem() 134 | 135 | dvs = p.model.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) 136 | dvs.add_output('h', val=np.ones(NUM_ELEMENTS)*1.0) 137 | p.model.add_subsystem('FEM', FEMBeam(E=1, L=1, b=0.1, 138 | num_elements=NUM_ELEMENTS), 139 | promotes_inputs=['h'], 140 | promotes_outputs=['compliance', 'volume']) 141 | 142 | 143 | p.driver = om.ScipyOptimizeDriver() 144 | p.driver.options['tol'] = 1e-4 145 | p.driver.options['disp'] = True 146 | p.model.add_design_var('h', lower=0.01, upper=10.0) 147 | p.model.add_objective('compliance') 148 | p.model.add_constraint('volume', equals=0.01) 149 | 150 | # p.model.approx_totals(method='fd', step=1e-4, step_calc='abs') 151 | 152 | p.model.linear_solver = om.DirectSolver() 153 | 154 | p.setup() 155 | 156 | # p['h'] = [0.14007896, 0.12362061, 0.1046475 , 0.08152954, 0.05012339] 157 | 158 | p.run_driver() 159 | 160 | p.model.list_outputs(print_arrays=True) 161 | 162 | 163 | -------------------------------------------------------------------------------- /lab_3/standalone_beam.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division, absolute_import 2 | 3 | import numpy as np 4 | from scipy.sparse import coo_matrix 5 | from scipy.sparse.linalg import splu 6 | from scipy.optimize import minimize, Bounds 7 | 8 | def fmt_data(data): 9 | """helper to format array data with lots of sig figs""" 10 | to_str = ['{:10.16f}'.format(n) for n in data] 11 | return '[{}]'.format(','.join(to_str)) 12 | 13 | 14 | 15 | def assemble_CSC_K(K_local, num_elements): 16 | """ 17 | Assemble the stiffness matrix in sparse CSC format. 18 | This takes in the local stiffness matrices and assembles a full 19 | stiffness matrix of all the elements. 20 | 21 | Returns 22 | ------- 23 | ndarray 24 | Stiffness matrix as dense ndarray. 25 | """ 26 | num_nodes = num_elements + 1 27 | num_entry = num_elements * 12 + 4 28 | ndim = num_entry + 4 29 | 30 | data = np.zeros((ndim, )) 31 | cols = np.empty((ndim, )) 32 | rows = np.empty((ndim, )) 33 | 34 | # First element. 35 | data[:16] = K_local[0, :, :].flat 36 | cols[:16] = np.tile(np.arange(4), 4) 37 | rows[:16] = np.repeat(np.arange(4), 4) 38 | 39 | j = 16 40 | for ind in range(1, num_elements): 41 | ind1 = 2 * ind 42 | K = K_local[ind, :, :] 43 | 44 | # NW quadrant gets summed with previous connected element. 45 | data[j-6:j-4] += K[0, :2] 46 | data[j-2:j] += K[1, :2] 47 | 48 | # NE quadrant 49 | data[j:j+4] = K[:2, 2:].flat 50 | rows[j:j+4] = np.array([ind1, ind1, ind1 + 1, ind1 + 1]) 51 | cols[j:j+4] = np.array([ind1 + 2, ind1 + 3, ind1 + 2, ind1 + 3]) 52 | 53 | # SE and SW quadrants together 54 | data[j+4:j+12] = K[2:, :].flat 55 | rows[j+4:j+12] = np.repeat(np.arange(ind1 + 2, ind1 + 4), 4) 56 | cols[j+4:j+12] = np.tile(np.arange(ind1, ind1 + 4), 2) 57 | 58 | j += 12 59 | 60 | data[-4:] = 1.0 61 | rows[-4] = 2 * num_nodes 62 | rows[-3] = 2 * num_nodes + 1 63 | rows[-2] = 0.0 64 | rows[-1] = 1.0 65 | cols[-4] = 0.0 66 | cols[-3] = 1.0 67 | cols[-2] = 2 * num_nodes 68 | cols[-1] = 2 * num_nodes + 1 69 | 70 | n_K = 2 * num_nodes + 2 71 | return coo_matrix((data, (rows, cols)), shape=(n_K, n_K)).tocsc() 72 | 73 | 74 | def assemble_K_local(h, E, L, b, num_elements): 75 | # Compute moment of inertia 76 | I = 1./12. * b * h ** 3 77 | 78 | # Compute local stiffness matrices 79 | L0 = L / num_elements 80 | coeffs = np.empty((4, 4)) 81 | coeffs[0, :] = [12, 6 * L0, -12, 6 * L0] 82 | coeffs[1, :] = [6 * L0, 4 * L0 ** 2, -6 * L0, 2 * L0 ** 2] 83 | coeffs[2, :] = [-12, -6 * L0, 12, -6 * L0] 84 | coeffs[3, :] = [6 * L0, 2 * L0 ** 2, -6 * L0, 4 * L0 ** 2] 85 | coeffs *= E / L0 ** 3 86 | 87 | mtx = np.zeros((num_elements, 4, 4, num_elements)) 88 | for ind in range(num_elements): 89 | mtx[ind, :, :, ind] = coeffs 90 | 91 | K_local = np.zeros((num_elements, 4, 4)) 92 | for ind in range(num_elements): 93 | K_local[ind, :, :] = mtx[ind, :, :, ind] * I[ind] 94 | 95 | return K_local 96 | 97 | def beam_model(h, E, L, b, num_elements): 98 | """ 99 | This is the main function that evaluates the performance of a beam model. 100 | 101 | It takes in data for the beam, applies a load, computes the 102 | displacements, and returns the compliance of the structure. 103 | """ 104 | num_nodes = num_elements + 1 105 | 106 | # Create force vector 107 | force_vector = np.zeros(2 * num_nodes) 108 | force_vector[-2] = -1. 109 | 110 | 111 | # Solve linear system to obtain displacements 112 | force_vector = np.concatenate([force_vector, np.zeros(2)]) 113 | 114 | K_local = assemble_K_local(h, E, L, b, num_elements) 115 | K = assemble_CSC_K(K_local, num_elements) 116 | lu = splu(K) 117 | 118 | displacements = lu.solve(force_vector) 119 | 120 | 121 | return displacements, force_vector 122 | 123 | def beam_FEM_residuals(h, E, L, b, num_elements, u): 124 | """given the inputs (h, E, L, b, num_elements) and 125 | the state vector (u) return the residuals 126 | """ 127 | 128 | num_nodes = num_elements + 1 129 | 130 | # Create force vector 131 | force_vector = np.zeros(2 * num_nodes) 132 | force_vector[-2] = -1. 133 | 134 | 135 | # Solve linear system to obtain displacements 136 | force_vector = np.concatenate([force_vector, np.zeros(2)]) 137 | 138 | K_local = assemble_K_local(h, E, L, b, num_elements) 139 | K = assemble_CSC_K(K_local, num_elements) 140 | 141 | u_residuals = K.dot(u) - force_vector 142 | return u_residuals, force_vector 143 | 144 | 145 | def compliance_function(force_vector, displacements): 146 | # Compute and return the compliance of the beam 147 | compliance = np.dot(force_vector, displacements) 148 | return compliance 149 | 150 | 151 | 152 | def volume_function(h, L, b, num_elements): 153 | """ 154 | This function computes the volume of a beam structure 155 | """ 156 | 157 | L0 = L / num_elements 158 | return np.sum(h * b * L0) 159 | 160 | 161 | 162 | if __name__ == "__main__": 163 | 164 | import sys 165 | 166 | if len(sys.argv) == 1: 167 | sys.argv.append('solve') 168 | 169 | if sys.argv[1] == "solve": 170 | 171 | ################################################ 172 | # simple run script that reads inputs from 173 | # input.txt and writes to output.txt 174 | ################################################ 175 | 176 | import os 177 | import numpy as np 178 | 179 | from standalone_beam import volume_function, beam_model 180 | 181 | # this will pull all the inputs into the global namespace 182 | with open('input.txt', 'r') as f: 183 | inp = f.read() 184 | exec(inp) 185 | # h, E, L, b, num_elements are now assigned 186 | 187 | print('solve call', h) 188 | 189 | 190 | u, force_vector = beam_model(h, E, L, b, num_elements) 191 | compliance = compliance_function(force_vector, u) 192 | volume = volume_function(h, L, b, num_elements) 193 | 194 | with open('output.txt', 'w') as f: 195 | f.write('u = {}\n'.format(fmt_data(u))) 196 | f.write('compliance = {}\n'.format(compliance)) 197 | f.write('volume = {}'.format(volume)) 198 | 199 | elif sys.argv[1] == "apply": 200 | 201 | # this will pull all the inputs into the global namespace 202 | with open('input.txt', 'r') as f: 203 | inp = f.read() 204 | exec(inp) 205 | # h, E, L, b, num_elements, and u, c, v are now assigned 206 | 207 | print('apply call', h, u, compliance, volume) 208 | 209 | u_residuals, force_vector = beam_FEM_residuals(h, E, L, b, num_elements, u) 210 | c_residual = compliance - compliance_function(force_vector, u) 211 | v_residual = volume - volume_function(h, L, b, num_elements) 212 | 213 | with open('output.txt', 'w') as f: 214 | f.write('u_residuals = {}\n'.format(u_residuals.tolist())) 215 | f.write('c_residual = {}\n'.format(c_residual)) 216 | f.write('v_residual = {}\n'.format(v_residual)) 217 | 218 | 219 | if sys.argv[1] == "opt": 220 | ############################################## 221 | #run an optimization using FD and scipy 222 | ############################################## 223 | 224 | def compliance_objective(h, E, L, b, num_elements): 225 | """ 226 | Wraps the FEM in a function that matches what scipy expects 227 | """ 228 | 229 | u, force_vector = beam_model(h, E, L, b, num_elements) 230 | return compliance_function(force_vector, u) 231 | 232 | 233 | def volume_constraint(h, L, b, num_elements, req_volume): 234 | """ 235 | Computes the actual optimization constraint required by scipy. 236 | This won't be used by the OpenMDAO wrapper. 237 | """ 238 | vol = volume_function(h, L, b, num_elements) 239 | 240 | volume_diff = req_volume - volume_function(h, L, b, num_elements) 241 | 242 | return volume_diff 243 | 244 | num_elements = 5 245 | E = 1. 246 | L = 1. 247 | b = 0.1 248 | volume = 0.01 249 | h = np.ones((num_elements)) * 1.0 250 | 251 | constraint_dict = { 252 | 'type' : 'eq', 253 | 'fun' : volume_constraint, 254 | 'args' : (L, b, num_elements, volume), 255 | } 256 | 257 | bounds = Bounds(0.01, 10.) 258 | result = minimize(compliance_objective, h, tol=1e-9, bounds=bounds, 259 | args=(E, L, b, num_elements), 260 | constraints=constraint_dict, 261 | options={'maxiter' : 500}) 262 | 263 | print('Optimal element height distribution:') 264 | print(repr(result.x)) 265 | print(result.fun) -------------------------------------------------------------------------------- /lecture/Formatting/grid_overlay.sty: -------------------------------------------------------------------------------- 1 | \setbeamertemplate{background}{\tikz[overlay, remember picture, help lines]{ 2 | \foreach \x in {0,...,15} \path (current page.south west) +(\x,6.75) node {\small$\x$}; 3 | \foreach \y in {0,...,7} \path (current page.south west) +(15.5,\y) node {\small$\y$}; 4 | \foreach \x in {0,0.5,...,15.5} \draw (current page.south west) ++(\x,0) -- +(0,6.6); 5 | \foreach \y in {0,0.5,...,7} \draw (current page.south west) ++(0,\y) -- +(15.5,0); 6 | } 7 | } 8 | 9 | % \setbeamertemplate{background}{\tikz[overlay, remember picture, help lines]{ 10 | % \foreach \x in {0,...,15} \path (current page.south west) +(\x,7.25) node {\small$\x$}; 11 | % \foreach \y in {0,...,7} \path (current page.south west) +(15.5,\y) node {\small$\y$}; 12 | % \foreach \x in {0,0.5,...,15.5} \draw (current page.south west) ++(\x,0) -- +(0,7.25); 13 | % \foreach \y in {0,0.5,...,7.5} \draw (current page.south west) ++(0,\y) -- +(15.5,0); 14 | % } 15 | % } -------------------------------------------------------------------------------- /lecture/Formatting/python_formatting.sty: -------------------------------------------------------------------------------- 1 | \definecolor{Code}{rgb}{0,0,0} 2 | \definecolor{Decorators}{rgb}{0.8,0,0.8} 3 | \definecolor{Numbers}{rgb}{0.5,0,0} 4 | \definecolor{MatchingBrackets}{rgb}{0.25,0.5,0.5} 5 | \definecolor{Keywords}{rgb}{0,0,.8} 6 | \definecolor{self}{rgb}{.8,.1,0} 7 | \definecolor{Strings}{rgb}{0,0.63,0} 8 | \definecolor{Comments}{rgb}{0,1,0} 9 | \definecolor{Backquotes}{rgb}{0,0,0} 10 | \definecolor{Classname}{rgb}{0,0,0} 11 | \definecolor{FunctionName}{rgb}{0,0,0} 12 | \definecolor{Operators}{rgb}{0,0,0} 13 | \definecolor{Background}{rgb}{0.98,0.98,0.98} 14 | \lstdefinelanguage{Python}{ 15 | numbers=left, 16 | numberstyle=\tiny, 17 | numbersep=1em, 18 | xleftmargin=0.25em, 19 | framextopmargin=2em, 20 | framexbottommargin=2em, 21 | showspaces=false, 22 | showtabs=false, 23 | showstringspaces=false, 24 | % frame=l, 25 | tabsize=4, 26 | % Basic 27 | basicstyle=\ttfamily\tiny, 28 | backgroundcolor=\color{Background}, 29 | % Comments 30 | commentstyle=\color{Comments}\slshape, 31 | % Strings 32 | stringstyle=\color{Strings}, 33 | morecomment=[s][\color{Strings}]{"""}{"""}, 34 | morecomment=[s][\color{Strings}]{'''}{'''}, 35 | % keywords 36 | morekeywords={Problem,connect,add_subsystem,add_output,IndepVarComp,StdAtmComp,ComputeLift,run_model,import,from,class,def,for,while,if,is,in,elif,else,not,and,or,print,break,continue,return,True,False,None,access,as,del,except,exec,finally,global,import,lambda,pass,print,raise,try,assert}, 37 | keywordstyle={\color{Keywords}\bfseries}, 38 | % additional keywords 39 | morekeywords={[2]@invariant,pylab,numpy,np,scipy,initialize,setup,compute}, 40 | keywordstyle={[2]\color{Decorators}\slshape}, 41 | emph={self,units,num_pts}, 42 | emphstyle={\color{self}\slshape}, 43 | % 44 | } -------------------------------------------------------------------------------- /lecture/SourceCodes/Partial_Derivatives_in_OpenMDAO.py: -------------------------------------------------------------------------------- 1 | class WeightBuild(ExplicitComponent) 2 | """Compute TOW from component weights""" 3 | 4 | def setup(self): 5 | """ define the following inputs: W_payload, W_empty, TOW""" 6 | self.add_input('W_payload', 800, units='1lbm') 7 | self.add_input('W_empty', 5800, units='lbm') 8 | self.add_input('W_battery', 1500, units='lbm') 9 | 10 | """ define the following outputs: W_battery """ 11 | self.add_output('TOW', val=6000, units='lbm') 12 | 13 | """ declare generic finite difference partials """ 14 | self.declare_partials('TOW',['*']) 15 | 16 | def compute(self, inputs, outputs): 17 | """ implement the calculation W_battery = TOW - W_payload - W_empty """ 18 | outputs['TOW'] = inputs['W_battery'] + inputs['W_payload'] + inputs['W_empty'] 19 | 20 | def compute_partials (self, inputs, partials): 21 | partials['TOW', 'W_battery'] = 1 22 | partials['TOW', 'W_payload'] = 1 23 | partials['TOW', 'W_empty'] = 1 24 | 25 | -------------------------------------------------------------------------------- /lecture/SourceCodes/another_way_to_connect.py: -------------------------------------------------------------------------------- 1 | class PromoteExample(Group): 2 | 3 | def setup(self): 4 | """ set some input values - optimizers act on independent variables """ 5 | indeps = self.add_subsystem('indeps', IndepVarComp(), promotes_outputs=['h','U','Sref']) 6 | indeps.add_output('h', 10000, units='ft') 7 | indeps.add_output('U', 200, units='kn') 8 | indeps.add_output('Sref', 200, units='ft**2') 9 | 10 | """ add your disciplinary models to the group """ 11 | self.add_subsystem('atmos', StdAtmComp(), promotes_inputs=['h'], promotes_outputs=['rho']) 12 | self.add_subsystem('lift', ComouteLift(num_pts = 1), promotes_inputs=['rho','Sref','U']) 13 | 14 | if __name__ == '__main__': 15 | prob = Problem() 16 | prob.model = PromoteExample() 17 | prob.setup() 18 | """ both of these are correct """ 19 | prob['indeps.U'] = 150. 20 | prob['U'] = 150. 21 | prob.run_model() 22 | print(prob['lift.L']) -------------------------------------------------------------------------------- /lecture/SourceCodes/another_way_to_connect_2.py: -------------------------------------------------------------------------------- 1 | class PromoteExample(Group): 2 | 3 | def setup(self): 4 | """ set some input values - optimizers act on independent variables """ 5 | indeps = self.add_subsystem('indeps', IndepVarComp(), promotes_outputs=['h','U','Sref']) 6 | indeps.add_output('h', 10000, units='ft') 7 | indeps.add_output('U', 200, units='kn') 8 | indeps.add_output('Sref', 200, units='ft**2') 9 | indeps2= self.add_subsystem('indeps2', IndepVarComp(), promotes_outputs=['U']) 10 | indeps2.add_output('U', 200, units='kn') 11 | 12 | """ add your disciplinary models to the group """ 13 | self.add_subsystem('atmos', StdAtmComp(), promotes_inputs=['h'], promotes_outputs=['rho']) 14 | self.add_subsystem('lift', ComouteLift(num_pts = 1), promotes_inputs=['rho','Sref','U']) 15 | self.add_subsystem('lift2', ComouteLift(num_pts = 1), promotes_inputs=['rho','Sref','U']) 16 | -------------------------------------------------------------------------------- /lecture/SourceCodes/connecting_multiple_components.py: -------------------------------------------------------------------------------- 1 | class ConnectExample(Group): 2 | 3 | def setup(self): 4 | """ set some input values - optimizers act on independent variables """ 5 | indeps = self.add_subsystem('indeps', IndepVarComp()) 6 | indeps.add_output('h',10000, units='ft') 7 | indeps.add_output('U',200, units='kn') 8 | indeps.add_output('Sref',200, units='ft**2') 9 | 10 | """ add your disciplinary models to the group """ 11 | self.add_subsystem('atmos', StdAtmComp()) 12 | self.add_subsystem('lift', ComputeLift(num_pts = 1)) 13 | 14 | """ connect variables together """ 15 | self.connect('indeps.h','atmos.h') 16 | self.connect('atmos.rho','lift.rho') 17 | self.connect('indeps.Sref','lift.Sref') 18 | self.connect('indeps.U','lift.U') 19 | 20 | if__name__=="__main__": 21 | prop = Problem() 22 | prob.model = ConnectExample() 23 | prob.setup() 24 | prob['indeps.U'] = 150. 25 | prob.run_model() 26 | print(prob['lift.L']) 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /lecture/SourceCodes/connecting_multiple_components_2.py: -------------------------------------------------------------------------------- 1 | class ConnectExample(Group): 2 | 3 | def setup(self): 4 | """ set some input values - optimizers act on independent variables """ 5 | indeps = self.add_subsystem('indeps', IndepVarComp(), promotes_outputs=['h','U']) 6 | indeps.add_output('h',10000, units='ft') 7 | indeps.add_output('U',200, units='kn') 8 | indeps.add_output('Sref',200, units='ft**2') 9 | 10 | """ add your disciplinary models to the group """ 11 | self.add_subsystem('atmos', StdAtmComp(), promotes_inputs=['h'], promotes_outputs=['rho']) 12 | self.add_subsystem('lift', ComputeLift(num_pts = 1), promotes_inputs=['rho','U']) 13 | self.connect('indeps.Sref', 'lift.Sref') 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lecture/SourceCodes/explicity_component_example.py: -------------------------------------------------------------------------------- 1 | class ComputeLift(ExplicitComponent): 2 | """Compute lift on a wing""" 3 | 4 | def initialize(self): 5 | self.options.declare('num_pts', default=1, desc="n analysis pts") 6 | 7 | def setup(self): 8 | npts = self.options['num_pts'] 9 | #Inputs 10 | self.add_input('Sref', 10.0, units='m**2', desc='Wing ref area') 11 | self.add_input('rho', 1.225, units='m/s', shape=(npts, ), desc='Air density') 12 | self.add_input('U', 80.0, units='m/s', shape=(npts, ), desc='Airspeed') 13 | self.add_input('CL', 0.5, units=None, shape=(npts, ), desc='Lift coefficient') 14 | 15 | # Outputs 16 | self.add_outputs('L', 0.0, units='N', shape=(npts, ), desc='Lift force') 17 | 18 | # Partial derivatives 19 | self.declare_partials('L', ['*']) 20 | 21 | def compute(self, inputs, outputs): 22 | 23 | q=1/2 * inputs['rho'] * inputs['U'] ** 2 24 | outputs['L'] = q * inputs['Sref'] * inputs['CL'] -------------------------------------------------------------------------------- /lecture/images/arch1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/arch1.png -------------------------------------------------------------------------------- /lecture/images/blade1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/blade1.png -------------------------------------------------------------------------------- /lecture/images/combined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/combined.png -------------------------------------------------------------------------------- /lecture/images/documentation_53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/documentation_53.png -------------------------------------------------------------------------------- /lecture/images/explicit_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/explicit_box.png -------------------------------------------------------------------------------- /lecture/images/frong2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/frong2.png -------------------------------------------------------------------------------- /lecture/images/front1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/front1.png -------------------------------------------------------------------------------- /lecture/images/jet1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/jet1.png -------------------------------------------------------------------------------- /lecture/images/lab_0_N2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/lab_0_N2.png -------------------------------------------------------------------------------- /lecture/images/mesh1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/mesh1.png -------------------------------------------------------------------------------- /lecture/images/michiganlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/michiganlogo.png -------------------------------------------------------------------------------- /lecture/images/n2_48.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/n2_48.PNG -------------------------------------------------------------------------------- /lecture/images/n2_legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/n2_legend.png -------------------------------------------------------------------------------- /lecture/images/omdao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/omdao.png -------------------------------------------------------------------------------- /lecture/images/plot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/plot1.png -------------------------------------------------------------------------------- /lecture/images/rangeplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/rangeplot.png -------------------------------------------------------------------------------- /lecture/images/slide73.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide73.png -------------------------------------------------------------------------------- /lecture/images/slide75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide75.png -------------------------------------------------------------------------------- /lecture/images/slide76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide76.png -------------------------------------------------------------------------------- /lecture/images/slide77.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide77.png -------------------------------------------------------------------------------- /lecture/images/slide78.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide78.png -------------------------------------------------------------------------------- /lecture/images/slide79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide79.png -------------------------------------------------------------------------------- /lecture/images/slide_65_image.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide_65_image.PNG -------------------------------------------------------------------------------- /lecture/images/slide_67_image.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide_67_image.PNG -------------------------------------------------------------------------------- /lecture/images/slide_68_image.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide_68_image.PNG -------------------------------------------------------------------------------- /lecture/images/slide_70_image.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide_70_image.PNG -------------------------------------------------------------------------------- /lecture/images/slide_81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/slide_81.png -------------------------------------------------------------------------------- /lecture/images/span.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/lecture/images/span.png -------------------------------------------------------------------------------- /lecture/main.tex: -------------------------------------------------------------------------------- 1 | \documentclass[aspectratio=169, usenames,dvipsnames, 14pt]{beamer} 2 | \usepackage[utf8]{inputenc} 3 | \usepackage[utf8]{inputenc} 4 | \usepackage{multirow} 5 | \usepackage{gensymb} 6 | \usepackage[absolute,overlay]{textpos} 7 | \usepackage[export]{adjustbox} 8 | \usepackage[absolute,overlay]{textpos} % Absolute placement of text in frame 9 | 10 | % \usepackage[dvipsnames]{xcolor} % Testing for color of slide 11 11 | \usepackage{listings} % For source code fonts 12 | \usepackage{tikz} % For absolute image placement and shapes 13 | \usepackage{Formatting/python_formatting} % User defined formatting file for source codes 14 | 15 | \usepackage{Formatting/grid_overlay} % Grid overlay for accurate placement of images 16 | 17 | \usetikzlibrary{calc} % For absolute image placement 18 | \usetikzlibrary{decorations.pathreplacing} % For curly braces 19 | 20 | \usetikzlibrary{arrows, positioning, shapes.geometric} 21 | 22 | \mode 23 | \usetheme{Rochester} 24 | 25 | 26 | \title{\textbf{Getting Started With OpenMDAO}} 27 | \author{Author Number One} 28 | 29 | \begin{document} 30 | 31 | %--------------------------------------------------------------------- 32 | \begin{frame} 33 | 34 | \maketitle 35 | 36 | \tikz[remember picture, overlay] \node[anchor=center] at ($(current page.center)-(5,1)$) {\includegraphics[scale=0.15]{images/frong2.png}}; 37 | 38 | \tikz[remember picture, overlay] \node[anchor=center] at ($(current page.center)-(-5,1)$) {\includegraphics[scale=0.17]{images/front1.png}}; 39 | 40 | \tikz[remember picture, overlay] \node[anchor=center] at ($(current page.center)-(0,-3.7)$) {\includegraphics[scale=0.24]{images/omdao.png}}; 41 | 42 | \tikz[remember picture, overlay] \node[anchor=center] at ($(current page.center)-(-5,3.4)$) {\includegraphics[scale=0.21]{images/michiganlogo.png}}; 43 | 44 | \end{frame} 45 | %--------------------------------------------------------------------- 46 | 47 | \begin{frame}{Download these slides and tutorial scripts from GitHub} 48 | 49 | \centering 50 | \url{https://github.com/openmdao/openmdao\_training} 51 | 52 | \end{frame} 53 | %--------------------------------------------------------------------- 54 | 55 | 56 | \begin{frame}{Acknowledgements} 57 | \begin{itemize} 58 | \item \textbf{Ben Brelje}, PhD candidate at University of Michigan MDO Lab for creating version 0 of this 59 | \vspace{0.5cm} 60 | \item \textbf{Justin Gray}, Engineer and team lead of OpenMDAO at NASA Glenn for refining this workshop 61 | \vspace{0.5cm} 62 | \item NASA ARMD’s TTT Project has supported OpenMDAO development since 2008 63 | \end{itemize} 64 | 65 | \end{frame} 66 | %--------------------------------------------------------------------- 67 | 68 | \begin{frame}{OpenMDAO is an open-source framework for efficient MDAO} 69 | \begin{itemize} 70 | \item Developed and supported a team at NASA Glenn since 2008 71 | \vspace{0.5cm} 72 | \item Apache 2.0 license is very permissive (no “copyleft”) 73 | \vspace{0.5cm} 74 | \item Fast enough for high-fidelity, expensive problems but easy enough for cheap conceptual models 75 | \vspace{0.5cm} 76 | \item Can be used as a framework, or a low level library for building stand alone codes 77 | \end{itemize} 78 | 79 | \end{frame} 80 | %--------------------------------------------------------------------- 81 | 82 | \begin{frame}{OpenMDAO is a reasonable choice for a wide array of MDAO problems} 83 | \begin{columns} 84 | 85 | \column[T]{0.5\textwidth} 86 | \begin{itemize} 87 | \item High-fidelity aeropropulsive optimization with RANS and CEA 88 | 89 | \uncover<2->{ 90 | \item Medium-fidelity aerostructural optimization (VLM/beam)} 91 | 92 | \uncover<3->{ 93 | \item Conceptual-fidelity sizing and tradespace exploration} 94 | \end{itemize} 95 | 96 | \column[t]{0.5\textwidth} 97 | 98 | \only<1>{ 99 | \includegraphics[scale=0.28]{images/front1.png}\\ 100 | } 101 | \only<2>{ 102 | \includegraphics[scale=0.32]{images/span.png} 103 | } 104 | 105 | \only<3>{ 106 | \includegraphics[scale=0.32]{images/rangeplot.png} 107 | } 108 | \end{columns} 109 | \end{frame} 110 | 111 | %--------------------------------------------------------------------- 112 | 113 | \begin{frame}{OpenMDAO is a reasonable choice for a wide array of MDAO problems} 114 | \includegraphics[scale=.27]{images/combined.png} 115 | \end{frame} 116 | 117 | %--------------------------------------------------------------------- 118 | 119 | \begin{frame}{Goals for today} 120 | \begin{itemize} 121 | \item Learn enough to run a useful OpenMDAO model 122 | \vspace{.5cm} 123 | \item Gain some intuition about the operating theory 124 | \vspace{.5cm} 125 | \item Get \textcolor{red}{\textbf{really excited}} about using analytic derivatives for optimization! 126 | 127 | \end{itemize} 128 | 129 | \end{frame} 130 | %--------------------------------------------------------------------- 131 | 132 | \begin{frame}{Overview} 133 | \begin{itemize} 134 | \item OpenMDAO intro and basics 135 | \begin{itemize} 136 | \item Lab 0: Explicit components and connections 137 | \end{itemize} 138 | \vspace{.5cm} 139 | 140 | \item Using solvers with implicit models 141 | \begin{itemize} 142 | \item Lab 1: Comparing gradient-free and gradient-based solvers 143 | \end{itemize} 144 | \vspace{.5cm} 145 | 146 | \item Optimization with and without analytic derivatives 147 | \begin{itemize} 148 | \item Lab 2: Optimizing the thickness distribution of a simple beam 149 | \end{itemize} 150 | \vspace{.5cm} 151 | 152 | \item Wrapping external codes 153 | \begin{itemize} 154 | \item Lab 3: Wrapping external codes as explicit and implicit components 155 | \end{itemize} 156 | \end{itemize} 157 | 158 | \end{frame} 159 | %--------------------------------------------------------------------- 160 | \begingroup 161 | \setbeamercolor{background canvas}{bg=GreenYellow} 162 | \begin{frame}{OpenMDAO intro and Basics} 163 | 164 | \begin{itemize} 165 | \item Intro and terminology 166 | \vspace{0.5cm} 167 | \item Building explicit components and connecting them together 168 | \vspace{0.5cm} 169 | \item Lab 0: Implementing a simple explicit calculation \\(Breguet Range) 170 | \end{itemize} 171 | 172 | \end{frame} 173 | \endgroup 174 | %--------------------------------------------------------------------- 175 | 176 | \begin{frame}{OpenMDAO has nice features} 177 | \begin{itemize} 178 | \item Units 179 | \begin{itemize} 180 | \item Conversions 181 | \item (In)compatibility checks 182 | \end{itemize} 183 | \pause 184 | \vspace{.5cm} 185 | \item Automatic checks for unconnected inputs 186 | \pause 187 | \vspace{.5cm} 188 | \item Interactive N2 diagrams 189 | \pause 190 | \vspace{.5cm} 191 | \item Models are Python objects (inheritance!) 192 | \end{itemize} 193 | \end{frame} 194 | %--------------------------------------------------------------------- 195 | 196 | \begin{frame}{OpenMDAO has best-in-class numerical methods} 197 | \begin{itemize} 198 | \item Efficient solvers for implicit systems 199 | \begin{itemize} 200 | \item Newton solver 201 | \item Nonlinear block Gauss-Seidel 202 | \end{itemize} 203 | \pause 204 | \vspace{0.5cm} 205 | \item Derivative computation (for gradient-based opt.) 206 | \begin{itemize} 207 | \item Forward and reverse analytic 208 | \item Finite difference or complex step 209 | \item Mixture of all! 210 | \end{itemize} 211 | \pause 212 | \vspace{0.5cm} 213 | \item MPI parallelization 214 | \end{itemize} 215 | 216 | \end{frame} 217 | %--------------------------------------------------------------------- 218 | 219 | \begin{frame}{OpenMDAO architecture} 220 | \begin{columns}[T] 221 | \column{0.5 \textwidth} 222 | \begin{itemize} 223 | \item \textbf{Components} implement the actual model computation 224 | \vspace{0.5cm} 225 | \uncover<2->{\item Groups organize the model and enable hierarchical solver strategies} 226 | \vspace{0.5cm} 227 | \uncover<3->{\item Drivers iteritively execute the model (optimizers and DOEs)} 228 | \vspace{0.5cm} 229 | \uncover<4->{\item \textbf{Problem} is the top-level container} 230 | \end{itemize} 231 | 232 | \column{0.5 \textwidth} 233 | \uncover<1->{\includegraphics[scale=0.2]{images/arch1.png}} 234 | \end{columns} 235 | 236 | \end{frame} 237 | %--------------------------------------------------------------------- 238 | 239 | \begin{frame}{ExplicitComponent class} 240 | \begin{columns}[T] 241 | 242 | \column{0.5 \textwidth} 243 | \begin{itemize} 244 | \item Used for doing explicit calculations 245 | \vspace{.5cm} 246 | \uncover<2->{\item Inputs $\rightarrow$ Outputs with no iteration / implicit states} 247 | \vspace{.5cm} 248 | \uncover<3->{\item Can be as simple as a one-line calculation, or as complex as an adjoint CFD solver} 249 | \end{itemize} 250 | 251 | \column{0.5 \textwidth} 252 | \begin{tikzpicture}[overlay] 253 | % \draw (2.9, -2) node [inner sep=0] {\uncover<1->{\includegraphics[scale=0.2]{images/arch1.png};}}; 254 | \draw (2.9, -2) node [inner sep=0] {\includegraphics[scale=0.2]{images/arch1.png}}; 255 | \draw [blue, ultra thick] (4.8,-2.7) circle (0.65cm); 256 | \draw[blue, very thick] (-2,1.3) -- (4.2, -2.5); 257 | \end{tikzpicture} 258 | \end{columns} 259 | 260 | \end{frame} 261 | %--------------------------------------------------------------------- 262 | % Need to have alternating white/grey lines in background 263 | \begin{frame}{ExplicitComponent example} 264 | 265 | \lstinputlisting[language=Python]{SourceCodes/explicity_component_example.py} 266 | 267 | \pause 268 | 269 | \only<2>{ 270 | \begin{tikzpicture}[overlay] 271 | \draw[red, very thick, <-] (6cm,6.3cm) to (8cm, 6.3cm); 272 | \end{tikzpicture} 273 | 274 | \begin{textblock*}{6cm}(9.3cm,2.35cm) 275 | \footnotesize \textcolor{red}{All your model components will \\ subclass OpenMDAO base classes} 276 | \end{textblock*} 277 | } 278 | 279 | \pause 280 | 281 | \only<3>{ 282 | \begin{tikzpicture}[overlay] 283 | \draw[red, very thick, <-] (10.6cm,5.4cm) to (11.5cm, 5.8cm); 284 | \end{tikzpicture} 285 | 286 | \begin{textblock*}{6cm}(9.3cm,2.35cm) 287 | \footnotesize \textcolor{red}{Define any options / run flags which\\do not change during evaluation} 288 | \end{textblock*} 289 | } 290 | 291 | \pause 292 | 293 | \only<4>{ 294 | \begin{tikzpicture}[overlay] 295 | \draw[red, very thick, <-] (10.6cm,4.4cm) to (11.5cm, 5.8cm); 296 | \end{tikzpicture} 297 | 298 | \begin{textblock*}{7cm}(8.5cm,2.2cm) 299 | \footnotesize \textcolor{red}{Define model inputs, output, and (optionally)\\partial derives using the setup() method.\\Called once before solve / optimization} 300 | \end{textblock*} 301 | } 302 | 303 | \pause 304 | 305 | \only<5>{ 306 | \begin{textblock*}{7cm}(9cm,6.6cm) 307 | \footnotesize \textcolor{red}{Do the actual computation using compute(). Need to fill in values for all your declared outputs by the end of this method. Called every time the model is evaluated} 308 | \end{textblock*} 309 | } 310 | 311 | \pause 312 | 313 | \only<6>{ 314 | %---------------------------------------------------------------------------------------- 315 | %----------Begin the arrows and text on the last 'ExplicitComponent example' Frame------- 316 | %---------------------------------------------------------------------------------------- 317 | \begin{tikzpicture}[overlay] 318 | \draw[red,very thick, <-](4.2cm,4.3cm) to (5.8cm,6.5cm); 319 | \end{tikzpicture} 320 | 321 | \begin{textblock*}{5cm}(7cm,2.3cm) 322 | \footnotesize \textcolor{red}{Variable name} 323 | \end{textblock*} 324 | %-------------%------------ 325 | \begin{tikzpicture}[overlay] 326 | \draw[red,very thick, <-](5cm,4.3cm) to (7cm,6cm); 327 | \end{tikzpicture} 328 | 329 | \begin{textblock*}{5cm}(7.6cm,2.7cm) 330 | \footnotesize \textcolor{red}{Default value (before model runs)} 331 | \end{textblock*} 332 | %-------------%------------ 333 | \begin{tikzpicture}[overlay] 334 | \draw[red,very thick, <-](6.2cm,4.3cm) to (8cm,5.6cm); 335 | \end{tikzpicture} 336 | 337 | \begin{textblock*}{5cm}(9cm,3.1cm) 338 | \footnotesize \textcolor{red}{Units (need not match upstream)} 339 | \end{textblock*} 340 | %-------------%------------ 341 | \begin{tikzpicture}[overlay] 342 | \draw[red,very thick, <-](8.8cm,4.4cm) to (10.9cm,4.8cm); 343 | \end{tikzpicture} 344 | 345 | \begin{textblock*}{3cm}(12.3cm,3.7cm) 346 | \footnotesize \textcolor{red}{Human-readable description (optional)} 347 | \end{textblock*} 348 | %-------------%------------ 349 | \begin{tikzpicture}[overlay] 350 | \draw[red,very thick, <-](8.2cm,2.8cm) to (11.4cm,3.1cm); 351 | \end{tikzpicture} 352 | 353 | \begin{textblock*}{3cm}(12.8cm,5.7cm) 354 | \footnotesize \textcolor{red}{Variable dimension (in this case, a n x 1 vector)} 355 | \end{textblock*} 356 | %-------------%------------ 357 | \begin{tikzpicture}[overlay] 358 | \draw[red,very thick, <-](6.2cm,2cm) to (8cm,2cm); 359 | \end{tikzpicture} 360 | 361 | \begin{textblock*}{3.6cm}(9.5cm,7.2cm) 362 | \footnotesize \textcolor{red}{Will default to expecting user provided analytic derivatives, but we can specify `fd` later…} 363 | \end{textblock*} 364 | } 365 | \end{frame} % Error is from the source code being outside the defined area (can ignore) 366 | %--------------------------------------------------------------------- 367 | 368 | \begin{frame}{Connecting multiple components} 369 | \begin{columns} 370 | \column[T]{0.5\textwidth} 371 | \begin{itemize} 372 | \item Combine components into a Group to build models 373 | \vspace{0.5cm} 374 | \item Groups can hold other Groups, forming a hierarchy 375 | \end{itemize} 376 | 377 | \column[T]{0.5\textwidth} 378 | \includegraphics[scale=0.2]{images/arch1.png} 379 | \begin{tikzpicture}[overlay] 380 | \draw [blue, ultra thick] (-4.75,2) circle (0.55cm); 381 | \end{tikzpicture} 382 | \end{columns} 383 | 384 | \end{frame} 385 | %--------------------------------------------------------------------- 386 | 387 | \begin{frame}{Connecting multiple components} 388 | \lstinputlisting[language=Python]{SourceCodes/connecting_multiple_components.py} 389 | 390 | \only<1>{ 391 | \begin{textblock*}{6cm}(8cm,2.7cm) 392 | \footnotesize \textcolor{red}{Define a model by subclassing Group} 393 | \end{textblock*} 394 | 395 | \begin{tikzpicture}[overlay] 396 | \draw[red,very thick,<-](3.9,6.35) to (6.7,6.25); 397 | \end{tikzpicture} 398 | 399 | \begin{textblock*}{5cm}(8.3cm,7cm) 400 | \footnotesize \textcolor{red}{This is the model structure that is being connected} 401 | \end{textblock*} 402 | 403 | \begin{tikzpicture}[overlay] % Curly brace 404 | \draw [red, very thick,decorate,decoration={brace,amplitude=10pt,mirror,raise=4pt},yshift=0pt] 405 | (6.2,2.3) -- (6.2,3.9) node [midway,xshift=3cm] {\footnotesize $\includegraphics[scale=0.2]{images/explicit_box.png}$}; 406 | \end{tikzpicture} 407 | } 408 | 409 | \pause 410 | 411 | \only<2>{ 412 | 413 | %-------------------------------% 414 | \begin{tikzpicture}[overlay] 415 | \draw[red, very thick, <-](4.6,5.8) to (8.5,5.2); % Top Arrow 416 | \draw[red, very thick] (4.6,4) rectangle ++(3.5,0.6); % Box at line 11, 12 417 | \draw[red, very thick,<-](7.9,4) to (10, 3.5); % Bottom arrow 418 | \end{tikzpicture} 419 | 420 | \begin{textblock*}{5cm}(9.5cm,3.7cm) 421 | \footnotesize \textcolor{red}{Include components using the add\_subsystem (name, component) method} 422 | \end{textblock*} 423 | 424 | \begin{textblock*}{4cm}(11cm,5.6cm) 425 | \footnotesize \textcolor{red}{Python note: These are instances of a component class} 426 | \end{textblock*} 427 | } 428 | 429 | \pause %---------------------------------------- 430 | 431 | \only<3>{ 432 | \begin{tikzpicture}[overlay] 433 | \draw[red, very thick, <-](6.8,4.4) to (9,4.4); %Top arrow 434 | \draw[red, very thick, <-](6.8,4) to (10,2.5); % Bottom arrow 435 | \end{tikzpicture} 436 | 437 | \begin{textblock*}{5cm}(10cm,3.8cm) 438 | \footnotesize \textcolor{red}{Your custom components can be defined in the same .py file, or imported from a package for modularity} 439 | \end{textblock*} 440 | 441 | \begin{textblock*}{5cm}(11cm,7cm) 442 | \footnotesize \textcolor{red}{Specify flags/options now} 443 | \end{textblock*} 444 | } 445 | 446 | \pause %---------------------------------------- 447 | 448 | \only<4>{ 449 | \tikz[overlay] 450 | \draw [red, very thick,decorate,decoration={brace,amplitude=10pt,mirror,raise=4pt},yshift=0pt] 451 | (6.2,2.3) -- (6.2,3.9) node [midway,xshift=4.45cm] {}; 452 | 453 | \begin{textblock*}{5cm}(8cm,6.1cm) 454 | \footnotesize \textcolor{red}{Connect parameters using the connect(from, to) method} 455 | \end{textblock*} 456 | 457 | \tikz[overlay] % Connection boxes image 458 | \node[anchor=center] at ($(current page.center)-(-3,-0.5)$) 459 | {\includegraphics[scale=0.22]{images/explicit_box.png}}; 460 | } 461 | 462 | \pause %---------------------------------------- 463 | 464 | \only<5>{ 465 | \begin{textblock*}{5cm}(10cm,6cm) 466 | \footnotesize \textcolor{red}{Note the namespace / address string\: component\_name.variable} 467 | \end{textblock*} 468 | 469 | \tikz[overlay] 470 | \draw[red, very thick, <-](6.5,2.6) to (8.5,2.6); 471 | \tikz[overlay] 472 | \draw[red, very thick, <-](2.65,0.5) to (8.45,2.5); 473 | } 474 | 475 | \pause %---------------------------------------- 476 | 477 | \only<6>{ 478 | \begin{tikzpicture}[overlay] 479 | % Problem image 480 | \node[anchor=center] at ($(current page.center)-(-4.5,-0.5)$) 481 | {\includegraphics[scale=0.15]{images/arch1.png}}; 482 | 483 | % Circle on image 484 | \draw [red, ultra thick] (12.55,5.3) circle (0.45cm); 485 | 486 | % Rectangle 487 | \draw [red, ultra thick](0.4,2.1) rectangle (5,1.5); % Format: (x1,y1) rectangle (x2,y2) 488 | 489 | % Dotted line connecting circle and rectangle 490 | \draw[dotted, red, ultra thick] (5,2.1) -- (12,5.3); 491 | \end{tikzpicture} 492 | 493 | \begin{textblock*}{6cm}(7cm,7.5cm) 494 | \footnotesize \textcolor{red}{When you run an MDA/MDO problem, you will add one top-level Group instance to the problem} 495 | \end{textblock*} 496 | } 497 | 498 | \end{frame} 499 | 500 | %---------------------------------------------------------------------% 501 | %---------------------------------------------------------------------% 502 | 503 | \begin{frame}{Another way to connect…} 504 | \begin{itemize} 505 | \item If parameters are widely used among many components, writing many connect() statements can be tedious 506 | \item Variable \textit{promotion} is another way to make connections 507 | \end{itemize} 508 | \vspace{0.75cm} 509 | 510 | % import python source code from file 511 | \lstinputlisting[firstline=1, lastline=12, language=Python]{SourceCodes/another_way_to_connect.py} 512 | 513 | % Rectangle at line 11 514 | \tikz[overlay] 515 | \draw[red, very thick] (6.7,0.8) rectangle (13.5,1.2); 516 | 517 | \end{frame} 518 | 519 | %---------------------------------------------------------------------% 520 | %---------------------------------------------------------------------% 521 | 522 | \begin{frame}{What does variable promotion do?} 523 | \begin{itemize} 524 | \item Creates an alias for the variable one level up in the namespace 525 | \begin{itemize} 526 | \item (atmos.h - h) 527 | \item (lift.rho - rho) 528 | \end{itemize} 529 | \item Automatically connects any matching I/O variable names 530 | \item Promote variables with wildcard (e.g. *\_\textcolor{blue}{in} or just *) 531 | \end{itemize} 532 | 533 | \lstinputlisting[firstline=1, lastline=12, language=Python]{SourceCodes/another_way_to_connect.py} 534 | 535 | % Rectangle at line 11 536 | \tikz[overlay] 537 | \draw[red, very thick] (6.7,0.8) rectangle (13.5,1.2); 538 | 539 | \end{frame} 540 | 541 | %---------------------------------------------------------------------% 542 | %---------------------------------------------------------------------% 543 | 544 | \begin{frame}{What does variable promotion do?} 545 | \lstinputlisting[language=Python]{SourceCodes/another_way_to_connect.py} 546 | 547 | \begin{tikzpicture}[overlay] 548 | \draw[red, very thick] (8.4,4.9) rectangle (13,5.25); % Top Box 549 | \draw[red, very thick] (8.2,3.05) rectangle (12.9,3.45); % Middle box 550 | \draw [red, ultra thick](0.4,1.1) rectangle (4,1.7); % Bottom box 551 | \node[anchor=center] at ($(current page.center)-(-3,3)$) % Image 552 | {\includegraphics[scale=0.25]{images/explicit_box.png}}; 553 | \end{tikzpicture} 554 | 555 | \begin{textblock*}{4cm}(10cm,4cm) 556 | \footnotesize \textcolor{red}{Input/output connection established automatically} 557 | \end{textblock*} 558 | \end{frame} 559 | 560 | %---------------------------------------------------------------------% 561 | %---------------------------------------------------------------------% 562 | 563 | \begin{frame}{What does variable promotion do?} 564 | \lstinputlisting[language=Python]{SourceCodes/another_way_to_connect_2.py} 565 | 566 | \only<1>{ 567 | \begin{textblock*}{5cm}(9cm,2.8cm) 568 | \footnotesize \textcolor{red}{Multiple promoted outputs with same name: not allowed (will raise an exception)} 569 | \end{textblock*} 570 | 571 | \begin{tikzpicture}[overlay] 572 | \draw[red, very thick] (8.2,3.05-0.5) rectangle (12.9,2.95); % Top box 573 | \draw[red, very thick] (8.35,2.05-0.5) rectangle (11.65,1.98); % Bottom box 574 | \end{tikzpicture} 575 | 576 | 577 | } 578 | 579 | \pause 580 | 581 | \only<2>{ 582 | \begin{tikzpicture}[overlay] 583 | \draw[red, very thick] (8.25,0.5) rectangle (13.1,1.2); % Top box 584 | \end{tikzpicture} 585 | 586 | \begin{textblock*}{6cm}(8cm,7.5cm) 587 | \footnotesize \textcolor{red}{Multiple promoted inputs with identical name: OK and encouraged (all connections automatically made)} 588 | \end{textblock*} 589 | 590 | } 591 | 592 | \end{frame} 593 | 594 | %---------------------------------------------------------------------% 595 | %---------------------------------------------------------------------% 596 | 597 | \begin{frame}{What does variable promotion do?} 598 | \lstinputlisting[language=Python]{SourceCodes/connecting_multiple_components_2.py} 599 | 600 | \tikz[overlay] 601 | \draw[red,very thick, <-] (5.5,.6) to (8,0.15); % Left Arrow 602 | \tikz[overlay] 603 | \draw[red, very thick, <-] (10.8,0.9) to (9,0.15); % Right Arrow 604 | 605 | \begin{textblock*}{5cm}(8.3cm, 7.4cm) 606 | \footnotesize \textcolor{red}{Mixing promotion with connect statements:allowed / appropriate} 607 | \end{textblock*} 608 | \end{frame} 609 | 610 | %---------------------------------------------------------------------% 611 | %---------------------------------------------------------------------% 612 | 613 | \begin{frame}{Lab 0: Implementing simple explicit calculations (Breguet Range)} 614 | \begin{itemize} 615 | \item Install OpenMDAO 616 | \vspace{0.5cm} 617 | \item Write your first component 618 | \vspace{0.5cm} 619 | \item World domination! 620 | \end{itemize} 621 | 622 | \end{frame} 623 | 624 | %---------------------------------------------------------------------% 625 | %---------------------------------------------------------------------% 626 | 627 | \begin{frame}{Lab 0.a: Install} 628 | \begin{itemize} 629 | \item Open cmd prompt 630 | \vspace{0.25cm} 631 | \item Internet install: \texttt{pip install openmdao} 632 | \vspace{0.25cm} 633 | \item Local Install: 634 | \begin{itemize} 635 | \item cd to wherever OpenMDAO is downloaded: \texttt{'pip install .'} \textcolor{red}{(note the period)} 636 | \item This installs from local source files, not PyPI 637 | \end{itemize} 638 | \vspace{0.25cm} 639 | \item cd ../openmdao\_training 640 | \vspace{0.25cm} 641 | \item \texttt{\textcolor{red}{python} paraboloid.py} 642 | \begin{itemize} 643 | \item If this works without error, your installation should be good 644 | \end{itemize} 645 | \end{itemize} 646 | \end{frame} 647 | 648 | %---------------------------------------------------------------------% 649 | %---------------------------------------------------------------------% 650 | 651 | \begin{frame}{Lab 0.b: Aircraft Range} 652 | \begin{itemize} 653 | \item Open \texttt{lab\_0\_template.py} in a text editor or IDE and rename it to \texttt{lab\_0.py} 654 | % \vspace{0.25cm} 655 | 656 | \item The \texttt{BreguetRange} component implements the electric Breguet equation: 657 | 658 | \begin{equation} 659 | \small R_b= \frac{L}{D}\eta_e \eta_int \eta_p \frac{e_b}{g} \frac{m_b}{m_TO} 660 | \end{equation} 661 | % \vspace{0.1cm} 662 | 663 | \begin{equation} 664 | \small m_TO = m_{empty} + m_{payload} + m_{battery} 665 | \end{equation} 666 | % \vspace{0.1cm} 667 | 668 | \item Your goal is to compute the maximum range of an airplane given a certain payload 669 | \end{itemize} 670 | \end{frame} 671 | 672 | %---------------------------------------------------------------------% 673 | %---------------------------------------------------------------------% 674 | 675 | \begin{frame}{Lab 0.b: Aircraft Range} 676 | \begin{itemize} 677 | \item Complete the TODOs in the \texttt{BatteryWeight} component 678 | \vspace{0.3cm} 679 | \item Complete the TODOs in the \texttt{ElecRangeGroup} definition by connecting the two components 680 | \vspace{0.3cm} 681 | \item \texttt{cd} into project folder to check and run the model 682 | \begin{itemize} 683 | \item \texttt{openmdao view\_model lab\_0.py} (creates model diagram) 684 | \item \texttt{python lab\_0.py} (runs the model) 685 | \end{itemize} 686 | \vspace{0.3cm} 687 | \item Answer key in the \texttt{lab\_0\_solution.py} file (don’t cheat!) 688 | \end{itemize} 689 | \end{frame} 690 | 691 | %---------------------------------------------------------------------% 692 | %---------------------------------------------------------------------% 693 | 694 | \begin{frame}{Lab 0.b: Aircraft Range} 695 | \begin{itemize} 696 | \item \texttt{openmdao view\_model lab\_0.py} 697 | \item If you forgot any connections, you’ll see some red 698 | \end{itemize} 699 | 700 | \includegraphics[scale=0.21]{images/lab_0_N2.png} 701 | 702 | \begin{textblock*}{4cm}(11cm,4cm) 703 | \small \textcolor{red}{All the red boxes represent unconnected inputs to components.} 704 | \end{textblock*} 705 | 706 | \begin{textblock*}{4cm}(11cm,6cm) 707 | \small Use explicit connection or variable promotion to get rid of all the red 708 | \end{textblock*} 709 | \end{frame} 710 | 711 | %---------------------------------------------------------------------% 712 | %---------------------------------------------------------------------% 713 | 714 | \begin{frame}{Lab 0.b: The n2 is your best friend!} 715 | \vspace{1cm} % Move image down 716 | 717 | % N2 Diagram and the Legend side by side 718 | \begin{columns} 719 | \column[T]{0.8\textwidth} 720 | \includegraphics[scale=0.3]{images/n2_48.PNG} % N2 diagram 721 | \column[T]{0.2\textwidth} 722 | \hspace{-2cm} 723 | \includegraphics[scale=0.26]{images/n2_legend.png} 724 | \end{columns} 725 | 726 | % Begin all text on the frame 727 | \begin{textblock*}{4.25cm}(3cm,2.3cm) 728 | \footnotesize \textcolor{red}{\textbf{Model hierarchy (groups, components, variables}} 729 | \end{textblock*} 730 | 731 | \begin{textblock*}{4cm}(10cm,2.3cm) 732 | \footnotesize \textcolor{red}{\textbf{Linear and nonlinear solver hierarchy (more on this later…)}} 733 | \end{textblock*} 734 | 735 | \begin{textblock*}{3cm}(3cm,7.5cm) 736 | \footnotesize \textcolor{red}{\textbf{Inside a box is within a single component}} 737 | \end{textblock*} 738 | 739 | \begin{textblock*}{4cm}(4.6cm,5.3cm) 740 | \footnotesize \textcolor{red}{\textbf{Connections!}} % Top grid-line = 2cm, ++ 741 | \end{textblock*} 742 | 743 | % Begin all tikzpictures 744 | \begin{tikzpicture}[overlay] 745 | \draw[red,very thick, <-] (1,5.6) to (1.8,6.3); %Left arrow 746 | \draw[red,very thick, <-] (7.5,5.6) to (8.9,6.3); % Right arrow 747 | \end{tikzpicture} 748 | 749 | 750 | 751 | \end{frame} 752 | 753 | %---------------------------------------------------------------------% 754 | %---------------------------------------------------------------------% 755 | 756 | \begin{frame}{Lab 0.b: Aircraft Range} 757 | \begin{columns} 758 | \column[T]{0.5\textwidth} 759 | \includegraphics[scale=0.3]{images/n2_48.PNG}\newline % N2 diagram 760 | \footnotesize \textcolor{red}{N2 diagram is upper-triangular: 761 | Pure explicit \newline computation (feed-forward)} 762 | 763 | \column[T]{0.4\textwidth} 764 | \begin{itemize} 765 | \item It should look like this! 766 | \vspace{0.5cm} 767 | \item \textbf{This diagram is interactive} 768 | \vspace{0.5cm} 769 | \item Right click on the components/groups to collapse and expand them 770 | \vspace{0.5cm} 771 | \item Click on any black colored square to trace connections between components 772 | \end{itemize} 773 | \end{columns} 774 | \end{frame} % Error is from the image being outside of the column fram (can ignore) 775 | 776 | %---------------------------------------------------------------------% 777 | %---------------------------------------------------------------------% 778 | 779 | \begin{frame}{Lab 0 summary:} 780 | \begin{itemize} 781 | \item \textit{Everything we just did we can do faster in Excel.} 782 | \vspace{0.5cm} 783 | \item This is a tutorial so the models are extremely simple and cheap, but … 784 | \vspace{0.25cm} 785 | \begin{itemize} 786 | \vspace{0.25cm} 787 | \item Real-world models have hierarchies of dozens or hundreds of logical components 788 | \vspace{0.25cm} 789 | \item Real-world models often lack a closed form solution and require some kind of solver or iteration strategy 790 | \end{itemize} 791 | \end{itemize} 792 | \end{frame} 793 | 794 | %---------------------------------------------------------------------% 795 | %---------------------------------------------------------------------% 796 | 797 | \begingroup 798 | \setbeamercolor{background canvas}{bg=GreenYellow} 799 | \begin{frame}{Using solvers with implicit models} 800 | 801 | \begin{itemize} 802 | \item Gradient-free solver: Nonlinear Block Gauss-Seidel (i.e. Fixed point iteration) 803 | \vspace{0.5cm} 804 | \item Gradient-based solver: Newton’s Method 805 | \vspace{0.5cm} 806 | \item Lab 1: Simple aircraft sizing and experimenting with different solver algorithms 807 | \end{itemize} 808 | \end{frame} 809 | \endgroup 810 | 811 | %---------------------------------------------------------------------% 812 | %---------------------------------------------------------------------% 813 | 814 | \begin{frame}{OpenMDAO Nonlinear Solvers} 815 | When a circular dependency is detected, OpenMDAO needs a solver to converge the problem: 816 | 817 | \begin{itemize} 818 | \item Choice 1: NonlinearBlockGS() 819 | \item Choice 2: NewtonSolver() 820 | \item Choice 3: BroydenSolver() 821 | \item Choice 4: NonlinearBlockJac() 822 | \end{itemize} 823 | \end{frame} 824 | 825 | %---------------------------------------------------------------------% 826 | %---------------------------------------------------------------------% 827 | 828 | \begin{frame}{Check out the docs for more info!} 829 | \begin{columns} 830 | % \vspace{1cm} 831 | \column[T]{0.5\textwidth} 832 | Look at the docs for OpenMDAO's standard library: 833 | \newline 834 | 835 | Lots of details on all the different solvers! 836 | \newline 837 | 838 | \uncover<2->{Also see sections for surrogates and helpful general purpose components!} 839 | 840 | \column[T]{0.5\textwidth} 841 | \includegraphics[scale=0.25]{images/documentation_53.png} 842 | \end{columns} 843 | 844 | \begin{tikzpicture}[overlay] 845 | % Arrow on the first frame 846 | \only<1>{\draw[red, very thick, ->](1.5,3.7) to (8.6,0.5);} 847 | % Arrows on the second frame 848 | \uncover<2->{\draw[red, very thick, ->](4,1.4) to (8.6,1.1); 849 | \draw[red, very thick, ->](4,1.4) to (8.6, 0.25);} 850 | \end{tikzpicture} 851 | \end{frame} 852 | 853 | %---------------------------------------------------------------------% 854 | %---------------------------------------------------------------------% 855 | 856 | \begin{frame}{Nonlinear Block Gauss-Seidel} 857 | % Setting up tikzpicture for all nodes, the /.style is the formatting for the respective nodes (shapes) 858 | \begin{tikzpicture}[overlay, 859 | node distance = 0cm and 0.5cm, 860 | model/.style = {rectangle, draw, fill=blue!60, minimum width=2cm, minimum height=2cm, align=center}, 861 | input/.style = {rectangle, draw, fill=blue!20, minimum width=0.75cm, minimum height=0.6cm, align=center}] 862 | 863 | % Large Model Block Nodes (mod#) 864 | \node (mod1) [model, xshift=5cm, yshift=1.7cm ] {Model 1}; 865 | \node (mod2) [model, below=of mod1, xshift=2cm ] {Model 2}; 866 | \node (mod3) [model, below=of mod2, xshift=2cm ] {Model 3}; 867 | 868 | % Smaller input nodes on the left side (in#) 869 | \node (in1) [input, left=of mod1, yshift= 0.7cm ] {}; 870 | \node (in2) [input, below=of in1, yshift= -0.066cm ] {}; 871 | \node (in3) [input, below=of in2, yshift= -0.066cm ] {}; 872 | 873 | \uncover<2->{ 874 | \node (mod1) [model, xshift=5cm, yshift= 1.7cm, fill=green!40] {Model 1}; 875 | \node (in4) [input, above=of mod1, yshift= 0.1cm, fill=green!40] {}; 876 | } 877 | 878 | \uncover<3->{ 879 | \node (mod2) [model, below=of mod1, xshift= 2cm, fill=yellow!40 ] {Model 2}; 880 | \node (in5) [input, above=of mod2, yshift= 0.1cm, fill=yellow!40 ] {}; 881 | \node (in6) [input, below=of in3, yshift= -0.066cm, fill=green!40 ] {}; 882 | \node (in7) [input, below=of in6, yshift= -0.066cm ] {}; 883 | \node (in8) [input, below=of in7, yshift= -0.066cm ] {}; 884 | } 885 | 886 | \uncover<4->{ 887 | \node (mod3) [model, below=of mod2, xshift= 2cm, fill=orange!50 ] {Model 3}; 888 | \node (in9) [input, above=of mod3, yshift= 0.1cm, fill=orange!50 ] {}; 889 | \node (in10) [input, below=of in8, yshift= -0.066cm, fill=green!40 ] {}; 890 | \node (in11) [input, below=of in10, yshift= -0.066cm, fill=yellow!40 ] {}; 891 | \node (in12) [input, below=of in11, yshift= -0.066cm ] {}; 892 | } 893 | 894 | \uncover<5->{ 895 | \node (mod1) [model, xshift=5cm, yshift=1.7cm, fill=red!10 ] {Model 1}; 896 | \node (in1) [input, left=of mod1, yshift= 0.7cm, fill=green!70 ] {}; 897 | \node (in2) [input, below=of in1, yshift= -0.066cm, fill=yellow!70 ] {}; 898 | \node (in3) [input, below=of in2, yshift= -0.066cm, fill=orange!70 ] {}; 899 | \node (in4) [input, above=of mod1, yshift= 0.1cm, fill=red!10 ] {}; 900 | } 901 | 902 | \uncover<6->{ 903 | \node (mod2) [model, below=of mod1, xshift= 2cm, fill=green!70 ] {Model 2}; 904 | \node (in5) [input, above=of mod2, yshift= 0.1cm, fill=green!70 ] {}; 905 | \node (in6) [input, below=of in3, yshift= -0.066cm, fill=red!10 ] {}; 906 | \node (in7) [input, below=of in6, yshift= -0.066cm, fill=yellow!70 ] {}; 907 | \node (in8) [input, below=of in7, yshift= -0.066cm, fill=orange!70 ] {}; 908 | 909 | } 910 | 911 | % line and arrow connecting Model 3 to the input, Only on frame 1 912 | \only<1>{ 913 | \draw [blue, very thick] (mod3) -- (2,-2.3); 914 | \draw [blue, very thick] (2,-2.3) -- (2,1.05); 915 | \draw [blue, very thick, ->] (2,1.05) -- (in3); 916 | } 917 | \end{tikzpicture} 918 | 919 | % Text Blocks ------------------------------------------- 920 | \begin{textblock*}{2cm}(13cm,3cm) 921 | \textbf{\textcolor{red}{Outputs}} 922 | \end{textblock*} 923 | 924 | \begin{textblock*}{3cm}(1cm,5cm) 925 | \footnotesize \textcolor{red}{\textbf{Inputs}} 926 | \end{textblock*} 927 | 928 | \only<1>{ 929 | % Text describing the connection arrow 930 | \begin{textblock*}{3cm}(5cm,7cm) 931 | \textcolor{red}{Cycle connection} 932 | \end{textblock*} 933 | } 934 | 935 | \only<1,2>{ 936 | % Text on left side 937 | \begin{textblock*}{3cm}(1cm,5.5cm) 938 | \footnotesize \textcolor{red}{Initial guess} 939 | \end{textblock*} 940 | } 941 | 942 | \only<2>{ 943 | % Text next to Model 1 block 944 | \begin{textblock*}{5cm}(7.5cm,3cm) 945 | \small \textcolor{red}{Evaluate Model 1 using initial guess inputs } 946 | \end{textblock*} 947 | } 948 | 949 | \only<3>{ 950 | % Text next to Model 2 block 951 | \begin{textblock*}{5cm}(9.5cm,5cm) 952 | \small \textcolor{red}{Substitute Model 1 output into state vector and run Model 2 953 | } 954 | \end{textblock*} 955 | } 956 | 957 | \only<4>{ 958 | % Text next to Model 3 block 959 | \begin{textblock*}{4.3cm}(11.3cm,7cm) 960 | \small \textcolor{red}{Substitute Model 2 output into state vector and run Model 3} 961 | \end{textblock*} 962 | } 963 | 964 | \only<5>{ 965 | % Text next to Model 1 block 966 | \begin{textblock*}{5cm}(7.5cm,2.5cm) 967 | \footnotesize \textcolor{red}{Run Model 1 with input from all prior tools. Repeat until converged} 968 | \end{textblock*} 969 | } 970 | 971 | \only<6>{ 972 | % Text next to Model 1 block 973 | \begin{textblock*}{5cm}(7.5cm,2.5cm) 974 | \footnotesize \textcolor{red}{Run Model 2 with input from all prior tools. Repeat until converged} 975 | \end{textblock*} 976 | } 977 | 978 | \end{frame} 979 | 980 | %---------------------------------------------------------------------% 981 | %---------------------------------------------------------------------% 982 | 983 | \begin{frame}{\small Newton’s Method is simple in one dimension (Gradient Based!)} 984 | \begin{columns} 985 | \column[T]{0.5\textwidth} 986 | \begin{equation} 987 | x_{n+1} = x_{n} - \frac{f(x_{n})}{f'(x_{n})} % Equation 988 | \end{equation} 989 | 990 | \column[T]{0.5\textwidth} 991 | \begin{tikzpicture} 992 | \draw [black, very thick] (0,-0.5) parabola (5,3); % parabola 993 | \draw [black, thick, ->] (0,0) to (6,0); % X-axis 994 | \draw [blue, thin] (2.6, 0) -- (5.4, 3.3); % Intersection line 995 | \draw [blue, ->] (4.5, 2.3) to (4.5, 0); % Xn arrow 996 | \filldraw [red] (4.5,2.3) circle (0.15cm); % Top red circle 997 | \filldraw [red] (2.6,0) circle (0.15cm); % Lower red circle 998 | \end{tikzpicture} 999 | 1000 | \end{columns} 1001 | 1002 | % Begin text blocks 1003 | \begin{textblock*}{3cm}(10cm, 3.5cm) 1004 | \small \textcolor{red}{Slope = $f'(x_{n})$} 1005 | \end{textblock*} 1006 | 1007 | \begin{textblock*}{3cm}(13cm, 5.5cm) 1008 | \small \textcolor{red}{$f'(x_{n})$} 1009 | \end{textblock*} 1010 | 1011 | \begin{textblock*}{3cm}(12.8cm, 7.15cm) 1012 | \small \textcolor{red}{$x_{n}$} 1013 | \end{textblock*} 1014 | 1015 | \begin{textblock*}{3cm}(11cm, 7.15cm) 1016 | \small \textcolor{red}{$x_{n+1}$} 1017 | \end{textblock*} 1018 | \end{frame} 1019 | 1020 | %---------------------------------------------------------------------% 1021 | %---------------------------------------------------------------------% 1022 | 1023 | \begin{frame}{Newton Solver for multiple dimensions is a bit more complex} 1024 | % Matrix representations on left side of frame 1025 | \begin{columns} 1026 | \column[]{0.25\textwidth} 1027 | \vspace{-7cm} 1028 | \begin{tikzpicture}[overlay, 1029 | node distance = 0cm and 0.1cm, 1030 | big/.style = {rectangle, draw, fill=blue!60, minimum width=1.5cm, minimum height=1.5cm, align=center}, 1031 | small/.style = {rectangle, draw, fill=blue!60, minimum width=0.5cm, minimum height=1.5cm, align=center}, 1032 | txt/.style={}] 1033 | 1034 | Note: location of the node matters! 1035 | \node (big1) [big, xshift=.6cm, yshift= 0cm] {}; 1036 | \node (small1) [small, right=of big1 ] {}; 1037 | \node (textA) [txt, above=of big1 ] {\textcolor{red}{$n_{y}$}}; 1038 | \node (textB) [txt, left= of big1 ] {\textcolor{red}{$n_{y}$}}; 1039 | \node (text_eq) [txt, right=of small1 ] {\textbf{\textcolor{red}{=}}}; 1040 | \node (small2) [small, right=of text_eq ] {}; 1041 | \node (textC) [txt, above=of small1 ] {\textcolor{red}{1}}; 1042 | \node (textD) [txt, above=of small2 ] {\textcolor{red}{1}}; 1043 | \node (textE) [txt, right=of small2 ] {\textcolor{red}{$n_{y}$}}; 1044 | \end{tikzpicture} 1045 | 1046 | \column[T]{0.33\textwidth} 1047 | \hspace{-1cm} 1048 | \begin{small} 1049 | \begin{equation*} 1050 | R(x,y)=0 1051 | \end{equation*} 1052 | \begin{equation*} 1053 | \hspace{-1cm} R(x,y + \delta y) = R(x,y) + \frac{\partial R}{\partial y} \delta y + HOT 1054 | \end{equation*} 1055 | \begin{equation*} 1056 | 0 = R(x,y) + \frac{\partial R}{\partial y} \delta y 1057 | \end{equation*} 1058 | \begin{equation*} 1059 | \delta y = -\frac{\partial R^{-1}}{\partial y}R(x,y) 1060 | \end{equation*} 1061 | \begin{equation*} 1062 | \frac{\partial R}{\partial y} \delta y = -R(x,y) 1063 | \end{equation*} 1064 | \begin{equation*} 1065 | y_{n+1} = y_{n} + \delta y 1066 | \end{equation*} 1067 | \end{small} 1068 | 1069 | 1070 | \column[T]{0.33\textwidth} 1071 | % Red circle around equation 1072 | \tikz[overlay] 1073 | \draw [red, very thick] (-3.2cm, -5cm) ellipse (2cm and 0.7cm); 1074 | 1075 | % Begin the text descriptions of each equation 1076 | \only<1>{ 1077 | \begin{textblock*}{5.5cm}(10cm, 6cm) 1078 | \small \textcolor{red}{Drive residuals to 0 x: design variables y: implicit state variables} 1079 | \end{textblock*} 1080 | \tikz[overlay] 1081 | \draw [red, very thick, ->] (1.9,-0.25) to (-2cm, -0.25); 1082 | } 1083 | 1084 | \only<2>{ 1085 | \begin{textblock*}{5.5cm}(10cm, 6cm) 1086 | \small \textcolor{red}{Do a Taylor series with respect to the current point} 1087 | \end{textblock*} 1088 | \tikz[overlay] 1089 | \draw [red, very thick, ->] (2.9,-1.25) to (0.25cm, -1.25); 1090 | } 1091 | 1092 | \only<3>{ 1093 | \begin{textblock*}{5.5cm}(10cm, 6cm) 1094 | \small \textcolor{red}{We want residuals to be 0 at the next iteration y + dy} 1095 | \end{textblock*} 1096 | \tikz[overlay] 1097 | \draw [red, very thick, ->] (2.9,-2.5) to (-1cm, -2.5); 1098 | } 1099 | 1100 | \only<4>{ 1101 | \begin{textblock*}{5.5cm}(10cm, 6cm) 1102 | \small \textcolor{red}{Solve for dy that would drive the linearize residuals to 0} 1103 | \end{textblock*} 1104 | \tikz[overlay] 1105 | \draw [red, very thick, ->] (-0.8,-3.6) to (-1.2cm, -3.6); 1106 | } 1107 | 1108 | \only<5>{ 1109 | \begin{textblock*}{5.5cm}(10cm, 6cm) 1110 | \small \textcolor{red}{But we don’t actually invert. Just solve this linear system} 1111 | \end{textblock*} 1112 | \tikz[overlay] 1113 | \draw [red, very thick, ->] (1,-5) to (-1cm, -5); 1114 | } 1115 | 1116 | \only<6>{ 1117 | \begin{textblock*}{5.5cm}(10cm, 6cm) 1118 | \small \textcolor{red}{Then take a step. Repeat from top until converged} 1119 | \end{textblock*} 1120 | \tikz[overlay] 1121 | \draw [red, very thick, ->] (1,-6) to (-1.5cm, -6); 1122 | } 1123 | 1124 | \end{columns} 1125 | 1126 | \end{frame} 1127 | 1128 | % %---------------------------------------------------------------------% 1129 | % %---------------------------------------------------------------------% 1130 | 1131 | \begin{frame}{Newton Solver needs some partial derivatives! } 1132 | 1133 | % Matrix representations on left side of frame 1134 | \vspace{1cm} 1135 | \begin{centering} 1136 | \begin{tikzpicture}[overlay, 1137 | node distance = 0cm and 0.1cm, 1138 | big/.style = {rectangle, draw, fill=blue!60, minimum width=1.5cm, minimum height=1.5cm, align=center}, 1139 | small/.style = {rectangle, draw, fill=blue!60, minimum width=0.5cm, minimum height=1.5cm, align=center}, 1140 | txt/.style={}] 1141 | 1142 | % Note: location of the node matters! 1143 | \node (big1) [big, xshift=6cm ] {}; 1144 | \node (small1) [small, right=of big1 ] {}; 1145 | \node (textA) [txt, above=of big1 ] {\textcolor{red}{$n_{y}$}}; 1146 | \node (textB) [txt, left= of big1 ] {\textcolor{red}{$n_{y}$}}; 1147 | \node (text_eq) [txt, right=of small1 ] {\textbf{\textcolor{red}{=}}}; 1148 | \node (small2) [small, right=of text_eq ] {}; 1149 | \node (textC) [txt, above=of small1 ] {\textcolor{red}{1}}; 1150 | \node (textD) [txt, above=of small2 ] {\textcolor{red}{1}}; 1151 | \node (textE) [txt, right=of small2 ] {\textcolor{red}{$n_{y}$}}; 1152 | \end{tikzpicture} 1153 | \end{centering} 1154 | 1155 | \begin{centering} 1156 | \vspace{1cm} 1157 | \begin{equation*} 1158 | \frac{\partial R}{\partial y} \delta y = -R(x,y) 1159 | \end{equation*} 1160 | 1161 | \begin{equation*} 1162 | y_{n+1} = y_{n} + \delta y 1163 | \end{equation*} 1164 | \end{centering} 1165 | 1166 | \begin{tikzpicture}[overlay] 1167 | \draw[red, very thick] (5,2.5) rectangle (5.8,4); % Red box 1168 | \draw[red, very thick, ->] (5.5, 4) to (5.7, 4.8); % Left arrow 1169 | \draw[red, very thick, ->] (6.2, 3.5) to (7, 4.8); % Middle arrow 1170 | \draw[red, very thick, ->] (8, 3.5) to (8.6, 4.8); % Right arrow 1171 | \end{tikzpicture} 1172 | 1173 | \end{frame} 1174 | 1175 | % % %---------------------------------------------------------------------% 1176 | % % %---------------------------------------------------------------------% 1177 | 1178 | \begin{frame}{Partial Derivatives in OpenMDAO} 1179 | \lstinputlisting[language=Python]{SourceCodes/Partial_Derivatives_in_OpenMDAO.py} 1180 | 1181 | % Textblocks for equations 1182 | \begin{textblock*}{5.5cm}(10cm, 4cm) 1183 | \footnotesize{ 1184 | \begin{equation*} 1185 | \frac{\partial R}{\partial y}\delta y = -R(x,y) 1186 | \end{equation*}} 1187 | 1188 | \begin{equation*} 1189 | y_{n+1} = y_{n} + \delta y 1190 | \end{equation*} 1191 | \end{textblock*} 1192 | 1193 | % Setting up node styles 1194 | \tikzstyle{eqn_box} = [rectangle, draw, red, very thick, minimum width=0.6cm, minimum height=1cm, align=center] 1195 | \tikzstyle{text_box} = [rectangle, minimum width=3cm, minimum height=1cm, text justified, draw=black, fill= white!50] 1196 | \tikzstyle{arrow} = [red,thick,->,>=stealth, line width=1.5pt] 1197 | \tikzstyle{line} = [red, thick, line width=1.5pt] 1198 | 1199 | % Beginning nodes 1200 | \begin{tikzpicture}[overlay, node distance = 0cm and 0.1cm] 1201 | \node(redbox) [eqn_box, xshift=10.6cm, yshift=4.2cm] {}; 1202 | 1203 | \node(text_1) [text_box, above of=redbox, node distance=1.5cm] {\textcolor{red}{\tiny OpenMDAO assembles the Jacobian for you, from provided component partials}}; 1204 | 1205 | \node(text_2) [text_box, left of=redbox, node distance=4cm, yshift=-.7cm] {\textcolor{red}{\tiny This tells OpenMDAO which partials exist}}; 1206 | 1207 | \node(text_3) [text_box, below of=text_2, node distance=3.3cm, xshift=2cm] {\textcolor{red}{\tiny This tells OpenMDAO the actual values of the partials}}; 1208 | 1209 | % Empty nodes for lines connecting to text boxes 1210 | \coordinate[below = 0.5cm of text_2, xshift=-1.4cm] (empty2); 1211 | \coordinate[left = 1cm of text_3, yshift=0.5cm] (empty3); 1212 | 1213 | % Lines and arrows connecting nodes 1214 | \draw [arrow] (text_1) to (redbox); 1215 | 1216 | \draw [line] (empty2) to (text_2); 1217 | \draw [arrow] (text_2) to (redbox); 1218 | 1219 | \draw [line] (empty3) to (text_3); 1220 | \draw [arrow] (text_3) to (redbox); 1221 | \end{tikzpicture} 1222 | 1223 | \end{frame} 1224 | 1225 | % % %---------------------------------------------------------------------% 1226 | % % %---------------------------------------------------------------------% 1227 | 1228 | \begin{frame}{Lab 1 : Aircraft Sizing} 1229 | \begin{columns} 1230 | \column[T]{0.4\textwidth} 1231 | Next, we will “size” an electric aircraft for desired range 1232 | \vspace{0.25cm} 1233 | 1234 | \begin{itemize} 1235 | \item \footnotesize Open lab\_1\_template.py in a text editor or IDE and rename lab\_1.py 1236 | \item \footnotesize Check the model by building an N2 diagram 1237 | \item \footnotesize This system is implicit (because it has a cycle) but has no implicit components 1238 | \end{itemize} 1239 | 1240 | \column[T]{0.5\textwidth} 1241 | \includegraphics[scale=0.25]{images/slide_65_image.PNG} 1242 | \end{columns} 1243 | \end{frame} 1244 | 1245 | % %---------------------------------------------------------------------% 1246 | % %---------------------------------------------------------------------% 1247 | 1248 | \begin{frame}{Lab 1 : Aircraft Sizing} 1249 | \begin{itemize} 1250 | \item Run the model as-is using NLBGS (python lab\_1.py) 1251 | \begin{itemize} 1252 | \item The numbers that print out are the absolute and relative residuals 1253 | \end{itemize} 1254 | 1255 | \vspace{0.25cm} 1256 | \item Change the empty weight fraction to 0.55 1257 | \begin{itemize} 1258 | \item check out the ExecComp 1259 | \end{itemize} 1260 | 1261 | \vspace{0.25cm} 1262 | \item Try using the Newton solver (~ Line 124). What happens? 1263 | \begin{itemize} 1264 | \item Check your partial derivatives (~ Line 173) 1265 | \item Fix the partial derivatives of the incorrect components 1266 | \item Which solver uses fewer iterations? 1267 | \end{itemize} 1268 | 1269 | \vspace{0.25cm} 1270 | \item Print all the inputs and outputs (~ Line 178, 179) 1271 | \end{itemize} 1272 | 1273 | \end{frame} 1274 | 1275 | %---------------------------------------------------------------------% 1276 | %---------------------------------------------------------------------% 1277 | 1278 | \begin{frame}{check partials output} 1279 | 1280 | \includegraphics[scale=0.25]{images/slide_67_image.PNG} 1281 | 1282 | \end{frame} 1283 | 1284 | %---------------------------------------------------------------------% 1285 | %---------------------------------------------------------------------% 1286 | 1287 | \begin{frame}{Lab 1 : Aircraft Sizing continued} 1288 | \begin{columns} 1289 | \hspace{-1cm} 1290 | \column[T]{0.6\textwidth} 1291 | 1292 | \begin{itemize} 1293 | \item Switch back to the Gauss-Seidel solver 1294 | 1295 | \vspace{0.25cm} 1296 | \item Switch to the WeightBuildImplicit component 1297 | 1298 | \begin{itemize} 1299 | \item Solves for TOW by forcing design range equal to analyzed range 1300 | \item What happens when you run the model? Why? 1301 | \item Try the Newton solver 1302 | \end{itemize} 1303 | \end{itemize} 1304 | 1305 | \column[T]{0.5\textwidth} 1306 | \includegraphics[scale=0.24]{images/slide_68_image.PNG} 1307 | \end{columns} 1308 | \end{frame} 1309 | 1310 | % %---------------------------------------------------------------------% 1311 | % %---------------------------------------------------------------------% 1312 | 1313 | \begingroup 1314 | \setbeamercolor{background canvas}{bg=GreenYellow} 1315 | \begin{frame}{Optimization with and without analytic derivatives} 1316 | 1317 | \begin{itemize} 1318 | \item High level introduction to different methods of computing derivatives 1319 | \vspace{0.5cm} 1320 | \item Lab 2: Optimization of a FEM for a cantilever beam 1321 | \end{itemize} 1322 | 1323 | \end{frame} 1324 | \endgroup 1325 | 1326 | % %---------------------------------------------------------------------% 1327 | % %---------------------------------------------------------------------% 1328 | 1329 | \begin{frame}{Optimization} 1330 | \begin{columns} 1331 | \column[T]{0.5\textwidth} 1332 | \begin{itemize} 1333 | \item OpenMDAO is capable of evaluating DOEs just like ModelCenter, but… 1334 | \item Gradient based optimization is A LOT faster! 1335 | \item So we need \textbf{total derivatives} across the whole model: 1336 | \end{itemize} 1337 | 1338 | \column[T]{0.5\textwidth} 1339 | \begin{equation*} 1340 | \hspace{-3cm} \vspace{-0.5cm} \frac{df(x)}{dx} 1341 | \end{equation*} 1342 | 1343 | \begin{tikzpicture}[overlay, node distance = 0cm and 0.1cm] 1344 | \tikzstyle{arrow} = [red, line width=1.5pt, ->, >=stealth] 1345 | \tikzstyle{text_box} = [rectangle, minimum width=1cm, minimum height=1cm, text justified, fill= white!50] 1346 | 1347 | \node(image) [text_box, xshift=3cm, yshift=-0.5cm] {\includegraphics[scale=0.2]{images/slide_70_image.PNG}}; 1348 | 1349 | \node(eqn1) [text_box, right of=image, node distance=3.4cm] {f(x)}; 1350 | 1351 | \node(eqn2) [text_box, below of=image, node distance=3.3cm] {$\displaystyle{\frac{df(x)}{dx}}$ \hspace{1cm} \textcolor{red}{??}}; 1352 | 1353 | \node(circ1) [below of=eqn2, circle,red, thick, draw, minimum size=2cm, xshift=-1cm] (c) {}; 1354 | 1355 | \draw [arrow] (image) to (eqn1); 1356 | \end{tikzpicture} 1357 | 1358 | \end{columns} 1359 | 1360 | \end{frame} 1361 | 1362 | % %---------------------------------------------------------------------% 1363 | % %---------------------------------------------------------------------% 1364 | 1365 | \begin{frame}{Finite difference is easy} 1366 | \begin{columns} 1367 | \column[T]{0.5\textwidth} 1368 | \small{ 1369 | \begin{equation*} 1370 | {\frac{\partial F}{\partial x_{j}} = \frac{F(x+xe_{j}h) - F(x)}{h} + \theta (h)} 1371 | \end{equation*} 1372 | 1373 | \vspace{1cm} 1374 | 1375 | f(x+h) \hspace{.25cm} + 1.2345678901234\textcolor{red}{31}\\ 1376 | f(x) \hspace{.75cm} + 1.234567890123456\\ 1377 | $\Delta f$ \hspace{1cm} - 0.000000000000025 1378 | } 1379 | 1380 | \column[T]{0.5\textwidth} 1381 | \begin{tikzpicture}[overlay, node distance=0m and 0 cm] 1382 | \tikzstyle{axes} = [black, line width = 1.5pt, ->, >=stealth] 1383 | \tikzstyle{dotted} = [dashed] 1384 | \tikzstyle{blueline} = [blue, thick] 1385 | \tikzstyle{redline} = [red, thick] 1386 | \tikzstyle{text_box} = [rectangle, minimum width=1cm, minimum height=1cm, text justified] 1387 | 1388 | % To move the image, change the 'yshift' value, and/or 'xshift' 1389 | \node(origin) [circle, fill, inner sep=1.5pt, yshift=-4.5cm, xshift=1cm] {}; 1390 | \coordinate[above = 5cm of origin] (y); 1391 | \coordinate[right = 5cm of origin] (x); 1392 | 1393 | \draw [axes] (origin) to (y); 1394 | \draw [axes] (origin) to (x); 1395 | 1396 | % coordinates along x and y axes 1397 | \coordinate [above = 2.5cm of origin] (fx); 1398 | \coordinate [above=0.75cm of origin] (fxh); 1399 | \coordinate [right=2cm of origin] (x_cord); 1400 | \coordinate [right=3.5cm of origin] (xh); 1401 | \coordinate [right=3.6cm of fxh] (fxh_xh_int); 1402 | 1403 | % intersection point of curved line 1404 | \node(int) [right=2cm of fx, circle, fill, inner sep=2pt] {}; 1405 | 1406 | % dotted lines 1407 | \draw[dotted] (fx) to (int); 1408 | \draw[dotted] (int) to (x_cord); 1409 | \draw[dotted] (fxh) to (fxh_xh_int); 1410 | \draw[dotted] (xh) to (fxh_xh_int); 1411 | 1412 | % red line 1413 | \draw[redline] (fxh_xh_int) to (int); 1414 | 1415 | % blue line 1416 | \coordinate [below=2cm of int, xshift=0.7cm] (blue_bot); 1417 | \coordinate [above=2cm of int, xshift=-0.7cm] (blue_top); 1418 | \draw[blueline] (blue_bot) to (blue_top); 1419 | 1420 | % Curved plot 1421 | \coordinate [above=2cm of fx, xshift=0.5cm] (top); 1422 | \coordinate [right=1cm of xh, yshift=0.6cm] (bottom); 1423 | \draw [very thick] (top) to [out=-20,in=110] (int) to [out=290,in=180] (bottom); 1424 | 1425 | \node(y_fx) [text_box, left of=fx, node distance=0.5cm] {\footnotesize f(x)} {}; 1426 | \node(y_fxh) [text_box, left of=fxh, node distance=0.75cm] {\footnotesize f(x+h)} {}; 1427 | \node(xx) [text_box, below of=x_cord, node distance=0.3cm] {\footnotesize x} {}; 1428 | \node(xxh) [text_box, below of=xh, node distance=0.3cm] {\footnotesize x+h} {}; 1429 | 1430 | \end{tikzpicture} 1431 | \end{columns} 1432 | 1433 | \end{frame} 1434 | 1435 | % \begin{frame}{Finite difference is easy, but. . .} 1436 | % 1437 | % \begin{columns} 1438 | % \column[T]{0.5 \textwidth} 1439 | % \begin{itemize} 1440 | % \item Treats your model as a black-box 1441 | % \item Expensive 1442 | % \item Accuracy can be bad, especially close to the optimal point 1443 | % \end{itemize} 1444 | % 1445 | % \vspace{-0.5cm} 1446 | % 1447 | % \begin{equation*} 1448 | % \frac{\partial \textbf{F}}{\partial x_{j}} =\frac{ \textbf{F}(\textbf{x}+e_{j}h)-\textbf{F(x)}}{h} +\Theta (h) 1449 | % \end{equation*} 1450 | % 1451 | % \column[T]{0.5 \textwidth} 1452 | % \includegraphics[scale=0.3]{images/slide73.png} 1453 | % 1454 | % \end{columns} 1455 | % 1456 | % \end{frame} 1457 | 1458 | 1459 | \begin{frame}{Friends don’t let friends finite difference across solvers!} 1460 | \textbf{\small Seriously. Don’t do this unless you really have to.} 1461 | 1462 | \begin{itemize} 1463 | \item \small It’s expensive (make your solver tolerances really tight) 1464 | \item \small It’s inaccurate 1465 | \item \small It will probably make your optimization converge slowly! 1466 | \end{itemize} 1467 | 1468 | \small Lots of papers on this topic: 1469 | 1470 | \tiny { 1471 | \begin{itemize} 1472 | \item \textbf{E. S. Hendricks and J. S. Gray, “Pycycle: a tool for efficient optimization of gas turbine engine cycles,”Aerospace, vol. 6, iss. 87, 2019.} 1473 | 1474 | \item E. S. Hendricks, “A multi-level multi-design point approach for gas turbine cycle and turbine conceptual design,” PhD Thesis, 2017. 1475 | 1476 | \item J. S. Gray, T. A. Hearn, K. T. Moore, J. Hwang, J. Martins, and A. Ning, “Automatic Evaluation of Multidisciplinary Derivatives Using a Graph-Based Problem Formulation in OpenMDAO,” in15th AIAA/ISSMO Multidisciplinary Analysis and Optimization Conference, 2014. 1477 | 1478 | \item J. S. Gray, K. T. Moore, T. A. Hearn, and B. A. Naylor, “Standard Platform for Benchmarking Multidisciplinary Design Analysis and Optimization Architectures,”AIAA Journal, vol. 51, iss. 10, p. 2380–2394, 2013. 1479 | 1480 | \item C. Marriage and Martins, J. R. R. A. “Reconfigurable Semi-Analytic Sensitivity Methods and MDO Architectures Within the $\pi$MDO Framework”, in Proceedings of the 12th AIAA/ISSMO Multidisciplinary Analysis and Optimization Conference, Victoria, BC, 2008 1481 | \end{itemize} 1482 | } 1483 | 1484 | \end{frame} 1485 | % ------------------------------------------ 1486 | % ------------------------------------------ 1487 | 1488 | % \begin{frame}{Complex step is more accurate than FD, but} 1489 | % \begin{columns} 1490 | % \column[T]{0.5 \textwidth} 1491 | % \begin{itemize} 1492 | % \item Treats your models as a gray-box (might require code modification 1493 | % \item Even more expensive 1494 | % \item some functions are tricky ... 1495 | % \begin{itemize} 1496 | % \item min(), max(), abs() can cause issues 1497 | % \end{itemize} 1498 | % \end{itemize} 1499 | % 1500 | % \vspace{-0.5cm} 1501 | % 1502 | % \begin{equation*} 1503 | % \frac{\partial \textbf{F}}{\partial x_{j}} =\frac{Im(\textbf{F}(\textbf{x}+ihe_{j}))}{h} +\Theta (h^{2}) 1504 | % \end{equation*} 1505 | % 1506 | % \column[T]{0.5 \textwidth} 1507 | % \includegraphics[scale=0.27]{images/slide75.png} 1508 | % \end{columns} 1509 | % 1510 | % \end{frame} 1511 | 1512 | 1513 | % %---------------------------------------------------------------------% 1514 | % %---------------------------------------------------------------------% 1515 | 1516 | % \begin{frame}{Analytic derivatives are both fast and accurate!} 1517 | % \begin{columns} 1518 | % \column[T]{0.5 \textwidth} 1519 | % \includegraphics[scale=0.25]{images/slide76.png} 1520 | % 1521 | % \column[T]{0.5 \textwidth} 1522 | % \vspace{2cm} 1523 | % \begin{equation*} 1524 | % \frac{df}{dx} = \frac{\partial f}{\partial y_{b}} \frac{\partial y_{b}}{\partial y_{a}} \frac{\partial y_{a}}{\partial x} 1525 | % \end{equation*} 1526 | % \end{columns} 1527 | % 1528 | % \end{frame} 1529 | 1530 | % %---------------------------------------------------------------------% 1531 | % %---------------------------------------------------------------------% 1532 | 1533 | % \begin{frame}{What about models with coupling?} 1534 | % \centering 1535 | % \includegraphics[scale=0.25]{images/slide77.png} 1536 | % 1537 | % \end{frame} 1538 | 1539 | % %---------------------------------------------------------------------% 1540 | % %---------------------------------------------------------------------% 1541 | 1542 | % \begin{frame}{How do you differentiate through this convergence loop? } 1543 | % \begin{columns} 1544 | % \column[T]{0.4 \textwidth} 1545 | % \includegraphics[scale=0.2]{images/slide78.png} 1546 | % 1547 | % \column[T]{0.6 \textwidth} 1548 | % \begin{itemize} 1549 | % \item We'll need a nonlinear solver to converge this coupling 1550 | % \vspace{0.5cm} 1551 | % \item The solver loop changes the way you compute analytic derivatives 1552 | % \end{itemize} 1553 | % \end{columns} 1554 | % 1555 | % \end{frame} 1556 | 1557 | % %---------------------------------------------------------------------% 1558 | % %---------------------------------------------------------------------% 1559 | % \begin{frame}{Manually computing analytic derivatives for coupled models takes a lot of work} 1560 | % \begin{columns} 1561 | % \column[T]{0.35 \textwidth} 1562 | % \includegraphics[scale=0.2]{images/slide79.png} 1563 | % 1564 | % \column[T]{0.65 \textwidth} 1565 | % 1566 | % \begin{equation*} 1567 | % \frac{df}{dx} = \frac{\partial f}{\partial y_{b}} \frac{\partial y_{b}}{\partial y_{a}} \frac{\partial y_{a}}{\partial x} + \frac{\partial f}{\partial y_{b}} \frac{\partial y_{b}}{dx} + \frac{\partial y_{b}}{dx} + \frac{\partial f}{\partial y_{a}} \frac{dy_{a}}{dx} 1568 | % \end{equation*} 1569 | % \end{columns} 1570 | % 1571 | % \end{frame} 1572 | 1573 | % %---------------------------------------------------------------------% 1574 | % %---------------------------------------------------------------------% 1575 | 1576 | % \begin{frame}{How to compute these extra terms?} 1577 | % \begin{columns} 1578 | % \column[T]{0.35 \textwidth} 1579 | % \includegraphics[scale=0.2]{images/slide79.png} 1580 | % 1581 | % \column[T]{0.65 \textwidth} 1582 | % 1583 | % \begin{equation*} 1584 | % \frac{df}{dx} = \frac{\partial f}{\partial y_{b}} \frac{\partial y_{b}}{\partial y_{a}} \frac{\partial y_{a}}{\partial x} + \frac{\partial f}{\partial y_{b}} \frac{\partial y_{b}}{dx} + \frac{\partial y_{b}}{dx} + \frac{\partial f}{\partial y_{a}} \frac{dy_{a}}{dx} 1585 | % \end{equation*} 1586 | % 1587 | % 1588 | % \begin{tikzpicture}[overlay, node distance=0m and 0 cm] 1589 | % \tikzstyle{red_box} = [rectangle, draw, red, very thick, minimum width=0.7cm, minimum height=1.4cm, align=center] 1590 | % \tikzstyle{text_box} = [rectangle, minimum width=1cm, minimum height=1cm, text justified] 1591 | % 1592 | % \node(text) [text_box, yshift=-2.5cm, xshift=4.5cm] {\textcolor{red}{These terms will be computed using adjoints!}}; 1593 | % 1594 | % % to shift red boxes, move only box 1, the lines will follow 1595 | % \node(box1) [red_box, above of=text, node distance=3.7cm, xshift=2.2cm] {}; 1596 | % \node(box2) [red_box, right of=box1, node distance=2cm] {}; 1597 | % 1598 | % \draw[red, thick] (text) -- (box1); 1599 | % \draw[red, thick] (text) -- (box2); 1600 | % \end{tikzpicture} 1601 | % \end{columns} 1602 | % 1603 | % \end{frame} 1604 | 1605 | %---------------------------------------------------------------------% 1606 | %---------------------------------------------------------------------% 1607 | 1608 | 1609 | \begin{frame}{When models get really big, have fun!} 1610 | \begin{columns} 1611 | \column[T]{0.6 \textwidth} 1612 | \includegraphics[scale=0.5]{images/slide_81.png} 1613 | 1614 | \column[T]{0.4 \textwidth} 1615 | \begin{center} 1616 | 1617 | 1618 | \begin{equation*} 1619 | \LARGE\frac{df}{dx} = ??? 1620 | \end{equation*} 1621 | \end{center} 1622 | Lets be honest, this is just not going to happen 1623 | 1624 | \end{columns} 1625 | 1626 | \end{frame} 1627 | 1628 | %---------------------------------------------------------------------% 1629 | %---------------------------------------------------------------------% 1630 | 1631 | \begin{frame}{OpenMDAO can compute these total derivatives for you, automatically!} 1632 | \begin{columns} 1633 | \column[T]{0.5 \textwidth} 1634 | \includegraphics[scale=0.5]{images/slide_81.png} 1635 | 1636 | \column[T]{0.5 \textwidth} 1637 | \begin{center} 1638 | 1639 | \begin{equation*} 1640 | \LARGE\frac{df}{dx} = [ask OpenMDAO] 1641 | \end{equation*} 1642 | 1643 | \textbf{For a deep dive on the math} 1644 | \begin{itemize} 1645 | \item Martins and Hwang 2013 1646 | \item Hwang and Martins 2018 1647 | \end{itemize} 1648 | \end{center} 1649 | \end{columns} 1650 | 1651 | \end{frame} 1652 | 1653 | %---------------------------------------------------------------------% 1654 | %---------------------------------------------------------------------% 1655 | 1656 | \begin{frame}{OpenMDAO splits total derivative computation into two steps} 1657 | \begin{columns} 1658 | \column[T]{0.4 \textwidth} 1659 | \includegraphics[scale=0.2]{images/slide76.png} 1660 | 1661 | \column[T]{0.6 \textwidth} 1662 | \begin{itemize} 1663 | \item Computing the partial derivatives for each component 1664 | \item Solving a linear system for total derivatives 1665 | \end{itemize} 1666 | \end{columns} 1667 | 1668 | \end{frame} 1669 | 1670 | 1671 | 1672 | 1673 | 1674 | 1675 | \end{document} 1676 | -------------------------------------------------------------------------------- /motivation_to_use_openmdao.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/motivation_to_use_openmdao.pdf -------------------------------------------------------------------------------- /openmdao_beam.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division, absolute_import 2 | 3 | import openmdao.api as om 4 | from openmdao.test_suite.test_examples.beam_optimization.beam_group import BeamGroup 5 | 6 | 7 | E = 1. 8 | L = 1. 9 | b = 0.1 10 | volume = 0.01 11 | 12 | num_elements = 50 13 | 14 | prob = om.Problem(model=BeamGroup(E=E, L=L, b=b, volume=volume, num_elements=num_elements)) 15 | 16 | prob.driver = om.ScipyOptimizeDriver() 17 | prob.driver.options['optimizer'] = 'SLSQP' 18 | prob.driver.options['tol'] = 1e-9 19 | prob.driver.options['disp'] = True 20 | 21 | prob.setup(force_alloc_complex=False) 22 | 23 | prob['inputs_comp.h'][:] = 1.0 24 | 25 | 26 | prob.run_driver() 27 | 28 | # prob.model.list_outputs(print_arrays=True) 29 | 30 | print('Optimal element height distribution:') 31 | print(prob['inputs_comp.h']) 32 | -------------------------------------------------------------------------------- /openmdao_workshop.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/openmdao_workshop.pdf -------------------------------------------------------------------------------- /openmdao_workshop.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenMDAO/openmdao_training/82fb9872b354fcba2c98aceae6143f5d67c32b1c/openmdao_workshop.pptx -------------------------------------------------------------------------------- /paraboloid.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demonstration of a model using the Paraboloid component. 3 | """ 4 | from __future__ import division, print_function 5 | import openmdao.api as om 6 | 7 | class Paraboloid(om.ExplicitComponent): 8 | """ 9 | Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3. 10 | """ 11 | 12 | def setup(self): 13 | self.add_input('x', val=0.0) 14 | self.add_input('y', val=0.0) 15 | 16 | self.add_output('f_xy', val=0.0) 17 | 18 | # Finite difference all partials. 19 | self.declare_partials('*', '*', method='fd') 20 | 21 | def compute(self, inputs, outputs): 22 | """ 23 | f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3 24 | 25 | Minimum at: x = 6.6667; y = -7.3333 26 | """ 27 | x = inputs['x'] 28 | y = inputs['y'] 29 | 30 | outputs['f_xy'] = (x-3.0)**2 + x*y + (y+4.0)**2 - 3.0 31 | 32 | 33 | if __name__ == "__main__": 34 | 35 | model = om.Group() 36 | ivc = om.IndepVarComp() 37 | ivc.add_output('x', 3.0) 38 | ivc.add_output('y', -4.0) 39 | model.add_subsystem('des_vars', ivc) 40 | model.add_subsystem('parab_comp', Paraboloid()) 41 | 42 | model.connect('des_vars.x', 'parab_comp.x') 43 | model.connect('des_vars.y', 'parab_comp.y') 44 | 45 | prob = om.Problem(model) 46 | prob.setup() 47 | prob.run_model() 48 | print(prob['parab_comp.f_xy']) 49 | 50 | prob['des_vars.x'] = 5.0 51 | prob['des_vars.y'] = -2.0 52 | prob.run_model() 53 | print(prob['parab_comp.f_xy']) 54 | --------------------------------------------------------------------------------