generator.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 '''
3 Wires and Channels
4 '''
5 from . import schema
6 from wirecell import units
7 
8 import math
9 from collections import namedtuple
10 
11 import numpy
12 
13 # Wire = namedtuple("Wire", "index Chan w1 h1 w2 h2")
14 
15 class Point(object):
16  def __init__(self, *coords):
17  self._coords = list(coords)
18 
19  def __str__(self):
20  s = ",".join([str(a) for a in self._coords])
21  return "Point(%s)" % s
22 
23  def __repr__(self):
24  return str(self)
25 
26  @property
27  def x(self):
28  return self[0]
29  @x.setter
30  def x(self, val):
31  self[0] = val
32 
33  @property
34  def y(self):
35  return self[1]
36  @y.setter
37  def y(self, val):
38  self[1] = val
39 
40  def __len__(self):
41  return len(self._coords)
42  def __getitem__(self, key):
43  return self._coords[key]
44  def __setitem__(self, key, val):
45  self._coords[key] = val
46  def __iter__(self):
47  return self._coords.__iter__()
48 
49  def __abs__(self):
50  return Point(*[abs(a) for a in self])
51 
52  def __sub__(self, other):
53  try:
54  return Point(*[(a-b) for a,b in zip(self, other)])
55  except TypeError:
56  return Point(*[(a-other) for a in self])
57 
58  def __add__(self, other):
59  try:
60  return Point(*[(a+b) for a,b in zip(self, other)])
61  except TypeError:
62  return Point(*[(a+other) for a in self])
63 
64  def __mul__(self, other):
65  try:
66  return Point(*[(a*b) for a,b in zip(self, other)])
67  except TypeError:
68  return Point(*[(a*other) for a in self])
69 
70  def __div__(self, other):
71  try:
72  return Point(*[(a/b) for a,b in zip(self, other)])
73  except TypeError:
74  return Point(*[(a/other) for a in self])
75  __truediv__ = __div__
76 
77  def dot(self, other):
78  return sum([a*b for a,b in zip(self, other)])
79 
80  @property
81  def magnitude(self):
82  return math.sqrt(self.dot(self))
83 
84  @property
85  def unit(self):
86  mag = self.magnitude
87  return self/mag
88 
89 class Ray(object):
90  def __init__(self, tail, head):
91  self.tail = tail
92  self.head = head
93 
94  def __str__(self):
95  return "%s -> %s" % (self.tail, self.head)
96 
97  def __repr__(self):
98  return str(self)
99 
100  @property
101  def vector(self):
102  return self.head - self.tail
103 
104  @property
105  def unit(self):
106  return self.vector.unit
107 
108 
109 class Rectangle(object):
110  def __init__(self, width, height, center = Point(0.0, 0.0)):
111  self.width = width
112  self.height = height
113  self.center = center
114 
115  @property
116  def ll(self):
117  return Point(self.center.x - 0.5*self.width,
118  self.center.y - 0.5*self.height);
119 
120  def relative(self, point):
121  return point - self.center
122 
123  def inside(self, point):
124  r = self.relative(point)
125  return abs(r.x) <= 0.5*self.width and abs(r.y) <= 0.5*self.height
126 
127  def toedge(self, point, direction):
128  '''
129  Return a vector that takes point along direction to the nearest edge.
130  '''
131  p1 = self.relative(point)
132  d1 = direction.unit
133 
134  #print "toedge: p1:%s d1:%s" % (p1, d1)
135 
136  corn = Point(0.5*self.width, 0.5*self.height)
137 
138  xdir = d1.dot((1.0, 0.0)) # cos(theta_x)
139  if xdir == 0:
140  tx = None
141  else:
142  xsign = xdir/abs(xdir)
143  dx = xsign*corn.x - p1.x
144  tx = dx/d1.x
145 
146  ydir = d1.dot((0.0, 1.0)) # cos(theta_y)
147  if ydir == 0:
148  ty = None
149  else:
150  ysign = ydir/abs(ydir)
151  dy = ysign*corn.y - p1.y
152  ty = dy/d1.y
153 
154 
155  if ty is None:
156  return d1*tx
157  if tx is None:
158  return d1*ty
159 
160  if tx < ty: # closer to vertical side
161  return d1 * tx
162  return d1 * ty
163 
164 
165 
166 def wrap_one(start_ray, rect):
167  '''
168  Return wire end points by wrapping around a rectangle.
169  '''
170  p = rect.relative(start_ray.tail)
171  d = start_ray.unit
172  ret = [p]
173  while True:
174  #print "loop: p:%s d:%s" %(p,d)
175  jump = rect.toedge(p, d)
176  p = p + jump
177  ret.append(p)
178  if p.y <= -0.5*rect.height:
179  break
180  d.x = -1.0*d.x # swap direction
181  return ret
182 
183 
184 
185 
186 
187 
188 def wrapped_from_top(offset, angle, pitch, rect):
189  '''
190  Wrap a rectangle with a plane of wires starting along the top of
191  the given rectangle and starting at given offset from upper-left
192  corner of the rectangle and with angle measured from the vertical
193  to the wire direction. Positive angle means the wire starts going
194  down-left from the top of the rectangle.
195 
196  Return list of "wires" (wire segments) as tuple:
197 
198  - return :: (along_pitch, side, channel, seg, p1, p2)
199 
200  - channel :: counts the attachment point at the top of the
201  rectangle from left to right starting from 0
202 
203  - side :: identify which side the wire is on, (this value is
204  redundant with "seg").
205 
206  - seg :: the segment number, ie, how many times the wire's
207  conductor has wrapped around the rectangle.
208 
209  - p1 and p2 :: end points of wire assuming the original
210  rectangle is centered on the origin.
211  '''
212 
213  cang = math.cos(angle)
214  sang = math.sin(angle)
215  direc = Point(-sang, -cang)
216  pitchv = Point(cang, -sang)
217 
218  start = Point(-0.5*rect.width + offset, 0.5*rect.height) + rect.center
219 
220  step = pitch / cang
221  stop = rect.center.x + 0.5*rect.width
222 
223  #print -0.5*rect.width, start.x, step, stop
224 
225  wires = list()
226 
227  channel = 0
228  while True:
229  points = wrap_one(Ray(start, start+direc), rect)
230  side = 1
231  for seg, (p1, p2) in enumerate(zip(points[:-1], points[1:])):
232  wcenter = (p1+p2)*0.5 - rect.center
233  along_pitch = pitchv.dot(wcenter)
234  w = (along_pitch, side, channel, seg, p1, p2)
235  wires.append(w)
236  side *= -1
237  start.x += step
238  if start.x >= stop:
239  break
240  channel += 1
241  return wires
242 
243 def wrapped_from_top_oneside(offset, angle, pitch, rect):
244  '''
245  Wrap a rectangle with a plane of wires starting along the top of
246  the given rectangle and starting at given offset from upper-left
247  corner of the rectangle and with angle measured from the vertical
248  to the wire direction. Positive angle means the wire starts going
249  down-left from the top of the rectangle.
250 
251  Return list of "wires" (wire segments) as tuple:
252 
253  - return :: (along_pitch, side, channel, seg, p1, p2)
254 
255  - channel :: counts the attachment point at the top of the
256  rectangle from left to right starting from 0
257 
258  - side :: identify which side the wire is on, (this value is
259  redundant with "seg").
260 
261  - seg :: the segment number, ie, how many times the wire's
262  conductor has wrapped around the rectangle.
263 
264  - p1 and p2 :: end points of wire assuming the original
265  rectangle is centered on the origin.
266  '''
267 
268  cang = math.cos(angle)
269  sang = math.sin(angle)
270  direc = Point(-sang, -cang)
271  pitchv = Point(cang, -sang)
272 
273  start = Point(-0.5*rect.width + offset, 0.5*rect.height) + rect.center
274 
275  step = pitch / cang
276  stop = rect.center.x + 0.5*rect.width
277 
278  #print -0.5*rect.width, start.x, step, stop
279 
280  def swapx(p):
281  return Point(2*rect.center.x - p.x, p.y)
282 
283  wires = list()
284 
285  channel = 0
286  while True:
287  points = wrap_one(Ray(start, start+direc), rect)
288  side = 1
289  for seg, (p1, p2) in enumerate(zip(points[:-1], points[1:])):
290  if side < 0:
291  p1 = swapx(p1)
292  p2 = swapx(p2)
293  wcenter = (p1+p2)*0.5 - rect.center
294  along_pitch = pitchv.dot(wcenter)
295 
296  # The same wire can serve both both faces if the
297  # coordinate system of each face is related by a rotation
298  # of the plane along the x axis.
299  w = (along_pitch, side, channel, seg, p1, p2)
300 
301  wires.append(w)
302 
303  side *= -1 # for next time
304  start.x += step
305  if start.x >= stop:
306  break
307  channel += 1
308  return wires
309 
310 
311 
312 # https://www-microboone.fnal.gov/publications/TDRCD3.pdf
313 microboone_params = dict(
314  # drift is 2.5604*units.meter
315  width = 10.368*units.meter, # in Z
316  height = 2.325*units.meter, # in Y
317  pitches = [3*units.mm, 3*units.mm, 3*units.mm ],
318  # guess at left/right ambiguity
319  angles = [+60*units.deg, -60*units.deg, 0.0],
320  #
321  offsets = [0.0*units.mm, 0.0*units.mm, 0.0*units.mm],
322  # fixme: this is surely wrong
323  planex = [9*units.mm, 6*units.mm, 3*units.mm],
324  maxchanperplane = 3000,
325 )
326 
327 protodune_params = dict(
328  width = 2295*units.mm,
329  height = 5920*units.mm,
330  pitches = [4.669*units.mm, 4.669*units.mm, 4.790*units.mm ],
331  # guess at left/right ambiguity
332  angles = [+35.707*units.deg, -35.707*units.deg, 0.0],
333  # guess based on symmetry and above numbers
334  offsets = [0.3923*units.mm, 0.3923*units.mm, 0.295*units.mm],
335  # fixme: this is surely wrong
336  planex = [15*units.mm, 10*units.mm, 5*units.mm],
337  maxchanperplane = 1000,
338  )
339 
340 
341 def onesided_wrapped(params = protodune_params):
342  '''
343  Generate a schema.store of wires for a onesided but wrapped face.
344 
345  This does not populate electronics.
346  '''
347  rect = Rectangle(params['width'], params['height'])
348 
349  store = schema.maker()
350 
351  apa = 0
352 
353  # temporary per-plane lists of wires to allow sorting before tuplizing.
354  planes = [list(), list(), list()]
355  iface = 0
356 
357  planex = params['planex']
358 
359  mcpp = params['maxchanperplane']
360 
361  for iplane in range(3):
362  wires = wrapped_from_top(params['offsets'][iplane],
363  params['angles'][iplane],
364  params['pitches'][iplane],
365  rect)
366 
367  # a common X because each face has its own coordinate system
368  x = planex[iplane]
369 
370  # (along_pitch, side, channel, seg, p1, p2)
371  for iwire,wire in enumerate(wires):
372  ap, side, wan, seg, p1, p2 = wire
373 
374  z1, y1 = p1
375  z2, y2 = p2
376 
377  # side is +/-1, if back side, mirror due to wrapping. Fixme: this
378  # is very sensitive to offsets and will likely result in a shift as
379  # one goes from a unwrapped wire to its wrapped neighbor.
380  z1 *= side
381  z2 *= side
382 
383  chplane = iplane+1
384  if side < 0:
385  chplane += 3
386  ch = chplane*mcpp + wan
387 
388  wid = mcpp*10*(iplane+1) + (iwire+1)
389 
390  begind = store.make("point", x, y1, z1)
391  endind = store.make("point", x, y2, z2)
392  wireind = store.make("wire", wid, ch, seg, begind, endind)
393  planes[iplane].append(wireind)
394 
395  wire_plane_indices = list()
396  for iplane, wire_list in enumerate(planes):
397  if iplane == 0:
398  wire_list.sort(key = lambda w: -1*store.wire_ypos(w))
399  elif iplane == 1:
400  wire_list.sort(key = store.wire_ypos)
401  else:
402  wire_list.sort(key = store.wire_zpos)
403  wpid = schema.wire_plane_id(iplane, iface, apa)
404  index = store.make("plane", wpid, wire_list)
405  wire_plane_indices.append(index)
406  face_index = store.make("face", iface, wire_plane_indices)
407  store.make("anode", apa, [face_index])
408  return store.schema()
409 
410 
412  '''
413  Spit out contents of a file like:
414 
415  https://github.com/BNLIF/wire-cell-celltree/blob/master/geometry/ChannelWireGeometry_v2.txt
416 
417  columns of:
418  # channel plane wire sx sy sz ex ey ez
419  '''
420 
421 
422  #Wire = namedtuple("Wire", "index Chan w1 h1 w2 h2")
423  # wire: (along_pitch, side, channel, seg, p1, p2)
424  aps = set()
425  sides = set()
426  channels = set()
427  for iplane, letter in enumerate("uvw"):
428  rect, wires = protodune_plane_one_side(letter)
429  print (letter, len(wires))
430  for wire in wires:
431  ap, side, channel, seg, p1, p2 = wire
432  print (wire)
433  aps.add(ap)
434  sides.add(side)
435  channels.add(channel)
436  print (len(aps),len(sides),len(channels))
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
def wrapped_from_top(offset, angle, pitch, rect)
Definition: generator.py:188
T abs(T value)
def __setitem__(self, key, val)
Definition: generator.py:44
def wrap_one(start_ray, rect)
Definition: generator.py:166
def onesided_wrapped(params=protodune_params)
Definition: generator.py:341
def __init__(self, width, height, center=Point(0.0, 0.0))
Definition: generator.py:110
def toedge(self, point, direction)
Definition: generator.py:127
auto zip(Iterables &&...iterables)
Range-for loop helper iterating across many collections at the same time.
Definition: zip.h:295
static QCString str
def wrapped_from_top_oneside(offset, angle, pitch, rect)
Definition: generator.py:243
def __init__(self, tail, head)
Definition: generator.py:90