Example #1
0
    def test_guess_nonlinear_inputs_read_only_reset(self):
        class ImpWithInitial(ImplicitComponent):

            def setup(self):
                self.add_input('x', 3.0)
                self.add_output('y', 4.0)

            def guess_nonlinear(self, inputs, outputs, resids):
                raise AnalysisError("It's just a scratch.")

        group = Group()

        group.add_subsystem('px', IndepVarComp('x', 77.0))
        group.add_subsystem('comp1', ImpWithInitial())
        group.add_subsystem('comp2', ImpWithInitial())
        group.connect('px.x', 'comp1.x')
        group.connect('comp1.y', 'comp2.x')

        group.nonlinear_solver = NewtonSolver()
        group.nonlinear_solver.options['maxiter'] = 1

        prob = Problem(model=group)
        prob.set_solver_print(level=0)
        prob.setup(check=False)

        with self.assertRaises(AnalysisError):
            prob.run_model()

        # verify read_only status is reset after AnalysisError
        prob['comp1.x'] = 111.
Example #2
0
    def test_guess_nonlinear_resids_read_only(self):
        class ImpWithInitial(ImplicitComponent):

            def setup(self):
                self.add_input('x', 3.0)
                self.add_output('y', 4.0)

            def guess_nonlinear(self, inputs, outputs, resids):
                # inputs is read_only, should not be allowed
                resids['y'] = 0.

        group = Group()

        group.add_subsystem('px', IndepVarComp('x', 77.0))
        group.add_subsystem('comp1', ImpWithInitial())
        group.add_subsystem('comp2', ImpWithInitial())
        group.connect('px.x', 'comp1.x')
        group.connect('comp1.y', 'comp2.x')

        group.nonlinear_solver = NewtonSolver()
        group.nonlinear_solver.options['maxiter'] = 1

        prob = Problem(model=group)
        prob.set_solver_print(level=0)
        prob.setup(check=False)

        with self.assertRaises(ValueError) as cm:
            prob.run_model()

        self.assertEqual(str(cm.exception),
                         "Attempt to set value of 'y' in residual vector "
                         "when it is read only.")
Example #3
0
    def test_guess_nonlinear_inputs_read_only_reset(self):
        class ImpWithInitial(ImplicitComponent):
            def setup(self):
                self.add_input('x', 3.0)
                self.add_output('y', 4.0)

            def guess_nonlinear(self, inputs, outputs, resids):
                raise AnalysisError("It's just a scratch.")

        group = Group()

        group.add_subsystem('px', IndepVarComp('x', 77.0))
        group.add_subsystem('comp1', ImpWithInitial())
        group.add_subsystem('comp2', ImpWithInitial())
        group.connect('px.x', 'comp1.x')
        group.connect('comp1.y', 'comp2.x')

        group.nonlinear_solver = NewtonSolver()
        group.nonlinear_solver.options['maxiter'] = 1

        prob = Problem(model=group)
        prob.set_solver_print(level=0)
        prob.setup(check=False)

        with self.assertRaises(AnalysisError):
            prob.run_model()

        # verify read_only status is reset after AnalysisError
        prob['comp1.x'] = 111.
Example #4
0
    def test_shape(self):
        n = 100

        bal = BalanceComp()
        bal.add_balance('x', shape=(n,))

        tgt = IndepVarComp(name='y_tgt', val=4*np.ones(n))

        exe = ExecComp('y=x**2', x=np.zeros(n), y=np.zeros(n))

        model = Group()

        model.add_subsystem('tgt', tgt, promotes_outputs=['y_tgt'])
        model.add_subsystem('exe', exe)
        model.add_subsystem('bal', bal)

        model.connect('y_tgt', 'bal.rhs:x')
        model.connect('bal.x', 'exe.x')
        model.connect('exe.y', 'bal.lhs:x')

        model.linear_solver = DirectSolver(assemble_jac=True)
        model.nonlinear_solver = NewtonSolver(maxiter=100, iprint=0)

        prob = Problem(model)
        prob.setup()

        prob['bal.x'] = np.random.rand(n)

        prob.run_model()

        assert_almost_equal(prob['bal.x'], 2.0*np.ones(n), decimal=7)
Example #5
0
    def test_shape(self):
        n = 100

        bal = BalanceComp()
        bal.add_balance('x', shape=(n, ))

        tgt = IndepVarComp(name='y_tgt', val=4 * np.ones(n))

        exe = ExecComp('y=x**2', x=np.zeros(n), y=np.zeros(n))

        model = Group()

        model.add_subsystem('tgt', tgt, promotes_outputs=['y_tgt'])
        model.add_subsystem('exe', exe)
        model.add_subsystem('bal', bal)

        model.connect('y_tgt', 'bal.rhs:x')
        model.connect('bal.x', 'exe.x')
        model.connect('exe.y', 'bal.lhs:x')

        model.linear_solver = DirectSolver(assemble_jac=True)
        model.nonlinear_solver = NewtonSolver(maxiter=100, iprint=0)

        prob = Problem(model)
        prob.setup()

        prob['bal.x'] = np.random.rand(n)

        prob.run_model()

        assert_almost_equal(prob['bal.x'], 2.0 * np.ones(n), decimal=7)
Example #6
0
    def test_guess_nonlinear_resids_read_only(self):
        class ImpWithInitial(ImplicitComponent):
            def setup(self):
                self.add_input('x', 3.0)
                self.add_output('y', 4.0)

            def guess_nonlinear(self, inputs, outputs, resids):
                # inputs is read_only, should not be allowed
                resids['y'] = 0.

        group = Group()

        group.add_subsystem('px', IndepVarComp('x', 77.0))
        group.add_subsystem('comp1', ImpWithInitial())
        group.add_subsystem('comp2', ImpWithInitial())
        group.connect('px.x', 'comp1.x')
        group.connect('comp1.y', 'comp2.x')

        group.nonlinear_solver = NewtonSolver()
        group.nonlinear_solver.options['maxiter'] = 1

        prob = Problem(model=group)
        prob.set_solver_print(level=0)
        prob.setup(check=False)

        with self.assertRaises(ValueError) as cm:
            prob.run_model()

        self.assertEqual(
            str(cm.exception),
            "Attempt to set value of 'y' in residual vector "
            "when it is read only.")
Example #7
0
    def test_scalar_with_guess_func_additional_input(self):

        model = Group(assembled_jac_type='dense')

        bal = BalanceComp()
        bal.add_balance('x')
        bal.add_input('guess_x', val=0.0)

        ivc = IndepVarComp()
        ivc.add_output(name='y_tgt', val=4)
        ivc.add_output(name='guess_x', val=2.5)

        exec_comp = ExecComp('y=x**2', x={'value': 1}, y={'value': 1})

        model.add_subsystem(name='ivc',
                            subsys=ivc,
                            promotes_outputs=['y_tgt', 'guess_x'])
        model.add_subsystem(name='exec', subsys=exec_comp)
        model.add_subsystem(name='balance', subsys=bal)

        model.connect('guess_x', 'balance.guess_x')
        model.connect('y_tgt', 'balance.rhs:x')
        model.connect('balance.x', 'exec.x')
        model.connect('exec.y', 'balance.lhs:x')

        model.linear_solver = DirectSolver(assemble_jac=True)
        model.nonlinear_solver = NewtonSolver(maxiter=100, iprint=0)

        prob = Problem(model)
        prob.setup()

        # run problem without a guess function
        prob['balance.x'] = .5
        prob.run_model()

        assert_almost_equal(prob['balance.x'], 2.0, decimal=7)

        iters_no_guess = model.nonlinear_solver._iter_count

        # run problem with same initial value and a guess function
        def guess_function(inputs, outputs, resids):
            outputs['x'] = inputs['guess_x']

        bal.options['guess_func'] = guess_function

        prob['balance.x'] = .5
        prob.run_model()

        assert_almost_equal(prob['balance.x'], 2.0, decimal=7)

        iters_with_guess = model.nonlinear_solver._iter_count

        # verify it converges faster with the guess function
        self.assertTrue(iters_with_guess < iters_no_guess)
Example #8
0
    def setup(self):
        #Design variables
        self.add_subsystem('z_ini',
                           IndepVarComp(
                               'z', np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0])),
                           promotes=['*'])
        self.add_subsystem('x_aer_ini',
                           IndepVarComp('x_aer', 1.0),
                           promotes=['*'])
        self.add_subsystem('x_str_ini',
                           IndepVarComp('x_str', np.array([1.0, 1.0])),
                           promotes=['*'])
        self.add_subsystem('x_pro_ini',
                           IndepVarComp('x_pro', 1.0),
                           promotes=['*'])

        #Disciplines
        sap_group = Group()
        sap_group.add_subsystem('Structure',
                                Structure(self.scalers),
                                promotes=['*'])
        sap_group.add_subsystem('Aerodynamics',
                                Aerodynamics(self.scalers),
                                promotes=['*'])
        sap_group.add_subsystem('Propulsion',
                                Propulsion(self.scalers),
                                promotes=['*'])

        sap_group.nonlinear_solver = NonlinearBlockGS()
        sap_group.nonlinear_solver.options['atol'] = 1.0e-3
        sap_group.linear_solver = ScipyKrylov()
        self.add_subsystem('Mda', sap_group, promotes=['*'])

        self.add_subsystem('Performance',
                           Performance(self.scalers),
                           promotes=['*'])

        #Constraints
        cstrs = [
            'con_theta_up = Theta*' + str(self.scalers['Theta']) + '-1.04',
            'con_theta_low = 0.96-Theta*' + str(self.scalers['Theta']),
            'con_dpdx = dpdx*' + str(self.scalers['dpdx']) + '-1.04',
            'con1_esf = ESF*' + str(self.scalers['ESF']) + '-1.5',
            'con2_esf = 0.5-ESF*' + str(self.scalers['ESF']),
            'con_temp = Temp*' + str(self.scalers['Temp']) + '-1.02',
            'con_dt=DT'
        ]
        for i in range(5):
            cstrs.append('con_sigma' + str(i + 1) + ' = sigma[' + str(i) +
                         ']*' + str(self.scalers['sigma'][i]) + '-1.09')
        self.add_subsystem('Constraints',
                           ExecComp(cstrs, sigma=np.zeros(5)),
                           promotes=['*'])
