Source

chroma_pds / chroma_pds / detector.py

'''Chroma geometry describing photon detector system in LBNE.'''

import numpy as np
from chroma_pds.optics import vacuum, acrylic, liquid_Ar, tpb, \
                              photocathode, steel, aluminum
from chroma.geometry import Solid
from chroma.detector import Detector
from chroma.make import box


#### Basic detector properties
APA_HEIGHT = 7000.0  # mm

APA_WIDTH = 2520.0  # mm
APA_SPACING = 4640.0  # mm
LONGITUDINAL_APA_GAP = 20.0  # mm
VERTICAL_APA_GAP = 25.0  # mm

INNER_HEIGHT = 16000.0  # mm
INNER_WIDTH = 15600.0  # mm
INNER_LENGTH = 27000.0  # mm
####


def build_cryostat(ev_display=False):
    '''Build the rectangular steel cryostat.'''
    tank_mesh = box(INNER_LENGTH, INNER_WIDTH, INNER_HEIGHT)
    if ev_display:
        color = 0xFF00FFAA
    else:
        color = 0xAA00FFAA
    tank = Solid(tank_mesh, liquid_Ar, vacuum, surface=steel,
                 color=color)

    return tank


def build_paddle(ev_display=False, mask_side=None, double_side=False):
    '''Build an acrylic bar with optional mask and photo-sensitive detectors
    on each end.

    Parameters:
        ev_display: If true, make bars partially transparent for display.
        mask_side: If None, no mask.  Otherwise -1 or +1 to select the
                   side for the aluminum mask.
        double_side: If True, put photo-sensitive detector on both ends
                     of bar, otherwise just on one end.
    '''
    length = 2000.0 + 250.0  # mm (including 90 twist length here)
    width = 88.0  # mm
    thickness = 4.8  # mm

    mesh = box(length, thickness, width)

    if ev_display:
        tpb_color = 0xEEEEEEEE
        reflector_color = 0xEEEE00EE
        detector_color = tpb_color
    else:
        tpb_color = 0x44EE0000
        reflector_color = 0x44EEEEEE
        detector_color = 0x44FFFF00

    surface = np.empty(shape=len(mesh.triangles), dtype=np.object)
    color = np.empty(shape=len(mesh.triangles), dtype=np.uint32)

    # Pick out the triangles on one end of the paddle for photon detection
    triangles = mesh.assemble(group=True)
    triangle_u = triangles[:, 1, :] - triangles[:, 0, :]
    triangle_v = triangles[:, 2, :] - triangles[:, 0, :]
    triangle_normal = np.cross(triangle_u, triangle_v)
    if double_side:
        pc_side = np.abs(triangle_normal[:, 0]) > 0.01
    else:
        pc_side = triangle_normal[:, 0] > 0.01

    surface[~pc_side] = tpb
    surface[pc_side] = photocathode
    color[~pc_side] = tpb_color
    color[pc_side] = detector_color

    # Add a mask if requested
    if mask_side is not None:
        assert mask_side in (-1, 1)
        mask_triangles = mask_side * triangle_normal[:, 1] > 0.01
        surface[mask_triangles] = aluminum
        color[mask_triangles] = reflector_color

    paddle = Solid(mesh, acrylic, liquid_Ar, surface=surface,
                   color=color)
    return paddle


def add_apa(geo, displacement, ev_display=False, num_paddles=10,
            masked_bars=False, double_side=False):
    '''Add an anode plane assembly to geo with the given displacement.

    Parameters:
        geo: chroma.geometry.Geometry
        displacement: numpy.ndarray(shape=3)
        ev_display: If true, make bars very translucent for ev_display
        num_paddles: number of acrylic bars per APA
        masked_bars: Cover one side of each bar with aluminum.  Alternates on each bar.
        double_side: Put photo-sensitive detector on each end of bar.
    '''
    height = APA_HEIGHT
    width = APA_WIDTH
    bar_width = 90.0
    stride = height / num_paddles

    if ev_display:
        bar_color = 0xE8EEEEEE
    else:
        bar_color = 0x00888888

    #Steel frame components
    left_bar = Solid(box(bar_width, bar_width, height),
                     liquid_Ar, liquid_Ar,
                     surface=steel, color=bar_color)
    left_bar.mesh.vertices[:, 0] -= (width - bar_width) / 2

    right_bar = Solid(box(bar_width, bar_width, height),
                      liquid_Ar, liquid_Ar,
                      surface=steel, color=bar_color)
    right_bar.mesh.vertices[:, 0] += (width - bar_width) / 2

    top_bar = Solid(box(width - bar_width, bar_width, bar_width),
                    liquid_Ar, liquid_Ar,
                    surface=steel, color=bar_color)
    top_bar.mesh.vertices[:, 2] -= (height - bar_width) / 2

    bottom_bar = Solid(box(width - bar_width, bar_width, bar_width),
                       liquid_Ar, liquid_Ar,
                       surface=steel, color=bar_color)
    bottom_bar.mesh.vertices[:, 2] += (height - bar_width) / 2

    # Assemble APA frame
    apa = left_bar + right_bar + top_bar + bottom_bar
    geo.add_solid(apa, displacement=displacement)

    # Add acrylic bars
    for i, z in enumerate(np.arange(-(height - stride) / 2,
                                    (height - stride) / 2 + 0.001,
                                    stride)):
        if masked_bars:
            mask_side = (i % 2) * 2 - 1  # alternate -1 and 1
        else:
            mask_side = None
        paddle = build_paddle(ev_display=ev_display, mask_side=mask_side,
                              double_side=double_side)
        paddle.mesh.vertices[:] += (-bar_width / 4, 0, z)
        geo.add_pmt(paddle, displacement=displacement)


