def calc_derivatives(self, first=False, second=False, savebase=True, required_inputs=None, required_outputs=None):
        """Calculate the derivatives for this non-differentiable block using
        Finite Difference."""
        # We don't do this in __init__ because some inputs and outputs
        # are added after creation (for nested driver support).
        if self.fd is None:
            from openmdao.main.derivatives import FiniteDifference

            self.fd = FiniteDifference(self)

        if hasattr(self.wflow, "_severed_edges"):
            self.wflow.sever_edges(self.wflow._severed_edges)

        try:
            # First, linearize about operating point.
            # Note: Only needed for differentiable islands, which are handled
            # with Fake Finite Difference.
            # Don't do this for full-model finite difference.
            if first and self.ffd_order > 0:
                for name in self.comps:
                    comp = self.wflow.scope.get(name)

                    # Comp list contains full graph, so don't double up on
                    # the subdrivers.
                    if not has_interface(comp, IDriver) and name not in self.wflow._J_cache:
                        comp.calc_derivatives(first, second, True)

            self.J = self.fd.calculate()
        finally:
            if hasattr(self.wflow, "_severed_edges"):
                self.wflow.unsever_edges()

        return self.J
class PseudoAssembly(object):
    """The PseudoAssembly is used to aggregate blocks of components that cannot
    provide derivatives, and thus must be finite differenced. It is not a real
    assembly, and should never be used in an OpenMDAO model."""

    def __init__(self, name, comps, graph, wflow, fd=False, drv_name=None, boundary_params=()):
        """Initialized with list of components, and the parent workflow."""

        inputs, outputs, renames = self._pre_init(name, comps, graph, fd, boundary_params)

        self.comps = wflow.scope._depgraph.order_components(set(comps))

        self.name = name
        self.boundary_params = list(boundary_params)
        self.wflow = wflow
        self.inputs = list(inputs)
        self.outputs = list(outputs)
        self.renames = renames
        self.mapped_inputs = []
        self._removed_comps = []
        self._provideJ_bounds = None
        for varpath in self.inputs:
            if isinstance(varpath, basestring):
                val = to_PA_var(varpath, name).partition(".")[2]
            else:
                val = tuple([to_PA_var(vp, name).partition(".")[2] for vp in varpath])
            self.mapped_inputs.append(val)

        self.mapped_outputs = [to_PA_var(varpath, name).partition(".")[2] for varpath in self.outputs]
        self.itername = ""

        self.fd = None
        self.J = None

        if fd:  # for full-model fd, turn off fake finite difference
            self.ffd_order = 0
        else:  # use fake finite difference on comps having derivatives
            self.ffd_order = 1

        if fd:
            self.itercomps = [c.name for c in wflow]
        elif drv_name is not None:
            self.itercomps = [drv_name]
        else:
            self.itercomps = list(self.comps)

    def _pre_init(self, pa_name, group, dgraph, fd, boundary_params):
        """Return a tuple of the form (pa_inputs, pa_outputs, renames)
        for the PseudoAssembly that would be created given the nodes in
        group and the given graph.
        """

        # First, find our group boundary
        self._orig_group_nodes = list(group) + list(boundary_params)
        allnodes = dgraph.find_prefixed_nodes(self._orig_group_nodes)
        out_edges = nx.edge_boundary(dgraph, allnodes)
        in_edges = nx.edge_boundary(dgraph, set(dgraph.nodes()).difference(allnodes))
        solver_states = []
        if fd is False:
            for comp in group:
                solver_states.extend(
                    [node for node in dgraph.predecessors(comp) if "solver_state" in dgraph.node[node]]
                )

        pa_inputs = edges_to_dict(in_edges).values()
        pa_inputs.extend(solver_states)
        pa_outputs = set([a for a, b in out_edges])

        renames = {}

        # Add pseudoassy inputs
        for varpath in list(flatten_list_of_iters(pa_inputs)) + list(pa_outputs):
            varname = to_PA_var(varpath, pa_name)
            if varpath in dgraph:
                renames[varpath] = varname
                old = dgraph.base_var(varpath)
                if old != varpath:
                    renames[old] = to_PA_var(old, pa_name)

        # make boundary params outputs of the PA
        pa_outputs.update(boundary_params)

        return pa_inputs, pa_outputs, renames

    def set_itername(self, name):
        """Comp API compatibility; allows iteration coord to be set in
        components."""
        self.itername = name

    def run(self, ffd_order=0, case_id=""):
        """Run all components contained in this assy. Used by finite
        difference."""

        # Override fake finite difference if requested. This enables a pure
        # finite-differences for check_derivatives.
        if self.ffd_order == 0:
            ffd_order = 0

        for name in self.itercomps:
            comp = self.wflow.scope.get(name)
            comp.set_itername(self.itername + "-fd")
            comp.run(ffd_order=ffd_order, case_id=case_id)

    def calc_derivatives(self, first=False, second=False, savebase=True, required_inputs=None, required_outputs=None):
        """Calculate the derivatives for this non-differentiable block using
        Finite Difference."""
        # We don't do this in __init__ because some inputs and outputs
        # are added after creation (for nested driver support).
        if self.fd is None:
            from openmdao.main.derivatives import FiniteDifference

            self.fd = FiniteDifference(self)

        if hasattr(self.wflow, "_severed_edges"):
            self.wflow.sever_edges(self.wflow._severed_edges)

        try:
            # First, linearize about operating point.
            # Note: Only needed for differentiable islands, which are handled
            # with Fake Finite Difference.
            # Don't do this for full-model finite difference.
            if first and self.ffd_order > 0:
                for name in self.comps:
                    comp = self.wflow.scope.get(name)

                    # Comp list contains full graph, so don't double up on
                    # the subdrivers.
                    if not has_interface(comp, IDriver) and name not in self.wflow._J_cache:
                        comp.calc_derivatives(first, second, True)

            self.J = self.fd.calculate()
        finally:
            if hasattr(self.wflow, "_severed_edges"):
                self.wflow.unsever_edges()

        return self.J

    def provideJ(self):
        """Jacobian for this block"""
        return self.J

    def list_deriv_vars(self):
        """Derivative inputs and outputs for this block"""
        return self.mapped_inputs, self.mapped_outputs

    def get(self, varname):
        """ Return the value of a variable in the Pseudoassembly. Used
        when sizing variables in the Jacobian."""

        return self.wflow.scope.get(from_PA_var(self.name + "." + varname))

    def add_to_graph(self, startgraph, dgraph, excludes=()):
        """Add this PseudoAssembly to the given graph, absorbing
        nodes that represent components contained in this PA.
        """
        from openmdao.main.depgraph import is_subvar_node, is_input_base_node, is_output_base_node

        self._removed_comps = [c for c in self.comps if c not in excludes]

        # Add pseudoassys to graph
        dgraph.add_node(self.name, pa_object=self, comp=True, pseudo="assembly", valid=True)

        empty = {}
        for oldname, newname in self.renames.items():
            attrs = startgraph.node.get(oldname, empty).copy()
            if oldname in self.boundary_params:
                attrs["iotype"] = "out"
            dgraph.add_node(newname, attr_dict=attrs)
            for u, v, data in dgraph.edges(oldname, data=True):
                dgraph.add_edge(newname, v, attr_dict=data.copy())
            for u, v, data in dgraph.in_edges(oldname, data=True):
                dgraph.add_edge(u, newname, attr_dict=data.copy())

            if is_subvar_node(dgraph, newname):
                dgraph.node[newname]["basevar"] = newname.split("[", 1)[0]
            if is_input_base_node(dgraph, newname):
                dgraph.add_edge(newname, self.name)
            elif is_output_base_node(dgraph, newname):
                dgraph.add_edge(self.name, newname)

        dgraph.config_changed()

    def clean_graph(self, startgraph, dgraph, excludes=()):
        """ Clean up the old nodes in the graph, leaving out ones that
        are excluded because they're used by ancestor drivers.
        """
        dgraph.remove_nodes_from(
            dgraph.find_prefixed_nodes([n for n in self._orig_group_nodes if n not in excludes and n in dgraph])
        )

        # if this PA represents a driver, remove the corresponding driver
        # node
        if self.name[1:] in dgraph:
            dgraph.remove_nodes_from(dgraph.find_prefixed_nodes([self.name[1:]]))

        # update mapped inputs/outputs in the graph
        map_inputs = dgraph.graph["mapped_inputs"]
        map_outputs = dgraph.graph["mapped_outputs"]

        for i, varpath in enumerate(map_inputs):
            if isinstance(varpath, basestring):
                varpath = [varpath]

            mapped = []
            for path in varpath:
                compname, _, varname = path.partition(".")
                if varname and (compname in self.comps):
                    mapped.append(to_PA_var(path, self.name))
                else:
                    mapped.append(path)  # keep old value in that spot

            map_inputs[i] = tuple(mapped)

        # Add requested outputs
        for i, varpath in enumerate(map_outputs):
            compname, _, varname = varpath.partition(".")
            if varname and (compname in self.comps):
                map_outputs[i] = to_PA_var(varpath, self.name)

        dgraph.config_changed()