Example #9
0
    def test_scalar_with_guess_func_additional_input(self):

        model = Group(assembled_jac_type='dense')

        bal = BalanceComp()
        bal.add_balance('x')
        bal.add_input('guess_x', val=0.0)

        ivc = IndepVarComp()
        ivc.add_output(name='y_tgt', val=4)
        ivc.add_output(name='guess_x', val=2.5)

        exec_comp = ExecComp('y=x**2', x={'value': 1}, y={'value': 1})

        model.add_subsystem(name='ivc', subsys=ivc, promotes_outputs=['y_tgt', 'guess_x'])
        model.add_subsystem(name='exec', subsys=exec_comp)
        model.add_subsystem(name='balance', subsys=bal)

        model.connect('guess_x', 'balance.guess_x')
        model.connect('y_tgt', 'balance.rhs:x')
        model.connect('balance.x', 'exec.x')
        model.connect('exec.y', 'balance.lhs:x')

        model.linear_solver = DirectSolver(assemble_jac=True)
        model.nonlinear_solver = NewtonSolver(maxiter=100, iprint=0)

        prob = Problem(model)
        prob.setup()

        # run problem without a guess function
        prob['balance.x'] = .5
        prob.run_model()

        assert_almost_equal(prob['balance.x'], 2.0, decimal=7)

        iters_no_guess = model.nonlinear_solver._iter_count

        # run problem with same initial value and a guess function
        def guess_function(inputs, outputs, resids):
            outputs['x'] = inputs['guess_x']

        bal.options['guess_func'] = guess_function

        prob['balance.x'] = .5
        prob.run_model()

        assert_almost_equal(prob['balance.x'], 2.0, decimal=7)

        iters_with_guess = model.nonlinear_solver._iter_count

        # verify it converges faster with the guess function
        self.assertTrue(iters_with_guess < iters_no_guess)
Example #10
0
    def coupled_group(self):
        # type: () -> Optional[Group]
        """:obj:`Group`, optional: Group wrapping the coupled blocks with a converger specified in the CMDOWS file.

        If no coupled blocks are specified in the CMDOWS file this property is `None`.
        """
        if self.coupled_blocks:
            coupled_group = Group()
            for uid in self.coupled_blocks:
                # Get the correct DisciplineComponent
                discipline_component = self.discipline_components[uid]

                # Change input variable names if they are provided as copies of coupling variables
                promotes = ['*']  # type: List[Union[str, Tuple[str, str]]]
                if not self.has_converger:
                    for i in discipline_component.inputs_from_xml.keys():
                        if i in self.coupling_vars:
                            promotes.append((i, self.coupling_vars[i]['copy']))

                # Add the DisciplineComponent to the group
                coupled_group.add_subsystem(uid, self.discipline_components[uid], promotes)

            # Find the convergence type of the coupled group
            if self.has_converger:
                conv_type = self.elem_problem_def.find('problemFormulation/convergerType').text
                if conv_type == 'Gauss-Seidel':
                    coupled_group.linear_solver = LinearBlockGS()
                    coupled_group.nonlinear_solver = NonlinearBlockGS()
                elif conv_type == 'Jacobi':
                    coupled_group.linear_solver = LinearBlockJac()
                    coupled_group.nonlinear_solver = NonlinearBlockJac()
                else:
                    raise RuntimeError('Specified convergerType "%s" is not supported.' % conv_type)
            else:
                coupled_group.linear_solver = LinearRunOnce()
                coupled_group.nonlinear_solver = NonLinearRunOnce()
            return coupled_group
        return None
Example #11
0
    def test_scalar_guess_func_using_outputs(self):
        # Implicitly solve -(ax^2 + bx) = c using a BalanceComp.
        # For a=1, b=-4 and c=3, there are solutions at x=1 and x=3.

        # Verify that we can set the guess value (and target a solution) based on outputs.

        ind = IndepVarComp()
        ind.add_output('a', 1)
        ind.add_output('b', -4)
        ind.add_output('c', 3)

        lhs = ExecComp('lhs=-(a*x**2+b*x)')

        bal = BalanceComp()

        def guess_function(inputs, outputs, residuals):
            if outputs['x'] < 0:
                return 5.
            else:
                return 0.

        bal.add_balance(name='x', rhs_name='c', guess_func=guess_function)

        model = Group()

        model.add_subsystem('ind_comp', ind, promotes_outputs=['a', 'b', 'c'])
        model.add_subsystem('lhs_comp', lhs, promotes_inputs=['a', 'b', 'x'])
        model.add_subsystem('bal_comp', bal, promotes_inputs=['c'], promotes_outputs=['x'])

        model.connect('lhs_comp.lhs', 'bal_comp.lhs:x')

        model.linear_solver = DirectSolver()
        model.nonlinear_solver = NewtonSolver(maxiter=1000, iprint=0)

        prob = Problem(model)
        prob.setup()

        # initial value of 'x' less than zero, guess should steer us to solution of 3.
        prob['bal_comp.x'] = -1
        prob.run_model()
        assert_almost_equal(prob['bal_comp.x'], 3.0, decimal=7)

        # initial value of 'x' greater than zero, guess should steer us to solution of 1.
        prob['bal_comp.x'] = 99
        prob.run_model()
        assert_almost_equal(prob['bal_comp.x'], 1.0, decimal=7)
Example #12
0
    def test_scalar_with_guess_func(self):

        n = 1

        model = Group(assembled_jac_type='dense')

        def guess_function(inputs, outputs, residuals):
            outputs['x'] = np.sqrt(inputs['rhs:x'])

        bal = BalanceComp(
            'x', guess_func=guess_function)  # test guess_func as kwarg

        tgt = IndepVarComp(name='y_tgt', val=4)

        exec_comp = ExecComp('y=x**2', x={'value': 1}, y={'value': 1})

        model.add_subsystem(name='target',
                            subsys=tgt,
                            promotes_outputs=['y_tgt'])
        model.add_subsystem(name='exec', subsys=exec_comp)
        model.add_subsystem(name='balance', subsys=bal)

        model.connect('y_tgt', 'balance.rhs:x')
        model.connect('balance.x', 'exec.x')
        model.connect('exec.y', 'balance.lhs:x')

        model.linear_solver = DirectSolver(assemble_jac=True)
        model.nonlinear_solver = NewtonSolver(maxiter=100, iprint=0)

        prob = Problem(model)
        prob.setup()

        prob['balance.x'] = np.random.rand(n)
        prob.run_model()

        assert_almost_equal(prob['balance.x'], 2.0, decimal=7)

        # should converge with no iteration due to the guess function
        self.assertEqual(model.nonlinear_solver._iter_count, 1)

        cpd = prob.check_partials(out_stream=None)
        for (of, wrt) in cpd['balance']:
            assert_almost_equal(cpd['balance'][of, wrt]['abs error'],
                                0.0,
                                decimal=5)
Example #13
0
    def test_guess_nonlinear_transfer_subbed2(self):
        # Test that data is transfered to a component before calling guess_nonlinear.

        class ImpWithInitial(ImplicitComponent):

            def setup(self):
                self.add_input('x', 3.0)
                self.add_output('y', 4.0)

            def solve_nonlinear(self, inputs, outputs):
                """ Do nothing. """
                pass

            def apply_nonlinear(self, inputs, outputs, resids):
                """ Do nothing. """
                resids['y'] = 1.0e-6
                pass

            def guess_nonlinear(self, inputs, outputs, resids):
                # Passthrough
                outputs['y'] = inputs['x']


        group = Group()
        sub = Group()

        group.add_subsystem('px', IndepVarComp('x', 77.0))
        sub.add_subsystem('comp1', ImpWithInitial())
        sub.add_subsystem('comp2', ImpWithInitial())
        group.connect('px.x', 'sub.comp1.x')
        group.connect('sub.comp1.y', 'sub.comp2.x')

        group.add_subsystem('sub', sub)

        sub.nonlinear_solver = NewtonSolver()
        sub.nonlinear_solver.options['maxiter'] = 1

        prob = Problem(model=group)
        prob.set_solver_print(level=0)
        prob.setup(check=False)

        prob.run_model()
        assert_rel_error(self, prob['sub.comp2.y'], 77., 1e-5)
Example #14
0
    def test_guess_nonlinear_transfer_subbed2(self):
        # Test that data is transfered to a component before calling guess_nonlinear.

        class ImpWithInitial(ImplicitComponent):

            def setup(self):
                self.add_input('x', 3.0)
                self.add_output('y', 4.0)

            def solve_nonlinear(self, inputs, outputs):
                """ Do nothing. """
                pass

            def apply_nonlinear(self, inputs, outputs, resids):
                """ Do nothing. """
                resids['y'] = 1.0e-6
                pass

            def guess_nonlinear(self, inputs, outputs, resids):
                # Passthrough
                outputs['y'] = inputs['x']

        group = Group()
        sub = Group()

        group.add_subsystem('px', IndepVarComp('x', 77.0))
        sub.add_subsystem('comp1', ImpWithInitial())
        sub.add_subsystem('comp2', ImpWithInitial())
        group.connect('px.x', 'sub.comp1.x')
        group.connect('sub.comp1.y', 'sub.comp2.x')

        group.add_subsystem('sub', sub)

        sub.nonlinear_solver = NewtonSolver()
        sub.nonlinear_solver.options['maxiter'] = 1

        prob = Problem(model=group)
        prob.set_solver_print(level=0)
        prob.setup(check=False)

        prob.run_model()
        assert_rel_error(self, prob['sub.comp2.y'], 77., 1e-5)