def make_cathode_plane(surface, y, height, length, ev_display=False):
    '''Create a solid cathode plane (infinitesmial thickness).

    Parameters:
        surface: chroma.geometry.Surface
        y: Offset of plane in y direction (mm)
        height: Height of plane (mm)
        ev_display: If True, make plane render transparent for display
    '''
    center = (0, y, 0)
    thickness = 3.0  # mm
    plate = box(length, thickness, height, center)
    if ev_display:
        color = 0xBB222244
    else:
        color = 0x44222244
    return Solid(plate, liquid_Ar, liquid_Ar, surface=surface, color=color)


def lar5(ev_display=False, efficiency_scale=1.0, masked_bars=False,
         double_side=False):
    '''Create an LBNE cryostat (5 kton fiducial).

    Parameters:
        ev_display: If True, sets the colors of most detector elements
            to transparent for event display.
        efficiency_scale: Multiply the photocathode efficiency by a factor
        masked_bars: If True, make acrylic bars have one side masked, alternating
            sides down the APA.
        double_side: If True, put photo-sensitive detectors on both ends of bars.

    Returns: chroma.detector.Detector
    '''
    photocathode.detect[:, 1] *= efficiency_scale
    geo = Detector(liquid_Ar)

    geo.add_solid(build_cryostat(ev_display=ev_display))
    nplanes = 3
    nlong = 10
    apa_count = 0

    # Anode planes
    for x in np.arange(-(APA_WIDTH + LONGITUDINAL_APA_GAP) * (nlong - 1) / 2.0,
                       (APA_WIDTH + LONGITUDINAL_APA_GAP) * (nlong - 1) / 2.0 + 0.001,
                        APA_WIDTH + LONGITUDINAL_APA_GAP):
        for y in np.arange(-(APA_SPACING) * (nplanes - 1) / 2.0,
                            (APA_SPACING) * (nplanes - 1) / 2.0 + 0.001,
                            APA_SPACING):
            for z in [-(APA_HEIGHT + VERTICAL_APA_GAP) / 2,
                       (APA_HEIGHT + VERTICAL_APA_GAP) / 2]:
                displacement = (x + 500, y, z)
                add_apa(geo, displacement=displacement, ev_display=ev_display,
                        masked_bars=masked_bars, double_side=double_side)
                apa_count += 1

    # Cathode planes
    for y in np.arange(-(APA_SPACING) * (nplanes) / 2.0,
                        (APA_SPACING) * (nplanes) / 2.0 + 0.001,
                        APA_SPACING):
        geo.add_solid(make_cathode_plane(steel, y,
                                height=(APA_HEIGHT + VERTICAL_APA_GAP) * 2,
                                length=(APA_WIDTH + LONGITUDINAL_APA_GAP) * 10))

    geo.set_time_dist_gaussian(0.34, -10, 10)
    geo.set_charge_dist_gaussian(1.0, 0.01, 0.5, 1.5)
    print 'APAs:', apa_count
    return geo


def lar5_mask():
    '''Create a 5 kton cryostat with masked bars.'''
    return lar5(masked_bars=True)


def lar5_mask_double():
    '''Create a 5 kton cryostat with double-ended photon readout.'''
    return lar5(masked_bars=True, double_side=True)


def lar5_display():
    '''Create a 5 kton cryostat with triangles colored for event display.'''
    return lar5(ev_display=True)