示例#1
0
    def read_partials_file(self, file, partials):
        # type: (Union[str, etree._ElementTree], Vector) -> None
        """Read the partials from a given XML file and store them in this `Component`'s variables.

        Parameters
        ----------
            file : str or :obj:`etree._ElementTree`
                Path to or :obj:`etree._ElementTree` of a partials XML file.

            partials : Vector
                Partials vector of this `Component`.

        """
        output_rename_map = self.output_rename_map

        _partials = Partials(file)
        for of, wrts in _partials.get_partials().items():
            for wrt, val in wrts.items():
                of = xpath_to_param(of)
                if of in output_rename_map:
                    of = output_rename_map[of][0]

                wrt = xpath_to_param(wrt)
                if (of, wrt) in partials:
                    try:
                        partials[of, wrt] = val
                    except Exception as e:
                        print(e.message)
    def setup(self):
        for name, value in self.inputs_from_xml.items():
            if (not isinstance(value, float) and not isinstance(value, np.ndarray)) or \
                        (isinstance(value, float) and np.isnan(value)) or \
                        (isinstance(value, np.ndarray) and any(np.isnan(value))):
                self.add_discrete_input(name, value)
            else:
                self.add_input(name, value)

        for name, value in self.outputs_from_xml.items():
            if (not isinstance(value, float) and not isinstance(value, np.ndarray)) or \
                        (isinstance(value, float) and np.isnan(value)) or \
                        (isinstance(value, np.ndarray) and any(np.isnan(value))):
                self.add_discrete_output(name, value)
            else:
                # Use the value stored in the input.xml as a reference value
                if isinstance(value, np.ndarray):
                    ref = value.mean()
                else:
                    ref = value
                if ref == 0.:
                    ref = 1.

                self.add_output(name, value, ref=ref)

        if self.partials_from_xml:
            for of, wrt in self.partials_from_xml.items():
                if of is not None and wrt is not None:
                    self.declare_partials(
                        xpath_to_param(of),
                        [xpath_to_param(_wrt) for _wrt in wrt.keys()])
        else:
            self.declare_partials('*', '*', method='fd', step_calc='rel')
示例#3
0
    def setup(self):
        for name, value in self.inputs_from_xml.items():
            if not isinstance(value, float) and not isinstance(
                    value, np.ndarray):
                # TODO: pass_by_obj
                # raise NotImplementedError('pass-by-object variables are not yet supported by OpenMDAO 2.0')
                pass
            else:
                self.add_input(name, value)

        for name, value in self.outputs_from_xml.items():
            if not isinstance(value, float) and not isinstance(
                    value, np.ndarray):
                # TODO: pass_by_obj
                # raise NotImplementedError('pass-by-object variables are not yet supported by OpenMDAO 2.0')
                pass
            else:
                # Use the value stored in the input.xml as a reference value
                if isinstance(value, np.ndarray):
                    ref = value.mean()
                else:
                    ref = value
                if ref == 0.:
                    ref = 1.

                self.add_output(name, value, ref=ref)

        if self.partials_from_xml:
            for of, wrt in self.partials_from_xml.items():
                if of is not None and wrt is not None:
                    self.declare_partials(
                        xpath_to_param(of),
                        [xpath_to_param(_wrt) for _wrt in wrt.keys()])
        else:
            self.declare_partials('*', '*', method='fd', step_calc='rel')
