Module genice_core.topology
Arrange edges appropriately.
Expand source code
"""
Arrange edges appropriately.
"""
from logging import getLogger, DEBUG
import networkx as nx
import numpy as np
from typing import Union
import random
# Generate documents for these functions only.
# __all__ = []
def _extend_path(g: nx.Graph, path: list) -> list:
"""extend the path
Args:
g (nx.Graph): a linear or a simple cyclic graph.
path (list): A given path tho be extended
Returns:
list: the extended path or cycle
"""
while True:
last, head = path[-2:]
for next in g.neighbors(head):
if next != last:
break
else:
# no next node
return path
path.append(next)
if next == path[0]:
# is cyclic
return path
def _find_path(g: nx.Graph) -> list:
"""Find the path in g
Args:
g (nx.Graph): a linear or a simple cyclic graph.
Returns:
list: the path or cycle
"""
nodes = list(g.nodes())
head = nodes[0]
nei = [v for v in g.neighbors(head)]
if len(nei) == 0:
# lone node
return []
elif len(nei) == 1:
# an end node, fortunately.
return _extend_path(g, [head, nei[0]])
c0 = _extend_path(g, [head, nei[0]])
if c0[-1] != head:
# linear chain graph
# join the another side
c1 = _extend_path(g, [head, nei[1]])
return list(reversed(c0)) + c1[1:]
# cyclic graph
return c0
def _divide(divg: nx.Graph, nei: list, vertex: int, offset: int):
# fill by Nones if number of neighbors is less than 4
nei = (nei + [None, None, None, None])[:4]
# two neighbor nodes that are passed away to the new node
migrants = set(np.random.choice(nei, 2, replace=False)) - set([None])
# new node label
newVertex = vertex + offset
# assemble edges
for migrant in migrants:
divg.remove_edge(migrant, vertex)
divg.add_edge(newVertex, migrant)
def _divide_node(
divg: nx.Graph, vertex: int, offset: int, fixed: nx.DiGraph, numFixedEdges: int
):
nei = list(divg.neighbors(vertex))
fullDegree = len(nei) + numFixedEdges
# minimal assertion
assert fullDegree <= 4, f"degree {fullDegree} > 4"
# 一部の辺が固定されている場合、分割は自由に行えない。この部分の処理が非常にこみいったことになる。
if numFixedEdges == 0:
_divide(divg, nei, vertex, offset)
elif numFixedEdges == 1:
# 自由結合が3本: 1本が固定されているので、一本が分割後に固定されることになる。
# 本来なら前処理でそのような状況をなくしておかれるべき。
assert len(nei) != 3
# 前処理のあとで自由結合が2本の場合、第4のダングリング結合が固定結合をキャンセルしているはずなので、分割不要。
# 自由結合が1の場合はそもそも分割不要。
elif numFixedEdges == 2:
# 2本の固定結合が1i1oの場合のみ、残る2本は自由に向きを決められる。
# 2i/2oの場合には、前処理で残る2本も固定されていなければならないので、ここにはこない。
pass
# 残りは割らなくていい。
elif numFixedEdges == 3:
# 残る1本は自動的に向きが定まるので前処理で排除されていなければならない。
# 可能なのは3結合で3本固定の場合のみ。
assert len(nei) == 0
def noodlize(g: nx.Graph, fixed: Union[nx.DiGraph, None] = nx.DiGraph()) -> nx.Graph:
"""Divide each vertex of the graph and make a set of paths.
A new algorithm suggested by Prof. Sakuma, Yamagata University.
Args:
g (nx.Graph): An ice-like undirected graph. All vertices must not be >4-degree.
fixed (Union[nx.DiGraph,None], optional): Specifies the edges whose direction is fixed.. Defaults to None.
Returns:
nx.Graph: A graph mode of chains and cycles.
"""
logger = getLogger()
fixg = nx.Graph(fixed) # undirected copy
offset = len(g)
# divided graph
divg = nx.Graph(g)
for edge in fixed.edges():
divg.remove_edge(*edge)
for v in g:
if fixg.has_node(v):
nfixed = fixg.degree[v]
else:
nfixed = 0
_divide_node(divg, v, offset, fixed, nfixed)
# divg is made of chains and cycles.
# divg does not contain the edges in fixed.
return divg
def _decompose_complex_path(path: list):
"""A generator that divides a complex path with self-crossings to set of simple cycles and paths.
Args:
path (list): A complex path
Yields:
list: a short and simple path/cycle
"""
logger = getLogger()
if len(path) == 0:
return
logger.debug(f"decomposing {path}...")
order = dict()
order[path[0]] = 0
store = [path[0]]
headp = 1
while headp < len(path):
node = path[headp]
if node in order:
# it is a cycle!
size = len(order) - order[node]
cycle = store[-size:] + [node]
yield cycle
# remove them from the order[]
for v in cycle[1:]:
del order[v]
# truncate the store
store = store[:-size]
order[node] = len(order)
store.append(node)
headp += 1
logger.debug([order, store])
if len(store) > 1:
yield store
logger.debug(f"Done decomposition.")
def split_into_simple_paths(
nnode: int,
divg: nx.Graph,
):
"""Set the orientations to the components.
Args:
nnode (int): number of nodes in the original graph.
divg (nx.Graph): the divided graph.
Yields:
list: a short and simple path/cycle
"""
for c in nx.connected_components(divg):
# a component of c is either a chain or a cycle.
subg = divg.subgraph(c)
nn = len(subg)
ne = len([e for e in subg.edges()])
assert nn == ne or nn == ne + 1
# Find a simple path in the doubled graph
# It must be a simple path or a simple cycle.
path = _find_path(subg)
# Flatten then path. It may make the path self-crossing.
path = [v % nnode for v in path]
# Divide a long path into simple paths and cycles.
yield from _decompose_complex_path(path)
# def _decorate(g: nx.Graph):
# """Add virtual nodes to make all nodes 4-connected.
# Args:
# g (nx.Graph): _description_
# """
# nodes = list(g)
# for i in nodes:
# room = 4 - len(g[i])
# for j in range(room):
# g.add_edge(i, -j - 1)
def _remove_dummy_nodes(g: Union[nx.Graph, nx.DiGraph]):
for i in range(-1, -5, -1):
if g.has_node(i):
g.remove_node(i)
def balance(fixed: nx.DiGraph, g: nx.Graph, hook=None):
"""Extend the prefixed digraph to make the remaining graph balanced.
Args:
fixed (nx.DiGraph): fixed edges
g (nx.Graph): skeletal graph
"""
# prepare the perimeter
perimeter = [
node
for node in fixed
if fixed.in_degree[node] + fixed.out_degree[node] < g.degree[node]
]
while len(perimeter) > 0:
node = perimeter.pop(0)
if node < 0:
continue
if hook is not None:
hook(fixed)
# fill if degree is less than 4
neighborNodes = list(g[node]) + [-1, -2, -3, -4]
neighborNodes = neighborNodes[:4]
while fixed.in_degree(node) > fixed.out_degree(node):
next = random.choice(neighborNodes)
if not (fixed.has_edge(node, next) or fixed.has_edge(next, node)):
fixed.add_edge(node, next)
perimeter.append(next)
while fixed.in_degree(node) < fixed.out_degree(node):
next = random.choice(neighborNodes)
if not (fixed.has_edge(node, next) or fixed.has_edge(next, node)):
fixed.add_edge(next, node)
perimeter.append(next)
_remove_dummy_nodes(fixed)
Functions
def balance(fixed: networkx.classes.digraph.DiGraph, g: networkx.classes.graph.Graph, hook=None)
-
Extend the prefixed digraph to make the remaining graph balanced.
Args
fixed
:nx.DiGraph
- fixed edges
g
:nx.Graph
- skeletal graph
Expand source code
def balance(fixed: nx.DiGraph, g: nx.Graph, hook=None): """Extend the prefixed digraph to make the remaining graph balanced. Args: fixed (nx.DiGraph): fixed edges g (nx.Graph): skeletal graph """ # prepare the perimeter perimeter = [ node for node in fixed if fixed.in_degree[node] + fixed.out_degree[node] < g.degree[node] ] while len(perimeter) > 0: node = perimeter.pop(0) if node < 0: continue if hook is not None: hook(fixed) # fill if degree is less than 4 neighborNodes = list(g[node]) + [-1, -2, -3, -4] neighborNodes = neighborNodes[:4] while fixed.in_degree(node) > fixed.out_degree(node): next = random.choice(neighborNodes) if not (fixed.has_edge(node, next) or fixed.has_edge(next, node)): fixed.add_edge(node, next) perimeter.append(next) while fixed.in_degree(node) < fixed.out_degree(node): next = random.choice(neighborNodes) if not (fixed.has_edge(node, next) or fixed.has_edge(next, node)): fixed.add_edge(next, node) perimeter.append(next) _remove_dummy_nodes(fixed)
def noodlize(g: networkx.classes.graph.Graph, fixed: Optional[networkx.classes.digraph.DiGraph] = <networkx.classes.digraph.DiGraph object>) ‑> networkx.classes.graph.Graph
-
Divide each vertex of the graph and make a set of paths.
A new algorithm suggested by Prof. Sakuma, Yamagata University.
Args
g
:nx.Graph
- An ice-like undirected graph. All vertices must not be >4-degree.
fixed
:Union[nx.DiGraph,None]
, optional- Specifies the edges whose direction is fixed.. Defaults to None.
Returns
nx.Graph
- A graph mode of chains and cycles.
Expand source code
def noodlize(g: nx.Graph, fixed: Union[nx.DiGraph, None] = nx.DiGraph()) -> nx.Graph: """Divide each vertex of the graph and make a set of paths. A new algorithm suggested by Prof. Sakuma, Yamagata University. Args: g (nx.Graph): An ice-like undirected graph. All vertices must not be >4-degree. fixed (Union[nx.DiGraph,None], optional): Specifies the edges whose direction is fixed.. Defaults to None. Returns: nx.Graph: A graph mode of chains and cycles. """ logger = getLogger() fixg = nx.Graph(fixed) # undirected copy offset = len(g) # divided graph divg = nx.Graph(g) for edge in fixed.edges(): divg.remove_edge(*edge) for v in g: if fixg.has_node(v): nfixed = fixg.degree[v] else: nfixed = 0 _divide_node(divg, v, offset, fixed, nfixed) # divg is made of chains and cycles. # divg does not contain the edges in fixed. return divg
def split_into_simple_paths(nnode: int, divg: networkx.classes.graph.Graph)
-
Set the orientations to the components.
Args
nnode
:int
- number of nodes in the original graph.
divg
:nx.Graph
- the divided graph.
Yields
list
- a short and simple path/cycle
Expand source code
def split_into_simple_paths( nnode: int, divg: nx.Graph, ): """Set the orientations to the components. Args: nnode (int): number of nodes in the original graph. divg (nx.Graph): the divided graph. Yields: list: a short and simple path/cycle """ for c in nx.connected_components(divg): # a component of c is either a chain or a cycle. subg = divg.subgraph(c) nn = len(subg) ne = len([e for e in subg.edges()]) assert nn == ne or nn == ne + 1 # Find a simple path in the doubled graph # It must be a simple path or a simple cycle. path = _find_path(subg) # Flatten then path. It may make the path self-crossing. path = [v % nnode for v in path] # Divide a long path into simple paths and cycles. yield from _decompose_complex_path(path)