graph.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 '''
3 Do fun stuff to a connectivity graph
4 '''
5 
6 import networkx
7 from wirecell import units
8 
9 def nodes_by_type(G, typename):
10  '''
11  Return a list of all nodes in G of the given type.
12  '''
13  return [n for n in G.nodes if G.node[n]['type'] == typename]
14 
15 
16 def neighbors_by_type(G, seed, typename, radius=1):
17  '''
18  Return a set of all neighbor nodes withing given radius of seed
19  which are of the given typename.
20  '''
21  if radius == 1:
22  return set([n for n in networkx.neighbors(G, seed) if G.node[n]['type'] == typename])
23 
24  return set([n for n in networkx.ego_graph(G, seed, radius) if G.node[n]['type'] == typename])
25 
26 def neighbors_by_path(G, seed, typenamepath):
27  '''
28  Return all neighbor nodes by following a path of type names from
29  given seed.
30  '''
31  if not typenamepath:
32  return set()
33  nn = neighbors_by_type(G, seed, typenamepath[0])
34  for node in list(nn):
35  nnn = neighbors_by_path(G, node, typenamepath[1:])
36  nn.update(nnn)
37  return nn
38 
39 def child_by_path(G, seed, edgepath):
40  '''
41  Return a child by walking an "edgepath" from a seed node.
42 
43  The edgepath is a list of triples:
44 
45  >>> [(typename, attrname, attrval), ...]
46 
47  Where `typename` is the node type for the step in the path and
48  `attrname` and `attrval` give a name/value for an edge attribute
49  which must join current node with next node.
50  '''
51  if not edgepath:
52  return seed
53 
54  tn, an, av = edgepath.pop(0)
55 
56  for nn in neighbors_by_type(G, seed, tn):
57  try:
58  val = G[seed][nn][an]
59  except KeyError:
60  continue
61  if val != av:
62  continue
63  return child_by_path(G, nn, edgepath)
64  return
65 
66 
67 def parent(G, child, parent_type):
68  '''
69  Return parent node of given type
70  '''
71  for n in networkx.neighbors(G, child):
72  if G.node[n]['type'] == parent_type:
73  return n
74  return None
75 
76 
77 ### this assumes a particular hashing scheme.
78 # def channel_node(G, addrhash):
79 # '''
80 # Return channel node associated with address hash
81 # '''
82 # a = str(addrhash)
83 # iconn,islot,ichip,iaddr = [int(n)-1 for n in [a[0], a[1], a[2], a[3:5]]]
84 # edgepath = [
85 # ('wib', 'slot', islot),
86 # ('board', 'connector', iconn),
87 # ('chip', 'spot', ichip),
88 # ('channel', 'address', iaddr)
89 # ]
90 # # Fixme: should not hardcode a single APA name here!
91 # return child_by_path(G, 'apa', edgepath)
92 
93 
94 def flatten_to_conductor(G, channel_hash):
95  '''
96  Flatten the graph to the conductor level.
97 
98  The channel_hash is a callable like apa.channel_hash().
99  '''
100  ret = list()
101  apa = 'apa'
102 
103  spots_in_layer = [40,40,48]
104  boxes_in_face = 10
105 
106  for wib in neighbors_by_type(G, apa, 'wib'):
107  ind_slot = G[apa][wib]['slot']
108 
109  for board in neighbors_by_type(G, wib, 'board'):
110  face = parent(G, board, 'face')
111  ind_connector = G[wib][board]['connector']
112  ind_box = G[face][board]['spot']
113  ind_side = G[apa][face]['side']
114 
115  for chip in neighbors_by_type(G, board, 'chip'):
116  ind_chip = G[board][chip]['spot']
117 
118  for channel in neighbors_by_type(G, chip, 'channel'):
119  conductor = parent(G, channel, "conductor")
120 
121  ind_address = G[chip][channel]['address']
122  ind_layer = G[board][conductor]['layer']
123  ind_spot = G[board][conductor]['spot']
124 
125  wires = list(neighbors_by_type(G, conductor, 'wire'))
126  wires.sort(key = lambda w: G[conductor][w]['segment'])
127  nwires = len(wires)
128  wire0 = wires[0]
129  plane = parent(G, wire0, 'plane')
130  ind_wip = G[plane][wire0]['wip']
131  ind_plane = G[face][plane]['plane']
132  if ind_plane != ind_layer:
133  raise ValueError('Inconsistent graph, layer and plane differ')
134 
135  # wire attachment number along top of APA frame.
136  ind_wan = ind_box * spots_in_layer[ind_layer] + ind_spot
137  ind_wanglobal = ind_wan + spots_in_layer[ind_layer]*boxes_in_face*ind_side
138 
139  one = dict(channel = channel_hash(ind_connector, ind_slot, ind_chip, ind_address))
140  for k,v in locals().items():
141  if k.startswith('ind_'):
142  one[k[4:]] = v
143  ret.append(one)
144  return ret
145 
146 
147 
148 def to_celltree_wires(G, channel_ident, face='face0'):
149  '''
150  Return list of tuples: (ch, plane, wip, sx, sy, sz, ex, ey, ez)
151 
152  corresponding to rows in the "ChannelWireGeometry" file used by
153 
154  https://github.com/bnlif/wire-cell-celltree
155 
156  for the wires in the given face.
157 
158  Note: this only returns the one face of wires but channel numbers
159  are calculated with full knowledge of wrapped wires.
160  '''
161  ret = list()
162 
163  planes = list(neighbors_by_type(G, face, 'plane'))
164  planes.sort(key = lambda p : G[face][p]['plane'])
165  for plane in planes:
166  wires = list(neighbors_by_type(G, plane, 'wire'))
167  wires.sort(key = lambda w : G[plane][w]['wip'])
168  iplane = G[face][plane]['plane']
169  for wire in wires:
170  iwire = G[plane][wire]['wip']
171 
172  pts = list(neighbors_by_type(G, wire, 'point'))[:2]
173  head, tail = pts[0:2]
174  if G[wire][head]['endpoint'] == 1:
175  head, tail = pts[1], pts[0]
176  ecm = [r/units.cm for r in G.node[head]['pos']]
177  scm = [r/units.cm for r in G.node[tail]['pos']]
178 
179  chident = channel_ident(G, wire)
180  one = [chident, iplane, iwire] + scm + ecm
181  #print one
182  ret.append(tuple(one))
183  return ret
184 
185 
186 def to_schema(G, P, channel_ident):
187  '''
188  Return a wirecell.util.wires.schema store filled with information
189  from connection graph G starting from given face.
190  '''
191  # n.b. this is called from the CLI main.
192 
193  from . import schema
194  m = schema.maker()
195 
196  # fixme: currently only support one APA
197  iapa = 0
198  apa = P.apa
199 
200  face_indices = list()
201  for face in neighbors_by_type(G, apa, 'face'):
202  iface = G[apa][face]['side']
203 
204  sign = +1
205  if iface == 1: # "back" face
206  sign = -1
207 
208  planes = list(neighbors_by_type(G, face, 'plane'))
209  planes.sort(key = lambda p : G[face][p]['plane'])
210  plane_wires = [list() for _ in planes] # temp stash
211  for plane in planes:
212  wires = list(neighbors_by_type(G, plane, 'wire'))
213  wires.sort(key = lambda w : G[plane][w]['wip'])
214  iplane = G[face][plane]['plane']
215  for wire in wires:
216  wip = G[plane][wire]['wip']
217 
218  pts = list(neighbors_by_type(G, wire, 'point'))[:2]
219  head, tail = pts[0:2]
220  if G[wire][head]['endpoint'] == 1:
221  head, tail = pts[1], pts[0]
222  hpos = G.node[head]['pos']
223  tpos = G.node[tail]['pos']
224  h_id = m.make('point', sign*hpos.x, hpos.y, sign*hpos.z)
225  t_id = m.make('point', sign*tpos.x, tpos.y, sign*tpos.z)
226 
227  conductor = parent(G, wire, 'conductor')
228  segment = G[wire][conductor]['segment']
229  chident = channel_ident(G, wire)
230  wire_id = m.make('wire', wip, chident, segment, t_id, h_id)
231  plane_wires[iplane].append(wire_id)
232 
233  wire_plane_indices = list()
234  for iplane, wire_list in enumerate(plane_wires):
235  # note, this assumes an APA with a "portrait" aspect ratio
236  if iplane == 0:
237  wire_list.sort(key = lambda w: -1*m.wire_ypos(w))
238  elif iplane == 1:
239  wire_list.sort(key = m.wire_ypos)
240  else:
241  wire_list.sort(key = lambda w: sign*m.wire_zpos(w))
242  wpid = schema.wire_plane_id(iplane, iface, iapa)
243  index = m.make("plane", iplane, wire_list)
244  wire_plane_indices.append(index)
245  fi = m.make("face", iface, wire_plane_indices)
246  face_indices.append(fi)
247  m.make("anode", iapa, face_indices)
248  return m.schema()
249 
250 
251 def wires_in_plane(G, plane):
252  '''
253  Return set of wire nodes connected to the given plane node.
254  '''
255  return neighbors_by_type(G, plane, 'wire')
256 
257 def wires_in_chip(G, chip, intermediates=False):
258  '''
259  Return set of wire nodes connected to a chip node. If
260  intermediates is true, return the conductor and channel nodes that
261  form the connection to the wires.
262  '''
263  channels = neighbors_by_type(G, chip, 'channel')
264  conductors = set()
265  for ch in channels:
266  cs = neighbors_by_type(G, ch, 'conductor')
267  conductors.update(cs)
268 
269  wires = set()
270  for cond in conductors:
271  w = neighbors_by_type(G, cond, 'wire')
272  wires.update(w)
273 
274  if intermediates:
275  return channels | conductors | wires
276  return wires
277 
278 def wires_graph(G, wires):
279  '''
280  Return a new graph with wire endpoints as nodes and a dictionary of 2D points
281  '''
282  newG = networkx.DiGraph()
283  pos = dict()
284  for wire in wires:
285  pt1, pt2 = neighbors_by_type(G, wire, 'point')
286  if G[wire][pt1]['endpoint'] == 2:
287  pt1, pt2 = pt2, pt1
288  pos1 = G.node[pt1]['pos']
289  pos2 = G.node[pt2]['pos']
290  pos[pt1] = (pos1.z, pos1.y)
291  pos[pt2] = (pos2.z, pos2.y)
292  newG.add_edge(pt1, pt2)
293  return newG, pos
294 
295 def conductors_graph(G, conductors):
296  '''
297  Like wires graph but swap sign of the 2D "X" (3D "Z") coordinates
298  so a conductor zig-zags across a transparent frame.
299  '''
300  newG = networkx.DiGraph()
301  pos = dict()
302  for icond, cond in enumerate(conductors):
303  wires = neighbors_by_type(G, cond, 'wire')
304  for wire in wires:
305  seg = G[cond][wire]['segment']
306  sign = 1
307  style="solid"
308  if seg%2:
309  sign = -1
310  style="dashed"
311  pt1, pt2 = neighbors_by_type(G, wire, 'point')
312  if G[wire][pt1]['endpoint'] == 2:
313  pt1, pt2 = pt2, pt1
314  pos1 = G.node[pt1]['pos']
315  pos2 = G.node[pt2]['pos']
316  pos[pt1] = (sign*pos1.z, pos1.y)
317  pos[pt2] = (sign*pos2.z, pos2.y)
318  newG.add_edge(pt1, pt2, style=style, icolor=icond)
319 
320  return newG, pos
def nodes_by_type(G, typename)
Definition: graph.py:9
def wires_graph(G, wires)
Definition: graph.py:278
def neighbors_by_type(G, seed, typename, radius=1)
Definition: graph.py:16
def conductors_graph(G, conductors)
Definition: graph.py:295
def to_schema(G, P, channel_ident)
Definition: graph.py:186
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
def wires_in_plane(G, plane)
Definition: graph.py:251
def flatten_to_conductor(G, channel_hash)
this assumes a particular hashing scheme.
Definition: graph.py:94
def child_by_path(G, seed, edgepath)
Definition: graph.py:39
def neighbors_by_path(G, seed, typenamepath)
Definition: graph.py:26
def wires_in_chip(G, chip, intermediates=False)
Definition: graph.py:257
def to_celltree_wires(G, channel_ident, face='face0')
Definition: graph.py:148
def parent(G, child, parent_type)
Definition: graph.py:67
def channel_ident(G, wire)
Definition: apa.py:445
def channel_hash(iconn, islot, ichip, iaddr)
Definition: apa.py:429