示例#4
0
    def postprocess_experiments(self,
                                vector,
                                vector_name,
                                failed_experiments=(None, None)):
        # type: (np.array, str, Optional(Tuple)) -> np.array
        """
        Postprocess experiments from a DOE to remove failed experiments from the vector.

        Parameters
        ----------
            vector : vector with experiment results
            vector_name : name of vector
            failed_experiments : failed experiments of other vectors (to speed up)

        Returns
        -------
            tuple with post-processed vector and failed experiments

        Raises
        ------
            AssertionError : if vector name is not found in sample lists
        """
        # Determine whether it concerns input or output sample lists
        if vector_name in [
                xpath_to_param(xpath)
                for xpath in self.doe_sample_lists['inputs']
        ]:
            # Assert that the failed_experiments are known, or else throw an error
            if failed_experiments[0] is None:
                raise IOError(
                    'For DOE input sample lists the failed experiments need to be known '
                    'before postprocessing.')
            return np.delete(vector,
                             list(failed_experiments[0])), failed_experiments
        elif vector_name in [
                xpath_to_param(xpath)
                for xpath in self.doe_sample_lists['outputs']
        ]:
            # Determine the failed experiments in the vector
            vector_failures = set(np.where(np.isnan(vector))[0])

            # Add or compare failed experiments w.r.t. failed_experiments input
            if failed_experiments[0] is None:
                failed_experiments = (vector_failures,
                                      len(vector_failures) / len(vector))
            else:
                if not vector_failures == failed_experiments[0]:
                    raise AssertionError(
                        'The failed experiments of {} are not consistent in the '
                        'training data.'.format(vector_name))
            return np.array(list(filter(lambda x: not np.isnan(x),
                                        vector))), failed_experiments
        else:
            raise AssertionError(
                'Could not determine the vector type for vector_name: {}.'.
                format(vector_name))
示例#5
0
 def sm_of_training_params(self):
     # type: () -> Dict[str]
     """Mapping between training parameters and surrogate model UIDs."""
     _sm_mapping = {}
     for sm_uid, xpaths in self.sm_training_inputs.items():
         for xpath in xpaths:
             _sm_mapping[xpath_to_param(xpath)] = sm_uid
     for sm_uid, xpaths in self.sm_training_outputs.items():
         for xpath in xpaths:
             _sm_mapping[xpath_to_param(xpath)] = sm_uid
     return _sm_mapping
示例#6
0
    def setup(self):
        has_cont_input = False
        input_names = set()
        discrete_input_names = set()
        for name, value in self.inputs_from_xml.items():
            if not is_float(value):
                self.add_discrete_input(name, value)
                discrete_input_names.add(name)
            else:
                self.add_input(name, value)
                input_names.add(name)
                has_cont_input = True

        has_cont_output = False
        output_rename_map = self.output_rename_map
        discrete_output_rename_map = self.discrete_output_rename_map
        for name, value in self.outputs_from_xml.items():
            if not is_float(value):
                # Rename output variable if in conflict with input
                if name in discrete_output_rename_map:
                    name = discrete_output_rename_map[name]

                self.add_discrete_output(name, value)

            else:
                # Use the value stored in the input.xml as a reference value
                if isinstance(value, np.ndarray):
                    ref = value.mean()
                else:
                    ref = value
                if ref == 0.:
                    ref = 1.

                # Rename output variable if in conflict with input
                if name in output_rename_map:
                    name = output_rename_map[name][0]

                self.add_output(name, value, ref=ref)
                has_cont_output = True

        # Only declare partials if we have at least one continuous input and output parameter
        if has_cont_input and has_cont_output:
            if self.partials_from_xml:
                for of, wrt in self.partials_from_xml.items():
                    if of is not None and wrt is not None:
                        out_param = xpath_to_param(of)
                        if out_param in output_rename_map:
                            out_param = output_rename_map[out_param][0]
                        self.declare_partials(
                            out_param,
                            [xpath_to_param(_wrt) for _wrt in wrt.keys()])
            else:
                self.declare_partials('*', '*', method='fd', step_calc='rel')
示例#7
0
    def read_outputs_file(self, file, outputs, discrete_outputs=None):
        # type: (Union[str, etree._ElementTree], Vector, Optional[dict]) -> None
        """Read the outputs from a given XML file and store them in this `Component`'s variables.

        Parameters
        ----------
            file : str or :obj:`etree._ElementTree`
                Path to or :obj:`etree._ElementTree` of an output XML file.

            outputs : Vector
                Output vector of this `Component`.

            discrete_outputs : dict
                Discrete (i.e. not treated as floats) outputs.
        """
        output_rename_map = self.output_rename_map
        discrete_output_rename_map = self.discrete_output_rename_map

        # Extract the results from the output xml
        for xpath, value in xml_to_dict(file).items():
            name = xpath_to_param(xpath)
            if name in self.outputs_from_xml:
                # Rename output
                if name in output_rename_map:
                    name = output_rename_map[name][0]
                elif name in discrete_output_rename_map:
                    name = discrete_output_rename_map[name][0]

                if name in outputs:
                    outputs[name] = value
                elif discrete_outputs is not None and name in discrete_outputs:
                    discrete_outputs[name] = value