Example #15
0
    def test_guess_nonlinear(self):

        class ImpWithInitial(QuadraticLinearize):

            def solve_nonlinear(self, inputs, outputs):
                """ Do nothing. """
                pass

            def guess_nonlinear(self, inputs, outputs, resids):
                # Solution at x=1 and x=3. Default value takes us to the x=1 solution. Here
                # we set it to a value that will take us to the x=3 solution.
                outputs['x'] = 5.0


        group = Group()

        group.add_subsystem('pa', IndepVarComp('a', 1.0))
        group.add_subsystem('pb', IndepVarComp('b', 1.0))
        group.add_subsystem('pc', IndepVarComp('c', 1.0))
        group.add_subsystem('comp2', ImpWithInitial())
        group.connect('pa.a', 'comp2.a')
        group.connect('pb.b', 'comp2.b')
        group.connect('pc.c', 'comp2.c')

        prob = Problem(model=group)
        group.nonlinear_solver = NewtonSolver()
        group.nonlinear_solver.options['solve_subsystems'] = True
        group.nonlinear_solver.options['max_sub_solves'] = 1
        group.linear_solver = ScipyIterativeSolver()

        prob.setup(check=False)

        prob['pa.a'] = 1.
        prob['pb.b'] = -4.
        prob['pc.c'] = 3.

        # Making sure that guess_nonlinear is called early enough to eradicate this.
        prob['comp2.x'] = np.NaN

        prob.run_model()
        assert_rel_error(self, prob['comp2.x'], 3.)
Example #16
0
    def test_scalar_with_guess_func(self):

        n = 1

        model=Group(assembled_jac_type='dense')

        def guess_function(inputs, outputs, residuals):
            outputs['x'] = np.sqrt(inputs['rhs:x'])

        bal = BalanceComp('x', guess_func=guess_function)  # test guess_func as kwarg

        tgt = IndepVarComp(name='y_tgt', val=4)

        exec_comp = ExecComp('y=x**2', x={'value': 1}, y={'value': 1})

        model.add_subsystem(name='target', subsys=tgt, promotes_outputs=['y_tgt'])
        model.add_subsystem(name='exec', subsys=exec_comp)
        model.add_subsystem(name='balance', subsys=bal)

        model.connect('y_tgt', 'balance.rhs:x')
        model.connect('balance.x', 'exec.x')
        model.connect('exec.y', 'balance.lhs:x')

        model.linear_solver = DirectSolver(assemble_jac=True)
        model.nonlinear_solver = NewtonSolver(maxiter=100, iprint=0)

        prob = Problem(model)
        prob.setup()

        prob['balance.x'] = np.random.rand(n)
        prob.run_model()

        assert_almost_equal(prob['balance.x'], 2.0, decimal=7)

        # should converge with no iteration due to the guess function
        self.assertEqual(model.nonlinear_solver._iter_count, 1)

        cpd = prob.check_partials(out_stream=None)
        for (of, wrt) in cpd['balance']:
            assert_almost_equal(cpd['balance'][of, wrt]['abs error'], 0.0, decimal=5)
Example #17
0
    def test_guess_nonlinear(self):

        class ImpWithInitial(QuadraticLinearize):

            def solve_nonlinear(self, inputs, outputs):
                """ Do nothing. """
                pass

            def guess_nonlinear(self, inputs, outputs, resids):
                # Solution at x=1 and x=3. Default value takes us to the x=1 solution. Here
                # we set it to a value that will take us to the x=3 solution.
                outputs['x'] = 5.0

        group = Group()

        group.add_subsystem('pa', IndepVarComp('a', 1.0))
        group.add_subsystem('pb', IndepVarComp('b', 1.0))
        group.add_subsystem('pc', IndepVarComp('c', 1.0))
        group.add_subsystem('comp2', ImpWithInitial())
        group.connect('pa.a', 'comp2.a')
        group.connect('pb.b', 'comp2.b')
        group.connect('pc.c', 'comp2.c')

        prob = Problem(model=group)
        group.nonlinear_solver = NewtonSolver()
        group.nonlinear_solver.options['solve_subsystems'] = True
        group.nonlinear_solver.options['max_sub_solves'] = 1
        group.linear_solver = ScipyKrylov()

        prob.setup(check=False)

        prob['pa.a'] = 1.
        prob['pb.b'] = -4.
        prob['pc.c'] = 3.

        # Making sure that guess_nonlinear is called early enough to eradicate this.
        prob['comp2.x'] = np.NaN

        prob.run_model()
        assert_rel_error(self, prob['comp2.x'], 3.)
Example #18
0
    def setup(self):
        surfaces = self.options['surfaces']

        coupled = Group()

        for surface in surfaces:

            name = surface['name']

            # Connect the output of the loads component with the FEM
            # displacement parameter. This links the coupling within the coupled
            # group that necessitates the subgroup solver.
            coupled.connect(name + '_loads.loads', name + '.loads')

            # Perform the connections with the modified names within the
            # 'aero_states' group.
            coupled.connect(name + '.normals', 'aero_states.' + name + '_normals')
            coupled.connect(name + '.def_mesh', 'aero_states.' + name + '_def_mesh')

            # Connect the results from 'coupled' to the performance groups
            coupled.connect(name + '.def_mesh', name + '_loads.def_mesh')
            coupled.connect('aero_states.' + name + '_sec_forces', name + '_loads.sec_forces')

            # Connect the results from 'aero_states' to the performance groups
            self.connect('coupled.aero_states.' + name + '_sec_forces', name + '_perf' + '.sec_forces')

            # Connection performance functional variables
            self.connect(name + '_perf.CL', 'total_perf.' + name + '_CL')
            self.connect(name + '_perf.CD', 'total_perf.' + name + '_CD')
            self.connect('coupled.aero_states.' + name + '_sec_forces', 'total_perf.' + name + '_sec_forces')
            self.connect('coupled.' + name + '.chords', name + '_perf.aero_funcs.chords')

            # Connect parameters from the 'coupled' group to the performance
            # groups for the individual surfaces.
            self.connect('coupled.' + name + '.disp', name + '_perf.disp')
            self.connect('coupled.' + name + '.S_ref', name + '_perf.S_ref')
            self.connect('coupled.' + name + '.widths', name + '_perf.widths')
            # self.connect('coupled.' + name + '.chords', name + '_perf.chords')
            self.connect('coupled.' + name + '.lengths', name + '_perf.lengths')
            self.connect('coupled.' + name + '.cos_sweep', name + '_perf.cos_sweep')

            # Connect parameters from the 'coupled' group to the total performance group.
            self.connect('coupled.' + name + '.S_ref', 'total_perf.' + name + '_S_ref')
            self.connect('coupled.' + name + '.widths', 'total_perf.' + name + '_widths')
            self.connect('coupled.' + name + '.chords', 'total_perf.' + name + '_chords')
            self.connect('coupled.' + name + '.b_pts', 'total_perf.' + name + '_b_pts')

            # Add components to the 'coupled' group for each surface.
            # The 'coupled' group must contain all components and parameters
            # needed to converge the aerostructural system.
            coupled_AS_group = CoupledAS(surface=surface)

            if surface['distributed_fuel_weight']:
                promotes = ['load_factor']
            else:
                promotes = []

            coupled.add_subsystem(name, coupled_AS_group, promotes_inputs=promotes)

        # Add a single 'aero_states' component for the whole system within the
        # coupled group.
        coupled.add_subsystem('aero_states',
            VLMStates(surfaces=surfaces),
            promotes_inputs=['v', 'alpha', 'rho'])

        # Explicitly connect parameters from each surface's group and the common
        # 'aero_states' group.
        for surface in surfaces:
            name = surface['name']

            # Add a loads component to the coupled group
            coupled.add_subsystem(name + '_loads', LoadTransfer(surface=surface))

        """
        ### Change the solver settings here ###
        """

        # Set solver properties for the coupled group
        # coupled.linear_solver = ScipyIterativeSolver()
        # coupled.linear_solver.precon = LinearRunOnce()

        coupled.nonlinear_solver = NonlinearBlockGS(use_aitken=True)
        coupled.nonlinear_solver.options['maxiter'] = 100
        coupled.nonlinear_solver.options['atol'] = 1e-7
        coupled.nonlinear_solver.options['rtol'] = 1e-30

        # coupled.linear_solver = DirectSolver()

        coupled.linear_solver = DirectSolver(assemble_jac=True)
        coupled.options['assembled_jac_type'] = 'csc'

        # coupled.nonlinear_solver = NewtonSolver(solve_subsystems=True)
        # coupled.nonlinear_solver.options['maxiter'] = 50
        coupled.nonlinear_solver.options['iprint'] = 2

        """
        ### End change of solver settings ###
        """

        # Add the coupled group to the model problem
        self.add_subsystem('coupled', coupled, promotes_inputs=['v', 'alpha', 'rho'])

        for surface in surfaces:
            name = surface['name']

            # Add a performance group which evaluates the data after solving
            # the coupled system
            perf_group = CoupledPerformance(surface=surface)

            self.add_subsystem(name + '_perf', perf_group, promotes_inputs=['rho', 'v', 'alpha', 're', 'Mach_number'])

        # Add functionals to evaluate performance of the system.
        # Note that only the interesting results are promoted here; not all
        # of the parameters.
        self.add_subsystem('total_perf',
                 TotalPerformance(surfaces=surfaces,
                 user_specified_Sref=self.options['user_specified_Sref'],
                 internally_connect_fuelburn=self.options['internally_connect_fuelburn']),
                 promotes_inputs=['v', 'rho', 'empty_cg', 'total_weight', 'CT', 'speed_of_sound', 'R', 'Mach_number', 'W0', 'load_factor', 'S_ref_total'],
                 promotes_outputs=['L_equals_W', 'fuelburn', 'CL', 'CD', 'CM', 'cg'])
