Skip to content
Snippets Groups Projects
generate_prim_file 5.31 KiB
#!/usr/local/bin/python

# same as make_dawn_views but stops at generating the prim file.
# W. Armstrong (ANL), original bash script
# C. Peng (ANL), translate to python and add flexible run time for simulation

import os
import signal
import subprocess
import argparse
import atexit
import time
from datetime import datetime
import fcntl
import psutil


def readline_nonblocking(output):
    fd = output.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    try:
        return output.readline()
    except:
        return ''


# arguments
parser = argparse.ArgumentParser()

parser.add_argument('-c', '--compact-file', type=str, dest='compact',
        default=os.path.join(os.environ.get('DETECTOR_PATH', '.'), 'athena.xml'),
        help='Top level compact file for detectors')

parser.add_argument('-s', '--skip', type=int,
        default=0,
        help='Number of events number to skip in the input')

parser.add_argument('-i', '--input', type=str,
        default='scripts/input_data/few_events.hepmc',
        help='Input hepmc file')

parser.add_argument('-o', '--output-dir', type=str, dest='out_dir',
        default='sim_output',
        help='output directory')

parser.add_argument('-D', '--detector-only', action='store_true', dest='detector_only',
        help='only generate the prim files for the detector geometry')

parser.add_argument('-t', '--tag', type=str,dest='file_tag',
        default='view',
        help='Output file tag')

parser.add_argument('--timeout', type=int,
        default=60,
        help='Timeout in seconds')

parser.add_argument('passthrough', nargs='*')

args = parser.parse_args()

macro = 'macro/dawn_picture.mac' if args.detector_only else 'macro/dawn_picture2.mac'

# raise error if cannot create a temporary working dir
# os.makedirs('dawn_view_tmp', exist_ok=False)
os.makedirs(args.out_dir, exist_ok=True)

# use absolute path so the chdir does not affect them
args.input = os.path.abspath(args.input)
args.out_dir = os.path.abspath(args.out_dir)
args.compact = os.path.abspath(args.compact)
macro = os.path.abspath(macro)

# adjust fiber radius to reduce the number of fibers
compact_dir = os.path.dirname(os.path.realpath(args.compact))
ci_ecal = os.path.join(compact_dir, 'compact', 'ci_ecal_scfi.xml')
os.system('sed -i \'s/radius=\"EcalEndcapP_FiberRadius\"/radius=\"EcalEndcapP_FiberRadius*10\"/\' {}'.format(ci_ecal))

prim_file = 'g4_0000.prim'
dawn_env = os.environ.copy()
dawn_env['DAWN_BATCH'] = 'a'
# sdir = os.path.dirname(os.path.realpath(__file__))

# Using a python warpper such as npsim introduces some problem in managing the subprocess.
# The process1 managed by subprocess will generate another process with proc2_pid = proc1_pid + 1, which will not
# be terminated by terminating or killing the process1.
# In addition, running Geant4 with vis mode will never exit automatically (it waits for input).
# Thus the created process 2 will occupy the system resources.
sim_cmd = ['npsim', '--runType', 'vis',
        '--compact', args.compact,
        '--inputFiles', args.input,
        '--outputFile', 'derp.root',
        '--numberOfEvents', '1',
        '--skipNEvents', str(args.skip),
        '--macroFile', macro]

start = datetime.now()
elapse = datetime.now() - start
last_update = datetime.now()
finished = False

# run simulation
print(' '.join(sim_cmd))
p = subprocess.Popen(args=sim_cmd, env=dawn_env,
                     stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
__child_pid = p.pid
while elapse.seconds < args.timeout:
    line = readline_nonblocking(p.stdout)
    elapse = datetime.now() - start
    time_left = args.timeout - elapse.seconds
    time_str = '[{:02d}:{:02d}]'.format(elapse.seconds // 60, elapse.seconds % 60)

    if time_left < 10:
        print('{} === TIMEOUT ===: Terminating in {:d} seconds'.format(time_str, time_left))

    if line:
        decoded_line = line.decode('utf-8').strip()
        print('{} {}'.format(time_str, decoded_line))
        # what we are looking for
        if decoded_line == 'File  {}  is generated.'.format(prim_file):
            print('{} === FINISHED ===: Got the prim file, terminating.'.format(time_str))
            finished = True
            break
        if decoded_line == 'Idle>':
            p.stdin.write(b'exit')
            break
        # do not sleep
        continue

    # ended early before file
    if p.poll() is not None:
        print(p.poll())
        break

    time.sleep(1)

p.kill()
# use to kill the subprocess generated from the python wrapper
# this is unsafe so maybe more checks required
for proc in psutil.process_iter():
    pinfo = proc.as_dict(attrs=['pid', 'name', 'create_time'])
    if pinfo['pid'] == p.pid + 1 and pinfo['name'] == 'python':
        print('kill {}, generated from {}'.format(pinfo, p.pid))
        os.kill(pinfo['pid'], signal.SIGTERM)

# revert the change
os.system('sed -i \'s/radius=\"EcalEndcapP_FiberRadius*10\"/radius=\"EcalEndcapP_FiberRadius\"/\' {}'.format(ci_ecal))

line = b'stderr outputs:\n'
while line:
    print(line.decode('utf-8'), end='')
    line = readline_nonblocking(p.stderr)

if finished:
    print('Simulation finished')
else:
    print('Simulation failed')
    exit(1)

# move the prim files (which can be quite large)
# to the local pipeline storage path
os.system('mv g4_0000.prim {}/{}.prim'.format(args.out_dir,args.file_tag))