示例#8
0
    def initialize_from_xml(self, xml):
        # type: (Union[str, _ElementTree]) -> None
        """Initialize the problem with initial values from an XML file.

        Parameters
        ----------
            xml : str or :obj:`etree._ElementTree`
                Path to an XML file or an instance of `etree._ElementTree` representing it.
                """
        self.initialize()
        for xpath, value in xml_to_dict(xml).items():
            name = xpath_to_param(xpath)
            if name in self.model._var_allprocs_prom2abs_list['input'] or \
                    name in self.model._var_allprocs_prom2abs_list['output']:
                self[name] = value
            if name in self.model.mapped_parameters_inv:
                for mapping in self.model.mapped_parameters_inv[name]:
                    if mapping in self.model._var_allprocs_prom2abs_list['input'] or \
                    mapping in self.model._var_allprocs_prom2abs_list['output']:
                        try:
                            self[mapping] = value
                        except RuntimeError as e:
                            if 'The promoted name' in e[0] and 'is invalid' in e[0]:
                                warnings.warn('Could not automatically set this invalid promoted name from the XML: '
                                              '{}.'.format(mapping))
                            else:
                                raise RuntimeError(e)
示例#9
0
    def coupling_vars(self):
        # type: () -> Dict[str, Dict[str, str]]
        """:obj:`dict`: Dictionary with coupling variables."""
        coupling_vars = dict()

        # First create a map between related param and coupling copy var
        for var in self.elem_arch_elems.iter('couplingCopyVariable'):
            related_param = var.find('relatedParameterUID').text
            coupling_vars.update({xpath_to_param(related_param): xpath_to_param(var.attrib['uID'])})

        # Then update dict with corresponding consitency constraint var
        for convar in self.elem_arch_elems.iter('consistencyConstraintVariable'):
            param = xpath_to_param(convar.find('relatedParameterUID').text)
            if param not in coupling_vars:
                raise RuntimeError('invalid cmdows file')

            coupling_vars.update({param: {'copy': coupling_vars[param], 'con': xpath_to_param(convar.attrib['uID'])}})
        return coupling_vars
示例#10
0
    def objective(self):
        # type: () -> str
        """:obj:`str`: Name of the objective variable."""
        objvars = self.elem_params.find('objectiveVariables')
        if objvars is None:
            raise Exception('cmdows does not contain (valid) objective variables')
        if len(objvars) > 1:
            raise Exception('cmdows contains multiple objectives, but this is not supported')

        return xpath_to_param(objvars[0].find('parameterUID').text)
示例#11
0
    def initialize_from_xml(self, xml):
        # type: (Union[str, _ElementTree]) -> None
        """Initialize the problem with initial values from an XML file.

        Parameters
        ----------
            xml : str or :obj:`etree._ElementTree`
                Path to an XML file or an instance of `etree._ElementTree` representing it.
                """
        self.initialize()
        for xpath, value in xml_to_dict(xml).items():
            name = xpath_to_param(xpath)
            prom2abs_list_inputs = self.model._var_allprocs_prom2abs_list[
                'input']
            prom2abs_list_outputs = self.model._var_allprocs_prom2abs_list[
                'output']
            if self.comm.size > 1:  # Small workaround for issue in OpenMDAO with mpirun
                if name in prom2abs_list_inputs:
                    for abs_name in prom2abs_list_inputs[name]:
                        self[abs_name] = value
                if name in prom2abs_list_outputs:
                    for abs_name in prom2abs_list_outputs[name]:
                        self[abs_name] = value
            else:
                if name in prom2abs_list_inputs or name in prom2abs_list_outputs:
                    self[name] = value
            if name in self.model.mapped_parameters_inv:
                for mapping in self.model.mapped_parameters_inv[name]:
                    if mapping in prom2abs_list_inputs or mapping in prom2abs_list_outputs:
                        try:
                            # Small workaround for issue in OpenMDAO with mpirun
                            if self.comm.size > 1:
                                abs_names = []
                                if mapping in prom2abs_list_inputs:
                                    abs_names.extend([
                                        abs_name for abs_name in
                                        prom2abs_list_inputs[mapping]
                                    ])
                                if mapping in prom2abs_list_outputs:
                                    abs_names.extend([
                                        abs_name for abs_name in
                                        prom2abs_list_outputs[mapping]
                                    ])
                                for abs_name in abs_names:
                                    self[abs_name] = value
                            else:
                                self[mapping] = value
                        except RuntimeError as e:
                            if 'The promoted name' in e[
                                    0] and 'is invalid' in e[0]:
                                warnings.warn(
                                    'Could not automatically set this invalid promoted '
                                    'name from the XML: {}.'.format(mapping))
                            else:
                                raise RuntimeError(e)