Example #19
0
    def test_scalar_guess_func_using_outputs(self):

        model = Group()

        ind = IndepVarComp()
        ind.add_output('a', 1)
        ind.add_output('b', -4)
        ind.add_output('c', 3)

        lhs = ExecComp('lhs=-(a*x**2+b*x)')
        bal = BalanceComp(name='x', rhs_name='c')

        model.add_subsystem('ind_comp', ind, promotes_outputs=['a', 'b', 'c'])
        model.add_subsystem('lhs_comp', lhs, promotes_inputs=['a', 'b', 'x'])
        model.add_subsystem('bal_comp',
                            bal,
                            promotes_inputs=['c'],
                            promotes_outputs=['x'])

        model.connect('lhs_comp.lhs', 'bal_comp.lhs:x')

        model.linear_solver = DirectSolver()
        model.nonlinear_solver = NewtonSolver(maxiter=100, iprint=0)

        # first verify behavior of the balance comp without the guess function
        # at initial conditions x=5, x=0 and x=-1
        prob = Problem(model)
        prob.setup()

        # default solution with initial value of 5 is x=3.
        prob['x'] = 5
        prob.run_model()
        assert_almost_equal(prob['x'], 3.0, decimal=7)

        # default solution with initial value of 0 is x=1.
        prob['x'] = 0
        prob.run_model()
        assert_almost_equal(prob['x'], 1.0, decimal=7)

        # default solution with initial value of -1 is x=1.
        prob['x'] = -1
        prob.run_model()
        assert_almost_equal(prob['x'], 1.0, decimal=7)

        # now use a guess function that steers us to the x=3 solution only
        # if the initial value of x is less than zero
        def guess_function(inputs, outputs, residuals):
            if outputs['x'] < 0:
                outputs['x'] = 3.

        bal.options['guess_func'] = guess_function

        # solution with initial value of 5 is still x=3.
        prob['x'] = 5
        prob.run_model()
        assert_almost_equal(prob['x'], 3.0, decimal=7)

        # solution with initial value of 0 is still x=1.
        prob['x'] = 0
        prob.run_model()
        assert_almost_equal(prob['x'], 1.0, decimal=7)

        # solution with initial value of -1 is now x=3.
        prob['x'] = -1
        prob.run_model()
        assert_almost_equal(prob['x'], 3.0, decimal=7)
    def test_simple_external_code_implicit_comp(self):
        from openmdao.api import Group, NewtonSolver, Problem, IndepVarComp, DirectSolver, \
            ExternalCodeImplicitComp

        class MachExternalCodeComp(ExternalCodeImplicitComp):

            def initialize(self):
                self.options.declare('super_sonic', types=bool)

            def setup(self):
                self.add_input('area_ratio', val=1.0, units=None)
                self.add_output('mach', val=1., units=None)
                self.declare_partials(of='mach', wrt='area_ratio', method='fd')

                self.input_file = 'mach_input.dat'
                self.output_file = 'mach_output.dat'

                # providing these are optional; the component will verify that any input
                # files exist before execution and that the output files exist after.
                self.options['external_input_files'] = [self.input_file]
                self.options['external_output_files'] = [self.output_file]

                self.options['command_apply'] = [
                    'python', 'extcode_mach.py', self.input_file, self.output_file,
                ]
                self.options['command_solve'] = [
                    'python', 'extcode_mach.py', self.input_file, self.output_file,
                ]

            def apply_nonlinear(self, inputs, outputs, residuals):
                with open(self.input_file, 'w') as input_file:
                    input_file.write('residuals\n')
                    input_file.write('{}\n'.format(inputs['area_ratio'][0]))
                    input_file.write('{}\n'.format(outputs['mach'][0]))

                # the parent apply_nonlinear function actually runs the external code
                super(MachExternalCodeComp, self).apply_nonlinear(inputs, outputs, residuals)

                # parse the output file from the external code and set the value of mach
                with open(self.output_file, 'r') as output_file:
                    mach = float(output_file.read())
                residuals['mach'] = mach

            def solve_nonlinear(self, inputs, outputs):
                with open(self.input_file, 'w') as input_file:
                    input_file.write('outputs\n')
                    input_file.write('{}\n'.format(inputs['area_ratio'][0]))
                    input_file.write('{}\n'.format(self.options['super_sonic']))
                # the parent apply_nonlinear function actually runs the external code
                super(MachExternalCodeComp, self).solve_nonlinear(inputs, outputs)

                # parse the output file from the external code and set the value of mach
                with open(self.output_file, 'r') as output_file:
                    mach = float(output_file.read())
                outputs['mach'] = mach

        group = Group()
        group.add_subsystem('ar', IndepVarComp('area_ratio', 0.5))
        mach_comp = group.add_subsystem('comp', MachExternalCodeComp(), promotes=['*'])
        prob = Problem(model=group)
        group.nonlinear_solver = NewtonSolver()
        group.nonlinear_solver.options['solve_subsystems'] = True
        group.nonlinear_solver.options['iprint'] = 0
        group.nonlinear_solver.options['maxiter'] = 20
        group.linear_solver = DirectSolver()

        prob.setup(check=False)

        area_ratio = 1.3
        super_sonic = False
        prob['area_ratio'] = area_ratio
        mach_comp.options['super_sonic'] = super_sonic
        prob.run_model()
        assert_rel_error(self, prob['mach'], mach_solve(area_ratio, super_sonic=super_sonic), 1e-8)

        area_ratio = 1.3
        super_sonic = True
        prob['area_ratio'] = area_ratio
        mach_comp.options['super_sonic'] = super_sonic
        prob.run_model()
        assert_rel_error(self, prob['mach'], mach_solve(area_ratio, super_sonic=super_sonic), 1e-8)
Example #21
0
    def setup(self):
        super(ImplicitTMIntegrator, self).setup()

        ode_function = self.options['ode_function']
        method = self.options['method']
        starting_coeffs = self.options['starting_coeffs']

        has_starting_method = method.starting_method is not None
        is_starting_method = starting_coeffs is not None

        states = ode_function._states
        static_parameters = ode_function._static_parameters
        dynamic_parameters = ode_function._dynamic_parameters
        time_units = ode_function._time_options['units']

        starting_norm_times, my_norm_times = self._get_meta()

        glm_A, glm_B, glm_U, glm_V, num_stages, num_step_vars = self._get_method()

        num_times = len(my_norm_times)
        num_stages = method.num_stages
        num_step_vars = method.num_values

        glm_A = method.A
        glm_B = method.B
        glm_U = method.U
        glm_V = method.V

        # ------------------------------------------------------------------------------------

        integration_group = Group()
        self.add_subsystem('integration_group', integration_group)

        for i_step in range(len(my_norm_times) - 1):
            group = Group(assembled_jac_type='dense')
            group_old_name = 'integration_group.step_%i' % (i_step - 1)
            group_new_name = 'integration_group.step_%i' % i_step
            integration_group.add_subsystem(group_new_name.split('.')[1], group)

            comp = self._create_ode(num_stages)
            group.add_subsystem('ode_comp', comp)
            if ode_function._time_options['targets']:
                self.connect('time_comp.stage_times',
                    ['.'.join((group_new_name + '.ode_comp', t)) for t in
                    ode_function._time_options['targets']],
                    src_indices=i_step * (num_stages) + np.arange(num_stages))

            if len(static_parameters) > 0:
                self._connect_multiple(
                    self._get_static_parameter_names('static_parameter_comp', 'out'),
                    self._get_static_parameter_names(group_new_name + '.ode_comp', 'targets'),
                    src_indices_list=[[0] * num_stages for _ in range(len(static_parameters))]
                )
            if len(dynamic_parameters) > 0:
                src_indices_list = []
                for parameter_name, value in iteritems(dynamic_parameters):
                    size = np.prod(value['shape'])
                    shape = value['shape']

                    arange = np.arange(((len(my_norm_times) - 1) * num_stages * size)).reshape(
                        ((len(my_norm_times) - 1, num_stages,) + shape))
                    src_indices = arange[i_step, :, :]
                    src_indices_list.append(src_indices.flat)
                self._connect_multiple(
                    self._get_dynamic_parameter_names('dynamic_parameter_comp', 'out'),
                    self._get_dynamic_parameter_names(group_new_name + '.ode_comp', 'targets'),
                    src_indices_list,
                )

            comp = ImplicitTMStageComp(
                states=states, time_units=time_units,
                num_stages=num_stages, num_step_vars=num_step_vars,
                glm_A=glm_A, glm_U=glm_U, i_step=i_step,
            )
            group.add_subsystem('stage_comp', comp)
            self.connect('time_comp.h_vec', group_new_name + '.stage_comp.h', src_indices=i_step)

            comp = ImplicitTMStepComp(
                states=states, time_units=time_units,
                num_stages=num_stages, num_step_vars=num_step_vars,
                glm_B=glm_B, glm_V=glm_V, i_step=i_step,
            )
            group.add_subsystem('step_comp', comp)
            self.connect('time_comp.h_vec', group_new_name + '.step_comp.h', src_indices=i_step)

            self._connect_multiple(
                self._get_state_names(group_new_name + '.ode_comp', 'rate_source'),
                self._get_state_names(group_new_name + '.step_comp', 'F', i_step=i_step),
            )

            self._connect_multiple(
                self._get_state_names(group_new_name + '.ode_comp', 'rate_source'),
                self._get_state_names(group_new_name + '.stage_comp', 'F', i_step=i_step),
            )

            self._connect_multiple(
                self._get_state_names(group_new_name + '.stage_comp', 'Y', i_step=i_step),
                self._get_state_names(group_new_name + '.ode_comp', 'targets'),
            )

            if i_step == 0:
                self._connect_multiple(
                    self._get_state_names('starting_system', 'starting'),
                    self._get_state_names(group_new_name + '.step_comp', 'y_old', i_step=i_step),
                )
                self._connect_multiple(
                    self._get_state_names('starting_system', 'starting'),
                    self._get_state_names(group_new_name + '.stage_comp', 'y_old', i_step=i_step),
                )
            else:
                self._connect_multiple(
                    self._get_state_names(group_old_name + '.step_comp', 'y_new', i_step=i_step - 1),
                    self._get_state_names(group_new_name + '.step_comp', 'y_old', i_step=i_step),
                )
                self._connect_multiple(
                    self._get_state_names(group_old_name + '.step_comp', 'y_new', i_step=i_step - 1),
                    self._get_state_names(group_new_name + '.stage_comp', 'y_old', i_step=i_step),
                )

            group.nonlinear_solver = NewtonSolver(iprint=2, maxiter=100)
            group.linear_solver = DirectSolver(assemble_jac=True)

        promotes = []
        promotes.extend([get_name('state', state_name) for state_name in states])
        if is_starting_method:
            promotes.extend([get_name('starting', state_name) for state_name in states])

        comp = TMOutputComp(
            states=states, num_starting_times=len(starting_norm_times),
            num_my_times=len(my_norm_times), num_step_vars=num_step_vars,
            starting_coeffs=starting_coeffs)
        self.add_subsystem('output_comp', comp, promotes_outputs=promotes)
        if has_starting_method:
            self._connect_multiple(
                self._get_state_names('starting_system', 'state'),
                self._get_state_names('output_comp', 'starting_state'),
            )

        for i_step in range(len(my_norm_times)):
            if i_step == 0:
                self._connect_multiple(
                    self._get_state_names('starting_system', 'starting'),
                    self._get_state_names('output_comp', 'y', i_step=i_step),
                )
            else:
                self._connect_multiple(
                    self._get_state_names('integration_group.step_%i' % (i_step - 1) + '.step_comp', 'y_new', i_step=i_step - 1),
                    self._get_state_names('output_comp', 'y', i_step=i_step),
                )