예제 #3
0
    def calc_derivatives(self,
                         first=False,
                         second=False,
                         savebase=True,
                         required_inputs=None,
                         required_outputs=None):
        """Calculate the derivatives for this non-differentiable block using
        Finite Difference."""
        # We don't do this in __init__ because some inputs and outputs
        # are added after creation (for nested driver support).
        if self.fd is None:
            from openmdao.main.derivatives import FiniteDifference
            self.fd = FiniteDifference(self)

        if hasattr(self.wflow, '_severed_edges'):
            self.wflow.sever_edges(self.wflow._severed_edges)

        try:
            # First, linearize about operating point.
            # Note: Only needed for differentiable islands, which are handled
            # with Fake Finite Difference.
            # Don't do this for full-model finite difference.
            if first and self.ffd_order > 0:
                for name in self.comps:

                    # TODO: I think the cache is blown away each time before
                    # this is called
                    if name in self.wflow._J_cache:
                        continue

                    comp = self.wflow.scope.get(name)

                    # Assemblies need some required inputs and outputs
                    # to calculate the Jacobians
                    if has_interface(comp, IAssembly):

                        # Need to know which assy bdry variables are
                        # required, and pass them in. Cache this once.
                        if name not in self.ffd_cache:
                            dgraph = self.wflow.scope._depgraph
                            inputs = [
                                dgraph.base_var(inp)
                                for inp in flatten_list_of_iters(self.inputs)
                            ]
                            outputs = [
                                dgraph.base_var(outp) for outp in self.outputs
                            ]
                            from openmdao.main.depgraph import _get_inner_edges
                            edges = _get_inner_edges(dgraph, inputs, outputs)

                            req_inputs = []
                            req_outputs = []
                            for inp in inputs:
                                comp_str, _, var_str = inp.partition('.')
                                if comp_str == name:
                                    req_inputs.append(var_str)

                            for inp in outputs:
                                comp_str, _, var_str = inp.partition('.')
                                if comp_str == name:
                                    req_outputs.append(var_str)

                            for edge in edges:
                                src, target = edge
                                comp_str, _, var_str = src.partition('.')
                                if comp_str == name:
                                    req_outputs.append(var_str)
                                comp_str, _, var_str = target.partition('.')
                                if comp_str == name:
                                    req_inputs.append(var_str)

                            self.ffd_cache[name] = (req_inputs, req_outputs)

                        req_inputs, req_outputs = self.ffd_cache[name]

                        comp.calc_derivatives(first,
                                              second,
                                              savebase=True,
                                              required_inputs=req_inputs,
                                              required_outputs=req_outputs)

                    # Comp list contains full graph, so don't double up on
                    # the subdrivers.
                    elif not has_interface(comp, IDriver):
                        comp.calc_derivatives(first, second, True)

            self.J = self.fd.calculate()
        finally:
            if hasattr(self.wflow, '_severed_edges'):
                self.wflow.unsever_edges()

        return self.J
