def general_graph(self, timeline: Iterable[Optional[List[int]]], edges: Iterable[Iterable[int]], extra_nodes: Iterable[int] = tuple(), view: bool = False, fontsize: int = 20, fontcolor: str = 'black', penwidth: float = 2.2, first_color: str = 'yellow', first_style: str = 'filled', second_color: str = 'green', second_style: str = 'dotted,filled', third_color: str = 'red', graph_name: str = 'graph', file_basename: str = 'graph', do_sort_nodes: bool = True, do_adj_nodes: bool = True, var_name: str = '') -> None: """ Creates one graph emphasized for the given timeline. Parameters ---------- edges : Iterable of: {int, int} All edges between nodes in the graph. Should NOT contain self-edges! BOTH edges (x, y) and (y, x) could be in the edgelist. extra_nodes : Iterable of int Nodes that are probably not in the edges, but should be rendered. TIMELINE : Iterable of: None | [int...] None if no variables get highlighted in this step. Else the 'timeline' provides the set of variables that are in the bag(s) under consideration. This function computes all other variables that are involved in this timestep using the 'edgelist'. colors : Iterable of color Colors to use for the graph parts. Returns ------- None, but outputs the files with the graph for each timestep. """ _filename = self.outfolder / file_basename LOGGER.info("Generating general-graph for '%s'", file_basename) vartag_n: str = var_name + '%d' # sfdp http://yifanhu.net/SOFTWARE/SFDP/index.html default_engine = 'sfdp' graph = Graph(graph_name, strict=True, engine=default_engine, graph_attr={ 'fontsize': str(fontsize), 'overlap': 'false', 'outputorder': 'edgesfirst', 'K': '2' }, node_attr={ 'fontcolor': str(fontcolor), 'penwidth': str(penwidth), 'style': 'filled', 'fillcolor': 'white' }) if do_sort_nodes: bodybaselen = len(graph.body) # 1: layout with circo graph.engine = 'circo' # 2: nodes in edges+extra_nodes make a circle nodes = sorted([ vartag_n % n for n in set(itertools.chain(flatten(edges), extra_nodes)) ], key=lambda x: (len(x), x)) for i, node in enumerate(nodes): graph.edge(str(nodes[i - 1]), str(node)) # 3: reads in bytes! code_lines = graph.pipe('plain').splitlines() # 4: save the (sorted) positions assert code_lines[0].startswith(b'graph') node_positions = [ line.split()[1:4] for line in code_lines[1:] if line.startswith(b'node') ] # 5: cut layout graph.body = graph.body[:bodybaselen] for line in node_positions: graph.node(line[0].decode(), pos='%f,%f!' % (float(line[1]), float(line[2]))) # 6: Engine uses previous positions graph.engine = 'neato' for (src, tar) in edges: graph.edge(vartag_n % src, vartag_n % tar) for nodeid in extra_nodes: graph.node(vartag_n % nodeid) bodybaselen = len(graph.body) for i, variables in enumerate(timeline, start=1): # all timesteps # reset highlighting graph.body = graph.body[:bodybaselen] if variables is None: graph.render(view=view, format='svg', filename=str(_filename) + str(i)) continue for var in variables: graph.node(vartag_n % var, fillcolor=first_color, style=first_style) # highlight edges between variables for (s, t) in edges: if s in variables and t in variables: graph.edge(vartag_n % s, vartag_n % t, color=third_color, penwidth=str(penwidth)) if do_adj_nodes: # set.difference accepts list as argument, "-" does not. edges = [set(edge) for edge in edges] adjacent = { edge.difference(variables).pop() for edge in edges if len(edge.difference(variables)) == 1 } for var in adjacent: graph.node(vartag_n % var, color=second_color, style=second_style) graph.render(view=view, format='svg', filename=str(_filename) + str(i))
def incidence(self, timeline: Iterable[Optional[List[int]]], num_vars: int, colors: List, edges: List, inc_file: str = 'IncidenceGraphStep', view: bool = False, fontsize: Union[str, int] = 16, penwidth: float = 2.2, basefill: str = 'white', sndshape: str = 'diamond', neg_tail: str = 'odot', var_name_one: str = '', var_name_two: str = '', column_distance: float = 0.5) -> None: """ Creates the incidence graph emphasized for the given timeline. Parameters ---------- timeline : Iterable of: None | [int...] None if no variables get highlighted in this step. Else the 'timeline' provides the set of variables that are in the bag(s) under consideration. This function computes all other variables that are involved in this timestep using the 'edgelist'. num_vars : int Count of variables that are used in the clauses. colors : Iterable of color Colors to use for the graph parts. inc_file : string Basis for the file created, gets appended with the step number. view : bool If true opens the created file after creation. Default is false. basefill : color Background color of all nodes. Default is 'white'. sndshape : string Description of the shape for nodes with the variables. Default diamond. neg_tail : string Description of the shape of the edge-tail indicating a negated variable. Default is 'odot'. column_distance : float Changes the distance between both partitions, measured in image-units. Default is 0.5 Returns ------- None, but outputs the files with the graph for each timestep. """ _filename = self.outfolder / inc_file clausetag_n = var_name_one + '%d' vartag_n = var_name_two + '%d' g_incid = Graph(inc_file, strict=True, graph_attr={ 'splines': 'false', 'ranksep': '0.2', 'nodesep': str(float(column_distance)), 'fontsize': str(int(fontsize)), 'compound': 'true' }, edge_attr={ 'penwidth': str(float(penwidth)), 'dir': 'back', 'arrowtail': 'none' }) with g_incid.subgraph(name='cluster_clause', edge_attr={'style': 'invis'}, node_attr={ 'style': 'rounded,filled', 'fillcolor': basefill }) as clauses: clauses.attr(label='clauses') clauses.edges([(clausetag_n % (i + 1), clausetag_n % (i + 2)) for i in range(len(edges) - 1)]) g_incid.attr('node', shape=sndshape, penwidth=str(float(penwidth)), style='dotted') with g_incid.subgraph(name='cluster_ivar', edge_attr={'style': 'invis'}) as ivars: ivars.attr(label='variables') ivars.edges([(vartag_n % (i + 1), vartag_n % (i + 2)) for i in range(num_vars - 1)]) for i in range(num_vars): g_incid.node(vartag_n % (i + 1), vartag_n % (i + 1), color=colors[(i + 1) % len(colors)]) g_incid.attr('edge', constraint='false') for clause in edges: for var in clause[1]: if var >= 0: g_incid.edge(clausetag_n % clause[0], vartag_n % var, color=colors[var % len(colors)]) else: g_incid.edge(clausetag_n % clause[0], vartag_n % -var, color=colors[-var % len(colors)], arrowtail=neg_tail) # make edgelist variable-based (varX, clauseY), ... # var_cl_iter [(1, 1), (4, 1), ... var_cl_iter = tuple(flatten([[(x, y[0]) for x in y[1]] for y in edges])) bodybaselen = len(g_incid.body) for i, variables in enumerate(timeline, start=1): # all timesteps # reset highlighting g_incid.body = g_incid.body[:bodybaselen] if variables is None: g_incid.render(view=view, format='svg', filename=str(_filename) + str(i)) continue emp_clause = { var_cl[1] for var_cl in var_cl_iter if abs(var_cl[0]) in variables } emp_var = { abs(var_cl[0]) for var_cl in var_cl_iter if var_cl[1] in emp_clause } for var in emp_var: _vartag = vartag_n % abs(var) _style = 'solid,filled' if var in variables else 'dotted,filled' g_incid.node(_vartag, _vartag, style=_style, fillcolor='yellow') for clause in emp_clause: g_incid.node(clausetag_n % clause, clausetag_n % clause, fillcolor='yellow') for edge in var_cl_iter: (var, clause) = edge _style = 'solid' if clause in emp_clause else 'dotted' _vartag = vartag_n % abs(var) if var >= 0: g_incid.edge(clausetag_n % clause, _vartag, color=colors[var % len(colors)], style=_style) else: # negated variable g_incid.edge(clausetag_n % clause, _vartag, color=colors[-var % len(colors)], arrowtail='odot', style=_style) g_incid.render(view=view, format='svg', filename=str(_filename) + str(i))
def incidence(self, timeline, num_vars, colors, inc_file='IncidenceGraphStep', view=False, fontsize=16, penwidth=2.2, basefill='white', sndshape='diamond', neg_tail='odot', var_name_one='', var_name_two='', column_distance=0.5) -> None: """ Creates the incidence graph emphasized for the given timeline. Parameters ---------- TIMELINE : Iterable of: None | [int...] None if no variables get highlighted in this step. Else the 'timeline' provides the set of variables that are in the bag(s) under consideration. This function computes all other variables that are involved in this timestep using the 'edgelist'. num_vars : int Count of variables that are used in the clauses. colors : Iterable of color Colors to use for the graph parts. Returns ------- None, but outputs the files with the graph for each timestep. """ _filename = self.outfolder + inc_file + '%d' clausetag_n = var_name_one + '%d' vartag_n = var_name_two + '%d' g_incid = Graph(inc_file, strict=True, graph_attr={ 'splines': 'false', 'ranksep': '0.2', 'nodesep': str(column_distance), 'fontsize': str(int(fontsize)), 'compound': 'true' }, edge_attr={ 'penwidth': str(penwidth), 'dir': 'back', 'arrowtail': 'none' }) __incid = self.data.incidence_graph with g_incid.subgraph(name='cluster_clause', edge_attr={'style': 'invis'}, node_attr={ 'style': 'rounded,filled', 'fillcolor': basefill }) as clauses: clauses.attr(label='clauses') clauses.edges([(clausetag_n % (i + 1), clausetag_n % (i + 2)) for i in range(len(__incid.edges) - 1)]) g_incid.attr('node', shape=sndshape, fontcolor='black', penwidth=str(penwidth), style='dotted') with g_incid.subgraph(name='cluster_ivar', edge_attr={'style': 'invis'}) as ivars: ivars.attr(label='variables') ivars.edges([(vartag_n % (i + 1), vartag_n % (i + 2)) for i in range(num_vars - 1)]) for i in range(num_vars): g_incid.node(vartag_n % (i + 1), vartag_n % (i + 1), color=colors[(i + 1) % len(colors)]) g_incid.attr('edge', constraint='false') for clause in __incid.edges: for var in clause[1]: if var >= 0: g_incid.edge(clausetag_n % clause[0], vartag_n % var, color=colors[var % len(colors)]) else: g_incid.edge(clausetag_n % clause[0], vartag_n % -var, color=colors[-var % len(colors)], arrowtail=neg_tail) # make edgelist variable-based (varX, clauseY), ... # var_cl_iter [(1, 1), (4, 1), ... vcmapping = map(lambda y: map(lambda x: (x, y[0]), y[1]), __incid.edges) var_cl_iter = tuple(flatten(vcmapping)) bodybaselen = len(g_incid.body) for i, variables in enumerate(timeline, start=1): # all timesteps # reset highlighting g_incid.body = g_incid.body[:bodybaselen] if variables is None: g_incid.render(view=view, format='svg', filename=_filename % i) continue emp_clause = { var_cl[1] for var_cl in var_cl_iter if abs(var_cl[0]) in variables } emp_var = { abs(var_cl[0]) for var_cl in var_cl_iter if var_cl[1] in emp_clause } for var in emp_var: _vartag = vartag_n % abs(var) _style = 'solid,filled' if var in variables else 'dotted,filled' g_incid.node(_vartag, _vartag, style=_style, fillcolor='yellow') for clause in emp_clause: g_incid.node(clausetag_n % clause, clausetag_n % clause, fillcolor='yellow') for edge in var_cl_iter: (var, clause) = edge _style = 'solid' if clause in emp_clause else 'dotted' _vartag = vartag_n % abs(var) if var >= 0: g_incid.edge(clausetag_n % clause, _vartag, color=colors[var % len(colors)], style=_style) else: # negated variable g_incid.edge(clausetag_n % clause, _vartag, color=colors[-var % len(colors)], arrowtail='odot', style=_style) g_incid.render(view=view, format='svg', filename=_filename % i)