Example #22
0
    def test_scalar_guess_func_using_outputs(self):

        model = Group()

        ind = IndepVarComp()
        ind.add_output('a', 1)
        ind.add_output('b', -4)
        ind.add_output('c', 3)

        lhs = ExecComp('lhs=-(a*x**2+b*x)')
        bal = BalanceComp(name='x', rhs_name='c')

        model.add_subsystem('ind_comp', ind, promotes_outputs=['a', 'b', 'c'])
        model.add_subsystem('lhs_comp', lhs, promotes_inputs=['a', 'b', 'x'])
        model.add_subsystem('bal_comp', bal, promotes_inputs=['c'], promotes_outputs=['x'])

        model.connect('lhs_comp.lhs', 'bal_comp.lhs:x')

        model.linear_solver = DirectSolver()
        model.nonlinear_solver = NewtonSolver(maxiter=100, iprint=0)

        # first verify behavior of the balance comp without the guess function
        # at initial conditions x=5, x=0 and x=-1
        prob = Problem(model)
        prob.setup()

        # default solution with initial value of 5 is x=3.
        prob['x'] = 5
        prob.run_model()
        assert_almost_equal(prob['x'], 3.0, decimal=7)

        # default solution with initial value of 0 is x=1.
        prob['x'] = 0
        prob.run_model()
        assert_almost_equal(prob['x'], 1.0, decimal=7)

        # default solution with initial value of -1 is x=1.
        prob['x'] = -1
        prob.run_model()
        assert_almost_equal(prob['x'], 1.0, decimal=7)

        # now use a guess function that steers us to the x=3 solution only
        # if the initial value of x is less than zero
        def guess_function(inputs, outputs, residuals):
            if outputs['x'] < 0:
                outputs['x'] = 3.

        bal.options['guess_func'] = guess_function

        # solution with initial value of 5 is still x=3.
        prob['x'] = 5
        prob.run_model()
        assert_almost_equal(prob['x'], 3.0, decimal=7)

        # solution with initial value of 0 is still x=1.
        prob['x'] = 0
        prob.run_model()
        assert_almost_equal(prob['x'], 1.0, decimal=7)

        # solution with initial value of -1 is now x=3.
        prob['x'] = -1
        prob.run_model()
        assert_almost_equal(prob['x'], 3.0, decimal=7)