示例#12
0
    def system_inputs(self):
        # type: () -> Dict[str, int]
        """:obj:`dict`: Dictionary containing the system input sizes by their names."""
        system_inputs = {}
        for value in self.elem_cmdows.xpath(
                    r'workflow/dataGraph/edges/edge[fromExecutableBlockUID="Coordinator"]/toParameterUID/text()'):
            if 'architectureNodes' not in value or 'designVariables' in value:
                name = xpath_to_param(value)
                system_inputs.update({name: self.variable_sizes[name]})

        return system_inputs
示例#13
0
    def set_outputs_from_xml(self, output_xml):
        # type: (Union[str, etree._ElementTree]) -> None
        """Set outputs to the `Component` based on an output XML template file.

        Parameter names correspond to their XML elements' full XPaths, converted to valid ``OpenMDAO`` names using the
        `xpath_to_param()` method.

        Parameters
        ----------
            output_xml : str or :obj:`etree._ElementTree`
                Path to or an `etree._ElementTree` of an output XML file.
        """
        self.outputs_from_xml.clear()
        for xpath, value in xml_to_dict(output_xml).items():
            name = xpath_to_param(xpath)
            self.outputs_from_xml.update({name: value})
示例#14
0
    def initialize_from_xml(self, xml):
        # type: (Union[str, _ElementTree]) -> None
        """Initialize the problem with initial values from an XML file.

        This function can only be called after the problem's setup method has been called.

        Parameters
        ----------
            xml : str or :obj:`etree._ElementTree`
                Path to an XML file or an instance of `etree._ElementTree` representing it.
        """
        for xpath, value in xml_to_dict(xml).items():
            name = xpath_to_param(xpath)
            if name in self._outputs:
                self._outputs[name] = value
            elif name in self._inputs:
                self._inputs[name] = value
示例#15
0
    def read_outputs_file(self, file, outputs):
        # type: (Union[str, etree._ElementTree], Vector) -> None
        """Read the outputs from a given XML file and store them in this `Component`'s variables.

        Parameters
        ----------
            file : str or :obj:`etree._ElementTree`
                Path to or :obj:`etree._ElementTree` of an output XML file.

            outputs : Vector
                Output vector of this `Component`.
        """
        # Extract the results from the output xml
        for xpath, value in xml_to_dict(file).items():
            name = xpath_to_param(xpath)
            if name in self.outputs_from_xml and name in outputs:
                outputs[name] = value
示例#16
0
    def design_vars(self):
        # type: () -> Dict[str, Dict[str, Any]]
        """:obj:`dict`: Dictionary containing the design variables' initial values, lower bounds, and upper bounds."""
        desvars = self.elem_params.find('designVariables')
        if desvars is None:
            raise Exception('cmdows does not contain (valid) design variables')

        design_vars = {}
        for desvar in desvars:
            name = xpath_to_param(desvar.find('parameterUID').text)

            # Obtain the initial value
            initial = desvar.find('nominalValue')
            if initial is not None:
                initial = parse_cmdows_value(initial)
                if not self.does_value_fit(name, initial):
                    raise ValueError('incompatible size of nominalValue for design variable "%s"' % name)
            else:
                warnings.warn('no nominalValue given for designVariable "%s". Default is all zeros.' % name)
                initial = np.zeros(self.variable_sizes[name])

            if name in self.coupling_vars:
                # If this is a coupling variable the bounds are -1e99 and 1e99 and it should not be normalized
                design_vars.update(
                    {self.coupling_vars[name]['copy']: {'initial': initial,
                                                        'lower': -1e99*np.ones(self.variable_sizes[name]),
                                                        'upper': 1e99*np.ones(self.variable_sizes[name]),
                                                        'ref0': None, 'ref': None}})
            else:
                # Obtain the lower and upper bounds
                bounds = 2 * [None]  # type: List[Optional[str]]
                limit_range = desvar.find('validRanges/limitRange')
                if limit_range is not None:
                    for index, bnd, in enumerate(['minimum', 'maximum']):
                        elem = limit_range.find(bnd)
                        if elem is not None:
                            bounds[index] = parse_cmdows_value(elem)
                            if not self.does_value_fit(name, bounds[index]):
                                raise ValueError('incompatible size of %s for design variable %s' % (bnd, name))

                # Add the design variable to the dict
                design_vars.update({name: {'initial': initial,
                                           'lower': bounds[0], 'upper': bounds[1],
                                           'ref0': bounds[0], 'ref': bounds[1]}})
        return design_vars
