Skip to content
Snippets Groups Projects
plot_ce_ecal_placement.py 11.3 KiB
Newer Older
'''
    A script to calcualte placement of ecal endcap modules
    lxml is not included in container, get it by simply typing 'pip install lxml'
    Author: Chao Peng (ANL)
    Date: 06/17/2021
'''

import os
import DDG4
from matplotlib import pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle, Circle
CRYSTAL_SIZE = (20., 20., 200.) # mm
CRYSTAL_GAP = 0.5 # mm
    (5, 21), (5, 21), (5, 21), (4, 22),
    (3, 23), (0, 26), (0, 24), (0, 24),
    (0, 24), (0, 24), (0, 24), (0, 24),
    (0, 22), (0, 22), (0, 20), (0, 20),
    (0, 18), (0, 18), (0, 16), (0, 16),
    (0, 14), (0, 14), (0, 12), (0, 12),
    (0,  6), (0,  6),
GLASS_SIZE = (40., 40., 400.) # mm
GLASS_GAP = 1.0 # mm
    (13, 10), (13, 10), (13, 10), (12, 10),
    (12, 10), (12, 10), (11, 11), (10, 11),
    (9, 12),  (8, 13),  (7, 13),  (6, 14),
    (3, 16),  (0, 18),  (0, 18),  (0, 16),
    (0, 16),  (0, 14),  (0, 13),  (0, 11),
    (0, 10),  (0, 7),   (0, 3),
]

# calculate positions of modules with a quad-alignment and module size
def individual_placement(alignment, module_x, module_y, gap=0.):
    placements = []
    for row, (start, num) in enumerate(alignment):
Chao Peng's avatar
Chao Peng committed
        for col in np.arange(start, start + num):
            placements.append(((col + 0.5)*(module_y + gap), (row + 0.5)*(module_x + gap)))
    placements = np.asarray(placements)
    return np.vstack((placements,
            np.vstack((placements.T[0]*-1., placements.T[1])).T,
            np.vstack((placements.T[0], placements.T[1]*-1.)).T,
            np.vstack((placements.T[0]*-1., placements.T[1]*-1.)).T))


def draw_placement(axis, colors=('teal'), module_alignment=((CRYSTAL_SIZE, CRYSTAL_GAP, CRYSTAL_ALIGNMENT))):
    xmin, ymin, xmax, ymax = 0., 0., 0., 0.
    for color, (mod_size, mod_gap, alignment) in zip(colors, module_alignment):
        placements = individual_placement(alignment, *mod_size[:2], mod_gap)
        boxes = [Rectangle((x - (mod_size[0] + mod_gap)/2., y - (mod_size[1] + mod_gap)/2.), mod_size[0], mod_size[1])
                 for x, y in placements]
        patches.append(Rectangle((0., 0.), *mod_size[:2], facecolor=color, alpha=0.5, edgecolor='k'))
        numbers.append(len(placements))
        pc = PatchCollection(boxes, facecolor=color, alpha=0.5, edgecolor='k')

        xmin = min(xmin, placements.T[0].min() - 8.*(mod_size[0] + mod_gap))
        ymin = min(ymin, placements.T[1].min() - 8.*(mod_size[1] + mod_gap))
        xmax = max(xmax, placements.T[0].max() + 8.*(mod_size[0] + mod_gap))
        ymax = max(ymax, placements.T[1].max() + 8.*(mod_size[1] + mod_gap))

        # Add collection to axes
        axis.add_collection(pc)
    axis.set_xlim(xmin, xmax)
    axis.set_ylim(ymin, ymax)
    return axis, patches, numbers


def compact_constants(path, names):
    if not os.path.exists(path):
        print('Cannot find compact file \"{}\".'.format(path))
        return []
    kernel = DDG4.Kernel()
    description = kernel.detectorDescription()
    kernel.loadGeometry("file:{}".format(path))
    try:
        vals = [description.constantAsDouble(n) for n in names]
    except:
        print('Fail to extract values from {}, return empty.'.format(names))
        vals = []
    kernel.terminate()
    return vals


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-s', '--save', default='ce_ecal_placement_test.xml',
            help='path to save compact file.')
    parser.add_argument('-c', '--compact', default='',
            help='compact file to get contant to plot')
    parser.add_argument('--radii-constants', dest='radii', default='EcalBarrel_rmin',
            help='constant names in compact file to plot, seprate by \",\"')
