Source code for openmdao.core.relevance

""" Relevance object for systems. Manages the data connectivity graph."""

from __future__ import print_function

from collections import OrderedDict
import json
from six import string_types, itervalues, iteritems

import networkx as nx


[docs]class Relevance(object): """ Object that manages the data connectivity graph for systems.""" def __init__(self, group, params_dict, unknowns_dict, connections, inputs, outputs, mode): self.params_dict = params_dict self.unknowns_dict = unknowns_dict self.mode = mode param_groups = {} output_groups = {} g_id = 0 # turn all inputs and outputs, even singletons, into tuples self.inputs = [] for inp in inputs: if isinstance(inp, string_types): inp = (inp,) if len(inp) == 1: param_groups.setdefault(None, []).append(inp[0]) else: param_groups[g_id] = tuple(inp) g_id += 1 self.inputs.append(tuple(inp)) self.outputs = [] for out in outputs: if isinstance(out, string_types): out = (out,) if len(out) == 1: output_groups.setdefault(None, []).append(out) else: output_groups[g_id] = tuple(out) g_id += 1 self.outputs.append(out) self._vgraph, self._sgraph = self._setup_graphs(group, connections) self.relevant = self._get_relevant_vars(self._vgraph) if mode == 'fwd': self.groups = param_groups else: self.groups = output_groups def __getitem__(self, name): # if name is None, everything is relevant if name is None: return set(self._vgraph.nodes_iter()) elif name in self.relevant: return self.relevant[name] return ()
[docs] def is_relevant(self, var_of_interest, varname): """ Returns True if a variable is relevant to a particular variable of interest. Args ---- var_of_interest : str Name of a variable of interest (either a parameter or a constraint or objective output, depending on mode.) varname : str Name of some other variable in the model. Returns ------- bool: True if varname is in the relevant path of var_of_interest """ if var_of_interest is None: return True return varname in self.relevant[var_of_interest]
[docs] def vars_of_interest(self, mode=None): """ Determines our list of var_of_interest depending on mode. Args ---- mode : string Derivative mode, can be 'fwd' or 'rev'. Returns ------- list : Our inputs, or output, or both, depending on mode. """ if mode is None: mode = self.mode if mode == 'fwd': return self.inputs elif mode == 'rev': return self.outputs else: return self.inputs+self.outputs
def _setup_graphs(self, group, connections): """ Set up dependency graphs for variables and components in the Problem. Returns ------- tuple of (nx.DiGraph, nx.DiGraph) The variable graph and the component graph. """ params_dict = self.params_dict unknowns_dict = self.unknowns_dict vgraph = nx.DiGraph() # var graph sgraph = nx.DiGraph() # subsystem graph compins = {} # maps input vars to components compouts = {} # maps output vars to components promote_map = {} # ensure we have system graph nodes even for unconnected subsystems sgraph.add_nodes_from([s.pathname for s in group.subsystems(recurse=True)]) for target, source in iteritems(connections): vgraph.add_edge(source, target) sgraph.add_edge(source.rsplit('.', 1)[0], target.rsplit('.', 1)[0]) for meta in itervalues(params_dict): param = meta['pathname'] tcomp = param.rsplit('.', 1)[0] compins.setdefault(tcomp, []).append(param) if meta['promoted_name'] != param:# and param in connections: promote_map[param] = meta['promoted_name'] if param not in vgraph: vgraph.add_node(param) for meta in itervalues(unknowns_dict): unknown = meta['pathname'] scomp = unknown.rsplit('.', 1)[0] compouts.setdefault(scomp, []).append(unknown) if meta['promoted_name'] != unknown: promote_map[unknown] = meta['promoted_name'] if unknown not in vgraph: vgraph.add_node(unknown) # connect inputs to outputs on same component in order to fully # connect the variable graph. for comp, inputs in iteritems(compins): for inp in inputs: for out in compouts.get(comp, ()): vgraph.add_edge(inp, out) # now collapse any var nodes with implicit connections nx.relabel_nodes(vgraph, promote_map, copy=False) # remove any self edges created by the relabeling for u, v in vgraph.edges(): if u == v: vgraph.remove_edge(u, v) return vgraph, sgraph def _get_relevant_vars(self, g): """ Args ---- g : nx.DiGraph A graph of variable dependencies. Returns ------- dict Dictionary that maps a variable name to all other variables in the graph that are relevant to it. """ succs = {} for nodes in self.inputs: for node in nodes: succs[node] = set([v for u, v in nx.dfs_edges(g, node)]) succs[node].add(node) relevant = {} grev = g.reverse() for nodes in self.outputs: for node in nodes: relevant[node] = set() preds = set([v for u, v in nx.dfs_edges(grev, node)]) preds.add(node) for inps in self.inputs: for inp in inps: common = preds.intersection(succs[inp]) relevant[node].update(common) relevant.setdefault(inp, set()).update(common) return relevant
[docs] def json_dependencies(self): """ Returns a json representation of a model's data dependency graph. Returns ------- A json string with a dependency matrix and a list of variable name labels. """ idxs = OrderedDict() matrix = [] size = len(self._vgraph) for i, node in enumerate(self._vgraph.nodes_iter()): idxs[node] = i matrix.append([0]*size) for u, v in self._vgraph.edges_iter(): matrix[idxs[u]][idxs[v]] = 1 dct = { 'dependencies': { 'matrix' : matrix, 'labels' : self._vgraph.nodes() } } return json.dumps(dct)