summaryrefslogtreecommitdiff
path: root/detectors/sno.py
blob: 156dcbe661cbfc377bb6df4f626623d83c66f4b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import numpy as np
import numpy.linalg
import math
from make import rotate_extrude
from stl import mesh_from_stl
from geometry import *
from optics import *
from solids import build_8inch_pmt_with_lc
from transform import rotate, make_rotation_matrix
import os

def sno_vessel(sphere_radius, neck_radius, neck_top, angle_step=math.pi/20):
    '''Compute the 2D coordinates of the profile of one side of a 
    SNO-style acrylic vessel.  The center of the sphere is at (0,0), with
    the neck extending along the positive y direction.
    
       sphere_radius: Radius of spherical part of profile
       neck_radius: Radius of neck part
       neck_top: y coordinate of top of neck
       angle_step: angular step size (radius) between points on the sphere

       Returns: Tuple of x and y coordinate numpy arrays.
    '''
    if neck_radius >= sphere_radius:
        raise ValueError('neck_radius must be less than sphere_radius')

    intersect_height = (sphere_radius**2 - neck_radius**2)**0.5
    max_angle = math.atan2(intersect_height, neck_radius)

    if neck_top < intersect_height:
        raise ValueError('neck_top must be greater than the y-value where the sphere and cylinder intersect')

    # Start with point at bottom
    angles = np.arange(-math.pi/2, max_angle, angle_step)
    x = list(np.cos(angles) * sphere_radius)
    y = list(np.sin(angles) * sphere_radius)
    x[0] = 0.0 # Round-off error might make cos(-pi/2) not exactly zero
    
    # Neck intersection point
    x.append(neck_radius)
    y.append(intersect_height)

    # Top of neck
    x.append(neck_radius)
    y.append(neck_top)
    
    # Top of neck on axis
    x.append(0.0)
    y.append(neck_top)
    
    return x, y

##### SNO Parts

angle_step = math.pi/20
av_outside_profile = sno_vessel(sphere_radius=6.0604, neck_radius=0.79375, 
                                neck_top=10.50, angle_step=angle_step)
# For simplicity, cap the top of the AV with acrylic
av_inside_profile  = sno_vessel(sphere_radius=6.0053, neck_radius=0.72898,
                                neck_top=10.00, angle_step=angle_step)

av_outside_mesh = rotate_extrude(av_outside_profile[0], av_outside_profile[1],
                                 angle_step)
av_outside_mesh.vertices = rotate(av_outside_mesh.vertices, np.pi/2, (-1,0,0))

av_inside_mesh = rotate_extrude(av_inside_profile[0], av_inside_profile[1],
                                 angle_step)
av_inside_mesh.vertices = rotate(av_inside_mesh.vertices, np.pi/2, (-1,0,0))

dir = os.path.split(os.path.realpath(__file__))[0]

def build_sno(real_av=False):
    import h5py
    pmtinfo = h5py.File(dir+'/sno_pmt_info.hdf5')

    pmt = build_8inch_pmt_with_lc()

    geo = Geometry()

    if real_av:
        real_av_mesh = mesh_from_stl(dir+'/sno_av_ascii.stl.bz2')
        real_av_mesh.vertices *= 0.0254 # inch -> meter
        geo.add_solid(Solid(real_av_mesh, glass, water, color=0xBBAAAAFF))
    else:
        geo.add_solid(Solid(av_outside_mesh, acrylic_sno, water, 
                            color=0xBBFFFFFF))
        geo.add_solid(Solid(av_inside_mesh, labppo_scintillator, acrylic_sno, 
                            color=0xBB0000FF))
    geo.pmtids = []

    snoman_id_dict = {}

    pmt_offset = 122.0 - 2 * 0.01# max bucket height - 2 * facegap (mm)

    for position, direction, idnum, pmt_type in zip(pmtinfo['pos'], pmtinfo['dir'],
                                          pmtinfo['snoman_id'],
                                          pmtinfo['pmt_type']):

        # PMT type codes:
        #        1 = normal; 2 = OWL; 3 = Low Gain; 4 = BUTT; 5 = Neck
        #        6 = Calib channel;  10 = spare;  11 = invalid
        if pmt_type != 1:  # All the other PMTs have nonsense directions
            continue

        
        # Flip and renormalize
        direction *= -1.0/numpy.linalg.norm(direction)

        # Orient PMT that starts facing Y axis
        y_axis = np.array((0.0,1.0,0.0))
        axis = np.cross(direction, y_axis)
        angle = np.arccos(np.dot(y_axis, direction))
        rotation = make_rotation_matrix(angle, axis)

        # Values in database are for front face of concentrator.
        # need to shift so position is for equator of PMT
        if pmt_type == 1:
            displacement = position - direction * pmt_offset
        displacement /= 1000.0 # mm -> m
        chroma_id = geo.add_solid(pmt, rotation, displacement)
        snoman_id_dict[chroma_id] = idnum
        geo.pmtids.append(chroma_id)

    # Convert dict to numpy array for fast array remapping later
    chroma_ids = np.array(snoman_id_dict.keys())
    snoman_ids = np.array(snoman_id_dict.values())
    snoman_id_map = np.zeros(max(chroma_ids)+1) - 1 # Fill with -1 everywhere
    snoman_id_map[chroma_ids] = snoman_ids

    geo.snoman_id_map = snoman_id_map
        
    return geo