Chao Peng's avatar
Chao Peng committed
    parser.add_argument('--individual', dest='indiv', action='store_true',
            help='individual block placements instead of line placements')
    args = parser.parse_args()

    data = ET.Element('lccdd')
    defines = ET.SubElement(data, 'define')

    # constants: name, value
    CONSTANTS = [
        ('CrystalModule_sx', '{:.2f}*mm'.format(CRYSTAL_SIZE[0])),
        ('CrystalModule_sy', '{:.2f}*mm'.format(CRYSTAL_SIZE[1])),
        ('CrystalModule_sz', '{:.2f}*mm'.format(CRYSTAL_SIZE[2])),
        ('CrystalModule_wrap', '{:.2f}*mm'.format(CRYSTAL_GAP)),

        ('GlassModule_sx', '{:.2f}*mm'.format(GLASS_SIZE[0])),
        ('GlassModule_sy', '{:.2f}*mm'.format(GLASS_SIZE[1])),
        ('GlassModule_sz', '{:.2f}*mm'.format(GLASS_SIZE[2])),
        ('GlassModule_wrap', '{:.2f}*mm'.format(GLASS_GAP)),

        ('CrystalModule_z0', '10.*cm'),
        ('GlassModule_z0', '0.0*cm'),
        ('EcalEndcapN_z0', '-EcalEndcapN_zmin-max(CrystalModule_sz,GlassModule_sz)/2.'),

        ('CrystalModule_dx', 'CrystalModule_sx + CrystalModule_wrap'),
        ('CrystalModule_dy', 'CrystalModule_sy + CrystalModule_wrap'),
        ('GlassModule_dx', 'GlassModule_sx + GlassModule_wrap'),
        ('GlassModule_dy', 'GlassModule_sy + GlassModule_wrap'),
    ]

# line-by-line alignment start pos, total number of blocks
Chao Peng's avatar
Chao Peng committed
    for name, value in CONSTANTS:
        constant = ET.SubElement(defines, 'constant')
        constant.set('name', name)
        constant.set('value', value)

    # this value will be used multiple times, so define it here
    readout_name = 'EcalEndcapNHits'

    # detector and its dimension/position/rotation
    dets = ET.SubElement(data, 'detectors')
    cmt = ET.SubElement(dets, 'comment')
    cmt.text = ' Backwards Endcap EM Calorimeter, placements generated by script '

    det = ET.SubElement(dets, 'detector')
    det.set('id', 'ECalEndcapN_ID')
    det.set('name', 'EcalEndcapN')
    det.set('type', 'HomogeneousCalorimeter')
    det.set('readout', readout_name)

    pos = ET.SubElement(det, 'position')
    pos.set('x', '0')
    pos.set('y', '0')
    pos.set('z', 'EcalEndcapN_z0')

    rot = ET.SubElement(det, 'rotation')
    rot.set('x', '0')
    rot.set('y', '0')
    rot.set('z', '0')

    # placements of modules
    plm = ET.SubElement(det, 'placements')
Chao Peng's avatar
Chao Peng committed
    pltype = 'individuals' if args.indiv else 'lines'
Chao Peng's avatar
Chao Peng committed
    # crystal
    crystal = ET.SubElement(plm, pltype)
    crystal.set('sector', '1')
    crystal_mod = ET.SubElement(crystal, 'module')
    crystal_mod.set('sizex', 'CrystalModule_sx')
    crystal_mod.set('sizey', 'CrystalModule_sy')
    crystal_mod.set('sizez', 'CrystalModule_sz')
    crystal_mod.set('material', 'PbWO4')
    crystal_mod.set('vis', 'AnlTeal')
    crystal_wrap = ET.SubElement(crystal, 'wrapper')
    crystal_wrap.set('thickness', 'CrystalModule_wrap')
    crystal_wrap.set('material', 'Epoxy')
    crystal_wrap.set('vis', 'WhiteVis')
    # crystal placements (for individuals)
Chao Peng's avatar
Chao Peng committed
    if args.indiv:
        for m, (x, y) in enumerate(individual_placement(CRYSTAL_ALIGNMENT, *CRYSTAL_SIZE[:2], CRYSTAL_GAP)):
Chao Peng's avatar
Chao Peng committed
            module = ET.SubElement(crystal, 'placement')
            module.set('x', '{:.3f}*mm'.format(x))
            module.set('y', '{:.3f}*mm'.format(y))
            module.set('z', 'CrystalModule_z0')
            module.set('id', '{:d}'.format(m))
Chao Peng's avatar
Chao Peng committed
    else:
        crystal.set('mirrorx', 'true')
        crystal.set('mirrory', 'true')
        for row, (begin, nmods) in enumerate(CRYSTAL_ALIGNMENT):
            line = ET.SubElement(crystal, 'line')
            line.set('axis', 'x')
            line.set('x', 'CrystalModule_dx/2.')
            line.set('y', 'CrystalModule_dy*{:d}/2.'.format(row*2 + 1))
            line.set('z', 'CrystalModule_z0')
            line.set('begin', '{:d}'.format(begin))
            line.set('nmods', '{:d}'.format(nmods))
