diff --git a/.gitmodules b/.gitmodules index f3c8fc9..878337e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "simulations/airshower_beacon_simulation/earsim"] - path = simulations/airshower_beacon_simulation/lib/earsim + path = airshower_beacon_simulation/lib/earsim url = https://gitlab.com/harmscho/earsim.git [submodule "simulations/airshower_beacon_simulation/lib/atmocal"] - path = simulations/airshower_beacon_simulation/lib/atmocal + path = airshower_beacon_simulation/lib/atmocal url = https://gitlab.com/harmscho/AtmosphereCal diff --git a/simulations/airshower_beacon_simulation/.gitignore b/airshower_beacon_simulation/.gitignore similarity index 100% rename from simulations/airshower_beacon_simulation/.gitignore rename to airshower_beacon_simulation/.gitignore diff --git a/simulations/airshower_beacon_simulation/Makefile b/airshower_beacon_simulation/Makefile similarity index 78% rename from simulations/airshower_beacon_simulation/Makefile rename to airshower_beacon_simulation/Makefile index 6ab7b77..f2b76ea 100644 --- a/simulations/airshower_beacon_simulation/Makefile +++ b/airshower_beacon_simulation/Makefile @@ -27,35 +27,56 @@ SEED ?= 12345 all: beacon clocks phases findks vary-fixes reconstruct -beacon: +beacon: generate-beacon signal-to-noise +phases: beacon-phase clock-phase baseline-phase antenna-phase + +###### + +generate-beacon: ./aa_generate_beacon.py -N ${TRACE_N} -P ${TRACE_PRE} -n ${NOISE_SIGMA} -a ${BEAC_AMP} -f ${BEAC_F} -l ${PB_LOW} -u ${PB_HIGH} -d ${BEAC_DECAY} --data-dir ${DATA_DIR} --input-fname ${INPUT_DIR} | tee ${FIG_DIR}/aa.log - ./ab_modify_clocks.py 0 --data-dir ${DATA_DIR} | tee ${FIG_DIR}/ab.log + ./ab_modify_clocks.py 0 --no-save-clocks --data-dir ${DATA_DIR} | tee ${FIG_DIR}/ab.log + +signal-to-noise: ./ac_show_signal_to_noise.py --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} ./view_beaconed_antenna.py 72 -p x -p y -p z -p n -p b --ft --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} -clocks: +# +new-clocks: ./ab_modify_clocks.py ${CLK_DEV} --seed ${SEED} --gaussian --data-dir ${DATA_DIR} -phases: +clocks: + ./ab_modify_clocks.py ${CLK_DEV} --read-clocks-file --seed ${SEED} --gaussian --data-dir ${DATA_DIR} + +reset-clocks: + ./ab_modify_clocks.py 0 --data-dir ${DATA_DIR} + +# +beacon-phase: ./ba_measure_beacon_phase.py --N-mask ${N_MASK} --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} + +clock-phase: ./bb_measure_clock_phase.py --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} + +baseline-phase: ./bc_baseline_phase_deltas.py ${REF_ANTS} --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} + +antenna-phase: ./bd_antenna_phase_deltas.py --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} +# findks: ./ca_period_from_shower.py --input-fname ${INPUT_DIR} --max-k ${MAX_K} --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} -l ${PB_LOW} -u ${PB_HIGH} ./cb_report_measured_antenna_time_offsets.py --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} +# vary-fixes: ./dc_grid_power_time_fixes.py --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} --input-fname ${INPUT_DIR} +# reconstruct: ./da_reconstruction.py --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} --input-fname ${INPUT_DIR} ./db_longitudinal_figure.py --no-show-plots --fig-dir=${FIG_DIR} --data-dir ${DATA_DIR} dist-clean: - rm -vf ${DATA_DIR}/antennas.hdf5 - rm -vf ${DATA_DIR}/ca_breaked_run - rm -vf ${DATA_DIR}/res.pkl - rm -vf ${DATA_DIR}/clocks.csv - rm -vf ${DATA_DIR}/tx.json + rm -vf ${DATA_DIR}/ + rm -vf ${FIG_DIR}/ diff --git a/simulations/airshower_beacon_simulation/README.md b/airshower_beacon_simulation/README.md similarity index 100% rename from simulations/airshower_beacon_simulation/README.md rename to airshower_beacon_simulation/README.md diff --git a/simulations/airshower_beacon_simulation/ZH_airshower/.gitignore b/airshower_beacon_simulation/ZH_airshower/.gitignore similarity index 100% rename from simulations/airshower_beacon_simulation/ZH_airshower/.gitignore rename to airshower_beacon_simulation/ZH_airshower/.gitignore diff --git a/simulations/airshower_beacon_simulation/aa_generate_beacon.py b/airshower_beacon_simulation/aa_generate_beacon.py similarity index 95% rename from simulations/airshower_beacon_simulation/aa_generate_beacon.py rename to airshower_beacon_simulation/aa_generate_beacon.py index 8fb41a6..bf58e45 100755 --- a/simulations/airshower_beacon_simulation/aa_generate_beacon.py +++ b/airshower_beacon_simulation/aa_generate_beacon.py @@ -18,6 +18,7 @@ import lib # {{{ vim marker tx_fname = 'tx.json' antennas_fname = 'antennas.hdf5' +snr_fname = 'snr.json' c_light = lib.c_light def read_antenna_clock_repair_offsets(antennas, mode='all', freq_name=None): @@ -49,6 +50,17 @@ def read_antenna_clock_repair_offsets(antennas, mode='all', freq_name=None): return time_offsets +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): with open(fname, 'w') as fp: return json.dump( @@ -309,6 +321,7 @@ if __name__ == "__main__": # 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 @@ -369,6 +382,7 @@ if __name__ == "__main__": init_antenna_hdf5(antennas_fname, tx, f_beacon) # make beacon per antenna + noise_realisation = np.array([0]) for i, antenna in enumerate(ev.antennas): #TODO: allow to change the samplerate (2, 4, 8 ns) @@ -397,9 +411,9 @@ if __name__ == "__main__": 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 - noise_realisation = 0 - if noise_sigma is not None: - noise_realisation = 1e-6 * rng.normal(0, noise_sigma, size=len(beacon)) # mu V/m + 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]) diff --git a/simulations/airshower_beacon_simulation/ab_modify_clocks.py b/airshower_beacon_simulation/ab_modify_clocks.py similarity index 87% rename from simulations/airshower_beacon_simulation/ab_modify_clocks.py rename to airshower_beacon_simulation/ab_modify_clocks.py index 1532cdd..b0e5978 100755 --- a/simulations/airshower_beacon_simulation/ab_modify_clocks.py +++ b/airshower_beacon_simulation/ab_modify_clocks.py @@ -26,6 +26,8 @@ if __name__ == "__main__": parser.add_argument('-s', '--seed', type=int, nargs='?', default=None, help='Fix seed if supplied.') parser.add_argument('--uniform', action='store_const', const='uniform', dest='dist_type') parser.add_argument('--gaussian', action='store_const', const='gauss', dest='dist_type') + parser.add_argument('-r','--read-clocks-file', action='store_true', dest='read_clocks_file') + parser.add_argument('--no-save-clocks', action='store_false', dest='save_clocks') parser.set_defaults(dist_type='gauss') parser.add_argument('--data-dir', type=str, default="./data", help='Path to Data Directory. (Default: %(default)s)') @@ -62,7 +64,11 @@ if __name__ == "__main__": N_antennas = len(group.keys()) - if True: + if args.read_clocks_file and path.isfile(clocks_fname): # read clock deviations from file + print(f"Reading clocks from {clocks_fname}.") + clock_offsets = np.loadtxt(clocks_fname) + + elif True: # random clock deviations print(f"Modifying clocks upto {max_clock_offset}ns.") clock_offsets = np.zeros( N_antennas ) if args.dist_type == 'uniform': # uniform @@ -110,6 +116,7 @@ if __name__ == "__main__": h5ant['E_AxB'][0, :] += clk_offset # save to simple csv - np.savetxt(clocks_fname, clock_offsets) + if args.save_clocks: + np.savetxt(clocks_fname, clock_offsets) print("Antenna clocks modified in " + str(antennas_fname)) diff --git a/simulations/airshower_beacon_simulation/ac_show_signal_to_noise.py b/airshower_beacon_simulation/ac_show_signal_to_noise.py similarity index 97% rename from simulations/airshower_beacon_simulation/ac_show_signal_to_noise.py rename to airshower_beacon_simulation/ac_show_signal_to_noise.py index 1c8fad9..d813a9d 100755 --- a/simulations/airshower_beacon_simulation/ac_show_signal_to_noise.py +++ b/airshower_beacon_simulation/ac_show_signal_to_noise.py @@ -42,6 +42,7 @@ if __name__ == "__main__": fname_dir = args.data_dir antennas_fname = path.join(fname_dir, beacon.antennas_fname) tx_fname = path.join(fname_dir, beacon.tx_fname) + snr_fname = path.join(fname_dir, beacon.snr_fname) # create fig_dir if fig_dir: @@ -92,6 +93,9 @@ if __name__ == "__main__": N_samples = len(antennas[0].beacon) beacon_snrs = [ lib.signal_to_noise(myfilter(beacon_amp*ant.beacon), myfilter(ant.noise), samplerate=1/dt, signal_band=beacon_pb, noise_band=noise_pb) for i, ant in enumerate(antennas) ] + # write mean and std to file + beacon.write_snr_file(snr_fname, beacon_snrs) + fig, ax = plt.subplots(figsize=figsize) ax.set_title(f"Maximum Beacon/Noise SNR (N_samples:{N_samples:.1e})") ax.set_xlabel("Antenna no.") diff --git a/simulations/airshower_beacon_simulation/ba_measure_beacon_phase.py b/airshower_beacon_simulation/ba_measure_beacon_phase.py similarity index 79% rename from simulations/airshower_beacon_simulation/ba_measure_beacon_phase.py rename to airshower_beacon_simulation/ba_measure_beacon_phase.py index 235de64..b5ea1ca 100755 --- a/simulations/airshower_beacon_simulation/ba_measure_beacon_phase.py +++ b/airshower_beacon_simulation/ba_measure_beacon_phase.py @@ -12,6 +12,8 @@ import numpy as np import aa_generate_beacon as beacon import lib +from lib import figlib + if __name__ == "__main__": from os import path @@ -49,6 +51,7 @@ if __name__ == "__main__": #### fname_dir = args.data_dir antennas_fname = path.join(fname_dir, beacon.antennas_fname) + snr_fname = path.join(fname_dir, beacon.snr_fname) fig_dir = args.fig_dir # set None to disable saving @@ -83,7 +86,8 @@ if __name__ == "__main__": N_antennas = len(group.keys()) # just for funzies - found_data = np.zeros((N_antennas, 3)) + found_data = np.zeros((N_antennas, 3)) # freq, phase, amp + noise_data = np.zeros((N_antennas, 2)) # phase, amp # Determine frequency and phase for i, name in enumerate(group.keys()): @@ -133,6 +137,7 @@ if __name__ == "__main__": # TODO: refine masking # use beacon but remove where E_AxB-Beacon != 0 # Uses the first traces as reference + t_mask = 0 if N_mask and orients[0] != 'B': N_pre, N_post = N_mask//2, N_mask//2 @@ -166,6 +171,18 @@ if __name__ == "__main__": beacon_phases = [ 2*np.pi*t0*f_beacon ] amps = [ 3e-7 ] + # Also try to find the phase from the noise trace if available + if len(h5ant[traces_key]) > 4: + noise_trace = h5ant[traces_key][5] + if np.any(t_mask): # Mask the same area + noise_trace = noise_trace[t_mask] + + real, imag = lib.direct_fourier_transform(f_beacon, t_trace, noise_trace) + noise_phase = np.arctan2(imag, real) + noise_amp = (real**2 + imag**2)**0.5 + + noise_data[i] = noise_phase, noise_amp + # choose highest amp idx = np.argmax(amps) if False and len(beacon_phases) > 1: @@ -246,10 +263,16 @@ if __name__ == "__main__": h5attrs['amplitude'] = amplitude h5attrs['orientation'] = orientation + if noise_phase: + h5attrs['noise_phase'] = noise_phase + h5attrs['noise_amp'] = noise_amp + print("Beacon Phases, Amplitudes and Frequencies written to", antennas_fname) # show histogram of found frequencies if show_plots or fig_dir: + snrs = beacon.read_snr_file(snr_fname) + if True or allow_frequency_fitting: fig, ax = plt.subplots(figsize=figsize) ax.set_xlabel("Frequency") @@ -260,12 +283,45 @@ if __name__ == "__main__": fig.savefig(path.join(fig_dir, path.basename(__file__) + f".hist_freq.pdf")) if True: - fig, ax = plt.subplots(figsize=figsize) - ax.set_xlabel("Amplitudes") + fig, _ = figlib.fitted_histogram_figure(found_data[:,2], fit_distr=[], mean_snr=snrs['mean']) + ax = fig.axes[0] + ax.set_xlabel("Amplitude") ax.set_ylabel("Counts") ax.hist(found_data[:,2], bins='sqrt', density=False) + ax.legend() if fig_dir: fig.savefig(path.join(fig_dir, path.basename(__file__) + f".hist_amp.pdf")) + if (noise_data[0] != 0).any(): + if True: + fig, ax = plt.subplots(figsize=figsize) + ax.set_title("Noise Phases") + ax.set_xlabel("Phase") + ax.set_ylabel("#") + ax.hist(noise_data[:,0], bins='sqrt', density=False) + ax.legend() + if fig_dir: + fig.savefig(path.join(fig_dir, path.basename(__file__) + f".noise.hist_phase.pdf")) + + if True: + fig, ax = plt.subplots(figsize=figsize) + ax.set_title("Noise Phase vs Amplitude") + ax.set_xlabel("Phase") + ax.set_ylabel("Amplitude [a.u.]") + ax.plot(noise_data[:,0], noise_data[:,1], ls='none', marker='x') + if fig_dir: + fig.savefig(path.join(fig_dir, path.basename(__file__) + f".noise.phase_vs_amp.pdf")) + + if True: + fig, _ = figlib.fitted_histogram_figure(noise_data[:,1], fit_distr=['rice', 'rayleigh'], mean_snr=snrs['mean']) + ax = fig.axes[0] + ax.set_title("Noise Amplitudes") + ax.set_xlabel("Amplitude [a.u.]") + ax.set_ylabel("#") + ax.legend() + + if fig_dir: + fig.savefig(path.join(fig_dir, path.basename(__file__) + f".noise.hist_amp.pdf")) + if show_plots: plt.show() diff --git a/airshower_beacon_simulation/bb_measure_clock_phase.py b/airshower_beacon_simulation/bb_measure_clock_phase.py new file mode 100755 index 0000000..2dfb1fe --- /dev/null +++ b/airshower_beacon_simulation/bb_measure_clock_phase.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +# vim: fdm=indent ts=4 + +import h5py +from itertools import combinations, zip_longest +import matplotlib.pyplot as plt +import numpy as np + +import aa_generate_beacon as beacon +import lib +from lib import figlib + +if __name__ == "__main__": + from os import path + import sys + + import os + import matplotlib + if os.name == 'posix' and "DISPLAY" not in os.environ: + matplotlib.use('Agg') + + from scriptlib import MyArgumentParser + parser = MyArgumentParser() + args = parser.parse_args() + + figsize = (12,8) + c_light = lib.c_light + show_plots = args.show_plots + + remove_absolute_phase_offset_first_antenna = True # takes precedence + remove_absolute_phase_offset_minimum = True + + #### + fname_dir = args.data_dir + antennas_fname = path.join(fname_dir, beacon.antennas_fname) + snr_fname = path.join(fname_dir, beacon.snr_fname) + + fig_dir = args.fig_dir # set None to disable saving + + if not path.isfile(antennas_fname): + print("Antenna file cannot be found, did you try generating a beacon?") + sys.exit(1) + + # Read in antennas from file + f_beacon, tx, antennas = beacon.read_beacon_hdf5(antennas_fname) + + # Make sure at least one beacon has been identified + if not hasattr(antennas[0], 'beacon_info') or len(antennas[0].beacon_info) == 0: + print(f"No analysed beacon found for {antennas[0].name}, try running the beacon phase analysis script first.") + sys.exit(1) + + # + N_beacon_freqs = len(antennas[0].beacon_info) + + for freq_name in antennas[0].beacon_info.keys(): + beacon_phases = np.empty( (len(antennas)) ) + for i, ant in enumerate(antennas): + beacon_phases[i] = ant.beacon_info[freq_name]['beacon_phase'] + + f_beacon = antennas[0].beacon_info[freq_name]['freq'] + + clock_phases = lib.remove_antenna_geometry_phase(tx, antennas, f_beacon, beacon_phases, c_light=c_light) + + # Remove the phase from one antenna + # this is a free parameter + # (only required for absolute timing) + if remove_absolute_phase_offset_first_antenna or remove_absolute_phase_offset_minimum: + if remove_absolute_phase_offset_first_antenna: # just take the first phase + minimum_phase = clock_phases[0] + else: # take the minimum + minimum_phase = np.min(clock_phases, axis=-1) + + clock_phases -= minimum_phase + clock_phases = lib.phase_mod(clock_phases) + + # Save to antennas in file + with h5py.File(antennas_fname, 'a') as fp: + h5group = fp['antennas'] + + for i, ant in enumerate(antennas): + h5ant = fp['antennas'][ant.name] + + h5beacon_freq = h5ant['beacon_info'][freq_name] + h5beacon_freq.attrs['clock_phase'] = clock_phases[i] + + # Plot True Phases at their locations + if show_plots or fig_dir: + actual_clock_phases = lib.phase_mod(np.array([ -2*np.pi*a.attrs['clock_offset']*f_beacon for a in antennas ])) + for i in range(2): + plot_residuals = i == 1 + spatial_unit='m' + + antenna_locs = list(zip(*[(ant.x, ant.y) for ant in antennas])) + + scatter_kwargs = {} + scatter_kwargs['cmap'] = 'inferno' + + # Measurements + if not plot_residuals: + title='Clock phases' + color_label='$\\varphi(\\sigma_t)$ [rad]' + + fname_extra='measured.' + + #scatter_kwargs['vmin'] = -np.pi + #scatter_kwargs['vmax'] = +np.pi + + # Plot Clock Phases - True Clock Phases at their location + else: + title='Clock phase Residuals' + color_label='$\\Delta\\varphi(\\sigma_t) = \\varphi_{meas} - \\varphi_{true}$ [rad]' + + fname_extra='residuals.' + + # Modify actual_clock_phases, the same way as clock_phases + # was modified + if remove_absolute_phase_offset_first_antenna or remove_absolute_phase_offset_minimum: + if remove_absolute_phase_offset_first_antenna: # just take the first phase + minimum_phase = actual_clock_phases[0] + else: # take the minimum + minimum_phase = np.min(actual_clock_phases, axis=-1) + + actual_clock_phases -= minimum_phase + actual_clock_phases = lib.phase_mod(actual_clock_phases) + + clock_phase_residuals = lib.phase_mod(clock_phases - actual_clock_phases) + + if not plot_residuals: + loc_c = clock_phases + else: + loc_c = clock_phase_residuals + + ## + ## Geometrical Plot + ## + fig, ax = plt.subplots(figsize=figsize) + + ax.set_xlabel('x' if spatial_unit is None else 'x [{}]'.format(spatial_unit)) + ax.set_ylabel('y' if spatial_unit is None else 'y [{}]'.format(spatial_unit)) + + fig.suptitle(title+'\nf_beacon= {:2.0f}MHz'.format(f_beacon*1e3)) + + sc = ax.scatter(*antenna_locs, c=loc_c, **scatter_kwargs) + fig.colorbar(sc, ax=ax, label=color_label) + + if False: + for i, ant in enumerate(antennas): + ax.text(ant.x, ant.y, ant.name) + + if not True: + ax.plot(tx.x, tx.y, 'X', color='k', markeredgecolor='white') + + if fig_dir: + fig.tight_layout() + fig.savefig(path.join(fig_dir, path.basename(__file__) + f".geom.{fname_extra}F{freq_name}.pdf")) + + ## + ## Histogram + ## + snrs = beacon.read_snr_file(snr_fname) + + fig = figlib.phase_comparison_figure( + loc_c, + actual_clock_phases, + plot_residuals=plot_residuals, + f_beacon=f_beacon, + figsize=figsize, + fit_gaussian=plot_residuals, + mean_snr=snrs['mean'] + ) + + if plot_residuals: + fig.suptitle("Difference between Measured and True Clock phases") + else: + fig.suptitle("Comparison Measured and True Clock Phases") + + axs = fig.get_axes() + axs[-1].set_xlabel(f'Antenna {title} {color_label}') + + # + i=0 + secax = axs[i].child_axes[0] + secax.set_xlabel('Time $\\Delta\\varphi/(2\\pi f_{beac})$ [ns]') + + # + i=1 + axs[i].set_ylabel("Antenna no.") + + # Save figure + if fig_dir: + fig.tight_layout() + fig.savefig(path.join(fig_dir, path.basename(__file__) + f".{fname_extra}F{freq_name}.pdf")) + + print(f"True phases written to", antennas_fname) + + if show_plots: + plt.show() diff --git a/simulations/airshower_beacon_simulation/bc_baseline_phase_deltas.py b/airshower_beacon_simulation/bc_baseline_phase_deltas.py similarity index 76% rename from simulations/airshower_beacon_simulation/bc_baseline_phase_deltas.py rename to airshower_beacon_simulation/bc_baseline_phase_deltas.py index a3c067f..0175df1 100755 --- a/simulations/airshower_beacon_simulation/bc_baseline_phase_deltas.py +++ b/airshower_beacon_simulation/bc_baseline_phase_deltas.py @@ -8,6 +8,7 @@ import numpy as np import aa_generate_beacon as beacon import lib +from lib import figlib if __name__ == "__main__": @@ -108,45 +109,42 @@ if __name__ == "__main__": for i in range(2): plot_residuals = i == 1 - colors = ['blue', 'orange'] - fig, axs = plt.subplots(2, 1, sharex=True, figsize=figsize) + true_phases = my_phase_diffs + measured_phases = phase_diffs[:,1] - if True: - phase2time = lambda x: x/(2*np.pi*f_beacon) - time2phase = lambda x: 2*np.pi*x*f_beacon - secax = axs[0].secondary_xaxis('top', functions=(phase2time, time2phase)) - secax.set_xlabel('Time $\\Delta\\varphi/(2\\pi f_{beac})$ [ns]') + hist_kwargs = {} + if plot_residuals: + measured_phases = lib.phase_mod(measured_phases - true_phases) + hist_kwargs['histtype'] = 'stepfilled' + + fig = figlib.phase_comparison_figure( + measured_phases, + true_phases, + plot_residuals=plot_residuals, + f_beacon=f_beacon, + figsize=figsize, + hist_kwargs=hist_kwargs, + fit_gaussian=plot_residuals, + ) + + axs = fig.get_axes() if plot_residuals: - phase_residuals = lib.phase_mod(phase_diffs[:,1] - my_phase_diffs) - fig.suptitle("Difference between Measured and Actual phase difference\n for Baselines (i,j" + (')' if not ref_ant_id else '='+str([ int(a.name) for a in ref_ants])+')')) axs[-1].set_xlabel("Baseline Phase Residual $\\Delta\\varphi_{ij_{meas}} - \\Delta\\varphi_{ij_{true}}$ [rad]") else: fig.suptitle("Comparison Measured and Actual phase difference\n for Baselines (i,j" + (')' if not ref_ant_id else '='+str([ int(a.name) for a in ref_ants])+')')) axs[-1].set_xlabel("Baseline Phase $\\Delta\\varphi_{ij}$ [rad]") - + # i=0 - axs[i].set_ylabel("#") - if plot_residuals: - axs[i].hist(phase_residuals, bins='sqrt', density=False, alpha=0.8, color=colors[0]) - else: - axs[i].hist(phase_diffs[:,1], bins='sqrt', density=False, alpha=0.8, color=colors[0], ls='solid' , histtype='step', label='Measured') - axs[i].hist(my_phase_diffs, bins='sqrt', density=False, alpha=0.8, color=colors[1], ls='dashed', histtype='step', label='Actual') - + secax = axs[i].child_axes[0] + secax.set_xlabel('Time $\\Delta\\varphi/(2\\pi f_{beac})$ [ns]') + # i=1 axs[i].set_ylabel("Baseline no.") - if plot_residuals: - axs[i].plot(phase_residuals, np.arange(N_base), alpha=0.6, ls='none', marker='x', color=colors[0]) - else: - axs[i].plot(phase_diffs[:,1], np.arange(N_base), alpha=0.8, color=colors[0], ls='none', marker='x', label='calculated') - axs[i].plot(my_phase_diffs, np.arange(N_base), alpha=0.8, color=colors[1], ls='none', marker='+', label='actual time shifts') - - axs[i].legend() - fig.tight_layout() if fig_dir: extra_name = "measured" diff --git a/simulations/airshower_beacon_simulation/bd_antenna_phase_deltas.py b/airshower_beacon_simulation/bd_antenna_phase_deltas.py similarity index 83% rename from simulations/airshower_beacon_simulation/bd_antenna_phase_deltas.py rename to airshower_beacon_simulation/bd_antenna_phase_deltas.py index 6abd475..4064640 100755 --- a/simulations/airshower_beacon_simulation/bd_antenna_phase_deltas.py +++ b/airshower_beacon_simulation/bd_antenna_phase_deltas.py @@ -7,9 +7,11 @@ import matplotlib.pyplot as plt from matplotlib.colors import Normalize import matplotlib as mpl import numpy as np +import json import aa_generate_beacon as beacon import lib +from lib import figlib if __name__ == "__main__": @@ -118,6 +120,11 @@ if __name__ == "__main__": mean_clock_phase = np.nanmean(clock_phase_matrix[my_mask], axis=0) std_clock_phase = np.nanstd( clock_phase_matrix[my_mask], axis=0) + # Remove the mean from the matrix + if False: + clock_phase_matrix = clock_phase_matrix - np.mean(mean_clock_phase) + mean_clock_phase = np.nanmean(clock_phase_matrix[my_mask], axis=0) + std_clock_phase = np.nanstd( clock_phase_matrix[my_mask], axis=0) # Show resulting matrix as figure if True: @@ -174,45 +181,40 @@ if __name__ == "__main__": global_phase_shift = actual_antenna_phase_shifts[0] - mean_clock_phase[0] actual_antenna_phase_shifts = lib.phase_mod(actual_antenna_phase_shifts - global_phase_shift ) + fit_info = {} for i in range(2): plot_residuals = i == 1 - colors = ['blue', 'orange'] + true_phases = actual_antenna_phase_shifts + measured_phases = mean_clock_phase - fig, axs = plt.subplots(2, 1, sharex=True, figsize=figsize) + hist_kwargs = {} + if plot_residuals: + measured_phases = lib.phase_mod(measured_phases - actual_antenna_phase_shifts) - if True: - phase2time = lambda x: x/(2*np.pi*f_beacon) - time2phase = lambda x: 2*np.pi*x*f_beacon - secax = axs[0].secondary_xaxis('top', functions=(phase2time, time2phase)) - secax.set_xlabel('Time $\\Delta\\varphi/(2\\pi f_{beac})$ [ns]') + fig, _fit_info = figlib.phase_comparison_figure( + measured_phases, + true_phases, + plot_residuals=plot_residuals, + f_beacon=f_beacon, + figsize=figsize, + hist_kwargs=hist_kwargs, + fit_gaussian=plot_residuals, + return_fit_info = True, + ) + + axs = fig.get_axes() if plot_residuals: - phase_residuals = lib.phase_mod(mean_clock_phase - actual_antenna_phase_shifts) fig.suptitle("Difference between Measured and Actual phases (minus global phase)\n for Antenna $i$") - axs[-1].set_xlabel("Antenna Phase Residual $\\Delta_\\varphi$") + axs[-1].set_xlabel("Antenna Mean Phase Residual $\\Delta_\\varphi$") else: fig.suptitle("Comparison Measured and Actual phases (minus global phase)\n for Antenna $i$") - axs[-1].set_xlabel("Antenna Phase $\\varphi$") - - - i=0 - axs[i].set_ylabel("#") - if plot_residuals: - axs[i].hist(phase_residuals, bins='sqrt', alpha=0.8, color=colors[0]) - else: - axs[i].hist(mean_clock_phase, bins='sqrt', density=False, alpha=0.8, color=colors[0], ls='solid' , histtype='step', label='Measured') - axs[i].hist(actual_antenna_phase_shifts, bins='sqrt', density=False, alpha=0.8, color=colors[1], ls='dashed', histtype='step', label='Actual') - + axs[-1].set_xlabel("Antenna Mean Phase $\\varphi$") i=1 axs[i].set_ylabel("Antenna no.") - if plot_residuals: - axs[i].plot(phase_residuals, np.arange(N_ant), alpha=0.6, ls='none', marker='x', color=colors[0]) - else: - axs[i].errorbar(mean_clock_phase, np.arange(N_ant), yerr=std_clock_phase, marker='4', alpha=0.7, ls='none', color=colors[0], label='Measured') - axs[i].plot(actual_antenna_phase_shifts, antenna_names, ls='none', marker='3', alpha=0.8, color=colors[1], label='Actual') + #axs[i].errorbar(mean_clock_phase, np.arange(N_ant), yerr=std_clock_phase, marker='4', alpha=0.7, ls='none', color=colors[0], label='Measured') - axs[i].legend() fig.tight_layout() if fig_dir: @@ -221,6 +223,19 @@ if __name__ == "__main__": extra_name = "residuals" fig.savefig(path.join(fig_dir, path.basename(__file__) + f".phase.{extra_name}.pdf")) + # Save fit_info to data file + if fname_dir and plot_residuals: + with open(path.join(fname_dir, 'phase_time_residuals.json'), 'w') as fp: + json.dump( + { + 'mean': np.mean(measured_phases), + 'std': np.std(measured_phases), + 'values': measured_phases.tolist(), + }, + fp) + + + ########################## ########################## ########################## diff --git a/simulations/airshower_beacon_simulation/ca_period_from_shower.py b/airshower_beacon_simulation/ca_period_from_shower.py similarity index 100% rename from simulations/airshower_beacon_simulation/ca_period_from_shower.py rename to airshower_beacon_simulation/ca_period_from_shower.py diff --git a/simulations/airshower_beacon_simulation/cb_report_measured_antenna_time_offsets.py b/airshower_beacon_simulation/cb_report_measured_antenna_time_offsets.py similarity index 69% rename from simulations/airshower_beacon_simulation/cb_report_measured_antenna_time_offsets.py rename to airshower_beacon_simulation/cb_report_measured_antenna_time_offsets.py index 547216f..2871a03 100755 --- a/simulations/airshower_beacon_simulation/cb_report_measured_antenna_time_offsets.py +++ b/airshower_beacon_simulation/cb_report_measured_antenna_time_offsets.py @@ -10,6 +10,7 @@ import numpy as np from os import path import aa_generate_beacon as beacon +from lib import figlib if __name__ == "__main__": import sys @@ -76,15 +77,28 @@ if __name__ == "__main__": for i in range(2): plot_residuals = i == 1 - colors = ['blue', 'orange'] - fig, axs = plt.subplots(2, 1, sharex=True, figsize=figsize) - if True: - phase2time = lambda x: x/(2*np.pi*f_beacon) - time2phase = lambda x: 2*np.pi*x*f_beacon - secax = axs[0].secondary_xaxis('top', functions=(time2phase, phase2time)) - secax.set_xlabel('Phase $2\\pi t f_{beac}$ [rad]') + true_phases = actual_time_shifts + measured_phases = measured_time_shifts + + hist_kwargs = {} + if plot_residuals: + measured_phases = measured_phases - true_phases + hist_kwargs['histtype'] = 'stepfilled' + + fig = figlib.phase_comparison_figure( + measured_phases, + true_phases, + plot_residuals=plot_residuals, + f_beacon=f_beacon, + figsize=figsize, + hist_kwargs=hist_kwargs, + secondary_axis='phase', + fit_gaussian=True, + ) + + axs = fig.get_axes() if plot_residuals: time_shift_residuals = measured_time_shifts - actual_time_shifts @@ -94,26 +108,6 @@ if __name__ == "__main__": fig.suptitle("Comparison Measured and Actual clock offset") axs[-1].set_xlabel("Antenna Time Offset $t_c = \\left(\\frac{\\Delta\\varphi}{2\\pi} + k\\right) / f_{beac}$ [ns]") - i=0 - axs[i].set_ylabel("#") - if plot_residuals: - axs[i].hist(time_shift_residuals, bins='sqrt', alpha=0.8, color=colors[0]) - else: - axs[i].hist(measured_time_shifts, bins='sqrt', density=False, alpha=0.8, color=colors[0], ls='solid' , histtype='step', label='Measured') - axs[i].hist(actual_time_shifts, bins='sqrt', density=False, alpha=0.8, color=colors[1], ls='dashed', histtype='step', label='Actual') - - - i=1 - axs[i].set_ylabel("Antenna no.") - if plot_residuals: - axs[i].plot(time_shift_residuals, np.arange(N_ant), alpha=0.6, ls='none', marker='x', color=colors[0]) - else: - axs[i].errorbar(measured_time_shifts, np.arange(N_ant), yerr=None, marker='4', alpha=0.7, ls='none', color=colors[0], label='Measured') - axs[i].plot(actual_time_shifts, antenna_names, ls='none', marker='3', alpha=0.8, color=colors[1], label='Actual') - - axs[i].legend() - fig.tight_layout() - if fig_dir: extra_name = "comparison" if plot_residuals: diff --git a/simulations/airshower_beacon_simulation/da_reconstruction.py b/airshower_beacon_simulation/da_reconstruction.py similarity index 99% rename from simulations/airshower_beacon_simulation/da_reconstruction.py rename to airshower_beacon_simulation/da_reconstruction.py index f54ec7c..2cf79d5 100755 --- a/simulations/airshower_beacon_simulation/da_reconstruction.py +++ b/airshower_beacon_simulation/da_reconstruction.py @@ -46,6 +46,7 @@ if __name__ == "__main__": fig_subdir = path.join(fig_dir, 'reconstruction') show_plots = args.show_plots + apply_signal_window_from_max = True remove_beacon_from_traces = True #### diff --git a/simulations/airshower_beacon_simulation/db_longitudinal_figure.py b/airshower_beacon_simulation/db_longitudinal_figure.py similarity index 100% rename from simulations/airshower_beacon_simulation/db_longitudinal_figure.py rename to airshower_beacon_simulation/db_longitudinal_figure.py diff --git a/simulations/airshower_beacon_simulation/dc_grid_power_time_fixes.py b/airshower_beacon_simulation/dc_grid_power_time_fixes.py similarity index 62% rename from simulations/airshower_beacon_simulation/dc_grid_power_time_fixes.py rename to airshower_beacon_simulation/dc_grid_power_time_fixes.py index d1e460d..1d834ed 100755 --- a/simulations/airshower_beacon_simulation/dc_grid_power_time_fixes.py +++ b/airshower_beacon_simulation/dc_grid_power_time_fixes.py @@ -19,6 +19,65 @@ import lib from lib import rit +def save_overlapping_traces_figure(test_location, ev, N_plot = 30, wx=200, title_extra=None, fname_distinguish='', fig_dir=None, **fig_kwargs): + P, t_, a_, a_sum, t_sum = rit.pow_and_time(test_location, ev, dt=1) + + fig, axs = plt.subplots(**fig_kwargs) + axs.set_title("Antenna traces" + (("\n" + title_extra) if title_extra is not None else '') ) + axs.set_xlabel("Time [ns]") + axs.set_ylabel("Amplitude [$\\mu V/m$]") + if False: + text_loc = (0.02, 0.95) + axs.text(*text_loc, '[' + ', '.join(['{:.2e}'.format(x) for x in test_location]) + ']', ha='left', transform=axs.transAxes) + + a_max = [ np.amax(ant.E_AxB) for ant in ev.antennas ] + power_sort_idx = np.argsort(a_max) + + for i, idx in enumerate(reversed(power_sort_idx)): + if i >= N_plot: + break + + alpha = max(0.4, 1/N_plot) + + axs.plot(t_[idx], a_[idx], color='r', alpha=alpha, lw=2) + + if fig_dir: + if fname_distinguish: + fname_distinguish = "." + fname_distinguish + + fig.tight_layout() + + fig.savefig(path.join(fig_dir, path.basename(__file__) + f'{fname_distinguish}.trace_overlap.{case}.pdf')) + fig.savefig(path.join(fig_dir, path.basename(__file__) + f'{fname_distinguish}.trace_overlap.{case}.png'), transparent=True) + + # Take center between t_low and t_high + if True: + orig_xlims = axs.get_xlim() + + if not True: # t_high and t_low from strongest signal + t_low = np.min(t_[power_sort_idx[-1]]) + t_high = np.max(t_[power_sort_idx[-1]]) + else: # take t_high and t_low from plotted signals + a = [np.min(t_[idx]) for idx in power_sort_idx[-N_plot:]] + t_low = np.nanmin(a) + b = [np.max(t_[idx]) for idx in power_sort_idx[-N_plot:]] + t_high = np.nanmax(b) + + if False: + axs.plot(a, [0]*N_plot, 'gx', ms=10) + axs.plot(b, [0]*N_plot, 'b+', ms=10) + + center_x = (t_high - t_low)/2 + t_low + + low_xlim = max(orig_xlims[0], center_x - wx) + high_xlim = min(orig_xlims[1], center_x + wx) + + axs.set_xlim(low_xlim, high_xlim) + fig.savefig(path.join(fig_dir, path.basename(__file__) + f'{fname_distinguish}.trace_overlap.zoomed.{case}.pdf')) + fig.savefig(path.join(fig_dir, path.basename(__file__) + f'{fname_distinguish}.trace_overlap.zoomed.{case}.png'), transparent=True) + + return fig + if __name__ == "__main__": valid_cases = ['no_offset', 'repair_none', 'repair_phases', 'repair_all'] @@ -95,6 +154,9 @@ if __name__ == "__main__": 'scale02d': scale02d, } + N_plot = 30 + trace_zoom_wx = 100 + plot_titling = { 'no_offset': "no clock offset", 'repair_none': "unrepaired clock offset", @@ -175,6 +237,9 @@ if __name__ == "__main__": backup_antenna_t = [ ant.t for ant in ev.antennas ] backup_antenna_t_AxB = [ ant.t_AxB for ant in ev.antennas ] + fig = save_overlapping_traces_figure([0,0,0], ev, N_plot=1, wx=trace_zoom_wx, title_extra = plot_titling[case], fname_distinguish=f'single', fig_dir=fig_dir, figsize=figsize) + plt.close(fig) + with joblib.parallel_backend("loky"): for case in wanted_cases: print(f"Starting {case} figure") @@ -195,73 +260,29 @@ if __name__ == "__main__": if i == 0: # Specifically compare the times - print(bak_ants[i].t[0], ev.antennas[i].t[0], ev.antennas[i].t[0], ev.antennas[i].attrs['clock_offset'], measured_offsets[i]) + print("backup time, time with measured_offset, true clock offset, measured clock offset") + print(bak_ants[i].t[0], ev.antennas[i].t[0], ev.antennas[i].attrs['clock_offset'], measured_offsets[i]) # # Plot overlapping traces at 0,0,0 # - if True: - P, t_, a_, a_sum, t_sum = rit.pow_and_time([0,0,0], ev, dt=1) - - fig, axs = plt.subplots(figsize=figsize) - axs.set_title("Antenna traces" + "\n" + plot_titling[case]) - axs.set_xlabel("Time [ns]") - axs.set_ylabel("Amplitude [$\\mu V/m$]") - - a_max = [ np.amax(ant.E_AxB) for ant in ev.antennas ] - power_sort_idx = np.argsort(a_max) - - N_plot = 30 - for i, idx in enumerate(reversed(power_sort_idx)): - if i > N_plot: - break - - alpha = max(0.4, 1/len(a_)) - - axs.plot(t_[idx], a_[idx], color='r', alpha=alpha, lw=2) - - if fig_dir: - fig.tight_layout() - - fig.savefig(path.join(fig_dir, path.basename(__file__) + f'.trace_overlap.{case}.pdf')) - fig.savefig(path.join(fig_dir, path.basename(__file__) + f'.trace_overlap.{case}.png'), transparent=True) - - # Take center between t_low and t_high - if True: - orig_xlims = axs.get_xlim() - - if not True: # t_high and t_low from strongest signal - t_low = np.min(t_[power_sort_idx[-1]]) - t_high = np.max(t_[power_sort_idx[-1]]) - else: # take t_high and t_low from plotted signals - a = [np.min(t_[idx]) for idx in power_sort_idx[-N_plot:]] - axs.plot(a, [0]*N_plot, 'gx', ms=10) - t_low = np.nanmin(a) - b = [np.max(t_[idx]) for idx in power_sort_idx[-N_plot:]] - axs.plot(b, [0]*N_plot, 'b+', ms=10) - t_high = np.nanmax(b) - - center_x = (t_high - t_low)/2 + t_low - wx = 200 - - low_xlim = max(orig_xlims[0], center_x - wx) - high_xlim = min(orig_xlims[1], center_x + wx) - - axs.set_xlim(low_xlim, high_xlim) - fig.savefig(path.join(fig_dir, path.basename(__file__) + f'.trace_overlap.zoomed.{case}.pdf')) - fig.savefig(path.join(fig_dir, path.basename(__file__) + f'.trace_overlap.zoomed.{case}.png'), transparent=True) - - - if True: - continue + fig = save_overlapping_traces_figure([0,0,0], ev, N_plot=N_plot, wx=trace_zoom_wx, title_extra = plot_titling[case], fname_distinguish=f'{case}.0', fig_dir=fig_dir, figsize=figsize) + plt.close(fig) # Measure power on grid + # and plot overlapping traces at position with highest power for scalename, scale in scales.items(): wx, wy = scale, scale print(f"Starting grid measurement for figure {case} with {scalename}") - xx, yy, p, maxp = rit.shower_plane_slice(ev, X=X, Nx=Nx, Ny=Nx, wx=wx, wy=wy) + xx, yy, p, maxp_loc = rit.shower_plane_slice(ev, X=X, Nx=Nx, Ny=Nx, wx=wx, wy=wy, zgr=zgr) - fig, axs = rit.slice_figure(ev, X, xx, yy, p, mode='sp') + fig, axs = rit.slice_figure(ev, X, xx, yy, p, mode='sp', scatter_kwargs=dict( + vmax=1e5, + vmin=0, + s=150, + cmap='inferno', +# edgecolor='black', + )) suptitle = fig._suptitle.get_text() fig.suptitle("") @@ -271,7 +292,24 @@ if __name__ == "__main__": if fig_dir: fig.tight_layout() fig.savefig(path.join(fig_dir, path.basename(__file__) + f'.X{X}.{case}.{scalename}.pdf')) + plt.close(fig) + + # + # Plot overlapping traces at highest power of each scale + # + fig = save_overlapping_traces_figure(maxp_loc, ev, N_plot=N_plot, wx=trace_zoom_wx, title_extra = plot_titling[case] + ', ' + scalename + ' best', fname_distinguish=scalename+'.best', fig_dir=fig_dir, figsize=figsize) + + # + # and plot overlapping traces at two other locations + # + if True: + for dist in [ 0.5, 5, 10, 50, 100]: + # only add distance horizontally + location = maxp_loc + np.sqrt(dist*1e3)*np.array([1,1,0]) + + fig = save_overlapping_traces_figure(location, ev, N_plot=N_plot, wx=wx, title_extra = plot_titling[case] + ', ' + scalename + f', x + {dist}km', fname_distinguish=f'{scalename}.{dist}', fig_dir=fig_dir, figsize=figsize) + plt.close(fig) + if args.show_plots: plt.show() - diff --git a/simulations/airshower_beacon_simulation/lib/__init__.py b/airshower_beacon_simulation/lib/__init__.py similarity index 100% rename from simulations/airshower_beacon_simulation/lib/__init__.py rename to airshower_beacon_simulation/lib/__init__.py diff --git a/simulations/airshower_beacon_simulation/lib/atmocal b/airshower_beacon_simulation/lib/atmocal similarity index 100% rename from simulations/airshower_beacon_simulation/lib/atmocal rename to airshower_beacon_simulation/lib/atmocal diff --git a/airshower_beacon_simulation/lib/earsim b/airshower_beacon_simulation/lib/earsim new file mode 160000 index 0000000..6ef8090 --- /dev/null +++ b/airshower_beacon_simulation/lib/earsim @@ -0,0 +1 @@ +Subproject commit 6ef809020477cf78415b9fa733d4fbb4a74102a1 diff --git a/airshower_beacon_simulation/lib/figlib.py b/airshower_beacon_simulation/lib/figlib.py new file mode 100644 index 0000000..36f1c61 --- /dev/null +++ b/airshower_beacon_simulation/lib/figlib.py @@ -0,0 +1,219 @@ +import matplotlib.pyplot as plt +import numpy as np + +import scipy.stats as stats +from itertools import zip_longest + +def phase_comparison_figure( + measured_phases, + true_phases, + plot_residuals=True, + f_beacon=None, + hist_kwargs={}, + sc_kwargs={}, + text_kwargs={}, + colors=['blue', 'orange'], + legend_on_scatter=True, + secondary_axis='time', + fit_gaussian=False, + mean_snr=None, + return_fit_info=False, + **fig_kwargs + ): + """ + Create a figure comparing measured_phase against true_phase + by both plotting the values, and the residuals. + """ + default_fig_kwargs = dict(sharex=True) + default_hist_kwargs = dict(bins='sqrt', density=False, alpha=0.8, histtype='step') + default_text_kwargs = dict(fontsize=14, verticalalignment='top') + default_sc_kwargs = dict(alpha=0.6, ls='none') + + fig_kwargs = {**default_fig_kwargs, **fig_kwargs} + hist_kwargs = {**default_hist_kwargs, **hist_kwargs} + text_kwargs = {**default_text_kwargs, **text_kwargs} + sc_kwargs = {**default_sc_kwargs, **sc_kwargs} + + do_hist_plot = hist_kwargs is not False + do_scatter_plot = sc_kwargs is not False + + fig, axs = plt.subplots(0+do_hist_plot+do_scatter_plot, 1, **fig_kwargs) + + if not hasattr(axs, '__len__'): + axs = [axs] + + if f_beacon and secondary_axis in ['phase', 'time']: + phase2time = lambda x: x/(2*np.pi*f_beacon) + time2phase = lambda x: 2*np.pi*x*f_beacon + + if secondary_axis == 'time': + functions = (phase2time, time2phase) + label = 'Time $\\varphi/(2\\pi f_{beac})$ [ns]' + else: + functions = (time2phase, phase2time) + label = 'Phase $2\\pi t f_{beac}$ [rad]' + + secax = axs[0].secondary_xaxis('top', functions=functions) + + # Histogram + fit_info = {} + if do_hist_plot: + i=0 + + axs[i].set_ylabel("#") + + this_kwargs = dict( + ax = axs[i], + text_kwargs=text_kwargs, + hist_kwargs={**hist_kwargs, **dict(label='Measured', color=colors[0], ls='solid')}, + mean_snr=mean_snr, + ) + + if fit_gaussian: + this_kwargs['fit_distr'] = 'gaussian' + + _, fit_info = fitted_histogram_figure( + measured_phases, + **this_kwargs + ) + + if not plot_residuals: # also plot the true clock phases + _bins = fit_info['bins'] + axs[i].hist(true_phases, color=colors[1], label='Actual', ls='dashed', **{**hist_kwargs, **dict(bins=_bins)}) + + # Scatter plot + if do_scatter_plot: + i=1 + axs[i].set_ylabel("Antenna no.") + axs[i].plot(measured_phases, np.arange(len(measured_phases)), marker='x' if plot_residuals else '3', color=colors[0], label='Measured', **sc_kwargs) + + if not plot_residuals: # also plot the true clock phases + axs[i].plot(true_phases, np.arange(len(true_phases)), marker='4', color=colors[1], label='Actual', **sc_kwargs) + + if not plot_residuals and legend_on_scatter: + axs[i].legend() + + fig.tight_layout() + + if return_fit_info: + return fig, fit_info + + return fig + + +def fitted_histogram_figure( + amplitudes, + fit_distr = None, + calc_chisq = True, + text_kwargs={}, + hist_kwargs={}, + mean_snr = None, + ax = None, + **fig_kwargs + ): + """ + Create a figure showing $amplitudes$ as a histogram. + If fit_distr is a (list of) string, also fit the respective + distribution function and show the parameters on the figure. + """ + default_hist_kwargs = dict(bins='sqrt', density=False, alpha=0.8, histtype='step', label='hist') + default_text_kwargs = dict(fontsize=14, verticalalignment='top') + + if isinstance(fit_distr, str): + fit_distr = [fit_distr] + + hist_kwargs = {**default_hist_kwargs, **hist_kwargs} + text_kwargs = {**default_text_kwargs, **text_kwargs} + + if ax is None: + fig, ax = plt.subplots(1,1, **fig_kwargs) + else: + fig = ax.get_figure() + + text_kwargs['transform'] = ax.transAxes + + counts, bins, _patches = ax.hist(amplitudes, **hist_kwargs) + + fit_info = [] + if fit_distr: + min_x = min(amplitudes) + max_x = max(amplitudes) + + dx = bins[1] - bins[0] + scale = len(amplitudes) * dx + + xs = np.linspace(min_x, max_x) + + for distr in fit_distr: + fit_params2text_params = lambda x: x + + if 'rice' == distr: + name = "Rice" + param_names = [ "$\\nu$", "$\\sigma$" ] + distr_func = stats.rice + + fit_params2text_params = lambda x: (x[0]*x[1], x[1]) + + elif 'gaussian' == distr: + name = "Norm" + param_names = [ "$\\mu$", "$\\sigma$" ] + distr_func = stats.norm + + elif 'rayleigh' == distr: + name = "Rayleigh" + param_names = [ "$\\sigma$" ] + distr_func = stats.rayleigh + + fit_params2text_params = lambda x: (x[0]+x[1]/2,) + + else: + raise ValueError('Unknown distribution function '+distr) + + label = name +"(" + ','.join(param_names) + ')' + + fit_params = distr_func.fit(amplitudes) + fit_ys = distr_func.pdf(xs, *fit_params) + + ax.plot(xs, fit_ys*scale, label=label) + + chisq_strs = [] + if calc_chisq: + ct = np.diff(distr_func.cdf(bins, *fit_params))*np.sum(counts) + c2t = stats.chisquare(counts, ct, ddof=len(fit_params)) + chisq_strs = [ + f"$\\chi^2$/dof = {c2t[0]: .2g}/{len(fit_params)}" + ] + + # change parameters if needed + text_fit_params = fit_params2text_params(fit_params) + + text_str = "\n".join( + [label] + + + [ f"{param} = {value: .2e}" for param, value in zip_longest(param_names, text_fit_params, fillvalue='?') ] + + + chisq_strs + ) + + this_info = { + 'name': name, + 'param_names': param_names, + 'param_values': text_fit_params, + 'text_str': text_str, + } + + if chisq_strs: + this_info['chisq'] = c2t[0] + this_info['dof'] = len(fit_params) + + fit_info.append(this_info) + + loc = (0.02, 0.95) + ax.text(*loc, "\n\n".join([info['text_str'] for info in fit_info]), **{**text_kwargs, **dict(ha='left')}) + + if mean_snr: + text_str = f"$\\langle SNR \\rangle$ = {mean_snr: .1e}" + loc = (0.98, 0.95) + ax.text(*loc, text_str, **{**text_kwargs, **dict(ha='right')}) + + return fig, dict(fit_info=fit_info, counts=counts, bins=bins) diff --git a/simulations/airshower_beacon_simulation/lib/lib.py b/airshower_beacon_simulation/lib/lib.py similarity index 100% rename from simulations/airshower_beacon_simulation/lib/lib.py rename to airshower_beacon_simulation/lib/lib.py diff --git a/simulations/airshower_beacon_simulation/lib/rit.py b/airshower_beacon_simulation/lib/rit.py similarity index 96% rename from simulations/airshower_beacon_simulation/lib/rit.py rename to airshower_beacon_simulation/lib/rit.py index db2704c..db0ae45 100644 --- a/simulations/airshower_beacon_simulation/lib/rit.py +++ b/airshower_beacon_simulation/lib/rit.py @@ -58,6 +58,8 @@ def pow_and_time(test_loc,ev,dt=1.0): a_sum = np.add(a_sum,a_int) if len(a_sum) != 0: P = np.sum(np.square(np.absolute(np.fft.fft(a_sum)))) + # normalise P with the length of the traces + P = P/( t_sum[-1] - t_sum[0]) else: print("ERROR, a_sum lenght = 0", "tmin ",t_min, @@ -112,11 +114,21 @@ def shower_plane_slice(e,X=750.,Nx=10,Ny=10,wx=1e3,wy=1e3,xoff=0,yoff=0,zgr=0,n_ return xx,yy,p,locs[np.argmax(p)] -def slice_figure(e,X,xx,yy,p,mode='horizontal'): +def slice_figure(e,X,xx,yy,p,mode='horizontal', scatter_kwargs={}, colorbar_kwargs={'label':'Power'}): + scatter_kwargs = { + **dict( + cmap='Spectral_r', + alpha=0.9, + s=30 + ), + **scatter_kwargs + } + + fig, axs = plt.subplots(1,figsize=(10,8)) fig.suptitle(r'E = %.1f EeV, $\theta$ = %.1f$^\circ$, $\phi$ = %.1f$^\circ$ X = %.f'%(e.energy,e.zenith,e.azimuth,X)) - sc = axs.scatter(xx/1e3,yy/1e3,c=p,cmap='Spectral_r',alpha=0.6) - fig.colorbar(sc,ax=axs) + sc = axs.scatter(xx/1e3,yy/1e3,c=p,**scatter_kwargs) + fig.colorbar(sc,ax=axs, **colorbar_kwargs) zgr = 0 + e.core[2] dX = atm.distance_to_slant_depth(np.deg2rad(e.zenith),X,zgr) xc = np.sin(np.deg2rad(e.zenith))*np.cos(np.deg2rad(e.azimuth))* dX diff --git a/simulations/airshower_beacon_simulation/lib/snr.py b/airshower_beacon_simulation/lib/snr.py similarity index 100% rename from simulations/airshower_beacon_simulation/lib/snr.py rename to airshower_beacon_simulation/lib/snr.py diff --git a/simulations/airshower_beacon_simulation/lib/tests/lib b/airshower_beacon_simulation/lib/tests/lib similarity index 100% rename from simulations/airshower_beacon_simulation/lib/tests/lib rename to airshower_beacon_simulation/lib/tests/lib diff --git a/simulations/airshower_beacon_simulation/lib/tests/test_beacon_fourier.py b/airshower_beacon_simulation/lib/tests/test_beacon_fourier.py similarity index 100% rename from simulations/airshower_beacon_simulation/lib/tests/test_beacon_fourier.py rename to airshower_beacon_simulation/lib/tests/test_beacon_fourier.py diff --git a/simulations/airshower_beacon_simulation/lib/tests/test_calculated_phase_measurement.py b/airshower_beacon_simulation/lib/tests/test_calculated_phase_measurement.py similarity index 100% rename from simulations/airshower_beacon_simulation/lib/tests/test_calculated_phase_measurement.py rename to airshower_beacon_simulation/lib/tests/test_calculated_phase_measurement.py diff --git a/simulations/airshower_beacon_simulation/matrix_base/base b/airshower_beacon_simulation/matrix_base/base similarity index 100% rename from simulations/airshower_beacon_simulation/matrix_base/base rename to airshower_beacon_simulation/matrix_base/base diff --git a/airshower_beacon_simulation/matrix_base/bin/collect_figures.py b/airshower_beacon_simulation/matrix_base/bin/collect_figures.py new file mode 100755 index 0000000..8d36272 --- /dev/null +++ b/airshower_beacon_simulation/matrix_base/bin/collect_figures.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +import os +import re +import tempfile + +def parse_env_file(fname): + envs = {} + env_reg = re.compile('^(?:\s*export)?\s*(.*)\s*=\s*(.*)\s*$') + + with open(fname, 'r') as f: + for line in f: + if '=' not in line: + continue + + m = env_reg.match(line) + envs[m.group(1)] = m.group(2) + + return envs + +def mutilate_figure_name(fig_fname, envs): + return fig_fname + +if __name__ == "__main__": + from argparse import ArgumentParser + + parser = ArgumentParser() + #parser.add_argument('-f', '--figures', nargs='*') + parser.add_argument("-d", "--directories", nargs='*') + parser.add_argument('out_dir', default='./figures', type=str) + + args = parser.parse_args() + + os.makedirs(args.out_dir, exist_ok=True) + + #with open(args.out_file, 'w') as fp: + if True: + for d in args.directories: + d = os.path.realpath(d) + fig_dir = os.path.join(d, 'figures') + env_fname = os.path.join(d, 'env.sh') + + if not os.path.exists(fig_dir): + print(f"Cannot find {fig_dir}") + continue + + ## parse properties from env.sh + #envs = parse_env_file(env_fname) + #print(envs, fig_dir) + + for f in os.listdir(fig_dir): + fname, ext = os.path.splitext(f) + + dname = os.path.basename(d) + + if ext not in ['.pdf', '.png']: + continue + + link_name = fname + "_" + dname + ext + + target = os.path.realpath(os.path.join(fig_dir, f)) + tmpLink = tempfile.mktemp(dir=args.out_dir) + + os.symlink(target, tmpLink) + os.rename(tmpLink, os.path.join(args.out_dir, link_name)) + + diff --git a/simulations/airshower_beacon_simulation/matrix_base/make_matrix.py b/airshower_beacon_simulation/matrix_base/bin/make_matrix.py similarity index 68% rename from simulations/airshower_beacon_simulation/matrix_base/make_matrix.py rename to airshower_beacon_simulation/matrix_base/bin/make_matrix.py index 5f7d3fc..cff2240 100755 --- a/simulations/airshower_beacon_simulation/matrix_base/make_matrix.py +++ b/airshower_beacon_simulation/matrix_base/bin/make_matrix.py @@ -17,10 +17,17 @@ for options in product(baselines, clock_devs, noise_sigmas, trace_lengths): # Make directory if path.exists(dirname): - print(f"{dirname} already exists! continuing..") + print(f"{dirname} already exists! continuing anyway..") os.makedirs(dirname, exist_ok=True) + # Soft link clock file if available + if True: + os.makedirs(path.join(dirname, 'data'), exist_ok=True) + + if not path.isfile(path.join(dirname, 'data/clocks.csv')): + os.symlink(f'../../c{clk_dev}_clocks.csv', path.join(dirname, 'data/clocks.csv')) + # Setup config.mk with open(path.join(dirname, 'env.sh'), 'w') as fp: template = f""" diff --git a/airshower_beacon_simulation/matrix_base/bin/time_res_vs_snr.py b/airshower_beacon_simulation/matrix_base/bin/time_res_vs_snr.py new file mode 100755 index 0000000..c230163 --- /dev/null +++ b/airshower_beacon_simulation/matrix_base/bin/time_res_vs_snr.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 + + +import numpy as np +import matplotlib.pyplot as plt +import json + +from scipy import special + + +# Mimic import aa_generate_beacon as beacon +beacon = lambda: None + +def read_snr_file(fname): + with open(fname, 'r') as fp: + return json.load(fp) + +def read_tx_file(fname): + with open(fname, 'r') as fp: + data = json.load(fp) + + f_beacon = data['f_beacon'] + tx = data['tx'] + + del data['f_beacon'] + del data['tx'] + + return tx, f_beacon, data + + +beacon.snr_fname = 'snr.json' +beacon.tx_fname = 'tx.json' +beacon.read_snr_file = read_snr_file +beacon.read_tx_file = read_tx_file + +def read_snr_directories(data_directories, tx_fname=beacon.tx_fname, snr_fname=beacon.snr_fname, phase_res_fname='phase_time_residuals.json'): + # Read in snr + snrs = np.zeros(len(data_directories), dtype=float) + time_residuals = np.zeros( (len(snrs), 2), dtype=float) + + + tx, f_beacon, _ = beacon.read_tx_file(path.join(data_directories[0], tx_fname)) + for i, data_dir in enumerate(data_directories): + # Read SNR file + snr_data = read_snr_file(path.join(data_dir, snr_fname)) + + # Open antennas file + with open(path.join(data_dir, phase_res_fname), 'r') as fp: + time_res_data = json.load(fp) + + snrs[i] = snr_data['mean'] + + time_residuals[i] = time_res_data['mean'], time_res_data['std'] + + return f_beacon, snrs, time_residuals + + +## Math +def expectation(x, pdfx): + dx = x[1] - x[0] + return np.sum(x*pdfx*dx) + +def variance(x, pdfx): + mu = expectation(x, pdfx) + dx = x[1] - x[0] + return np.sum(x**2 *pdfx*dx) - mu**2 + +def phase_distribution(theta, sigma, s): + k = s/sigma + ct = np.cos(theta) + st = np.sin(theta) + pipi = 2*np.pi + + return (np.exp(-k**2/2)/pipi) \ + + ( + (pipi**-0.5)*k*np.exp(-(k*st)**2/2) + * ((1.+special.erf(k*ct*2**-0.5))*ct/2) + ) + +def phase_distribution_gauss(theta, sigma, s): + k = s/sigma + return 1/np.sqrt(2*np.pi) * k *np.exp( -(k*theta)**2/2) + + + +if __name__ == "__main__": + from os import path + import sys + + import os + import matplotlib + if os.name == 'posix' and "DISPLAY" not in os.environ: + matplotlib.use('Agg') + + from argparse import ArgumentParser + parser = ArgumentParser() + + figsize = (12,8) + + # Multiple Data Directories + parser.add_argument("-d", dest='data_directories', default=[], nargs='*') + + # Whether to show plots + group1 = parser.add_mutually_exclusive_group(required=False) + group1.add_argument('--show-plots', action='store_true', default=True, help='Default: %(default)s') + group1.add_argument('--no-show-plots', dest='show-plots', action='store_false') + + # Figures directory + parser.add_argument('--fig-dir', type=str, default='./figures', help='Set None to disable saving figures. (Default: %(default)s)') + + args = parser.parse_args() + + show_plots = args.show_plots + + if not args.data_directories: + parser.error("At least one data directory should be specified.") + + f_beacon, snrs, time_residuals = read_snr_directories(args.data_directories) + + phase2time = lambda x: x/(2*np.pi*f_beacon) + time2phase = lambda x: 2*np.pi*x*f_beacon + + fig, ax = plt.subplots(figsize=(8,6)) + ax.set_title("Beacon ({:.2f}MHz) Simulation".format(f_beacon*1e3)) + ax.set_xlabel('Signal to Noise ratio') + ax.set_ylabel('Time Accuracy $\\sigma(t)$ [ns]') + + # group per directories per N + if True: + import re + + dirdict = {} + N_re = re.compile(r'_N(\d+)_') + + for d in args.data_directories: + m = N_re.findall(d)[0] + + if m not in dirdict: + dirdict[m] = [] + + dirdict[m].append(d) + + + dirlist = dirdict.values() + + # plot data directories as one group + else: + dirlist = [args.data_directories] + + for dirlist in dirlist: + f_beacon, snrs, time_residuals = read_snr_directories(dirlist) + + # plot data + ax.plot(snrs*np.sqrt(np.pi/2), phase2time(time_residuals[:,1]), ls='none', marker='o') + + # Add secondary axis + if True: + if False and secondary_axis == 'time': + functions = (phase2time, time2phase) + label = 'Time Accuracy $\\sigma_t\\varphi/(2\\pi f_{beac})$ [ns]' + else: + functions = (time2phase, phase2time) + label = 'Phase Accuracy $\\sigma_\\varphi$ [rad]' + + secax = ax.secondary_yaxis('right', functions=functions) + secax.set_ylabel(label) + + # logscales + if True: + ax.set_xscale('log') + ax.set_yscale('log') + + # plot phasor snr + if True: + thetas = np.linspace(-np.pi, np.pi, 500) + sigma = 1 + _snr_min = min(10, min(snrs)) + _snr_max = min(100, max(snrs)) + + if ax.get_xscale() == 'log': #log + _snrs = np.logspace(np.log10(_snr_min), np.log10(_snr_max)) + else: #linear + _snrs = np.linspace(_snr_min, _snr_max) + + # Phasor Rice + phasor_pdfs = np.array([phase_distribution(thetas, sigma, s) for s in _snrs ]) + phasor_sigma = np.sqrt(np.array([variance(thetas, pdf) for pdf in phasor_pdfs])) + + ax.plot(_snrs, phase2time(phasor_sigma), label='Rice') + + if True: # plot a pdf + fig2, ax2 = plt.subplots() + for idx in [0, len(_snrs)//4, len(_snrs)//2, -1]: + ax2.plot(thetas, phasor_pdfs[idx], label='s = {:.1f}'.format(_snrs[idx])) + ax2.set_xlabel('$\\theta$') + ax2.set_ylabel('$p(\\theta)$') + ax2.legend() + + # Gauss Phasor + phasor_pdfs = np.array([phase_distribution_gauss(thetas, sigma, s) for s in _snrs ]) + phasor_sigma = np.sqrt(np.array([variance(thetas, pdf) for pdf in phasor_pdfs])) + + ax.plot(_snrs, phase2time(phasor_sigma), label='Gauss') + ax.legend() + + # Set limit on x values + if not True or ax.get_xscale() != 'log': + ax.set_xlim(0, 100) + + if args.fig_dir: + fig.tight_layout() + fig.savefig(path.join(args.fig_dir, "time_res_vs_snr.pdf")) + + if args.show_plots: + plt.show() + + diff --git a/simulations/airshower_beacon_simulation/matrix_base/run_matrix.sh b/airshower_beacon_simulation/matrix_base/run_matrix.sh similarity index 100% rename from simulations/airshower_beacon_simulation/matrix_base/run_matrix.sh rename to airshower_beacon_simulation/matrix_base/run_matrix.sh diff --git a/simulations/airshower_beacon_simulation/reconstruction.py b/airshower_beacon_simulation/reconstruction.py similarity index 100% rename from simulations/airshower_beacon_simulation/reconstruction.py rename to airshower_beacon_simulation/reconstruction.py diff --git a/simulations/airshower_beacon_simulation/scriptlib.py b/airshower_beacon_simulation/scriptlib.py similarity index 100% rename from simulations/airshower_beacon_simulation/scriptlib.py rename to airshower_beacon_simulation/scriptlib.py diff --git a/simulations/airshower_beacon_simulation/show_beacon_amplitude_antennas.py b/airshower_beacon_simulation/show_beacon_amplitude_antennas.py similarity index 100% rename from simulations/airshower_beacon_simulation/show_beacon_amplitude_antennas.py rename to airshower_beacon_simulation/show_beacon_amplitude_antennas.py diff --git a/simulations/airshower_beacon_simulation/view_beaconed_antenna.py b/airshower_beacon_simulation/view_beaconed_antenna.py similarity index 100% rename from simulations/airshower_beacon_simulation/view_beaconed_antenna.py rename to airshower_beacon_simulation/view_beaconed_antenna.py diff --git a/simulations/airshower_beacon_simulation/view_orig_ant0.py b/airshower_beacon_simulation/view_orig_ant0.py similarity index 100% rename from simulations/airshower_beacon_simulation/view_orig_ant0.py rename to airshower_beacon_simulation/view_orig_ant0.py diff --git a/simulations/airshower_beacon_simulation/bb_measure_clock_phase.py b/simulations/airshower_beacon_simulation/bb_measure_clock_phase.py deleted file mode 100755 index a83f717..0000000 --- a/simulations/airshower_beacon_simulation/bb_measure_clock_phase.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -# vim: fdm=indent ts=4 - -import h5py -from itertools import combinations, zip_longest -import matplotlib.pyplot as plt -import numpy as np - -import aa_generate_beacon as beacon -import lib - -if __name__ == "__main__": - from os import path - import sys - - import os - import matplotlib - if os.name == 'posix' and "DISPLAY" not in os.environ: - matplotlib.use('Agg') - - from scriptlib import MyArgumentParser - parser = MyArgumentParser() - args = parser.parse_args() - - figsize = (12,8) - c_light = lib.c_light - show_plots = args.show_plots - - remove_absolute_phase_offset_first_antenna = True # takes precedence - remove_absolute_phase_offset_minimum = True - - #### - fname_dir = args.data_dir - antennas_fname = path.join(fname_dir, beacon.antennas_fname) - - fig_dir = args.fig_dir # set None to disable saving - - if not path.isfile(antennas_fname): - print("Antenna file cannot be found, did you try generating a beacon?") - sys.exit(1) - - # Read in antennas from file - f_beacon, tx, antennas = beacon.read_beacon_hdf5(antennas_fname) - - # Make sure at least one beacon has been identified - if not hasattr(antennas[0], 'beacon_info') or len(antennas[0].beacon_info) == 0: - print(f"No analysed beacon found for {antennas[0].name}, try running the beacon phase analysis script first.") - sys.exit(1) - - # - N_beacon_freqs = len(antennas[0].beacon_info) - - for freq_name in antennas[0].beacon_info.keys(): - beacon_phases = np.empty( (len(antennas)) ) - for i, ant in enumerate(antennas): - beacon_phases[i] = ant.beacon_info[freq_name]['beacon_phase'] - - f_beacon = antennas[0].beacon_info[freq_name]['freq'] - - clock_phases = lib.remove_antenna_geometry_phase(tx, antennas, f_beacon, beacon_phases, c_light=c_light) - - # Remove the phase from one antenna - # this is a free parameter - # (only required for absolute timing) - if remove_absolute_phase_offset_first_antenna or remove_absolute_phase_offset_minimum: - if remove_absolute_phase_offset_first_antenna: # just take the first phase - minimum_phase = clock_phases[0] - else: # take the minimum - minimum_phase = np.min(clock_phases, axis=-1) - - clock_phases -= minimum_phase - clock_phases = lib.phase_mod(clock_phases) - - # Save to antennas in file - with h5py.File(antennas_fname, 'a') as fp: - h5group = fp['antennas'] - - for i, ant in enumerate(antennas): - h5ant = fp['antennas'][ant.name] - - h5beacon_freq = h5ant['beacon_info'][freq_name] - h5beacon_freq.attrs['clock_phase'] = clock_phases[i] - - # Plot True Phases at their locations - if show_plots or fig_dir: - fig, ax = plt.subplots(figsize=figsize) - spatial_unit='m' - fig.suptitle('Clock phases\nf_beacon= {:2.0f}MHz'.format(f_beacon*1e3)) - - antenna_locs = list(zip(*[(ant.x, ant.y) for ant in antennas])) - ax.set_xlabel('x' if spatial_unit is None else 'x [{}]'.format(spatial_unit)) - ax.set_ylabel('y' if spatial_unit is None else 'y [{}]'.format(spatial_unit)) - scatter_kwargs = {} - scatter_kwargs['cmap'] = 'inferno' - #scatter_kwargs['vmin'] = -np.pi - #scatter_kwargs['vmax'] = +np.pi - color_label='$\\varphi(\\sigma_t)$ [rad]' - - sc = ax.scatter(*antenna_locs, c=clock_phases, **scatter_kwargs) - fig.colorbar(sc, ax=ax, label=color_label) - - if False: - for i, ant in enumerate(antennas): - ax.text(ant.x, ant.y, ant.name) - - if not True: - ax.plot(tx.x, tx.y, 'X', color='k', markeredgecolor='white') - - if fig_dir: - fig.savefig(path.join(fig_dir, path.basename(__file__) + f".F{freq_name}.pdf")) - - # Plot True Phases - Actual True Phases at their location - if show_plots or fig_dir: - fig, ax = plt.subplots(figsize=figsize) - fig.suptitle('Clock phase Residuals\nf_beacon={:2.0f}MHz'.format(f_beacon*1e3)) - - actual_clock_phases = np.array([ -2*np.pi*a.attrs['clock_offset']*f_beacon for a in antennas ]) - - # Modify actual_clock_phases, the same way as clock_phases - # was modified - if remove_absolute_phase_offset_first_antenna or remove_absolute_phase_offset_minimum: - if remove_absolute_phase_offset_first_antenna: # just take the first phase - minimum_phase = actual_clock_phases[0] - else: # take the minimum - minimum_phase = np.min(actual_clock_phases, axis=-1) - - actual_clock_phases -= minimum_phase - actual_clock_phases = lib.phase_mod(actual_clock_phases) - - clock_phase_residuals = lib.phase_mod(clock_phases - actual_clock_phases) - - antenna_locs = list(zip(*[(ant.x, ant.y) for ant in antennas])) - ax.set_xlabel('x' if spatial_unit is None else 'x [{}]'.format(spatial_unit)) - ax.set_ylabel('y' if spatial_unit is None else 'y [{}]'.format(spatial_unit)) - scatter_kwargs = {} - scatter_kwargs['cmap'] = 'inferno' - color_label='$\\Delta\\varphi(\\sigma_t) = \\varphi_{meas} - \\varphi_{true}$ [rad]' - - sc = ax.scatter(*antenna_locs, c=clock_phase_residuals, **scatter_kwargs) - fig.colorbar(sc, ax=ax, label=color_label) - - if fig_dir: - fig.savefig(path.join(fig_dir, path.basename(__file__) + f".residual.F{freq_name}.pdf")) - - print(f"True phases written to", antennas_fname) - - if show_plots: - plt.show() diff --git a/simulations/airshower_beacon_simulation/lib/earsim b/simulations/airshower_beacon_simulation/lib/earsim deleted file mode 160000 index a5abe36..0000000 --- a/simulations/airshower_beacon_simulation/lib/earsim +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a5abe360fa5210be6ab423ecf2b5f783ae45b675