Example #23
0
    def setup(self):
        num_times = self.options['num_times']
        num_cp = self.options['num_cp']
        step_size = self.options['step_size']
        cubesat = self.options['cubesat']
        mtx = self.options['mtx']

        shape = (3, num_times)

        drag_unit_vec = np.outer(
            np.array([0., 0., 1.]),
            np.ones(num_times),
        )

        comp = IndepVarComp()
        comp.add_output('drag_unit_vec_t_3xn', val=drag_unit_vec)
        comp.add_output('dry_mass', val=cubesat['dry_mass'], shape=num_times)
        comp.add_output('radius_earth_km',
                        val=cubesat['radius_earth_km'],
                        shape=num_times)
        for var_name in ['initial_orbit_state']:
            comp.add_output(var_name, val=cubesat[var_name])
        self.add_subsystem('input_comp', comp, promotes=['*'])

        # comp = InitialOrbitComp()
        # self.add_subsystem('initial_orbit_comp', comp, promotes=['*'])

        comp = LinearCombinationComp(
            shape=(num_times, ),
            out_name='mass',
            coeffs_dict=dict(dry_mass=1., propellant_mass=1.),
        )
        self.add_subsystem('mass_comp', comp, promotes=['*'])

        if 1:

            coupled_group = Group()

            comp = LinearCombinationComp(
                shape=shape,
                out_name='force_3xn',
                coeffs_dict=dict(thrust_3xn=1., drag_3xn=1.),
            )
            coupled_group.add_subsystem('force_3xn_comp', comp, promotes=['*'])

            comp = RelativeOrbitRK4Comp(
                num_times=num_times,
                step_size=step_size,
            )
            coupled_group.add_subsystem('relative_orbit_rk4_comp',
                                        comp,
                                        promotes=['*'])

            comp = LinearCombinationComp(
                shape=(6, num_times),
                out_name='orbit_state',
                coeffs_dict=dict(
                    relative_orbit_state=1.,
                    reference_orbit_state=1.,
                ),
            )
            coupled_group.add_subsystem('orbit_state_comp',
                                        comp,
                                        promotes=['*'])

            comp = LinearCombinationComp(
                shape=(6, num_times),
                out_name='orbit_state_km',
                coeffs_dict=dict(orbit_state=1.e-3),
            )
            coupled_group.add_subsystem('orbit_state_km_comp',
                                        comp,
                                        promotes=['*'])

            comp = RotMtxTIComp(num_times=num_times)
            coupled_group.add_subsystem('rot_mtx_t_i_3x3xn_comp',
                                        comp,
                                        promotes=['*'])

            comp = ArrayReorderComp(
                in_shape=(3, 3, num_times),
                out_shape=(3, 3, num_times),
                in_subscripts='ijn',
                out_subscripts='jin',
                in_name='rot_mtx_t_i_3x3xn',
                out_name='rot_mtx_i_t_3x3xn',
            )
            coupled_group.add_subsystem('rot_mtx_i_t_3x3xn_comp',
                                        comp,
                                        promotes=['*'])

            comp = MtxVecComp(
                num_times=num_times,
                mtx_name='rot_mtx_i_t_3x3xn',
                vec_name='drag_unit_vec_t_3xn',
                out_name='drag_unit_vec_3xn',
            )
            coupled_group.add_subsystem('drag_unit_vec_3xn_comp',
                                        comp,
                                        promotes=['*'])

            comp = PowerCombinationComp(shape=shape,
                                        out_name='drag_3xn',
                                        powers_dict=dict(
                                            drag_unit_vec_3xn=1.,
                                            drag_scalar_3xn=1.,
                                        ))
            coupled_group.add_subsystem('drag_3xn_comp', comp, promotes=['*'])

            coupled_group.nonlinear_solver = NonlinearBlockGS(iprint=0,
                                                              maxiter=40,
                                                              atol=1e-14,
                                                              rtol=1e-12)
            coupled_group.linear_solver = LinearBlockGS(iprint=0,
                                                        maxiter=40,
                                                        atol=1e-14,
                                                        rtol=1e-12)

            self.add_subsystem('coupled_group', coupled_group, promotes=['*'])

        comp = OrbitStateDecompositionComp(
            num_times=num_times,
            position_name='position_km',
            velocity_name='velocity_km_s',
            orbit_state_name='orbit_state_km',
        )
        self.add_subsystem('orbit_state_decomposition_comp',
                           comp,
                           promotes=['*'])

        comp = LinearCombinationComp(
            shape=shape,
            out_name='position',
            coeffs_dict=dict(position_km=1.e3),
        )
        self.add_subsystem('position_comp', comp, promotes=['*'])

        comp = LinearCombinationComp(
            shape=shape,
            out_name='velocity',
            coeffs_dict=dict(velocity_km_s=1.e3),
        )
        self.add_subsystem('velocity_comp', comp, promotes=['*'])

        #

        group = DecomposeVectorGroup(
            num_times=num_times,
            vec_name='position_km',
            norm_name='radius_km',
            unit_vec_name='position_unit_vec',
        )
        self.add_subsystem('position_decomposition_group',
                           group,
                           promotes=['*'])

        group = DecomposeVectorGroup(
            num_times=num_times,
            vec_name='velocity_km_s',
            norm_name='speed_km_s',
            unit_vec_name='velocity_unit_vec',
        )
        self.add_subsystem('velocity_decomposition_group',
                           group,
                           promotes=['*'])

        #

        comp = LinearCombinationComp(
            shape=(num_times, ),
            out_name='altitude_km',
            coeffs_dict=dict(radius_km=1., radius_earth_km=-1.),
        )
        self.add_subsystem('altitude_km_comp', comp, promotes=['*'])

        comp = KSComp(
            in_name='altitude_km',
            out_name='ks_altitude_km',
            shape=(1, ),
            constraint_size=num_times,
            rho=100.,
            lower_flag=True,
        )
        comp.add_constraint('ks_altitude_km', lower=450.)
        self.add_subsystem('ks_altitude_km_comp', comp, promotes=['*'])

        comp = PowerCombinationComp(shape=(
            6,
            num_times,
        ),
                                    out_name='relative_orbit_state_sq',
                                    powers_dict={
                                        'relative_orbit_state': 2.,
                                    })
        self.add_subsystem('relative_orbit_state_sq_comp',
                           comp,
                           promotes=['*'])

        comp = ScalarContractionComp(
            shape=(
                6,
                num_times,
            ),
            out_name='relative_orbit_state_sq_sum',
            in_name='relative_orbit_state_sq',
        )
        self.add_subsystem('relative_orbit_state_sq_sum_comp',
                           comp,
                           promotes=['*'])
    def test_simple_external_code_implicit_comp(self):
        from openmdao.api import Group, NewtonSolver, Problem, IndepVarComp, DirectSolver, \
            ExternalCodeImplicitComp

        class MachExternalCodeComp(ExternalCodeImplicitComp):
            def initialize(self):
                self.options.declare('super_sonic', types=bool)

            def setup(self):
                self.add_input('area_ratio', val=1.0, units=None)
                self.add_output('mach', val=1., units=None)
                self.declare_partials(of='mach', wrt='area_ratio', method='fd')

                self.input_file = 'mach_input.dat'
                self.output_file = 'mach_output.dat'

                # providing these are optional; the component will verify that any input
                # files exist before execution and that the output files exist after.
                self.options['external_input_files'] = [self.input_file]
                self.options['external_output_files'] = [self.output_file]

                self.options['command_apply'] = [
                    'python',
                    'extcode_mach.py',
                    self.input_file,
                    self.output_file,
                ]
                self.options['command_solve'] = [
                    'python',
                    'extcode_mach.py',
                    self.input_file,
                    self.output_file,
                ]

            def apply_nonlinear(self, inputs, outputs, residuals):
                with open(self.input_file, 'w') as input_file:
                    input_file.write('residuals\n')
                    input_file.write('{}\n'.format(inputs['area_ratio'][0]))
                    input_file.write('{}\n'.format(outputs['mach'][0]))

                # the parent apply_nonlinear function actually runs the external code
                super(MachExternalCodeComp,
                      self).apply_nonlinear(inputs, outputs, residuals)

                # parse the output file from the external code and set the value of mach
                with open(self.output_file, 'r') as output_file:
                    mach = float(output_file.read())
                residuals['mach'] = mach

            def solve_nonlinear(self, inputs, outputs):
                with open(self.input_file, 'w') as input_file:
                    input_file.write('outputs\n')
                    input_file.write('{}\n'.format(inputs['area_ratio'][0]))
                    input_file.write('{}\n'.format(
                        self.options['super_sonic']))
                # the parent apply_nonlinear function actually runs the external code
                super(MachExternalCodeComp,
                      self).solve_nonlinear(inputs, outputs)

                # parse the output file from the external code and set the value of mach
                with open(self.output_file, 'r') as output_file:
                    mach = float(output_file.read())
                outputs['mach'] = mach

        group = Group()
        group.add_subsystem('ar', IndepVarComp('area_ratio', 0.5))
        mach_comp = group.add_subsystem('comp',
                                        MachExternalCodeComp(),
                                        promotes=['*'])
        prob = Problem(model=group)
        group.nonlinear_solver = NewtonSolver()
        group.nonlinear_solver.options['solve_subsystems'] = True
        group.nonlinear_solver.options['iprint'] = 0
        group.nonlinear_solver.options['maxiter'] = 20
        group.linear_solver = DirectSolver()

        prob.setup(check=False)

        area_ratio = 1.3
        super_sonic = False
        prob['area_ratio'] = area_ratio
        mach_comp.options['super_sonic'] = super_sonic
        prob.run_model()
        assert_rel_error(self, prob['mach'],
                         mach_solve(area_ratio, super_sonic=super_sonic), 1e-8)

        area_ratio = 1.3
        super_sonic = True
        prob['area_ratio'] = area_ratio
        mach_comp.options['super_sonic'] = super_sonic
        prob.run_model()
        assert_rel_error(self, prob['mach'],
                         mach_solve(area_ratio, super_sonic=super_sonic), 1e-8)
