3 Organize the connectivity of the parts of protoDUNE and DUNE FD 4 single-phase detectors. 6 FIXME: only TPC related parts are supported for now! 8 FIXME: this design is currently ambigous as to whether each part is 9 "logical" or "touchable" (in the Geant4 sense of these words). It 10 allows for both and it is up to the user to determine interpretation. 11 Future development may provide explicit interpretation. 13 This organization separates into the following conceptual parts: 15 - part identifiers :: the database provides a table for each type 16 of detector part relevant to identifiers that may be present in 19 - part connectivity :: relationships between part tables and join 20 tables form edges in an overall connectivity graph. 22 - connectivity ordering :: edges in the connectivity graph have a 23 scalar ordering based on one or more table columns or class 26 - numbering convention :: the database does not assume any 27 particular numbering convention except in so much as it can be 28 expressed in terms of the connectivity ordering attributes. 30 A part table has relationships to others which fall into one of these 33 - child :: a part may have a child sub-part which is considered 34 contained by the part. 36 - parent :: a part may itself be a child and maintain a connection 39 - peer :: two parts may reference each other 41 - simple :: a table may simply reference another in a 42 unidirectional manner. 44 - special :: a relationship of convenience 46 The child and parent connectivity is maintened, in part, through a 47 join table ("link") which allows specifying one or more connectivity 48 ordering attributes. These conections are expressed in two 49 relationships as rows/attributes on the part table/class. they take 50 names after the "other" part: 52 - parts :: a sequence of the other connected parts with has no 55 - part_links :: a sequence of links from the part to its children 56 or its parent(s). When referencing children, the links are 57 ordered by the connectivit ordering attributes held in the link 58 objects. Each link object gives access to both parent and child 61 As a convenience, a child part may be associated to a parent part 62 through the parent's `add_<part>()` method. These methods accept an 63 existing child object and all connection ordering parameters. A join 64 object is constructed and returned (but can usually be ignored). 66 Note, in detector description limited to a single detector where each 67 part is considered "touchable" (see FIXME above) the parent-child 68 relationship is one-to-many. OTOH, multiple connections from one 69 child back to multiple parents are supported. As an example, a 70 database may contain descriptions for both protoDUNE and DUNE where 71 with two Detector objects and where the former merely references six 74 The branches of the parent-child tree terminate at the channels leaf, 75 although channels have "peer" links to conductors (see below). 76 Conceptually, if one considers a containment tree, the tree also 77 terminates at wire leafs. However, there are cycles in relationship. 78 Wires are contained both by conductors which connect up through 79 electronics back to the detector and in planes which connect up 80 through structural elements to the detector. Likewise boards are 81 conceptually "in" both WIBs and Faces. 83 The "peer" relationship is a simple one-to-one connection. Each part 84 on either end of the connection has an attribute named after the 85 "other" part. These are used to directly express a connection which 86 either can not be expressed or which would require traversal up and 87 down many intermeidate branches of the parent/child tree. For example 88 a Channel has a "conductor" and a Conductor has a "channel". Also, 89 Crates and Anodes share a peer relationship. To indirectly associate 90 the two would require redundancy in the join tables and require 91 complex queries. Instead, an explicit relationship is formed which 92 represents a physical soldering of a conductor to a wire board layer 93 and spot. A side effect benefit is that mistakes in such physical 94 connections can be represented. 96 The "peer" relationships must be set explicitly set either at the time 97 of constructing the final end point object or later by setting the 98 corresponsing attribute on one (not both) of the end point objects. 100 A "simple" relationship is used to compose compound values such as 101 referencing rays which themselves reference points. In some cases 102 "special" relationships are provided. For example, a Point can 103 determine which Rays refer to it, but other tables may reference a 104 Point and no back reference will exist. 109 from sqlalchemy
import Column, ForeignKey, Float, Integer, String, CheckConstraint
110 from sqlalchemy.ext.declarative
import declarative_base
111 from sqlalchemy.orm
import relationship
113 Base = declarative_base()
114 metadata = Base.metadata
118 One detector, eg protoDUNE/SP, DUNE FD SP module. 120 __tablename__ =
"detectors" 121 id = Column(Integer, primary_key=
True)
123 crates = relationship(
'Crate',
124 secondary=
'detector_crate_links')
125 crate_links = relationship(
'DetectorCrateLink',
126 order_by=
'DetectorCrateLink.address',
127 back_populates=
"detector")
129 anodes = relationship(
'Anode',
130 secondary=
'detector_anode_links')
131 anode_links = relationship(
'DetectorAnodeLink',
132 order_by=
'DetectorAnodeLink.layer, ' 133 'DetectorAnodeLink.column, ' 134 'DetectorAnodeLink.row',
135 back_populates =
"detector")
139 Add an anode at a given address, return the DetectorAnodeLink. 142 row=row, column=column, layer=layer)
146 Add a create at a given address, return the DetectorCrateLink. 151 return "<Detector: %s>" % self.
id 155 An anode (APA, anode plane assembly) is in a detector and has two faces. 157 __tablename__ =
'anodes' 158 id = Column(Integer, primary_key=
True)
163 detectors = relationship(
'Detector',
164 secondary=
'detector_anode_links')
165 detector_links = relationship(
'DetectorAnodeLink',
166 back_populates =
"anode")
168 faces = relationship(
'Face',
169 secondary =
'anode_face_links')
170 face_links = relationship(
'AnodeFaceLink',
171 order_by=
'AnodeFaceLink.side',
172 back_populates =
'anode')
176 Add a face to this anode on the given side. 183 return "<Anode: %s>" % self.
id 188 An anode face contains boards and planes. 190 __tablename__ =
'faces' 191 id = Column(Integer, primary_key=
True)
193 anodes = relationship(
'Anode',
194 secondary=
'anode_face_links')
195 anode_links = relationship(
'AnodeFaceLink',
196 back_populates =
"face")
198 boards = relationship(
'Board',
199 secondary =
'face_board_links')
200 board_links = relationship(
'FaceBoardLink',
201 order_by =
'FaceBoardLink.spot',
202 back_populates =
'face')
204 planes = relationship(
'Plane',
205 secondary =
'face_plane_links')
206 plane_links = relationship(
'FacePlaneLink',
207 order_by =
'FacePlaneLink.layer',
208 back_populates =
'face')
212 Add a board at a spot into this face. 218 Add a plane at a layer into this face. 224 return "<Face: %s>" % self.
id 229 A plane is in a face and has wires. 231 __tablename__ =
'planes' 232 id = Column(Integer, primary_key=
True)
234 faces = relationship(
'Face',
235 secondary=
'face_plane_links')
236 face_links = relationship(
'FacePlaneLink',
237 back_populates =
"plane")
239 wires = relationship(
'Wire',
240 secondary =
'plane_wire_links')
241 wire_links = relationship(
'PlaneWireLink',
242 order_by =
'PlaneWireLink.spot',
243 back_populates =
'plane')
247 Add a wire in a particular spot in the plane. 254 return "<Plane: %s>" % self.
id 258 One crate of a detector holding WIBs. 260 __tablename__ =
"crates" 261 id = Column(Integer, primary_key=
True)
263 detectors = relationship(
'Detector',
264 secondary=
'detector_crate_links')
265 detector_links = relationship(
'DetectorCrateLink',
266 back_populates =
"crate")
268 wibs = relationship(
'Wib',
269 secondary =
'crate_wib_links')
270 wib_links = relationship(
'CrateWibLink',
271 order_by =
'CrateWibLink.slot',
272 back_populates =
'crate')
276 Add a WIB into the slot of this crate. 281 return "<Crate: %s>" % self.
id 286 A WIB (warm interface board) sits in a crate and connects to a 287 four (electronics wire) boards. 289 __tablename__ =
"wibs" 290 id = Column(Integer, primary_key=
True)
292 crates = relationship(
"Crate",
293 secondary =
'crate_wib_links')
294 crate_links = relationship(
'CrateWibLink',
295 back_populates =
'wib')
297 boards = relationship(
'Board',
298 secondary =
'wib_board_links')
299 board_links = relationship(
'WibBoardLink',
300 order_by =
'WibBoardLink.connector',
301 back_populates =
'wib')
304 Add an cold electronics wire board to this WIB 306 return WibBoardLink(wib=self, board=board, connector=connector)
309 return "<Wib: %s>" % self.
id 314 An (electronics wire) board sits on top (or bottom) of an APA 315 frame and holds 8 pairs of FE/ADC ASIC chips and connects to three 316 rows of 40, 40 and 48 conductors. 318 __tablename__ =
"boards" 319 id = Column(Integer, primary_key=
True)
322 wibs = relationship(
"Wib",
323 secondary =
"wib_board_links")
324 wib_links = relationship(
"WibBoardLink",
325 back_populates =
"board")
327 faces = relationship(
"Face",
328 secondary =
"face_board_links")
329 face_links = relationship(
"FaceBoardLink",
330 back_populates =
"board")
333 conductors = relationship(
"Conductor",
334 secondary =
"board_conductor_links")
335 conductor_links = relationship(
"BoardConductorLink",
336 order_by =
'BoardConductorLink.layer, ' 337 'BoardConductorLink.spot',
338 back_populates =
"board")
339 chips = relationship(
"Chip",
340 secondary =
"board_chip_links")
341 chip_links = relationship(
"BoardChipLink",
342 order_by =
'BoardChipLink.spot',
343 back_populates =
"board")
347 Add a conductor to this board at the given spot of a layer. 353 Add a chip to this board at the given spot. 358 return "<Board: %s>" % self.
id 363 A chip is a pair of FE/ADC asics with 16 channels 365 __tablename__ =
"chips" 366 id = Column(Integer, primary_key=
True)
368 boards = relationship(
"Board",
369 secondary =
"board_chip_links")
370 board_links = relationship(
"BoardChipLink",
371 back_populates =
"chip")
373 channels = relationship(
"Channel",
374 secondary =
"chip_channel_links")
375 channel_links = relationship(
"ChipChannelLink",
376 order_by =
'ChipChannelLink.address',
377 back_populates =
"chip")
381 Add a channel to this chip at the given address. 386 return "<Chip: %s>" % self.
id 390 A conductor is a length of wire segments connecting to a board at 391 a given position indicated by plane and spot . 393 __tablename__ =
"conductors" 394 id = Column(Integer, primary_key=
True)
396 boards = relationship(
"Board",
397 secondary =
"board_conductor_links")
398 board_links = relationship(
"BoardConductorLink",
399 back_populates =
"conductor")
401 wires = relationship(
"Wire",
402 secondary =
"conductor_wire_links")
403 wire_links = relationship(
"ConductorWireLink",
404 order_by =
'ConductorWireLink.segment',
405 back_populates =
"conductor")
407 channel_id = Column(Integer, ForeignKey(
'channels.id'))
408 channel = relationship(
'Channel', back_populates=
'conductor')
412 Add a wire to this conductor as the given segment. 417 return "<Conductor: %s>" % self.
id 422 An electronics channel. 424 __tablename__ =
'channels' 425 id = Column(Integer, primary_key=
True)
427 chips = relationship(
"Chip",
428 secondary =
"chip_channel_links")
429 chip_links = relationship(
"ChipChannelLink",
430 back_populates =
"channel")
433 conductor = relationship(
'Conductor', uselist=
False,
434 back_populates=
'channel')
436 return "<Channel: %s>" % self.
id 443 __tablename__ =
'wires' 444 id = Column(Integer, primary_key=
True)
446 conductors = relationship(
"Conductor",
447 secondary =
"conductor_wire_links")
448 conductor_links = relationship(
"ConductorWireLink",
449 back_populates =
"wire")
451 planes = relationship(
"Plane",
452 secondary =
"plane_wire_links")
453 plane_links = relationship(
"PlaneWireLink",
454 back_populates =
"wire")
457 ray_id = Column(Integer, ForeignKey(
'rays.id'))
458 ray = relationship(
'Ray')
461 return "<Wire %d>" % self.
id 466 Two endpoints and a microphone. 468 __tablename__ =
"rays" 470 id = Column(Integer, primary_key=
True)
473 tail_id = Column(Integer, ForeignKey(
'points.id'))
474 tail = relationship(
"Point", foreign_keys = [tail_id])
477 head_id = Column(Integer, ForeignKey(
'points.id'))
478 head = relationship(
"Point", foreign_keys = [head_id])
481 wires = relationship(
"Wire",
482 primaryjoin=
'Ray.id == Wire.ray_id')
484 return "<Ray %d>" % self.
id 489 A point in some unspecified 3-space coordinate system. 491 __tablename__ =
"points" 493 id = Column(Integer, primary_key=
True)
499 tails = relationship(
"Ray",
500 primaryjoin=
'Point.id == Ray.tail_id')
502 heads = relationship(
"Ray",
503 primaryjoin=
'Point.id == Ray.head_id')
505 rays = relationship(
"Ray",
506 primaryjoin=
'or_(Point.id == Ray.head_id, Point.id == Ray.tail_id)')
509 return "<Point %d x:%f y:%f z:%f>" % (self.
id, self.
x, self.
y, self.
z)
518 A join table for detector-crate association. 520 __tablename__ =
"detector_crate_links" 522 detector_id = Column(Integer, ForeignKey(
'detectors.id'), primary_key=
True)
523 crate_id = Column(Integer, ForeignKey(
'crates.id'), primary_key=
True)
524 detector = relationship(
'Detector', back_populates=
'crate_links')
525 crate = relationship(
'Crate', back_populates=
'detector_links')
527 address = Column(Integer)
530 return "<DetectorCrateLink address:%s [%s %s]> " % \
535 A join table for crate-WIB association 537 __tablename__ =
"crate_wib_links" 538 crate_id = Column(Integer, ForeignKey(
'crates.id'), primary_key=
True)
539 wib_id = Column(Integer, ForeignKey(
'wibs.id'), primary_key=
True)
541 crate = relationship(
'Crate', back_populates=
'wib_links')
542 wib = relationship(
'Wib', back_populates=
'crate_links')
544 slot = Column(Integer)
547 return "<CrateWibLink slot:%s [%s %s]> " % \
548 (self.address, self.
crate, self.
wib)
552 A join table for WIB-board association. 554 __tablename__ =
'wib_board_links' 556 wib_id = Column(Integer, ForeignKey(
'wibs.id'), primary_key =
True)
557 board_id = Column(Integer, ForeignKey(
'boards.id'), primary_key =
True)
559 wib = relationship(
'Wib', back_populates=
'board_links')
560 board = relationship(
'Board', back_populates=
'wib_links')
565 connector = Column(Integer)
568 return "<WibBoardLink slot:%s [%s %s]> " % \
574 A join table for board-conductor association. 576 __tablename__ =
'board_conductor_links' 578 board_id = Column(Integer, ForeignKey(
'boards.id'), primary_key =
True)
579 conductor_id = Column(Integer, ForeignKey(
'conductors.id'), primary_key =
True)
581 board = relationship(
'Board', back_populates=
'conductor_links')
582 conductor = relationship(
'Conductor', back_populates=
'board_links')
588 layer = Column(Integer)
589 spot = Column(Integer)
592 return "<BoardConductorLink layer:%s spot:%s [%s %s]> " % \
597 A join table for board-chip association. 599 __tablename__ =
'board_chip_links' 601 board_id = Column(Integer, ForeignKey(
'boards.id'), primary_key =
True)
602 chip_id = Column(Integer, ForeignKey(
'chips.id'), primary_key =
True)
604 board = relationship(
'Board', back_populates=
'chip_links')
605 chip = relationship(
'Chip', back_populates=
'board_links')
607 spot = Column(Integer)
610 return "<BoardChipLink spot:%s [%s %s]> " % \
616 A join table for chip-channel association. 618 __tablename__ =
'chip_channel_links' 620 chip_id = Column(Integer, ForeignKey(
'chips.id'), primary_key =
True)
621 channel_id = Column(Integer, ForeignKey(
'channels.id'), primary_key =
True)
623 chip = relationship(
'Chip', back_populates=
'channel_links')
624 channel = relationship(
'Channel', back_populates=
'chip_links')
626 address = Column(Integer)
629 return "<ChipOffsetLink address:%s [%s %s]> " % \
634 A join table for conductor-wire association. 636 __tablename__ =
'conductor_wire_links' 638 conductor_id = Column(Integer, ForeignKey(
'conductors.id'), primary_key =
True)
639 wire_id = Column(Integer, ForeignKey(
'wires.id'), primary_key =
True)
641 conductor = relationship(
'Conductor', back_populates=
'wire_links')
642 wire = relationship(
'Wire', back_populates=
'conductor_links')
644 segment = Column(Integer)
647 return "<ConductorWireLink segment:%s [%s %s]> " % \
653 A join table for detector-anode association 655 __tablename__ =
'detector_anode_links' 657 detector_id = Column(Integer, ForeignKey(
'detectors.id'), primary_key =
True)
658 anode_id = Column(Integer, ForeignKey(
'anodes.id'), primary_key =
True)
660 detector = relationship(
'Detector', back_populates=
'anode_links')
661 anode = relationship(
'Anode', back_populates=
'detector_links')
664 row = Column(Integer)
666 column = Column(Integer)
670 layer = Column(Integer)
673 return "<DetectorAnodeLink apanum:%s [%s %s]> " % \
674 (self.apanum, self.detecotr, self.
anode)
678 A join table for anode-face association 680 __tablename__ =
'anode_face_links' 682 anode_id = Column(Integer, ForeignKey(
'anodes.id'), primary_key =
True)
683 face_id = Column(Integer, ForeignKey(
'faces.id'), primary_key =
True)
685 anode = relationship(
'Anode', back_populates=
'face_links')
686 face = relationship(
'Face', back_populates=
'anode_links')
689 side = Column(Integer)
692 return "<AnodeFaceLink side:%s [%s %s]> " % \
697 A join table for face-baord association 699 __tablename__ =
'face_board_links' 701 face_id = Column(Integer, ForeignKey(
'faces.id'), primary_key =
True)
702 board_id = Column(Integer, ForeignKey(
'boards.id'), primary_key =
True)
704 face = relationship(
'Face', back_populates=
'board_links')
705 board = relationship(
'Board', back_populates=
'face_links')
707 spot = Column(Integer)
710 return "<FaceBoardLink spot:%s [%s %s]> " % \
715 A join table for face-plane association 717 __tablename__ =
'face_plane_links' 719 face_id = Column(Integer, ForeignKey(
'faces.id'), primary_key =
True)
720 plane_id = Column(Integer, ForeignKey(
'planes.id'), primary_key =
True)
722 face = relationship(
'Face', back_populates=
'plane_links')
723 plane = relationship(
'Plane', back_populates=
'face_links')
725 layer = Column(Integer)
728 return "<FacePlaneLink layer:%s [%s %s]> " % \
733 A join table for plane-wire association 735 __tablename__ =
'plane_wire_links' 737 plane_id = Column(Integer, ForeignKey(
'planes.id'), primary_key =
True)
738 wire_id = Column(Integer, ForeignKey(
'wires.id'), primary_key =
True)
740 plane = relationship(
'Plane', back_populates=
'wire_links')
741 wire = relationship(
'Wire', back_populates=
'plane_links')
744 spot = Column(Integer)
747 return "<PlaneWireLink index:%s [%s %s]> " % \
752 from sqlalchemy
import create_engine
753 from sqlalchemy.orm
import sessionmaker
758 engine = create_engine(dburl)
759 Base.metadata.create_all(engine)
760 Session = sessionmaker(bind=engine)
def add_wire(self, wire, spot)
def add_board(self, board, spot)
def add_anode(self, anode, row, column, layer)
def add_crate(self, cr, address)
def add_face(self, face, side)
def add_conductor(self, conductor, spot, layer)
def add_board(self, board, connector)
def session(dburl="sqlite:///:memory:")
def add_channel(self, channel, address)
def add_plane(self, plane, layer)
def add_wire(self, wire, segment)
def add_chip(self, chip, spot)
def add_wib(self, wib, slot)