示例#17
0
    def consistency_constraint_group(self):
        # type: () -> Optional[Group]
        """:obj:`Group`, optional: Group containing ExecComps for the consistency constraints."""
        elem_ccf = self.elem_arch_elems.find('executableBlocks/consistencyConstraintFunctions')
        if elem_ccf is not None:
            group = Group()

            # Loop over all consistencyConstraintFunction elements
            for child in elem_ccf:
                uid = child.attrib['uID']
                xpaths = []

                # Loop over all coupling variables which need to be constraint by this consistencyConstraintFunction
                for value in self.elem_cmdows.xpath(
                        'workflow/dataGraph/edges/edge[toExecutableBlockUID="{}"]/fromParameterUID/text()'.format(uid)):
                    # Only add a given variable once
                    if 'architectureNodes' not in value and value not in xpaths:
                        xpaths.append(value)

                        name = xpath_to_param(value)
                        size = self.variable_sizes[name]
                        coupling_var = self.coupling_vars[name]

                        if size == 1:
                            val = 0.
                        else:
                            val = np.zeros(size)

                        sys_name = re_sys_name_char.sub('', self.elem_arch_elems.xpath(
                            'parameters/consistencyConstraintVariables/' +
                            'consistencyConstraintVariable[@uID="{}"]/label/text()'.format(coupling_var['con']))[0])
                        while not re_sys_name_starts.match(sys_name):
                            sys_name = sys_name[1:]

                        # Add an ExecComp to the Group for this equality constraint
                        group.add_subsystem(
                            sys_name,
                            ExecComp('g = y_c - y', g=val, y_c=val, y=val),
                            [('g', coupling_var['con']), ('y_c', coupling_var['copy']), ('y', name)])
            return group
        return None
示例#18
0
    def constraints(self):
        # type: () -> Dict[str, Dict[str, Any]]
        """:obj:`dict`: Dictionary containing the constraints' lower, upper, and equals reference values."""
        convars = self.elem_params.find('constraintVariables')
        constraints = {}
        if convars is not None:
            for convar in convars:
                con = {'lower': None, 'upper': None, 'equals': None}
                name = xpath_to_param(convar.find('parameterUID').text)

                if self.coupling_var_cons is not None and name in self.coupling_var_cons.values():
                    # If this is a coupling variable consistency constraint, equals should just be zero
                    for key, value in self.coupling_var_cons.items():
                        if name == value:
                            size = self.variable_sizes[key]
                            if size == 1:
                                con['equals'] = 0.
                            else:
                                con['equals'] = np.zeros(self.variable_sizes[key])
                            break
                else:
                    # Obtain the reference value of the constraint
                    constr_ref = convar.find('referenceValue')  # type: etree._Element
                    if constr_ref is not None:
                        ref = parse_cmdows_value(constr_ref)
                        if isinstance(ref, str):
                            raise ValueError('referenceValue for constraint "%s" is not numerical' % name)
                        elif not self.does_value_fit(name, ref):
                            warnings.warn('incompatible size of constraint "%s". Will assume the same for all.' % name)
                            ref = np.ones(self.variable_sizes[name]) * np.atleast_1d(ref)[0]
                    else:
                        warnings.warn('no referenceValue given for constraint "%s". Default is all zeros.' % name)
                        ref = np.zeros(self.variable_sizes[name])

                    # Process the constraint type
                    constr_type = convar.find('constraintType')
                    if constr_type is not None:
                        if constr_type.text == 'inequality':
                            constr_oper = convar.find('constraintOperator')
                            if constr_oper is not None:
                                oper = constr_oper.text
                                if oper == '>=' or oper == '>':
                                    con['lower'] = ref
                                elif oper == '<=' or oper == '<':
                                    con['upper'] = ref
                                else:
                                    raise ValueError('invalid constraintOperator "%s" for constraint "%s"' % (oper, name))
                            else:
                                warnings.warn(
                                    'no constraintOperator given for inequality constraint. Default is "&lt;=".')
                                con['upper'] = ref
                        elif constr_type.text == 'equality':
                            if convar.find('constraintOperator') is not None:
                                warnings.warn('constraintOperator given for an equalityConstraint will be ignored')
                            con['equals'] = ref
                        else:
                            raise ValueError('invalid constraintType "%s" for constraint "%s".' % (constr_type.text, name))
                    else:
                        warnings.warn('no constraintType specified for constraint "%s". Default is a <= inequality.')
                        con['upper'] = ref

                # Add constraint to the dictionary
                constraints.update({name: con})
        return constraints
