m-thesis-introduction/airshower_beacon_simulation/aa_generate_beacon.py

447 lines
15 KiB
Python
Raw Normal View History

2022-09-26 17:18:07 +02:00
#!/usr/bin/env python3
# vim: fdm=marker ts=4
2022-11-14 20:49:35 +01:00
"""
Add a beacon measurement on top of the
simulated airshower.
"""
2022-09-26 17:18:07 +02:00
import numpy as np
import json
import h5py
2022-09-28 13:21:41 +02:00
import os.path as path
from copy import deepcopy
2022-09-26 17:18:07 +02:00
from earsim import REvent, Antenna, block_filter
2022-09-26 17:18:07 +02:00
import lib
# {{{ vim marker
2022-09-26 17:18:07 +02:00
tx_fname = 'tx.json'
2022-09-28 13:21:41 +02:00
antennas_fname = 'antennas.hdf5'
beacon_snr_fname = 'beacon_snr.json'
airshower_snr_fname = 'airshower_snr.json'
2022-12-15 14:40:20 +01:00
c_light = lib.c_light
2022-09-26 17:18:07 +02:00
def read_antenna_clock_repair_offsets(antennas, mode='all', freq_name=None):
valid_modes = ['orig', 'ks', 'phases', 'all']
time_offsets = []
for i, ant in enumerate(antennas):
_clock_delta = 0
# original timing
if mode == 'orig':
_clock_delta = -1*ant.attrs['clock_offset']
# phase
if mode in ['all', 'phases']:
clock_phase = ant.beacon_info[freq_name]['clock_phase_mean']
f_beacon = ant.beacon_info[freq_name]['freq']
clock_phase_time = clock_phase/(2*np.pi*f_beacon)
_clock_delta += clock_phase_time
# ks
if mode in ['all', 'ks']:
best_k_time = ant.beacon_info[freq_name]['best_k_time']
_clock_delta += best_k_time
time_offsets.append(_clock_delta)
return time_offsets
2023-02-14 11:14:28 +01:00
def write_snr_file(fname, snrs):
with open(fname, 'w') as fp:
return json.dump(
{'mean': np.mean(snrs), 'std': np.std(snrs), 'values': snrs},
fp
)
def read_snr_file(fname):
with open(fname, 'r') as fp:
return json.load(fp)
def write_tx_file(fname, tx, f_beacon, **kwargs):
2022-09-26 17:18:07 +02:00
with open(fname, 'w') as fp:
return json.dump(
{
**kwargs,
**dict(
f_beacon=f_beacon,
tx=dict(
x=tx.x,
y=tx.y,
z=tx.z,
name=tx.name
2022-09-26 17:18:07 +02:00
)
)
},
2022-09-26 17:18:07 +02:00
fp
)
2022-09-26 17:18:07 +02:00
2022-09-28 13:21:41 +02:00
def read_tx_file(fname):
2022-09-26 17:18:07 +02:00
with open(fname, 'r') as fp:
data = json.load(fp)
f_beacon = data['f_beacon']
tx = Antenna(**data['tx'])
del data['f_beacon']
del data['tx']
return tx, f_beacon, data
2022-09-26 17:18:07 +02:00
2022-11-22 11:06:15 +01:00
def read_beacon_hdf5(fname, **h5ant_kwargs):
2022-09-26 17:18:07 +02:00
with h5py.File(fname, 'r') as h5:
2022-11-22 11:06:15 +01:00
tx = Antenna_from_h5ant(h5['tx'], traces_key=None)
f_beacon = tx.attrs['f_beacon']
2022-09-28 13:21:41 +02:00
2022-09-26 17:18:07 +02:00
antennas = []
for k, h5ant in h5['antennas'].items():
2022-11-22 11:06:15 +01:00
ant = Antenna_from_h5ant(h5ant, **h5ant_kwargs)
antennas.append(ant)
2022-09-26 17:18:07 +02:00
return f_beacon, tx, antennas
def Antenna_from_h5ant(h5ant, traces_key='filtered_traces', raise_exception=True, read_AxB=True, read_beacon_info=True):
2022-11-22 11:06:15 +01:00
mydict = { k:h5ant.attrs.get(k) for k in ['x', 'y', 'z', 'name'] }
ant = Antenna(**mydict)
2022-11-22 11:40:00 +01:00
if h5ant.attrs:
ant.attrs = {**h5ant.attrs}
2022-11-22 11:06:15 +01:00
2022-11-22 11:40:00 +01:00
# Traces
2022-11-22 11:06:15 +01:00
if traces_key is None:
pass
elif traces_key not in h5ant:
if raise_exception:
raise ValueError("Traces_key not in file")
else:
ant.t = deepcopy(h5ant[traces_key][0])
ant.Ex = deepcopy(h5ant[traces_key][1])
ant.Ey = deepcopy(h5ant[traces_key][2])
ant.Ez = deepcopy(h5ant[traces_key][3])
2022-11-22 11:06:15 +01:00
if len(h5ant[traces_key]) > 4:
ant.beacon = deepcopy(h5ant[traces_key][4])
if len(h5ant[traces_key]) > 5:
ant.noise = deepcopy(h5ant[traces_key][5])
2022-11-22 11:06:15 +01:00
2022-11-22 11:40:00 +01:00
# E_AxB
if read_AxB and 'E_AxB' in h5ant:
ant.t_AxB = deepcopy(h5ant['E_AxB'][0])
ant.E_AxB = deepcopy(h5ant['E_AxB'][1])
2022-11-22 11:06:15 +01:00
2022-11-22 11:40:00 +01:00
# Beacons
if read_beacon_info and 'beacon_info' in h5ant:
h5beacon = h5ant['beacon_info']
2022-11-22 11:40:00 +01:00
beacon_info = {}
for name in h5beacon.keys():
beacon_info[name] = dict(h5beacon[name].attrs)
2022-11-22 11:40:00 +01:00
ant.beacon_info = beacon_info
2022-11-22 11:06:15 +01:00
return ant
2022-09-28 13:21:41 +02:00
def init_antenna_hdf5(fname, tx = None, f_beacon = None):
2022-09-26 17:18:07 +02:00
with h5py.File(fname, 'w') as fp:
2022-09-28 13:21:41 +02:00
if tx is not None or f_beacon is not None:
tx_group = fp.create_group('tx')
tx_attrs = tx_group.attrs
2022-09-26 17:18:07 +02:00
2022-09-28 13:21:41 +02:00
if f_beacon is not None:
tx_attrs['f_beacon'] = f_beacon
2022-09-26 17:18:07 +02:00
2022-09-28 13:21:41 +02:00
if tx is not None:
tx_attrs['x'] = tx.x
tx_attrs['y'] = tx.y
tx_attrs['z'] = tx.z
tx_attrs['name'] = tx.name
2022-09-26 17:18:07 +02:00
2022-09-28 13:21:41 +02:00
return fname
2022-11-18 11:02:41 +01:00
def append_antenna_hdf5(fname, antenna, columns = [], name='traces', prepend_time=True, overwrite=True, attrs_dict={}):
2022-09-28 15:54:11 +02:00
if not overwrite:
raise NotImplementedError
2022-09-26 17:18:07 +02:00
with h5py.File(fname, 'a') as fp:
2022-09-28 15:54:11 +02:00
if 'antennas' in fp.keys():
if not overwrite:
raise NotImplementedError
2022-09-26 17:18:07 +02:00
group = fp['antennas']
2022-09-28 15:54:11 +02:00
else:
group = fp.create_group('antennas')
2022-09-26 17:18:07 +02:00
2022-09-28 15:54:11 +02:00
if antenna.name in group:
if not overwrite:
raise NotImplementedError
h5ant = group[antenna.name]
2022-09-28 15:54:11 +02:00
else:
h5ant = group.create_group(antenna.name)
2022-09-28 15:54:11 +02:00
h5ant_attrs = h5ant.attrs
h5ant_attrs['x'] = antenna.x
h5ant_attrs['y'] = antenna.y
h5ant_attrs['z'] = antenna.z
h5ant_attrs['name'] = antenna.name
2022-09-26 17:18:07 +02:00
2022-11-18 11:02:41 +01:00
for k,v in attrs_dict.items():
h5ant_attrs[k] = v
2022-11-18 11:02:41 +01:00
if name in h5ant:
2022-09-28 15:54:11 +02:00
if not overwrite:
raise NotImplementedError
del h5ant[name]
2022-09-26 17:18:07 +02:00
dset = h5ant.create_dataset(name, (len(columns) + 1*prepend_time, len(columns[0])), dtype='f')
2022-09-28 13:21:41 +02:00
2022-09-28 15:54:11 +02:00
if prepend_time:
dset[0] = antenna.t
2022-09-28 13:21:41 +02:00
2022-09-28 15:54:11 +02:00
for i, col in enumerate(columns, 1*prepend_time):
dset[i] = col
2022-11-22 11:06:15 +01:00
2022-11-23 16:53:33 +01:00
def read_baseline_time_diffs_hdf5(fname):
"""
Read Baseline Time Diff information from HDF5 storage.
"""
with h5py.File(fname, 'r') as fp:
group_name = 'baseline_time_diffs'
base_dset_name = 'baselines'
dset_name = 'time_diffs'
group = fp[group_name]
names = group[base_dset_name][:].astype(str)
2022-11-23 16:53:33 +01:00
dset = group[dset_name]
2022-11-24 14:47:29 +01:00
time_diffs = dset[:,0]
f_beacon = dset[:,1]
clock_phase_diffs = dset[:,2]
2022-11-24 14:47:29 +01:00
k_periods = dset[:,3]
2022-11-23 16:53:33 +01:00
return names, time_diffs, f_beacon, clock_phase_diffs, k_periods
2022-11-23 16:53:33 +01:00
def write_baseline_time_diffs_hdf5(fname, baselines, clock_phase_diffs, k_periods, f_beacon, time_diffs=None, overwrite=True):
2022-11-23 16:53:33 +01:00
"""
Write a combination of baselines, phase_diff, k_period and f_beacon to file.
Note that f_beacon is allowed to broadcast, but the others are not.
"""
if not hasattr(baselines[0], '__len__'):
# this is a single baseline
N_baselines = 1
baselines = [baselines]
clock_phase_diffs = [clock_phase_diffs]
2022-11-23 16:53:33 +01:00
k_periods = [k_periods]
2022-11-24 14:47:29 +01:00
f_beacon = np.array([f_beacon])
2022-11-23 16:53:33 +01:00
else:
N_baselines = len(baselines)
# Expand the f_beacon list
if not hasattr(f_beacon, '__len__'):
2022-11-24 14:47:29 +01:00
f_beacon = np.array([f_beacon]*N_baselines)
if time_diffs is None:
time_diffs = k_periods/f_beacon + clock_phase_diffs/(2*np.pi*f_beacon)
2022-11-23 16:53:33 +01:00
assert len(baselines) == len(clock_phase_diffs) == len(k_periods) == len(f_beacon)
2022-11-23 16:53:33 +01:00
with h5py.File(fname, 'a') as fp:
group_name = 'baseline_time_diffs'
base_dset_name = 'baselines'
dset_name = 'time_diffs'
group = fp.require_group(group_name)
if base_dset_name in group:
if not overwrite:
raise NotImplementedError
del group[base_dset_name]
if dset_name in group:
if not overwrite:
raise NotImplementedError
del group[dset_name]
# save baselines list
basenames = np.array([ [b[0].name, b[1].name] for b in baselines ], dtype='S')
base_dset = group.create_dataset(base_dset_name, data=basenames)
data = np.vstack( (time_diffs, f_beacon, clock_phase_diffs, k_periods) ).T
2022-11-23 16:53:33 +01:00
dset = group.create_dataset(dset_name, data=data)
# }}} vim marker
2022-11-23 16:53:33 +01:00
2022-09-26 17:18:07 +02:00
if __name__ == "__main__":
from os import path
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-n', '--noise-sigma', type=float, default=1e3, help='in [muV/m] (Default: %(default)d)')
parser.add_argument('-f', '--beacon-frequency', type=float, default=51.53e-3, help='The beacon\'s frequency [GHz] (Default: %(default)d)')
# Beacon Properties
parser.add_argument('-a', '--beacon-amplitudes', type=float, nargs=3, default=[1e3, 0, 0], help='in [muV/m] (Default: %(default)s)')
parser.add_argument('-d', '--beacon-rsq-decay', type=bool, default=True, help='Let the beacon amplitude fall of with distance. Uses Beacon amplitudes at 0,0,0 (Default: %(default)d)')
# Bandpass
parser.add_argument('-p', '--use-passband', type=bool, default=True, help='(Default: %(default)d)')
2023-02-02 19:27:01 +01:00
parser.add_argument('-l', '--passband-low', type=float, default=30e-3, help='Lower frequency [GHz] of the passband filter. (set -1 for np.inf) (Default: %(default)g)')
parser.add_argument('-u', '--passband-high', type=float, default=80e-3, help='Upper frequency [GHz] of the passband filter. (set -1 for np.inf) (Default: %(default)g)')
# Trace length modification
parser.add_argument('-N', '--new-trace-length', type=float, help='resize airshower trace (Default: %(default)d)', default=1e4)
parser.add_argument('-P', '--pre-trace-length', type=float, help='amount of trace to prepend the airshower when resizing (Default: %(default)d)', default=2e3)
# Input directory
parser.add_argument('--input-fname', type=str, default=None, help='Path to mysim.sry, either directory or path. If empty it takes DATA_DIR and appends mysim.sry. (Default: %(default)s)')
parser.add_argument('--data-dir', type=str, default="./ZH_airshower", help='Path to Data Directory. (Default: %(default)s)')
args = parser.parse_args()
if not args.input_fname:
args.input_fname = args.data_dir
if path.isdir(args.input_fname):
args.input_fname = path.join(args.input_fname, "mysim.sry")
##
## End of ArgumentParsing
##
2022-09-26 17:18:07 +02:00
rng = np.random.default_rng()
# Noise properties
noise_sigma = args.noise_sigma # mu V/m set to None to ignore
unique_noise_realisations = True # a new noise realisation per antenna vs. single noise realisation shared across antennas
# Beacon properties
beacon_amplitudes = np.array(args.beacon_amplitudes) # mu V/m
beacon_radiate_rsq = args.beacon_rsq_decay # beacon_amplitude is repaired for distance to 0,0,0
# Beacon properties
f_beacon = args.beacon_frequency # GHz
2022-11-14 20:49:35 +01:00
# Transmitter
remake_tx = True
tx = Antenna(x=0,y=0,z=0,name='tx') # m
if True:
# Move tx out a long way
tx.x, tx.y = -75e3, 75e3 # m
elif False:
# Move it to 0,0,0 (among the antennas)
tx.x, tx.y = 0, 0 #m
2023-01-16 11:00:40 +01:00
# modify beacon power to be beacon_amplitude at 0,0,0
if beacon_radiate_rsq:
dist = lib.distance(tx, Antenna(x=0, y=0, z=0))
ampl = max(1, dist**2)
beacon_amplitudes *= ampl
# Bandpass for E field blockfilter
low_bp = args.passband_low if args.passband_low >= 0 else np.inf # GHz
high_bp = args.passband_high if args.passband_high >= 0 else np.inf # GHz
# Enable/Disable block_filter
if not args.use_passband:
block_filter = lambda x, dt, low, high: x
2022-09-26 17:18:07 +02:00
####
fname_dir = args.data_dir
2022-09-26 17:18:07 +02:00
tx_fname = path.join(fname_dir, tx_fname)
2022-09-28 13:21:41 +02:00
antennas_fname = path.join(fname_dir, antennas_fname)
2022-09-26 17:18:07 +02:00
# read/write tx properties
2022-09-26 17:18:07 +02:00
if not path.isfile(tx_fname) or remake_tx:
write_tx_file(tx_fname, tx, f_beacon, amplitudes=beacon_amplitudes.tolist(), radiate_rsq=beacon_radiate_rsq)
2022-09-26 17:18:07 +02:00
else:
tx, f_beacon, _ = read_tx_file(tx_fname)
print("Beacon amplitude at tx [muV/m]:", beacon_amplitudes)
print("Beacon amplitude at 0,0,0 [muV/m]:", beacon_amplitudes/ampl)
print("Tx location:", [tx.x, tx.y, tx.z])
print("Noise sigma [muV/m]:", noise_sigma)
2022-09-26 17:18:07 +02:00
# read in antennas
ev = REvent(args.input_fname)
2022-09-26 17:18:07 +02:00
N_antennas = len(ev.antennas)
2022-09-28 13:21:41 +02:00
# initialize hdf5 file
init_antenna_hdf5(antennas_fname, tx, f_beacon)
2022-09-26 17:18:07 +02:00
# make beacon per antenna
noise_realisation = np.array([0])
2022-09-26 17:18:07 +02:00
for i, antenna in enumerate(ev.antennas):
#TODO: allow to change the samplerate (2, 4, 8 ns)
if i%10 == 0:
print(f"Beaconed antenna {i} out of", len(ev.antennas))
if args.new_trace_length: # modify trace lengths
2022-12-05 14:35:35 +01:00
N_samples = len(antenna.t)
new_N = int(args.new_trace_length)
pre_N = int(args.pre_trace_length)
after_N = new_N - pre_N
2022-12-05 14:35:35 +01:00
dt = antenna.t[1] - antenna.t[0]
new_t = np.arange(-pre_N, after_N)*dt + antenna.t[0]
2022-12-05 14:35:35 +01:00
antenna.t = new_t
# TODO:trace extrapolation?
antenna.Ex = np.pad(antenna.Ex, (pre_N, after_N-N_samples), mode='constant', constant_values=0)
antenna.Ey = np.pad(antenna.Ey, (pre_N, after_N-N_samples), mode='constant', constant_values=0)
antenna.Ez = np.pad(antenna.Ez, (pre_N, after_N-N_samples), mode='constant', constant_values=0)
if i%10 == 0:
print(f"Modified trace lengths by {pre_N},{after_N-N_samples}")
2022-12-05 14:35:35 +01:00
beacon = 1e-6 * lib.beacon_from(tx, antenna, f_beacon, antenna.t, c_light=c_light, radiate_rsq=beacon_radiate_rsq) # mu V/m
# noise realisation
if unique_noise_realisations or (noise_realisation == 0).all(): # either create one for every antenna, or generate a single one
print("Noise realisation!")
noise_realisation = 1e-6 * rng.normal(0, noise_sigma or 0, size=len(antenna.t)) # mu V/m
# Collect all data to be saved (with the first 3 values the E fields)
traces = np.array([antenna.Ex, antenna.Ey, antenna.Ez, beacon, noise_realisation])
2022-09-28 13:21:41 +02:00
append_antenna_hdf5( antennas_fname, antenna, traces, name='original_traces', prepend_time=True)
E_AxB = [np.dot(ev.uAxB,[ex,ey,ez]) for ex,ey,ez in zip(traces[0], traces[1], traces[2])]
t_AxB = antenna.t
append_antenna_hdf5( antennas_fname, antenna, [t_AxB, E_AxB, t_AxB, t_AxB], name='original_E_AxB', prepend_time=False)# Note the 4 element list containing dummys at idx 2 and 3
# add beacon and noise to relevant polarisations
for j, amp in enumerate(beacon_amplitudes):
traces[j] = traces[j] + amp*beacon + noise_realisation
append_antenna_hdf5( antennas_fname, antenna, traces, name='prefiltered_traces', prepend_time=True)
# .. and apply block_filter to every trace
dt = antenna.t[1] - antenna.t[0]
for j in range(len(traces)):
traces[j] = block_filter(traces[j], dt, low_bp, high_bp)
2022-09-28 13:21:41 +02:00
append_antenna_hdf5( antennas_fname, antenna, traces, name='filtered_traces', prepend_time=True)
2022-09-28 13:21:41 +02:00
# Save filtered E field in E_AxB
2022-12-05 14:35:35 +01:00
E_AxB = [np.dot(ev.uAxB,[ex,ey,ez]) for ex,ey,ez in zip(traces[0], traces[1], traces[2])]
2022-12-05 14:35:35 +01:00
append_antenna_hdf5( antennas_fname, antenna, [t_AxB, E_AxB], name='E_AxB', prepend_time=False)
2022-09-28 13:21:41 +02:00
print("Antenna HDF5 file written as " + str(antennas_fname))