Example #25
0
    def setup(self):
        super(VectorizedIntegrator, self).setup()

        ode_function = self.options['ode_function']
        method = self.options['method']
        starting_coeffs = self.options['starting_coeffs']
        formulation = self.options['formulation']

        has_starting_method = method.starting_method is not None
        is_starting_method = starting_coeffs is not None

        states = ode_function._states
        static_parameters = ode_function._static_parameters
        dynamic_parameters = ode_function._dynamic_parameters
        time_units = ode_function._time_options['units']

        starting_norm_times, my_norm_times = self._get_meta()

        glm_A, glm_B, glm_U, glm_V, num_stages, num_step_vars = self._get_method(
        )

        num_times = len(my_norm_times)

        # ------------------------------------------------------------------------------------

        integration_group = Group(assembled_jac_type='dense')
        self.add_subsystem('integration_group', integration_group)

        if formulation == 'optimizer-based':
            comp = IndepVarComp()
            for state_name, state in iteritems(states):
                comp.add_output('Y:%s' % state_name,
                                shape=(
                                    num_times - 1,
                                    num_stages,
                                ) + state['shape'],
                                units=state['units'])
                comp.add_design_var('Y:%s' % state_name)
            integration_group.add_subsystem('desvars_comp', comp)
        elif formulation == 'solver-based':
            comp = IndepVarComp()
            for state_name, state in iteritems(states):
                comp.add_output('Y:%s' % state_name,
                                val=0.,
                                shape=(
                                    num_times - 1,
                                    num_stages,
                                ) + state['shape'],
                                units=state['units'])
            integration_group.add_subsystem('dummy_comp', comp)

        comp = self._create_ode((num_times - 1) * num_stages)
        integration_group.add_subsystem('ode_comp', comp)
        if ode_function._time_options['targets']:
            self.connect(
                'time_comp.stage_times',
                [
                    '.'.join(('integration_group.ode_comp', t))
                    for t in ode_function._time_options['targets']
                ],
            )
        if len(static_parameters) > 0:
            self._connect_multiple(
                self._get_static_parameter_names('static_parameter_comp',
                                                 'out'),
                self._get_static_parameter_names('integration_group.ode_comp',
                                                 'targets'),
                [
                    np.array([0] * self._get_stage_norm_times(), np.int)
                    for _ in range(len(static_parameters))
                ])
        if len(dynamic_parameters) > 0:
            self._connect_multiple(
                self._get_dynamic_parameter_names('dynamic_parameter_comp',
                                                  'out'),
                self._get_dynamic_parameter_names('integration_group.ode_comp',
                                                  'targets'),
            )

        comp = VectorizedStageStepComp(
            states=states,
            time_units=time_units,
            num_times=num_times,
            num_stages=num_stages,
            num_step_vars=num_step_vars,
            glm_A=glm_A,
            glm_U=glm_U,
            glm_B=glm_B,
            glm_V=glm_V,
        )
        integration_group.add_subsystem('vectorized_stagestep_comp', comp)
        self.connect('time_comp.h_vec',
                     'integration_group.vectorized_stagestep_comp.h_vec')

        comp = VectorizedStepComp(
            states=states,
            time_units=time_units,
            num_times=num_times,
            num_stages=num_stages,
            num_step_vars=num_step_vars,
            glm_B=glm_B,
            glm_V=glm_V,
        )
        self.add_subsystem('vectorized_step_comp', comp)
        self.connect('time_comp.h_vec', 'vectorized_step_comp.h_vec')
        self._connect_multiple(
            self._get_state_names('starting_system', 'starting'),
            self._get_state_names(
                'integration_group.vectorized_stagestep_comp', 'y0'),
        )
        self._connect_multiple(
            self._get_state_names('starting_system', 'starting'),
            self._get_state_names('vectorized_step_comp', 'y0'),
        )

        comp = VectorizedOutputComp(
            states=states,
            num_starting_times=len(starting_norm_times),
            num_my_times=len(my_norm_times),
            num_step_vars=num_step_vars,
            starting_coeffs=starting_coeffs,
        )

        promotes = []
        promotes.extend(
            [get_name('state', state_name) for state_name in states])
        if is_starting_method:
            promotes.extend(
                [get_name('starting', state_name) for state_name in states])

        self.add_subsystem('output_comp', comp, promotes_outputs=promotes)
        if has_starting_method:
            self._connect_multiple(
                self._get_state_names('starting_system', 'state'),
                self._get_state_names('output_comp', 'starting_state'),
            )

        src_indices_to_ode = []
        src_indices_from_ode = []
        for state_name, state in iteritems(states):
            size = np.prod(state['shape'])
            shape = state['shape']

            src_indices_to_ode.append(
                np.arange(
                    (num_times - 1) * num_stages *
                    size).reshape(((num_times - 1) * num_stages, ) + shape))

            src_indices_from_ode.append(
                np.arange((num_times - 1) * num_stages * size).reshape((
                    num_times - 1,
                    num_stages,
                ) + shape))

        src_indices_to_ode = [
            np.array(idx).squeeze() for idx in src_indices_to_ode
        ]

        self._connect_multiple(
            self._get_state_names('vectorized_step_comp', 'y'),
            self._get_state_names('output_comp', 'y'),
        )

        self._connect_multiple(
            self._get_state_names('integration_group.ode_comp', 'rate_source'),
            self._get_state_names('vectorized_step_comp', 'F'),
            src_indices_from_ode,
        )
        self._connect_multiple(
            self._get_state_names('integration_group.ode_comp', 'rate_source'),
            self._get_state_names(
                'integration_group.vectorized_stagestep_comp', 'F'),
            src_indices_from_ode,
        )

        if formulation == 'solver-based':
            self._connect_multiple(
                self._get_state_names(
                    'integration_group.vectorized_stagestep_comp', 'Y_out'),
                self._get_state_names('integration_group.ode_comp', 'targets'),
                src_indices_to_ode,
            )
            self._connect_multiple(
                self._get_state_names('integration_group.dummy_comp', 'Y'),
                self._get_state_names(
                    'integration_group.vectorized_stagestep_comp', 'Y_in'),
            )
        elif formulation == 'optimizer-based':
            self._connect_multiple(
                self._get_state_names('integration_group.desvars_comp', 'Y'),
                self._get_state_names('integration_group.ode_comp', 'targets'),
                src_indices_to_ode,
            )
            self._connect_multiple(
                self._get_state_names('integration_group.desvars_comp', 'Y'),
                self._get_state_names(
                    'integration_group.vectorized_stagestep_comp', 'Y_in'),
            )
            for state_name, state in iteritems(states):
                integration_group.add_constraint(
                    'vectorized_stagestep_comp.Y_out:%s' % state_name,
                    equals=0.,
                    vectorize_derivs=True,
                )

        if has_starting_method:
            self.starting_system.options['formulation'] = self.options[
                'formulation']

        if formulation == 'solver-based':
            if 1:
                integration_group.nonlinear_solver = NonlinearBlockGS(
                    iprint=2, maxiter=40, atol=1e-14, rtol=1e-12)
            else:
                integration_group.nonlinear_solver = NewtonSolver(iprint=2,
                                                                  maxiter=100)

            if 1:
                integration_group.linear_solver = LinearBlockGS(iprint=1,
                                                                maxiter=40,
                                                                atol=1e-14,
                                                                rtol=1e-12)
            else:
                integration_group.linear_solver = DirectSolver(
                    assemble_jac=True, iprint=1)
    def setup(self):
        surfaces = self.options['surfaces']

        coupled = Group()

        for surface in surfaces:

            name = surface['name']

            # Connect the output of the loads component with the FEM
            # displacement parameter. This links the coupling within the coupled
            # group that necessitates the subgroup solver.
            coupled.connect(name + '_loads.loads', name + '.loads')

            # Perform the connections with the modified names within the
            # 'aero_states' group.
            coupled.connect(name + '.normals', 'aero_states.' + name + '_normals')
            coupled.connect(name + '.def_mesh', 'aero_states.' + name + '_def_mesh')

            # Connect the results from 'coupled' to the performance groups
            coupled.connect(name + '.def_mesh', name + '_loads.def_mesh')
            coupled.connect('aero_states.' + name + '_sec_forces', name + '_loads.sec_forces')

            # Connect the results from 'aero_states' to the performance groups
            self.connect('coupled.aero_states.' + name + '_sec_forces', name + '_perf' + '.sec_forces')

            # Connection performance functional variables
            self.connect(name + '_perf.CL', 'total_perf.' + name + '_CL')
            self.connect(name + '_perf.CD', 'total_perf.' + name + '_CD')
            self.connect('coupled.aero_states.' + name + '_sec_forces', 'total_perf.' + name + '_sec_forces')
            self.connect('coupled.' + name + '.chords', name + '_perf.aero_funcs.chords')

            # Connect parameters from the 'coupled' group to the performance
            # groups for the individual surfaces.
            self.connect('coupled.' + name + '.disp', name + '_perf.disp')
            self.connect('coupled.' + name + '.S_ref', name + '_perf.S_ref')
            self.connect('coupled.' + name + '.widths', name + '_perf.widths')
            # self.connect('coupled.' + name + '.chords', name + '_perf.chords')
            self.connect('coupled.' + name + '.lengths', name + '_perf.lengths')
            self.connect('coupled.' + name + '.cos_sweep', name + '_perf.cos_sweep')

            # Connect parameters from the 'coupled' group to the total performance group.
            self.connect('coupled.' + name + '.S_ref', 'total_perf.' + name + '_S_ref')
            self.connect('coupled.' + name + '.widths', 'total_perf.' + name + '_widths')
            self.connect('coupled.' + name + '.chords', 'total_perf.' + name + '_chords')
            self.connect('coupled.' + name + '.b_pts', 'total_perf.' + name + '_b_pts')

            # Add components to the 'coupled' group for each surface.
            # The 'coupled' group must contain all components and parameters
            # needed to converge the aerostructural system.
            coupled_AS_group = CoupledAS(surface=surface)

            if surface['distributed_fuel_weight']:
                promotes = ['load_factor']
            else:
                promotes = []

            coupled.add_subsystem(name, coupled_AS_group, promotes_inputs=promotes)

        # Add a single 'aero_states' component for the whole system within the
        # coupled group.
        coupled.add_subsystem('aero_states',
            VLMStates(surfaces=surfaces),
            promotes_inputs=['v', 'alpha', 'rho'])

        # Explicitly connect parameters from each surface's group and the common
        # 'aero_states' group.
        for surface in surfaces:
            name = surface['name']

            # Add a loads component to the coupled group
            coupled.add_subsystem(name + '_loads', LoadTransfer(surface=surface))

        """
        ### Change the solver settings here ###
        """

        # Set solver properties for the coupled group
        # coupled.linear_solver = ScipyIterativeSolver()
        # coupled.linear_solver.precon = LinearRunOnce()

        coupled.nonlinear_solver = NonlinearBlockGS(use_aitken=True)
        coupled.nonlinear_solver.options['maxiter'] = 50
        coupled.nonlinear_solver.options['atol'] = 5e-6
        coupled.nonlinear_solver.options['rtol'] = 1e-12

        # coupled.linear_solver = DirectSolver()

        coupled.linear_solver = DirectSolver(assemble_jac=True)
        coupled.options['assembled_jac_type'] = 'csc'

        # coupled.nonlinear_solver = NewtonSolver(solve_subsystems=True)
        # coupled.nonlinear_solver.options['maxiter'] = 50
        coupled.nonlinear_solver.options['iprint'] = 0

        """
        ### End change of solver settings ###
        """

        # Add the coupled group to the model problem
        self.add_subsystem('coupled', coupled, promotes_inputs=['v', 'alpha', 'rho'])

        for surface in surfaces:
            name = surface['name']

            # Add a performance group which evaluates the data after solving
            # the coupled system
            perf_group = CoupledPerformance(surface=surface)

            self.add_subsystem(name + '_perf', perf_group, promotes_inputs=["rho", "v", "alpha", "re", "M"])

        # Add functionals to evaluate performance of the system.
        # Note that only the interesting results are promoted here; not all
        # of the parameters.
        self.add_subsystem('total_perf',
                 TotalPerformance(surfaces=surfaces,
                 user_specified_Sref=self.options['user_specified_Sref'],
                 internally_connect_fuelburn=self.options['internally_connect_fuelburn']),
                 promotes_inputs=['v', 'rho', 'empty_cg', 'total_weight', 'CT', 'a', 'R', 'M', 'W0', 'load_factor', 'S_ref_total'],
                 promotes_outputs=['L_equals_W', 'fuelburn', 'CL', 'CD', 'CM', 'cg'])
Example #27
0
prob.model.connect("ivc.Cla_t", "Aerodynamics.cla_tail.Cla_t")
prob.model.connect("ivc.S_t", "Aerodynamics.cla_tail.S_t")
prob.model.connect("ivc.d_fuse_t", "Aerodynamics.cla_tail.d_fuse_t")
prob.model.connect("ivc.b_t", "Aerodynamics.cla_tail.b_t")
prob.model.connect("ivc.b", "Aerodynamics.wing_param.b")
prob.model.connect("ivc.taper", "Aerodynamics.wing_param.taper")
prob.model.connect("ivc.croot", "Aerodynamics.wing_param.croot")
prob.model.connect("ivc.W_0", "Weights.W_lg.W_0")
prob.model.connect("ivc.S_t", "Weights.W_tail.S_t")
prob.model.connect("ivc.S", "Weights.W_wing.S")
prob.model.connect("ivc.AR", "Weights.W_wing.AR")
prob.model.connect("ivc.q", "Weights.W_wing.q")
prob.model.connect("ivc.taper", "Weights.W_wing.taper")
prob.model.connect("ivc.thickness_ratio", "Weights.W_wing.thickness_ratio")
prob.model.connect("ivc.n", "Weights.W_wing.n")
prob.model.connect("ivc.W_0", "Weights.W_wing.W_0")
prob.model.connect("ivc.W_0", "Weights.W_wing_control.W_0")
# replace all "ivc.W_0" with "Weights.GrossWeightComp.W_0"

group.nonlinear_solver = NonlinearBlockGS(iprint=2, maxiter=30)