示例#19
0
    def collect_results(self, cases_to_collect='default', print_in_log=True):
        # type: (Union[str, list]) -> Dict[dict]
        """Print the results that were stored in the case reader.

        Parameters
        ----------
            cases_to_collect : str or list
                Setting on which cases should be print (e.g. 'last', 'all', 'default', [2, 3, 5])

            print_in_log : bool
                Setting on whether the results should also be printed in the log

        Returns
        -------
            results : dict of dicts
                Dictionary containing the results that were collected
        """
        if not isinstance(cases_to_collect, (str, list)):
            raise AssertionError('cases_to_print must be of type str or list.')
        if isinstance(cases_to_collect, str):
            if cases_to_collect not in ['default', 'all', 'last']:
                raise AssertionError(
                    'Invalid cases_to_print string value provided.')
        if cases_to_collect == 'default':
            if self.driver_type == 'doe':
                cases_to_collect = 'all'
            elif self.driver_type == 'optimizer':
                cases_to_collect = 'last'
            else:
                cases_to_collect = 'last'
        results = dict()

        # Get all cases from the case reader and determine amount of cases
        cr = self.get_case_reader()
        cases = cr.list_cases('driver')
        num_cases = len(cases)

        if num_cases == 0:
            raise AssertionError(
                'No cases were recorded and therefore no results can be collected.'
                ' Note that collect_results only works after the driver has been '
                'run.')

        # Change cases_to_print to a list of integers with case numbers
        if isinstance(cases_to_collect, str):
            if cases_to_collect == 'all':
                cases_to_collect = range(num_cases)
            elif cases_to_collect == 'last':
                cases_to_collect = [num_cases - 1]

        # Print results
        print_optional(
            '\nPrinting results from case reader: {}.'.format(
                self.case_reader_path), print_in_log)
        if self.driver.fail:
            if self.driver_type == 'optimizer':
                print_optional('Optimum not found in driver execution!',
                               print_in_log)
            else:
                print_optional('Driver failed for some reason!', print_in_log)
        else:
            if self.driver_type == 'optimizer':
                print_optional('Optimum found!', print_in_log)
            else:
                print_optional('Driver finished!', print_in_log)
        print_optional('\nPrinting case numbers: {}'.format(cases_to_collect),
                       print_in_log)
        for num_case in cases_to_collect:
            case = cr.get_case(cases[num_case])
            print_optional(
                '\n\n  Case {}/{} ({})'.format(num_case, num_cases - 1,
                                               case.iteration_coordinate),
                print_in_log)

            # Get objectives, design variables and contraints
            recorded_objectives = case.get_objectives(scaled=False)
            recorded_design_vars = case.get_design_vars(scaled=False)
            recorded_constraints = case.get_constraints(scaled=False)

            # Get objectives, design variables and contraints
            var_objectives = sorted(list(recorded_objectives.keys()))
            var_design_vars = sorted(list(recorded_design_vars.keys()))
            var_constraints = sorted(list(recorded_constraints.keys()))

            var_does = []
            if isinstance(self.driver, DOEDriver):
                var_does = sorted([
                    elem.text for elem in self.elem_arch_elems.findall(
                        'parameters/doeOutputSampleLists/doeOutputSampleList/'
                        'relatedParameterUID')
                ])
            var_convs = sorted([
                elem.text for elem in self.elem_problem_def.findall(
                    'problemRoles/parameters/stateVariables/stateVariable/'
                    'parameterUID')
            ])

            # Print objective
            if var_objectives:
                print_optional('    Objectives', print_in_log)
                for var_objective in var_objectives:
                    value = recorded_objectives[xpath_to_param(var_objective)]
                    print_optional('    {}: {}'.format(var_objective, value),
                                   print_in_log)
                    results = add_or_append_dict_entry(results, 'objectives',
                                                       var_objective, value)

            # Print design variables
            if var_design_vars:
                print_optional('\n    Design variables', print_in_log)
                for var_desvar in var_design_vars:
                    metadata_name = cr._prom2abs['output'][var_desvar][0]
                    value = recorded_design_vars[var_desvar]
                    if len(value) == 1:
                        value = value[0]
                    lb_value = cr.problem_metadata['variables'][metadata_name][
                        'ref0']
                    ub_value = cr.problem_metadata['variables'][metadata_name][
                        'ref']
                    print_optional(
                        '    {}: {} ({} < x < {})'.format(
                            var_desvar, value, lb_value, ub_value),
                        print_in_log)
                    results = add_or_append_dict_entry(results, 'desvars',
                                                       var_desvar, value)

            # Print constraint values
            if var_constraints:
                print_optional('\n    Constraints', print_in_log)
                for var_constraint in var_constraints:
                    metadata_name = cr._prom2abs['output'][var_constraint][0]
                    value = recorded_constraints[var_constraint]
                    if len(value) == 1:
                        value = value[0]
                    lb_value = cr.problem_metadata['variables'][metadata_name][
                        'lower']
                    ub_value = cr.problem_metadata['variables'][metadata_name][
                        'upper']
                    eq_value = cr.problem_metadata['variables'][metadata_name][
                        'equals']
                    if eq_value is not None:
                        print_optional(
                            '    {}: {} (c == {})'.format(
                                var_constraint, value, eq_value), print_in_log)
                    else:
                        if lb_value > -1e29 and ub_value < 1e29:
                            print_optional(
                                '    {}: {} ({} < c < {})'.format(
                                    var_constraint, value, lb_value, ub_value),
                                print_in_log)
                        elif lb_value < -1e29 and ub_value < 1e29:
                            print_optional(
                                '    {}: {} (c < {})'.format(
                                    var_constraint, value, ub_value),
                                print_in_log)
                        elif lb_value > -1e29 and ub_value > 1e29:
                            print_optional(
                                '    {}: {} (c > {})'.format(
                                    var_constraint, value, lb_value),
                                print_in_log)
                        else:
                            print_optional(
                                '    {}: {} (c is unbounded)'.format(
                                    var_constraint, value), print_in_log)
                    results = add_or_append_dict_entry(results, 'constraints',
                                                       var_constraint, value)

            # Print DOE quantities of interest
            if var_does:
                print_optional('\n    Quantities of interest', print_in_log)
                for var_qoi in var_does:
                    if var_qoi in var_does:
                        value = case.outputs[var_qoi]
                        if len(value) == 1:
                            value = value[0]
                        print_optional('    {}: {}'.format(var_qoi, value),
                                       print_in_log)
                        results = add_or_append_dict_entry(
                            results, 'qois', var_qoi, value)

            # Print other quantities of interest
            title_not_printed = True
            if var_convs:
                for var_qoi in var_convs:
                    if var_qoi not in var_objectives + var_constraints + var_does:
                        if title_not_printed:
                            print_optional('\n    Quantities of interest',
                                           print_in_log)
                            title_not_printed = False
                        value = case.outputs[var_qoi]
                        if len(value) == 1:
                            value = value[0]
                        print_optional('    {}: {}'.format(var_qoi, value),
                                       print_in_log)
                        results = add_or_append_dict_entry(
                            results, 'qois', var_qoi, value)