Chao Peng's avatar
Chao Peng committed
    glass = ET.SubElement(plm, pltype)
    glass.set('sector', '2')
    glass_mod = ET.SubElement(glass, 'module')
    glass_mod.set('sizex', 'GlassModule_sx')
    glass_mod.set('sizey', 'GlassModule_sy')
    glass_mod.set('sizez', 'GlassModule_sz')
    # TODO: change glass material
    glass_mod.set('material', 'PbGlass')
    glass_mod.set('vis', 'AnlBlue')
    glass_wrap = ET.SubElement(glass, 'wrapper')
    glass_wrap.set('thickness', 'GlassModule_wrap')
    glass_wrap.set('material', 'Epoxy')
    glass_wrap.set('vis', 'WhiteVis')
    # crystal placements (for individuals)
Chao Peng's avatar
Chao Peng committed
    if args.indiv:
        for m, (x, y) in enumerate(individual_placement(GLASS_ALIGNMENT, *GLASS_SIZE[:2], GLASS_GAP)):
Chao Peng's avatar
Chao Peng committed
            module = ET.SubElement(glass, 'placement')
            module.set('x', '{:.3f}*mm'.format(x))
            module.set('y', '{:.3f}*mm'.format(y))
            module.set('z', 'GlassModule_z0')
            module.set('id', '{:d}'.format(m))
Chao Peng's avatar
Chao Peng committed
    else:
        glass.set('mirrorx', 'true')
        glass.set('mirrory', 'true')
        for row, (begin, nmods) in enumerate(GLASS_ALIGNMENT):
            line = ET.SubElement(glass, 'line')
            line.set('axis', 'x')
            line.set('x', 'GlassModule_dx/2.')
            line.set('y', 'GlassModule_dy*{:d}/2.'.format(row*2 + 1))
            line.set('z', 'GlassModule_z0')
            line.set('begin', '{:d}'.format(begin))
            line.set('nmods', '{:d}'.format(nmods))


    # readout
    readouts = ET.SubElement(data, 'readouts')
    cmt = ET.SubElement(readouts, 'comment')
    cmt.text = 'Effectively no segmentation, the segmentation is used to provide cell dimension info'
    readout = ET.SubElement(readouts, 'readout')
    readout.set('name', readout_name)
    seg = ET.SubElement(readout, 'segmentation')
    # need segmentation to provide cell dimension info
    # seg.set('type', 'NoSegmentation')
    seg.set('type', 'MultiSegmentation')
    seg.set('key', 'sector')
    crystal_seg = ET.SubElement(seg, 'segmentation')
    crystal_seg.set('name', 'CrystalSeg')
    crystal_seg.set('key_value', '1')
    crystal_seg.set('type', 'CartesianGridXY')
Chao Peng's avatar
Chao Peng committed
    crystal_seg.set('grid_size_x', 'CrystalModule_dx')
    crystal_seg.set('grid_size_y', 'CrystalModule_dy')
    glass_seg = ET.SubElement(seg, 'segmentation')
    glass_seg.set('name', 'GlassSeg')
    glass_seg.set('key_value', '2')
    glass_seg.set('type', 'CartesianGridXY')
Chao Peng's avatar
Chao Peng committed
    glass_seg.set('grid_size_x', 'GlassModule_dx')
    glass_seg.set('grid_size_y', 'GlassModule_dy')
    rid = ET.SubElement(readout, 'id')
    rid.text = 'system:8,sector:4,module:20,x:32:-16,y:-16'


    text = ET.tostring(data, pretty_print=True)
    with open(args.save, 'wb') as f:
        f.write(text)


    fig, ax = plt.subplots(figsize=(12, 12), dpi=160)
    ax, patches, nblocks = draw_placement(ax, ['teal', 'royalblue'],
            [(CRYSTAL_SIZE, CRYSTAL_GAP, CRYSTAL_ALIGNMENT), (GLASS_SIZE, GLASS_GAP, GLASS_ALIGNMENT)])
    ax.set_xlabel('x (mm)', fontsize=24)
    ax.set_ylabel('y (mm)', fontsize=24)
    ax.tick_params(direction='in', labelsize=22, which='both')
    ax.set_axisbelow(True)
    ax.grid(linestyle=':', which='both')
    ax.legend(patches, ['{} {}'.format(num, name) for num, name in zip(nblocks, ['PbWO$_4$', 'SciGlass'])], fontsize=24)

    if args.compact and args.radii:
        names = [c.strip() for c in args.radii.split(',')]
        radii = compact_constants(args.compact, names)
        for name, radius in zip(names, radii):
            ax.add_patch(Circle((0, 0), radius*10., facecolor='none', edgecolor='k', linewidth=2))
            ax.annotate(name, xy=(radius*10/1.4, radius*10/1.4), fontsize=22)
    fig.savefig('ce_ecal_placement.png')