prob.setup()
prob.run_model()
#print(prob['cla_wing.CLa'])
#prob.check_partials(compact_print=True)

# TO RUN: 'python run.py'
# TO VISUALIZE: 'openmdao view_model run.py'

view_model(prob)
def main():

    num_nodes = 1
    num_blades = 10
    num_radial = 15
    num_cp = 6

    af_filename = 'airfoils/mh117.dat'
    chord = 20.
    theta = 5.0 * np.pi / 180.0
    pitch = 0.

    # Numbers taken from the Aviary group's study of the RVLT tiltwing
    # turboelectric concept vehicle.
    n_props = 4
    hub_diameter = 30.  # cm
    prop_diameter = 15 * 30.48  # 15 ft in cm
    c0 = np.sqrt(1.4 * 287.058 * 300.)  # meters/second
    rho0 = 1.4 * 98600. / (c0 * c0)  # kg/m^3
    omega = 236.  # rad/s

    # Find the thrust per rotor from the vehicle's mass.
    m_full = 6367  # kg
    g = 9.81  # m/s**2
    thrust_vtol = 0.1 * m_full * g / n_props

    prob = Problem()

    comp = IndepVarComp()
    comp.add_discrete_output('B', val=num_blades)
    comp.add_output('rho', val=rho0, shape=num_nodes, units='kg/m**3')
    comp.add_output('mu', val=1., shape=num_nodes, units='N/m**2*s')
    comp.add_output('asound', val=c0, shape=num_nodes, units='m/s')
    comp.add_output('v', val=2., shape=num_nodes, units='m/s')
    comp.add_output('alpha', val=0., shape=num_nodes, units='rad')
    comp.add_output('incidence', val=0., shape=num_nodes, units='rad')
    comp.add_output('precone', val=0., units='deg')
    comp.add_output('hub_diameter',
                    val=hub_diameter,
                    shape=num_nodes,
                    units='cm')
    comp.add_output('prop_diameter',
                    val=prop_diameter,
                    shape=num_nodes,
                    units='cm')
    comp.add_output('pitch', val=pitch, shape=num_nodes, units='rad')
    comp.add_output('chord_dv', val=chord, shape=num_cp, units='cm')
    comp.add_output('theta_dv', val=theta, shape=num_cp, units='rad')
    comp.add_output('thrust_vtol', val=thrust_vtol, shape=num_nodes, units='N')
    prob.model.add_subsystem('indep_var_comp', comp, promotes=['*'])

    comp = GeometryGroup(num_nodes=num_nodes,
                         num_cp=num_cp,
                         num_radial=num_radial)
    prob.model.add_subsystem(
        'geometry_group',
        comp,
        promotes_inputs=[
            'hub_diameter', 'prop_diameter', 'chord_dv', 'theta_dv', 'pitch'
        ],
        promotes_outputs=['radii', 'dradii', 'chord', 'theta'])

    balance_group = Group()

    comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
    balance_group.add_subsystem(
        'inflow_comp',
        comp,
        promotes_inputs=['v', 'omega', 'radii', 'precone'],
        promotes_outputs=['Vx', 'Vy'])

    comp = CCBladeGroup(num_nodes=num_nodes,
                        num_radial=num_radial,
                        num_blades=num_blades,
                        af_filename=af_filename,
                        turbine=False)
    balance_group.add_subsystem(
        'ccblade_group',
        comp,
        promotes_inputs=[
            'B', 'radii', 'dradii', 'chord', 'theta', 'rho', 'mu', 'asound',
            'v', 'precone', 'omega', 'Vx', 'Vy', 'precone', 'hub_diameter',
            'prop_diameter'
        ],
        promotes_outputs=['thrust', 'torque', 'efficiency'])

    comp = BalanceComp()
    comp.add_balance(
        name='omega',
        eq_units='N',
        lhs_name='thrust',
        rhs_name='thrust_vtol',
        val=omega,
        units='rad/s',
        # lower=0.,
    )
    balance_group.add_subsystem('thrust_balance_comp', comp, promotes=['*'])

    balance_group.linear_solver = DirectSolver(assemble_jac=True)
    balance_group.nonlinear_solver = NewtonSolver(maxiter=50, iprint=2)
    balance_group.nonlinear_solver.options['solve_subsystems'] = True
    # prob.model.nonlinear_solver.linesearch = BoundsEnforceLS()
    balance_group.nonlinear_solver.options['atol'] = 1e-9

    prob.model.add_subsystem('thrust_balance_group',
                             balance_group,
                             promotes=['*'])

    prob.setup()
    prob.final_setup()

    # Calculate the induced axial velocity at the rotor for hover, used for
    # non-diminsionalation.
    rho = prob.get_val('rho', units='kg/m**3')[0]
    hub_diameter = prob.get_val('hub_diameter', units='m')[0]
    prop_diameter = prob.get_val('prop_diameter', units='m')[0]
    thrust_vtol = prob.get_val('thrust_vtol', units='N')[0]
    A_rotor = 0.25 * np.pi * (prop_diameter**2 - hub_diameter**2)
    v_h = np.sqrt(thrust_vtol / (2 * rho * A_rotor))

    # Climb:
    climb_velocity_nondim = np.linspace(0.1, 2., 10)
    induced_velocity_nondim = np.zeros_like(climb_velocity_nondim)
    for vc, vi in np.nditer([climb_velocity_nondim, induced_velocity_nondim],
                            op_flags=[['readonly'], ['writeonly']]):

        # Run the model with the requested climb velocity.
        prob.set_val('v', vc * v_h, units='m/s')
        print(f"v = {prob.get_val('v', units='m/s')}")
        prob.run_model()

        # Calculate the area-weighted average induced velocity at the rotor.
        # Need the area of each blade section.
        radii = prob.get_val('radii', units='m')
        dradii = prob.get_val('dradii', units='m')
        dArea = 2 * np.pi * radii * dradii

        # Get the induced velocity at the rotor plane for each blade section.
        Vx = prob.get_val('Vx', units='m/s')
        a = prob.get_val('ccblade_group.ccblade_comp.a')

        # Get the area-weighted average of the induced velocity.
        vi[...] = np.sum(a * Vx * dArea / A_rotor) / v_h

    # Induced velocity from plain old momentum theory (for climb).
    induced_velocity_mt = (-0.5 * climb_velocity_nondim +
                           np.sqrt((0.5 * climb_velocity_nondim)**2 + 1.))

    fig, ax = plt.subplots()
    ax.plot(climb_velocity_nondim,
            -induced_velocity_nondim,
            label='CCBlade.jl (climb)')
    ax.plot(climb_velocity_nondim,
            induced_velocity_mt,
            label='Momentum Theory (climb)')

    # Descent:
    # climb_velocity_nondim = np.linspace(-3.5, -2.6, 10)
    climb_velocity_nondim = np.linspace(-5.0, -2.1, 10)
    induced_velocity_nondim = np.zeros_like(climb_velocity_nondim)
    for vc, vi in np.nditer([climb_velocity_nondim, induced_velocity_nondim],
                            op_flags=[['readonly'], ['writeonly']]):

        # Run the model with the requested climb velocity.
        prob.set_val('v', vc * v_h, units='m/s')
        print(f"vc = {vc}, v = {prob.get_val('v', units='m/s')}")
        prob.run_model()

        # Calculate the area-weighted average induced velocity at the rotor.
        # Need the area of each blade section.
        radii = prob.get_val('radii', units='m')
        dradii = prob.get_val('dradii', units='m')
        dArea = 2 * np.pi * radii * dradii

        # Get the induced velocity at the rotor plane for each blade section.
        Vx = prob.get_val('Vx', units='m/s')
        a = prob.get_val('ccblade_group.ccblade_comp.a')

        # Get the area-weighted average of the induced velocity.
        vi[...] = np.sum(a * Vx * dArea / A_rotor) / v_h

    # Plot the induced velocity for descent.
    ax.plot(climb_velocity_nondim,
            -induced_velocity_nondim,
            label='CCBlade.jl (descent)')

    # Induced velocity from plain old momentum theory (for descent).
    induced_velocity_mt = (-0.5 * climb_velocity_nondim -
                           np.sqrt((0.5 * climb_velocity_nondim)**2 - 1.))
    ax.plot(climb_velocity_nondim,
            induced_velocity_mt,
            label='Momentum Theory (descent)')

    # Empirical region:
    climb_velocity_nondim = np.linspace(-1.9, -1.5, 5)
    induced_velocity_nondim = np.zeros_like(climb_velocity_nondim)
    for vc, vi in np.nditer([climb_velocity_nondim, induced_velocity_nondim],
                            op_flags=[['readonly'], ['writeonly']]):

        # Run the model with the requested climb velocity.
        prob.set_val('v', vc * v_h, units='m/s')
        print(f"vc = {vc}, v = {prob.get_val('v', units='m/s')}")
        prob.run_model()

        # Calculate the area-weighted average induced velocity at the rotor.
        # Need the area of each blade section.
        radii = prob.get_val('radii', units='m')
        dradii = prob.get_val('dradii', units='m')
        dArea = 2 * np.pi * radii * dradii

        # Get the induced velocity at the rotor plane for each blade section.
        Vx = prob.get_val('Vx', units='m/s')
        a = prob.get_val('ccblade_group.ccblade_comp.a')

        # Get the area-weighted average of the induced velocity.
        vi[...] = np.sum(a * Vx * dArea / A_rotor) / v_h

    # Plot the induced velocity for the empirical region.
    ax.plot(climb_velocity_nondim,
            -induced_velocity_nondim,
            label='CCBlade.jl (empirical region)')

    ax.set_xlabel('Vc/vh')
    ax.set_ylabel('Vi/vh')
    ax.legend()
    fig.savefig('induced_velocity.png')