def get_inner_edges(graph, srcs, dests, wflow):

    graph = graph.subgraph(graph.nodes())

    # add @in and @out nodes, rewire input srcs, etc.
    mod_for_derivs(graph, srcs, dests, wflow)

    # sort edges by src so that basevars occur before subvars
    edges = sorted(graph.list_connections(), key=lambda e: e[0])
    return edges_to_dict(edges)
def get_inner_edges(graph, srcs, dests, wflow):
    graph = graph.subgraph(graph.nodes())

    # add @in and @out nodes, rewire input srcs, etc.
    graph = mod_for_derivs(graph, srcs, dests, wflow)

    # sort edges by src so that basevars occur before subvars
    edges = sorted(graph.list_connections(), key=lambda e: e[0])
    return edges_to_dict(edges)
    def derivative_graph(self,
                         inputs=None,
                         outputs=None,
                         fd=False,
                         severed=None,
                         group_nondif=True):
        """Returns the local graph that we use for derivatives.

        inputs: list of strings or tuples of strings
            List of input variables that we are taking derivatives with respect
            to. They must be within this workflow's scope. If no inputs are
            given, the parent driver's parameters are used. A tuple can be used
            to link inputs together.

        outputs: list of strings
            List of output variables that we are taking derivatives of.
            They must be within this workflow's scope. If no outputs are
            given, the parent driver's objectives and constraints are used.

        fd: boolean
            set to True to finite difference the whole model together with
            fake finite difference turned off. This is mainly for checking
            your model's analytic derivatives.

        severed: list
            If a workflow has a cylic connection, some edges must be severed.
            When a cyclic workflow calls this function, it passes a list of
            edges so that they can be severed prior to the topological sort.

        group_nondif: bool
            If True, collapse parts of the graph into PseudoAssemblies when
            necessary.
        """

        if self._derivative_graph is None or group_nondif is False:
            # when we call with group_nondif = False, we want the union of the
            # passed inputs/outputs plus the inputs/outputs from the solver
            if group_nondif is False:
                tmp_inputs = [] if inputs is None else inputs
                tmp_outputs = [] if outputs is None else outputs
                inputs = None
                outputs = None

            # If inputs aren't specified, use the parameters
            parent_deriv_vars = list_deriv_vars(self._parent.parent)
            if inputs is None:
                if hasattr(self._parent, 'list_param_group_targets'):
                    inputs = self._parent.list_param_group_targets()
                elif parent_deriv_vars[0]:
                    inputs = parent_deriv_vars[0]
                else:
                    msg = "No inputs given for derivatives."
                    self.scope.raise_exception(msg, RuntimeError)

            if group_nondif is False:
                inputs = list(set(tmp_inputs).union(inputs))

            # If outputs aren't specified, use the objectives and constraints
            if outputs is None:
                outputs = []
                if hasattr(self._parent, 'get_objectives'):
                    outputs.extend([
                        "%s.out0" % item.pcomp_name
                        for item in self._parent.get_objectives().values()
                    ])
                if hasattr(self._parent, 'get_constraints'):
                    outputs.extend([
                        "%s.out0" % item.pcomp_name
                        for item in self._parent.get_constraints().values()
                    ])

            if group_nondif is False:
                outputs = list(set(tmp_outputs).union(outputs))

            if len(outputs) == 0:
                if parent_deriv_vars[1]:
                    outputs = parent_deriv_vars[1]
                else:
                    msg = "No outputs given for derivatives."
                    self.scope.raise_exception(msg, RuntimeError)

            graph = self.scope._depgraph

            # make a copy of the graph because it will be
            # modified by mod_for_derivs
            dgraph = graph.subgraph(graph.nodes())
            dgraph = mod_for_derivs(dgraph, inputs, outputs, self, fd)

            if group_nondif:
                self._derivative_graph = dgraph
                self._group_nondifferentiables(fd, severed)
            else:
                # we're being called to determine the deriv graph
                # for a subsolver, so get rid of @in and @out nodes
                dgraph.remove_nodes_from(
                    ['@in%d' % i for i in range(len(inputs))])
                dgraph.remove_nodes_from(
                    ['@out%d' % i for i in range(len(outputs))])
                dgraph.graph['inputs'] = inputs[:]
                dgraph.graph['outputs'] = outputs[:]
                return dgraph

        return self._derivative_graph
    def derivative_graph(self, inputs=None, outputs=None, fd=False,
                         severed=None, group_nondif=True):
        """Returns the local graph that we use for derivatives.

        inputs: list of strings or tuples of strings
            List of input variables that we are taking derivatives with respect
            to. They must be within this workflow's scope. If no inputs are
            given, the parent driver's parameters are used. A tuple can be used
            to link inputs together.

        outputs: list of strings
            List of output variables that we are taking derivatives of.
            They must be within this workflow's scope. If no outputs are
            given, the parent driver's objectives and constraints are used.

        fd: boolean
            set to True to finite difference the whole model together with
            fake finite difference turned off. This is mainly for checking
            your model's analytic derivatives.

        severed: list
            If a workflow has a cylic connection, some edges must be severed.
            When a cyclic workflow calls this function, it passes a list of
            edges so that they can be severed prior to the topological sort.

        group_nondif: bool
            If True, collapse parts of the graph into PseudoAssemblies when
            necessary.
        """

        if self._derivative_graph is None or group_nondif is False:
            # when we call with group_nondif = False, we want the union of the
            # passed inputs/outputs plus the inputs/outputs from the solver
            if group_nondif is False:
                tmp_inputs = [] if inputs is None else inputs
                tmp_outputs = [] if outputs is None else outputs
                inputs = None
                outputs = None

            # If inputs aren't specified, use the parameters
            if inputs is None:
                if hasattr(self._parent, 'list_param_group_targets'):
                    inputs = self._parent.list_param_group_targets()
                else:
                    msg = "No inputs given for derivatives."
                    self.scope.raise_exception(msg, RuntimeError)

            if group_nondif is False:
                inputs = list(set(tmp_inputs).union(inputs))

            # If outputs aren't specified, use the objectives and constraints
            if outputs is None:
                outputs = []
                if hasattr(self._parent, 'get_objectives'):
                    outputs.extend(["%s.out0" % item.pcomp_name for item in
                                    self._parent.get_objectives().values()])
                if hasattr(self._parent, 'get_constraints'):
                    outputs.extend(["%s.out0" % item.pcomp_name for item in
                                    self._parent.get_constraints().values()])

            if group_nondif is False:
                outputs = list(set(tmp_outputs).union(outputs))

            if len(outputs) == 0:
                msg = "No outputs given for derivatives."
                self.scope.raise_exception(msg, RuntimeError)

            graph = self.scope._depgraph

            # make a copy of the graph because it will be
            # modified by mod_for_derivs
            dgraph = graph.subgraph(graph.nodes())
            mod_for_derivs(dgraph, inputs, outputs, self, fd)

            if group_nondif:
                self._derivative_graph = dgraph
                self._group_nondifferentiables(fd, severed)
            else:
                # we're being called to determine the deriv graph
                # for a subsolver, so get rid of @in and @out nodes
                dgraph.remove_nodes_from(['@in%d' % i for i in range(len(inputs))])
                dgraph.remove_nodes_from(['@out%d' % i for i in range(len(outputs))])
                dgraph.graph['inputs'] = inputs[:]
                dgraph.graph['outputs'] = outputs[:]
                return dgraph

        return self._derivative_graph