예제 #4
0
class PseudoAssembly(object):
    """The PseudoAssembly is used to aggregate blocks of components that cannot
    provide derivatives, and thus must be finite differenced. It is not a real
    assembly, and should never be used in an OpenMDAO model."""
    def __init__(self,
                 name,
                 comps,
                 graph,
                 wflow,
                 fd=False,
                 drv_name=None,
                 boundary_params=()):
        """Initialized with list of components, and the parent workflow."""

        inputs, outputs, renames = self._pre_init(name, comps, graph, fd,
                                                  boundary_params)

        self.comps = wflow.scope._depgraph.order_components(set(comps))

        self.name = name
        self.boundary_params = list(boundary_params)
        self.wflow = wflow
        self.inputs = list(inputs)
        self.outputs = list(outputs)
        self.renames = renames
        self.mapped_inputs = []
        self._removed_comps = []
        self._provideJ_bounds = None
        for varpath in self.inputs:
            if isinstance(varpath, basestring):
                val = to_PA_var(varpath, name).partition('.')[2]
            else:
                val = tuple(
                    [to_PA_var(vp, name).partition('.')[2] for vp in varpath])
            self.mapped_inputs.append(val)

        self.mapped_outputs = [
            to_PA_var(varpath, name).partition('.')[2]
            for varpath in self.outputs
        ]
        self.itername = ''

        self.fd = None
        self.J = None
        self.ffd_cache = {}

        if fd:  # for full-model fd, turn off fake finite difference
            self.ffd_order = 0
        else:  # use fake finite difference on comps having derivatives
            # TODO: Fake Finite Difference has been disabled until we upgrade
            # it to support arrays and vartrees.
            self.ffd_order = 0

        if fd:
            self.itercomps = [c.name for c in wflow]
        elif drv_name is not None:
            self.itercomps = [drv_name]
        else:
            self.itercomps = list(self.comps)

        #print "Created PseudoAssembly:", self.comps

    def _pre_init(self, pa_name, group, dgraph, fd, boundary_params):
        """Return a tuple of the form (pa_inputs, pa_outputs, renames)
        for the PseudoAssembly that would be created given the nodes in
        group and the given graph.
        """

        # First, find our group boundary
        self._orig_group_nodes = list(group) + list(boundary_params)
        allnodes = dgraph.find_prefixed_nodes(self._orig_group_nodes)
        out_edges = nx.edge_boundary(dgraph, allnodes)
        in_edges = nx.edge_boundary(dgraph,
                                    set(dgraph.nodes()).difference(allnodes))
        solver_states = []
        if fd is False:
            for comp in group:
                solver_states.extend([
                    node for node in dgraph.predecessors(comp)
                    if 'solver_state' in dgraph.node[node]
                ])

        pa_inputs = edges_to_dict(in_edges).values()
        pa_inputs.extend(solver_states)
        pa_outputs = set([a for a, b in out_edges])

        renames = {}

        # Add pseudoassy inputs
        for varpath in list(flatten_list_of_iters(pa_inputs)) + \
                       list(pa_outputs):
            varname = to_PA_var(varpath, pa_name)
            if varpath in dgraph:
                renames[varpath] = varname
                old = dgraph.base_var(varpath)
                if old != varpath:
                    renames[old] = to_PA_var(old, pa_name)

        # make boundary params outputs of the PA
        pa_outputs.update(boundary_params)

        return pa_inputs, pa_outputs, renames

    def set_itername(self, name):
        """Comp API compatibility; allows iteration coord to be set in
        components."""
        self.itername = name

    def run(self, ffd_order=0, case_id=''):
        """Run all components contained in this assy. Used by finite
        difference."""

        # Override fake finite difference if requested. This enables a pure
        # finite-differences for check_derivatives.
        if self.ffd_order == 0:
            ffd_order = 0

        for name in self.itercomps:
            comp = self.wflow.scope.get(name)
            comp.set_itername(self.itername + '-fd')
            comp.run(ffd_order=ffd_order, case_id=case_id)

    def calc_derivatives(self,
                         first=False,
                         second=False,
                         savebase=True,
                         required_inputs=None,
                         required_outputs=None):
        """Calculate the derivatives for this non-differentiable block using
        Finite Difference."""
        # We don't do this in __init__ because some inputs and outputs
        # are added after creation (for nested driver support).
        if self.fd is None:
            from openmdao.main.derivatives import FiniteDifference
            self.fd = FiniteDifference(self)

        if hasattr(self.wflow, '_severed_edges'):
            self.wflow.sever_edges(self.wflow._severed_edges)

        try:
            # First, linearize about operating point.
            # Note: Only needed for differentiable islands, which are handled
            # with Fake Finite Difference.
            # Don't do this for full-model finite difference.
            if first and self.ffd_order > 0:
                for name in self.comps:

                    # TODO: I think the cache is blown away each time before
                    # this is called
                    if name in self.wflow._J_cache:
                        continue

                    comp = self.wflow.scope.get(name)

                    # Assemblies need some required inputs and outputs
                    # to calculate the Jacobians
                    if has_interface(comp, IAssembly):

                        # Need to know which assy bdry variables are
                        # required, and pass them in. Cache this once.
                        if name not in self.ffd_cache:
                            dgraph = self.wflow.scope._depgraph
                            inputs = [
                                dgraph.base_var(inp)
                                for inp in flatten_list_of_iters(self.inputs)
                            ]
                            outputs = [
                                dgraph.base_var(outp) for outp in self.outputs
                            ]
                            from openmdao.main.depgraph import _get_inner_edges
                            edges = _get_inner_edges(dgraph, inputs, outputs)

                            req_inputs = []
                            req_outputs = []
                            for inp in inputs:
                                comp_str, _, var_str = inp.partition('.')
                                if comp_str == name:
                                    req_inputs.append(var_str)

                            for inp in outputs:
                                comp_str, _, var_str = inp.partition('.')
                                if comp_str == name:
                                    req_outputs.append(var_str)

                            for edge in edges:
                                src, target = edge
                                comp_str, _, var_str = src.partition('.')
                                if comp_str == name:
                                    req_outputs.append(var_str)
                                comp_str, _, var_str = target.partition('.')
                                if comp_str == name:
                                    req_inputs.append(var_str)

                            self.ffd_cache[name] = (req_inputs, req_outputs)

                        req_inputs, req_outputs = self.ffd_cache[name]

                        comp.calc_derivatives(first,
                                              second,
                                              savebase=True,
                                              required_inputs=req_inputs,
                                              required_outputs=req_outputs)

                    # Comp list contains full graph, so don't double up on
                    # the subdrivers.
                    elif not has_interface(comp, IDriver):
                        comp.calc_derivatives(first, second, True)

            self.J = self.fd.calculate()
        finally:
            if hasattr(self.wflow, '_severed_edges'):
                self.wflow.unsever_edges()

        return self.J

    def provideJ(self):
        """Jacobian for this block"""
        return self.J

    def list_deriv_vars(self):
        """Derivative inputs and outputs for this block"""
        return self.mapped_inputs, self.mapped_outputs

    def get(self, varname):
        """ Return the value of a variable in the Pseudoassembly. Used
        when sizing variables in the Jacobian."""

        return self.wflow.scope.get(from_PA_var(self.name + '.' + varname))

    def add_to_graph(self, startgraph, dgraph, excludes=()):
        """Add this PseudoAssembly to the given graph, absorbing
        nodes that represent components contained in this PA.
        """
        from openmdao.main.depgraph import is_subvar_node, \
                                 is_input_base_node, is_output_base_node

        self._removed_comps = [c for c in self.comps if c not in excludes]

        # Add pseudoassys to graph
        dgraph.add_node(self.name,
                        pa_object=self,
                        comp=True,
                        pseudo='assembly',
                        valid=True)

        empty = {}
        for oldname, newname in self.renames.items():
            attrs = startgraph.node.get(oldname, empty).copy()
            if oldname in self.boundary_params:
                attrs['iotype'] = 'out'
            dgraph.add_node(newname, attr_dict=attrs)
            for u, v, data in dgraph.edges(oldname, data=True):
                dgraph.add_edge(newname, v, attr_dict=data.copy())
            for u, v, data in dgraph.in_edges(oldname, data=True):
                dgraph.add_edge(u, newname, attr_dict=data.copy())

            if is_subvar_node(dgraph, newname):
                dgraph.node[newname]['basevar'] = \
                    newname.split('[', 1)[0]
            if is_input_base_node(dgraph, newname):
                dgraph.add_edge(newname, self.name)
            elif is_output_base_node(dgraph, newname):
                dgraph.add_edge(self.name, newname)

    def clean_graph(self, startgraph, dgraph, excludes=()):
        """ Clean up the old nodes in the graph, leaving out ones that
        are excluded because they're used by ancestor drivers.
        """
        dgraph.remove_nodes_from(
            dgraph.find_prefixed_nodes([
                n for n in self._orig_group_nodes
                if n not in excludes and n in dgraph
            ]))

        # if this PA represents a driver, remove the corresponding driver
        # node
        if self.name[1:] in dgraph:
            dgraph.remove_nodes_from(
                dgraph.find_prefixed_nodes([self.name[1:]]))

        # update mapped inputs/outputs in the graph
        map_inputs = dgraph.graph['mapped_inputs']
        map_outputs = dgraph.graph['mapped_outputs']

        for i, varpath in enumerate(map_inputs):
            if isinstance(varpath, basestring):
                varpath = [varpath]

            mapped = []
            for path in varpath:
                compname, _, varname = path.partition('.')
                if varname and (compname in self.comps):
                    mapped.append(to_PA_var(path, self.name))
                else:
                    mapped.append(path)  # keep old value in that spot

            map_inputs[i] = tuple(mapped)

        # Add requested outputs
        for i, varpath in enumerate(map_outputs):
            compname, _, varname = varpath.partition('.')
            if varname and (compname in self.comps):
                map_outputs[i] = to_PA_var(varpath, self.name)
    def calc_derivatives(self, first=False, second=False, savebase=True,
                         required_inputs=None, required_outputs=None):
        """Calculate the derivatives for this non-differentiable block using
        Finite Difference."""
        # We don't do this in __init__ because some inputs and outputs
        # are added after creation (for nested driver support).
        if self.fd is None:
            from openmdao.main.derivatives import FiniteDifference
            self.fd = FiniteDifference(self)

        if hasattr(self.wflow, '_severed_edges'):
            self.wflow.sever_edges(self.wflow._severed_edges)

        try:
            # First, linearize about operating point.
            # Note: Only needed for differentiable islands, which are handled
            # with Fake Finite Difference.
            # Don't do this for full-model finite difference.
            if first and self.ffd_order > 0:
                for name in self.comps:

                    # TODO: I think the cache is blown away each time before
                    # this is called
                    if name in self.wflow._J_cache:
                        continue

                    comp = self.wflow.scope.get(name)

                    # Assemblies need some required inputs and outputs
                    # to calculate the Jacobians
                    if has_interface(comp, IAssembly):

                        # Need to know which assy bdry variables are
                        # required, and pass them in. Cache this once.
                        if name not in self.ffd_cache:
                            dgraph = self.wflow.scope._depgraph
                            inputs = [dgraph.base_var(inp)
                                       for inp in flatten_list_of_iters(self.inputs)]
                            outputs = [dgraph.base_var(outp)
                                       for outp in self.outputs]
                            from openmdao.main.depgraph import _get_inner_edges
                            edges = _get_inner_edges(dgraph, inputs, outputs)

                            req_inputs = []
                            req_outputs = []
                            for inp in inputs:
                                comp_str, _, var_str = inp.partition('.')
                                if comp_str == name:
                                    req_inputs.append(var_str)

                            for inp in outputs:
                                comp_str, _, var_str = inp.partition('.')
                                if comp_str == name:
                                    req_outputs.append(var_str)

                            for edge in edges:
                                src, target = edge
                                comp_str, _, var_str = src.partition('.')
                                if comp_str == name:
                                    req_outputs.append(var_str)
                                comp_str, _, var_str = target.partition('.')
                                if comp_str == name:
                                    req_inputs.append(var_str)

                            self.ffd_cache[name] = (req_inputs, req_outputs)

                        req_inputs, req_outputs = self.ffd_cache[name]

                        comp.calc_derivatives(first, second, savebase=True,
                                              required_inputs=req_inputs,
                                              required_outputs=req_outputs)

                    # Comp list contains full graph, so don't double up on
                    # the subdrivers.
                    elif not has_interface(comp, IDriver):
                        comp.calc_derivatives(first, second, True)

            self.J = self.fd.calculate()
        finally:
            if hasattr(self.wflow, '_severed_edges'):
                self.wflow.unsever_edges()

        return self.J